From 8b0793436973d47dfe445a0aa610bbe630a25b98 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 30 Mar 2014 00:38:10 -0400 Subject: [PATCH] More work on GitPython integration. --- gitup/script.py | 6 ++-- gitup/update.py | 107 +++++++++++++++++++++++++++++++++++--------------------- 2 files changed, 70 insertions(+), 43 deletions(-) diff --git a/gitup/script.py b/gitup/script.py index 72f3d9b..8ed971a 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -41,7 +41,7 @@ def main(): rebase_or_merge.add_argument( '-r', '--rebase', action="store_true", help="""always rebase upstream branches instead of following `pull.rebase` and `branch..rebase` - in git config (like `git pull --rebase`)""") + in git config (like `git pull --rebase=preserve`)""") rebase_or_merge.add_argument( '-m', '--merge', action="store_true", help="""like --rebase, but merge instead""") @@ -82,10 +82,10 @@ def main(): list_bookmarks() acted = True if args.directories_to_update: - update_directories(args.directories_to_update, *update_args) + update_directories(args.directories_to_update, update_args) acted = True if args.update or not acted: - update_bookmarks(get_bookmarks(), *update_args) + update_bookmarks(get_bookmarks(), update_args) def run(): """Thin wrapper for main() that catches KeyboardInterrupts.""" diff --git a/gitup/update.py b/gitup/update.py index 2620016..b896061 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -20,46 +20,74 @@ RESET = Style.RESET_ALL INDENT1 = " " * 3 INDENT2 = " " * 7 +ERROR = RED + "Error:" + RESET -def _update_repository(repo, rebase=True): +def _read_config(repo, attr): + """Read an attribute from git config.""" + try: + return repo.git.config("--get", attr) + except exc.GitCommandError: + return None + +def _update_repository(repo, current_only=False, rebase=False, merge=False, + verbose=False): """Update a single git repository by fetching remotes and rebasing/merging. - The specific actions depends on ... + The specific actions depend on the arguments given. We will fetch all + remotes if *current_only* is ``False``, or only the remote tracked by the + current branch if ``True``. By default, we will merge unless + ``pull.rebase`` or ``branch..rebase`` is set in config; *rebase* will + cause us to always rebase with ``--preserve-merges``, and *merge* will + 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:", branch, end=" ") + upstream = branch.tracking_branch() + if not upstream: + print("Branch is not tracking any remote.") + continue + c_attr = "branch.{0}.rebase".format(branch.name) + if not merge and (rebase or repo_rebase or _read_config(repo, c_attr)): + ### TODO: rebase + else: + ### TODO: merge + print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") - ref = repo.head.ref.tracking_branch() - if ref: - remote = repo.remotes[ref.remote_name] - # else: - ### + active = repo.active_branch + if current_only: + ref = active.tracking_branch() + if not ref: + print(INDENT2, ERROR, "no remote tracked by current branch.") + return + remotes = [repo.remotes[ref.remote_name]] + else: + remotes = repo.remotes - remote.fetch() + for remote in remotes: + print(INDENT2, "Fetching remote:", remote.name) + remote.fetch() # TODO: show progress - if not repo.remotes: - print(INDENT2, RED + "Error:" + RESET, "no remotes configured.") - return - try: - repo = repo.remotes.origin - except AttributeError: - if len(repo.remotes) == 1: - repo = repo.remotes[0] - else: - print(INDENT2, RED + "Error:" + RESET, "ambiguous remotes:", - ", ".join(remote.name for remote in repo.remotes)) + repo_rebase = _read_config(repo, "pull.rebase") + _update_branch(active) + branches = set(repo.heads) - {active} + if branches: + stashed = repo.git.stash("--all") != "No local changes to save" + try: + for branch in sorted(branches, key=lambda b: b.name): + branch.checkout() + _update_branch(branch) + finally: + active.checkout() + if stashed: + repo.git.stash("pop") ##################################### try: - # Check if there is anything to pull, but don't do it yet: - dry_fetch = _exec_shell("git fetch --dry-run") - except subprocess.CalledProcessError: - print(INDENT2, RED + "Error:" + RESET, "cannot fetch;", - "do you have a remote repository configured correctly?") - return - - try: last_commit = _exec_shell("git log -n 1 --pretty=\"%ar\"") except subprocess.CalledProcessError: last_commit = "never" # Couldn't get a log, so no commits @@ -87,7 +115,7 @@ def _update_repository(repo, rebase=True): "you have uncommitted changes in this repository!") print(INDENT2, "Ignoring.") -def _update_subdirectories(path, long_name): +def _update_subdirectories(path, long_name, update_args): """Update all subdirectories that are git repos in a given directory.""" repos = [] for item in os.listdir(path): @@ -101,9 +129,9 @@ def _update_subdirectories(path, long_name): print(long_name[0].upper() + long_name[1:], "contains {0} git repositor{1}:".format(len(repos), suffix)) for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]): - _update_repository(repo) + _update_repository(repo, *update_args) -def _update_directory(path, is_bookmark=False): +def _update_directory(path, update_args, is_bookmark=False): """Update a particular directory. Determine whether the directory is a git repo on its own, a directory of @@ -117,29 +145,28 @@ def _update_directory(path, is_bookmark=False): try: repo = Repo(path) except exc.NoSuchPathError: - print(RED + "Error:" + RESET, long_name, "doesn't exist!") + print(ERROR, long_name, "doesn't exist!") except exc.InvalidGitRepositoryError: if os.path.isdir(path): - _update_subdirectories(path, long_name) + _update_subdirectories(path, long_name, update_args) else: - print(RED + "Error:" + RESET, long_name, "isn't a repository!") + print(ERROR, long_name, "isn't a repository!") else: long_name = (dir_type.capitalize() + ' "' + BOLD + repo.working_dir + RESET + '"') print(long_name, "is a git repository:") - _update_repository(repo) + _update_repository(repo, *update_args) -def update_bookmarks(bookmarks, current_only=False, rebase=False, merge=False, - verbose=False): +def update_bookmarks(bookmarks, update_args): """Loop through and update all bookmarks.""" if bookmarks: for path, name in bookmarks: - _update_directory(path, is_bookmark=True) + _update_directory(path, update_args, is_bookmark=True) else: print("You don't have any bookmarks configured! Get help with 'gitup -h'.") -def update_directories(paths, current_only=False, rebase=False, merge=False, - verbose=False): +def update_directories(paths, update_args): """Update a list of directories supplied by command arguments.""" for path in paths: - _update_directory(os.path.abspath(path), is_bookmark=False) + full_path = os.path.abspath(path) + _update_directory(full_path, update_args, is_bookmark=False)