diff --git a/gitup/update.py b/gitup/update.py index 1f2ee08..fb8e81f 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -22,6 +22,27 @@ INDENT1 = " " * 3 INDENT2 = " " * 7 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): """Read an attribute from git config.""" try: @@ -29,6 +50,12 @@ def _read_config(repo, attr): except exc.GitCommandError: 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): """Rebase the current HEAD of *repo* onto the branch *name*.""" print(" rebasing...", end="") @@ -59,6 +86,25 @@ def _merge(repo, name): else: 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, verbose=False): """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 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] + ":") active = repo.active_branch @@ -104,23 +133,19 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False, return 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} if branches: - stashed = repo.git.stash("--all") != "No local changes to save" ### TODO: don't do this unless actually necessary + stasher = _Stasher(repo) try: for branch in sorted(branches, key=lambda b: b.name): - _update_branch(branch) + _update_branch(repo, branch, merge, rebase, stasher) finally: active.checkout() - if stashed: - repo.git.stash("pop") + stasher.restore() def _update_subdirectories(path, long_name, update_args): """Update all subdirectories that are git repos in a given directory."""