diff --git a/CHANGELOG b/CHANGELOG index d2a32a7..3e8f87b 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,9 +1,10 @@ -v0.3.1 (unreleased): +v0.4 (unreleased): - Added a `--prune` flag to delete remote-tracking branches that no longer exist on their remote after fetching. - Added a `--bookmark-file` flag to support multiple bookmark config files. - Added a `--cleanup` flag to remove old bookmarks that don't exist. +- Added an `--execute` flag to run a shell command on all of your repos. - 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. diff --git a/gitup/__init__.py b/gitup/__init__.py index 2bbd655..4de96d9 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-2016 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.3.1.dev0" +__version__ = "0.4.dev0" __email__ = "ben.kurtovic@gmail.com" diff --git a/gitup/script.py b/gitup/script.py index 32fe441..8f0c2f7 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -14,7 +14,7 @@ from colorama import init as color_init, Fore, Style from . import __version__ from .config import (get_default_config_path, get_bookmarks, add_bookmarks, delete_bookmarks, list_bookmarks, clean_bookmarks) -from .update import update_bookmarks, update_directories +from .update import update_bookmarks, update_directories, run_command def _decode(path): """Decode the given string using the system's filesystem encoding.""" @@ -34,6 +34,7 @@ def main(): group_u = parser.add_argument_group("updating repositories") group_b = parser.add_argument_group("bookmarking") + group_a = parser.add_argument_group("advanced") group_m = parser.add_argument_group("miscellaneous") group_u.add_argument( @@ -71,6 +72,10 @@ def main(): help="use a specific bookmark config file (default: {0})".format( get_default_config_path())) + group_a.add_argument( + '-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") group_m.add_argument( @@ -113,11 +118,18 @@ def main(): if args.clean_bookmarks: clean_bookmarks(args.bookmark_file) acted = True - if args.directories_to_update: - update_directories(args.directories_to_update, update_args) - acted = True - if args.update or not acted: - update_bookmarks(get_bookmarks(args.bookmark_file), update_args) + + if args.command: + if args.directories_to_update: + run_command(args.directories_to_update, args.command) + if args.update or not args.directories_to_update: + run_command(get_bookmarks(args.bookmark_file), args.command) + else: + if args.directories_to_update: + update_directories(args.directories_to_update, update_args) + acted = True + if args.update or not acted: + update_bookmarks(get_bookmarks(args.bookmark_file), update_args) def run(): """Thin wrapper for main() that catches KeyboardInterrupts.""" diff --git a/gitup/update.py b/gitup/update.py index acea96c..139312f 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -6,12 +6,13 @@ from __future__ import print_function import os +import shlex from colorama import Fore, Style from git import RemoteReference as RemoteRef, Repo, exc from git.util import RemoteProgress -__all__ = ["update_bookmarks", "update_directories"] +__all__ = ["update_bookmarks", "update_directories", "run_command"] BOLD = Style.BRIGHT BLUE = Fore.BLUE + BOLD @@ -176,8 +177,23 @@ 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 _update_subdirectories(path, update_args): - """Update all subdirectories that are git repos in a given directory.""" +def _run_command(repo, command): + """Run an arbitrary shell command on the given repository.""" + print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") + + cmd = shlex.split(command) + try: + out = repo.git.execute( + cmd, with_extended_output=True, with_exceptions=False) + except exc.GitCommandNotFound as err: + print(INDENT2, ERROR, err) + return + + for line in out[1].splitlines() + out[2].splitlines(): + print(INDENT2, line) + +def _dispatch_to_subdirs(path, callback, *args): + """Apply the callback to all git repo subdirectories in the directory.""" repos = [] for item in os.listdir(path): try: @@ -189,15 +205,17 @@ def _update_subdirectories(path, update_args): suffix = "" if len(repos) == 1 else "s" print(BOLD + path, "({0} repo{1}):".format(len(repos), suffix)) for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]): - _update_repository(repo, *update_args) + callback(repo, *args) -def _update_directory(path, update_args): - """Update a particular directory. +def _dispatch(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 - git repositories, or something invalid. If the first, update the single - repository; if the second, update all repositories contained within; if the - third, print an error. + git repositories, or something invalid. If the first, apply the callback on + it; if the second, apply the callback on all repositories contained within; + if the third, print an error. + + The given args are passed directly to the callback function after the repo. """ try: repo = Repo(path) @@ -205,18 +223,18 @@ def _update_directory(path, update_args): print(ERROR, BOLD + path, "doesn't exist!") except exc.InvalidGitRepositoryError: if os.path.isdir(path): - _update_subdirectories(path, update_args) + _dispatch_to_subdirs(path, callback, *args) else: print(ERROR, BOLD + path, "isn't a repository!") else: print(BOLD + repo.working_dir, "(1 repo):") - _update_repository(repo, *update_args) + callback(repo, *args) def update_bookmarks(bookmarks, update_args): """Loop through and update all bookmarks.""" if bookmarks: for path in bookmarks: - _update_directory(path, update_args) + _dispatch(path, _update_repository, *update_args) else: print("You don't have any bookmarks configured! Get help with 'gitup -h'.") @@ -224,4 +242,10 @@ def update_directories(paths, update_args): """Update a list of directories supplied by command arguments.""" for path in paths: full_path = os.path.abspath(path) - _update_directory(full_path, update_args) + _dispatch(full_path, _update_repository, *update_args) + +def run_command(paths, command): + """Run an arbitrary shell command on all repos.""" + for path in paths: + full_path = os.path.abspath(path) + _dispatch(full_path, _run_command, command)