From a4810c3c5c4eac0a2dedf2c76777d77e5eef1cef Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Wed, 7 Feb 2018 00:09:01 -0500 Subject: [PATCH 1/4] Traverse into subdirectories when searching for repos (fixes #42). --- CHANGELOG | 7 ++++ README.md | 9 ++--- gitup/__init__.py | 4 +-- gitup/update.py | 103 +++++++++++++++++++++++++++++++++--------------------- 4 files changed, 78 insertions(+), 45 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 71fb1cd..6ac8fae 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,10 @@ +v0.5 (unreleased): + +- Improved repository detection when passed a directory that contains repos: + will now traverse through subdirectories automatically. +- Fixed an error when updating branches if the upstream is completely unrelated + from the local branch (no common ancestor). + v0.4.1 (released December 13, 2017): - Bump dependencies to deal with newer versions of Git. diff --git a/README.md b/README.md index bfc125a..fee9762 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ Additionally, you can just type: gitup ~/repos -to automatically update all git repositories in that directory. +to automatically update all git repositories in that directory and its +subdirectories. To add a bookmark (or bookmarks), either of these will work: @@ -82,16 +83,16 @@ Update all git repositories in your current directory: gitup . By default, gitup will fetch all remotes in a repository. Pass `--current-only` -(or `-c`) to make it fetch _only_ the remote tracked by the current branch. +(or `-c`) to make it fetch only the remote tracked by the current branch. Also by default, gitup will try to fast-forward all branches that have upstreams configured. It will always skip branches where this is not possible (e.g. dirty working directory or a merge/rebase is required). Pass -`--fetch-only` (or `-f`) to only fetch remotes. +`--fetch-only` (or `-f`) to skip this step and only fetch remotes. After fetching, gitup will _keep_ remote-tracking branches that no longer exist upstream. Pass `--prune` (or `-p`) to delete them, or set `fetch.prune` or -`remote..prune` in git config to do this by default. +`remote..prune` in your git config to do this by default. For a full list of all command arguments and abbreviations: diff --git a/gitup/__init__.py b/gitup/__init__.py index f246034..6270d00 100644 --- a/gitup/__init__.py +++ b/gitup/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2017 Ben Kurtovic +# Copyright (C) 2011-2018 Ben Kurtovic # Released under the terms of the MIT License. See LICENSE for details. """ @@ -10,5 +10,5 @@ gitup: the git repository updater __author__ = "Ben Kurtovic" __copyright__ = "Copyright (C) 2011-2017 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.4.1" +__version__ = "0.5.dev0" __email__ = "ben.kurtovic@gmail.com" diff --git a/gitup/update.py b/gitup/update.py index 64991eb..1400f17 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2016 Ben Kurtovic +# Copyright (C) 2011-2018 Ben Kurtovic # Released under the terms of the MIT License. See LICENSE for details. from __future__ import print_function @@ -115,7 +115,12 @@ def _update_branch(repo, branch, is_active=False): print(YELLOW + "skipped:", "upstream does not exist.") return - base = repo.git.merge_base(branch.commit, upstream.commit) + try: + base = repo.git.merge_base(branch.commit, upstream.commit) + except exc.GitCommandError as err: + print(YELLOW + "skipped:", "can't find merge base with upstream.") + return + if repo.commit(base) == upstream.commit: print(BLUE + "up to date", end=".\n") return @@ -140,7 +145,7 @@ def _update_branch(repo, branch, is_active=False): repo.git.branch(branch.name, upstream.name, force=True) print(GREEN + "done", end=".\n") -def _update_repository(repo, current_only, fetch_only, prune): +def _update_repository(repo, repo_name, current_only, fetch_only, prune): """Update a single git repository by fetching remotes and rebasing/merging. The specific actions depend on the arguments given. We will fetch all @@ -150,7 +155,7 @@ def _update_repository(repo, current_only, fetch_only, prune): If *prune* is ``True``, remote-tracking branches that no longer exist on their remote after fetching will be deleted. """ - print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") + print(INDENT1, BOLD + repo_name + ":") try: active = repo.active_branch @@ -178,9 +183,9 @@ def _update_repository(repo, current_only, fetch_only, prune): for branch in sorted(repo.heads, key=lambda b: b.name): _update_branch(repo, branch, branch == active) -def _run_command(repo, command): +def _run_command(repo, repo_name, command): """Run an arbitrary shell command on the given repository.""" - print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") + print(INDENT1, BOLD + repo_name + ":") cmd = shlex.split(command) try: @@ -193,24 +198,7 @@ def _run_command(repo, command): for line in out[1].splitlines() + out[2].splitlines(): print(INDENT2, line) -def _dispatch_multi(base, paths, callback, *args): - """Apply the callback to all git repos in the list of paths.""" - valid = [] - for path in paths: - try: - Repo(path) - except (exc.InvalidGitRepositoryError, exc.NoSuchPathError): - continue - valid.append(path) - - base = os.path.abspath(base) - suffix = "" if len(valid) == 1 else "s" - print(BOLD + base, "({0} repo{1}):".format(len(valid), suffix)) - - for path in sorted(valid, key=os.path.basename): - callback(Repo(path), *args) - -def _dispatch(path, callback, *args): +def _dispatch(base_path, callback, *args): """Apply a callback function on each valid repo in the given path. Determine whether the directory is a git repo on its own, a directory of @@ -220,24 +208,61 @@ def _dispatch(path, callback, *args): The given args are passed directly to the callback function after the repo. """ - path = os.path.expanduser(path) + def _collect(paths, maxdepth=6): + """Return all valid repo paths in the given paths, recursively.""" + if maxdepth <= 0: + return [] + + valid = [] + for path in paths: + try: + Repo(path) + valid.append(path) + except exc.InvalidGitRepositoryError: + if not os.path.isdir(path): + continue + children = [os.path.join(path, it) for it in os.listdir(path)] + valid += _collect(children, maxdepth=maxdepth-1) + except exc.NoSuchPathError: + continue + return valid + + def _get_basename(base, path): + """Return a reasonable name for a repo path in the given base.""" + if path.startswith(base + os.path.sep): + return path.split(base + os.path.sep, 1)[1] + prefix = os.path.commonprefix([base, path]) + while not base.startswith(prefix + os.path.sep): + old = prefix + prefix = os.path.split(prefix)[0] + if prefix == old: + break # Prevent infinite loop, but should be almost impossible + return path.split(prefix + os.path.sep, 1)[1] + + base = os.path.expanduser(base_path) try: - repo = Repo(path) + Repo(base) + valid = [base] except exc.NoSuchPathError: - paths = glob(path) - if paths: - _dispatch_multi(path, paths, callback, *args) - else: - print(ERROR, BOLD + path, "doesn't exist!") + paths = glob(base) + if not paths: + print(ERROR, BOLD + base, "doesn't exist!") + return + valid = _collect(paths) except exc.InvalidGitRepositoryError: - if os.path.isdir(path): - paths = [os.path.join(path, item) for item in os.listdir(path)] - _dispatch_multi(path, paths, callback, *args) - else: - print(ERROR, BOLD + path, "isn't a repository!") - else: - print(BOLD + repo.working_dir, "(1 repo):") - callback(repo, *args) + if not os.path.isdir(base): + print(ERROR, BOLD + base, "isn't a repository!") + return + valid = _collect([base]) + + base = os.path.abspath(base) + suffix = "" if len(valid) == 1 else "s" + print(BOLD + base, "({0} repo{1}):".format(len(valid), suffix)) + + valid = [os.path.abspath(path) for path in valid] + paths = [(_get_basename(base, path), path) for path in valid] + for name, path in sorted(paths): + callback(Repo(path), name, *args) def update_bookmarks(bookmarks, update_args): """Loop through and update all bookmarks.""" From cc0254d60d66776244b87331af1a2a333f3e4c3c Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 18 Feb 2018 00:03:44 -0500 Subject: [PATCH 2/4] Add a --depth argument to control recursion depth. Cleanup. --- CHANGELOG | 12 +++++++++--- LICENSE | 2 +- README.md | 25 ++++++++++++++----------- gitup/__init__.py | 2 +- gitup/script.py | 19 +++++++++++-------- gitup/update.py | 54 +++++++++++++++++++++++++++++------------------------- setup.py | 14 ++++++++------ 7 files changed, 73 insertions(+), 55 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6ac8fae..716b0d1 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,7 +1,13 @@ v0.5 (unreleased): -- Improved repository detection when passed a directory that contains repos: - will now traverse through subdirectories automatically. +- Added a `--depth` flag to control recursion depth when searching for + repositories inside of subdirectories. For example: + - `--depth 0` will never recurse into subdirectories; the provided paths must + be repositories by themselves. + - `--depth 1` will descend one level to look for repositories. This is the + old behavior. + - `--depth 3` will look three levels deep. This is the new default. + - `--depth -1` will recurse indefinitely. This is not recommended. - Fixed an error when updating branches if the upstream is completely unrelated from the local branch (no common ancestor). @@ -20,7 +26,7 @@ v0.4 (released January 17, 2017): - Cleaned up the bookmark file format, fixing a related Windows bug. The script will automatically migrate to the new one. - Fixed a bug related to Python 3 compatibility. -- Fixed unicode support. +- Fixed Unicode support. v0.3 (released June 7, 2015): diff --git a/LICENSE b/LICENSE index 5ed060b..fa06940 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011-2017 Ben Kurtovic +Copyright (C) 2011-2018 Ben Kurtovic Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index fee9762..eefa4e2 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ __gitup__ (the _git-repo-updater_) -gitup is a tool designed to update a large number of git repositories at once. -It is smart enough to handle multiple remotes, branches, dirty working -directories, and more, hopefully providing a great way to get everything -up-to-date for short periods of internet access between long periods of none. +gitup is a tool for updating multiple git repositories at once. It is smart +enough to handle several remotes, dirty working directories, diverged local +branches, detached HEADs, and more. It was originally created to manage a large +collection of projects and deal with sporadic internet access. gitup should work on OS X, Linux, and Windows. You should have the latest version of git and either Python 2.7 or Python 3 installed. @@ -25,7 +25,7 @@ Then, to install for everyone: sudo python setup.py install -...or for just yourself (make sure you have `~/.local/bin` in your PATH): +or for just yourself (make sure you have `~/.local/bin` in your PATH): python setup.py install --user @@ -51,10 +51,9 @@ Additionally, you can just type: gitup ~/repos -to automatically update all git repositories in that directory and its -subdirectories. +to automatically update all git repositories in that directory. -To add a bookmark (or bookmarks), either of these will work: +To add bookmarks, either of these will work: gitup --add ~/repos/foo ~/repos/bar ~/repos/baz gitup --add ~/repos @@ -82,6 +81,13 @@ Update all git repositories in your current directory: gitup . +You can control how deep gitup will look for repositories in a given directory, +if that directory is not a git repo by itself, with the `--depth` (or `-t`) +option. `--depth 0` will disable recursion entirely, meaning the provided paths +must be repos by themselves. `--depth 1` will descend one level (this is the +old behavior from pre-0.5 gitup). `--depth -1` will recurse indefinitely, +which is not recommended. The default is `--depth 3`. + By default, gitup will fetch all remotes in a repository. Pass `--current-only` (or `-c`) to make it fetch only the remote tracked by the current branch. @@ -97,6 +103,3 @@ upstream. Pass `--prune` (or `-p`) to delete them, or set `fetch.prune` or For a full list of all command arguments and abbreviations: gitup --help - -Finally, all paths can be either absolute (e.g. `/path/to/repo`) or relative -(e.g. `../my/repo`). diff --git a/gitup/__init__.py b/gitup/__init__.py index 6270d00..2ef3e7d 100644 --- a/gitup/__init__.py +++ b/gitup/__init__.py @@ -8,7 +8,7 @@ gitup: the git repository updater """ __author__ = "Ben Kurtovic" -__copyright__ = "Copyright (C) 2011-2017 Ben Kurtovic" +__copyright__ = "Copyright (C) 2011-2018 Ben Kurtovic" __license__ = "MIT License" __version__ = "0.5.dev0" __email__ = "ben.kurtovic@gmail.com" diff --git a/gitup/script.py b/gitup/script.py index 8f0c2f7..b8dc344 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2016 Ben Kurtovic +# Copyright (C) 2011-2018 Ben Kurtovic # Released under the terms of the MIT License. See LICENSE for details. from __future__ import print_function @@ -39,12 +39,16 @@ def main(): group_u.add_argument( 'directories_to_update', nargs="*", metavar="path", type=_decode, - help="""update all repositories in this directory (or the directory - itself, if it is a repo)""") + help="""update this repository, or all repositories it contains + (if not a repo directly)""") group_u.add_argument( '-u', '--update', action="store_true", help="""update all bookmarks (default behavior when called without arguments)""") group_u.add_argument( + '-t', '--depth', dest="max_depth", metavar="n", type=int, default=3, + help="""max recursion depth when searching for repos in subdirectories + (default: 3; use 0 for no recursion, or -1 for unlimited)""") + group_u.add_argument( '-c', '--current-only', action="store_true", help="""only fetch the remote tracked by the current branch instead of all remotes""") group_u.add_argument( @@ -90,7 +94,6 @@ def main(): color_init(autoreset=True) args = parser.parse_args() - update_args = args.current_only, args.fetch_only, args.prune print(Style.BRIGHT + "gitup" + Style.RESET_ALL + ": the git-repo-updater") print() @@ -121,15 +124,15 @@ def main(): if args.command: if args.directories_to_update: - run_command(args.directories_to_update, args.command) + run_command(args.directories_to_update, args) if args.update or not args.directories_to_update: - run_command(get_bookmarks(args.bookmark_file), args.command) + run_command(get_bookmarks(args.bookmark_file), args) else: if args.directories_to_update: - update_directories(args.directories_to_update, update_args) + update_directories(args.directories_to_update, args) acted = True if args.update or not acted: - update_bookmarks(get_bookmarks(args.bookmark_file), update_args) + update_bookmarks(get_bookmarks(args.bookmark_file), args) def run(): """Thin wrapper for main() that catches KeyboardInterrupts.""" diff --git a/gitup/update.py b/gitup/update.py index 1400f17..5c73e68 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -145,15 +145,15 @@ def _update_branch(repo, branch, is_active=False): repo.git.branch(branch.name, upstream.name, force=True) print(GREEN + "done", end=".\n") -def _update_repository(repo, repo_name, current_only, fetch_only, prune): +def _update_repository(repo, repo_name, args): """Update a single git repository by fetching remotes and rebasing/merging. 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``. If *fetch_only* is ``False``, we will also - update all fast-forwardable branches that are tracking valid upstreams. - If *prune* is ``True``, remote-tracking branches that no longer exist on - their remote after fetching will be deleted. + remotes if *args.current_only* is ``False``, or only the remote tracked by + the current branch if ``True``. If *args.fetch_only* is ``False``, we will + also update all fast-forwardable branches that are tracking valid + upstreams. If *args.prune* is ``True``, remote-tracking branches that no + longer exist on their remote after fetching will be deleted. """ print(INDENT1, BOLD + repo_name + ":") @@ -161,7 +161,7 @@ def _update_repository(repo, repo_name, current_only, fetch_only, prune): active = repo.active_branch except TypeError: # Happens when HEAD is detached active = None - if current_only: + if args.current_only: if not active: print(INDENT2, ERROR, "--current-only doesn't make sense with a detached HEAD.") @@ -177,17 +177,17 @@ def _update_repository(repo, repo_name, current_only, fetch_only, prune): if not remotes: print(INDENT2, ERROR, "no remotes configured to fetch.") return - _fetch_remotes(remotes, prune) + _fetch_remotes(remotes, args.prune) - if not fetch_only: + if not args.fetch_only: for branch in sorted(repo.heads, key=lambda b: b.name): _update_branch(repo, branch, branch == active) -def _run_command(repo, repo_name, command): +def _run_command(repo, repo_name, args): """Run an arbitrary shell command on the given repository.""" print(INDENT1, BOLD + repo_name + ":") - cmd = shlex.split(command) + cmd = shlex.split(args.command) try: out = repo.git.execute( cmd, with_extended_output=True, with_exceptions=False) @@ -198,7 +198,7 @@ def _run_command(repo, repo_name, command): for line in out[1].splitlines() + out[2].splitlines(): print(INDENT2, line) -def _dispatch(base_path, callback, *args): +def _dispatch(base_path, callback, args): """Apply a callback function on each valid repo in the given path. Determine whether the directory is a git repo on its own, a directory of @@ -208,9 +208,9 @@ def _dispatch(base_path, callback, *args): The given args are passed directly to the callback function after the repo. """ - def _collect(paths, maxdepth=6): + def _collect(paths, max_depth): """Return all valid repo paths in the given paths, recursively.""" - if maxdepth <= 0: + if max_depth == 0: return [] valid = [] @@ -222,7 +222,7 @@ def _dispatch(base_path, callback, *args): if not os.path.isdir(path): continue children = [os.path.join(path, it) for it in os.listdir(path)] - valid += _collect(children, maxdepth=maxdepth-1) + valid += _collect(children, max_depth - 1) except exc.NoSuchPathError: continue return valid @@ -240,6 +240,10 @@ def _dispatch(base_path, callback, *args): return path.split(prefix + os.path.sep, 1)[1] base = os.path.expanduser(base_path) + max_depth = args.max_depth + if max_depth >= 0: + max_depth += 1 + try: Repo(base) valid = [base] @@ -248,12 +252,12 @@ def _dispatch(base_path, callback, *args): if not paths: print(ERROR, BOLD + base, "doesn't exist!") return - valid = _collect(paths) + valid = _collect(paths, max_depth) except exc.InvalidGitRepositoryError: - if not os.path.isdir(base): + if not os.path.isdir(base) or args.max_depth == 0: print(ERROR, BOLD + base, "isn't a repository!") return - valid = _collect([base]) + valid = _collect([base], max_depth) base = os.path.abspath(base) suffix = "" if len(valid) == 1 else "s" @@ -262,23 +266,23 @@ def _dispatch(base_path, callback, *args): valid = [os.path.abspath(path) for path in valid] paths = [(_get_basename(base, path), path) for path in valid] for name, path in sorted(paths): - callback(Repo(path), name, *args) + callback(Repo(path), name, args) -def update_bookmarks(bookmarks, update_args): +def update_bookmarks(bookmarks, args): """Loop through and update all bookmarks.""" if not bookmarks: print("You don't have any bookmarks configured! Get help with 'gitup -h'.") return for path in bookmarks: - _dispatch(path, _update_repository, *update_args) + _dispatch(path, _update_repository, args) -def update_directories(paths, update_args): +def update_directories(paths, args): """Update a list of directories supplied by command arguments.""" for path in paths: - _dispatch(path, _update_repository, *update_args) + _dispatch(path, _update_repository, args) -def run_command(paths, command): +def run_command(paths, args): """Run an arbitrary shell command on all repos.""" for path in paths: - _dispatch(path, _run_command, command) + _dispatch(path, _run_command, args) diff --git a/setup.py b/setup.py index a6dc6fb..7b8ce11 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2016 Ben Kurtovic +# Copyright (C) 2011-2018 Ben Kurtovic # Released under the terms of the MIT License. See LICENSE for details. import sys @@ -8,7 +8,7 @@ import sys from setuptools import setup, find_packages if sys.hexversion < 0x02070000: - exit("Please upgrade to Python 2.7 or greater: .") + exit("Please upgrade to Python 2.7 or greater: .") from gitup import __version__ @@ -23,18 +23,19 @@ setup( version = __version__, author = "Ben Kurtovic", author_email = "ben.kurtovic@gmail.com", - description = "Easily pull to multiple git repositories at once", + description = "Easily update multiple git repositories at once", long_description = long_desc, license = "MIT License", keywords = "git repository pull update", - url = "http://github.com/earwig/git-repo-updater", + url = "https://github.com/earwig/git-repo-updater", classifiers = [ + "Development Status :: 4 - Beta", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", "Operating System :: MacOS :: MacOS X", - "Operating System :: POSIX :: Linux", + "Operating System :: POSIX", "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 2.7", @@ -44,6 +45,7 @@ setup( "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", - "Topic :: Software Development :: Version Control" + "Programming Language :: Python :: 3.7", + "Topic :: Software Development :: Version Control :: Git" ] ) From 3eb6e16892dd0ab6eafb55f938308da91830ae2c Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Mon, 27 Aug 2018 23:30:58 -0400 Subject: [PATCH 3/4] Add a __main__.py; fix GitPython remote fetch error message. --- CHANGELOG | 2 ++ gitup/__main__.py | 9 +++++++++ gitup/script.py | 4 +++- gitup/update.py | 12 ++++++++++-- 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 gitup/__main__.py diff --git a/CHANGELOG b/CHANGELOG index 716b0d1..6b62186 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -8,8 +8,10 @@ v0.5 (unreleased): old behavior. - `--depth 3` will look three levels deep. This is the new default. - `--depth -1` will recurse indefinitely. This is not recommended. +- Allow gitup to be run directly as a Python module (python -m gitup). - Fixed an error when updating branches if the upstream is completely unrelated from the local branch (no common ancestor). +- Fixed error message when fetching from a remote fails. v0.4.1 (released December 13, 2017): diff --git a/gitup/__main__.py b/gitup/__main__.py new file mode 100644 index 0000000..86df533 --- /dev/null +++ b/gitup/__main__.py @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011-2018 Ben Kurtovic +# Released under the terms of the MIT License. See LICENSE for details. + +from .script import run + +if __name__ == "__main__": + run() diff --git a/gitup/script.py b/gitup/script.py index b8dc344..38d7e3e 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -7,6 +7,7 @@ from __future__ import print_function import argparse import os +import platform import sys from colorama import init as color_init, Fore, Style @@ -84,7 +85,8 @@ def main(): '-h', '--help', action="help", help="show this help message and exit") group_m.add_argument( '-v', '--version', action="version", - version="gitup " + __version__) + version="gitup {0} (Python {1})".format( + __version__, platform.python_version())) # TODO: deprecated arguments, for removal in v1.0: parser.add_argument( diff --git a/gitup/update.py b/gitup/update.py index 5c73e68..051ef00 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -7,6 +7,8 @@ from __future__ import print_function from glob import glob import os +import pipes +import re import shlex from colorama import Fore, Style @@ -77,8 +79,14 @@ def _fetch_remotes(remotes, prune): try: results = remote.fetch(progress=_ProgressMonitor(), prune=prune) except exc.GitCommandError as err: - msg = err.command[0].replace("Error when fetching: ", "") - if not msg.endswith("."): + # We should have to do this ourselves, but GitPython doesn't give + # us a sensible way to get the raw stderr... + msg = re.sub(r"\s+", " ", err.stderr).strip() + msg = re.sub(r"^stderr: *'(fatal: *)?", "", msg).strip("'") + if not msg: + command = " ".join(pipes.quote(arg) for arg in err.command) + msg = "{0} failed with status {1}.".format(command, err.status) + elif not msg.endswith("."): msg += "." print(":", RED + "error:", msg) return From ba5eec698999c36cb3d603ae46baa6f076b7b695 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Tue, 28 Aug 2018 00:16:47 -0400 Subject: [PATCH 4/4] release/0.5 --- CHANGELOG | 2 +- gitup/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6b62186..f9a9800 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -v0.5 (unreleased): +v0.5 (released August 28, 2018): - Added a `--depth` flag to control recursion depth when searching for repositories inside of subdirectories. For example: diff --git a/gitup/__init__.py b/gitup/__init__.py index 2ef3e7d..b973f47 100644 --- a/gitup/__init__.py +++ b/gitup/__init__.py @@ -10,5 +10,5 @@ gitup: the git repository updater __author__ = "Ben Kurtovic" __copyright__ = "Copyright (C) 2011-2018 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.5.dev0" +__version__ = "0.5" __email__ = "ben.kurtovic@gmail.com"