From 04baebd742a112a3047e620d79010e25e1c47d85 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Fri, 13 Nov 2015 11:56:27 -0500 Subject: [PATCH 01/18] Add '--depth' to the docs --- docs/interfaces/cli.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index 4ff00370..9769a72e 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -16,6 +16,12 @@ or filter the dependency list by directory name: gdm install ``` +or limit the traversal of nested dependencies: + +```sh +gdm install --depth= +``` + Delete all untracked files in dependencies by instead running: ```sh @@ -42,6 +48,12 @@ or filter the dependency list by directory name: gdm update ``` +or limit the traversal of nested dependencies: + +```sh +gdm update --depth= +``` + This will also record the exact versions that were checked out. Disable this behavior by instead running: ```sh From a8203e756ee603bc6d75dadc8e7e9ae730b8d3db Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 30 Nov 2015 17:25:55 -0500 Subject: [PATCH 02/18] Completely skip directories when beyond specified depth --- gdm/config.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/gdm/config.py b/gdm/config.py index b3ec10b4..80364eb9 100644 --- a/gdm/config.py +++ b/gdm/config.py @@ -148,6 +148,10 @@ def location_path(self): def install_deps(self, *names, depth=None, update=True, recurse=False, force=False, clean=True): """Get all sources.""" + if depth == 0: + log.info("Skipped directory: %s", self.location_path) + return 0 + if not os.path.isdir(self.location_path): self.mkdir(self.location_path) self.cd(self.location_path) @@ -161,11 +165,7 @@ def install_deps(self, *names, depth=None, for source in sources: if source.dir in dirs: dirs.remove(source.dir) - if depth == 0: - log.info("Skipped dependency: %s", source.dir) - continue - else: - count += 1 + count += 1 else: log.info("Skipped dependency: %s", source.dir) continue From adfce5c493538c5e310e8c2ab563b695ccc348ad Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 30 Nov 2015 17:56:56 -0500 Subject: [PATCH 03/18] Update style for PyLint 1.5 --- .pylintrc | 2 +- gdm/__init__.py | 3 ++- gdm/commands.py | 2 +- gdm/common.py | 2 +- gdm/shell.py | 47 ++++++++++++++++++++---------------------- gdm/test/test_cli.py | 3 ++- gdm/test/test_shell.py | 24 ++++++++++----------- 7 files changed, 41 insertions(+), 42 deletions(-) diff --git a/.pylintrc b/.pylintrc index 2853e641..3977dd48 100644 --- a/.pylintrc +++ b/.pylintrc @@ -1,6 +1,6 @@ [MESSAGES CONTROL] -disable=locally-disabled,fixme,too-few-public-methods,too-many-public-methods,invalid-name,global-statement,too-many-ancestors +disable=locally-disabled,fixme,too-few-public-methods,too-many-public-methods,invalid-name,global-statement,too-many-ancestors,misplaced-comparison-constant [FORMAT] diff --git a/gdm/__init__.py b/gdm/__init__.py index a1cb19ed..50ee4d73 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -12,10 +12,11 @@ PYTHON_VERSION = 3, 3 -if not sys.version_info >= PYTHON_VERSION: # pragma: no cover (manual test) +if sys.version_info < PYTHON_VERSION: # pragma: no cover (manual test) exit("Python {}.{}+ is required.".format(*PYTHON_VERSION)) try: + # pylint: disable=wrong-import-position from .commands import install from .commands import update from .commands import display as list # pylint: disable=redefined-builtin diff --git a/gdm/commands.py b/gdm/commands.py index 88de8082..830ba15d 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -174,7 +174,7 @@ def _display_result(present, past, count, allow_zero=False): """ if count is None: - log.warn("No dependencies to %s", present) + log.warning("No dependencies to %s", present) elif count == 1: log.info("%s 1 dependency", past) else: diff --git a/gdm/common.py b/gdm/common.py index 1a37d5a3..76f09f37 100644 --- a/gdm/common.py +++ b/gdm/common.py @@ -94,7 +94,7 @@ def configure_logging(count=0): # Warn about excessive verbosity if count > _Config.MAX_VERBOSITY: msg = "maximum verbosity level is {}".format(_Config.MAX_VERBOSITY) - logging.warn(msg) + logging.warning(msg) _Config.verbosity = _Config.MAX_VERBOSITY else: _Config.verbosity = count diff --git a/gdm/shell.py b/gdm/shell.py index 85cb9351..6ba896a0 100644 --- a/gdm/shell.py +++ b/gdm/shell.py @@ -14,8 +14,14 @@ log = logging.getLogger(__name__) -def _call(name, *args, ignore=False, catch=True, capture=False): +def call(name, *args, ignore=False, catch=True, capture=False, visible=True): """Call a shell program with arguments.""" + msg = CMD_PREFIX + ' '.join([name] + list(args)) + if visible: + common.show(msg) + else: + log.debug(msg) + if name == 'cd' and len(args) == 1: os.chdir(args[0]) else: @@ -38,39 +44,29 @@ def _call(name, *args, ignore=False, catch=True, capture=False): raise common.CallException(msg) -class _Base: - """Functions to call shell commands.""" - - @staticmethod - def _call(*args, visible=True, catch=True, ignore=False, capture=False): - msg = CMD_PREFIX + ' '.join(args) - if visible: - common.show(msg) - else: - log.debug(msg) - return _call(*args, catch=catch, ignore=ignore, capture=capture) - - -class ShellMixin(_Base): +class ShellMixin: """Provides classes with shell utilities.""" - def mkdir(self, path): - self._call('mkdir', '-p', path) + @staticmethod + def mkdir(path): + call('mkdir', '-p', path) - def cd(self, path, visible=True): - self._call('cd', path, visible=visible) + @staticmethod + def cd(path, visible=True): + call('cd', path, visible=visible) def ln(self, source, target): dirpath = os.path.dirname(target) if not os.path.isdir(dirpath): self.mkdir(dirpath) - self._call('ln', '-s', source, target) + call('ln', '-s', source, target) - def rm(self, path): - self._call('rm', '-rf', path) + @staticmethod + def rm(path): + call('rm', '-rf', path) -class GitMixin(_Base): +class GitMixin: """Provides classes with Git utilities.""" def git_clone(self, repo, path): @@ -142,5 +138,6 @@ def _git_get_sha_from_rev(self, rev): branch, visible=False, capture=True) return rev - def _git(self, *args, **kwargs): - return self._call('git', *args, **kwargs) + @staticmethod + def _git(*args, **kwargs): + return call('git', *args, **kwargs) diff --git a/gdm/test/test_cli.py b/gdm/test/test_cli.py index 85be4da7..f94b8140 100644 --- a/gdm/test/test_cli.py +++ b/gdm/test/test_cli.py @@ -1,10 +1,11 @@ """Unit tests for the 'cli' module.""" # pylint: disable=no-self-use -import pytest from unittest.mock import Mock, patch import logging +import pytest + from gdm import cli from gdm.common import _Config diff --git a/gdm/test/test_shell.py b/gdm/test/test_shell.py index 5579cbc1..95f9ef4e 100644 --- a/gdm/test/test_shell.py +++ b/gdm/test/test_shell.py @@ -6,7 +6,7 @@ import pytest from gdm.common import CallException -from gdm.shell import _call, ShellMixin, GitMixin +from gdm.shell import call, ShellMixin, GitMixin class TestCall: @@ -16,32 +16,32 @@ class TestCall: @patch('os.chdir') def test_cd(self, mock_chdir): """Verify directories are changed correctly.""" - _call('cd', 'mock/dir') + call('cd', 'mock/dir') mock_chdir.assert_called_once_with('mock/dir') @patch('gdm.shell.Command') def test_other(self, mock_command): """Verify directories are changed correctly.""" - _call('mock_program') + call('mock_program') mock_command.assert_called_once_with('mock_program') def test_other_error(self): """Verify program errors are handled.""" with pytest.raises(SystemExit): - _call('git', '--invalid-git-argument') + call('git', '--invalid-git-argument') def test_other_error_uncaught(self): """Verify program errors can be left uncaught.""" with pytest.raises(CallException): - _call('git', '--invalid-git-argument', catch=False) + call('git', '--invalid-git-argument', catch=False) def test_other_error_ignored(self): """Verify program errors can be ignored.""" - _call('git', '--invalid-git-argument', ignore=True) + call('git', '--invalid-git-argument', ignore=True) def test_other_capture(self): """Verify a program's output can be captured.""" - stdout = _call('echo', 'Hello, world!\n', capture=True) + stdout = call('echo', 'Hello, world!\n', capture=True) assert "Hello, world!" == stdout @@ -56,7 +56,7 @@ def assert_calls(mock_call, expected): assert expected == actual -@patch('gdm.shell._call') +@patch('gdm.shell.call') class TestShell(_BaseTestCalls): """Tests for calls to shell utilities.""" @@ -92,7 +92,7 @@ def test_rm(self, mock_call): self.assert_calls(mock_call, ["rm -rf mock/dir/path"]) -@patch('gdm.shell._call') +@patch('gdm.shell.call') class TestGit(_BaseTestCalls): """Tests for calls to Git.""" @@ -152,17 +152,17 @@ def test_changes(self, mock_call): def test_changes_false(self, _): """Verify the absence of changes can be detected.""" - with patch('gdm.shell._call', Mock(return_value="")): + with patch('gdm.shell.call', Mock(return_value="")): assert False is self.shell.git_changes() def test_changes_true_untracked(self, _): """Verify untracked files can be detected.""" - with patch('gdm.shell._call', Mock(return_value="file_1")): + with patch('gdm.shell.call', Mock(return_value="file_1")): assert True is self.shell.git_changes() def test_changes_true_uncommitted(self, _): """Verify uncommitted changes can be detected.""" - with patch('gdm.shell._call', Mock(side_effect=CallException)): + with patch('gdm.shell.call', Mock(side_effect=CallException)): assert True is self.shell.git_changes() def test_update(self, mock_call): From 7dfc8600fb7552fabe281390a1ae630f2c3b9c52 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 2 Dec 2015 12:59:16 -0500 Subject: [PATCH 04/18] Convert base class to a function --- gdm/test/test_shell.py | 51 +++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 28 deletions(-) diff --git a/gdm/test/test_shell.py b/gdm/test/test_shell.py index 95f9ef4e..824a8fef 100644 --- a/gdm/test/test_shell.py +++ b/gdm/test/test_shell.py @@ -45,19 +45,14 @@ def test_other_capture(self): assert "Hello, world!" == stdout -class _BaseTestCalls: - - """Base test class to verify shell calls are correct.""" - - @staticmethod - def assert_calls(mock_call, expected): - """Confirm the expected list of calls matches the mock call.""" - actual = [' '.join(args[0]) for args in mock_call.call_args_list] - assert expected == actual +def assert_calls(mock_call, expected): + """Confirm the expected list of calls matches the mock call.""" + actual = [' '.join(args[0]) for args in mock_call.call_args_list] + assert expected == actual @patch('gdm.shell.call') -class TestShell(_BaseTestCalls): +class TestShell: """Tests for calls to shell utilities.""" @@ -66,34 +61,34 @@ class TestShell(_BaseTestCalls): def test_mkdir(self, mock_call): """Verify the commands to create directories.""" self.shell.mkdir('mock/dir/path') - self.assert_calls(mock_call, ["mkdir -p mock/dir/path"]) + assert_calls(mock_call, ["mkdir -p mock/dir/path"]) def test_cd(self, mock_call): """Verify the commands to change directories.""" self.shell.cd('mock/dir/path') - self.assert_calls(mock_call, ["cd mock/dir/path"]) + assert_calls(mock_call, ["cd mock/dir/path"]) @patch('os.path.isdir', Mock(return_value=True)) def test_ln(self, mock_call): """Verify the commands to create symbolic links.""" self.shell.ln('mock/target', 'mock/source') - self.assert_calls(mock_call, ["ln -s mock/target mock/source"]) + assert_calls(mock_call, ["ln -s mock/target mock/source"]) @patch('os.path.isdir', Mock(return_value=False)) def test_ln_missing_parent(self, mock_call): """Verify the commands to create symbolic links (missing parent).""" self.shell.ln('mock/target', 'mock/source') - self.assert_calls(mock_call, ["mkdir -p mock", - "ln -s mock/target mock/source"]) + assert_calls(mock_call, ["mkdir -p mock", + "ln -s mock/target mock/source"]) def test_rm(self, mock_call): """Verify the commands to delete files/folders.""" self.shell.rm('mock/dir/path') - self.assert_calls(mock_call, ["rm -rf mock/dir/path"]) + assert_calls(mock_call, ["rm -rf mock/dir/path"]) @patch('gdm.shell.call') -class TestGit(_BaseTestCalls): +class TestGit: """Tests for calls to Git.""" @@ -102,12 +97,12 @@ class TestGit(_BaseTestCalls): def test_clone(self, mock_call): """Verify the commands to clone a new Git repository.""" self.shell.git_clone('mock.git', 'mock/path') - self.assert_calls(mock_call, ["git clone mock.git mock/path"]) + assert_calls(mock_call, ["git clone mock.git mock/path"]) def test_fetch(self, mock_call): """Verify the commands to fetch from a Git repository.""" self.shell.git_fetch('mock.git') - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git remote remove origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", @@ -116,7 +111,7 @@ def test_fetch(self, mock_call): def test_fetch_rev(self, mock_call): """Verify the commands to fetch from a Git repository w/ rev.""" self.shell.git_fetch('mock.git', 'mock-rev') - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git remote remove origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin mock-rev", @@ -125,7 +120,7 @@ def test_fetch_rev(self, mock_call): def test_fetch_rev_sha(self, mock_call): """Verify the commands to fetch from a Git repository w/ SHA.""" self.shell.git_fetch('mock.git', 'abcdef1234' * 4) - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git remote remove origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", @@ -134,7 +129,7 @@ def test_fetch_rev_sha(self, mock_call): def test_fetch_rev_revparse(self, mock_call): """Verify the commands to fetch from a Git repository w/ rev-parse.""" self.shell.git_fetch('mock.git', 'master@{2015-02-12 18:30:00}') - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git remote remove origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", @@ -143,7 +138,7 @@ def test_fetch_rev_revparse(self, mock_call): def test_changes(self, mock_call): """Verify the commands to check for uncommitted changes.""" self.shell.git_changes() - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ # based on: http://stackoverflow.com/questions/3878624 "git update-index -q --refresh", "git diff-index --quiet HEAD", @@ -168,7 +163,7 @@ def test_changes_true_uncommitted(self, _): def test_update(self, mock_call): """Verify the commands to update a working tree to a revision.""" self.shell.git_update('mock_rev') - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git stash", "git clean --force -d -x", "git checkout --force mock_rev", @@ -178,7 +173,7 @@ def test_update(self, mock_call): def test_update_no_clean(self, mock_call): self.shell.git_update('mock_rev', clean=False) - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git stash", "git checkout --force mock_rev", "git branch --set-upstream-to origin/mock_rev", @@ -189,7 +184,7 @@ def test_update_revparse(self, mock_call): """Verify the commands to update a working tree to a rev-parse.""" mock_call.return_value = "abc123" self.shell.git_update('mock_branch@{2015-02-12 18:30:00}') - self.assert_calls(mock_call, [ + assert_calls(mock_call, [ "git checkout --force mock_branch", "git rev-list -n 1 --before='2015-02-12 18:30:00' mock_branch", "git stash", @@ -202,9 +197,9 @@ def test_update_revparse(self, mock_call): def test_get_url(self, mock_call): """Verify the commands to get the current repository's URL.""" self.shell.git_get_url() - self.assert_calls(mock_call, ["git config --get remote.origin.url"]) + assert_calls(mock_call, ["git config --get remote.origin.url"]) def test_get_sha(self, mock_call): """Verify the commands to get the working tree's SHA.""" self.shell.git_get_sha() - self.assert_calls(mock_call, ["git rev-parse HEAD"]) + assert_calls(mock_call, ["git rev-parse HEAD"]) From 178ab57ab2d145bb391d0bde35c79b0c9f7a8e48 Mon Sep 17 00:00:00 2001 From: Aravindhan Dhanasekaran Date: Mon, 14 Dec 2015 10:42:12 -0800 Subject: [PATCH 05/18] Use 'rm' instead of 'remove' for 'git remote' cmds Issue ===== The fetch part will remove an existing remote and add a new one. The code for removing a remote was using 'git remote remove' command, but it should have been 'git remote rm'. Fix Description =============== Updated the GDM and corresponding test code to use "git remote rm origin" in place of "git remote remove origin". --- gdm/shell.py | 2 +- gdm/test/test_shell.py | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gdm/shell.py b/gdm/shell.py index 6ba896a0..92ad6094 100644 --- a/gdm/shell.py +++ b/gdm/shell.py @@ -75,7 +75,7 @@ def git_clone(self, repo, path): def git_fetch(self, repo, rev=None): """Fetch the latest changes from the remote repository.""" - self._git('remote', 'remove', 'origin', visible=False, ignore=True) + self._git('remote', 'rm', 'origin', visible=False, ignore=True) self._git('remote', 'add', 'origin', repo) args = ['fetch', '--tags', '--force', '--prune', 'origin'] if rev: diff --git a/gdm/test/test_shell.py b/gdm/test/test_shell.py index 824a8fef..e36286ea 100644 --- a/gdm/test/test_shell.py +++ b/gdm/test/test_shell.py @@ -103,7 +103,7 @@ def test_fetch(self, mock_call): """Verify the commands to fetch from a Git repository.""" self.shell.git_fetch('mock.git') assert_calls(mock_call, [ - "git remote remove origin", + "git remote rm origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", ]) @@ -112,7 +112,7 @@ def test_fetch_rev(self, mock_call): """Verify the commands to fetch from a Git repository w/ rev.""" self.shell.git_fetch('mock.git', 'mock-rev') assert_calls(mock_call, [ - "git remote remove origin", + "git remote rm origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin mock-rev", ]) @@ -121,7 +121,7 @@ def test_fetch_rev_sha(self, mock_call): """Verify the commands to fetch from a Git repository w/ SHA.""" self.shell.git_fetch('mock.git', 'abcdef1234' * 4) assert_calls(mock_call, [ - "git remote remove origin", + "git remote rm origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", ]) @@ -130,7 +130,7 @@ def test_fetch_rev_revparse(self, mock_call): """Verify the commands to fetch from a Git repository w/ rev-parse.""" self.shell.git_fetch('mock.git', 'master@{2015-02-12 18:30:00}') assert_calls(mock_call, [ - "git remote remove origin", + "git remote rm origin", "git remote add origin mock.git", "git fetch --tags --force --prune origin", ]) From 2558e77c83caa915c37095ca3296af197470974d Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Dec 2015 20:06:27 -0500 Subject: [PATCH 06/18] Switch to Python 3.5 for development And temporarily disable doctests. --- Makefile | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index d413af95..314e0200 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ EGG_INFO := $(subst -,_,$(PROJECT)).egg-info # Python settings PYTHON_MAJOR ?= 3 -PYTHON_MINOR ?= 4 +PYTHON_MINOR ?= 5 # Test settings UNIT_TEST_COVERAGE := 72 @@ -118,7 +118,7 @@ $(DEPENDS_DEV_FLAG): Makefile ifdef WINDOWS $(PIP) install --upgrade pywin32 else ifdef MAC - $(PIP) install --upgrade pync MacFSEvents + $(PIP) install --upgrade pync MacFSEvents==0.4 else ifdef LINUX $(PIP) install --upgrade pyinotify endif @@ -205,7 +205,7 @@ fix: depends-dev RANDOM_SEED ?= $(shell date +%s) -PYTEST_CORE_OPTS := --doctest-modules --verbose -r X --maxfail=3 +PYTEST_CORE_OPTS := --verbose -r X --maxfail=3 PYTEST_COV_OPTS := --cov=$(PACKAGE) --cov-report=term-missing --no-cov-on-fail PYTEST_RANDOM_OPTS := --random --random-seed=$(RANDOM_SEED) From f2e854ef4c901947e045cf3f0665076572da6bf9 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Dec 2015 21:02:14 -0500 Subject: [PATCH 07/18] Apply dependency filter to locking Fixes #76 Fixes #77 --- gdm/commands.py | 2 +- gdm/config.py | 26 ++++++++++++++++++++++---- gdm/test/test_config.py | 10 +++++++++- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/gdm/commands.py b/gdm/commands.py index 830ba15d..a4e79bb0 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -72,7 +72,7 @@ def update(*names, root=None, depth=None, if count and lock: common.show("Recording installed versions...", log=False) common.show() - config.lock_deps() + config.lock_deps(*names) return _display_result("update", "Updated", count) diff --git a/gdm/config.py b/gdm/config.py index 80364eb9..b836976d 100644 --- a/gdm/config.py +++ b/gdm/config.py @@ -38,6 +38,12 @@ def __str__(self): fmt += " <- '{s}'" return fmt.format(r=self.repo, v=self.rev, d=self.dir, s=self.link) + def __eq__(self, other): + return self.dir == other.dir + + def __ne__(self, other): + return self.dir != other.dir + def __lt__(self, other): return self.dir < other.dir @@ -195,15 +201,27 @@ def install_deps(self, *names, depth=None, return count - def lock_deps(self): + def lock_deps(self, *names): """Lock down the immediate dependency versions.""" self.cd(self.location_path) common.show() common.indent() - self.sources_locked = [] - for source in self.sources: - self.sources_locked.append(source.lock()) + sources = self.sources_locked.copy() + dirs = list(names) if names else [source.dir for source in sources] + + for source in sources: + if source.dir not in dirs: + log.info("Skipped dependency: %s", source.dir) + continue + + try: + index = self.sources_locked.index(source) + except ValueError: + self.sources_locked.append(source.lock()) + else: + self.sources_locked[index] = source.lock() + common.show() self.cd(self.location_path, visible=False) diff --git a/gdm/test/test_config.py b/gdm/test/test_config.py index d2332390..d2a63c31 100644 --- a/gdm/test/test_config.py +++ b/gdm/test/test_config.py @@ -2,6 +2,7 @@ # pylint: disable=no-self-use,redefined-outer-name from unittest.mock import patch, Mock +from copy import copy import pytest @@ -55,7 +56,14 @@ def test_repr_no_link(self, source): assert "" == repr(source) - def test_sorting(self): + def test_eq(self, source): + source2 = copy(source) + assert source == source2 + + source2.dir = "dir2" + assert source != source2 + + def test_lt(self): sources = [ Source('zzz', '123'), Source('bbb', '456'), From 72fb4b8df2358a523a101e768dda489adeaecd63 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Dec 2015 21:10:45 -0500 Subject: [PATCH 08/18] Use default Python version on Travis CI --- Makefile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index 314e0200..8ec8e401 100644 --- a/Makefile +++ b/Makefile @@ -4,9 +4,11 @@ PACKAGE := gdm SOURCES := Makefile setup.py $(shell find $(PACKAGE) -name '*.py') EGG_INFO := $(subst -,_,$(PROJECT)).egg-info -# Python settings -PYTHON_MAJOR ?= 3 -PYTHON_MINOR ?= 5 +# Python +ifndef TRAVIS + PYTHON_MAJOR ?= 3 + PYTHON_MINOR ?= 5 +endif # Test settings UNIT_TEST_COVERAGE := 72 From 5246f7fc44426968ed66be8725626afd9f00a5f7 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Dec 2015 21:20:45 -0500 Subject: [PATCH 09/18] Update docs for 0.7.dev1 --- CHANGES.md | 11 +++++++++-- gdm/__init__.py | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 58b73e3c..2bf86c42 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,18 @@ Revision History ================ +0.7 (unreleased) +---------------- + +- Fixed `git remote rm` command (@hdnivara). +- Now applying the `update` dependency filter to locking as well. +- Now only locking previous locked dependencies. + 0.6 (2015/11/13) ---------------- -- Added the ability to filter the dependency list on `install` and `update` -- Added `--depth` option to limit dependency traversal on `install`, `update`, and `list` +- Added the ability to filter the dependency list on `install` and `update`. +- Added `--depth` option to limit dependency traversal on `install`, `update`, and `list`. 0.5 (2015/10/20) ---------------- diff --git a/gdm/__init__.py b/gdm/__init__.py index 50ee4d73..ac226e81 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GDM' -__version__ = '0.6' +__version__ = '0.7.dev1' CLI = 'gdm' PLUGIN = 'deps' From 5270bda39fff0fd4dec53196cf87ee58b3d2a0f4 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 17 Dec 2015 20:09:07 -0500 Subject: [PATCH 10/18] Retry unit tests before failing integration tests --- Makefile | 1 - scent.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 8ec8e401..e7a26df7 100644 --- a/Makefile +++ b/Makefile @@ -219,7 +219,6 @@ FAILED_FLAG := .pytest/failed .PHONY: test test-unit test: test-unit test-unit: depends-ci - @ if test -e $(FAILED_FLAG); then $(MAKE) test-all; fi @ $(COVERAGE) erase $(PYTEST) $(PYTEST_OPTS) $(PACKAGE) ifndef TRAVIS diff --git a/scent.py b/scent.py index 1fe0bae0..a09aa8dc 100644 --- a/scent.py +++ b/scent.py @@ -24,14 +24,14 @@ def py_files(filename): @runnable def python(*_): - for count, (command, title) in enumerate(( - (('make', 'test'), "Unit Tests"), - (('make', 'tests'), "Integration Tests"), - (('make', 'check'), "Static Analysis"), - (('make', 'doc'), None), + for count, (command, title, retry) in enumerate(( + (('make', 'test'), "Unit Tests", True), + (('make', 'tests'), "Integration Tests", False), + (('make', 'check'), "Static Analysis", True), + (('make', 'doc'), None, True), ), start=1): - if not run(command, title, count): + if not run(command, title, count, retry): return False return True @@ -43,7 +43,7 @@ def python(*_): _rerun_args = None -def run(command, title, count): +def run(command, title, count, retry): global _rerun_args if _rerun_args: @@ -66,7 +66,7 @@ def run(command, title, count): show_coverage() - if failure: + if failure and retry: _rerun_args = command, title, count return not failure From a86ad55e7eb8204ebc75bdea37375b745ec52bd2 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 21 Dec 2015 17:39:40 -0500 Subject: [PATCH 11/18] Add 'lock' command --- Makefile | 2 +- gdm/__init__.py | 1 + gdm/cli.py | 12 +++- gdm/commands.py | 45 ++++++++++++++- gdm/config.py | 34 ++++++++---- gdm/test/conftest.py | 13 ++++- gdm/test/test_cli.py | 122 +++++++++++++++++------------------------ requirements.txt | 2 +- scent.py | 2 +- tests/conftest.py | 13 ++--- tests/test_all.py | 49 ----------------- tests/test_api.py | 128 +++++++++++++++++++++++++++++++++++++++++++ tests/test_cli.py | 0 13 files changed, 275 insertions(+), 148 deletions(-) delete mode 100644 tests/test_all.py create mode 100644 tests/test_api.py create mode 100644 tests/test_cli.py diff --git a/Makefile b/Makefile index e7a26df7..72729f55 100644 --- a/Makefile +++ b/Makefile @@ -110,7 +110,7 @@ depends: depends-ci 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-cov pytest-random pytest-runfailed mkdocs + $(PIP) install --upgrade pep8 pep257 pylint coverage pytest pytest-describe pytest-cov pytest-random pytest-runfailed mkdocs @ touch $(DEPENDS_CI_FLAG) # flag to indicate dependencies are installed .PHONY: depends-dev diff --git a/gdm/__init__.py b/gdm/__init__.py index ac226e81..fc9e3ee9 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -20,6 +20,7 @@ from .commands import install from .commands import update from .commands import display as list # pylint: disable=redefined-builtin + from .commands import lock from .commands import delete as uninstall except ImportError: # pragma: no cover (manual test) pass diff --git a/gdm/cli.py b/gdm/cli.py index bb8ac936..536991c6 100644 --- a/gdm/cli.py +++ b/gdm/cli.py @@ -73,6 +73,13 @@ def main(args=None, function=None): dest='allow_dirty', help="fail if a source has uncommitted changes") + # Lock parser + info = "lock the current version of each dependency" + 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") + # Uninstall parser info = "delete all installed dependencies" sub = subs.add_parser('uninstall', description=info.capitalize() + '.', @@ -113,6 +120,9 @@ def _get_command(function, namespace): function = commands.display kwargs.update(dict(depth=namespace.depth, allow_dirty=namespace.allow_dirty)) + elif namespace.command == 'lock': + function = getattr(commands, namespace.command) + args = namespace.name elif namespace.command == 'uninstall': function = commands.delete kwargs.update(force=namespace.force) @@ -124,7 +134,7 @@ def _get_command(function, namespace): def _run_command(function, args, kwargs, exit_msg): success = False try: - log.debug("Running %r command...", function.__name__) + log.debug("Running %r command...", getattr(function, '__name__', 'a')) success = function(*args, **kwargs) except KeyboardInterrupt: log.debug("Command canceled") diff --git a/gdm/commands.py b/gdm/commands.py index a4e79bb0..3ab8e493 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -1,6 +1,7 @@ """Functions to manage the installation of dependencies.""" import os +import functools import logging from . import common @@ -9,6 +10,17 @@ log = logging.getLogger(__name__) +def restore_cwd(func): + @functools.wraps(func) + def wrapped(*args, **kwargs): + cwd = os.getcwd() + result = func(*args, **kwargs) + os.chdir(cwd) + return result + return wrapped + + +@restore_cwd def install(*names, root=None, depth=None, force=False, clean=True): """Install dependencies for a project. @@ -38,8 +50,9 @@ def install(*names, root=None, depth=None, force=False, clean=True): return _display_result("install", "Installed", count) +@restore_cwd def update(*names, root=None, depth=None, - recurse=False, force=False, clean=True, lock=True): + recurse=False, force=False, clean=True, lock=True): # pylint: disable=redefined-outer-name """Update dependencies for a project. Optional arguments: @@ -77,6 +90,7 @@ def update(*names, root=None, depth=None, return _display_result("update", "Updated", count) +@restore_cwd def display(root=None, depth=None, allow_dirty=True): """Display installed dependencies for a project. @@ -101,6 +115,32 @@ def display(root=None, depth=None, allow_dirty=True): return _display_result("display", "Displayed", count) +@restore_cwd +def lock(*names, root=None): + """Lock current dependency versions for a project. + + Optional arguments: + + - `*names`: optional list of dependency directory names to filter on + - `root`: specifies the path to the root working tree + + """ + log.info("Locking dependencies...") + count = None + + root = _find_root(root) + config = load(root) + + if config: + common.show("Locking dependencies...", log=False) + common.show() + count = config.lock_deps(*names, obey_existing=False) + common.dedent(level=0) + + return _display_result("lock", "Locked", count) + + +@restore_cwd def delete(root=None, force=False): """Delete dependencies for a project. @@ -131,6 +171,7 @@ def delete(root=None, force=False): def _find_root(root, cwd=None): if cwd is None: cwd = os.getcwd() + log.info("Current directory: %s", cwd) if root: root = os.path.abspath(root) @@ -141,7 +182,7 @@ def _find_root(root, cwd=None): log.info("Searching for root...") while path != prev: - log.debug("Path: %s", path) + log.debug("Checking path: %s", path) if '.git' in os.listdir(path): root = path break diff --git a/gdm/config.py b/gdm/config.py index b836976d..5757be91 100644 --- a/gdm/config.py +++ b/gdm/config.py @@ -162,7 +162,7 @@ def install_deps(self, *names, depth=None, self.mkdir(self.location_path) self.cd(self.location_path) - sources = self._get_sources(update) + sources = self._get_sources(use_locked=False if update else None) dirs = list(names) if names else [source.dir for source in sources] common.show() common.indent() @@ -171,13 +171,14 @@ def install_deps(self, *names, depth=None, for source in sources: if source.dir in dirs: dirs.remove(source.dir) - count += 1 else: log.info("Skipped dependency: %s", source.dir) continue source.update_files(force=force, clean=clean) source.create_link(self.root, force=force) + count += 1 + common.show() config = load() @@ -201,15 +202,16 @@ def install_deps(self, *names, depth=None, return count - def lock_deps(self, *names): + def lock_deps(self, *names, obey_existing=True): """Lock down the immediate dependency versions.""" self.cd(self.location_path) common.show() common.indent() - sources = self.sources_locked.copy() + sources = self._get_sources(use_locked=obey_existing).copy() dirs = list(names) if names else [source.dir for source in sources] + count = 0 for source in sources: if source.dir not in dirs: log.info("Skipped dependency: %s", source.dir) @@ -221,11 +223,16 @@ def lock_deps(self, *names): self.sources_locked.append(source.lock()) else: self.sources_locked[index] = source.lock() + count += 1 common.show() self.cd(self.location_path, visible=False) + if count: + yorm.update_file(self) + return count + def uninstall_deps(self): """Remove the sources location.""" self.rm(self.location_path) @@ -262,14 +269,21 @@ def get_deps(self, depth=None, allow_dirty=True): common.dedent() - def _get_sources(self, update): - if update: + def _get_sources(self, *, use_locked=None): + if use_locked is True: + if self.sources_locked: + return self.sources_locked + else: + log.info("No locked sources, defaulting to none...") + return [] + elif use_locked is False: return self.sources - elif self.sources_locked: - return self.sources_locked else: - log.info("No locked sources available, installing latest...") - return self.sources + if self.sources_locked: + log.info("Defalting to locked sources...") + else: + log.info("No locked sources, using latest...") + return self.sources def load(root=None): diff --git a/gdm/test/conftest.py b/gdm/test/conftest.py index 0a8d523b..73f9e42d 100644 --- a/gdm/test/conftest.py +++ b/gdm/test/conftest.py @@ -1,7 +1,7 @@ -"""pytest configuration.""" -# pylint:disable=E1101 +"""Unit test configuration.""" import os +import logging import pytest import yorm @@ -15,7 +15,14 @@ def pytest_configure(config): - """Silence verbose test runner output.""" + """Conigure logging and silence verbose test runner output.""" + logging.basicConfig( + level=logging.DEBUG, + format="[%(levelname)-8s] (%(name)s @%(lineno)4d) %(message)s", + ) + logging.getLogger('yorm').setLevel(logging.WARNING) + logging.getLogger('sh').setLevel(logging.WARNING) + terminal = config.pluginmanager.getplugin('terminal') class QuietReporter(terminal.TerminalReporter): diff --git a/gdm/test/test_cli.py b/gdm/test/test_cli.py index f94b8140..023a61be 100644 --- a/gdm/test/test_cli.py +++ b/gdm/test/test_cli.py @@ -16,7 +16,7 @@ class TestMain: def test_main(self): """Verify the top-level command can be run.""" - mock_function = Mock(return_value=True, __name__='pass') + mock_function = Mock(return_value=True) cli.main([], mock_function) @@ -25,7 +25,7 @@ def test_main(self): def test_main_fail(self): """Verify error in commands are detected.""" with pytest.raises(SystemExit): - cli.main([], Mock(return_value=False, __name__='fail')) + cli.main([], Mock(return_value=False)) def test_main_help(self): """Verify the help text can be displayed.""" @@ -40,12 +40,12 @@ def test_main_none(self): def test_main_interrupt(self): """Verify a command can be interrupted.""" with pytest.raises(SystemExit): - cli.main([], Mock(side_effect=KeyboardInterrupt, __name__='exit')) + cli.main([], Mock(side_effect=KeyboardInterrupt)) def test_main_error(self): """Verify runtime errors are handled.""" with pytest.raises(SystemExit): - cli.main([], Mock(side_effect=RuntimeError, __name__='error')) + cli.main([], Mock(side_effect=RuntimeError)) class TestInstall: @@ -55,8 +55,6 @@ class TestInstall: @patch('gdm.commands.install') def test_install(self, mock_install): """Verify the 'install' command can be run.""" - mock_install.__name__ = 'mock' - cli.main(['install']) mock_install.assert_called_once_with( @@ -65,8 +63,6 @@ def test_install(self, mock_install): @patch('gdm.commands.install') def test_install_root(self, mock_install): """Verify the project's root can be specified.""" - mock_install.__name__ = 'mock' - cli.main(['install', '--root', 'mock/path/to/root']) mock_install.assert_called_once_with( @@ -75,8 +71,6 @@ def test_install_root(self, mock_install): @patch('gdm.commands.install') def test_install_force(self, mock_install): """Verify dependencies can be force-installed.""" - mock_install.__name__ = 'mock' - cli.main(['install', '--force']) mock_install.assert_called_once_with( @@ -85,8 +79,6 @@ def test_install_force(self, mock_install): @patch('gdm.commands.install') def test_install_clean(self, mock_install): """Verify dependency cleaning can be enabled.""" - mock_install.__name__ = 'mock' - cli.main(['install', '--clean']) mock_install.assert_called_once_with( @@ -95,8 +87,6 @@ def test_install_clean(self, mock_install): @patch('gdm.commands.install') def test_install_specific_sources(self, mock_install): """Verify individual dependencies can be installed.""" - mock_install.__name__ = 'mock' - cli.main(['install', 'foo', 'bar']) mock_install.assert_called_once_with( @@ -106,18 +96,14 @@ def test_install_specific_sources(self, mock_install): @patch('gdm.commands.install') def test_install_with_depth(self, mock_update): """Verify the 'install' command can be limited by depth.""" - mock_update.__name__ = 'mock' - cli.main(['install', '--depth', '5']) mock_update.assert_called_once_with( root=None, depth=5, force=False, clean=False) - @patch('gdm.commands.install') - def test_install_with_depth_invalid(self, mock_update): + @patch('gdm.commands.install', Mock()) + def test_install_with_depth_invalid(self): """Verify depths below 1 are rejected.""" - mock_update.__name__ = 'mock' - with pytest.raises(SystemExit): cli.main(['install', '--depth', '0']) with pytest.raises(SystemExit): @@ -131,8 +117,6 @@ class TestUpdate: @patch('gdm.commands.update') def test_update(self, mock_update): """Verify the 'update' command can be run.""" - mock_update.__name__ = 'mock' - cli.main(['update']) mock_update.assert_called_once_with( @@ -142,8 +126,6 @@ def test_update(self, mock_update): @patch('gdm.commands.update') def test_update_recursive(self, mock_update): """Verify the 'update' command can be run recursively.""" - mock_update.__name__ = 'mock' - cli.main(['update', '--all']) mock_update.assert_called_once_with( @@ -153,8 +135,6 @@ def test_update_recursive(self, mock_update): @patch('gdm.commands.update') def test_update_no_lock(self, mock_update): """Verify the 'update' command can be run without locking.""" - mock_update.__name__ = 'mock' - cli.main(['update', '--no-lock']) mock_update.assert_called_once_with( @@ -164,8 +144,6 @@ def test_update_no_lock(self, mock_update): @patch('gdm.commands.update') def test_update_specific_sources(self, mock_install): """Verify individual dependencies can be installed.""" - mock_install.__name__ = 'mock' - cli.main(['update', 'foo', 'bar']) mock_install.assert_called_once_with( @@ -175,8 +153,6 @@ def test_update_specific_sources(self, mock_install): @patch('gdm.commands.update') def test_update_with_depth(self, mock_update): """Verify the 'update' command can be limited by depth.""" - mock_update.__name__ = 'mock' - cli.main(['update', '--depth', '5']) mock_update.assert_called_once_with( @@ -184,41 +160,6 @@ def test_update_with_depth(self, mock_update): force=False, clean=False, recurse=False, lock=True) -class TestUninstall: - - """Unit tests for the `uninstall` command.""" - - @patch('gdm.commands.delete') - def test_uninstall(self, mock_uninstall): - """Verify the 'uninstall' command can be run.""" - mock_uninstall.__name__ = 'mock' - - cli.main(['uninstall']) - - mock_uninstall.assert_called_once_with( - root=None, force=False) - - @patch('gdm.commands.delete') - def test_uninstall_root(self, mock_uninstall): - """Verify the project's root can be specified.""" - mock_uninstall.__name__ = 'mock' - - cli.main(['uninstall', '--root', 'mock/path/to/root']) - - mock_uninstall.assert_called_once_with( - root='mock/path/to/root', force=False) - - @patch('gdm.commands.delete') - def test_uninstall_force(self, mock_uninstall): - """Verify the 'uninstall' command can be forced.""" - mock_uninstall.__name__ = 'mock' - - cli.main(['uninstall', '--force']) - - mock_uninstall.assert_called_once_with( - root=None, force=True) - - class TestList: """Unit tests for the `list` command.""" @@ -226,8 +167,6 @@ class TestList: @patch('gdm.commands.display') def test_list(self, mock_display): """Verify the 'list' command can be run.""" - mock_display.__name__ = 'mock' - cli.main(['list']) mock_display.assert_called_once_with( @@ -236,8 +175,6 @@ def test_list(self, mock_display): @patch('gdm.commands.display') def test_list_root(self, mock_display): """Verify the project's root can be specified.""" - mock_display.__name__ = 'mock' - cli.main(['list', '--root', 'mock/path/to/root']) mock_display.assert_called_once_with( @@ -246,8 +183,6 @@ def test_list_root(self, mock_display): @patch('gdm.commands.display') def test_list_no_dirty(self, mock_display): """Verify the 'list' command can be set to fail when dirty.""" - mock_display.__name__ = 'mock' - cli.main(['list', '--no-dirty']) mock_display.assert_called_once_with( @@ -256,14 +191,55 @@ def test_list_no_dirty(self, mock_display): @patch('gdm.commands.display') def test_update_with_depth(self, mock_update): """Verify the 'list' command can be limited by depth.""" - mock_update.__name__ = 'mock' - cli.main(['list', '--depth', '5']) mock_update.assert_called_once_with( root=None, depth=5, allow_dirty=True) +def describe_lock(): + # pylint: disable=unused-variable + + @patch('gdm.commands.lock') + def with_no_arguments(lock): + cli.main(['lock']) + lock.assert_called_once_with(root=None) + + @patch('gdm.commands.lock') + def with_dependencies(lock): + cli.main(['lock', 'foo', 'bar']) + lock.assert_called_once_with('foo', 'bar', root=None) + + +class TestUninstall: + + """Unit tests for the `uninstall` command.""" + + @patch('gdm.commands.delete') + def test_uninstall(self, mock_uninstall): + """Verify the 'uninstall' command can be run.""" + cli.main(['uninstall']) + + mock_uninstall.assert_called_once_with( + root=None, force=False) + + @patch('gdm.commands.delete') + def test_uninstall_root(self, mock_uninstall): + """Verify the project's root can be specified.""" + cli.main(['uninstall', '--root', 'mock/path/to/root']) + + mock_uninstall.assert_called_once_with( + root='mock/path/to/root', force=False) + + @patch('gdm.commands.delete') + def test_uninstall_force(self, mock_uninstall): + """Verify the 'uninstall' command can be forced.""" + cli.main(['uninstall', '--force']) + + mock_uninstall.assert_called_once_with( + root=None, force=True) + + class TestLogging: """Unit tests for logging.""" diff --git a/requirements.txt b/requirements.txt index 0f28b468..57bfdb6b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -YORM ~= 0.5 +YORM ~= 0.6.dev1 sh ~= 1.11 diff --git a/scent.py b/scent.py index a09aa8dc..85ac09e4 100644 --- a/scent.py +++ b/scent.py @@ -67,7 +67,7 @@ def run(command, title, count, retry): show_coverage() if failure and retry: - _rerun_args = command, title, count + _rerun_args = command, title, count, retry return not failure diff --git a/tests/conftest.py b/tests/conftest.py index e078d4c2..bdc801c9 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,19 +1,18 @@ -"""pytest configuration.""" +"""Integration tests configuration.""" +# pylint: disable=unused-argument import os import yorm -from gdm.test.conftest import pytest_configure # pylint:disable=unused-import +from gdm.test.conftest import pytest_configure # pylint: disable=unused-import +# TODO: delete if unused (and files) ROOT = os.path.dirname(__file__) FILES = os.path.join(ROOT, 'files') def pytest_runtest_setup(item): - """pytest setup.""" - if 'integration' in item.keywords: - yorm.settings.fake = False - else: - yorm.settings.fake = True + """Ensure files are created for integration tests.""" + yorm.settings.fake = False diff --git a/tests/test_all.py b/tests/test_all.py deleted file mode 100644 index e1468db1..00000000 --- a/tests/test_all.py +++ /dev/null @@ -1,49 +0,0 @@ -"""Integration tests for the `gdm` package.""" -# pylint: disable=no-self-use - -import os -import shutil - -import pytest - -import gdm -from gdm.config import Config - -from .conftest import FILES, ROOT - - -@pytest.mark.integration -class TestCommands: - - def setup_method(self, _): - os.chdir(ROOT) - - def teardown_method(self, _): - os.chdir(ROOT) - - def test_commands(self): - config = Config(FILES) - shutil.rmtree(config.location, ignore_errors=True) - assert not os.path.exists(config.location) - - # install sources - assert gdm.install(root=FILES) - assert 'gdm_1' in os.listdir(config.location) - assert 'gdm_2' in os.listdir(config.location) - - # list versions - assert gdm.list(FILES) - - # update sources - assert gdm.update(root=FILES) - assert 'gdm_1' in os.listdir(config.location) - assert 'gdm_2' in os.listdir(config.location) - - # install locked sources - assert gdm.install(root=FILES) - assert 'gdm_1' in os.listdir(config.location) - assert 'gdm_2' in os.listdir(config.location) - - # uninstall sources - assert gdm.uninstall(root=FILES) - assert not os.path.isdir(config.location) diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..e825cfd7 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,128 @@ +"""Integration tests for the `gdm` package.""" +# pylint: disable=no-self-use,redefined-outer-name,unused-variable + +import os +import shutil +from contextlib import suppress +import logging + +import pytest +from yorm.test import strip + +import gdm +from gdm.config import Config + + +CONFIG = """ +location: deps +sources: +- dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch +- dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-tag +- dir: gdm_3 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 9bf18e16b956041f0267c21baad555a23237b52e +""".lstrip() + +log = logging.getLogger(__name__) + + +@pytest.fixture +def config(root="/tmp/gdm-shared"): + with suppress(FileNotFoundError): + shutil.rmtree(root) + with suppress(FileExistsError): + os.makedirs(root) + os.chdir(root) + log.info("Temporary directory: %s", root) + + os.system("touch .git") + config = Config(root=root) + config.__mapper__.text = CONFIG # pylint: disable=no-member + + log.debug("File listing: %s", os.listdir(root)) + + return config + + +def describe_install(): + + def it_should_create_missing_directories(config): + assert not os.path.isdir(config.location) + + assert gdm.install('gdm_1', depth=1) + assert 'gdm_1' in os.listdir(config.location) + + def it_should_not_modify_config(config): + assert gdm.install('gdm_1', depth=1) + + assert CONFIG == config.__mapper__.text + + +def describe_uninstall(): + + def it_should_delete_dependencies_when_they_exist(config): + gdm.install('gdm_1', depth=1) + assert os.path.isdir(config.location) + + assert gdm.uninstall() + + assert not os.path.isdir(config.location) + + def it_should_not_fail_when_no_dependnecies_exist(config): + assert not os.path.isdir(config.location) + + assert gdm.uninstall() + + +def describe_update(): + + def it_should_not_modify_config(config): + gdm.update('gdm_1', depth=1) + + assert CONFIG == config.__mapper__.text + + +def describe_lock(): + + def it_should_record_all_versions_when_no_arguments(config): + assert gdm.update(depth=1, lock=False) + assert gdm.lock() + + assert CONFIG + strip(""" + sources_locked: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: eb37743011a398b208dd9f9ef79a408c0fc10d48 + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 + - dir: gdm_3 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 9bf18e16b956041f0267c21baad555a23237b52e + """) == config.__mapper__.text + + def it_should_record_specified_dependencies(config): + assert gdm.update(depth=1, lock=False) + assert gdm.lock('gdm_1', 'gdm_3') + + assert CONFIG + strip(""" + sources_locked: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: eb37743011a398b208dd9f9ef79a408c0fc10d48 + - dir: gdm_3 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 9bf18e16b956041f0267c21baad555a23237b52e + """) == config.__mapper__.text diff --git a/tests/test_cli.py b/tests/test_cli.py new file mode 100644 index 00000000..e69de29b From b731b9c1a120ed246b2a49a81afbdc7a4a9aa8c8 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 21 Dec 2015 17:58:47 -0500 Subject: [PATCH 12/18] Drop Python 3.3 support --- .travis.yml | 1 - README.md | 2 +- docs/index.md | 4 ++-- setup.py | 1 - 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 93f54e4e..dd36f8ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ sudo: false language: python python: -- 3.3 - 3.4 - 3.5 diff --git a/README.md b/README.md index 6d65200f..fc298537 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ Git Dependency Manager (GDM) is a language-agnostic "dependency manager" using G Requirements ------------ -* Python 3.3+ +* Python 3.4+ * Latest version of Git (with [stored credentials](http://stackoverflow.com/questions/7773181)) * OSX/Linux (with a decent shell for Git) diff --git a/docs/index.md b/docs/index.md index a33ef5f0..e26e559b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,10 +1,10 @@ -# Git Dependency Manager +# Git Dependency Manager Git Dependency Manager (GDM) is a language-agnostic "dependency manager" using Git. It aims to serve as a submodules replacement and provides advanced options for managing versions of nested Git repositories. ## Requirements -* Python 3.3+ +* Python 3.4+ * Latest version of Git (with [stored credentials](http://stackoverflow.com/questions/7773181)) * OSX/Linux (with a decent shell for Git) diff --git a/setup.py b/setup.py index 012f02fd..5ab7e86d 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,6 @@ 'Operating System :: POSIX', 'Programming Language :: Python', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Topic :: Software Development', From 165b1c07e33b76bea97b80b8cc6219393bfb7604 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 21 Dec 2015 21:57:54 -0500 Subject: [PATCH 13/18] Add test for defaulting to locked sources on install --- gdm/config.py | 1 + tests/test_api.py | 24 ++++++++++++++++++++++-- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/gdm/config.py b/gdm/config.py index 5757be91..a1e4f0dd 100644 --- a/gdm/config.py +++ b/gdm/config.py @@ -281,6 +281,7 @@ def _get_sources(self, *, use_locked=None): else: if self.sources_locked: log.info("Defalting to locked sources...") + return self.sources_locked else: log.info("No locked sources, using latest...") return self.sources diff --git a/tests/test_api.py b/tests/test_api.py index e825cfd7..ed8d2284 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -57,13 +57,33 @@ def it_should_create_missing_directories(config): assert not os.path.isdir(config.location) assert gdm.install('gdm_1', depth=1) - assert 'gdm_1' in os.listdir(config.location) + + assert ['gdm_1'] == os.listdir(config.location) def it_should_not_modify_config(config): assert gdm.install('gdm_1', depth=1) assert CONFIG == config.__mapper__.text + def it_should_use_locked_sources_if_available(config): + config.__mapper__.text = strip(""" + location: deps + sources: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch + sources_locked: + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 + """) + + assert gdm.install(depth=1) + + assert ['gdm_2'] == os.listdir(config.location) + def describe_uninstall(): @@ -73,7 +93,7 @@ def it_should_delete_dependencies_when_they_exist(config): assert gdm.uninstall() - assert not os.path.isdir(config.location) + assert not os.path.exists(config.location) def it_should_not_fail_when_no_dependnecies_exist(config): assert not os.path.isdir(config.location) From 3d77b9c17ed746a3ca68ac74ca06f6e6cc89b10f Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 21 Dec 2015 22:08:40 -0500 Subject: [PATCH 14/18] Update docs for 0.7.dev2 --- CHANGES.md | 1 + README.md | 4 ++-- docs/index.md | 4 ++-- docs/interfaces/api.md | 13 +++++++++++++ docs/interfaces/cli.md | 13 +++++++++++++ gdm/__init__.py | 2 +- 6 files changed, 32 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2bf86c42..c8aa7d65 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,6 +7,7 @@ Revision History - Fixed `git remote rm` command (@hdnivara). - Now applying the `update` dependency filter to locking as well. - Now only locking previous locked dependencies. +- Added `lock` command to manually save all dependency versions. 0.6 (2015/11/13) ---------------- diff --git a/README.md b/README.md index fc298537..816b79aa 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Installation GDM can be installed with pip: ```sh -$ pip3 install gdm +$ pip install gdm ``` or directly from the source code: @@ -30,7 +30,7 @@ or directly from the source code: ```sh $ git clone https://github.com/jacebrowning/gdm.git $ cd gdm -$ python3 setup.py install +$ python setup.py install ``` Setup diff --git a/docs/index.md b/docs/index.md index e26e559b..332029c2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -13,7 +13,7 @@ Git Dependency Manager (GDM) is a language-agnostic "dependency manager" using G GDM can be installed with pip: ```sh -$ pip3 install gdm +$ pip install gdm ``` or directly from the source code: @@ -21,7 +21,7 @@ or directly from the source code: ```sh $ git clone https://github.com/jacebrowning/gdm.git $ cd gdm -$ python3 setup.py install +$ python setup.py install ``` ## Setup diff --git a/docs/interfaces/api.md b/docs/interfaces/api.md index cadc1f51..8c3bce53 100644 --- a/docs/interfaces/api.md +++ b/docs/interfaces/api.md @@ -50,6 +50,19 @@ with optional arguments: - `depth`: number of levels of dependencies to traverse - `allow_dirty`: causes uncommitted changes to be ignored +## Lock + +To record the exact versions of currently checked out dependencies, call: + +```python +gdm.lock(*names, root=None) +``` + +with optional arguments: + +- `*names`: optional list of dependency directory names to filter on +- `root`: specifies the path to the root working tree + ## Uninstall To delete all source dependencies, call: diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index 9769a72e..b2b2aa8e 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -86,6 +86,19 @@ or exit with an error if there are any uncommitted changes: gdm list --no-dirty ``` +## Lock + +To manually record the exact version of each dependency, run: + +```sh +gdm lock +``` +or lock down specific dependencies: + +```sh +gdm lock +``` + ## Uninstall To delete all source dependencies, run: diff --git a/gdm/__init__.py b/gdm/__init__.py index fc9e3ee9..28a9f865 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GDM' -__version__ = '0.7.dev1' +__version__ = '0.7.dev2' CLI = 'gdm' PLUGIN = 'deps' From daa848cb5dad499c518f7d8d9ccffe192c3b0af7 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Tue, 22 Dec 2015 16:26:56 -0500 Subject: [PATCH 15/18] Show info on skipped tests and warnings --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 72729f55..3b3d5085 100644 --- a/Makefile +++ b/Makefile @@ -207,7 +207,7 @@ fix: depends-dev RANDOM_SEED ?= $(shell date +%s) -PYTEST_CORE_OPTS := --verbose -r X --maxfail=3 +PYTEST_CORE_OPTS := --verbose -r xXw --maxfail=3 PYTEST_COV_OPTS := --cov=$(PACKAGE) --cov-report=term-missing --no-cov-on-fail PYTEST_RANDOM_OPTS := --random --random-seed=$(RANDOM_SEED) From cacbc794447e316903982880e1403bf1e4610c7e Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Tue, 22 Dec 2015 17:20:12 -0500 Subject: [PATCH 16/18] Make locking explicit Closes #78 --- gdm/cli.py | 10 +++-- gdm/commands.py | 6 +-- gdm/test/test_cli.py | 24 ++++++++--- tests/test_api.py | 97 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 126 insertions(+), 11 deletions(-) diff --git a/gdm/cli.py b/gdm/cli.py index 536991c6..25c21541 100644 --- a/gdm/cli.py +++ b/gdm/cli.py @@ -61,9 +61,13 @@ def main(args=None, function=None): help="list of dependencies (`dir` values) to update") sub.add_argument('-a', '--all', action='store_true', dest='recurse', help="update all nested dependencies, recursively") - sub.add_argument('-L', '--no-lock', - action='store_false', dest='lock', default=True, - help="skip recording of versions for later reinstall") + group = sub.add_mutually_exclusive_group() + group.add_argument('-l', '--lock', + action='store_true', dest='lock', default=None, + help="enable recording of versions for later reinstall") + group.add_argument('-L', '--no-lock', + action='store_false', dest='lock', default=None, + help="disable recording of versions for later reinstall") # Display parser info = "display the current version of each dependency" diff --git a/gdm/commands.py b/gdm/commands.py index 3ab8e493..d0a22c23 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -52,7 +52,7 @@ def install(*names, root=None, depth=None, force=False, clean=True): @restore_cwd def update(*names, root=None, depth=None, - recurse=False, force=False, clean=True, lock=True): # pylint: disable=redefined-outer-name + recurse=False, force=False, clean=True, lock=None): # pylint: disable=redefined-outer-name """Update dependencies for a project. Optional arguments: @@ -82,10 +82,10 @@ def update(*names, root=None, depth=None, *names, update=True, depth=depth, recurse=recurse, force=force, clean=clean) common.dedent(level=0) - if count and lock: + if count and lock is not False: common.show("Recording installed versions...", log=False) common.show() - config.lock_deps(*names) + config.lock_deps(*names, obey_existing=lock is None) return _display_result("update", "Updated", count) diff --git a/gdm/test/test_cli.py b/gdm/test/test_cli.py index 023a61be..3adeb4fe 100644 --- a/gdm/test/test_cli.py +++ b/gdm/test/test_cli.py @@ -121,7 +121,7 @@ def test_update(self, mock_update): mock_update.assert_called_once_with( root=None, depth=None, - force=False, clean=False, recurse=False, lock=True) + force=False, clean=False, recurse=False, lock=None) @patch('gdm.commands.update') def test_update_recursive(self, mock_update): @@ -130,17 +130,31 @@ def test_update_recursive(self, mock_update): mock_update.assert_called_once_with( root=None, depth=None, - force=False, clean=False, recurse=True, lock=True) + force=False, clean=False, recurse=True, lock=None) @patch('gdm.commands.update') def test_update_no_lock(self, mock_update): - """Verify the 'update' command can be run without locking.""" + """Verify the 'update' command can disable locking.""" cli.main(['update', '--no-lock']) mock_update.assert_called_once_with( root=None, depth=None, force=False, clean=False, recurse=False, lock=False) + @patch('gdm.commands.update') + def test_update_lock(self, mock_update): + """Verify the 'update' command can enable locking.""" + cli.main(['update', '--lock']) + + mock_update.assert_called_once_with( + root=None, depth=None, + force=False, clean=False, recurse=False, lock=True) + + def test_update_lock_conflict(self): + """Verify the 'update' command cannot specify both locking options.""" + with pytest.raises(SystemExit): + cli.main(['update', '--lock', '--no-lock']) + @patch('gdm.commands.update') def test_update_specific_sources(self, mock_install): """Verify individual dependencies can be installed.""" @@ -148,7 +162,7 @@ def test_update_specific_sources(self, mock_install): mock_install.assert_called_once_with( 'foo', 'bar', root=None, depth=None, - force=False, clean=False, recurse=False, lock=True) + force=False, clean=False, recurse=False, lock=None) @patch('gdm.commands.update') def test_update_with_depth(self, mock_update): @@ -157,7 +171,7 @@ def test_update_with_depth(self, mock_update): mock_update.assert_called_once_with( root=None, depth=5, - force=False, clean=False, recurse=False, lock=True) + force=False, clean=False, recurse=False, lock=None) class TestList: diff --git a/tests/test_api.py b/tests/test_api.py index ed8d2284..eb224858 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -108,6 +108,103 @@ def it_should_not_modify_config(config): assert CONFIG == config.__mapper__.text + def it_should_lock_previously_locked_dependnecies(config): + config.__mapper__.text = strip(""" + location: deps + sources: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-tag + sources_locked: + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: (old revision) + """) + + gdm.update(depth=1) + + assert strip(""" + location: deps + sources: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-tag + sources_locked: + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 + """) == config.__mapper__.text + + def it_should_not_lock_dependnecies_when_disabled(config): + config.__mapper__.text = strip(""" + location: deps + sources: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-tag + sources_locked: + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: (old revision) + """) + + gdm.update(depth=1, lock=False) + + assert strip(""" + location: deps + sources: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-branch + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: example-tag + sources_locked: + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: (old revision) + """) == config.__mapper__.text + + def it_should_lock_all_when_enabled(config): + gdm.update(depth=1, lock=True) + + assert CONFIG + strip(""" + sources_locked: + - dir: gdm_1 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: eb37743011a398b208dd9f9ef79a408c0fc10d48 + - dir: gdm_2 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 + - dir: gdm_3 + link: '' + repo: https://github.com/jacebrowning/gdm-demo + rev: 9bf18e16b956041f0267c21baad555a23237b52e + """) == config.__mapper__.text + def describe_lock(): From a06ce577fa8a5a3120ec518a05118440d5d2bc04 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Tue, 22 Dec 2015 17:30:53 -0500 Subject: [PATCH 17/18] Update docs for 0.7.dev3 --- CHANGES.md | 1 + docs/interfaces/api.md | 2 +- docs/interfaces/cli.md | 21 ++++++++++++++------- gdm/__init__.py | 2 +- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c8aa7d65..bd512f1c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Revision History - Now applying the `update` dependency filter to locking as well. - Now only locking previous locked dependencies. - Added `lock` command to manually save all dependency versions. +- Now requiring `--lock` option on `update` to explicitly lock dependencies. 0.6 (2015/11/13) ---------------- diff --git a/docs/interfaces/api.md b/docs/interfaces/api.md index 8c3bce53..815de7b6 100644 --- a/docs/interfaces/api.md +++ b/docs/interfaces/api.md @@ -23,7 +23,7 @@ with optional arguments: If any of the dependencies track a branch (rather than a specific commit), the current upstream version of that branch can be checked out by calling: ```python -gdm.update(*names, root=None, depth=None, recurse=False, force=False, clean=True, lock=True) +gdm.update(*names, root=None, depth=None, recurse=False, force=False, clean=True, lock=None) ``` with optional arguments: diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index b2b2aa8e..0b68a22b 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -54,7 +54,7 @@ or limit the traversal of nested dependencies: gdm update --depth= ``` -This will also record the exact versions that were checked out. Disable this behavior by instead running: +This will also record the exact versions of any previously locked dependencies. Disable this behavior by instead running: ```sh gdm update --no-lock @@ -66,12 +66,6 @@ or to additionally get the latest versions of all nested dependencies, run: gdm update --all ``` -To restore the exact versions previously checked out, run: - -```sh -gdm install -``` - ## List To display the currently checked out dependencies, run: @@ -93,12 +87,25 @@ To manually record the exact version of each dependency, run: ```sh gdm lock ``` + or lock down specific dependencies: ```sh gdm lock ``` +This can be combined with updating dependencies by running: + +```sh +gdm update --lock +``` + +To restore the exact versions previously checked out, run: + +```sh +gdm install +``` + ## Uninstall To delete all source dependencies, run: diff --git a/gdm/__init__.py b/gdm/__init__.py index 28a9f865..965aefa4 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GDM' -__version__ = '0.7.dev2' +__version__ = '0.7.dev3' CLI = 'gdm' PLUGIN = 'deps' From 6bf1c9272bfbf48f5852bca8e9a4a09be4f4fe1a Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Tue, 22 Dec 2015 17:43:34 -0500 Subject: [PATCH 18/18] Bump version to 0.7 --- CHANGES.md | 2 +- CONTRIBUTING.md | 12 ++++++------ gdm/__init__.py | 2 +- setup.py | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index bd512f1c..32f1ebae 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Revision History ================ -0.7 (unreleased) +0.7 (2015/12/22) ---------------- - Fixed `git remote rm` command (@hdnivara). diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9fc55148..6584422a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -16,7 +16,7 @@ Create a virtualenv: -``` +```sh $ make env ``` @@ -26,14 +26,14 @@ $ make env Manually run the tests: -``` +```sh $ make test $ make tests # includes integration tests ``` or keep them running on change: -``` +```sh $ make watch ``` @@ -43,7 +43,7 @@ $ make watch Build the documentation: -``` +```sh $ make doc ``` @@ -51,7 +51,7 @@ $ make doc Run linters and static analyzers: -``` +```sh $ make pep8 $ make pep257 $ make pylint @@ -62,7 +62,7 @@ $ make check # includes all checks Release to PyPI: -``` +```sh $ make upload-test # dry run upload to a test server $ make upload ``` diff --git a/gdm/__init__.py b/gdm/__init__.py index 965aefa4..155e53b9 100644 --- a/gdm/__init__.py +++ b/gdm/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GDM' -__version__ = '0.7.dev3' +__version__ = '0.7' CLI = 'gdm' PLUGIN = 'deps' diff --git a/setup.py b/setup.py index 5ab7e86d..415365fc 100644 --- a/setup.py +++ b/setup.py @@ -19,7 +19,7 @@ version=__version__, description=DESCRIPTION, - url='https://github.com/jacebrowning/gdm', + url='http://git-dependency-manager.info', author='Jace Browning', author_email='jacebrowning@gmail.com',