@@ -1,3 +1,16 @@ | |||
v0.4 (released January 17, 2017): | |||
- 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 `--exec` flag to run a shell command on all of your repos. | |||
- Added support for shell glob patterns and tilde expansion in bookmark files. | |||
- 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. | |||
v0.3 (released June 7, 2015): | |||
- Added support for Python 3. | |||
@@ -1,4 +1,4 @@ | |||
Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Copyright (C) 2011-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
Permission is hereby granted, free of charge, to any person obtaining a copy | |||
of this software and associated documentation files (the "Software"), to deal | |||
@@ -89,6 +89,10 @@ 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. | |||
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.<name>.prune` in git config to do this by default. | |||
For a full list of all command arguments and abbreviations: | |||
gitup --help | |||
@@ -1,6 +1,6 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Copyright (C) 2011-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
""" | |||
@@ -8,7 +8,7 @@ gitup: the git repository updater | |||
""" | |||
__author__ = "Ben Kurtovic" | |||
__copyright__ = "Copyright (C) 2011-2015 Ben Kurtovic" | |||
__copyright__ = "Copyright (C) 2011-2017 Ben Kurtovic" | |||
__license__ = "MIT License" | |||
__version__ = "0.3" | |||
__version__ = "0.4" | |||
__email__ = "ben.kurtovic@gmail.com" |
@@ -1,21 +1,19 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
from __future__ import print_function | |||
from glob import glob | |||
import os | |||
try: | |||
import configparser | |||
except ImportError: # Python 2 | |||
import ConfigParser as configparser | |||
from colorama import Fore, Style | |||
__all__ = ["get_bookmarks", "add_bookmarks", "delete_bookmarks", | |||
"list_bookmarks"] | |||
from .migrate import run_migrations | |||
__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 | |||
@@ -25,63 +23,60 @@ INDENT1 = " " * 3 | |||
def _ensure_dirs(path): | |||
"""Ensure the directories within the given pathname exist.""" | |||
dirname = os.path.dirname(path) | |||
if not os.path.exists(dirname): # Race condition, meh... | |||
if dirname and not os.path.exists(dirname): # Race condition, meh... | |||
os.makedirs(dirname) | |||
def _get_config_path(): | |||
"""Return the 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_config_path(): | |||
"""Migrate the old config location (~/.gitup) to the new one.""" | |||
old_path = os.path.expanduser(os.path.join("~", ".gitup")) | |||
if os.path.exists(old_path): | |||
new_path = _get_config_path() | |||
_ensure_dirs(new_path) | |||
os.rename(old_path, new_path) | |||
def _load_config_file(): | |||
"""Read the config file and return a SafeConfigParser() object.""" | |||
_migrate_old_config_path() | |||
config = configparser.SafeConfigParser() | |||
# Don't lowercase option names, because we are storing paths there: | |||
config.optionxform = str | |||
config.read(_get_config_path()) | |||
return config | |||
def _save_config_file(config): | |||
"""Save config changes to the config file returned by _get_config_path.""" | |||
_migrate_old_config_path() | |||
cfg_path = _get_config_path() | |||
def _load_config_file(config_path=None): | |||
"""Read the config file and return a list of bookmarks.""" | |||
run_migrations() | |||
cfg_path = config_path or get_default_config_path() | |||
try: | |||
with open(cfg_path, "rb") as config_file: | |||
paths = config_file.read().split(b"\n") | |||
except IOError: | |||
return [] | |||
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() | |||
cfg_path = config_path or get_default_config_path() | |||
_ensure_dirs(cfg_path) | |||
dump = b"\n".join(path.encode("utf8") for path in bookmarks) | |||
with open(cfg_path, "wb") as config_file: | |||
config.write(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(): | |||
def get_bookmarks(config_path=None): | |||
"""Get a list of all bookmarks, or an empty list if there are none.""" | |||
config = _load_config_file() | |||
try: | |||
return [path for path, _ in config.items("bookmarks")] | |||
except configparser.NoSectionError: | |||
return [] | |||
return _load_config_file(config_path) | |||
def add_bookmarks(paths): | |||
def add_bookmarks(paths, config_path=None): | |||
"""Add a list of paths as bookmarks to the config file.""" | |||
config = _load_config_file() | |||
if not config.has_section("bookmarks"): | |||
config.add_section("bookmarks") | |||
config = _load_config_file(config_path) | |||
paths = [_normalize_path(path) for path in paths] | |||
added, exists = [], [] | |||
for path in paths: | |||
path = os.path.abspath(path) | |||
if config.has_option("bookmarks", path): | |||
if path in config: | |||
exists.append(path) | |||
else: | |||
path_name = os.path.split(path)[1] | |||
config.set("bookmarks", path, path_name) | |||
config.append(path) | |||
added.append(path) | |||
_save_config_file(config) | |||
_save_config_file(config, config_path) | |||
if added: | |||
print(YELLOW + "Added bookmarks:") | |||
@@ -92,22 +87,22 @@ def add_bookmarks(paths): | |||
for path in exists: | |||
print(INDENT1, path) | |||
def delete_bookmarks(paths): | |||
def delete_bookmarks(paths, config_path=None): | |||
"""Remove a list of paths from the bookmark config file.""" | |||
config = _load_config_file() | |||
config = _load_config_file(config_path) | |||
paths = [_normalize_path(path) for path in paths] | |||
deleted, notmarked = [], [] | |||
if config.has_section("bookmarks"): | |||
if config: | |||
for path in paths: | |||
path = os.path.abspath(path) | |||
config_was_changed = config.remove_option("bookmarks", path) | |||
if config_was_changed: | |||
if path in config: | |||
config.remove(path) | |||
deleted.append(path) | |||
else: | |||
notmarked.append(path) | |||
_save_config_file(config) | |||
_save_config_file(config, config_path) | |||
else: | |||
notmarked = [os.path.abspath(path) for path in paths] | |||
notmarked = paths | |||
if deleted: | |||
print(YELLOW + "Deleted bookmarks:") | |||
@@ -118,12 +113,32 @@ def delete_bookmarks(paths): | |||
for path in notmarked: | |||
print(INDENT1, path) | |||
def list_bookmarks(): | |||
def list_bookmarks(config_path=None): | |||
"""Print all of our current bookmarks.""" | |||
bookmarks = get_bookmarks() | |||
bookmarks = _load_config_file(config_path) | |||
if bookmarks: | |||
print(YELLOW + "Current bookmarks:") | |||
for bookmark_path in bookmarks: | |||
print(INDENT1, bookmark_path) | |||
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) | |||
if not bookmarks: | |||
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)))] | |||
if not delete: | |||
print("All of your bookmarks are valid.") | |||
return | |||
bookmarks = [path for path in bookmarks if path not in delete] | |||
_save_config_file(bookmarks, config_path) | |||
print(YELLOW + "Deleted bookmarks:") | |||
for path in delete: | |||
print(INDENT1, path) |
@@ -0,0 +1,60 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
import os | |||
try: | |||
from configparser import ConfigParser, NoSectionError | |||
PY3K = True | |||
except ImportError: # Python 2 | |||
from ConfigParser import SafeConfigParser as ConfigParser, NoSectionError | |||
PY3K = False | |||
__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")) | |||
if not os.path.exists(old_path): | |||
return | |||
temp_path = _get_old_path() | |||
temp_dir = os.path.dirname(temp_path) | |||
if not os.path.exists(temp_dir): | |||
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() | |||
if not os.path.exists(old_path): | |||
return | |||
config = ConfigParser(delimiters="=") if PY3K else ConfigParser() | |||
config.optionxform = lambda opt: opt | |||
config.read(old_path) | |||
try: | |||
bookmarks = [path for path, _ in config.items("bookmarks")] | |||
except NoSectionError: | |||
bookmarks = [] | |||
if PY3K: | |||
bookmarks = [path.encode("utf8") for path in bookmarks] | |||
new_path = os.path.join(os.path.split(old_path)[0], "bookmarks") | |||
os.rename(old_path, new_path) | |||
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() | |||
_migrate_old_format() |
@@ -1,18 +1,26 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
from __future__ import print_function | |||
import argparse | |||
import os | |||
import sys | |||
from colorama import init as color_init, Fore, Style | |||
from . import __version__ | |||
from .config import (get_bookmarks, add_bookmarks, delete_bookmarks, | |||
list_bookmarks) | |||
from .update import update_bookmarks, update_directories | |||
from .config import (get_default_config_path, get_bookmarks, add_bookmarks, | |||
delete_bookmarks, list_bookmarks, clean_bookmarks) | |||
from .update import update_bookmarks, update_directories, run_command | |||
def _decode(path): | |||
"""Decode the given string using the system's filesystem encoding.""" | |||
if sys.version_info.major > 2: | |||
return path | |||
return path.decode(sys.getfilesystemencoding()) | |||
def main(): | |||
"""Parse arguments and then call the appropriate function(s).""" | |||
@@ -20,16 +28,17 @@ def main(): | |||
description="Easily update multiple git repositories at once.", | |||
epilog=""" | |||
Both relative and absolute paths are accepted by all arguments. | |||
Direct bug reports and feature requests to: | |||
Direct bug reports and feature requests to | |||
https://github.com/earwig/git-repo-updater.""", | |||
add_help=False) | |||
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( | |||
'directories_to_update', nargs="*", metavar="path", | |||
'directories_to_update', nargs="*", metavar="path", type=_decode, | |||
help="""update all repositories in this directory (or the directory | |||
itself, if it is a repo)""") | |||
group_u.add_argument( | |||
@@ -41,16 +50,31 @@ def main(): | |||
group_u.add_argument( | |||
'-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""") | |||
group_b.add_argument( | |||
'-a', '--add', dest="bookmarks_to_add", nargs="+", metavar="path", | |||
help="add directory(s) as bookmarks") | |||
type=_decode, help="add directory(s) as bookmarks") | |||
group_b.add_argument( | |||
'-d', '--delete', dest="bookmarks_to_del", nargs="+", metavar="path", | |||
type=_decode, | |||
help="delete bookmark(s) (leaves actual directories alone)") | |||
group_b.add_argument( | |||
'-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") | |||
group_b.add_argument( | |||
'-b', '--bookmark-file', nargs="?", metavar="path", type=_decode, | |||
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") | |||
@@ -66,7 +90,7 @@ def main(): | |||
color_init(autoreset=True) | |||
args = parser.parse_args() | |||
update_args = args.current_only, args.fetch_only | |||
update_args = args.current_only, args.fetch_only, args.prune | |||
print(Style.BRIGHT + "gitup" + Style.RESET_ALL + ": the git-repo-updater") | |||
print() | |||
@@ -78,21 +102,34 @@ def main(): | |||
"upstream branch and can be safely fast-forwarded. Use " | |||
"--fetch-only to\navoid updating any branches.\n") | |||
if args.bookmark_file: | |||
args.bookmark_file = os.path.expanduser(args.bookmark_file) | |||
acted = False | |||
if args.bookmarks_to_add: | |||
add_bookmarks(args.bookmarks_to_add) | |||
add_bookmarks(args.bookmarks_to_add, args.bookmark_file) | |||
acted = True | |||
if args.bookmarks_to_del: | |||
delete_bookmarks(args.bookmarks_to_del) | |||
delete_bookmarks(args.bookmarks_to_del, args.bookmark_file) | |||
acted = True | |||
if args.list_bookmarks: | |||
list_bookmarks() | |||
list_bookmarks(args.bookmark_file) | |||
acted = True | |||
if args.directories_to_update: | |||
update_directories(args.directories_to_update, update_args) | |||
if args.clean_bookmarks: | |||
clean_bookmarks(args.bookmark_file) | |||
acted = True | |||
if args.update or not acted: | |||
update_bookmarks(get_bookmarks(), 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.""" | |||
@@ -1,17 +1,19 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
from __future__ import print_function | |||
from glob import glob | |||
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 | |||
@@ -53,12 +55,13 @@ class _ProgressMonitor(RemoteProgress): | |||
print(str(cur_count), end=end) | |||
def _fetch_remotes(remotes): | |||
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")] | |||
@@ -72,7 +75,7 @@ def _fetch_remotes(remotes): | |||
continue | |||
try: | |||
results = remote.fetch(progress=_ProgressMonitor()) | |||
results = remote.fetch(progress=_ProgressMonitor(), prune=prune) | |||
except exc.GitCommandError as err: | |||
msg = err.command[0].replace("Error when fetching: ", "") | |||
if not msg.endswith("."): | |||
@@ -101,12 +104,16 @@ def _update_branch(repo, branch, is_active=False): | |||
if not upstream: | |||
print(YELLOW + "skipped:", "no upstream is tracked.") | |||
return | |||
try: | |||
branch.commit, upstream.commit | |||
branch.commit | |||
except ValueError: | |||
print(YELLOW + "skipped:", "branch has no revisions.") | |||
return | |||
try: | |||
upstream.commit | |||
except ValueError: | |||
print(YELLOW + "skipped:", "upstream does not exist.") | |||
return | |||
base = repo.git.merge_base(branch.commit, upstream.commit) | |||
if repo.commit(base) == upstream.commit: | |||
@@ -133,13 +140,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, current_only=False, fetch_only=False): | |||
def _update_repository(repo, 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 | |||
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. | |||
""" | |||
print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") | |||
@@ -163,58 +172,88 @@ def _update_repository(repo, current_only=False, fetch_only=False): | |||
if not remotes: | |||
print(INDENT2, ERROR, "no remotes configured to fetch.") | |||
return | |||
_fetch_remotes(remotes) | |||
_fetch_remotes(remotes, prune) | |||
if not fetch_only: | |||
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.""" | |||
repos = [] | |||
for item in os.listdir(path): | |||
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_multi(base, paths, callback, *args): | |||
"""Apply the callback to all git repos in the list of paths.""" | |||
valid = [] | |||
for path in paths: | |||
try: | |||
repo = Repo(os.path.join(path, item)) | |||
Repo(path) | |||
except (exc.InvalidGitRepositoryError, exc.NoSuchPathError): | |||
continue | |||
repos.append(repo) | |||
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)) | |||
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) | |||
for path in sorted(valid, key=os.path.basename): | |||
callback(Repo(path), *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, a shell glob pattern, or something invalid. If the first, | |||
apply the callback on it; if the second or third, apply the callback on all | |||
repositories contained within; if the last, print an error. | |||
The given args are passed directly to the callback function after the repo. | |||
""" | |||
path = os.path.expanduser(path) | |||
try: | |||
repo = Repo(path) | |||
except exc.NoSuchPathError: | |||
print(ERROR, BOLD + path, "doesn't exist!") | |||
paths = glob(path) | |||
if paths: | |||
_dispatch_multi(path, paths, callback, *args) | |||
else: | |||
print(ERROR, BOLD + path, "doesn't exist!") | |||
except exc.InvalidGitRepositoryError: | |||
if os.path.isdir(path): | |||
_update_subdirectories(path, update_args) | |||
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):") | |||
_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) | |||
else: | |||
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) | |||
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(path, _update_repository, *update_args) | |||
def run_command(paths, command): | |||
"""Run an arbitrary shell command on all repos.""" | |||
for path in paths: | |||
_dispatch(path, _run_command, command) |
@@ -1,6 +1,6 @@ | |||
# -*- coding: utf-8 -*- | |||
# | |||
# Copyright (C) 2011-2015 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||
# Released under the terms of the MIT License. See LICENSE for details. | |||
import sys | |||
@@ -19,7 +19,7 @@ setup( | |||
name = "gitup", | |||
packages = find_packages(), | |||
entry_points = {"console_scripts": ["gitup = gitup.script:run"]}, | |||
install_requires = ["GitPython >= 1.0.1", "colorama >= 0.3.3"], | |||
install_requires = ["GitPython >= 2.1.1", "colorama >= 0.3.7"], | |||
version = __version__, | |||
author = "Ben Kurtovic", | |||
author_email = "ben.kurtovic@gmail.com", | |||
@@ -43,6 +43,7 @@ setup( | |||
"Programming Language :: Python :: 3.3", | |||
"Programming Language :: Python :: 3.4", | |||
"Programming Language :: Python :: 3.5", | |||
"Programming Language :: Python :: 3.6", | |||
"Topic :: Software Development :: Version Control" | |||
] | |||
) |