From fe7c92a1e18e84f277e2d24bfbace77f5ebcb824 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Sat, 2 Apr 2016 13:29:50 -0400 Subject: [PATCH 01/10] Add command to show dependency paths --- gitman/cli.py | 26 ++++++++++++++++++++++---- gitman/commands.py | 25 +++++++++++++++++++++---- gitman/models/config.py | 29 ++++++++++++++++++++++++++++- gitman/test/test_cli.py | 9 +-------- gitman/test/test_models_config.py | 24 +++++++++++++++++++++++- 5 files changed, 95 insertions(+), 18 deletions(-) diff --git a/gitman/cli.py b/gitman/cli.py index bc98546c..d3f885f1 100644 --- a/gitman/cli.py +++ b/gitman/cli.py @@ -50,7 +50,7 @@ def main(args=None, function=None): help=info, parents=[debug, project, depth, options], **shared) sub.add_argument('name', nargs='*', - help="list of dependencies (`dir` values) to install") + help="list of dependencies names to install") sub.add_argument('-e', '--fetch', action='store_true', help="always fetch the latest branches") @@ -60,7 +60,7 @@ def main(args=None, function=None): help=info, parents=[debug, project, depth, options], **shared) sub.add_argument('name', nargs='*', - help="list of dependencies (`dir` values) to update") + help="list of dependencies names to update") sub.add_argument('-a', '--all', action='store_true', dest='recurse', help="update all nested dependencies, recursively") group = sub.add_mutually_exclusive_group() @@ -84,7 +84,7 @@ def main(args=None, function=None): sub = subs.add_parser('lock', description=info.capitalize() + '.', help=info, parents=[debug, project], **shared) sub.add_argument('name', nargs='*', - help="list of dependencies (`dir` values) to lock") + help="list of dependency names to lock") # Uninstall parser info = "delete all installed dependencies" @@ -93,6 +93,17 @@ def main(args=None, function=None): sub.add_argument('-f', '--force', action='store_true', help="delete uncommitted changes in dependencies") + # Show parser + info = "display the path of a dependency or internal file" + sub = subs.add_parser('show', description=info.capitalize() + '.', + help=info, parents=[debug, project], **shared) + sub.add_argument('name', nargs='*', + help="display the path of this dependency") + sub.add_argument('-c', '--config', action='store_true', + help="display the path of the config file") + sub.add_argument('-l', '--log', action='store_true', + help="display the path of the log file") + # Edit parser info = "open the configuration file in the default editor" sub = subs.add_parser('edit', description=info.capitalize() + '.', @@ -117,7 +128,7 @@ def _get_command(function, namespace): kwargs = dict(root=namespace.root) exit_msg = "" - if namespace.command in ('install', 'update'): + if namespace.command in ['install', 'update']: function = getattr(commands, namespace.command) args = namespace.name kwargs.update(depth=namespace.depth, @@ -140,6 +151,13 @@ def _get_command(function, namespace): function = commands.delete kwargs.update(force=namespace.force) exit_msg = "\n" + "Run again with '--force' to ignore" + elif namespace.command == 'show': + function = commands.show + args = namespace.name + if namespace.config: + args.append('__config__') + if namespace.log: + args.append('__log__') elif namespace.command == 'edit': function = commands.edit diff --git a/gitman/commands.py b/gitman/commands.py index e8d59e5d..5a896123 100644 --- a/gitman/commands.py +++ b/gitman/commands.py @@ -93,7 +93,7 @@ def update(*names, root=None, depth=None, @restore_cwd -def display(root=None, depth=None, allow_dirty=True): +def display(*, root=None, depth=None, allow_dirty=True): """Display installed dependencies for a project. Optional arguments: @@ -143,7 +143,7 @@ def lock(*names, root=None): @restore_cwd -def delete(root=None, force=False): +def delete(*, root=None, force=False): """Delete dependencies for a project. Optional arguments: @@ -170,8 +170,25 @@ def delete(root=None, force=False): return _display_result("delete", "Deleted", count, allow_zero=True) -@restore_cwd -def edit(root=None): +def show(*names, root=None): + """Display the path of an installed dependency or internal file. + + - `name`: dependency name or internal file keyword + - `root`: specifies the path to the root working tree + + """ + log.info("Finding paths...") + + root = _find_root(root) + config = load_config(root) + + for name in names or [None]: + common.show(config.get_path(name)) + + return True + + +def edit(*, root=None): """Open the confuration file for a project. Optional arguments: diff --git a/gitman/models/config.py b/gitman/models/config.py index d102041e..e09457f1 100644 --- a/gitman/models/config.py +++ b/gitman/models/config.py @@ -1,6 +1,7 @@ """Wrappers for the dependency configuration files.""" import os +import datetime import logging import yorm @@ -20,6 +21,8 @@ class Config: """A dictionary of dependency configuration options.""" + LOG = "gitman.log" + def __init__(self, root, filename="gitman.yml", location="gdm_sources"): super().__init__() self.root = root @@ -29,15 +32,33 @@ def __init__(self, root, filename="gitman.yml", location="gdm_sources"): self.sources_locked = [] @property - def path(self): + def config_path(self): """Get the full path to the configuration file.""" return os.path.join(self.root, self.filename) + path = config_path + + @property + def log_path(self): + """Get the full path to the log file.""" + return os.path.join(self.location_path, self.LOG) @property def location_path(self): """Get the full path to the sources location.""" return os.path.join(self.root, self.location) + def get_path(self, name=None): + """Get the full path to a dependency or internal file.""" + base = self.location_path + if name == '__config__': + return self.path + elif name == '__log__': + return self.log_path + elif name: + return os.path.join(base, name) + else: + return base + def install_deps(self, *names, depth=None, update=True, recurse=False, force=False, fetch=False, clean=True): @@ -158,6 +179,12 @@ def get_deps(self, depth=None, allow_dirty=True): common.dedent() + def log(self, message, *args): + """Append a message to the log file.""" + stamp = datetime.datetime.now().strftime("+%F %T") + with open(self.log_path, 'a') as outfile: + outfile.write("{}: {}\n".format(stamp, message % args)) + def _get_sources(self, *, use_locked=None): """Merge source lists using requested section as the base.""" if use_locked is True: diff --git a/gitman/test/test_cli.py b/gitman/test/test_cli.py index 06031d1b..03367aee 100644 --- a/gitman/test/test_cli.py +++ b/gitman/test/test_cli.py @@ -1,4 +1,4 @@ -# pylint: disable=no-self-use +# pylint: disable=no-self-use,unused-variable from unittest.mock import Mock, patch import logging @@ -10,7 +10,6 @@ class TestMain: - """Unit tests for the top-level arguments.""" def test_main(self): @@ -48,7 +47,6 @@ def test_main_error(self): class TestInstall: - """Unit tests for the `install` command.""" @patch('gitman.commands.install') @@ -119,7 +117,6 @@ def test_install_with_depth_invalid(self): class TestUpdate: - """Unit tests for the `update` command.""" @patch('gitman.commands.update') @@ -183,7 +180,6 @@ def test_update_with_depth(self, mock_update): class TestList: - """Unit tests for the `list` command.""" @patch('gitman.commands.display') @@ -220,7 +216,6 @@ def test_update_with_depth(self, mock_update): def describe_lock(): - # pylint: disable=unused-variable @patch('gitman.commands.lock') def with_no_arguments(lock): @@ -234,7 +229,6 @@ def with_dependencies(lock): class TestUninstall: - """Unit tests for the `uninstall` command.""" @patch('gitman.commands.delete') @@ -263,7 +257,6 @@ def test_uninstall_force(self, mock_uninstall): class TestLogging: - """Unit tests for logging.""" arg_verbosity = [ diff --git a/gitman/test/test_models_config.py b/gitman/test/test_models_config.py index d1608317..6174ea17 100644 --- a/gitman/test/test_models_config.py +++ b/gitman/test/test_models_config.py @@ -1,6 +1,7 @@ -# pylint: disable=no-self-use,redefined-outer-name +# pylint: disable=no-self-use,redefined-outer-name,unused-variable,expression-not-assigned import pytest +from expecter import expect from gitman.models import Config, load_config @@ -101,6 +102,27 @@ def test_install_with_depth_2(self): assert 5 == count +def describe_config(): + + @pytest.fixture + def config(): + return Config('m/root', 'm.ext', 'm/location') + + def describe_get_path(): + + def it_defaults_to_sources_location(config): + expect(config.get_path()) == "m/root/m/location" + + def it_can_get_the_config_path(config): + expect(config.get_path('__config__')) == "m/root/m.ext" + + def it_can_get_log_path(config): + expect(config.get_path('__log__')) == "m/root/m/location/gitman.log" + + def it_can_get_dependency_path(config): + expect(config.get_path('foobar')) == "m/root/m/location/foobar" + + class TestLoad: def test_load_from_directory_with_config_file(self): From bf6776e402b9d3b000e56f087e345084ebd46b47 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Sat, 2 Apr 2016 22:56:38 -0400 Subject: [PATCH 02/10] Write to the log when displaying versions --- gitman/commands.py | 8 +++++++- gitman/models/config.py | 6 ++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/gitman/commands.py b/gitman/commands.py index 5a896123..8f7c7ed8 100644 --- a/gitman/commands.py +++ b/gitman/commands.py @@ -2,6 +2,7 @@ import os import functools +import datetime import logging from . import common, system @@ -112,7 +113,12 @@ def display(*, root=None, depth=None, allow_dirty=True): if config: common.show("Displaying current dependency versions...", log=False) common.show() - count = len(list(config.get_deps(depth=depth, allow_dirty=allow_dirty))) + config.log(datetime.datetime.now().strftime("%F %T")) + count = 0 + for identity in config.get_deps(depth=depth, allow_dirty=allow_dirty): + count += 1 + config.log("{}: {} @ {}", *identity) + config.log() return _display_result("display", "Displayed", count) diff --git a/gitman/models/config.py b/gitman/models/config.py index e09457f1..5412569c 100644 --- a/gitman/models/config.py +++ b/gitman/models/config.py @@ -1,7 +1,6 @@ """Wrappers for the dependency configuration files.""" import os -import datetime import logging import yorm @@ -179,11 +178,10 @@ def get_deps(self, depth=None, allow_dirty=True): common.dedent() - def log(self, message, *args): + def log(self, message="", *args): """Append a message to the log file.""" - stamp = datetime.datetime.now().strftime("+%F %T") with open(self.log_path, 'a') as outfile: - outfile.write("{}: {}\n".format(stamp, message % args)) + outfile.write(message.format(*args) + '\n') def _get_sources(self, *, use_locked=None): """Merge source lists using requested section as the base.""" From 5b2ef18ac580bd897030008792e389088c3d8beb Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Sat, 2 Apr 2016 23:39:12 -0400 Subject: [PATCH 03/10] Freeze time to test logging --- Makefile | 2 +- tests/test_api.py | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a5fadade..6a5b900b 100644 --- a/Makefile +++ b/Makefile @@ -119,7 +119,7 @@ depends: depends-ci depends-doc depends-dev .PHONY: depends-ci depends-ci: env Makefile $(DEPENDS_CI_FLAG) $(DEPENDS_CI_FLAG): Makefile - $(PIP) install --upgrade pep8 pep257 pylint coverage pytest pytest-describe pytest-expecter pytest-cov pytest-random + $(PIP) install --upgrade pep8 pep257 pylint coverage pytest pytest-describe pytest-expecter pytest-cov pytest-random freezegun @ touch $(DEPENDS_CI_FLAG) # flag to indicate dependencies are installed .PHONY: depends-doc diff --git a/tests/test_api.py b/tests/test_api.py index 801efaf0..f00ecf17 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -7,6 +7,7 @@ import pytest from expecter import expect +from freezegun import freeze_time import gitman from gitman.models import Config @@ -262,6 +263,24 @@ def it_should_lock_all_when_enabled(config): """) +def describe_list(): + + @freeze_time("2012-01-14 12:00:01") + def it_updates_the_log(config): + gitman.install() + gitman.list() + expect(open(config.log_path).read()) == strip(""" + 2012-01-14 12:00:01 + /private/tmp/gitman-shared/deps/gitman_1: https://github.com/jacebrowning/gitman-demo @ eb37743011a398b208dd9f9ef79a408c0fc10d48 + /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ ddbe17ef173538d1fda29bd99a14bab3c5d86e78 + /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ fb693447579235391a45ca170959b5583c5042d8 + /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 + /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 + /private/tmp/gitman-shared/deps/gitman_2: https://github.com/jacebrowning/gitman-demo @ 7bd138fe7359561a8c2ff9d195dff238794ccc04 + /private/tmp/gitman-shared/deps/gitman_3: https://github.com/jacebrowning/gitman-demo @ 9bf18e16b956041f0267c21baad555a23237b52e + """, end='\n\n') + + def describe_lock(): def it_should_record_all_versions_when_no_arguments(config): From 9e28a088683965c565c68af7f1ab8eb17e22484d Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 4 Apr 2016 19:54:30 -0400 Subject: [PATCH 04/10] Add unit and integration tests for the show command --- Makefile | 2 +- gitman/test/test_cli.py | 77 +++++++++++++++++++++++++++++++---------- tests/test_cli.py | 25 +++++++++++++ 3 files changed, 84 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 6a5b900b..02383623 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ endif # Test settings UNIT_TEST_COVERAGE := 73 -INTEGRATION_TEST_COVERAGE := 52 +INTEGRATION_TEST_COVERAGE := 79 COMBINED_TEST_COVERAGE := 97 # System paths diff --git a/gitman/test/test_cli.py b/gitman/test/test_cli.py index 03367aee..b1f52b39 100644 --- a/gitman/test/test_cli.py +++ b/gitman/test/test_cli.py @@ -1,9 +1,10 @@ -# pylint: disable=no-self-use,unused-variable +# pylint: disable=no-self-use,unused-variable,expression-not-assigned from unittest.mock import Mock, patch import logging import pytest +from expecter import expect from gitman import cli from gitman.common import _Config @@ -256,11 +257,51 @@ def test_uninstall_force(self, mock_uninstall): root=None, force=True) -class TestLogging: - """Unit tests for logging.""" +def describe_show(): - arg_verbosity = [ - ('', 0), + @patch('gitman.commands.show') + def with_no_arguments(show): + cli.main(['show']) + show.assert_called_once_with(root=None) + + @patch('gitman.commands.show') + def with_root(show): + cli.main(['show', '--root', "mock/root"]) + show.assert_called_once_with(root="mock/root") + + @patch('gitman.commands.show') + def with_names(show): + cli.main(['show', 'foo', 'bar']) + show.assert_called_once_with('foo', 'bar', root=None) + + @patch('gitman.commands.show') + def with_config(show): + cli.main(['show', '--config']) + show.assert_called_once_with('__config__', root=None) + + @patch('gitman.commands.show') + def with_log(show): + cli.main(['show', '--log']) + show.assert_called_once_with('__log__', root=None) + + +def describe_edit(): + + @patch('gitman.commands.edit') + def with_no_arguments(edit): + cli.main(['edit']) + edit.assert_called_once_with(root=None) + + @patch('gitman.commands.edit') + def with_root(edit): + cli.main(['edit', '--root', "mock/root"]) + edit.assert_called_once_with(root="mock/root") + + +def describe_logging(): + + argument_verbosity = [ + (None, 0), ('-v', 1), ('-vv', 2), ('-vvv', 3), @@ -269,17 +310,15 @@ class TestLogging: ('-q', -1), ] - @staticmethod - def mock_function(*args, **kwargs): - """Placeholder logic for logging tests.""" - logging.debug(args) - logging.debug(kwargs) - logging.warning("warning") - logging.error("error") - return True - - @pytest.mark.parametrize("arg,verbosity", arg_verbosity) - def test_level(self, arg, verbosity): - """Verify verbose level can be set.""" - cli.main([arg] if arg else [], self.mock_function) - assert verbosity == _Config.verbosity + @pytest.mark.parametrize("argument,verbosity", argument_verbosity) + def at_each_level(argument, verbosity): + + def function(*args, **kwargs): + logging.debug(args) + logging.debug(kwargs) + logging.warning("warning") + logging.error("error") + return True + + cli.main([argument] if argument else [], function) + expect(_Config.verbosity) == verbosity diff --git a/tests/test_cli.py b/tests/test_cli.py index a3cd167c..cdf3c5e1 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,5 +1,6 @@ # pylint: disable=unused-variable,redefined-outer-name,expression-not-assigned +import os from unittest.mock import patch, call import pytest @@ -16,6 +17,30 @@ def config(tmpdir): return path +@pytest.fixture +def location(tmpdir): + tmpdir.chdir() + path = str(tmpdir.join("gdm.yml")) + with open(path, 'w') as outfile: + outfile.write("location: foo") + return str(tmpdir.join("foo")) + + +def describe_show(): + + @patch('gitman.common.show') + def it_prints_location_by_default(show, location): + cli.main(['show']) + + expect(show.mock_calls) == [call(location)] + + @patch('gitman.common.show') + def it_can_print_a_depenendcy_path(show, location): + cli.main(['show', 'bar']) + + expect(show.mock_calls) == [call(os.path.join(location, "bar"))] + + def describe_edit(): @patch('gitman.system.launch') From 56f94717269464212096248f1a0bafded49be2e7 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 4 Apr 2016 20:39:25 -0400 Subject: [PATCH 05/10] Add documentation for the 'show' command --- CHANGES.md | 5 +++ docs/interfaces/api.md | 2 +- docs/interfaces/cli.md | 36 +++++++++++++++-- docs/interfaces/plugin.md | 2 +- docs/use-cases/build-integration.md | 60 +++++++++++++++++++++++++++++ mkdocs.yml | 1 + 6 files changed, 100 insertions(+), 6 deletions(-) create mode 100644 docs/use-cases/build-integration.md diff --git a/CHANGES.md b/CHANGES.md index 19fe3958..3553ec50 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Revision History ================ +0.10 (unreleased) +----------------- + +- Added `show` command to display dependency and internal paths. + 0.9 (2016/03/31) ---------------- diff --git a/docs/interfaces/api.md b/docs/interfaces/api.md index 6633b730..13ff949e 100644 --- a/docs/interfaces/api.md +++ b/docs/interfaces/api.md @@ -66,7 +66,7 @@ with optional arguments: ## Uninstall -To delete all source dependencies, call: +To delete all dependencies, call: ```python gitman.uninstall(root=None, force=False) diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index 319153d7..5470aadc 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -13,7 +13,7 @@ gitman install or filter the dependency list by directory name: ```sh -gitman install +gitman install ``` or limit the traversal of nested dependencies: @@ -51,7 +51,7 @@ gitman update or filter the dependency list by directory name: ```sh -gitman update +gitman update ``` or limit the traversal of nested dependencies: @@ -86,6 +86,8 @@ or exit with an error if there are any uncommitted changes: gitman list --no-dirty ``` +The `list` command will also record versions in the log file. + ## Lock To manually record the exact version of each dependency, run: @@ -97,7 +99,7 @@ gitman lock or lock down specific dependencies: ```sh -gitman lock +gitman lock ``` This can be combined with updating dependencies by running: @@ -114,7 +116,7 @@ gitman install ## Uninstall -To delete all source dependencies, run: +To delete all dependencies, run: ```sh gitman uninstall @@ -126,6 +128,32 @@ If any dependencies contain uncommitted changes, instead run: gitman uninstall --force ``` +## Show + +To display the path to the dependency storage location: + +```sh +gitman show +``` + +To display the path to a dependency: + +```sh +gitman show +``` + +To display the path to the configuration file: + +```sh +gitman show --config +``` + +To display the path to the log file: + +```sh +gitman show --log +``` + ## Edit To open the existing configuration file: diff --git a/docs/interfaces/plugin.md b/docs/interfaces/plugin.md index 93f88d8b..54652fce 100644 --- a/docs/interfaces/plugin.md +++ b/docs/interfaces/plugin.md @@ -58,7 +58,7 @@ git deps --list ## Uninstall -To delete all source dependencies, run: +To delete all dependencies, run: ```sh git deps --uninstall diff --git a/docs/use-cases/build-integration.md b/docs/use-cases/build-integration.md new file mode 100644 index 00000000..8d77603e --- /dev/null +++ b/docs/use-cases/build-integration.md @@ -0,0 +1,60 @@ +# Build System Integration + +GitMan can be invoked from your build system or continuous integration environment. It provides a convenient way to access its internal file and directory paths using the [`show`](../interfaces/cli.md#show) command. + +## Makefile + +The following example shows one way you might want to call `gitman` from within a Makefile: + +```makefile +.PHONY: all +all: depends + +.PHONY: depends +depends: $(shell gitman show --log) +$(shell gitman show --log): $(shell gitman show --config) + gitman install + make -C $(shell gitman show lib_foo) configure all install + make -C $(shell gitman show lib_bar) configure all install + gitman list + +.PHONY: clean +clean: + gitman uninstall +``` + +using a configuration file similar to: + +```yaml +location: sources +sources: +- dir: lib_foo + repo: https://github.com/example/lib_foo + rev: develop +- dir: lib_bar + repo: https://github.com/example/lib_bar + rev: master +sources_locked: +- dir: lib_foo + repo: https://github.com/example/lib_foo + rev: 73cb3668d4c9c3388fb21de16c9c3f6217cc0e1c +- dir: lib_bar + repo: https://github.com/example/lib_bar + rev: 560ea99953a4b3e393e170e07895d14904eb032c +``` + +## Workflow + +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 +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 + +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 diff --git a/mkdocs.yml b/mkdocs.yml index c251e067..c8043d64 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -18,6 +18,7 @@ pages: - 'Replacing Submodules': 'use-cases/submodules.md' - 'Tracking Branches': 'use-cases/branch-tracking.md' - 'Linking Feature Branches': 'use-cases/linked-features.md' + - 'Build System Integration': 'use-cases/build-integration.md' - About: - 'Release Notes': 'about/changes.md' - Contributing: 'about/contributing.md' From ead888fa3d7532df06ce0d073479ee9645545982 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 4 Apr 2016 20:50:06 -0400 Subject: [PATCH 06/10] Add a test to ensure the log file is deleted on uninstall --- gitman/__init__.py | 4 ++-- tests/test_api.py | 28 ++++++++++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/gitman/__init__.py b/gitman/__init__.py index ff453aaa..b0b7f21c 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '0.9' +__version__ = '0.10.dev1' CLI = 'gitman' PLUGIN = 'deps' @@ -14,7 +14,7 @@ PYTHON_VERSION = 3, 4 if sys.version_info < PYTHON_VERSION: # pragma: no cover (manual test) - exit("Python {}.{}+ is required.".format(*PYTHON_VERSION)) + sys.exit("Python {}.{}+ is required.".format(*PYTHON_VERSION)) try: # pylint: disable=wrong-import-position diff --git a/tests/test_api.py b/tests/test_api.py index f00ecf17..90f82844 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -56,7 +56,7 @@ def config(root="/tmp/gitman-shared"): def describe_install(): - def it_should_create_missing_directories(config): + def it_creates_missing_directories(config): expect(os.path.isdir(config.location)) == False expect(gitman.install('gitman_1', depth=1)) == True @@ -68,7 +68,7 @@ def it_should_not_modify_config(config): expect(config.__mapper__.text) == CONFIG - def it_should_merge_sources(config): + def it_merges_sources(config): config.__mapper__.text = strip(""" location: deps sources: @@ -125,18 +125,18 @@ def config_with_link(config): return config - def it_should_create(config_with_link): + def it_should_create_links(config_with_link): expect(gitman.install(depth=1)) == True expect(os.listdir()).contains('my_link') - def it_should_not_overwrite(config_with_link): + def it_should_not_overwrite_files(config_with_link): os.system("touch my_link") with pytest.raises(RuntimeError): gitman.install(depth=1) - def it_should_overwrite_with_force(config_with_link): + def it_overwrites_files_with_force(config_with_link): os.system("touch my_link") expect(gitman.install(depth=1, force=True)) == True @@ -144,7 +144,7 @@ def it_should_overwrite_with_force(config_with_link): def describe_uninstall(): - def it_should_delete_dependencies_when_they_exist(config): + def it_deletes_dependencies_when_they_exist(config): gitman.install('gitman_1', depth=1) expect(os.path.isdir(config.location)) == True @@ -157,6 +157,14 @@ def it_should_not_fail_when_no_dependnecies_exist(config): expect(gitman.uninstall()) == True + def it_deletes_the_log_file(config): + gitman.install('gitman_1', depth=1) + gitman.list() + expect(os.path.exists(config.log_path)) == True + + gitman.uninstall() + expect(os.path.exists(config.log_path)) == False + def describe_update(): @@ -165,7 +173,7 @@ def it_should_not_modify_config(config): expect(config.__mapper__.text) == CONFIG - def it_should_lock_previously_locked_dependnecies(config): + def it_locks_previously_locked_dependnecies(config): config.__mapper__.text = strip(""" location: deps sources: @@ -243,7 +251,7 @@ def it_should_not_lock_dependnecies_when_disabled(config): rev: (old revision) """) - def it_should_lock_all_when_enabled(config): + def it_should_lock_all_dependencies_when_enabled(config): gitman.update(depth=1, lock=True) expect(config.__mapper__.text) == CONFIG + strip(""" @@ -283,7 +291,7 @@ def it_updates_the_log(config): def describe_lock(): - def it_should_record_all_versions_when_no_arguments(config): + def it_records_all_versions_when_no_arguments(config): expect(gitman.update(depth=1, lock=False)) == True expect(gitman.lock()) == True @@ -303,7 +311,7 @@ def it_should_record_all_versions_when_no_arguments(config): rev: 9bf18e16b956041f0267c21baad555a23237b52e """) == config.__mapper__.text - def it_should_record_specified_dependencies(config): + def it_records_specified_dependencies(config): expect(gitman.update(depth=1, lock=False)) == True expect(gitman.lock('gitman_1', 'gitman_3')) == True From 16bb18a51f4dc09cbb6df6c4e04bc10301839ae3 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 4 Apr 2016 20:55:05 -0400 Subject: [PATCH 07/10] Strip "/private" from paths for cross-platform testing http://apple.stackexchange.com/questions/1043/why-is-tmp-a-symlink-to-pr ivate-tmp --- tests/test_api.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index 90f82844..41bab24c 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -277,15 +277,17 @@ def describe_list(): def it_updates_the_log(config): gitman.install() gitman.list() - expect(open(config.log_path).read()) == strip(""" + with open(config.log_path) as fin: + contents = fin.read().replace("/private", "") + expect(contents) == strip(""" 2012-01-14 12:00:01 - /private/tmp/gitman-shared/deps/gitman_1: https://github.com/jacebrowning/gitman-demo @ eb37743011a398b208dd9f9ef79a408c0fc10d48 - /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ ddbe17ef173538d1fda29bd99a14bab3c5d86e78 - /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ fb693447579235391a45ca170959b5583c5042d8 - /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 - /private/tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 - /private/tmp/gitman-shared/deps/gitman_2: https://github.com/jacebrowning/gitman-demo @ 7bd138fe7359561a8c2ff9d195dff238794ccc04 - /private/tmp/gitman-shared/deps/gitman_3: https://github.com/jacebrowning/gitman-demo @ 9bf18e16b956041f0267c21baad555a23237b52e + /tmp/gitman-shared/deps/gitman_1: https://github.com/jacebrowning/gitman-demo @ eb37743011a398b208dd9f9ef79a408c0fc10d48 + /tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ ddbe17ef173538d1fda29bd99a14bab3c5d86e78 + /tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_3: https://github.com/jacebrowning/gdm-demo @ fb693447579235391a45ca170959b5583c5042d8 + /tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_3/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 + /tmp/gitman-shared/deps/gitman_1/gdm_sources/gdm_4: https://github.com/jacebrowning/gdm-demo @ 63ddfd82d308ddae72d31b61cb8942c898fa05b5 + /tmp/gitman-shared/deps/gitman_2: https://github.com/jacebrowning/gitman-demo @ 7bd138fe7359561a8c2ff9d195dff238794ccc04 + /tmp/gitman-shared/deps/gitman_3: https://github.com/jacebrowning/gitman-demo @ 9bf18e16b956041f0267c21baad555a23237b52e """, end='\n\n') From caf08ff55fc1cba293d93c56d8134e0d5bfde25a Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 14 Apr 2016 10:04:59 -0400 Subject: [PATCH 08/10] Bump version to 0.10 --- CHANGES.md | 2 +- gitman/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3553ec50..bb826459 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Revision History ================ -0.10 (unreleased) +0.10 (2016/04/14) ----------------- - Added `show` command to display dependency and internal paths. diff --git a/gitman/__init__.py b/gitman/__init__.py index b0b7f21c..2881ddfa 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '0.10.dev1' +__version__ = '0.10' CLI = 'gitman' PLUGIN = 'deps' From 193c2e684776c2d1ef3bc2f19dc481ecc4de7b7f Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 14 Apr 2016 10:05:25 -0400 Subject: [PATCH 09/10] Remove unused --root option on the main command --- gitman/cli.py | 24 +++++++++++++++++------- gitman/commands.py | 21 ++++++++++++--------- gitman/test/test_cli.py | 2 +- tests/test_cli.py | 6 ++++++ 4 files changed, 36 insertions(+), 17 deletions(-) diff --git a/gitman/cli.py b/gitman/cli.py index d3f885f1..e89a3e40 100644 --- a/gitman/cli.py +++ b/gitman/cli.py @@ -40,8 +40,7 @@ def main(args=None, function=None): # Main parser parser = argparse.ArgumentParser(prog=CLI, description=DESCRIPTION, - parents=[debug, project], **shared) - + parents=[debug], **shared) subs = parser.add_subparsers(help="", dest='command', metavar="") # Install parser @@ -125,13 +124,14 @@ def main(args=None, function=None): def _get_command(function, namespace): args = [] - kwargs = dict(root=namespace.root) + kwargs = {} exit_msg = "" if namespace.command in ['install', 'update']: function = getattr(commands, namespace.command) args = namespace.name - kwargs.update(depth=namespace.depth, + kwargs.update(root=namespace.root, + depth=namespace.depth, force=namespace.force, clean=namespace.clean) if namespace.command == 'install': @@ -140,26 +140,36 @@ def _get_command(function, namespace): kwargs.update(recurse=namespace.recurse, lock=namespace.lock) exit_msg = "\n" + "Run again with '--force' to overwrite" + elif namespace.command == 'list': function = commands.display - kwargs.update(dict(depth=namespace.depth, - allow_dirty=namespace.allow_dirty)) + kwargs.update(root=namespace.root, + depth=namespace.depth, + allow_dirty=namespace.allow_dirty) + elif namespace.command == 'lock': function = getattr(commands, namespace.command) args = namespace.name + kwargs.update(root=namespace.root) + elif namespace.command == 'uninstall': function = commands.delete - kwargs.update(force=namespace.force) + kwargs.update(root=namespace.root, + force=namespace.force) exit_msg = "\n" + "Run again with '--force' to ignore" + elif namespace.command == 'show': function = commands.show args = namespace.name + kwargs.update(root=namespace.root) if namespace.config: args.append('__config__') if namespace.log: args.append('__log__') + elif namespace.command == 'edit': function = commands.edit + kwargs.update(root=namespace.root) return function, args, kwargs, exit_msg diff --git a/gitman/commands.py b/gitman/commands.py index 8f7c7ed8..7dce45e0 100644 --- a/gitman/commands.py +++ b/gitman/commands.py @@ -187,6 +187,9 @@ def show(*names, root=None): root = _find_root(root) config = load_config(root) + if not config: + log.error("No configuration found") + return False for name in names or [None]: common.show(config.get_path(name)) @@ -206,27 +209,27 @@ def edit(*, root=None): root = _find_root(root) config = load_config(root) - - if config: - return system.launch(config.path) - else: + if not config: log.error("No configuration found") return False + return system.launch(config.path) -def _find_root(root, cwd=None): + +def _find_root(base, cwd=None): if cwd is None: cwd = os.getcwd() log.info("Current directory: %s", cwd) - if root: - root = os.path.abspath(root) + if base: + root = os.path.abspath(base) log.info("Specified root: %s", root) + else: + log.info("Searching for root...") path = cwd prev = None - - log.info("Searching for root...") + root = None while path != prev: log.debug("Checking path: %s", path) if '.git' in os.listdir(path): diff --git a/gitman/test/test_cli.py b/gitman/test/test_cli.py index b1f52b39..e6e05dc4 100644 --- a/gitman/test/test_cli.py +++ b/gitman/test/test_cli.py @@ -19,7 +19,7 @@ def test_main(self): cli.main([], mock_function) - mock_function.assert_called_once_with(root=None) + mock_function.assert_called_once_with() def test_main_fail(self): """Verify error in commands are detected.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index cdf3c5e1..4900d01a 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -40,6 +40,12 @@ def it_can_print_a_depenendcy_path(show, location): expect(show.mock_calls) == [call(os.path.join(location, "bar"))] + def it_exits_when_no_config_found(tmpdir): + tmpdir.chdir() + + with expect.raises(SystemExit): + cli.main(['show']) + def describe_edit(): From d550db3b8918b2442d54bfbc99e812969fbce534 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 14 Apr 2016 10:40:37 -0400 Subject: [PATCH 10/10] Update to YORM 0.8 --- gitman/models/config.py | 5 +++-- requirements.txt | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/gitman/models/config.py b/gitman/models/config.py index 5412569c..38f525f9 100644 --- a/gitman/models/config.py +++ b/gitman/models/config.py @@ -16,7 +16,7 @@ @yorm.attr(location=String) @yorm.attr(sources=SortedList.of_type(Source)) @yorm.attr(sources_locked=SortedList.of_type(Source)) -@yorm.sync("{self.root}/{self.filename}") +@yorm.sync("{self.root}/{self.filename}", auto_save=False) class Config: """A dictionary of dependency configuration options.""" @@ -139,7 +139,8 @@ def lock_deps(self, *names, obey_existing=True): shell.cd(self.location_path, _show=False) if count: - yorm.update_file(self) + yorm.save(self) + return count def uninstall_deps(self): diff --git a/requirements.txt b/requirements.txt index 2c849ef1..9faa8628 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -YORM ~= 0.7.2 +YORM ~= 0.8 sh ~= 1.11