def _reset_head(self, branch=None): """Reset head of repo, discarding changes :param Optional[str] branch: Branch to reset head to """ if branch is None: try: self.repo.head.reset(index=True, working_tree=True) except GitError as err: ref_output = fmt.ref_string('HEAD') message = colored(' - Failed to reset ', 'red') + ref_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit() else: return 0 try: self.repo.git.reset('--hard', branch) except GitError as err: branch_output = fmt.ref_string(branch) message = colored(' - Failed to reset to ', 'red') + branch_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit() else: return 0
def prune_branch_local(self, branch, force): """Prune branch in repository""" branch_output = fmt.ref_string(branch) if branch not in self.repo.heads: self._print(' - Local branch ' + branch_output + " doesn't exist") return prune_branch = self.repo.heads[branch] if self.repo.head.ref == prune_branch: ref_output = fmt.ref_string(self.truncate_ref(self.default_ref)) try: self._print(' - Checkout ref ' + ref_output) self.repo.git.checkout(self.truncate_ref(self.default_ref)) except GitError as err: message = colored(' - Failed to checkout ref', 'red') + ref_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit() try: self._print(' - Delete local branch ' + branch_output) self.repo.delete_head(branch, force=force) return except GitError as err: message = colored(' - Failed to delete local branch ', 'red') + branch_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit()
def start(self, remote, branch, depth, tracking): """Start new branch in repository :param str remote: Remote name :param str branch: Local branch name to create :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer :param bool tracking: Whether to create a remote branch with tracking relationship """ if branch not in self.repo.heads: if not is_offline(): self.fetch(remote, ref=branch, depth=depth) try: self._create_branch_local(branch) self._checkout_branch_local(branch) except ClowderGitError: self._exit() else: print(' - ' + fmt.ref_string(branch) + ' already exists') if self._is_branch_checked_out(branch): print(' - On correct branch') else: try: self._checkout_branch_local(branch) except ClowderGitError: self._exit() if tracking and not is_offline(): self._create_branch_remote_tracking(branch, remote, depth)
def _set_tracking_branch_commit(self, branch, remote, depth): """Set tracking relationship between local and remote branch if on same commit :param str branch: Branch name :param str remote: Remote name :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer """ branch_output = fmt.ref_string(branch) origin = self._remote(remote) self.fetch(remote, depth=depth, ref=branch) if not self.existing_local_branch(branch): message = colored(' - No local branch ', 'red') + branch_output + '\n' self._print(message) self._exit(message) if not self.existing_remote_branch(branch, remote): message = colored(' - No remote branch ', 'red') + branch_output + '\n' self._print(message) self._exit(message) local_branch = self.repo.heads[branch] remote_branch = origin.refs[branch] if local_branch.commit != remote_branch.commit: message_1 = colored(' - Existing remote branch ', 'red') message_2 = colored(' on different commit', 'red') message = message_1 + branch_output + message_2 + '\n' self._print(message) self._exit(message_1) self._set_tracking_branch(remote, branch)
def _set_tracking_branch(self, remote, branch, remove_dir=False): """Set tracking branch .. py:function:: _set_tracking_branch(remote, branch, remove_dir=False) :param str remote: Remote name :param str branch: Branch name :param Optional[bool] remove_dir: Whether to remove the directory if commands fail """ branch_output = fmt.ref_string(branch) remote_output = fmt.remote_string(remote) origin = self._remote(remote) try: local_branch = self.repo.heads[branch] remote_branch = origin.refs[branch] self._print(' - Set tracking branch ' + branch_output + ' -> ' + remote_output + ' ' + branch_output) local_branch.set_tracking_branch(remote_branch) except GitError as err: message = colored(' - Failed to set tracking branch ', 'red') + branch_output if remove_dir: remove_directory(self.repo_path) self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): if remove_dir: remove_directory(self.repo_path) self._exit()
def _checkout_branch_local(self, branch, remove_dir=False): """Checkout local branch .. py:function:: _checkout_branch_local(branch, remove_dir=False) :param str branch: Branch name :param Optional[bool] remove_dir: Whether to remove the directory if commands fail """ branch_output = fmt.ref_string(branch) try: self._print(' - Checkout branch ' + branch_output) default_branch = self.repo.heads[branch] default_branch.checkout() except GitError as err: if remove_dir: remove_directory(self.repo_path) message = colored(' - Failed to checkout branch ', 'red') self._print(message + branch_output) self._print(fmt.error(err)) self._exit( fmt.parallel_exception_error(self.repo_path, message, branch_output)) except (KeyboardInterrupt, SystemExit): if remove_dir: remove_directory(self.repo_path) self._exit()
def sync(self, fork_remote, rebase=False): """Sync fork with upstream remote .. py:function:: sync(fork_remote, rebase=False) :param str fork_remote: Fork remote name :param Optional[bool] rebase: Whether to use rebase instead of pulling latest changes. """ self._print(' - Sync fork with upstream remote') if ref_type(self.default_ref) != 'branch': message = colored(' - Can only sync branches', 'red') self._print(message) self._exit(message) fork_remote_output = fmt.remote_string(fork_remote) branch_output = fmt.ref_string(truncate_ref(self.default_ref)) if rebase: self._rebase_remote_branch(self.remote, truncate_ref(self.default_ref)) else: self._pull(self.remote, truncate_ref(self.default_ref)) self._print(' - Push to ' + fork_remote_output + ' ' + branch_output) command = ['git', 'push', fork_remote, truncate_ref(self.default_ref)] try: execute_command(command, self.repo_path, print_output=self._print_output) except ClowderError: message = colored(' - Failed to push to ', 'red') + fork_remote_output + ' ' + branch_output self._print(message) self._print(fmt.command_failed_error(command)) self._exit(message)
def _create_branch_remote_tracking(self, branch, remote, depth): """Create remote tracking branch :param str branch: Branch name :param str remote: Remote name :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer """ branch_output = fmt.ref_string(branch) self.fetch(remote, depth=depth, ref=branch) if branch in self._remote(remote).refs: self._print_existing_remote_branch_message(branch) return try: self._print(' - Push remote branch ' + branch_output) self.repo.git.push(remote, branch) self._set_tracking_branch(remote, branch) except GitError as err: message = colored(' - Failed to push remote branch ', 'red') + branch_output self._print(message) self._print(fmt.error(err)) self._exit(fmt.error(err)) except (KeyboardInterrupt, SystemExit): self._exit()
def checkout(self, truncated_ref, allow_failure=False): """Checkout git ref .. py:function:: checkout(truncated_ref, allow_failure=False) :param str truncated_ref: Ref to git checkout :param Optional[bool] allow_failure: Whether to allow failing to checkout branch """ ref_output = fmt.ref_string(truncated_ref) try: self._print(' - Check out ' + ref_output) if self._print_output: print(self.repo.git.checkout(truncated_ref)) return self.repo.git.checkout(truncated_ref) except GitError as err: message = colored(' - Failed to checkout ', 'red') self._print(message + ref_output) if allow_failure: return self._print(fmt.error(err)) self._exit(fmt.parallel_exception_error(self.repo_path, message, ref_output)) except (KeyboardInterrupt, SystemExit): self._exit()
def _checkout_new_repo_commit(self, commit, remote, depth): """Checkout commit or fail and delete repo if it doesn't exist :param str commit: Commit sha :param str remote: Remote name :param int depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer """ commit_output = fmt.ref_string(commit) self._remote(remote, remove_dir=True) self.fetch(remote, depth=depth, ref=commit, remove_dir=True) self._print(' - Checkout commit ' + commit_output) try: self.repo.git.checkout(commit) except GitError as err: remove_directory(self.repo_path) message = colored(' - Failed to checkout commit ', 'red') self._print(message + commit_output) self._print(fmt.error(err)) self._exit( fmt.parallel_exception_error(self.repo_path, message, commit_output)) except (KeyboardInterrupt, SystemExit): remove_directory(self.repo_path) self._exit()
def start(self, remote, branch, depth, tracking): """Start new branch in repository""" if branch not in self.repo.heads: if not is_offline(): return_code = self.fetch(remote, ref=branch, depth=depth) if return_code != 0: sys.exit(1) return_code = self._create_branch_local(branch) if return_code != 0: self._exit('', return_code=return_code) return_code = self._checkout_branch_local(branch) if return_code != 0: self._exit('', return_code=return_code) else: branch_output = fmt.ref_string(branch) print(' - ' + branch_output + ' already exists') correct_branch = self._is_branch_checked_out(branch) if correct_branch: print(' - On correct branch') else: return_code = self._checkout_branch_local(branch) if return_code != 0: self._exit('', return_code=return_code) if tracking and not is_offline(): self._create_branch_remote_tracking(branch, remote, depth)
def _set_tracking_branch_commit(self, branch, remote, depth): """Set tracking relationship between local and remote branch if on same commit""" branch_output = fmt.ref_string(branch) origin = self._remote(remote) return_code = self.fetch(remote, depth=depth, ref=branch) if return_code != 0: raise ClowderGitError(msg=colored(' - Failed to fech', 'red')) if not self.existing_local_branch(branch): message = colored(' - No local branch ', 'red') + branch_output + '\n' self._print(message) self._exit(message) if not self.existing_remote_branch(branch, remote): message = colored(' - No remote branch ', 'red') + branch_output + '\n' self._print(message) self._exit(message) local_branch = self.repo.heads[branch] remote_branch = origin.refs[branch] if local_branch.commit != remote_branch.commit: message_1 = colored(' - Existing remote branch ', 'red') message_2 = colored(' on different commit', 'red') message = message_1 + branch_output + message_2 + '\n' self._print(message) self._exit(message_1) return_code = self._set_tracking_branch(remote, branch) if return_code != 0: self._exit(colored(' - Failed to set tracking branch', 'red'))
def sync(self, fork_remote, rebase=False): """Sync fork with upstream remote""" self._print(' - Sync fork with upstream remote') if self.ref_type(self.default_ref) != 'branch': message = colored(' - Can only sync branches', 'red') self._print(message) self._exit(message) fork_remote_output = fmt.remote_string(fork_remote) branch_output = fmt.ref_string(self.truncate_ref(self.default_ref)) if rebase: self._rebase_remote_branch(self.remote, self.truncate_ref(self.default_ref)) else: self._pull(self.remote, self.truncate_ref(self.default_ref)) self._print(' - Push to ' + fork_remote_output + ' ' + branch_output) command = [ 'git', 'push', fork_remote, self.truncate_ref(self.default_ref) ] return_code = execute_command(command, self.repo_path, print_output=self.print_output) if return_code != 0: message = colored(' - Failed to push to ', 'red') + fork_remote_output + ' ' + branch_output self._print(message) self._print(fmt.command_failed_error(command)) self._exit(message)
def _checkout_tag(self, tag): """Checkout commit tag is pointing to""" tag_output = fmt.ref_string(tag) if tag not in self.repo.tags: self._print(' - No existing tag ' + tag_output) return 1 try: same_commit = self.repo.head.commit == self.repo.tags[tag].commit is_detached = self.repo.head.is_detached if same_commit and is_detached: self._print(' - On correct commit for tag') return 0 self._print(' - Checkout tag ' + tag_output) self.repo.git.checkout('refs/tags/' + tag) return 0 except (GitError, ValueError) as err: message = colored(' - Failed to checkout tag ', 'red') self._print(message + tag_output) self._print(fmt.error(err)) self._exit( fmt.parallel_exception_error(self.repo_path, message, tag_output)) except (KeyboardInterrupt, SystemExit): self._exit()
def _set_tracking_branch(self, remote, branch, remove_dir=False): """Set tracking branch""" branch_output = fmt.ref_string(branch) remote_output = fmt.remote_string(remote) origin = self._remote(remote) try: local_branch = self.repo.heads[branch] remote_branch = origin.refs[branch] self._print(' - Set tracking branch ' + branch_output + ' -> ' + remote_output + ' ' + branch_output) local_branch.set_tracking_branch(remote_branch) return 0 except GitError as err: message = colored(' - Failed to set tracking branch ', 'red') + branch_output if remove_dir: remove_directory(self.repo_path) self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): if remove_dir: remove_directory(self.repo_path) self._exit()
def _checkout_branch(self, branch): """Checkout local branch or print message if already checked out .. py:function:: _checkout_branch(branch) :param str branch: Branch name """ if self._is_branch_checked_out(branch): self._print(' - Branch ' + fmt.ref_string(branch) + ' already checked out') else: self._checkout_branch_local(branch)
def _create_branch_local(self, branch): """Create local branch""" branch_output = fmt.ref_string(branch) try: self._print(' - Create branch ' + branch_output) self.repo.create_head(branch) return 0 except GitError as err: message = colored(' - Failed to create branch ', 'red') self._print(message + branch_output) self._print(fmt.error(err)) return 1 except (KeyboardInterrupt, SystemExit): self._exit()
def _is_tracking_branch(self, branch): """Check if branch is a tracking branch""" branch_output = fmt.ref_string(branch) try: local_branch = self.repo.heads[branch] tracking_branch = local_branch.tracking_branch() return True if tracking_branch else False except GitError as err: message = colored(' - No existing branch ', 'red') + branch_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit()
def _checkout_new_repo_tag(self, tag, remote, depth, remove_dir=False): """Checkout tag or fail and delete repo if it doesn't exist""" tag_output = fmt.ref_string(tag) self._remote(remote, remove_dir=remove_dir) self.fetch(remote, depth=depth, ref='refs/tags/' + tag, remove_dir=remove_dir) try: remote_tag = self.repo.tags[tag] except (GitError, IndexError): message = ' - No existing tag ' if remove_dir: remove_directory(self.repo_path) self._print(colored(message, 'red') + tag_output) self._exit( fmt.parallel_exception_error(self.repo_path, colored(message, 'red'), tag_output)) if self.print_output: self._print(message + tag_output) return 1 except (KeyboardInterrupt, SystemExit): if remove_dir: remove_directory(self.repo_path) self._exit() else: try: self._print(' - Checkout tag ' + tag_output) self.repo.git.checkout(remote_tag) return 0 except GitError as err: message = colored(' - Failed to checkout tag ', 'red') self._print(message + tag_output) self._print(fmt.error(err)) if remove_dir: remove_directory(self.repo_path) self._exit( fmt.parallel_exception_error(self.repo_path, message, tag_output)) return 1 except (KeyboardInterrupt, SystemExit): if remove_dir: remove_directory(self.repo_path) self._exit()
def _herd_branch_initial(self, url, branch, depth=0): """Herd branch initial""" self._init_repo() self._create_remote(self.remote, url, remove_dir=True) self.fetch(self.remote, depth=depth, ref=branch) if not self.existing_remote_branch(branch, self.remote): remote_output = fmt.remote_string(self.remote) self._print(' - No existing remote branch ' + remote_output + ' ' + fmt.ref_string(branch)) self._herd_initial(url, depth=depth) return self._create_branch_local_tracking(branch, self.remote, depth=depth, fetch=False, remove_dir=True)
def herd_branch(self, url, branch, **kwargs): """Herd branch .. py:function:: herd_branch(url, branch, depth=0, fork_remote=None, rebase=False) :param str url: URL of repo :param str branch: Branch name Keyword Args: depth (int): Git clone depth. 0 indicates full clone, otherwise must be a positive integer fork_remote (str): Fork remote name rebase (bool): Whether to use rebase instead of pulling latest changes """ depth = kwargs.get('depth', 0) rebase = kwargs.get('rebase', False) fork_remote = kwargs.get('fork_remote', None) if not existing_git_repository(self.repo_path): self._herd_branch_initial(url, branch, depth=depth) return branch_output = fmt.ref_string(branch) branch_ref = 'refs/heads/' + branch if self.existing_local_branch(branch): self._herd_branch_existing_local(branch, depth=depth, rebase=rebase, fork_remote=fork_remote) return self.fetch(self.remote, depth=depth, ref=branch_ref, allow_failure=True) if self.existing_remote_branch(branch, self.remote): self._herd(self.remote, branch_ref, depth=depth, fetch=False, rebase=rebase) return remote_output = fmt.remote_string(self.remote) self._print(' - No existing remote branch ' + remote_output + ' ' + branch_output) if fork_remote: self.fetch(fork_remote, depth=depth, ref=branch_ref) if self.existing_remote_branch(branch, fork_remote): self._herd(fork_remote, branch_ref, depth=depth, fetch=False, rebase=rebase) return remote_output = fmt.remote_string(fork_remote) self._print(' - No existing remote branch ' + remote_output + ' ' + branch_output) fetch = depth != 0 self.herd(url, depth=depth, fetch=fetch, rebase=rebase)
def prune_branch_remote(self, branch, remote): """Prune remote branch in repository""" branch_output = fmt.ref_string(branch) if not self.existing_remote_branch(branch, remote): self._print(' - Remote branch ' + branch_output + " doesn't exist") return try: self._print(' - Delete remote branch ' + branch_output) self.repo.git.push(remote, '--delete', branch) except GitError as err: message = colored(' - Failed to delete remote branch ', 'red') + branch_output self._print(message) self._print(fmt.error(err)) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit()
def _create_branch_local(self, branch): """Create local branch :param str branch: Branch name :raise ClowderGitError: """ branch_output = fmt.ref_string(branch) try: self._print(' - Create branch ' + branch_output) self.repo.create_head(branch) except GitError as err: message = colored(' - Failed to create branch ', 'red') self._print(message + branch_output) self._print(fmt.error(err)) raise ClowderGitError(err) except (KeyboardInterrupt, SystemExit): self._exit()
def fetch(self, remote, **kwargs): """Fetch from a specific remote ref .. py:function:: fetch(remote, ref=None, depth=0, remove_dir=False, allow_failure=False) :param str remote: Remote name Keyword Args: ref (str): Ref to fetch depth (int): Git clone depth. 0 indicates full clone, otherwise must be a positive integer remove_dir (bool): Whether to remove the directory if commands fail allow_failure (bool): Whether to allow failure """ ref = kwargs.get('ref', None) depth = kwargs.get('depth', 0) remove_dir = kwargs.get('remove_dir', False) allow_failure = kwargs.get('allow_failure', False) remote_output = fmt.remote_string(remote) if depth == 0: self._print(' - Fetch from ' + remote_output) message = colored(' - Failed to fetch from ', 'red') error = message + remote_output command = ['git fetch', remote, '--prune --tags'] elif ref is None: command = ['git fetch', remote, '--depth', str(depth), '--prune --tags'] message = colored(' - Failed to fetch remote ', 'red') error = message + remote_output else: ref_output = fmt.ref_string(truncate_ref(ref)) self._print(' - Fetch from ' + remote_output + ' ' + ref_output) message = colored(' - Failed to fetch from ', 'red') error = message + remote_output + ' ' + ref_output command = ['git fetch', remote, truncate_ref(ref), '--depth', str(depth), '--prune --tags'] try: execute_command(command, self.repo_path, print_output=self._print_output) except ClowderError: if remove_dir: remove_directory(self.repo_path) if not allow_failure: self._print(error) self._exit(error)
def checkout(self, truncated_ref): """Checkout git ref""" ref_output = fmt.ref_string(truncated_ref) try: self._print(' - Check out ' + ref_output) if self.print_output: print(self.repo.git.checkout(truncated_ref)) return self.repo.git.checkout(truncated_ref) except GitError as err: message = colored(' - Failed to checkout ref ', 'red') self._print(message + ref_output) self._print(fmt.error(err)) self._exit( fmt.parallel_exception_error(self.repo_path, message, ref_output)) except (KeyboardInterrupt, SystemExit): self._exit()
def _checkout_sha(self, sha): """Checkout commit by sha""" commit_output = fmt.ref_string(sha) try: if self.repo.head.commit.hexsha == sha: self._print(' - On correct commit') return 0 self._print(' - Checkout commit ' + commit_output) self.repo.git.checkout(sha) except GitError as err: message = colored(' - Failed to checkout commit ', 'red') self._print(message + commit_output) self._print(fmt.error(err)) self._exit( fmt.parallel_exception_error(self.repo_path, message, commit_output)) except (KeyboardInterrupt, SystemExit): self._exit()
def _herd_branch_initial(self, url, branch, depth=0): """Herd branch initial .. py:function:: _herd_branch_initial(url, branch, depth=0) :param str url: URL of repo :param str branch: Branch name to attempt to herd :param Optional[int] depth: Git clone depth. 0 indicates full clone, otherwise must be a positive integer """ self._init_repo() self._create_remote(self.remote, url, remove_dir=True) self.fetch(self.remote, depth=depth, ref=branch) if not self.existing_remote_branch(branch, self.remote): remote_output = fmt.remote_string(self.remote) self._print(' - No existing remote branch ' + remote_output + ' ' + fmt.ref_string(branch)) self._herd_initial(url, depth=depth) return self._create_branch_local_tracking(branch, self.remote, depth=depth, fetch=False, remove_dir=True)
def _print_existing_remote_branch_message(self, branch): """Print output message for existing remote branch :param str branch: Branch name """ branch_output = fmt.ref_string(branch) try: self.repo.git.config('--get', 'branch.' + branch + '.merge') except GitError: message = colored(' - Remote branch ', 'red') + branch_output + colored( ' already exists\n', 'red') self._print(message) self._exit(message) except (KeyboardInterrupt, SystemExit): self._exit() else: self._print(' - Tracking branch ' + branch_output + ' already exists')
def _pull(self, remote, branch): """Pull from remote branch""" if self.repo.head.is_detached: self._print(' - HEAD is detached') return branch_output = fmt.ref_string(branch) remote_output = fmt.remote_string(remote) self._print(' - Pull from ' + remote_output + ' ' + branch_output) command = ['git pull', remote, branch] return_code = execute_command(command, self.repo_path, print_output=self.print_output) if return_code != 0: message = colored(' - Failed to pull from ', 'red') + remote_output + ' ' + branch_output self._print(message) self._exit(message)
def _checkout_new_repo_branch(self, branch, depth): """Checkout remote branch or fail and delete repo if it doesn't exist""" branch_output = fmt.ref_string(branch) remote_output = fmt.remote_string(self.remote) self._remote(self.remote, remove_dir=True) self.fetch(self.remote, depth=depth, ref=branch, remove_dir=True) if not self.existing_remote_branch(branch, self.remote): remove_directory(self.repo_path) message = colored(' - No existing remote branch ', 'red') + remote_output + ' ' + branch_output self._print(message) self._exit(fmt.parallel_exception_error(self.repo_path, message)) self._create_branch_local_tracking(branch, self.remote, depth=depth, fetch=False, remove_dir=True)