|
|
@@ -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.""" |
|
|
|