Browse Source

Starting work on the new package structure.

tags/v0.2
Ben Kurtovic 10 years ago
parent
commit
e74944165a
3 changed files with 138 additions and 132 deletions
  1. +16
    -0
      gitup/__init__.py
  2. +95
    -101
      gitup/script.py
  3. +27
    -31
      setup.py

+ 16
- 0
gitup/__init__.py View File

@@ -0,0 +1,16 @@
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
# 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

gitup.py → gitup/script.py View File

@@ -1,9 +1,9 @@
#! /usr/bin/python
# -*- coding: utf-8 -*-
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
# See the LICENSE file for details.


"""
gitup: the git repository updater
"""
from __future__ import print_function


import argparse import argparse
import ConfigParser as configparser import ConfigParser as configparser
@@ -12,24 +12,20 @@ import re
import shlex import shlex
import subprocess 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") 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.""" """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", "reset": "\x1b[0m",
"bold": "\x1b[1m", "bold": "\x1b[1m",
"red": "\x1b[1m\x1b[31m", "red": "\x1b[1m\x1b[31m",
@@ -38,34 +34,34 @@ def style_text(text, effect):
"blue": "\x1b[1m\x1b[34m", "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: except KeyError:
return text return text


def out(indent, msg): def out(indent, msg):
"""Print a message at a given indentation level.""" """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: if indent == 0:
spacing = "\n" spacing = "\n"
else: else:
spacing = " " * width * indent 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): def exec_shell(command):
"""Execute a shell command and get the output.""" """Execute a shell command and get the output."""
command = shlex.split(command) command = shlex.split(command)
result = subprocess.check_output(command, stderr=subprocess.STDOUT) result = subprocess.check_output(command, stderr=subprocess.STDOUT)
if result: if result:
result = result[:-1] # strip newline if command returned anything
result = result[:-1] # Strip newline if command returned anything
return result return result


def directory_is_git_repo(directory_path): def directory_is_git_repo(directory_path):
"""Check if a directory is a git repository.""" """Check if a directory is a git repository."""
if os.path.isdir(directory_path): if os.path.isdir(directory_path):
git_subfolder = os.path.join(directory_path, ".git") 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 True
return False return False


@@ -73,60 +69,62 @@ def update_repository(repo_path, repo_name):
"""Update a single git repository by pulling from the remote.""" """Update a single git repository by pulling from the remote."""
out(1, bold(repo_name) + ":") 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: 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: 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 return


try: try:
last_commit = exec_shell("git log -n 1 --pretty=\"%ar\"") last_commit = exec_shell("git log -n 1 --pretty=\"%ar\"")
except subprocess.CalledProcessError: 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...") out(2, "There are new changes upstream...")
status = exec_shell("git status") status = exec_shell("git status")


if status.endswith("nothing to commit, working directory clean"): if status.endswith("nothing to commit, working directory clean"):
out(2, green("Pulling new changes...")) out(2, green("Pulling new changes..."))
result = exec_shell("git pull") 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)) last_commit))
print result
print(result)


else: 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.") out(2, "Ignoring.")


def update_directory(dir_path, dir_name, is_bookmark=False): 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 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 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: if is_bookmark:
dir_type = "bookmark" # where did we get this directory from?
dir_type = "bookmark" # Where did we get this directory from?
else: else:
dir_type = "directory" dir_type = "directory"


dir_long_name = "{} '{}'".format(dir_type, bold(dir_path))
dir_long_name = "{0} '{1}'".format(dir_type, bold(dir_path))


try: 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: 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 return


if not os.path.isdir(dir_path): if not os.path.isdir(dir_path):
@@ -143,11 +141,11 @@ def update_directory(dir_path, dir_name, is_bookmark=False):
else: else:
repositories = [] 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: for item in dir_contents:
repo_path = os.path.join(dir_path, item) repo_path = os.path.join(dir_path, item)
repo_name = os.path.join(dir_name, 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)) repositories.append((repo_path, repo_name))


