| @@ -22,6 +22,27 @@ INDENT1 = " " * 3 | |||||
| INDENT2 = " " * 7 | INDENT2 = " " * 7 | ||||
| ERROR = RED + "Error:" + RESET | ERROR = RED + "Error:" + RESET | ||||
| class _Stasher(object): | |||||
| """Manages the stash state of a given repository.""" | |||||
| def __init__(self, repo): | |||||
| self._repo = repo | |||||
| self._clean = self._stashed = False | |||||
| def clean(self): | |||||
| """Ensure the working directory is clean, so we can do checkouts.""" | |||||
| if not self._clean: | |||||
| res = self._repo.git.stash("--all") | |||||
| self._clean = True | |||||
| if res != "No local changes to save": | |||||
| self._stashed = True | |||||
| def restore(self): | |||||
| """Restore the pre-stash state.""" | |||||
| if self._stashed: | |||||
| self._repo.git.stash("pop", "--index") | |||||
| def _read_config(repo, attr): | def _read_config(repo, attr): | ||||
| """Read an attribute from git config.""" | """Read an attribute from git config.""" | ||||
| try: | try: | ||||
| @@ -29,6 +50,12 @@ def _read_config(repo, attr): | |||||
| except exc.GitCommandError: | except exc.GitCommandError: | ||||
| return None | return None | ||||
| def _fetch_remote(remote): | |||||
| """Fetch a given remote, and display progress info along the way.""" | |||||
| print(INDENT2, "Fetching", remote.name, end="...") | |||||
| remote.fetch() ### TODO: show progress | |||||
| print(" done.") | |||||
| def _rebase(repo, name): | def _rebase(repo, name): | ||||
| """Rebase the current HEAD of *repo* onto the branch *name*.""" | """Rebase the current HEAD of *repo* onto the branch *name*.""" | ||||
| print(" rebasing...", end="") | print(" rebasing...", end="") | ||||
| @@ -59,6 +86,25 @@ def _merge(repo, name): | |||||
| else: | else: | ||||
| print(" done.") | print(" done.") | ||||
| def _update_branch(repo, branch, merge, rebase, stasher=None): | |||||
| """Update a single branch.""" | |||||
| print(INDENT2, "Updating", branch, end="...") | |||||
| upstream = branch.tracking_branch() | |||||
| if not upstream: | |||||
| print(" skipped: no upstream is tracked.") | |||||
| return | |||||
| if branch.commit == upstream.commit: ### TODO: a better check is possible | |||||
| print(" up to date.") | |||||
| return | |||||
| if stasher: | |||||
| stasher.clean() | |||||
| branch.checkout() | |||||
| config_attr = "branch.{0}.rebase".format(branch.name) | |||||
| if not merge and (rebase or _read_config(repo, config_attr)): | |||||
| _rebase(repo, upstream.name) | |||||
| else: | |||||
| _merge(repo, upstream.name) | |||||
| def _update_repository(repo, current_only=False, rebase=False, merge=False, | def _update_repository(repo, current_only=False, rebase=False, merge=False, | ||||
| verbose=False): | verbose=False): | ||||
| """Update a single git repository by fetching remotes and rebasing/merging. | """Update a single git repository by fetching remotes and rebasing/merging. | ||||
| @@ -71,23 +117,6 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False, | |||||
| cause us to always merge. If *verbose* is set, additional information is | cause us to always merge. If *verbose* is set, additional information is | ||||
| printed out for the user. | printed out for the user. | ||||
| """ | """ | ||||
| def _update_branch(branch): | |||||
| """Update a single branch.""" | |||||
| print(INDENT2, "Updating", branch, end="...") | |||||
| upstream = branch.tracking_branch() | |||||
| if not upstream: | |||||
| print(" skipped: no upstream is tracked.") | |||||
| return | |||||
| if branch.commit == upstream.commit: ### TODO: a better check is possible | |||||
| print(" up to date.") | |||||
| return | |||||
| branch.checkout() | |||||
| c_attr = "branch.{0}.rebase".format(branch.name) | |||||
| if not merge and (rebase or repo_rebase or _read_config(repo, c_attr)): | |||||
| _rebase(repo, upstream.name) | |||||
| else: | |||||
| _merge(repo, upstream.name) | |||||
| print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") | print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") | ||||
| active = repo.active_branch | active = repo.active_branch | ||||
| @@ -104,23 +133,19 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False, | |||||
| return | return | ||||
| for remote in remotes: | for remote in remotes: | ||||
| print(INDENT2, "Fetching", remote.name, end="...") | |||||
| remote.fetch() ### TODO: show progress | |||||
| print(" done.") | |||||
| repo_rebase = _read_config(repo, "pull.rebase") | |||||
| _fetch_remote(remote) | |||||
| _update_branch(active) | |||||
| rebase = rebase or _read_config(repo, "pull.rebase") | |||||
| _update_branch(repo, active, merge, rebase) | |||||
| branches = set(repo.heads) - {active} | branches = set(repo.heads) - {active} | ||||
| if branches: | if branches: | ||||
| stashed = repo.git.stash("--all") != "No local changes to save" ### TODO: don't do this unless actually necessary | |||||
| stasher = _Stasher(repo) | |||||
| try: | try: | ||||
| for branch in sorted(branches, key=lambda b: b.name): | for branch in sorted(branches, key=lambda b: b.name): | ||||
| _update_branch(branch) | |||||
| _update_branch(repo, branch, merge, rebase, stasher) | |||||
| finally: | finally: | ||||
| active.checkout() | active.checkout() | ||||
| if stashed: | |||||
| repo.git.stash("pop") | |||||
| stasher.restore() | |||||
| def _update_subdirectories(path, long_name, update_args): | def _update_subdirectories(path, long_name, update_args): | ||||
| """Update all subdirectories that are git repos in a given directory.""" | """Update all subdirectories that are git repos in a given directory.""" | ||||