@@ -1,3 +1,18 @@ | |||||
v0.5 (released August 28, 2018): | |||||
- 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. | |||||
- 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): | v0.4.1 (released December 13, 2017): | ||||
- Bump dependencies to deal with newer versions of Git. | - Bump dependencies to deal with newer versions of Git. | ||||
@@ -13,7 +28,7 @@ v0.4 (released January 17, 2017): | |||||
- Cleaned up the bookmark file format, fixing a related Windows bug. The script | - Cleaned up the bookmark file format, fixing a related Windows bug. The script | ||||
will automatically migrate to the new one. | will automatically migrate to the new one. | ||||
- Fixed a bug related to Python 3 compatibility. | - Fixed a bug related to Python 3 compatibility. | ||||
- Fixed unicode support. | |||||
- Fixed Unicode support. | |||||
v0.3 (released June 7, 2015): | v0.3 (released June 7, 2015): | ||||
@@ -1,4 +1,4 @@ | |||||
Copyright (C) 2011-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
Permission is hereby granted, free of charge, to any person obtaining a copy | Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
of this software and associated documentation files (the "Software"), to deal | of this software and associated documentation files (the "Software"), to deal | ||||
@@ -1,9 +1,9 @@ | |||||
__gitup__ (the _git-repo-updater_) | __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 | 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. | 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 | 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 | python setup.py install --user | ||||
@@ -53,7 +53,7 @@ Additionally, you can just type: | |||||
to automatically update all git repositories in that directory. | 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/foo ~/repos/bar ~/repos/baz | ||||
gitup --add ~/repos | gitup --add ~/repos | ||||
@@ -81,21 +81,25 @@ Update all git repositories in your current directory: | |||||
gitup . | 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` | 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 | 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 | upstreams configured. It will always skip branches where this is not possible | ||||
(e.g. dirty working directory or a merge/rebase is required). Pass | (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 | 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 | upstream. Pass `--prune` (or `-p`) to delete them, or set `fetch.prune` or | ||||
`remote.<name>.prune` in git config to do this by default. | |||||
`remote.<name>.prune` in your git config to do this by default. | |||||
For a full list of all command arguments and abbreviations: | For a full list of all command arguments and abbreviations: | ||||
gitup --help | gitup --help | ||||
Finally, all paths can be either absolute (e.g. `/path/to/repo`) or relative | |||||
(e.g. `../my/repo`). |
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# | # | ||||
# Copyright (C) 2011-2017 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | # Released under the terms of the MIT License. See LICENSE for details. | ||||
""" | """ | ||||
@@ -8,7 +8,7 @@ gitup: the git repository updater | |||||
""" | """ | ||||
__author__ = "Ben Kurtovic" | __author__ = "Ben Kurtovic" | ||||
__copyright__ = "Copyright (C) 2011-2017 Ben Kurtovic" | |||||
__copyright__ = "Copyright (C) 2011-2018 Ben Kurtovic" | |||||
__license__ = "MIT License" | __license__ = "MIT License" | ||||
__version__ = "0.4.1" | |||||
__version__ = "0.5" | |||||
__email__ = "ben.kurtovic@gmail.com" | __email__ = "ben.kurtovic@gmail.com" |
@@ -0,0 +1,9 @@ | |||||
# -*- coding: utf-8 -*- | |||||
# | |||||
# Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | |||||
from .script import run | |||||
if __name__ == "__main__": | |||||
run() |
@@ -1,12 +1,13 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# | # | ||||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | # Released under the terms of the MIT License. See LICENSE for details. | ||||
from __future__ import print_function | from __future__ import print_function | ||||
import argparse | import argparse | ||||
import os | import os | ||||
import platform | |||||
import sys | import sys | ||||
from colorama import init as color_init, Fore, Style | from colorama import init as color_init, Fore, Style | ||||
@@ -39,12 +40,16 @@ def main(): | |||||
group_u.add_argument( | group_u.add_argument( | ||||
'directories_to_update', nargs="*", metavar="path", type=_decode, | '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( | group_u.add_argument( | ||||
'-u', '--update', action="store_true", help="""update all bookmarks | '-u', '--update', action="store_true", help="""update all bookmarks | ||||
(default behavior when called without arguments)""") | (default behavior when called without arguments)""") | ||||
group_u.add_argument( | 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 | '-c', '--current-only', action="store_true", help="""only fetch the | ||||
remote tracked by the current branch instead of all remotes""") | remote tracked by the current branch instead of all remotes""") | ||||
group_u.add_argument( | group_u.add_argument( | ||||
@@ -80,7 +85,8 @@ def main(): | |||||
'-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( | group_m.add_argument( | ||||
'-v', '--version', action="version", | '-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: | # TODO: deprecated arguments, for removal in v1.0: | ||||
parser.add_argument( | parser.add_argument( | ||||
@@ -90,7 +96,6 @@ def main(): | |||||
color_init(autoreset=True) | color_init(autoreset=True) | ||||
args = parser.parse_args() | 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(Style.BRIGHT + "gitup" + Style.RESET_ALL + ": the git-repo-updater") | ||||
print() | print() | ||||
@@ -121,15 +126,15 @@ def main(): | |||||
if args.command: | if args.command: | ||||
if args.directories_to_update: | 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: | 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: | else: | ||||
if args.directories_to_update: | if args.directories_to_update: | ||||
update_directories(args.directories_to_update, update_args) | |||||
update_directories(args.directories_to_update, args) | |||||
acted = True | acted = True | ||||
if args.update or not acted: | 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(): | def run(): | ||||
"""Thin wrapper for main() that catches KeyboardInterrupts.""" | """Thin wrapper for main() that catches KeyboardInterrupts.""" | ||||
@@ -1,12 +1,14 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# | # | ||||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | # Released under the terms of the MIT License. See LICENSE for details. | ||||
from __future__ import print_function | from __future__ import print_function | ||||
from glob import glob | from glob import glob | ||||
import os | import os | ||||
import pipes | |||||
import re | |||||
import shlex | import shlex | ||||
from colorama import Fore, Style | from colorama import Fore, Style | ||||
@@ -77,8 +79,14 @@ def _fetch_remotes(remotes, prune): | |||||
try: | try: | ||||
results = remote.fetch(progress=_ProgressMonitor(), prune=prune) | results = remote.fetch(progress=_ProgressMonitor(), prune=prune) | ||||
except exc.GitCommandError as err: | 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 += "." | msg += "." | ||||
print(":", RED + "error:", msg) | print(":", RED + "error:", msg) | ||||
return | return | ||||
@@ -115,7 +123,12 @@ def _update_branch(repo, branch, is_active=False): | |||||
print(YELLOW + "skipped:", "upstream does not exist.") | print(YELLOW + "skipped:", "upstream does not exist.") | ||||
return | 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: | if repo.commit(base) == upstream.commit: | ||||
print(BLUE + "up to date", end=".\n") | print(BLUE + "up to date", end=".\n") | ||||
return | return | ||||
@@ -140,23 +153,23 @@ def _update_branch(repo, branch, is_active=False): | |||||
repo.git.branch(branch.name, upstream.name, force=True) | repo.git.branch(branch.name, upstream.name, force=True) | ||||
print(GREEN + "done", end=".\n") | print(GREEN + "done", end=".\n") | ||||
def _update_repository(repo, current_only, fetch_only, prune): | |||||
def _update_repository(repo, repo_name, args): | |||||
"""Update a single git repository by fetching remotes and rebasing/merging. | """Update a single git repository by fetching remotes and rebasing/merging. | ||||
The specific actions depend on the arguments given. We will fetch all | 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 + os.path.split(repo.working_dir)[1] + ":") | |||||
print(INDENT1, BOLD + repo_name + ":") | |||||
try: | try: | ||||
active = repo.active_branch | active = repo.active_branch | ||||
except TypeError: # Happens when HEAD is detached | except TypeError: # Happens when HEAD is detached | ||||
active = None | active = None | ||||
if current_only: | |||||
if args.current_only: | |||||
if not active: | if not active: | ||||
print(INDENT2, ERROR, | print(INDENT2, ERROR, | ||||
"--current-only doesn't make sense with a detached HEAD.") | "--current-only doesn't make sense with a detached HEAD.") | ||||
@@ -172,17 +185,17 @@ def _update_repository(repo, current_only, fetch_only, prune): | |||||
if not remotes: | if not remotes: | ||||
print(INDENT2, ERROR, "no remotes configured to fetch.") | print(INDENT2, ERROR, "no remotes configured to fetch.") | ||||
return | 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): | for branch in sorted(repo.heads, key=lambda b: b.name): | ||||
_update_branch(repo, branch, branch == active) | _update_branch(repo, branch, branch == active) | ||||
def _run_command(repo, command): | |||||
def _run_command(repo, repo_name, args): | |||||
"""Run an arbitrary shell command on the given repository.""" | """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) | |||||
cmd = shlex.split(args.command) | |||||
try: | try: | ||||
out = repo.git.execute( | out = repo.git.execute( | ||||
cmd, with_extended_output=True, with_exceptions=False) | cmd, with_extended_output=True, with_exceptions=False) | ||||
@@ -193,24 +206,7 @@ def _run_command(repo, command): | |||||
for line in out[1].splitlines() + out[2].splitlines(): | for line in out[1].splitlines() + out[2].splitlines(): | ||||
print(INDENT2, line) | 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. | """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 | Determine whether the directory is a git repo on its own, a directory of | ||||
@@ -220,40 +216,81 @@ def _dispatch(path, callback, *args): | |||||
The given args are passed directly to the callback function after the repo. | The given args are passed directly to the callback function after the repo. | ||||
""" | """ | ||||
path = os.path.expanduser(path) | |||||
def _collect(paths, max_depth): | |||||
"""Return all valid repo paths in the given paths, recursively.""" | |||||
if max_depth == 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, max_depth - 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) | |||||
max_depth = args.max_depth | |||||
if max_depth >= 0: | |||||
max_depth += 1 | |||||
try: | try: | ||||
repo = Repo(path) | |||||
Repo(base) | |||||
valid = [base] | |||||
except exc.NoSuchPathError: | 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, max_depth) | |||||
except exc.InvalidGitRepositoryError: | 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) or args.max_depth == 0: | |||||
print(ERROR, BOLD + base, "isn't a repository!") | |||||
return | |||||
valid = _collect([base], max_depth) | |||||
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): | |||||
def update_bookmarks(bookmarks, args): | |||||
"""Loop through and update all bookmarks.""" | """Loop through and update all bookmarks.""" | ||||
if not bookmarks: | if not bookmarks: | ||||
print("You don't have any bookmarks configured! Get help with 'gitup -h'.") | print("You don't have any bookmarks configured! Get help with 'gitup -h'.") | ||||
return | return | ||||
for path in bookmarks: | 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.""" | """Update a list of directories supplied by command arguments.""" | ||||
for path in paths: | 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.""" | """Run an arbitrary shell command on all repos.""" | ||||
for path in paths: | for path in paths: | ||||
_dispatch(path, _run_command, command) | |||||
_dispatch(path, _run_command, args) |
@@ -1,6 +1,6 @@ | |||||
# -*- coding: utf-8 -*- | # -*- coding: utf-8 -*- | ||||
# | # | ||||
# Copyright (C) 2011-2016 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Copyright (C) 2011-2018 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
# Released under the terms of the MIT License. See LICENSE for details. | # Released under the terms of the MIT License. See LICENSE for details. | ||||
import sys | import sys | ||||
@@ -8,7 +8,7 @@ import sys | |||||
from setuptools import setup, find_packages | from setuptools import setup, find_packages | ||||
if sys.hexversion < 0x02070000: | if sys.hexversion < 0x02070000: | ||||
exit("Please upgrade to Python 2.7 or greater: <http://python.org/>.") | |||||
exit("Please upgrade to Python 2.7 or greater: <https://www.python.org/>.") | |||||
from gitup import __version__ | from gitup import __version__ | ||||
@@ -23,18 +23,19 @@ setup( | |||||
version = __version__, | version = __version__, | ||||
author = "Ben Kurtovic", | author = "Ben Kurtovic", | ||||
author_email = "ben.kurtovic@gmail.com", | 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, | long_description = long_desc, | ||||
license = "MIT License", | license = "MIT License", | ||||
keywords = "git repository pull update", | keywords = "git repository pull update", | ||||
url = "http://github.com/earwig/git-repo-updater", | |||||
url = "https://github.com/earwig/git-repo-updater", | |||||
classifiers = [ | classifiers = [ | ||||
"Development Status :: 4 - Beta", | |||||
"Environment :: Console", | "Environment :: Console", | ||||
"Intended Audience :: Developers", | "Intended Audience :: Developers", | ||||
"License :: OSI Approved :: MIT License", | "License :: OSI Approved :: MIT License", | ||||
"Natural Language :: English", | "Natural Language :: English", | ||||
"Operating System :: MacOS :: MacOS X", | "Operating System :: MacOS :: MacOS X", | ||||
"Operating System :: POSIX :: Linux", | |||||
"Operating System :: POSIX", | |||||
"Operating System :: Microsoft :: Windows", | "Operating System :: Microsoft :: Windows", | ||||
"Programming Language :: Python", | "Programming Language :: Python", | ||||
"Programming Language :: Python :: 2.7", | "Programming Language :: Python :: 2.7", | ||||
@@ -44,6 +45,7 @@ setup( | |||||
"Programming Language :: Python :: 3.4", | "Programming Language :: Python :: 3.4", | ||||
"Programming Language :: Python :: 3.5", | "Programming Language :: Python :: 3.5", | ||||
"Programming Language :: Python :: 3.6", | "Programming Language :: Python :: 3.6", | ||||
"Topic :: Software Development :: Version Control" | |||||
"Programming Language :: Python :: 3.7", | |||||
"Topic :: Software Development :: Version Control :: Git" | |||||
] | ] | ||||
) | ) |