num_of_repos = len(repositories) 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:") out(0, dir_long_name.capitalize() + " contains 1 git repository:")
else: else:
out(0, dir_long_name.capitalize() + 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: for repo_path, repo_name in repositories:
update_repository(repo_path, repo_name) update_repository(repo_path, repo_name)


def update_directories(paths): def update_directories(paths):
"""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:
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) update_directory(path, path_name, is_bookmark=False)


def update_bookmarks(): def update_bookmarks():
@@ -179,21 +177,19 @@ def update_bookmarks():
for bookmark_path, bookmark_name in bookmarks: for bookmark_path, bookmark_name in bookmarks:
update_directory(bookmark_path, bookmark_name, is_bookmark=True) update_directory(bookmark_path, bookmark_name, is_bookmark=True)
else: 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(): 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 = 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) config.read(config_filename)
return config return config


def save_config_file(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: with open(config_filename, "wb") as config_file:
config.write(config_file) config.write(config_file)


@@ -206,9 +202,9 @@ def add_bookmarks(paths):
out(0, yellow("Added bookmarks:")) out(0, yellow("Added bookmarks:"))


for path in paths: 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): if config.has_option("bookmarks", path):
out(1, "'{}' is already bookmarked.".format(path))
out(1, "'{0}' is already bookmarked.".format(path))
else: else:
path_name = os.path.split(path)[1] path_name = os.path.split(path)[1]
config.set("bookmarks", path, path_name) config.set("bookmarks", path, path_name)
@@ -223,12 +219,12 @@ def delete_bookmarks(paths):
if config.has_section("bookmarks"): if config.has_section("bookmarks"):
out(0, yellow("Deleted bookmarks:")) out(0, yellow("Deleted bookmarks:"))
for path in paths: 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) config_was_changed = config.remove_option("bookmarks", path)
if config_was_changed: if config_was_changed:
out(1, bold(path)) out(1, bold(path))
else: else:
out(1, "'{}' is not bookmarked.".format(path))
out(1, "'{0}' is not bookmarked.".format(path))
save_config_file(config) save_config_file(config)


else: else:
@@ -251,61 +247,59 @@ def list_bookmarks():


def main(): def main():
"""Parse arguments and then call the appropriate function(s).""" """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_u = parser.add_argument_group("updating repositories")
group_b = parser.add_argument_group("bookmarking") group_b = parser.add_argument_group("bookmarking")
group_m = parser.add_argument_group("miscellaneous") 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() args = parser.parse_args()

print bold("gitup") + ": the git-repo-updater"
print(bold("gitup") + ": the git-repo-updater")


if args.bookmarks_to_add: if args.bookmarks_to_add:
add_bookmarks(args.bookmarks_to_add) add_bookmarks(args.bookmarks_to_add)

if args.bookmarks_to_del: if args.bookmarks_to_del:
delete_bookmarks(args.bookmarks_to_del) delete_bookmarks(args.bookmarks_to_del)

if args.list_bookmarks: if args.list_bookmarks:
list_bookmarks() list_bookmarks()

if args.directories_to_update: if args.directories_to_update:
update_directories(args.directories_to_update) update_directories(args.directories_to_update)

if args.update: if args.update:
update_bookmarks() 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: try:
main() main()
except KeyboardInterrupt: except KeyboardInterrupt:

+ 27
- 31
setup.py View File

@@ -1,35 +1,35 @@
from setuptools import setup
import os
# -*- coding: utf-8 -*-
#
# Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com>
# See the LICENSE file for details.

import sys import sys


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: <http://python.org/>.")


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", "Intended Audience :: Developers",
"License :: OSI Approved :: MIT License", "License :: OSI Approved :: MIT License",
"Natural Language :: English", "Natural Language :: English",
@@ -38,9 +38,5 @@ try:
"Programming Language :: Python", "Programming Language :: Python",
"Programming Language :: Python :: 2.7", "Programming Language :: Python :: 2.7",
"Topic :: Software Development :: Version Control" "Topic :: Software Development :: Version Control"
]
)

finally:
if remove_py_extension:
os.rename("gitup", "gitup.py") # restore file location
]
)

Loading…
Cancel
Save