diff --git a/CHANGELOG.md b/CHANGELOG.md index e14ac15c..e29a9506 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,15 @@ # Revision History +## 1.4 (2017/03/21) + +- Allow config files to exist in subdirectories of the main project. +- Added `${GITMAN_CACHE}` to customize the repository cache location. + ## 1.3 (2017/02/03) -- Added `init` command to generate sample configuration files. +- Added `init` command to generate sample config files. - Added support for post-install scripts on dependencies. -- Updated configuration format to support `null` for links. +- Updated config format to support `null` for links. ## 1.2 (2017/01/08) @@ -39,7 +44,7 @@ ## 0.9 (2016/03/31) -- Added `edit` command to launch the configuration file. +- Added `edit` command to launch the config file. - Depth now defaults to 5 to prevent infinite recursion. - Fixed handling of source lists containing different dependencies. diff --git a/README.md b/README.md index e9bef252..6965e081 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ $ python setup.py install ## Setup -Generate a sample configuration file: +Generate a sample config file: ```sh $ gitman init @@ -83,7 +83,7 @@ which will essentially: 1. Create a working tree at ``/``/`` 2. Fetch from `repo` and checkout the specified `rev` 3. Symbolically link each ``/`` from ``/`` (if specified) -4. Repeat for all nested working trees containing a configuration file +4. Repeat for all nested working trees containing a config file 5. Record the actual commit SHAs that were checked out (with `--lock` option) 6. Run optional post-install scripts for each dependency diff --git a/docs/interfaces/api.md b/docs/interfaces/api.md index d8c7b3b7..de1a8b50 100644 --- a/docs/interfaces/api.md +++ b/docs/interfaces/api.md @@ -4,7 +4,7 @@ All of the [command-line interface](cli.md) functionality is available from the ## Init -To generate a sample configuration for a new project, call: +To generate a sample config for a new project, call: ```python gitman.init() diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index d7d24a4a..ff7c576e 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -1,10 +1,10 @@ # Command-line Interface -After setting up `gitman` with a [configuration file](../index.md#setup), various commands can be run to manage these Git-controlled source dependencies. +After setting up `gitman` with a [config file](../index.md#setup), various commands can be run to manage these Git-controlled source dependencies. ## Init -To generate a sample configuration for a new project, run: +To generate a sample config for a new project, run: ```sh $ gitman init @@ -150,7 +150,7 @@ To display the path to a dependency: gitman show ``` -To display the path to the configuration file: +To display the path to the config file: ```sh gitman show --config @@ -164,7 +164,7 @@ gitman show --log ## Edit -To open the existing configuration file: +To open the existing config file: ```sh gitman edit diff --git a/docs/setup/environment.md b/docs/setup/environment.md new file mode 100644 index 00000000..2a50b8a4 --- /dev/null +++ b/docs/setup/environment.md @@ -0,0 +1,11 @@ +# Environment Setup + +The following environment variables can be used to configure the behavior of GitMan. + +## `GITMAN_CACHE` + +GitMan utilizes local repository mirrors to cache dependencies and speed up cloning. +This variable specifies the path of a directory to store these repository references. +The default value should be overridden if `$HOME` is not set on your target system. + +**Default**: `~/.gitcache` diff --git a/docs/use-cases/branch-tracking.md b/docs/use-cases/branch-tracking.md index afac2bf9..5dc6a6e1 100644 --- a/docs/use-cases/branch-tracking.md +++ b/docs/use-cases/branch-tracking.md @@ -33,7 +33,7 @@ tests ## Understanding Locked Sources -In the configuration file, the `sources_locked` section identifies that commit `b27308` of the API was last used to test this web app -- the last time `$ gitman update` was run. +In the config file, the `sources_locked` section identifies that commit `b27308` of the API was last used to test this web app -- the last time `$ gitman update` was run. The `sources` section identifies that the `develop` branch should be used when checking out a new version of the API. diff --git a/docs/use-cases/build-integration.md b/docs/use-cases/build-integration.md index 15ce12fd..392a96f3 100644 --- a/docs/use-cases/build-integration.md +++ b/docs/use-cases/build-integration.md @@ -23,7 +23,7 @@ clean: gitman uninstall ``` -using a configuration file similar to: +using a config file similar to: ```yaml location: vendor/gitman @@ -47,8 +47,8 @@ sources_locked: Running `make depends` performs the following actions: -1. Check the modification times of the configuration and log files -2. If the configuration file is newer, continue +1. Check the modification times of the config and log files +2. If the config file is newer, continue 3. Install the locked dependency versions 4. Run `make` inside of each dependency's folder 5. Update the log file with the current versions of all dependencies @@ -57,4 +57,4 @@ To update your dependencies: 1. Run `gitman update` 2. Run `make depends` -3. If the new build passes your tests, commit the new configuration file +3. If the new build passes your tests, commit the new config file diff --git a/docs/use-cases/submodules.md b/docs/use-cases/submodules.md index 7bba5f05..d2f55582 100644 --- a/docs/use-cases/submodules.md +++ b/docs/use-cases/submodules.md @@ -4,7 +4,7 @@ While Git [submodules](http://git-scm.com/docs/git-submodule) are an obvious cho ## An Existing Submodule -When managing a single dependency using submodules, there will be two items in your working tree with special meaning. The `.gitmodules` file, which contains submodule configuration, and semi-ignored directory containing the checked out dependency: +When managing a single dependency using submodules, there will be two items in your working tree with special meaning. The `.gitmodules` file, which contains submodule config, and semi-ignored directory containing the checked out dependency: ```sh /vendor/my_dependency # submodule at: a943a702d06f34599aee1f8da8ef9f7296031d69 @@ -39,4 +39,4 @@ In other working trees, simply run `$ gitman install` to check out the source de ### Modifying Dependencies -To include a different version of a dependency, modify the `rev` value in the configuration file. +To include a different version of a dependency, modify the `rev` value in the config file. diff --git a/gitman/__init__.py b/gitman/__init__.py index 3ebe5a02..e64eef3a 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '1.3' +__version__ = '1.4' CLI = 'gitman' PLUGIN = 'deps' diff --git a/gitman/cli.py b/gitman/cli.py index 850ec852..a982e387 100644 --- a/gitman/cli.py +++ b/gitman/cli.py @@ -43,7 +43,7 @@ def main(args=None, function=None): # pylint: disable=too-many-statements subs = parser.add_subparsers(help="", dest='command', metavar="") # Init parser - info = "create a new configuration file for the project" + info = "create a new config file for the project" sub = subs.add_parser('init', description=info.capitalize() + '.', help=info, parents=[debug], **shared) @@ -108,7 +108,7 @@ def main(args=None, function=None): # pylint: disable=too-many-statements help="display the path of the log file") # Edit parser - info = "open the configuration file in the default editor" + info = "open the config file in the default editor" sub = subs.add_parser('edit', description=info.capitalize() + '.', help=info, parents=[debug, project], **shared) diff --git a/gitman/commands.py b/gitman/commands.py index 1af16a41..150b249d 100644 --- a/gitman/commands.py +++ b/gitman/commands.py @@ -22,10 +22,11 @@ def wrapped(*args, **kwargs): def init(): - """Create a new configuration file for the project.""" + """Create a new config file for the project.""" success = False config = load_config() + if config: msg = "Configuration file already exists: {}".format(config.path) common.show(msg, color='error') @@ -39,11 +40,11 @@ def init(): config.sources_locked.append(source) config.save() - msg = "Created sample configuration file: {}".format(config.path) + msg = "Created sample config file: {}".format(config.path) common.show(msg, color='success') success = True - msg = "To edit this configuration file, run: gitman edit" + msg = "To edit this config file, run: gitman edit" common.show(msg, color='message') return success @@ -70,7 +71,6 @@ def install(*names, root=None, depth=None, ', '.join(names) if names else '') count = None - root = _find_root(root) config = load_config(root) if config: @@ -111,7 +111,6 @@ def update(*names, root=None, depth=None, ', '.join(names) if names else '') count = None - root = _find_root(root) config = load_config(root) if config: @@ -166,7 +165,6 @@ def display(*, root=None, depth=None, allow_dirty=True): log.info("Displaying dependencies...") count = None - root = _find_root(root) config = load_config(root) if config: @@ -198,7 +196,6 @@ def lock(*names, root=None): log.info("Locking dependencies...") count = None - root = _find_root(root) config = load_config(root) if config: @@ -224,7 +221,6 @@ def delete(*, root=None, force=False): log.info("Deleting dependencies...") count = None - root = _find_root(root) config = load_config(root) if config: @@ -250,10 +246,10 @@ def show(*names, root=None): """ log.info("Finding paths...") - root = _find_root(root) config = load_config(root) + if not config: - log.error("No configuration found") + log.error("No config found") return False for name in names or [None]: @@ -270,48 +266,17 @@ def edit(*, root=None): - `root`: specifies the path to the root working tree """ - log.info("Launching configuration...") + log.info("Launching config...") - root = _find_root(root) config = load_config(root) + if not config: - log.error("No configuration found") + log.error("No config found") return False return system.launch(config.path) -def _find_root(base, cwd=None): - if cwd is None: - cwd = os.getcwd() - log.info("Current directory: %s", cwd) - - if base: - root = os.path.abspath(base) - log.info("Specified root: %s", root) - - else: - log.info("Searching for root...") - path = cwd - prev = None - root = None - while path != prev: - log.debug("Checking path: %s", path) - if '.git' in os.listdir(path): - root = path - break - prev = path - path = os.path.dirname(path) - - if root: - log.info("Found root: %s", root) - else: - root = cwd - log.warning("No root found, default: %s", root) - - return root - - def _display_result(present, past, count, allow_zero=False): """Convert a command's dependency count to a return status. diff --git a/gitman/common.py b/gitman/common.py index 20af0e49..eb3896db 100644 --- a/gitman/common.py +++ b/gitman/common.py @@ -45,7 +45,7 @@ def positive_int(value): class _Config: - """Share configuration options.""" + """Share logging options.""" MAX_VERBOSITY = 4 diff --git a/gitman/exceptions.py b/gitman/exceptions.py index 141b7a0d..dbf6c4a5 100644 --- a/gitman/exceptions.py +++ b/gitman/exceptions.py @@ -2,7 +2,7 @@ class InvalidConfig(ValueError): - """Raised when the configuration file is invalid.""" + """Raised when the config file is invalid.""" class ShellError(RuntimeError): diff --git a/gitman/git.py b/gitman/git.py index 7fc2c86a..2b990f72 100644 --- a/gitman/git.py +++ b/gitman/git.py @@ -4,7 +4,7 @@ import logging from contextlib import suppress -from . import common +from . import common, settings from .shell import call from .exceptions import ShellError @@ -16,13 +16,10 @@ def git(*args, **kwargs): return call('git', *args, **kwargs) -def clone(repo, path, *, cache=None): +def clone(repo, path, *, cache=settings.CACHE): """Clone a new Git repository.""" log.debug("Creating a new repository...") - cache = cache or os.path.expanduser("~/.gitcache") - cache = os.path.normpath(cache) - name = repo.split('/')[-1] if name.endswith(".git"): name = name[:-4] diff --git a/gitman/models/__init__.py b/gitman/models/__init__.py index aae020cf..a71cf5e3 100644 --- a/gitman/models/__init__.py +++ b/gitman/models/__init__.py @@ -1,4 +1,2 @@ -"""Domain models for interacting with dependency configuration.""" - from .source import Source from .config import Config, load_config diff --git a/gitman/models/config.py b/gitman/models/config.py index 7bf03c08..6a241eab 100644 --- a/gitman/models/config.py +++ b/gitman/models/config.py @@ -1,5 +1,3 @@ -"""Wrappers for the dependency configuration files.""" - import os import logging @@ -18,7 +16,7 @@ @yorm.attr(sources_locked=SortedList.of_type(Source)) @yorm.sync("{self.root}/{self.filename}", auto_save=False) class Config(yorm.ModelMixin): - """A dictionary of dependency configuration options.""" + """Specifies all dependencies for a project.""" LOG = "gitman.log" @@ -33,7 +31,7 @@ def __init__(self, root=None, @property def config_path(self): - """Get the full path to the configuration file.""" + """Get the full path to the config file.""" return os.path.normpath(os.path.join(self.root, self.filename)) path = config_path @@ -89,7 +87,7 @@ def install_dependencies(self, *names, depth=None, common.newline() count += 1 - config = load_config() + config = load_config(search=False) if config: common.indent() count += config.install_dependencies( @@ -130,7 +128,7 @@ def run_scripts(self, *names, depth=None, force=False): source.run_scripts(force=force) count += 1 - config = load_config() + config = load_config(search=False) if config: common.indent() count += config.run_scripts( @@ -198,7 +196,7 @@ def get_dependencies(self, depth=None, allow_dirty=True): yield source.identify(allow_dirty=allow_dirty) - config = load_config() + config = load_config(search=False) if config: common.indent() yield from config.get_dependencies( @@ -245,18 +243,36 @@ def _get_sources(self, *, use_locked=None): return sources + extras -def load_config(root=None): - """Load the configuration for the current project.""" - if root is None: - root = os.getcwd() +def load_config(start=None, *, search=True): + """Load the config for the current project.""" + if start: + start = os.path.abspath(start) + else: + start = os.getcwd() + + if search: + log.debug("Searching for config...") + + path = start + while path != os.path.dirname(path): + log.debug("Looking for config in: %s", path) + + for filename in os.listdir(path): + if _valid_filename(filename): + config = Config(path, filename) + log.debug("Found config: %s", config.path) + return config + + if search: + path = os.path.dirname(path) + else: + break - for filename in os.listdir(root): - if _valid_filename(filename): - config = Config(root, filename) - log.debug("Loaded config: %s", config.path) - return config + if search: + log.debug("No config found starting from: %s", start) + else: + log.debug("No config found in: %s", start) - log.debug("No config found in: %s", root) return None diff --git a/gitman/models/source.py b/gitman/models/source.py index 2b4bfb9e..63ffd960 100644 --- a/gitman/models/source.py +++ b/gitman/models/source.py @@ -1,5 +1,3 @@ -"""Wrappers for the dependency configuration files.""" - import os import logging import warnings diff --git a/gitman/settings.py b/gitman/settings.py index ce69b83a..f926dbbc 100644 --- a/gitman/settings.py +++ b/gitman/settings.py @@ -1,7 +1,11 @@ """Program defaults.""" +import os import logging +# Cache settings +CACHE = os.path.expanduser(os.getenv('GITMAN_CACHE', "~/.gitcache")) + # Logging settings DEFAULT_LOGGING_FORMAT = "%(message)s" LEVELED_LOGGING_FORMAT = "%(levelname)s: %(message)s" diff --git a/gitman/tests/test_commands.py b/gitman/tests/test_commands.py index 18466da6..ef9b078a 100644 --- a/gitman/tests/test_commands.py +++ b/gitman/tests/test_commands.py @@ -1,37 +1,61 @@ -# pylint: disable=no-self-use +# pylint: disable=redefined-outer-name,unused-argument,unused-variable,singleton-comparison,expression-not-assigned -import os +from expecter import expect -from gitman.commands import _find_root, install, update, display, delete +from gitman import commands -from .conftest import ROOT, FILES -PROJECT_ROOT = os.path.dirname(os.path.dirname(ROOT)) -PROJECT_PARENT = os.path.dirname(PROJECT_ROOT) +def describe_install(): + def can_be_run_without_project(tmpdir): + tmpdir.chdir() + + expect(commands.install()) == False + + +def describe_update(): + + def can_be_run_without_project(tmpdir): + tmpdir.chdir() + + expect(commands.update()) == False + + +def describe_display(): + + def can_be_run_without_project(tmpdir): + tmpdir.chdir() -class TestCommands: + expect(commands.display()) == False - def test_commands_can_be_run_without_project(self, tmpdir): + +def describe_lock(): + + def can_be_run_without_project(tmpdir): tmpdir.chdir() - assert not install() - assert not update() - assert not display() - assert not delete() + expect(commands.lock()) == False + +def describe_delete(): -class TestFindRoot: + def can_be_run_without_project(tmpdir): + tmpdir.chdir() + + expect(commands.delete()) == False - def test_specified(self): - os.chdir(PROJECT_PARENT) - assert FILES == _find_root(FILES) - def test_none(self): - assert PROJECT_ROOT == _find_root(None, cwd=ROOT) +def describe_show(): - def test_current(self): - assert PROJECT_ROOT == _find_root(PROJECT_ROOT, cwd=ROOT) + def can_be_run_without_project(tmpdir): + tmpdir.chdir() + + expect(commands.show()) == False + + +def describe_edit(): + + def can_be_run_without_project(tmpdir): + tmpdir.chdir() - def test_missing(self): - assert PROJECT_PARENT == _find_root(None, cwd=PROJECT_PARENT) + expect(commands.show()) == False diff --git a/gitman/tests/test_models_config.py b/gitman/tests/test_models_config.py index 534153e8..fc0c2032 100644 --- a/gitman/tests/test_models_config.py +++ b/gitman/tests/test_models_config.py @@ -12,7 +12,7 @@ class TestConfig: def test_init_defaults(self): - """Verify a configuration has a default filename and location.""" + """Verify a config has a default filename and location.""" config = Config('mock/root') assert 'mock/root' == config.root @@ -35,7 +35,7 @@ def test_init_location(self): assert '.gitman' == config.location def test_path(self): - """Verify a configuration's path is correct.""" + """Verify the path is correct.""" config = Config('mock/root') assert os.path.normpath("mock/root/gitman.yml") == config.path diff --git a/mkdocs.yml b/mkdocs.yml index 5f72d1b6..442d53d0 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -9,6 +9,7 @@ pages: - Home: index.md - Setup: - Git: setup/git.md + - Environment: setup/environment.md - Interfaces: - Command Line: interfaces/cli.md - Git Plugin: interfaces/plugin.md