From e74944165ab2b0cdd7a516ad4703e0a921ab3ad9 Mon Sep 17 00:00:00 2001 From: Ben Kurtovic Date: Thu, 20 Feb 2014 03:41:58 -0500 Subject: [PATCH] Starting work on the new package structure. --- gitup/__init__.py | 16 ++++ gitup.py => gitup/script.py | 196 +++++++++++++++++++++----------------------- setup.py | 58 ++++++------- 3 files changed, 138 insertions(+), 132 deletions(-) create mode 100644 gitup/__init__.py rename gitup.py => gitup/script.py (55%) diff --git a/gitup/__init__.py b/gitup/__init__.py new file mode 100644 index 0000000..2d8d6d9 --- /dev/null +++ b/gitup/__init__.py @@ -0,0 +1,16 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011-2014 Ben Kurtovic +# See the LICENSE file for details. + +""" +gitup: the git repository updater +""" + +__author__ = "Ben Kurtovic" +__copyright__ = "Copyright (C) 2011-2014 Ben Kurtovic" +__license__ = "MIT License" +__version__ = "0.2.dev" +__email__ = "ben.kurtovic@gmail.com" + +from . import script diff --git a/gitup.py b/gitup/script.py similarity index 55% rename from gitup.py rename to gitup/script.py index cf6c524..31e4a64 100644 --- a/gitup.py +++ b/gitup/script.py @@ -1,9 +1,9 @@ -#! /usr/bin/python -# -*- coding: utf-8 -*- +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011-2014 Ben Kurtovic +# See the LICENSE file for details. -""" -gitup: the git repository updater -""" +from __future__ import print_function import argparse import ConfigParser as configparser @@ -12,24 +12,20 @@ import re import shlex import subprocess -__author__ = "Ben Kurtovic" -__copyright__ = "Copyright (c) 2011-2014 Ben Kurtovic" -__license__ = "MIT License" -__version__ = "0.2.dev" -__email__ = "ben.kurtovic@gmail.com" +from . import __version__, __email__ config_filename = os.path.join(os.path.expanduser("~"), ".gitup") -# Text formatting functions -bold = lambda t: style_text(t, "bold") -red = lambda t: style_text(t, "red") -green = lambda t: style_text(t, "green") -yellow = lambda t: style_text(t, "yellow") -blue = lambda t: style_text(t, "blue") +# Text formatting functions: +bold = lambda t: _style_text(t, "bold") +red = lambda t: _style_text(t, "red") +green = lambda t: _style_text(t, "green") +yellow = lambda t: _style_text(t, "yellow") +blue = lambda t: _style_text(t, "blue") -def style_text(text, effect): +def _style_text(text, effect): """Give a text string a certain effect, such as boldness, or a color.""" - ansi = { # ANSI escape codes to make terminal output fancy + ansi = { # ANSI escape codes to make terminal output fancy "reset": "\x1b[0m", "bold": "\x1b[1m", "red": "\x1b[1m\x1b[31m", @@ -38,34 +34,34 @@ def style_text(text, effect): "blue": "\x1b[1m\x1b[34m", } - try: # pad text with effect, unless effect does not exist - return "{}{}{}".format(ansi[effect], text, ansi['reset']) + try: # Pad text with effect, unless effect does not exist + return ansi[effect] + text + ansi["reset"] except KeyError: return text def out(indent, msg): """Print a message at a given indentation level.""" - width = 4 # amount to indent at each level + width = 4 # Amount to indent at each level if indent == 0: spacing = "\n" else: spacing = " " * width * indent - msg = re.sub("\s+", " ", msg) # collapse multiple spaces into one - print spacing + msg + msg = re.sub(r"\s+", " ", msg) # Collapse multiple spaces into one + print(spacing + msg) def exec_shell(command): """Execute a shell command and get the output.""" command = shlex.split(command) result = subprocess.check_output(command, stderr=subprocess.STDOUT) if result: - result = result[:-1] # strip newline if command returned anything + result = result[:-1] # Strip newline if command returned anything return result def directory_is_git_repo(directory_path): """Check if a directory is a git repository.""" if os.path.isdir(directory_path): git_subfolder = os.path.join(directory_path, ".git") - if os.path.isdir(git_subfolder): # check for path/to/repository/.git + if os.path.isdir(git_subfolder): # Check for path/to/repository/.git return True return False @@ -73,60 +69,62 @@ def update_repository(repo_path, repo_name): """Update a single git repository by pulling from the remote.""" out(1, bold(repo_name) + ":") - os.chdir(repo_path) # cd into our folder so git commands target the correct - # repo + # cd into our folder so git commands target the correct repo: + os.chdir(repo_path) try: - dry_fetch = exec_shell("git fetch --dry-run") # check if there is - # anything to pull, but - # don't do it yet + # Check if there is anything to pull, but don't do it yet: + dry_fetch = exec_shell("git fetch --dry-run") except subprocess.CalledProcessError: - out(2, red("Error: ") + "cannot fetch; do you have a remote " + - "repository configured correctly?") + out(2, red("Error: ") + "cannot fetch; do you have a remote " \ + "repository configured correctly?") return try: last_commit = exec_shell("git log -n 1 --pretty=\"%ar\"") except subprocess.CalledProcessError: - last_commit = "never" # couldn't get a log, so no commits + last_commit = "never" # Couldn't get a log, so no commits - if not dry_fetch: # no new changes to pull - out(2, blue("No new changes.") + " Last commit was {}.".format( - last_commit)) + if not dry_fetch: # No new changes to pull + out(2, blue("No new changes.") + + " Last commit was {0}.".format(last_commit)) - else: # stuff has happened! + else: # Stuff has happened! out(2, "There are new changes upstream...") status = exec_shell("git status") if status.endswith("nothing to commit, working directory clean"): out(2, green("Pulling new changes...")) result = exec_shell("git pull") - out(2, "The following changes have been made since {}:".format( + out(2, "The following changes have been made since {0}:".format( last_commit)) - print result + print(result) else: - out(2, red("Warning: ") + "you have uncommitted changes in this " + - "repository!") + out(2, red("Warning: ") + + "you have uncommitted changes in this repository!") out(2, "Ignoring.") def update_directory(dir_path, dir_name, is_bookmark=False): - """First, make sure the specified object is actually a directory, then + """Update a particular directory. + + First, make sure the specified object is actually a directory, then determine whether the directory is a git repo on its own or a directory of git repositories. If the former, update the single repository; if the - latter, update all repositories contained within.""" + latter, update all repositories contained within. + """ if is_bookmark: - dir_type = "bookmark" # where did we get this directory from? + dir_type = "bookmark" # Where did we get this directory from? else: dir_type = "directory" - dir_long_name = "{} '{}'".format(dir_type, bold(dir_path)) + dir_long_name = "{0} '{1}'".format(dir_type, bold(dir_path)) try: - os.listdir(dir_path) # test if we can access this directory + os.listdir(dir_path) # Test if we can access this directory except OSError: - out(0, red("Error: ") + "cannot enter {}; does it exist?".format( - dir_long_name)) + out(0, red("Error: ") + + "cannot enter {0}; does it exist?".format(dir_long_name)) return if not os.path.isdir(dir_path): @@ -143,11 +141,11 @@ def update_directory(dir_path, dir_name, is_bookmark=False): else: repositories = [] - dir_contents = os.listdir(dir_path) # get potential repos in directory + dir_contents = os.listdir(dir_path) # Get potential repos in directory for item in dir_contents: repo_path = os.path.join(dir_path, item) repo_name = os.path.join(dir_name, item) - if directory_is_git_repo(repo_path): # filter out non-repositories + if directory_is_git_repo(repo_path): # Filter out non-repositories repositories.append((repo_path, repo_name)) num_of_repos = len(repositories) @@ -155,17 +153,17 @@ def update_directory(dir_path, dir_name, is_bookmark=False): out(0, dir_long_name.capitalize() + " contains 1 git repository:") else: out(0, dir_long_name.capitalize() + - " contains {} git repositories:".format(num_of_repos)) + " contains {0} git repositories:".format(num_of_repos)) - repositories.sort() # go alphabetically instead of randomly + repositories.sort() # Go alphabetically instead of randomly for repo_path, repo_name in repositories: update_repository(repo_path, repo_name) def update_directories(paths): """Update a list of directories supplied by command arguments.""" for path in paths: - path = os.path.abspath(path) # convert relative to absolute path - path_name = os.path.split(path)[1] # directory name; "x" in /path/to/x/ + path = os.path.abspath(path) # Convert relative to absolute path + path_name = os.path.split(path)[1] # Dir name ("x" in /path/to/x/) update_directory(path, path_name, is_bookmark=False) def update_bookmarks(): @@ -179,21 +177,19 @@ def update_bookmarks(): for bookmark_path, bookmark_name in bookmarks: update_directory(bookmark_path, bookmark_name, is_bookmark=True) else: - out(0, "You don't have any bookmarks configured! Get help with " + - "'gitup -h'.") + out(0, "You don't have any bookmarks configured! " \ + "Get help with 'gitup -h'.") def load_config_file(): - """Read the file storing our config options from config_filename and return - the resulting SafeConfigParser() object.""" + """Read the config file and return a SafeConfigParser() object.""" config = configparser.SafeConfigParser() - config.optionxform = str # don't lowercase option names, because we are - # storing paths there + # Don't lowercase option names, because we are storing paths there: + config.optionxform = str config.read(config_filename) return config def save_config_file(config): - """Save our config changes to the config file specified by - config_filename.""" + """Save config changes to the config file specified by config_filename.""" with open(config_filename, "wb") as config_file: config.write(config_file) @@ -206,9 +202,9 @@ def add_bookmarks(paths): out(0, yellow("Added bookmarks:")) for path in paths: - path = os.path.abspath(path) # convert relative to absolute path + path = os.path.abspath(path) # Convert relative to absolute path if config.has_option("bookmarks", path): - out(1, "'{}' is already bookmarked.".format(path)) + out(1, "'{0}' is already bookmarked.".format(path)) else: path_name = os.path.split(path)[1] config.set("bookmarks", path, path_name) @@ -223,12 +219,12 @@ def delete_bookmarks(paths): if config.has_section("bookmarks"): out(0, yellow("Deleted bookmarks:")) for path in paths: - path = os.path.abspath(path) # convert relative to absolute path + path = os.path.abspath(path) # Convert relative to absolute path config_was_changed = config.remove_option("bookmarks", path) if config_was_changed: out(1, bold(path)) else: - out(1, "'{}' is not bookmarked.".format(path)) + out(1, "'{0}' is not bookmarked.".format(path)) save_config_file(config) else: @@ -251,61 +247,59 @@ def list_bookmarks(): def main(): """Parse arguments and then call the appropriate function(s).""" - parser = argparse.ArgumentParser(description="""Easily pull to multiple git - repositories at once.""", epilog="""Both relative and absolute - paths are accepted by all arguments. Questions? Comments? Email the - author at {}.""".format(__email__), add_help=False) + parser = argparse.ArgumentParser( + description="""Easily pull to 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__), + add_help=False) group_u = parser.add_argument_group("updating repositories") group_b = parser.add_argument_group("bookmarking") group_m = parser.add_argument_group("miscellaneous") - group_u.add_argument('directories_to_update', nargs="*", metavar="path", - help="""update all repositories in this directory (or the directory - itself, if it is a repo)""") - - group_u.add_argument('-u', '--update', action="store_true", help="""update - all bookmarks (default behavior when called without arguments)""") - - group_b.add_argument('-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)") - - 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 "+__version__) + group_u.add_argument( + 'directories_to_update', nargs="*", metavar="path", + help="""update all repositories in this directory (or the directory + itself, if it is a repo)""") + group_u.add_argument( + '-u', '--update', action="store_true", help="""update all bookmarks + (default behavior when called without arguments)""") + group_b.add_argument( + '-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)") + 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 " + __version__) args = parser.parse_args() - - print bold("gitup") + ": the git-repo-updater" + print(bold("gitup") + ": the git-repo-updater") if args.bookmarks_to_add: add_bookmarks(args.bookmarks_to_add) - if args.bookmarks_to_del: delete_bookmarks(args.bookmarks_to_del) - if args.list_bookmarks: list_bookmarks() - if args.directories_to_update: update_directories(args.directories_to_update) - if args.update: update_bookmarks() - if not any(vars(args).values()): # if they did not tell us to do anything, - update_bookmarks() # automatically update bookmarks + # If they did not tell us to do anything, automatically update bookmarks: + if not any(vars(args).values()): + update_bookmarks() -if __name__ == "__main__": +def run(): + """Thin wrapper for main() that catches KeyboardInterrupts.""" try: main() except KeyboardInterrupt: diff --git a/setup.py b/setup.py index c571036..3d7b348 100644 --- a/setup.py +++ b/setup.py @@ -1,35 +1,35 @@ -from setuptools import setup -import os +# -*- coding: utf-8 -*- +# +# Copyright (C) 2011-2014 Ben Kurtovic +# See the LICENSE file for details. + import sys +from setuptools import setup, find_packages + if sys.hexversion < 0x02070000: exit("Please upgrade to Python 2.7 or greater: .") -remove_py_extension = True # install script as "gitup" instead of "gitup.py" - -if os.path.exists("gitup"): - remove_py_extension = False -else: - os.rename("gitup.py", "gitup") +from gitup import __version__ -desc = "Easily pull to multiple git repositories at once." +with open('README.md') as fp: + long_desc = fp.read() -with open('README.md') as file: - long_desc = file.read() - -try: - setup( - name = "gitup", - version = "0.1", - scripts = ['gitup'], - author = "Ben Kurtovic", - author_email = "ben.kurtovic@gmail.com", - description = desc, - long_description = long_desc, - license = "MIT License", - keywords = "git repository pull update", - url = "http://github.com/earwig/git-repo-updater", - classifiers = ["Environment :: Console", +setup( + name = "gitup", + packages = find_packages(), + entry_points = {"console_scripts": ["gitup = gitup.script:run"]}, + install_requires = ["GitPython >= 0.3.2.RC1"], + version = __version__, + author = "Ben Kurtovic", + author_email = "ben.kurtovic@gmail.com", + description = "Easily pull to multiple git repositories at once.", + long_description = long_desc, + license = "MIT License", + keywords = "git repository pull update", + url = "http://github.com/earwig/git-repo-updater", + classifiers = [ + "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Natural Language :: English", @@ -38,9 +38,5 @@ try: "Programming Language :: Python", "Programming Language :: Python :: 2.7", "Topic :: Software Development :: Version Control" - ] - ) - -finally: - if remove_py_extension: - os.rename("gitup", "gitup.py") # restore file location + ] +)