From 3e6dbf321a17ecca4a4e0428df9403201484e1f1 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Mon, 9 Jun 2025 01:36:53 -0400 Subject: [PATCH] Run ruff --- src/gitup/cli.py | 134 +++++++++++++++++++++++++++++---------- src/gitup/config.py | 27 ++++++-- src/gitup/migrate.py | 4 ++ src/gitup/test/__init__.py | 2 + src/gitup/test/test_bookmarks.py | 1 + src/gitup/test/test_cli.py | 5 +- src/gitup/update.py | 53 ++++++++++++---- 7 files changed, 174 insertions(+), 52 deletions(-) diff --git a/src/gitup/cli.py b/src/gitup/cli.py index 7e09b3a..de52de4 100644 --- a/src/gitup/cli.py +++ b/src/gitup/cli.py @@ -10,8 +10,14 @@ import platform from colorama import init as color_init, Style from gitup import __version__ -from gitup.config import (get_default_config_path, get_bookmarks, add_bookmarks, - delete_bookmarks, list_bookmarks, clean_bookmarks) +from gitup.config import ( + get_default_config_path, + get_bookmarks, + add_bookmarks, + delete_bookmarks, + list_bookmarks, + clean_bookmarks, +) from gitup.update import update_bookmarks, update_directories, run_command @@ -23,7 +29,8 @@ def _build_parser(): Both relative and absolute paths are accepted by all arguments. Direct bug reports and feature requests to https://github.com/earwig/git-repo-updater.""", - add_help=False) + add_help=False, + ) group_u = parser.add_argument_group("updating repositories") group_b = parser.add_argument_group("bookmarking") @@ -31,63 +38,125 @@ def _build_parser(): group_m = parser.add_argument_group("miscellaneous") group_u.add_argument( - 'directories_to_update', nargs="*", metavar="path", + "directories_to_update", + nargs="*", + metavar="path", help="""update this repository, or all repositories it contains - (if not a repo directly)""") + (if not a repo directly)""", + ) group_u.add_argument( - '-u', '--update', action="store_true", help="""update all bookmarks - (default behavior when called without arguments)""") + "-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, + "-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)""") + (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""") + "-c", + "--current-only", + action="store_true", + help="""only fetch the + remote tracked by the current branch instead of all remotes""", + ) group_u.add_argument( - '-f', '--fetch-only', action="store_true", - help="only fetch remotes, don't try to fast-forward any branches") + "-f", + "--fetch-only", + action="store_true", + help="only fetch remotes, don't try to fast-forward any branches", + ) group_u.add_argument( - '-p', '--prune', action="store_true", help="""after fetching, delete - remote-tracking branches that no longer exist on their remote""") + "-p", + "--prune", + action="store_true", + help="""after fetching, delete + remote-tracking branches that no longer exist on their remote""", + ) group_b.add_argument( - '-a', '--add', dest="bookmarks_to_add", nargs="+", metavar="path", help="add directory(s) as bookmarks") + "-a", + "--add", + dest="bookmarks_to_add", + nargs="+", + metavar="path", + help="add directory(s) as bookmarks", + ) group_b.add_argument( - '-d', '--delete', dest="bookmarks_to_del", nargs="+", metavar="path", - help="delete bookmark(s) (leaves actual directories alone)") + "-d", + "--delete", + dest="bookmarks_to_del", + nargs="+", + metavar="path", + help="delete bookmark(s) (leaves actual directories alone)", + ) group_b.add_argument( - '-l', '--list', dest="list_bookmarks", action="store_true", - help="list current bookmarks") + "-l", + "--list", + dest="list_bookmarks", + action="store_true", + help="list current bookmarks", + ) group_b.add_argument( - '-n', '--clean', '--cleanup', dest="clean_bookmarks", - action="store_true", help="delete any bookmarks that don't exist") + "-n", + "--clean", + "--cleanup", + dest="clean_bookmarks", + action="store_true", + help="delete any bookmarks that don't exist", + ) group_b.add_argument( - '-b', '--bookmark-file', nargs="?", metavar="path", + "-b", + "--bookmark-file", + nargs="?", + metavar="path", help="use a specific bookmark config file (default: {0})".format( - get_default_config_path())) + get_default_config_path() + ), + ) group_a.add_argument( - '-e', '--exec', '--batch', dest="command", metavar="command", - help="run a shell command on all repos") + "-e", + "--exec", + "--batch", + dest="command", + metavar="command", + help="run a shell command on all repos", + ) group_m.add_argument( - '-h', '--help', action="help", help="show this help message and exit") + "-h", "--help", action="help", help="show this help message and exit" + ) group_m.add_argument( - '-v', '--version', action="version", - version="gitup {0} (Python {1})".format( - __version__, platform.python_version())) + "-v", + "--version", + action="version", + version="gitup {0} (Python {1})".format(__version__, platform.python_version()), + ) group_m.add_argument( - '--selftest', action="store_true", - help="run integrated test suite and exit (pytest must be available)") + "--selftest", + action="store_true", + help="run integrated test suite and exit (pytest must be available)", + ) return parser + def _selftest(): """Run the integrated test suite with pytest.""" from .test import run_tests + run_tests() + def main(): """Parse arguments and then call the appropriate function(s).""" parser = _build_parser() @@ -130,6 +199,7 @@ def main(): if args.update or not acted: update_bookmarks(get_bookmarks(args.bookmark_file), args) + def run(): """Thin wrapper for main() that catches KeyboardInterrupts.""" try: diff --git a/src/gitup/config.py b/src/gitup/config.py index b34681f..c3e6bed 100644 --- a/src/gitup/config.py +++ b/src/gitup/config.py @@ -10,20 +10,28 @@ from colorama import Fore, Style from gitup.migrate import run_migrations -__all__ = ["get_default_config_path", "get_bookmarks", "add_bookmarks", - "delete_bookmarks", "list_bookmarks", "clean_bookmarks"] +__all__ = [ + "get_default_config_path", + "get_bookmarks", + "add_bookmarks", + "delete_bookmarks", + "list_bookmarks", + "clean_bookmarks", +] YELLOW = Fore.YELLOW + Style.BRIGHT RED = Fore.RED + Style.BRIGHT INDENT1 = " " * 3 + def _ensure_dirs(path): """Ensure the directories within the given pathname exist.""" dirname = os.path.dirname(path) if dirname and not os.path.exists(dirname): # Race condition, meh... os.makedirs(dirname) + def _load_config_file(config_path=None): """Read the config file and return a list of bookmarks.""" run_migrations() @@ -37,6 +45,7 @@ def _load_config_file(config_path=None): paths = [path.decode("utf8").strip() for path in paths] return [path for path in paths if path] + def _save_config_file(bookmarks, config_path=None): """Save the bookmarks list to the given config file.""" run_migrations() @@ -47,21 +56,25 @@ def _save_config_file(bookmarks, config_path=None): with open(cfg_path, "wb") as config_file: config_file.write(dump) + def _normalize_path(path): """Normalize the given path.""" if path.startswith("~"): return os.path.normcase(os.path.normpath(path)) return os.path.normcase(os.path.abspath(path)) + def get_default_config_path(): """Return the default path to the configuration file.""" xdg_cfg = os.environ.get("XDG_CONFIG_HOME") or os.path.join("~", ".config") return os.path.join(os.path.expanduser(xdg_cfg), "gitup", "bookmarks") + def get_bookmarks(config_path=None): """Get a list of all bookmarks, or an empty list if there are none.""" return _load_config_file(config_path) + def add_bookmarks(paths, config_path=None): """Add a list of paths as bookmarks to the config file.""" config = _load_config_file(config_path) @@ -85,6 +98,7 @@ def add_bookmarks(paths, config_path=None): for path in exists: print(INDENT1, path) + def delete_bookmarks(paths, config_path=None): """Remove a list of paths from the bookmark config file.""" config = _load_config_file(config_path) @@ -111,6 +125,7 @@ def delete_bookmarks(paths, config_path=None): for path in notmarked: print(INDENT1, path) + def list_bookmarks(config_path=None): """Print all of our current bookmarks.""" bookmarks = _load_config_file(config_path) @@ -121,6 +136,7 @@ def list_bookmarks(config_path=None): else: print("You have no bookmarks to display.") + def clean_bookmarks(config_path=None): """Delete any bookmarks that don't exist.""" bookmarks = _load_config_file(config_path) @@ -128,8 +144,11 @@ def clean_bookmarks(config_path=None): print("You have no bookmarks to clean up.") return - delete = [path for path in bookmarks - if not (os.path.isdir(path) or glob(os.path.expanduser(path)))] + delete = [ + path + for path in bookmarks + if not (os.path.isdir(path) or glob(os.path.expanduser(path))) + ] if not delete: print("All of your bookmarks are valid.") return diff --git a/src/gitup/migrate.py b/src/gitup/migrate.py index 700bd6d..57ae9c7 100644 --- a/src/gitup/migrate.py +++ b/src/gitup/migrate.py @@ -9,11 +9,13 @@ from configparser import ConfigParser, NoSectionError __all__ = ["run_migrations"] + def _get_old_path(): """Return the old default path to the configuration file.""" xdg_cfg = os.environ.get("XDG_CONFIG_HOME") or os.path.join("~", ".config") return os.path.join(os.path.expanduser(xdg_cfg), "gitup", "config.ini") + def _migrate_old_path(): """Migrate the old config location (~/.gitup) to the new one.""" old_path = os.path.expanduser(os.path.join("~", ".gitup")) @@ -26,6 +28,7 @@ def _migrate_old_path(): os.makedirs(temp_dir) os.rename(old_path, temp_path) + def _migrate_old_format(): """Migrate the old config file format (.INI) to our custom list format.""" old_path = _get_old_path() @@ -47,6 +50,7 @@ def _migrate_old_format(): with open(new_path, "wb") as handle: handle.write(b"\n".join(bookmarks)) + def run_migrations(): """Run any necessary migrations to ensure the config file is up-to-date.""" _migrate_old_path() diff --git a/src/gitup/test/__init__.py b/src/gitup/test/__init__.py index d632e70..2aaf64e 100644 --- a/src/gitup/test/__init__.py +++ b/src/gitup/test/__init__.py @@ -3,8 +3,10 @@ # Copyright (C) 2011-2018 Ben Kurtovic # Released under the terms of the MIT License. See LICENSE for details. + def run_tests(args=None): import pytest + if args is None: args = ["-v", "-rxw"] return pytest.main(args) diff --git a/src/gitup/test/test_bookmarks.py b/src/gitup/test/test_bookmarks.py index c9092e2..c051432 100644 --- a/src/gitup/test/test_bookmarks.py +++ b/src/gitup/test/test_bookmarks.py @@ -5,6 +5,7 @@ from gitup import config + def test_empty_list(tmpdir, capsys): config_path = tmpdir / "config" config.list_bookmarks(config_path) diff --git a/src/gitup/test/test_cli.py b/src/gitup/test/test_cli.py index e75f285..b077932 100644 --- a/src/gitup/test/test_cli.py +++ b/src/gitup/test/test_cli.py @@ -9,14 +9,15 @@ import sys from gitup import __version__ + def run_cli(*args): cmd = [sys.executable, "-m", "gitup"] + list(args) output = subprocess.check_output(cmd) return output.strip().decode("utf8") + def test_cli_version(): """make sure we're using the right version of gitup""" output = run_cli("-v") - expected = "gitup {} (Python {})".format( - __version__, platform.python_version()) + expected = "gitup {} (Python {})".format(__version__, platform.python_version()) assert output == expected diff --git a/src/gitup/update.py b/src/gitup/update.py index f24d5ef..a62536d 100644 --- a/src/gitup/update.py +++ b/src/gitup/update.py @@ -28,6 +28,7 @@ INDENT1 = " " * 3 INDENT2 = " " * 7 ERROR = RED + "Error:" + RESET + class _ProgressMonitor(RemoteProgress): """Displays relevant output during the fetching process.""" @@ -35,7 +36,7 @@ class _ProgressMonitor(RemoteProgress): super(_ProgressMonitor, self).__init__() self._started = False - def update(self, op_code, cur_count, max_count=None, message=''): + def update(self, op_code, cur_count, max_count=None, message=""): """Called whenever progress changes. Overrides default behavior.""" if op_code & (self.COMPRESSING | self.RECEIVING): cur_count = str(int(cur_count)) @@ -59,14 +60,17 @@ class _ProgressMonitor(RemoteProgress): def _fetch_remotes(remotes, prune): """Fetch a list of remotes, displaying progress info along the way.""" + def _get_name(ref): """Return the local name of a remote or tag reference.""" return ref.remote_head if isinstance(ref, RemoteRef) else ref.name # TODO: missing branch deleted (via --prune): - info = [("NEW_HEAD", "new branch", "new branches"), - ("NEW_TAG", "new tag", "new tags"), - ("FAST_FORWARD", "branch update", "branch updates")] + info = [ + ("NEW_HEAD", "new branch", "new branches"), + ("NEW_TAG", "new tag", "new tags"), + ("FAST_FORWARD", "branch update", "branch updates"), + ] up_to_date = BLUE + "up to date" + RESET for remote in remotes: @@ -92,19 +96,25 @@ def _fetch_remotes(remotes, prune): return except AssertionError: # Seems to be the result of a bug in GitPython # This happens when git initiates an auto-gc during fetch: - print(":", RED + "error:", "something went wrong in GitPython,", - "but the fetch might have been successful.") + print( + ":", + RED + "error:", + "something went wrong in GitPython,", + "but the fetch might have been successful.", + ) return rlist = [] for attr, singular, plural in info: - names = [_get_name(res.ref) - for res in results if res.flags & getattr(res, attr)] + names = [ + _get_name(res.ref) for res in results if res.flags & getattr(res, attr) + ] if names: desc = singular if len(names) == 1 else plural colored = GREEN + desc + RESET rlist.append("{0} ({1})".format(colored, ", ".join(names))) print(":", (", ".join(rlist) if rlist else up_to_date) + ".") + def _update_branch(repo, branch, is_active=False): """Update a single branch.""" print(INDENT2, "Updating", BOLD + branch.name, end=": ") @@ -146,14 +156,19 @@ def _update_branch(repo, branch, is_active=False): print(YELLOW + "skipped:", "not possible to fast-forward.") else: status = repo.git.merge_base( - branch.commit, upstream.commit, is_ancestor=True, - with_extended_output=True, with_exceptions=False)[0] + branch.commit, + upstream.commit, + is_ancestor=True, + with_extended_output=True, + with_exceptions=False, + )[0] if status != 0: print(YELLOW + "skipped:", "not possible to fast-forward.") else: repo.git.branch(branch.name, upstream.name, force=True) print(GREEN + "done", end=".\n") + def _update_repository(repo, repo_name, args): """Update a single git repository by fetching remotes and rebasing/merging. @@ -172,8 +187,11 @@ def _update_repository(repo, repo_name, args): active = None if args.current_only: if not active: - print(INDENT2, ERROR, - "--current-only doesn't make sense with a detached HEAD.") + print( + INDENT2, + ERROR, + "--current-only doesn't make sense with a detached HEAD.", + ) return ref = active.tracking_branch() if not ref: @@ -192,14 +210,14 @@ def _update_repository(repo, repo_name, args): for branch in sorted(repo.heads, key=lambda b: b.name): _update_branch(repo, branch, branch == active) + def _run_command(repo, repo_name, args): """Run an arbitrary shell command on the given repository.""" print(INDENT1, BOLD + repo_name + ":") cmd = shlex.split(args.command) try: - out = repo.git.execute( - cmd, with_extended_output=True, with_exceptions=False) + out = repo.git.execute(cmd, with_extended_output=True, with_exceptions=False) except exc.GitCommandNotFound as err: print(INDENT2, ERROR, err) return @@ -207,6 +225,7 @@ def _run_command(repo, repo_name, args): for line in out[1].splitlines() + out[2].splitlines(): print(INDENT2, line) + def _dispatch(base_path, callback, args): """Apply a callback function on each valid repo in the given path. @@ -217,6 +236,7 @@ def _dispatch(base_path, callback, args): The given args are passed directly to the callback function after the repo. """ + def _collect(paths, max_depth): """Return all valid repo paths in the given paths, recursively.""" if max_depth == 0: @@ -282,14 +302,17 @@ def _dispatch(base_path, callback, args): for name, path in sorted(paths): callback(Repo(path), name, args) + def is_comment(path): """Does the line start with a # symbol?""" return path.lstrip().startswith("#") + def get_comment(path): """Return the string minus the comment symbol.""" return path.lstrip().lstrip("#").strip() + def update_bookmarks(bookmarks, args): """Loop through and update all bookmarks.""" if not bookmarks: @@ -299,11 +322,13 @@ def update_bookmarks(bookmarks, args): for path in bookmarks: _dispatch(path, _update_repository, args) + def update_directories(paths, args): """Update a list of directories supplied by command arguments.""" for path in paths: _dispatch(path, _update_repository, args) + def run_command(paths, args): """Run an arbitrary shell command on all repos.""" for path in paths: