From 4e8dab5aab032d50186bc0200415ec6436be51c3 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 29 May 2015 21:12:20 -0400 Subject: [PATCH 01/12] Copyright update for 2015; dev version up for 0.2.5. --- LICENSE | 2 +- gitup/__init__.py | 6 +++--- gitup/config.py | 2 +- gitup/script.py | 2 +- gitup/update.py | 2 +- setup.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/LICENSE b/LICENSE index b755367..aef9ae4 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2011-2014 Ben Kurtovic +Copyright (C) 2011-2015 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/gitup/__init__.py b/gitup/__init__.py index 48be6a3..d259d59 100644 --- a/gitup/__init__.py +++ b/gitup/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2014 Ben Kurtovic +# Copyright (C) 2011-2015 Ben Kurtovic # See the LICENSE file for details. """ @@ -8,7 +8,7 @@ gitup: the git repository updater """ __author__ = "Ben Kurtovic" -__copyright__ = "Copyright (C) 2011-2014 Ben Kurtovic" +__copyright__ = "Copyright (C) 2011-2015 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.2.4" +__version__ = "0.2.5.dev0" __email__ = "ben.kurtovic@gmail.com" diff --git a/gitup/config.py b/gitup/config.py index 59f3c68..9fed6f4 100644 --- a/gitup/config.py +++ b/gitup/config.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2014 Ben Kurtovic +# Copyright (C) 2011-2015 Ben Kurtovic # See the LICENSE file for details. from __future__ import print_function diff --git a/gitup/script.py b/gitup/script.py index a29982f..42e78a5 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2014 Ben Kurtovic +# Copyright (C) 2011-2015 Ben Kurtovic # See the LICENSE file for details. from __future__ import print_function diff --git a/gitup/update.py b/gitup/update.py index 9feb875..e58f850 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2014 Ben Kurtovic +# Copyright (C) 2011-2015 Ben Kurtovic # See the LICENSE file for details. from __future__ import print_function diff --git a/setup.py b/setup.py index 5bec3dd..4c6f565 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (C) 2011-2014 Ben Kurtovic +# Copyright (C) 2011-2015 Ben Kurtovic # See the LICENSE file for details. import sys From 8439805e38c44c96ba8cc22daa33d0f1d0e5d024 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 29 May 2015 21:30:43 -0400 Subject: [PATCH 02/12] Wording tweaks; update brew instructions (fixes #13) --- README.md | 8 ++++---- gitup/__init__.py | 2 +- gitup/config.py | 2 +- gitup/script.py | 13 +++++++------ gitup/update.py | 2 +- setup.py | 4 ++-- 6 files changed, 16 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 05c345b..49b4ffd 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ version of git and at least Python 2.7 installed. With [Homebrew](http://brew.sh/): - brew install pr0d1r2/contrib/gitup && brew link gitup + brew install gitup ## From source @@ -85,9 +85,9 @@ By default, gitup will fetch all remotes in a repository. Pass `--current-only` (or `-c`) to make it only fetch the remote tracked by the current branch. gitup will _merge_ upstream branches by default unless `pull.rebase` or -`branch..rebase` is specified in git's config. Pass `--rebase` or `-r` to -make it always _rebase_ (like doing `git pull --rebase=preserve`). Pass -`--merge` or `-m` to make it always merge. +`branch..rebase` is specified in your git config. Pass `--rebase` or `-r` +to make it always _rebase_ (this is like doing `git pull --rebase=preserve`). +Pass `--merge` or `-m` to make it always merge. For a list of all command arguments and abbreviations: diff --git a/gitup/__init__.py b/gitup/__init__.py index d259d59..0e4e734 100644 --- a/gitup/__init__.py +++ b/gitup/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2011-2015 Ben Kurtovic -# See the LICENSE file for details. +# Released under the terms of the MIT License. See LICENSE for details. """ gitup: the git repository updater diff --git a/gitup/config.py b/gitup/config.py index 9fed6f4..c34dcb4 100644 --- a/gitup/config.py +++ b/gitup/config.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2011-2015 Ben Kurtovic -# See the LICENSE file for details. +# Released under the terms of the MIT License. See LICENSE for details. from __future__ import print_function diff --git a/gitup/script.py b/gitup/script.py index 42e78a5..7349c97 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2011-2015 Ben Kurtovic -# See the LICENSE file for details. +# Released under the terms of the MIT License. See LICENSE for details. from __future__ import print_function @@ -9,7 +9,7 @@ import argparse from colorama import init as color_init, Style -from . import __version__, __email__ +from . import __version__ from .config import (get_bookmarks, add_bookmarks, delete_bookmarks, list_bookmarks) from .update import update_bookmarks, update_directories @@ -17,10 +17,11 @@ from .update import update_bookmarks, update_directories def main(): """Parse arguments and then call the appropriate function(s).""" parser = argparse.ArgumentParser( - description="""Easily update multiple git repositories at once.""", + description="Easily update multiple git repositories at once.", epilog=""" Both relative and absolute paths are accepted by all arguments. - Questions? Comments? Email the author at {0}.""".format(__email__), + 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") @@ -41,7 +42,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=preserve`)""") + in git config (behaves like `git pull --rebase=preserve`)""") rebase_or_merge.add_argument( '-m', '--merge', action="store_true", help="""like --rebase, but merge instead""") @@ -59,7 +60,7 @@ 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__) + version="gitup " + __version__) color_init(autoreset=True) args = parser.parse_args() diff --git a/gitup/update.py b/gitup/update.py index e58f850..f0b8ae6 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2011-2015 Ben Kurtovic -# See the LICENSE file for details. +# Released under the terms of the MIT License. See LICENSE for details. from __future__ import print_function diff --git a/setup.py b/setup.py index 4c6f565..d61a0d9 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # # Copyright (C) 2011-2015 Ben Kurtovic -# See the LICENSE file for details. +# Released under the terms of the MIT License. See LICENSE for details. import sys @@ -23,7 +23,7 @@ setup( version = __version__, author = "Ben Kurtovic", author_email = "ben.kurtovic@gmail.com", - description = "Easily pull to multiple git repositories at once.", + description = "Easily pull to multiple git repositories at once", long_description = long_desc, license = "MIT License", keywords = "git repository pull update", From a9e2a1fe8df336472c80d66b3484e0eb7158ac9d Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 5 Jun 2015 19:04:18 -0400 Subject: [PATCH 03/12] Don't try to fetch remotes without configured refspecs. --- gitup/update.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/gitup/update.py b/gitup/update.py index f0b8ae6..456f277 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -94,6 +94,12 @@ def _fetch_remotes(remotes): for remote in remotes: print(INDENT2, "Fetching", BOLD + remote.name, end="") + + config_attr = "remote.{0}.fetch".format(remote.name) + if not _read_config(remote.repo, config_attr): + print(":", YELLOW + "skipped:", "no configured refspec.") + continue + try: results = remote.fetch(progress=_ProgressMonitor()) except exc.GitCommandError as err: From 2791418b9f049373113f7796a2ded13bfe5b9fd9 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 5 Jun 2015 20:04:25 -0400 Subject: [PATCH 04/12] Better solution to previous commit. --- gitup/update.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/gitup/update.py b/gitup/update.py index 456f277..15bedd4 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -95,8 +95,7 @@ def _fetch_remotes(remotes): for remote in remotes: print(INDENT2, "Fetching", BOLD + remote.name, end="") - config_attr = "remote.{0}.fetch".format(remote.name) - if not _read_config(remote.repo, config_attr): + if not remote.config_reader.has_option("fetch"): print(":", YELLOW + "skipped:", "no configured refspec.") continue From 5a4e49ea56ff7619df09ef99b83401f11e7cbe6a Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 5 Jun 2015 21:14:59 -0400 Subject: [PATCH 05/12] Don't update branches for bare repos (#14) --- gitup/update.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/gitup/update.py b/gitup/update.py index 15bedd4..5a3145c 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -237,12 +237,13 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False): else: remotes = repo.remotes if not remotes: - print(INDENT2, ERROR, "no remotes configured to pull from.") + print(INDENT2, ERROR, "no remotes configured to fetch.") return rebase = rebase or _read_config(repo, "pull.rebase") _fetch_remotes(remotes) - _update_branches(repo, active, merge, rebase) + if not repo.bare: + _update_branches(repo, active, merge, rebase) def _update_subdirectories(path, long_name, update_args): """Update all subdirectories that are git repos in a given directory.""" From fddc0cb465f2b6e95744c74ef0a5180758135edb Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Fri, 5 Jun 2015 22:10:12 -0400 Subject: [PATCH 06/12] Better error message when mixing --current-only with a detached HEAD. --- gitup/update.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/gitup/update.py b/gitup/update.py index 5a3145c..4ddd198 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -229,20 +229,25 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False): except TypeError: # Happens when HEAD is detached active = None if current_only: - ref = active.tracking_branch() if active else None + if not active: + print(INDENT2, ERROR, + "--current-only doesn't make sense with a detached HEAD.") + return + 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 + if not remotes: print(INDENT2, ERROR, "no remotes configured to fetch.") return - rebase = rebase or _read_config(repo, "pull.rebase") - _fetch_remotes(remotes) + if not repo.bare: + rebase = rebase or _read_config(repo, "pull.rebase") _update_branches(repo, active, merge, rebase) def _update_subdirectories(path, long_name, update_args): From 31a94772ff995b69e4ea3ca3fdb6e64519134cbb Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sat, 6 Jun 2015 02:38:36 -0400 Subject: [PATCH 07/12] Rework branch updating code to be safer and support bare repos (#14) * Only update _fast-forwardable_ branches to their upstreams. This uses `git merge --no-ff` for the active branch and `git branch --force` for others. Checks for clean working directories and ancestry are performed as expected. * Added --fetch-only / -f to disable branch updating entirely, if desired. * Deprecated --merge and --rebase. These have no effect now and instead give a warning. --- README.md | 12 ++--- gitup/script.py | 29 +++++++----- gitup/update.py | 137 ++++++++++++-------------------------------------------- 3 files changed, 53 insertions(+), 125 deletions(-) diff --git a/README.md b/README.md index 49b4ffd..849a4fb 100644 --- a/README.md +++ b/README.md @@ -82,14 +82,14 @@ 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 only fetch the remote tracked by the current branch. +(or `-c`) to make it fetch _only_ the remote tracked by the current branch. -gitup will _merge_ upstream branches by default unless `pull.rebase` or -`branch..rebase` is specified in your git config. Pass `--rebase` or `-r` -to make it always _rebase_ (this is like doing `git pull --rebase=preserve`). -Pass `--merge` or `-m` to make it always merge. +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. -For a list of all command arguments and abbreviations: +For a full list of all command arguments and abbreviations: gitup --help diff --git a/gitup/script.py b/gitup/script.py index 7349c97..496767e 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -7,7 +7,7 @@ from __future__ import print_function import argparse -from colorama import init as color_init, Style +from colorama import init as color_init, Fore, Style from . import __version__ from .config import (get_bookmarks, add_bookmarks, delete_bookmarks, @@ -27,7 +27,6 @@ def main(): group_u = parser.add_argument_group("updating repositories") group_b = parser.add_argument_group("bookmarking") group_m = parser.add_argument_group("miscellaneous") - rebase_or_merge = group_u.add_mutually_exclusive_group() group_u.add_argument( 'directories_to_update', nargs="*", metavar="path", @@ -39,13 +38,9 @@ def main(): group_u.add_argument( '-c', '--current-only', action="store_true", help="""only fetch the remote tracked by the current branch instead of all remotes""") - 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 (behaves like `git pull --rebase=preserve`)""") - rebase_or_merge.add_argument( - '-m', '--merge', action="store_true", help="""like --rebase, but merge - instead""") + group_u.add_argument( + '-f', '--fetch-only', action="store_true", help="""only fetch remotes, + don't try to fast-forward any branches""") group_b.add_argument( '-a', '--add', dest="bookmarks_to_add", nargs="+", metavar="path", @@ -56,19 +51,33 @@ def main(): group_b.add_argument( '-l', '--list', dest="list_bookmarks", action="store_true", help="list current bookmarks") + group_m.add_argument( '-h', '--help', action="help", help="show this help message and exit") group_m.add_argument( '-v', '--version', action="version", version="gitup " + __version__) + # TODO: deprecated arguments, for removal in v1.0: + parser.add_argument( + '-m', '--merge', action="store_true", help=argparse.SUPPRESS) + parser.add_argument( + '-r', '--rebase', action="store_true", help=argparse.SUPPRESS) + color_init(autoreset=True) args = parser.parse_args() - update_args = args.current_only, args.rebase, args.merge + update_args = args.current_only, args.fetch_only print(Style.BRIGHT + "gitup" + Style.RESET_ALL + ": the git-repo-updater") print() + # TODO: remove in v1.0 + if args.merge or args.rebase: + print(Style.BRIGHT + Fore.YELLOW + "Warning:", "--merge and --rebase " + "are deprecated. Branches are only updated if they\ntrack an " + "upstream branch and can be safely fast-forwarded. Use " + "--fetch-only to\navoid updating any branches.\n") + acted = False if args.bookmarks_to_add: add_bookmarks(args.bookmarks_to_add) diff --git a/gitup/update.py b/gitup/update.py index 4ddd198..a28027c 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -53,34 +53,6 @@ class _ProgressMonitor(RemoteProgress): print(str(cur_count), end=end) -class _Stasher(object): - """Manages the stash state of a given repository.""" - - def __init__(self, repo): - self._repo = repo - self._clean = self._stashed = False - - def clean(self): - """Ensure the working directory is clean, so we can do checkouts.""" - if not self._clean: - res = self._repo.git.stash("--all") - self._clean = True - if res != "No local changes to save": - self._stashed = True - - def restore(self): - """Restore the pre-stash state.""" - if self._stashed: - self._repo.git.stash("pop", "--index") - - -def _read_config(repo, attr): - """Read an attribute from git config.""" - try: - return repo.git.config("--get", attr) - except exc.GitCommandError: - return None - def _fetch_remotes(remotes): """Fetch a list of remotes, displaying progress info along the way.""" def _get_name(ref): @@ -122,56 +94,7 @@ def _fetch_remotes(remotes): rlist.append("{0} ({1})".format(colored, ", ".join(names))) print(":", (", ".join(rlist) if rlist else up_to_date) + ".") -def _is_up_to_date(repo, branch, upstream): - """Return whether *branch* is up-to-date with its *upstream*.""" - base = repo.git.merge_base(branch.commit, upstream.commit) - return repo.commit(base) == upstream.commit - -def _rebase(repo, name): - """Rebase the current HEAD of *repo* onto the branch *name*.""" - print(GREEN + "rebasing...", end="") - try: - res = repo.git.rebase(name, "--preserve-merges") - except exc.GitCommandError as err: - msg = err.stderr.replace("\n", " ").strip() - if not msg.endswith("."): - msg += "." - if "unstaged changes" in msg: - print(RED + " error:", "unstaged changes.") - elif "uncommitted changes" in msg: - print(RED + " error:", "uncommitted changes.") - else: - try: - repo.git.rebase("--abort") - except exc.GitCommandError: - pass - print(RED + " error:", msg if msg else "rebase conflict.", - "Aborted.") - else: - print("\b" * 6 + " " * 6 + "\b" * 6 + GREEN + "ed", end=".\n") - -def _merge(repo, name): - """Merge the branch *name* into the current HEAD of *repo*.""" - print(GREEN + "merging...", end="") - try: - repo.git.merge(name) - except exc.GitCommandError as err: - msg = err.stderr.replace("\n", " ").strip() - if not msg.endswith("."): - msg += "." - if "local changes" in msg and "would be overwritten" in msg: - print(RED + " error:", "uncommitted changes.") - else: - try: - repo.git.merge("--abort") - except exc.GitCommandError: - pass - print(RED + " error:", msg if msg else "merge conflict.", - "Aborted.") - else: - print("\b" * 6 + " " * 6 + "\b" * 6 + GREEN + "ed", end=".\n") - -def _update_branch(repo, branch, merge, rebase, stasher=None): +def _update_branch(repo, branch, is_active=False): """Update a single branch.""" print(INDENT2, "Updating", BOLD + branch.name, end=": ") upstream = branch.tracking_branch() @@ -184,43 +107,39 @@ def _update_branch(repo, branch, merge, rebase, stasher=None): except ValueError: print(YELLOW + "skipped:", "branch has no revisions.") return - if _is_up_to_date(repo, branch, upstream): + + base = repo.git.merge_base(branch.commit, upstream.commit) + if repo.commit(base) == upstream.commit: print(BLUE + "up to date", end=".\n") return - if stasher: - stasher.clean() - branch.checkout() - config_attr = "branch.{0}.rebase".format(branch.name) - if not merge and (rebase or _read_config(repo, config_attr)): - _rebase(repo, upstream.name) - else: - _merge(repo, upstream.name) - -def _update_branches(repo, active, merge, rebase): - """Update a list of branches.""" - if active: - _update_branch(repo, active, merge, rebase) - branches = set(repo.heads) - {active} - if branches: - stasher = _Stasher(repo) + if is_active: try: - for branch in sorted(branches, key=lambda b: b.name): - _update_branch(repo, branch, merge, rebase, stasher) - finally: - if active: - active.checkout() - stasher.restore() + repo.git.merge(upstream.name, ff_only=True) + print(GREEN + "done", end=".\n") + except exc.GitCommandError as err: + msg = err.stderr + if "local changes" in msg and "would be overwritten" in msg: + print(YELLOW + "skipped:", "uncommitted changes.") + else: + 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] + 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, current_only=False, rebase=False, merge=False): +def _update_repository(repo, current_only=False, fetch_only=False): """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``. 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. + current branch if ``True``. If *fetch_only* is ``False``, we will also + update all fast-forwardable branches that are tracking valid upstreams. """ print(INDENT1, BOLD + os.path.split(repo.working_dir)[1] + ":") @@ -246,9 +165,9 @@ def _update_repository(repo, current_only=False, rebase=False, merge=False): return _fetch_remotes(remotes) - if not repo.bare: - rebase = rebase or _read_config(repo, "pull.rebase") - _update_branches(repo, active, merge, rebase) + 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, long_name, update_args): """Update all subdirectories that are git repos in a given directory.""" From f6cd164400924a3d2fff21ea9986c0eb6b339461 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Jun 2015 00:18:34 -0400 Subject: [PATCH 08/12] Add Python 3 support (closes #16) --- README.md | 2 +- gitup/config.py | 6 +++++- setup.py | 6 ++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 849a4fb..0d4e9da 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ 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 should work on OS X, Linux, and Windows. You should have the latest -version of git and at least Python 2.7 installed. +version of git and either Python 2.7 or Python 3 installed. # Installation diff --git a/gitup/config.py b/gitup/config.py index c34dcb4..0120680 100644 --- a/gitup/config.py +++ b/gitup/config.py @@ -5,9 +5,13 @@ from __future__ import print_function -import ConfigParser as configparser 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", diff --git a/setup.py b/setup.py index d61a0d9..2098ffd 100644 --- a/setup.py +++ b/setup.py @@ -35,8 +35,14 @@ setup( "Natural Language :: English", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", + "Operating System :: Microsoft :: Windows", "Programming Language :: Python", "Programming Language :: Python :: 2.7", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.2", + "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", "Topic :: Software Development :: Version Control" ] ) From 8990580cd0c31bddf65e89973e7ba0e3bf1085c5 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Jun 2015 01:33:38 -0400 Subject: [PATCH 09/12] Added a changelog. --- CHANGELOG | 37 +++++++++++++++++++++++++++++++++++++ gitup/__init__.py | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 CHANGELOG diff --git a/CHANGELOG b/CHANGELOG new file mode 100644 index 0000000..6889f0e --- /dev/null +++ b/CHANGELOG @@ -0,0 +1,37 @@ +v0.3 (unreleased): + +- Added support for Python 3. +- Fixed behavior on bare repositories. +- Made branch updating code safer in general: only fast-forwardable branches + tracking upstreams are updated. This deprecates `--merge` and `--rebase`. +- Added `--fetch-only` to disable branch updating entirely, if desired. +- Fixed trying to fetch remotes without configured refspecs. +- Miscellaneous fixes and tweaks. + +v0.2.4 (released May 23, 2015): + +- Follow the XDG Base Directory Specification for the config file. +- Added installation instructions for Homebrew. + +v0.2.3 (released March 14, 2015): + +- Added support for newer versions of GitPython. + +v0.2.2 (released April 27, 2014): + +- Fixed an error being raised when HEAD is detached. + +v0.2.1 (released April 21, 2014): + +- Fixed a bug when handling errors during a fetch. + +v0.2 (released April 21, 2014): + +- Rewrote backend to use GitPython instead of direct shell calls. Improved + stability and fixed various bugs. +- Use colorama for highlighting instead of ANSI escape codes. +- Added `--current-only`, `--merge`, and `--rebase` options. + +v0.1 (released June 7, 2011): + +- Initial release. diff --git a/gitup/__init__.py b/gitup/__init__.py index 0e4e734..9a1d295 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-2015 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.2.5.dev0" +__version__ = "0.3.dev0" __email__ = "ben.kurtovic@gmail.com" From 9255ffa2155eaa0a9bd8e668de4f254ded0e7af8 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Jun 2015 15:18:18 -0400 Subject: [PATCH 10/12] Wording cleanup. --- gitup/config.py | 2 +- gitup/update.py | 28 +++++++++++----------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/gitup/config.py b/gitup/config.py index 0120680..9e06c20 100644 --- a/gitup/config.py +++ b/gitup/config.py @@ -62,7 +62,7 @@ def get_bookmarks(): """Get a list of all bookmarks, or an empty list if there are none.""" config = _load_config_file() try: - return config.items("bookmarks") + return [path for path, _ in config.items("bookmarks")] except configparser.NoSectionError: return [] diff --git a/gitup/update.py b/gitup/update.py index a28027c..a121a67 100644 --- a/gitup/update.py +++ b/gitup/update.py @@ -169,7 +169,7 @@ def _update_repository(repo, current_only=False, fetch_only=False): for branch in sorted(repo.heads, key=lambda b: b.name): _update_branch(repo, branch, branch == active) -def _update_subdirectories(path, long_name, update_args): +def _update_subdirectories(path, update_args): """Update all subdirectories that are git repos in a given directory.""" repos = [] for item in os.listdir(path): @@ -179,13 +179,12 @@ def _update_subdirectories(path, long_name, update_args): continue repos.append(repo) - suffix = "ies" if len(repos) != 1 else "y" - print(long_name[0].upper() + long_name[1:], - "contains {0} git repositor{1}:".format(len(repos), 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) -def _update_directory(path, update_args, is_bookmark=False): +def _update_directory(path, update_args): """Update a particular directory. Determine whether the directory is a git repo on its own, a directory of @@ -193,29 +192,24 @@ def _update_directory(path, update_args, is_bookmark=False): repository; if the second, update all repositories contained within; if the third, print an error. """ - dir_type = "bookmark" if is_bookmark else "directory" - long_name = dir_type + ' "' + BOLD + path + RESET + '"' - try: repo = Repo(path) except exc.NoSuchPathError: - print(ERROR, long_name, "doesn't exist!") + print(ERROR, BOLD + path, "doesn't exist!") except exc.InvalidGitRepositoryError: if os.path.isdir(path): - _update_subdirectories(path, long_name, update_args) + _update_subdirectories(path, update_args) else: - print(ERROR, long_name, "isn't a repository!") + print(ERROR, BOLD + path, "isn't a repository!") else: - long_name = (dir_type.capitalize() + ' "' + BOLD + repo.working_dir + - RESET + '"') - print(long_name, "is a git repository:") + print(BOLD + repo.working_dir, "(1 repo):") _update_repository(repo, *update_args) def update_bookmarks(bookmarks, update_args): """Loop through and update all bookmarks.""" if bookmarks: - for path, name in bookmarks: - _update_directory(path, update_args, is_bookmark=True) + for path in bookmarks: + _update_directory(path, update_args) else: print("You don't have any bookmarks configured! Get help with 'gitup -h'.") @@ -223,4 +217,4 @@ 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, is_bookmark=False) + _update_directory(full_path, update_args) From 3e238f1ad347764be5819d90361670bf53c95cc1 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Jun 2015 15:20:35 -0400 Subject: [PATCH 11/12] Bugfix from previous commit. --- gitup/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gitup/config.py b/gitup/config.py index 9e06c20..f1cab54 100644 --- a/gitup/config.py +++ b/gitup/config.py @@ -123,7 +123,7 @@ def list_bookmarks(): bookmarks = get_bookmarks() if bookmarks: print(YELLOW + "Current bookmarks:") - for bookmark_path, _ in bookmarks: + for bookmark_path in bookmarks: print(INDENT1, bookmark_path) else: print("You have no bookmarks to display.") From 10494e677bba19622acfa3fc62093a06451c8562 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Sun, 7 Jun 2015 15:28:35 -0400 Subject: [PATCH 12/12] release/0.3 --- CHANGELOG | 2 +- gitup/__init__.py | 2 +- gitup/script.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 6889f0e..202265f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,4 @@ -v0.3 (unreleased): +v0.3 (released June 7, 2015): - Added support for Python 3. - Fixed behavior on bare repositories. diff --git a/gitup/__init__.py b/gitup/__init__.py index 9a1d295..f031df0 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-2015 Ben Kurtovic" __license__ = "MIT License" -__version__ = "0.3.dev0" +__version__ = "0.3" __email__ = "ben.kurtovic@gmail.com" diff --git a/gitup/script.py b/gitup/script.py index 496767e..d353412 100644 --- a/gitup/script.py +++ b/gitup/script.py @@ -39,8 +39,8 @@ def main(): '-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_b.add_argument( '-a', '--add', dest="bookmarks_to_add", nargs="+", metavar="path",