Browse Source

Support shell globs and tilde expansion (fixes #29).

tags/v0.4
Ben Kurtovic 8 years ago
parent
commit
a702056178
3 changed files with 40 additions and 22 deletions
  1. +1
    -0
      CHANGELOG
  2. +13
    -4
      gitup/config.py
  3. +26
    -18
      gitup/update.py

+ 1
- 0
CHANGELOG View File

@@ -5,6 +5,7 @@ v0.4 (unreleased):
- Added a `--bookmark-file` flag to support multiple bookmark config files. - Added a `--bookmark-file` flag to support multiple bookmark config files.
- Added a `--cleanup` flag to remove old bookmarks that don't exist. - Added a `--cleanup` flag to remove old bookmarks that don't exist.
- Added an `--exec` flag to run a shell command on all of your repos. - Added an `--exec` flag to run a shell command on all of your repos.
- Added support for shell glob patterns and tilde expansion in bookmark files.
- 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.


+ 13
- 4
gitup/config.py View File

@@ -5,6 +5,7 @@


from __future__ import print_function from __future__ import print_function


from glob import glob
import os import os


from colorama import Fore, Style from colorama import Fore, Style
@@ -48,6 +49,12 @@ def _save_config_file(bookmarks, config_path=None):
with open(cfg_path, "wb") as config_file: with open(cfg_path, "wb") as config_file:
config_file.write(dump) config_file.write(dump)


def _normalize_path(path):
"""Normalize the given path."""
if path.startswith("~"):
return os.path.normcase(os.path.normpath(path))
return os.path.normcase(os.path.abspath(path))

def get_default_config_path(): def get_default_config_path():
"""Return the default path to the configuration file.""" """Return the default path to the configuration file."""
xdg_cfg = os.environ.get("XDG_CONFIG_HOME") or os.path.join("~", ".config") xdg_cfg = os.environ.get("XDG_CONFIG_HOME") or os.path.join("~", ".config")
@@ -60,9 +67,10 @@ def get_bookmarks(config_path=None):
def add_bookmarks(paths, config_path=None): def add_bookmarks(paths, config_path=None):
"""Add a list of paths as bookmarks to the config file.""" """Add a list of paths as bookmarks to the config file."""
config = _load_config_file(config_path) config = _load_config_file(config_path)
paths = [_normalize_path(path) for path in paths]

added, exists = [], [] added, exists = [], []
for path in paths: for path in paths:
path = os.path.normcase(os.path.abspath(path))
if path in config: if path in config:
exists.append(path) exists.append(path)
else: else:
@@ -82,11 +90,11 @@ def add_bookmarks(paths, config_path=None):
def delete_bookmarks(paths, config_path=None): def delete_bookmarks(paths, config_path=None):
"""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_path) config = _load_config_file(config_path)
paths = [_normalize_path(path) for path in paths]


deleted, notmarked = [], [] deleted, notmarked = [], []
if config: if config:
for path in paths: for path in paths:
path = os.path.normcase(os.path.abspath(path))
if path in config: if path in config:
config.remove(path) config.remove(path)
deleted.append(path) deleted.append(path)
@@ -94,7 +102,7 @@ def delete_bookmarks(paths, config_path=None):
notmarked.append(path) notmarked.append(path)
_save_config_file(config, config_path) _save_config_file(config, config_path)
else: else:
notmarked = [os.path.abspath(path) for path in paths]
notmarked = paths


if deleted: if deleted:
print(YELLOW + "Deleted bookmarks:") print(YELLOW + "Deleted bookmarks:")
@@ -122,7 +130,8 @@ def clean_bookmarks(config_path=None):
print("You have no bookmarks to clean up.") print("You have no bookmarks to clean up.")
return return


delete = [path for path in bookmarks if not os.path.isdir(path)]
delete = [path for path in bookmarks
if not (os.path.isdir(path) or glob(os.path.expanduser(path)))]
if not delete: if not delete:
print("All of your bookmarks are valid.") print("All of your bookmarks are valid.")
return return


+ 26
- 18
gitup/update.py View File

@@ -5,6 +5,7 @@


from __future__ import print_function from __future__ import print_function


from glob import glob
import os import os
import shlex import shlex


@@ -192,18 +193,20 @@ 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_to_subdirs(path, callback, *args):
"""Apply the callback to all git repo subdirectories in the directory."""
def _dispatch_multi(base, paths, callback, *args):
"""Apply the callback to all git repos in the list of paths."""
repos = [] repos = []
for item in os.listdir(path):
for path in paths:
try: try:
repo = Repo(os.path.join(path, item))
repo = Repo(path)
except (exc.InvalidGitRepositoryError, exc.NoSuchPathError): except (exc.InvalidGitRepositoryError, exc.NoSuchPathError):
continue continue
repos.append(repo) repos.append(repo)


base = os.path.abspath(base)
suffix = "" if len(repos) == 1 else "s" suffix = "" if len(repos) == 1 else "s"
print(BOLD + path, "({0} repo{1}):".format(len(repos), suffix))
print(BOLD + base, "({0} repo{1}):".format(len(repos), suffix))

for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]): for repo in sorted(repos, key=lambda r: os.path.split(r.working_dir)[1]):
callback(repo, *args) callback(repo, *args)


@@ -211,19 +214,25 @@ def _dispatch(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
git repositories, or something invalid. If the first, apply the callback on
it; if the second, apply the callback on all repositories contained within;
if the third, print an error.
git repositories, a shell glob pattern, or something invalid. If the first,
apply the callback on it; if the second or third, apply the callback on all
repositories contained within; if the last, print an error.


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)
try: try:
repo = Repo(path) repo = Repo(path)
except exc.NoSuchPathError: except exc.NoSuchPathError:
print(ERROR, BOLD + path, "doesn't exist!")
paths = glob(path)
if paths:
_dispatch_multi(path, paths, callback, *args)
else:
print(ERROR, BOLD + path, "doesn't exist!")
except exc.InvalidGitRepositoryError: except exc.InvalidGitRepositoryError:
if os.path.isdir(path): if os.path.isdir(path):
_dispatch_to_subdirs(path, callback, *args)
paths = [os.path.join(path, item) for item in os.listdir(path)]
_dispatch_multi(path, paths, callback, *args)
else: else:
print(ERROR, BOLD + path, "isn't a repository!") print(ERROR, BOLD + path, "isn't a repository!")
else: else:
@@ -232,20 +241,19 @@ def _dispatch(path, callback, *args):


def update_bookmarks(bookmarks, update_args): def update_bookmarks(bookmarks, update_args):
"""Loop through and update all bookmarks.""" """Loop through and update all bookmarks."""
if bookmarks:
for path in bookmarks:
_dispatch(path, _update_repository, *update_args)
else:
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

for path in bookmarks:
_dispatch(path, _update_repository, *update_args)


def update_directories(paths, update_args): def update_directories(paths, update_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:
full_path = os.path.abspath(path)
_dispatch(full_path, _update_repository, *update_args)
_dispatch(path, _update_repository, *update_args)


def run_command(paths, command): def run_command(paths, command):
"""Run an arbitrary shell command on all repos.""" """Run an arbitrary shell command on all repos."""
for path in paths: for path in paths:
full_path = os.path.abspath(path)
_dispatch(full_path, _run_command, command)
_dispatch(path, _run_command, command)

Loading…
Cancel
Save