| @@ -1,4 +1,4 @@ | |||||
| Copyright (c) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| Copyright (C) 2011-2014 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 | ||||
| @@ -13,4 +13,4 @@ __license__ = "MIT License" | |||||
| __version__ = "0.2.dev" | __version__ = "0.2.dev" | ||||
| __email__ = "ben.kurtovic@gmail.com" | __email__ = "ben.kurtovic@gmail.com" | ||||
| from . import script, update | |||||
| from . import config, script, update | |||||
| @@ -3,27 +3,34 @@ | |||||
| # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
| # See the LICENSE file for details. | # See the LICENSE file for details. | ||||
| from __future__ import print_function | |||||
| import ConfigParser as configparser | import ConfigParser as configparser | ||||
| import os | import os | ||||
| from .output import out, bold, yellow | |||||
| from colorama import Fore, Style | |||||
| __all__ = ["get_bookmarks", "add_bookmarks", "delete_bookmarks", | __all__ = ["get_bookmarks", "add_bookmarks", "delete_bookmarks", | ||||
| "list_bookmarks"] | "list_bookmarks"] | ||||
| _config_filename = os.path.join(os.path.expanduser("~"), ".gitup") | |||||
| CONFIG_FILENAME = os.path.join(os.path.expanduser("~"), ".gitup") | |||||
| YELLOW = Fore.YELLOW + Style.BRIGHT | |||||
| RED = Fore.RED + Style.BRIGHT | |||||
| INDENT1 = " " * 3 | |||||
| def _load_config_file(): | def _load_config_file(): | ||||
| """Read the config file and return a SafeConfigParser() object.""" | """Read the config file and return a SafeConfigParser() object.""" | ||||
| config = configparser.SafeConfigParser() | config = configparser.SafeConfigParser() | ||||
| # 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.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 config changes to the config file specified by _config_filename.""" | |||||
| with open(_config_filename, "wb") as config_file: | |||||
| """Save config changes to the config file specified by CONFIG_FILENAME.""" | |||||
| with open(CONFIG_FILENAME, "wb") as config_file: | |||||
| config.write(config_file) | config.write(config_file) | ||||
| def get_bookmarks(): | def get_bookmarks(): | ||||
| @@ -40,43 +47,58 @@ def add_bookmarks(paths): | |||||
| if not config.has_section("bookmarks"): | if not config.has_section("bookmarks"): | ||||
| config.add_section("bookmarks") | config.add_section("bookmarks") | ||||
| out(0, yellow("Added bookmarks:")) | |||||
| added, exists = [], [] | |||||
| for path in paths: | for path in paths: | ||||
| path = os.path.abspath(path) # Convert relative to absolute path | |||||
| path = os.path.abspath(path) | |||||
| if config.has_option("bookmarks", path): | if config.has_option("bookmarks", path): | ||||
| out(1, "'{0}' is already bookmarked.".format(path)) | |||||
| exists.append(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) | ||||
| out(1, bold(path)) | |||||
| added.append(path) | |||||
| _save_config_file(config) | _save_config_file(config) | ||||
| if added: | |||||
| print(YELLOW + "Added bookmarks:") | |||||
| for path in added: | |||||
| print(INDENT1, path) | |||||
| if exists: | |||||
| print(RED + "Already bookmarked:") | |||||
| for path in exists: | |||||
| print(INDENT1, path) | |||||
| def delete_bookmarks(paths): | def delete_bookmarks(paths): | ||||
| """Remove a list of paths from the bookmark config file.""" | """Remove a list of paths from the bookmark config file.""" | ||||
| config = _load_config_file() | config = _load_config_file() | ||||
| deleted, notmarked = [], [] | |||||
| if config.has_section("bookmarks"): | if config.has_section("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) | |||||
| 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)) | |||||
| deleted.append(path) | |||||
| else: | else: | ||||
| out(1, "'{0}' is not bookmarked.".format(path)) | |||||
| notmarked.append(path) | |||||
| _save_config_file(config) | _save_config_file(config) | ||||
| else: | else: | ||||
| out(0, "There are no bookmarks to delete!") | |||||
| notmarked = [os.path.abspath(path) for path in paths] | |||||
| if deleted: | |||||
| print(YELLOW + "Deleted bookmarks:") | |||||
| for path in deleted: | |||||
| print(INDENT1, path) | |||||
| if notmarked: | |||||
| print(RED + "Not bookmarked:") | |||||
| for path in notmarked: | |||||
| print(INDENT1, path) | |||||
| def list_bookmarks(): | def list_bookmarks(): | ||||
| """Print all of our current bookmarks.""" | """Print all of our current bookmarks.""" | ||||
| bookmarks = get_bookmarks() | bookmarks = get_bookmarks() | ||||
| if bookmarks: | if bookmarks: | ||||
| out(0, yellow("Current bookmarks:")) | |||||
| for bookmark_path, bookmark_name in bookmarks: | |||||
| out(1, bookmark_path) | |||||
| print(YELLOW + "Current bookmarks:") | |||||
| for bookmark_path, _ in bookmarks: | |||||
| print(INDENT1, bookmark_path) | |||||
| else: | else: | ||||
| out(0, "You have no bookmarks to display.") | |||||
| print("You have no bookmarks to display.") | |||||
| @@ -1,25 +0,0 @@ | |||||
| # -*- coding: utf-8 -*- | |||||
| # | |||||
| # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | |||||
| # See the LICENSE file for details. | |||||
| import re | |||||
| __all__ = ["out", "bold", "red", "green", "yellow", "blue"] | |||||
| # Text formatting functions: | |||||
| bold = lambda t: "\x1b[1m" + t + "\x1b[0m" | |||||
| red = lambda t: "\x1b[1m\x1b[31m" + t + "\x1b[0m" | |||||
| green = lambda t: "\x1b[1m\x1b[32m" + t + "\x1b[0m" | |||||
| yellow = lambda t: "\x1b[1m\x1b[33m" + t + "\x1b[0m" | |||||
| blue = lambda t: "\x1b[1m\x1b[34m" + t + "\x1b[0m" | |||||
| def out(indent, msg): | |||||
| """Print a message at a given indentation level.""" | |||||
| width = 4 # Amount to indent at each level | |||||
| if indent == 0: | |||||
| spacing = "\n" | |||||
| else: | |||||
| spacing = " " * width * indent | |||||
| msg = re.sub(r"\s+", " ", msg) # Collapse multiple spaces into one | |||||
| print(spacing + msg) | |||||
| @@ -7,10 +7,11 @@ from __future__ import print_function | |||||
| import argparse | import argparse | ||||
| from colorama import init as color_init, Style | |||||
| from . import __version__, __email__ | from . import __version__, __email__ | ||||
| from .config import (get_bookmarks, add_bookmarks, delete_bookmarks, | from .config import (get_bookmarks, add_bookmarks, delete_bookmarks, | ||||
| list_bookmarks) | list_bookmarks) | ||||
| from .output import out, bold | |||||
| from .update import update_bookmarks, update_directories | from .update import update_bookmarks, update_directories | ||||
| def main(): | def main(): | ||||
| @@ -48,8 +49,11 @@ def main(): | |||||
| '-v', '--version', action="version", | '-v', '--version', action="version", | ||||
| version="gitup version " + __version__) | version="gitup version " + __version__) | ||||
| color_init(autoreset=True) | |||||
| args = parser.parse_args() | args = parser.parse_args() | ||||
| print(bold("gitup") + ": the git-repo-updater") | |||||
| print(Style.BRIGHT + "gitup" + Style.RESET_ALL + ": the git-repo-updater") | |||||
| print() | |||||
| if args.bookmarks_to_add: | if args.bookmarks_to_add: | ||||
| add_bookmarks(args.bookmarks_to_add) | add_bookmarks(args.bookmarks_to_add) | ||||
| @@ -71,4 +75,4 @@ def run(): | |||||
| try: | try: | ||||
| main() | main() | ||||
| except KeyboardInterrupt: | except KeyboardInterrupt: | ||||
| out(0, "Stopped by user.") | |||||
| print("Stopped by user.") | |||||
| @@ -3,21 +3,24 @@ | |||||
| # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | # Copyright (C) 2011-2014 Ben Kurtovic <ben.kurtovic@gmail.com> | ||||
| # See the LICENSE file for details. | # See the LICENSE file for details. | ||||
| from __future__ import print_function | |||||
| import os | import os | ||||
| import shlex | import shlex | ||||
| import subprocess | import subprocess | ||||
| from .output import out, bold, red, green, blue | |||||
| from colorama import Fore, Style | |||||
| __all__ = ["update_bookmarks", "update_directories"] | __all__ = ["update_bookmarks", "update_directories"] | ||||
| 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 | |||||
| return result | |||||
| BOLD = Style.BRIGHT | |||||
| RED = Fore.RED + BOLD | |||||
| GREEN = Fore.GREEN + BOLD | |||||
| BLUE = Fore.BLUE + BOLD | |||||
| RESET = Style.RESET_ALL | |||||
| INDENT1 = " " * 3 | |||||
| INDENT2 = " " * 7 | |||||
| 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.""" | ||||
| @@ -29,17 +32,25 @@ def _directory_is_git_repo(directory_path): | |||||
| def _update_repository(repo_path, repo_name): | 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) + ":") | |||||
| 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 | |||||
| return result | |||||
| print(INDENT1, BOLD + repo_name + ":") | |||||
| # 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) | |||||
| os.chdir(repo_path) # TODO: remove this when using gitpython | |||||
| try: | try: | ||||
| # 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") | 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?") | |||||
| print(INDENT2, RED + "Error:" + RESET, "cannot fetch;", | |||||
| "do you have a remote repository configured correctly?") | |||||
| return | return | ||||
| try: | try: | ||||
| @@ -48,24 +59,27 @@ def _update_repository(repo_path, repo_name): | |||||
| 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 | if not dry_fetch: # No new changes to pull | ||||
| out(2, blue("No new changes.") + | |||||
| " Last commit was {0}.".format(last_commit)) | |||||
| print(INDENT2, BLUE + "No new changes." + RESET, | |||||
| "Last commit was {0}.".format(last_commit)) | |||||
| else: # Stuff has happened! | else: # Stuff has happened! | ||||
| out(2, "There are new changes upstream...") | |||||
| print(INDENT2, "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...")) | |||||
| print(INDENT2, GREEN + "Pulling new changes...") | |||||
| result = _exec_shell("git pull") | result = _exec_shell("git pull") | ||||
| out(2, "The following changes have been made since {0}:".format( | |||||
| last_commit)) | |||||
| if last_commit == "never": | |||||
| print(INDENT2, "The following changes have been made:") | |||||
| else: | |||||
| print(INDENT2, "The following changes have been made since", | |||||
| last_commit + ":") | |||||
| print(result) | print(result) | ||||
| else: | else: | ||||
| out(2, red("Warning: ") + | |||||
| "you have uncommitted changes in this repository!") | |||||
| out(2, "Ignoring.") | |||||
| print(INDENT2, RED + "Warning:" + RESET, | |||||
| "you have uncommitted changes in this repository!") | |||||
| print(INDENT2, "Ignoring.") | |||||
| def _update_directory(dir_path, dir_name, is_bookmark=False): | def _update_directory(dir_path, dir_name, is_bookmark=False): | ||||
| """Update a particular directory. | """Update a particular directory. | ||||
| @@ -79,25 +93,24 @@ def _update_directory(dir_path, dir_name, is_bookmark=False): | |||||
| 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 = "{0} '{1}'".format(dir_type, bold(dir_path)) | |||||
| dir_long_name = dir_type + ' "' + BOLD + dir_path + RESET + '"' | |||||
| 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 {0}; does it exist?".format(dir_long_name)) | |||||
| print(RED + "Error:" + RESET, | |||||
| "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): | ||||
| if os.path.exists(dir_path): | if os.path.exists(dir_path): | ||||
| out(0, red("Error: ") + dir_long_name + " is not a directory!") | |||||
| print(RED + "Error:" + RESET, dir_long_name, "is not a directory!") | |||||
| else: | else: | ||||
| out(0, red("Error: ") + dir_long_name + " does not exist!") | |||||
| print(RED + "Error:" + RESET, dir_long_name, "does not exist!") | |||||
| return | return | ||||
| if _directory_is_git_repo(dir_path): | if _directory_is_git_repo(dir_path): | ||||
| out(0, dir_long_name.capitalize() + " is a git repository:") | |||||
| print(dir_long_name.capitalize(), "is a git repository:") | |||||
| _update_repository(dir_path, dir_name) | _update_repository(dir_path, dir_name) | ||||
| else: | else: | ||||
| @@ -112,10 +125,10 @@ def _update_directory(dir_path, dir_name, is_bookmark=False): | |||||
| num_of_repos = len(repositories) | num_of_repos = len(repositories) | ||||
| if num_of_repos == 1: | if num_of_repos == 1: | ||||
| out(0, dir_long_name.capitalize() + " contains 1 git repository:") | |||||
| print(dir_long_name.capitalize(), "contains 1 git repository:") | |||||
| else: | else: | ||||
| out(0, dir_long_name.capitalize() + | |||||
| " contains {0} git repositories:".format(num_of_repos)) | |||||
| print(dir_long_name.capitalize(), | |||||
| "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: | ||||
| @@ -127,8 +140,7 @@ def update_bookmarks(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'.") | |||||
| print("You don't have any bookmarks configured! Get help with 'gitup -h'.") | |||||
| 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.""" | ||||
| @@ -19,7 +19,7 @@ setup( | |||||
| name = "gitup", | name = "gitup", | ||||
| packages = find_packages(), | packages = find_packages(), | ||||
| entry_points = {"console_scripts": ["gitup = gitup.script:run"]}, | entry_points = {"console_scripts": ["gitup = gitup.script:run"]}, | ||||
| install_requires = ["GitPython >= 0.3.2.RC1"], | |||||
| install_requires = ["GitPython >= 0.3.2.RC1", "colorama >= 0.2.7"], | |||||
| version = __version__, | version = __version__, | ||||
| author = "Ben Kurtovic", | author = "Ben Kurtovic", | ||||
| author_email = "ben.kurtovic@gmail.com", | author_email = "ben.kurtovic@gmail.com", | ||||