From fa998167f5f6de64bc8bdfd8b9433870d79ef814 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 21 Jan 2016 21:31:03 -0500 Subject: [PATCH 01/20] Fix erroneous release date --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 7aa68777..0a44f413 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,7 +6,7 @@ Revision History - Added an error message when attempting to lock invalid repositories. -0.8 (2015/10/13) +0.8 (2016/01/13) ---------------- - Switched to using repository mirrors to speed up cloning. From d315a06f03cd8e0dc51132751b7bc592a477270f Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Fri, 29 Jan 2016 21:02:46 -0500 Subject: [PATCH 02/20] Fix URL in auth token example --- docs/setup/git.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/setup/git.md b/docs/setup/git.md index 9f69cafc..718b508f 100644 --- a/docs/setup/git.md +++ b/docs/setup/git.md @@ -45,5 +45,5 @@ $ git clone git://github.com//.git Finally, the repository URL itself can contain an OAuth token (for [GitHub](https://github.com/blog/1270-easier-builds-and-deployments-using-git-over-https-and-oauth)): ```shell -$ git clone git://@github.com//.git +$ git clone https://@github.com//.git ``` From d2fdfb3255999d8691a4122ccfceb76ac74e1d6d Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Sat, 30 Jan 2016 23:20:24 -0500 Subject: [PATCH 03/20] Remove unused file --- docs/CHANGES.md | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/CHANGES.md diff --git a/docs/CHANGES.md b/docs/CHANGES.md deleted file mode 100644 index e69de29b..00000000 From 6c344ec470abda7c9fdcf642f53cdce87f453a27 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Fri, 12 Feb 2016 21:22:22 -0500 Subject: [PATCH 04/20] Update to support YORM 0.6.dev3 --- .project | 2 +- gdm/config.py | 4 ++-- gdm/source.py | 10 +++++----- requirements.txt | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.project b/.project index 24e1630c..16033967 100644 --- a/.project +++ b/.project @@ -3,7 +3,7 @@ GDM - YORM + GDM diff --git a/gdm/config.py b/gdm/config.py index 61990d7a..05d92af4 100644 --- a/gdm/config.py +++ b/gdm/config.py @@ -13,11 +13,11 @@ @yorm.attr(all=Source) -class Sources(yorm.converters.SortedList): +class Sources(yorm.types.SortedList): """A list of source dependencies.""" -@yorm.attr(location=yorm.converters.String) +@yorm.attr(location=yorm.types.String) @yorm.attr(sources=Sources) @yorm.attr(sources_locked=Sources) @yorm.sync("{self.root}/{self.filename}") diff --git a/gdm/source.py b/gdm/source.py index 501d95bc..05a97dfa 100644 --- a/gdm/source.py +++ b/gdm/source.py @@ -14,11 +14,11 @@ log = logging.getLogger(__name__) -@yorm.attr(repo=yorm.converters.String) -@yorm.attr(dir=yorm.converters.String) -@yorm.attr(rev=yorm.converters.String) -@yorm.attr(link=yorm.converters.String) -class Source(yorm.converters.AttributeDictionary): +@yorm.attr(repo=yorm.types.String) +@yorm.attr(dir=yorm.types.String) +@yorm.attr(rev=yorm.types.String) +@yorm.attr(link=yorm.types.String) +class Source(yorm.types.AttributeDictionary): """A dictionary of `git` and `ln` arguments.""" DIRTY = '' diff --git a/requirements.txt b/requirements.txt index 57bfdb6b..b01ff2a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -YORM ~= 0.6.dev1 +YORM == 0.6.dev3 sh ~= 1.11 From 7a52e767e80e11a66a992477b4084ed3f3d07b97 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Fri, 26 Feb 2016 22:55:12 -0500 Subject: [PATCH 05/20] Add 'edit' command --- CHANGES.md | 5 +++++ Makefile | 2 +- gdm/__main__.py | 7 +++++++ gdm/cli.py | 7 +++++++ gdm/commands.py | 22 +++++++++++++++++++++- gdm/system.py | 37 +++++++++++++++++++++++++++++++++++++ scent.py | 2 +- tests/test_cli.py | 32 ++++++++++++++++++++++++++++++++ tests/test_main.py | 13 +++++++++++++ 9 files changed, 124 insertions(+), 3 deletions(-) create mode 100644 gdm/__main__.py create mode 100644 gdm/system.py create mode 100644 tests/test_main.py diff --git a/CHANGES.md b/CHANGES.md index 71232430..e50181cf 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ Revision History ================ +0.9 (unreleased) +---------------- + +- Added `edit` command to launch the configuration file. + 0.8.2 (2016/02/24) ------------------ diff --git a/Makefile b/Makefile index 4bedbf68..045f55e6 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-describe pytest-cov pytest-random pytest-runfailed mkdocs + $(PIP) install --upgrade pep8 pep257 pylint coverage pytest pytest-expecter 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/__main__.py b/gdm/__main__.py new file mode 100644 index 00000000..7c8438a6 --- /dev/null +++ b/gdm/__main__.py @@ -0,0 +1,7 @@ +"""Package entry point.""" + +from .cli import main + + +if __name__ == '__main__': + main() diff --git a/gdm/cli.py b/gdm/cli.py index 1f6efbf9..83287677 100644 --- a/gdm/cli.py +++ b/gdm/cli.py @@ -93,6 +93,11 @@ def main(args=None, function=None): sub.add_argument('-f', '--force', action='store_true', help="delete uncommitted changes in dependencies") + # Edit parser + info = "open the configuration file in the default editor" + sub = subs.add_parser('edit', description=info.capitalize() + '.', + help=info, parents=[debug, project], **shared) + # Parse arguments namespace = parser.parse_args(args=args) @@ -135,6 +140,8 @@ 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 == 'edit': + function = commands.edit return function, args, kwargs, exit_msg diff --git a/gdm/commands.py b/gdm/commands.py index bd9009d8..d494b5f4 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -4,7 +4,7 @@ import functools import logging -from . import common +from . import common, system from .config import load log = logging.getLogger(__name__) @@ -170,6 +170,26 @@ def delete(root=None, force=False): return _display_result("delete", "Deleted", count, allow_zero=True) +@restore_cwd +def edit(root=None): + """Open the confuration file for a project. + + Optional arguments: + + - `root`: specifies the path to the root working tree + + """ + log.info("Launching configuration...") + + root = _find_root(root) + config = load(root) + + if config: + return system.launch(config.path) + else: + return False + + def _find_root(root, cwd=None): if cwd is None: cwd = os.getcwd() diff --git a/gdm/system.py b/gdm/system.py new file mode 100644 index 00000000..31e28dec --- /dev/null +++ b/gdm/system.py @@ -0,0 +1,37 @@ +"""Interface to the operating system.""" + +import os +import platform +import subprocess +import logging + +log = logging.getLogger(__name__) + + +def launch(path): # pragma: no cover (manual test) + """Open a file with its default program.""" + name = platform.system() + log.info("Opening %s", path) + try: + function = { + 'Windows': _launch_windows, + 'Darwin': _launch_mac, + 'Linux': _launch_linux, + }[name] + except KeyError: + raise AssertionError("Unknown OS: {}".format(name)) + else: + return function(path) + + +def _launch_windows(path): # pragma: no cover (manual test) + os.startfile(path) # pylint: disable=no-member + return True + + +def _launch_mac(path): # pragma: no cover (manual test) + return subprocess.call(['open', path]) == 0 + + +def _launch_linux(path): # pragma: no cover (manual test) + return subprocess.call(['xdg-open', path]) == 0 diff --git a/scent.py b/scent.py index 85ac09e4..8690886c 100644 --- a/scent.py +++ b/scent.py @@ -39,7 +39,7 @@ def python(*_): GROUP = int(time.time()) # unique per run -_show_coverage = True +_show_coverage = False _rerun_args = None diff --git a/tests/test_cli.py b/tests/test_cli.py index e69de29b..90958852 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -0,0 +1,32 @@ +# pylint: disable=unused-variable,expression-not-assigned + +from unittest.mock import patch, call + +import pytest +from expecter import expect + +from gdm import cli + + +def describe_gdm(): + + @pytest.fixture + def config(tmpdir): + tmpdir.chdir() + path = str(tmpdir.join("gdm.yml")) + open(path, 'w').close() + return path + + def describe_edit(): + + @patch('gdm.system.launch') + def it_launches_the_config(launch, config): + cli.main(['edit']) + + expect(launch.mock_calls) == [call(config), call().__bool__()] + + def it_exits_when_no_config_found(tmpdir): + tmpdir.chdir() + + with expect.raises(SystemExit): + cli.main(['edit']) diff --git a/tests/test_main.py b/tests/test_main.py new file mode 100644 index 00000000..a0d440af --- /dev/null +++ b/tests/test_main.py @@ -0,0 +1,13 @@ +# pylint: disable=unused-variable,expression-not-assigned + +import sys +import subprocess + +from expecter import expect + + +def describe_main(): + + def it_displays_version(): + code = subprocess.call([sys.executable, "-m", "gdm", "--version"]) + expect(code) == 0 From 2c96cefb226aceaeda94e3aec9872cc402326eee Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Fri, 26 Feb 2016 22:58:51 -0500 Subject: [PATCH 06/20] Update documentation --- docs/interfaces/cli.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index d211a442..9104d7ac 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -125,3 +125,11 @@ If any dependencies contain uncommitted changes, instead run: ```sh gdm uninstall --force ``` + +## Edit + +To open the existing configuration file: + +```sh +gdm edit +``` From e6cfa4502403d0f03b03c48dabe0694757c8b43b Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Sat, 27 Feb 2016 19:31:13 -0500 Subject: [PATCH 07/20] Add tests for platform detection --- gdm/commands.py | 1 + gdm/system.py | 4 ++-- gdm/test/test_system.py | 21 +++++++++++++++++++++ 3 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 gdm/test/test_system.py diff --git a/gdm/commands.py b/gdm/commands.py index d494b5f4..5a8ea492 100644 --- a/gdm/commands.py +++ b/gdm/commands.py @@ -187,6 +187,7 @@ def edit(root=None): if config: return system.launch(config.path) else: + log.error("No configuration found") return False diff --git a/gdm/system.py b/gdm/system.py index 31e28dec..1821dfc2 100644 --- a/gdm/system.py +++ b/gdm/system.py @@ -8,7 +8,7 @@ log = logging.getLogger(__name__) -def launch(path): # pragma: no cover (manual test) +def launch(path): """Open a file with its default program.""" name = platform.system() log.info("Opening %s", path) @@ -19,7 +19,7 @@ def launch(path): # pragma: no cover (manual test) 'Linux': _launch_linux, }[name] except KeyError: - raise AssertionError("Unknown OS: {}".format(name)) + raise RuntimeError("Unrecognized platform: {}".format(name)) from None else: return function(path) diff --git a/gdm/test/test_system.py b/gdm/test/test_system.py new file mode 100644 index 00000000..70aba835 --- /dev/null +++ b/gdm/test/test_system.py @@ -0,0 +1,21 @@ +# pylint: disable=unused-variable,expression-not-assigned + +from unittest.mock import patch, call, Mock + +from expecter import expect + +from gdm import system + + +def describe_launch(): + + @patch('platform.system', Mock(return_value="Windows")) + @patch('gdm.system._launch_windows') + def it_opens_files(startfile): + system.launch("fake/path") + expect(startfile.mock_calls) == [call("fake/path")] + + @patch('platform.system', Mock(return_value="fake")) + def it_raises_an_exception_when_platform_is_unknown(): + with expect.raises(RuntimeError): + system.launch(None) From b695d3c053f7938b22551120424eb1f3ddc7a546 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Mar 2016 17:55:07 -0400 Subject: [PATCH 08/20] Fix additional renaming --- .project | 2 +- docs/interfaces/cli.md | 2 +- {gdm => gitman}/__main__.py | 0 {gdm => gitman}/system.py | 0 {gdm => gitman}/test/test_system.py | 4 ++-- tests/test_cli.py | 35 ++++++++++++++--------------- tests/test_main.py | 2 +- 7 files changed, 22 insertions(+), 23 deletions(-) rename {gdm => gitman}/__main__.py (100%) rename {gdm => gitman}/system.py (100%) rename {gdm => gitman}/test/test_system.py (88%) diff --git a/.project b/.project index 18249b91..67ce2040 100644 --- a/.project +++ b/.project @@ -3,7 +3,7 @@ GitMan - GDM + GitMan diff --git a/docs/interfaces/cli.md b/docs/interfaces/cli.md index 8219e774..319153d7 100644 --- a/docs/interfaces/cli.md +++ b/docs/interfaces/cli.md @@ -131,5 +131,5 @@ gitman uninstall --force To open the existing configuration file: ```sh -gdm edit +gitman edit ``` diff --git a/gdm/__main__.py b/gitman/__main__.py similarity index 100% rename from gdm/__main__.py rename to gitman/__main__.py diff --git a/gdm/system.py b/gitman/system.py similarity index 100% rename from gdm/system.py rename to gitman/system.py diff --git a/gdm/test/test_system.py b/gitman/test/test_system.py similarity index 88% rename from gdm/test/test_system.py rename to gitman/test/test_system.py index 70aba835..196cffad 100644 --- a/gdm/test/test_system.py +++ b/gitman/test/test_system.py @@ -4,13 +4,13 @@ from expecter import expect -from gdm import system +from gitman import system def describe_launch(): @patch('platform.system', Mock(return_value="Windows")) - @patch('gdm.system._launch_windows') + @patch('gitman.system._launch_windows') def it_opens_files(startfile): system.launch("fake/path") expect(startfile.mock_calls) == [call("fake/path")] diff --git a/tests/test_cli.py b/tests/test_cli.py index 90958852..a3cd167c 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,32 +1,31 @@ -# pylint: disable=unused-variable,expression-not-assigned +# pylint: disable=unused-variable,redefined-outer-name,expression-not-assigned from unittest.mock import patch, call import pytest from expecter import expect -from gdm import cli +from gitman import cli -def describe_gdm(): +@pytest.fixture +def config(tmpdir): + tmpdir.chdir() + path = str(tmpdir.join("gdm.yml")) + open(path, 'w').close() + return path - @pytest.fixture - def config(tmpdir): - tmpdir.chdir() - path = str(tmpdir.join("gdm.yml")) - open(path, 'w').close() - return path - def describe_edit(): +def describe_edit(): - @patch('gdm.system.launch') - def it_launches_the_config(launch, config): - cli.main(['edit']) + @patch('gitman.system.launch') + def it_launches_the_config(launch, config): + cli.main(['edit']) - expect(launch.mock_calls) == [call(config), call().__bool__()] + expect(launch.mock_calls) == [call(config), call().__bool__()] - def it_exits_when_no_config_found(tmpdir): - tmpdir.chdir() + def it_exits_when_no_config_found(tmpdir): + tmpdir.chdir() - with expect.raises(SystemExit): - cli.main(['edit']) + with expect.raises(SystemExit): + cli.main(['edit']) diff --git a/tests/test_main.py b/tests/test_main.py index a0d440af..6fa878da 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -9,5 +9,5 @@ def describe_main(): def it_displays_version(): - code = subprocess.call([sys.executable, "-m", "gdm", "--version"]) + code = subprocess.call([sys.executable, "-m", "gitman", "--version"]) expect(code) == 0 From 4cc0653a0e821a9fe05d1287cdcf047d4d85deb1 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Mar 2016 18:12:02 -0400 Subject: [PATCH 09/20] Fix changelog spacing --- CHANGES.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES.md b/CHANGES.md index 7c087c82..7e9599af 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Revision History ---------------- - Added `edit` command to launch the configuration file. + 0.8.3 (2016/03/14) ------------------ From 72e2dcb35aaccc9cdad3666fcf355b2795e00000 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Mar 2016 18:30:22 -0400 Subject: [PATCH 10/20] Delete project files --- .project | 110 -------------------------------------------- .pydevproject | 8 ---- GDM.sublime-project | 41 ----------------- 3 files changed, 159 deletions(-) delete mode 100644 .project delete mode 100644 .pydevproject delete mode 100644 GDM.sublime-project diff --git a/.project b/.project deleted file mode 100644 index 67ce2040..00000000 --- a/.project +++ /dev/null @@ -1,110 +0,0 @@ - - - GitMan - - - GitMan - - - - org.python.pydev.PyDevBuilder - - - - - - org.python.pydev.pythonNature - - - - 1398478527151 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-__pycache__ - - - - 1398478527152 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-*.egg-info - - - - 1398478527153 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-env - - - - 1398478527154 - - 22 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-README.rst - - - - 1398478527155 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-apidocs - - - - 1398478527156 - - 22 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-*.sublime-* - - - - 1398478527157 - - 22 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-README*.html - - - - 1398478527158 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-build - - - - 1398478527159 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-dist - - - - 1429034866152 - - 26 - - org.eclipse.ui.ide.multiFilter - 1.0-name-matches-false-false-htmlcov - - - - diff --git a/.pydevproject b/.pydevproject deleted file mode 100644 index a2b8abd7..00000000 --- a/.pydevproject +++ /dev/null @@ -1,8 +0,0 @@ - - -python 3.0 -GitMan - -/${PROJECT_DIR_NAME} - - diff --git a/GDM.sublime-project b/GDM.sublime-project deleted file mode 100644 index a02dfb04..00000000 --- a/GDM.sublime-project +++ /dev/null @@ -1,41 +0,0 @@ -{ - "folders": - [ - { - "path": ".", - "file_exclude_patterns": [".coverage", "*.sublime-workspace"], - "folder_exclude_patterns": [".*", "__pycache__", "build", "dist", "env", "*.egg-info"] - } - ], - "settings": - { - "tab_size": 4, - "translate_tabs_to_spaces": true - }, - "SublimeLinter": - { - "linters": - { - "pep257": { - "@disable": false, - "args": [], - "excludes": [] - }, - "pep8": { - "@disable": false, - "args": ["--config=.pep8rc"], - "excludes": [], - "ignore": "", - "select": "" - }, - "pylint": { - "@disable": false, - "args": [], - "disable": "", - "enable": "", - "excludes": [], - "rcfile": ".pylintrc" - } - } - }, -} From c466882be2589fafdb688346206ca4e1239740e8 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Mon, 14 Mar 2016 23:53:03 -0400 Subject: [PATCH 11/20] Update coveralls badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 053e0430..fabc3dd9 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ [![Build Status](https://travis-ci.org/jacebrowning/gitman.svg?branch=develop)](https://travis-ci.org/jacebrowning/gitman) -[![Coverage Status](http://img.shields.io/coveralls/jacebrowning/gitman/master.svg)](https://coveralls.io/r/jacebrowning/gitman) +[![Coverage Status](https://coveralls.io/repos/github/jacebrowning/gitman/badge.svg?branch=develop)](https://coveralls.io/github/jacebrowning/gitman?branch=develop) [![Scrutinizer Code Quality](http://img.shields.io/scrutinizer/g/jacebrowning/gitman.svg)](https://scrutinizer-ci.com/g/jacebrowning/gitman/?branch=master) [![PyPI Version](http://img.shields.io/pypi/v/GitMan.svg)](https://pypi.python.org/pypi/GitMan) [![PyPI Downloads](http://img.shields.io/pypi/dm/GitMan.svg)](https://pypi.python.org/pypi/GitMan) From 114f160907ad15242a6beff45d58baded45a7635 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Tue, 22 Mar 2016 09:44:32 -0400 Subject: [PATCH 12/20] Default depth to 5 Fixes #3 --- CHANGES.md | 1 + gitman/cli.py | 2 +- gitman/test/test_cli.py | 40 ++++++++++++++++++++-------------------- 3 files changed, 22 insertions(+), 21 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 7e9599af..0f691876 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,6 +5,7 @@ Revision History ---------------- - Added `edit` command to launch the configuration file. +- Depth now defaults to 5 to prevent infinite recursion. 0.8.3 (2016/03/14) ------------------ diff --git a/gitman/cli.py b/gitman/cli.py index 83287677..bc98546c 100644 --- a/gitman/cli.py +++ b/gitman/cli.py @@ -29,7 +29,7 @@ def main(args=None, function=None): help="root directory of the project") depth = argparse.ArgumentParser(add_help=False) depth.add_argument('-d', '--depth', type=common.positive_int, - default=None, metavar="NUM", + default=5, metavar="NUM", help="limit the number of dependency levels") options = argparse.ArgumentParser(add_help=False) options.add_argument('-f', '--force', action='store_true', diff --git a/gitman/test/test_cli.py b/gitman/test/test_cli.py index 199a098e..06031d1b 100644 --- a/gitman/test/test_cli.py +++ b/gitman/test/test_cli.py @@ -57,7 +57,7 @@ def test_install(self, mock_install): cli.main(['install']) mock_install.assert_called_once_with( - root=None, depth=None, force=False, fetch=False, clean=False) + root=None, depth=5, force=False, fetch=False, clean=False) @patch('gitman.commands.install') def test_install_root(self, mock_install): @@ -65,7 +65,7 @@ def test_install_root(self, mock_install): cli.main(['install', '--root', 'mock/path/to/root']) mock_install.assert_called_once_with( - root='mock/path/to/root', depth=None, + root='mock/path/to/root', depth=5, force=False, fetch=False, clean=False) @patch('gitman.commands.install') @@ -74,7 +74,7 @@ def test_install_force(self, mock_install): cli.main(['install', '--force']) mock_install.assert_called_once_with( - root=None, depth=None, force=True, fetch=False, clean=False) + root=None, depth=5, force=True, fetch=False, clean=False) @patch('gitman.commands.install') def test_install_fetch(self, mock_install): @@ -82,7 +82,7 @@ def test_install_fetch(self, mock_install): cli.main(['install', '--fetch']) mock_install.assert_called_once_with( - root=None, depth=None, force=False, fetch=True, clean=False) + root=None, depth=5, force=False, fetch=True, clean=False) @patch('gitman.commands.install') def test_install_clean(self, mock_install): @@ -90,7 +90,7 @@ def test_install_clean(self, mock_install): cli.main(['install', '--clean']) mock_install.assert_called_once_with( - root=None, depth=None, force=False, fetch=False, clean=True) + root=None, depth=5, force=False, fetch=False, clean=True) @patch('gitman.commands.install') def test_install_specific_sources(self, mock_install): @@ -98,16 +98,16 @@ def test_install_specific_sources(self, mock_install): cli.main(['install', 'foo', 'bar']) mock_install.assert_called_once_with( - 'foo', 'bar', root=None, depth=None, + 'foo', 'bar', root=None, depth=5, force=False, fetch=False, clean=False) @patch('gitman.commands.install') def test_install_with_depth(self, mock_update): """Verify the 'install' command can be limited by depth.""" - cli.main(['install', '--depth', '5']) + cli.main(['install', '--depth', '10']) mock_update.assert_called_once_with( - root=None, depth=5, force=False, fetch=False, clean=False) + root=None, depth=10, force=False, fetch=False, clean=False) @patch('gitman.commands.install', Mock()) def test_install_with_depth_invalid(self): @@ -128,7 +128,7 @@ def test_update(self, mock_update): cli.main(['update']) mock_update.assert_called_once_with( - root=None, depth=None, + root=None, depth=5, force=False, clean=False, recurse=False, lock=None) @patch('gitman.commands.update') @@ -137,7 +137,7 @@ def test_update_recursive(self, mock_update): cli.main(['update', '--all']) mock_update.assert_called_once_with( - root=None, depth=None, + root=None, depth=5, force=False, clean=False, recurse=True, lock=None) @patch('gitman.commands.update') @@ -146,7 +146,7 @@ def test_update_no_lock(self, mock_update): cli.main(['update', '--no-lock']) mock_update.assert_called_once_with( - root=None, depth=None, + root=None, depth=5, force=False, clean=False, recurse=False, lock=False) @patch('gitman.commands.update') @@ -155,7 +155,7 @@ def test_update_lock(self, mock_update): cli.main(['update', '--lock']) mock_update.assert_called_once_with( - root=None, depth=None, + root=None, depth=5, force=False, clean=False, recurse=False, lock=True) def test_update_lock_conflict(self): @@ -169,16 +169,16 @@ def test_update_specific_sources(self, mock_install): cli.main(['update', 'foo', 'bar']) mock_install.assert_called_once_with( - 'foo', 'bar', root=None, depth=None, + 'foo', 'bar', root=None, depth=5, force=False, clean=False, recurse=False, lock=None) @patch('gitman.commands.update') def test_update_with_depth(self, mock_update): """Verify the 'update' command can be limited by depth.""" - cli.main(['update', '--depth', '5']) + cli.main(['update', '--depth', '10']) mock_update.assert_called_once_with( - root=None, depth=5, + root=None, depth=10, force=False, clean=False, recurse=False, lock=None) @@ -192,7 +192,7 @@ def test_list(self, mock_display): cli.main(['list']) mock_display.assert_called_once_with( - root=None, depth=None, allow_dirty=True) + root=None, depth=5, allow_dirty=True) @patch('gitman.commands.display') def test_list_root(self, mock_display): @@ -200,7 +200,7 @@ def test_list_root(self, mock_display): cli.main(['list', '--root', 'mock/path/to/root']) mock_display.assert_called_once_with( - root='mock/path/to/root', depth=None, allow_dirty=True) + root='mock/path/to/root', depth=5, allow_dirty=True) @patch('gitman.commands.display') def test_list_no_dirty(self, mock_display): @@ -208,15 +208,15 @@ def test_list_no_dirty(self, mock_display): cli.main(['list', '--no-dirty']) mock_display.assert_called_once_with( - root=None, depth=None, allow_dirty=False) + root=None, depth=5, allow_dirty=False) @patch('gitman.commands.display') def test_update_with_depth(self, mock_update): """Verify the 'list' command can be limited by depth.""" - cli.main(['list', '--depth', '5']) + cli.main(['list', '--depth', '10']) mock_update.assert_called_once_with( - root=None, depth=5, allow_dirty=True) + root=None, depth=10, allow_dirty=True) def describe_lock(): From 21e6711cb964a8f92042224e3cb5484586fe9d40 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 14:40:42 -0400 Subject: [PATCH 13/20] Add link .netrc usage on Travis CI --- docs/setup/git.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/setup/git.md b/docs/setup/git.md index 3aca6657..f6e9895a 100644 --- a/docs/setup/git.md +++ b/docs/setup/git.md @@ -47,3 +47,5 @@ Finally, the repository URL itself can contain an OAuth token (for [GitHub](http ```shell $ git clone https://@github.com//.git ``` + +The token can also be written to `.netrc` during builds, see the guide for [Travis CI](https://docs.travis-ci.com/user/private-dependencies/#API-Token). From 67ff71bfdfded9625783cf44b35f068280a6694a Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 20:31:14 -0400 Subject: [PATCH 14/20] Merge both source lists into the requested source list --- CHANGES.md | 1 + gitman/config.py | 29 +++++++++++----- tests/test_api.py | 86 ++++++++++++++++++++++++++++++----------------- 3 files changed, 76 insertions(+), 40 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 0f691876..08aea477 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ Revision History - Added `edit` command to launch the configuration file. - Depth now defaults to 5 to prevent infinite recursion. +- Fixed handling of source lists containing different dependencies. 0.8.3 (2016/03/14) ------------------ diff --git a/gitman/config.py b/gitman/config.py index ef553643..087ae2d0 100644 --- a/gitman/config.py +++ b/gitman/config.py @@ -133,13 +133,13 @@ def uninstall_deps(self): def get_deps(self, depth=None, allow_dirty=True): """Yield the path, repository URL, and hash of each dependency.""" - if os.path.exists(self.location_path): - shell.cd(self.location_path) - common.show() - common.indent() - else: + if not os.path.exists(self.location_path): return + shell.cd(self.location_path) + common.show() + common.indent() + for source in self.sources: if depth == 0: @@ -163,21 +163,32 @@ def get_deps(self, depth=None, allow_dirty=True): common.dedent() def _get_sources(self, *, use_locked=None): + """Merge source lists using requested section as the base.""" 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 + + sources = [] + if use_locked is False: + sources = self.sources else: if self.sources_locked: log.info("Defalting to locked sources...") - return self.sources_locked + sources = self.sources_locked else: log.info("No locked sources, using latest...") - return self.sources + sources = self.sources + + extras = [] + for source in self.sources + self.sources_locked: + if source not in sources: + log.info("Source %r missing from selected section", source.dir) + extras.append(source) + + return sources + extras def load(root=None): diff --git a/tests/test_api.py b/tests/test_api.py index 8f3a6561..243d2c2b 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,4 +1,4 @@ -# pylint: disable=no-self-use,redefined-outer-name,unused-variable,unused-argument +# pylint: disable=redefined-outer-name,unused-argument,unused-variable,singleton-comparison,expression-not-assigned import os import shutil @@ -6,6 +6,7 @@ import logging import pytest +from expecter import expect import gitman from gitman.config import Config @@ -55,18 +56,41 @@ def config(root="/tmp/gitman-shared"): def describe_install(): def it_should_create_missing_directories(config): - assert not os.path.isdir(config.location) + expect(os.path.isdir(config.location)) == False - assert gitman.install('gitman_1', depth=1) + expect(gitman.install('gitman_1', depth=1)) == True - assert ['gitman_1'] == os.listdir(config.location) + expect(os.listdir(config.location)) == ['gitman_1'] def it_should_not_modify_config(config): - assert gitman.install('gitman_1', depth=1) + expect(gitman.install('gitman_1', depth=1)) == True - assert CONFIG == config.__mapper__.text + expect(config.__mapper__.text) == CONFIG - def it_should_use_locked_sources_if_available(config): + def it_should_merge_sources(config): + config.__mapper__.text = strip(""" + location: deps + sources: + - dir: gitman_1 + link: '' + repo: https://github.com/jacebrowning/gitman-demo + rev: example-branch + sources_locked: + - dir: gitman_2 + link: '' + repo: https://github.com/jacebrowning/gitman-demo + rev: example-branch + - dir: gitman_3 + link: '' + repo: https://github.com/jacebrowning/gitman-demo + rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 + """) + + expect(gitman.install(depth=1)) == True + + expect(len(os.listdir(config.location))) == 3 + + def it_can_handle_missing_locked_sources(config): config.__mapper__.text = strip(""" location: deps sources: @@ -81,9 +105,9 @@ def it_should_use_locked_sources_if_available(config): rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 """) - assert gitman.install(depth=1) + expect(gitman.install('gitman_1', depth=1)) == True - assert ['gitman_2'] == os.listdir(config.location) + expect(os.listdir(config.location)) == ['gitman_1'] def describe_links(): @@ -101,9 +125,9 @@ def config_with_link(config): return config def it_should_create(config_with_link): - assert gitman.install(depth=1) + expect(gitman.install(depth=1)) == True - assert 'my_link' in os.listdir() + expect(os.listdir()).contains('my_link') def it_should_not_overwrite(config_with_link): os.system("touch my_link") @@ -114,23 +138,23 @@ def it_should_not_overwrite(config_with_link): def it_should_overwrite_with_force(config_with_link): os.system("touch my_link") - assert gitman.install(depth=1, force=True) + expect(gitman.install(depth=1, force=True)) == True def describe_uninstall(): def it_should_delete_dependencies_when_they_exist(config): gitman.install('gitman_1', depth=1) - assert os.path.isdir(config.location) + expect(os.path.isdir(config.location)) == True - assert gitman.uninstall() + expect(gitman.uninstall()) == True - assert not os.path.exists(config.location) + expect(os.path.exists(config.location)) == False def it_should_not_fail_when_no_dependnecies_exist(config): - assert not os.path.isdir(config.location) + expect(os.path.isdir(config.location)) == False - assert gitman.uninstall() + expect(gitman.uninstall()) == True def describe_update(): @@ -138,7 +162,7 @@ def describe_update(): def it_should_not_modify_config(config): gitman.update('gitman_1', depth=1) - assert CONFIG == config.__mapper__.text + expect(config.__mapper__.text) == CONFIG def it_should_lock_previously_locked_dependnecies(config): config.__mapper__.text = strip(""" @@ -161,7 +185,7 @@ def it_should_lock_previously_locked_dependnecies(config): gitman.update(depth=1) - assert strip(""" + expect(config.__mapper__.text) == strip(""" location: deps sources: - dir: gitman_1 @@ -177,7 +201,7 @@ def it_should_lock_previously_locked_dependnecies(config): link: '' repo: https://github.com/jacebrowning/gitman-demo rev: 7bd138fe7359561a8c2ff9d195dff238794ccc04 - """) == config.__mapper__.text + """) def it_should_not_lock_dependnecies_when_disabled(config): config.__mapper__.text = strip(""" @@ -200,7 +224,7 @@ def it_should_not_lock_dependnecies_when_disabled(config): gitman.update(depth=1, lock=False) - assert strip(""" + expect(config.__mapper__.text) == strip(""" location: deps sources: - dir: gitman_1 @@ -216,12 +240,12 @@ def it_should_not_lock_dependnecies_when_disabled(config): link: '' repo: https://github.com/jacebrowning/gitman-demo rev: (old revision) - """) == config.__mapper__.text + """) def it_should_lock_all_when_enabled(config): gitman.update(depth=1, lock=True) - assert CONFIG + strip(""" + expect(config.__mapper__.text) == CONFIG + strip(""" sources_locked: - dir: gitman_1 link: '' @@ -235,16 +259,16 @@ def it_should_lock_all_when_enabled(config): link: '' repo: https://github.com/jacebrowning/gitman-demo rev: 9bf18e16b956041f0267c21baad555a23237b52e - """) == config.__mapper__.text + """) def describe_lock(): def it_should_record_all_versions_when_no_arguments(config): - assert gitman.update(depth=1, lock=False) - assert gitman.lock() + expect(gitman.update(depth=1, lock=False)) == True + expect(gitman.lock()) == True - assert CONFIG + strip(""" + expect(config.__mapper__.text) == CONFIG + strip(""" sources_locked: - dir: gitman_1 link: '' @@ -261,10 +285,10 @@ def it_should_record_all_versions_when_no_arguments(config): """) == config.__mapper__.text def it_should_record_specified_dependencies(config): - assert gitman.update(depth=1, lock=False) - assert gitman.lock('gitman_1', 'gitman_3') + expect(gitman.update(depth=1, lock=False)) == True + expect(gitman.lock('gitman_1', 'gitman_3')) == True - assert CONFIG + strip(""" + expect(config.__mapper__.text) == CONFIG + strip(""" sources_locked: - dir: gitman_1 link: '' @@ -282,4 +306,4 @@ def it_should_fail_on_invalid_repositories(config): with pytest.raises(InvalidRepository): gitman.lock() - assert "" not in config.__mapper__.text + expect(config.__mapper__.text).does_not_contain("") From f13ff789c00f9baeaf9444718c47c3a57477e866 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 20:32:08 -0400 Subject: [PATCH 15/20] Update project structure --- .gitignore | 25 +++++--- .pep257 | 11 +++- .pep8rc | 3 +- .pylintrc | 2 +- Makefile | 142 ++++++++++++++++++++++++--------------------- gitman/__main__.py | 2 +- gitman/common.py | 2 +- 7 files changed, 104 insertions(+), 83 deletions(-) diff --git a/.gitignore b/.gitignore index 51619dc8..42c14afc 100644 --- a/.gitignore +++ b/.gitignore @@ -9,17 +9,21 @@ __pycache__ # Temporary OS files Icon* -# Temporary virtualenv files +# Temporary virtual environment files .cache -env +/env + +# Temporary server files +.env +*.pid # Generated documentation -docs/gen +/docs/gen /apidocs /site /*.html -*.rst -docs/*.png +/*.rst +/docs/*.png # Google Drive *.gdoc @@ -28,14 +32,17 @@ docs/*.png *.gdraw # Testing and coverage results +.cache .pytest .coverage -htmlcov -pyunit.xml +.coverage.* +/htmlcov +/pyunit.xml +*.tmp # Build and release directories -build -dist +/build +/dist # Sublime Text *.sublime-workspace diff --git a/.pep257 b/.pep257 index 229f6b24..e2641800 100644 --- a/.pep257 +++ b/.pep257 @@ -1,5 +1,10 @@ [pep257] -# D10*: docstring missing (checked by PyLint) -# D202: No blank lines allowed *after* function docstring (personal preference) -add-ignore = D102,D103,D105,D202 +# D211: No blank lines allowed before class docstring +add_select = D211 + +# D102: Missing docstring in public method +# D103: Missing docstring in public function +# D105: Missing docstring in magic method +# D202: No blank lines allowed after function docstring +add_ignore = D102,D103,D105,D202 diff --git a/.pep8rc b/.pep8rc index 7c1de86c..fb506d68 100644 --- a/.pep8rc +++ b/.pep8rc @@ -1,2 +1,3 @@ [pep8] -ignore = E501 + +ignore = E501,E712 diff --git a/.pylintrc b/.pylintrc index 3977dd48..c82cb82e 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,misplaced-comparison-constant +disable=locally-disabled,fixme,too-few-public-methods,too-many-public-methods,invalid-name,global-statement,too-many-ancestors,misplaced-comparison-constant,missing-docstring [FORMAT] diff --git a/Makefile b/Makefile index 4686efd4..a5fadade 100644 --- a/Makefile +++ b/Makefile @@ -2,9 +2,8 @@ PROJECT := GitMan PACKAGE := gitman SOURCES := Makefile setup.py $(shell find $(PACKAGE) -name '*.py') -EGG_INFO := $(subst -,_,$(PROJECT)).egg-info -# Python +# Python settings ifndef TRAVIS PYTHON_MAJOR ?= 3 PYTHON_MINOR ?= 5 @@ -13,7 +12,7 @@ endif # Test settings UNIT_TEST_COVERAGE := 73 INTEGRATION_TEST_COVERAGE := 52 -COMBINED_TEST_COVERAGE := 98 +COMBINED_TEST_COVERAGE := 97 # System paths PLATFORM := $(shell python -c 'import sys; print(sys.platform)') @@ -21,7 +20,6 @@ ifneq ($(findstring win32, $(PLATFORM)), ) WINDOWS := 1 SYS_PYTHON_DIR := C:\\Python$(PYTHON_MAJOR)$(PYTHON_MINOR) SYS_PYTHON := $(SYS_PYTHON_DIR)\\python.exe - SYS_VIRTUALENV := $(SYS_PYTHON_DIR)\\Scripts\\virtualenv.exe # https://bugs.launchpad.net/virtualenv/+bug/449537 export TCL_LIBRARY=$(SYS_PYTHON_DIR)\\tcl\\tcl8.5 else @@ -34,16 +32,17 @@ else ifdef PYTHON_MINOR SYS_PYTHON := $(SYS_PYTHON).$(PYTHON_MINOR) endif - SYS_VIRTUALENV := virtualenv endif -# virtualenv paths +# Virtual environment paths ENV := env ifneq ($(findstring win32, $(PLATFORM)), ) BIN := $(ENV)/Scripts + ACTIVATE := $(BIN)/activate.bat OPEN := cmd /c start else BIN := $(ENV)/bin + ACTIVATE := . $(BIN)/activate ifneq ($(findstring cygwin, $(PLATFORM)), ) OPEN := cygstart else @@ -51,25 +50,31 @@ else endif endif -# virtualenv executables -PYTHON := $(BIN)/python -PIP := $(BIN)/pip -EASY_INSTALL := $(BIN)/easy_install -RST2HTML := $(PYTHON) $(BIN)/rst2html.py -PDOC := $(PYTHON) $(BIN)/pdoc -PEP8 := $(BIN)/pep8 -PEP8RADIUS := $(BIN)/pep8radius -PEP257 := $(BIN)/pep257 -PYLINT := $(BIN)/pylint -PYREVERSE := $(BIN)/pyreverse -NOSE := $(BIN)/nosetests -PYTEST := $(BIN)/py.test -COVERAGE := $(BIN)/coverage -SNIFFER := $(BIN)/sniffer -MKDOCS := $(BIN)/mkdocs +# Virtual environment executables +ifndef TRAVIS + BIN_ := $(BIN)/ +endif +PYTHON := $(BIN_)python +PIP := $(BIN_)pip +EASY_INSTALL := $(BIN_)easy_install +RST2HTML := $(PYTHON) $(BIN_)rst2html.py +PDOC := $(PYTHON) $(BIN_)pdoc +MKDOCS := $(BIN_)mkdocs +PEP8 := $(BIN_)pep8 +PEP8RADIUS := $(BIN_)pep8radius +PEP257 := $(BIN_)pep257 +PYLINT := $(BIN_)pylint +PYREVERSE := $(BIN_)pyreverse +NOSE := $(BIN_)nosetests +PYTEST := $(BIN_)py.test +COVERAGE := $(BIN_)coverage +SNIFFER := $(BIN_)sniffer +HONCHO := $(ACTIVATE) && honcho # Flags for PHONY targets +INSTALLED_FLAG := $(ENV)/.installed DEPENDS_CI_FLAG := $(ENV)/.depends-ci +DEPENDS_DOC_FLAG := $(ENV)/.depends-doc DEPENDS_DEV_FLAG := $(ENV)/.depends-dev DOCS_FLAG := $(ENV)/.docs ALL_FLAG := $(ENV)/.all @@ -80,43 +85,53 @@ ALL_FLAG := $(ENV)/.all all: depends doc $(ALL_FLAG) $(ALL_FLAG): $(SOURCES) $(MAKE) check - @ touch $(ALL_FLAG) # flag to indicate all setup steps were successful + touch $(ALL_FLAG) # flag to indicate all setup steps were successful .PHONY: ci -ci: mkdocs check test tests +ifdef TRAVIS +ci: check test tests +else +ci: doc check test tests +endif .PHONY: watch -watch: depends-dev .clean-test +watch: depends .clean-test @ rm -rf $(FAILED_FLAG) $(SNIFFER) # Development Installation ##################################################### .PHONY: env -env: .virtualenv $(EGG_INFO) -$(EGG_INFO): Makefile setup.py requirements.txt +env: $(PIP) $(INSTALLED_FLAG) +$(INSTALLED_FLAG): Makefile setup.py requirements.txt VIRTUAL_ENV=$(ENV) $(PYTHON) setup.py develop - @ touch $(EGG_INFO) # flag to indicate package is installed + @ touch $(INSTALLED_FLAG) # flag to indicate package is installed -.PHONY: .virtualenv -.virtualenv: $(PIP) $(PIP): - $(SYS_VIRTUALENV) --python $(SYS_PYTHON) $(ENV) - $(PIP) install --upgrade pip + $(SYS_PYTHON) -m venv --clear $(ENV) + $(PIP) install --upgrade pip setuptools + +# Tools Installation ########################################################### .PHONY: depends -depends: depends-ci depends-dev +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-expecter pytest-describe pytest-cov pytest-random pytest-runfailed mkdocs + $(PIP) install --upgrade pep8 pep257 pylint coverage pytest pytest-describe pytest-expecter pytest-cov pytest-random @ touch $(DEPENDS_CI_FLAG) # flag to indicate dependencies are installed +.PHONY: depends-doc +depends-doc: env Makefile $(DEPENDS_DOC_FLAG) +$(DEPENDS_DOC_FLAG): Makefile + $(PIP) install --upgrade pylint docutils readme pdoc mkdocs pygments + @ touch $(DEPENDS_DOC_FLAG) # flag to indicate dependencies are installed + .PHONY: depends-dev depends-dev: env Makefile $(DEPENDS_DEV_FLAG) $(DEPENDS_DEV_FLAG): Makefile - $(PIP) install --upgrade pip pep8radius pygments docutils pdoc wheel readme sniffer + $(PIP) install --upgrade pip pep8radius wheel sniffer ifdef WINDOWS $(PIP) install --upgrade pywin32 else ifdef MAC @@ -146,34 +161,34 @@ read: doc $(OPEN) README-github.html .PHONY: readme -readme: depends-dev README-github.html README-pypi.html +readme: depends-doc README-github.html README-pypi.html README-github.html: README.md pandoc -f markdown_github -t html -o README-github.html README.md README-pypi.html: README.rst $(RST2HTML) README.rst README-pypi.html -README.rst: README.md - pandoc -f markdown_github -t rst -o README.rst README.md +%.rst: %.md + pandoc -f markdown_github -t rst -o $@ $< .PHONY: verify-readme verify-readme: $(DOCS_FLAG) -$(DOCS_FLAG): README.rst +$(DOCS_FLAG): README.rst CHANGES.rst $(PYTHON) setup.py check --restructuredtext --strict --metadata @ touch $(DOCS_FLAG) # flag to indicate README has been checked .PHONY: uml -uml: depends-dev docs/*.png +uml: depends-doc docs/*.png docs/*.png: $(SOURCES) $(PYREVERSE) $(PACKAGE) -p $(PACKAGE) -a 1 -f ALL -o png --ignore test - mv -f classes_$(PACKAGE).png docs/classes.png - mv -f packages_$(PACKAGE).png docs/packages.png .PHONY: apidocs -apidocs: depends-dev apidocs/$(PACKAGE)/index.html +apidocs: depends-doc apidocs/$(PACKAGE)/index.html apidocs/$(PACKAGE)/index.html: $(SOURCES) $(PDOC) --html --overwrite $(PACKAGE) --html-dir apidocs .PHONY: mkdocs -mkdocs: depends-ci site/index.html +mkdocs: depends-doc site/index.html site/index.html: mkdocs.yml docs/*.md $(MKDOCS) build --clean --strict echo $(URL) > site/CNAME @@ -193,11 +208,7 @@ pep257: depends-ci .PHONY: pylint pylint: depends-ci -# These warnings shouldn't fail builds, but warn in editors: -# C0111: Line too long -# R0913: Too many arguments -# R0914: Too many local variables - $(PYLINT) $(PACKAGE) tests --rcfile=.pylintrc --disable=missing-docstring,too-many-statements + $(PYLINT) $(PACKAGE) tests --rcfile=.pylintrc .PHONY: fix fix: depends-dev @@ -208,41 +219,38 @@ fix: depends-dev RANDOM_SEED ?= $(shell date +%s) PYTEST_CORE_OPTS := --verbose -r xXw --maxfail=3 -PYTEST_COV_OPTS := --cov=$(PACKAGE) --cov-report=term-missing --no-cov-on-fail +PYTEST_COV_OPTS := --cov=$(PACKAGE) --no-cov-on-fail --cov-report=term-missing PYTEST_RANDOM_OPTS := --random --random-seed=$(RANDOM_SEED) PYTEST_OPTS := $(PYTEST_CORE_OPTS) $(PYTEST_COV_OPTS) $(PYTEST_RANDOM_OPTS) PYTEST_OPTS_FAILFAST := $(PYTEST_OPTS) --failed --exitfirst -FAILED_FLAG := .pytest/failed +FAILURES := .cache/v/cache/lastfailed .PHONY: test test-unit test: test-unit test-unit: depends-ci - @ $(COVERAGE) erase + @- mv $(FAILURES) $(FAILURES).bak $(PYTEST) $(PYTEST_OPTS) $(PACKAGE) + @- mv $(FAILURES).bak $(FAILURES) ifndef TRAVIS $(COVERAGE) html --directory htmlcov --fail-under=$(UNIT_TEST_COVERAGE) endif .PHONY: test-int test-int: depends-ci - @ if test -e $(FAILED_FLAG); then $(MAKE) test-all; fi - @ $(COVERAGE) erase - TEST_INTEGRATION=1 $(PYTEST) $(PYTEST_OPTS_FAILFAST) tests + @ if test -e $(FAILURES); then $(PYTEST) $(PYTEST_OPTS_FAILFAST) tests; fi + $(PYTEST) $(PYTEST_OPTS) tests ifndef TRAVIS - @ rm -rf $(FAILED_FLAG) # next time, don't run the previously failing test $(COVERAGE) html --directory htmlcov --fail-under=$(INTEGRATION_TEST_COVERAGE) endif .PHONY: tests test-all tests: test-all test-all: depends-ci - @ if test -e $(FAILED_FLAG); then $(PYTEST) --failed $(PACKAGE) tests; fi - @ $(COVERAGE) erase - TEST_INTEGRATION=1 $(PYTEST) $(PYTEST_OPTS_FAILFAST) $(PACKAGE) tests + @ if test -e $(FAILURES); then $(PYTEST) $(PYTEST_OPTS_FAILFAST) $(PACKAGE) tests; fi + $(PYTEST) $(PYTEST_OPTS) $(PACKAGE) tests ifndef TRAVIS - @ rm -rf $(FAILED_FLAG) # next time, don't run the previously failing test $(COVERAGE) html --directory htmlcov --fail-under=$(COMBINED_TEST_COVERAGE) endif @@ -256,18 +264,14 @@ read-coverage: clean: .clean-dist .clean-test .clean-doc .clean-build rm -rf $(ALL_FLAG) -.PHONY: clean-env -clean-env: clean - rm -rf $(ENV) - .PHONY: clean-all -clean-all: clean clean-env .clean-workspace +clean-all: clean .clean-env .clean-workspace .PHONY: .clean-build .clean-build: - find $(PACKAGE) -name '*.pyc' -delete - find $(PACKAGE) -name '__pycache__' -delete - rm -rf $(EGG_INFO) + find $(PACKAGE) tests -name '*.pyc' -delete + find $(PACKAGE) tests -name '__pycache__' -delete + rm -rf $(INSTALLED_FLAG) *.egg-info .PHONY: .clean-doc .clean-doc: @@ -275,12 +279,16 @@ clean-all: clean clean-env .clean-workspace .PHONY: .clean-test .clean-test: - rm -rf .pytest .coverage htmlcov + rm -rf .cache .pytest .coverage htmlcov .PHONY: .clean-dist .clean-dist: rm -rf dist build +.PHONY: .clean-env +.clean-env: clean + rm -rf $(ENV) + .PHONY: .clean-workspace .clean-workspace: rm -rf *.sublime-workspace diff --git a/gitman/__main__.py b/gitman/__main__.py index 7c8438a6..1e771e17 100644 --- a/gitman/__main__.py +++ b/gitman/__main__.py @@ -3,5 +3,5 @@ from .cli import main -if __name__ == '__main__': +if __name__ == '__main__': # pragma: no cover (manual test) main() diff --git a/gitman/common.py b/gitman/common.py index 0782b7c5..c235a4ff 100644 --- a/gitman/common.py +++ b/gitman/common.py @@ -24,7 +24,7 @@ def __init__(self, default_format, verbose_format, *args, **kwargs): def format(self, record): """A hack to change the formatting style dynamically.""" - # pylint: disable=W0212 + # pylint: disable=protected-access if record.levelno > logging.INFO: self._style._fmt = self.verbose_format else: From 9abf40964a90f2498c229355c56ff153d1680687 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 20:39:37 -0400 Subject: [PATCH 16/20] Update YORM to 0.7.1 --- gitman/config.py | 12 ++++-------- gitman/source.py | 11 ++++++----- requirements.txt | 2 +- 3 files changed, 11 insertions(+), 14 deletions(-) diff --git a/gitman/config.py b/gitman/config.py index 087ae2d0..c71e1804 100644 --- a/gitman/config.py +++ b/gitman/config.py @@ -4,6 +4,7 @@ import logging import yorm +from yorm.types import String, SortedList from . import common from . import shell @@ -12,14 +13,9 @@ log = logging.getLogger(__name__) -@yorm.attr(all=Source) -class Sources(yorm.types.SortedList): - """A list of source dependencies.""" - - -@yorm.attr(location=yorm.types.String) -@yorm.attr(sources=Sources) -@yorm.attr(sources_locked=Sources) +@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}") class Config: """A dictionary of dependency configuration options.""" diff --git a/gitman/source.py b/gitman/source.py index 05a97dfa..33f0225f 100644 --- a/gitman/source.py +++ b/gitman/source.py @@ -4,6 +4,7 @@ import logging import yorm +from yorm.types import String, AttributeDictionary from . import common from . import git @@ -14,11 +15,11 @@ log = logging.getLogger(__name__) -@yorm.attr(repo=yorm.types.String) -@yorm.attr(dir=yorm.types.String) -@yorm.attr(rev=yorm.types.String) -@yorm.attr(link=yorm.types.String) -class Source(yorm.types.AttributeDictionary): +@yorm.attr(dir=String) +@yorm.attr(repo=String) +@yorm.attr(link=String) +@yorm.attr(rev=String) +class Source(AttributeDictionary): """A dictionary of `git` and `ln` arguments.""" DIRTY = '' diff --git a/requirements.txt b/requirements.txt index e5b21c0f..643bbf1c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -YORM ~= 0.6.1 +YORM ~= 0.7.1 sh ~= 1.11 From 419a5346f2a3fc2ad67e4b8f800b0e1ac3f018d4 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 20:52:40 -0400 Subject: [PATCH 17/20] Refactor models into their own package --- gitman/commands.py | 14 +++++++------- gitman/models/__init__.py | 4 ++++ gitman/{ => models}/config.py | 12 ++++++------ gitman/{ => models}/source.py | 8 ++++---- .../test/{test_config.py => test_models_config.py} | 6 +++--- .../{test_sources.py => test_models_source.py} | 2 +- tests/test_api.py | 2 +- 7 files changed, 26 insertions(+), 22 deletions(-) create mode 100644 gitman/models/__init__.py rename gitman/{ => models}/config.py (97%) rename gitman/{ => models}/source.py (96%) rename gitman/test/{test_config.py => test_models_config.py} (96%) rename gitman/test/{test_sources.py => test_models_source.py} (98%) diff --git a/gitman/commands.py b/gitman/commands.py index 5a8ea492..e8d59e5d 100644 --- a/gitman/commands.py +++ b/gitman/commands.py @@ -5,7 +5,7 @@ import logging from . import common, system -from .config import load +from .models import load_config log = logging.getLogger(__name__) @@ -41,7 +41,7 @@ def install(*names, root=None, depth=None, count = None root = _find_root(root) - config = load(root) + config = load_config(root) if config: common.show("Installing dependencies...", log=False) @@ -75,7 +75,7 @@ def update(*names, root=None, depth=None, count = None root = _find_root(root) - config = load(root) + config = load_config(root) if config: common.show("Updating dependencies...", log=False) @@ -107,7 +107,7 @@ def display(root=None, depth=None, allow_dirty=True): count = None root = _find_root(root) - config = load(root) + config = load_config(root) if config: common.show("Displaying current dependency versions...", log=False) @@ -131,7 +131,7 @@ def lock(*names, root=None): count = None root = _find_root(root) - config = load(root) + config = load_config(root) if config: common.show("Locking dependencies...", log=False) @@ -156,7 +156,7 @@ def delete(root=None, force=False): count = None root = _find_root(root) - config = load(root) + config = load_config(root) if config: common.show("Checking for uncommitted changes...", log=False) @@ -182,7 +182,7 @@ def edit(root=None): log.info("Launching configuration...") root = _find_root(root) - config = load(root) + config = load_config(root) if config: return system.launch(config.path) diff --git a/gitman/models/__init__.py b/gitman/models/__init__.py new file mode 100644 index 00000000..aae020cf --- /dev/null +++ b/gitman/models/__init__.py @@ -0,0 +1,4 @@ +"""Domain models for interacting with dependency configuration.""" + +from .source import Source +from .config import Config, load_config diff --git a/gitman/config.py b/gitman/models/config.py similarity index 97% rename from gitman/config.py rename to gitman/models/config.py index c71e1804..d102041e 100644 --- a/gitman/config.py +++ b/gitman/models/config.py @@ -6,9 +6,9 @@ import yorm from yorm.types import String, SortedList -from . import common -from . import shell -from .source import Source +from .. import common +from .. import shell +from . import Source log = logging.getLogger(__name__) @@ -69,7 +69,7 @@ def install_deps(self, *names, depth=None, common.show() - config = load() + config = load_config() if config: common.indent() count += config.install_deps( @@ -145,7 +145,7 @@ def get_deps(self, depth=None, allow_dirty=True): yield source.identify(allow_dirty=allow_dirty) common.show() - config = load() + config = load_config() if config: common.indent() yield from config.get_deps( @@ -187,7 +187,7 @@ def _get_sources(self, *, use_locked=None): return sources + extras -def load(root=None): +def load_config(root=None): """Load the configuration for the current project.""" if root is None: root = os.getcwd() diff --git a/gitman/source.py b/gitman/models/source.py similarity index 96% rename from gitman/source.py rename to gitman/models/source.py index 33f0225f..3af7152e 100644 --- a/gitman/source.py +++ b/gitman/models/source.py @@ -6,10 +6,10 @@ import yorm from yorm.types import String, AttributeDictionary -from . import common -from . import git -from . import shell -from .exceptions import InvalidConfig, InvalidRepository, UncommittedChanges +from .. import common +from .. import git +from .. import shell +from ..exceptions import InvalidConfig, InvalidRepository, UncommittedChanges log = logging.getLogger(__name__) diff --git a/gitman/test/test_config.py b/gitman/test/test_models_config.py similarity index 96% rename from gitman/test/test_config.py rename to gitman/test/test_models_config.py index 1d8ff267..d1608317 100644 --- a/gitman/test/test_config.py +++ b/gitman/test/test_models_config.py @@ -2,7 +2,7 @@ import pytest -from gitman.config import Config, load +from gitman.models import Config, load_config from .conftest import FILES @@ -104,13 +104,13 @@ def test_install_with_depth_2(self): class TestLoad: def test_load_from_directory_with_config_file(self): - config = load(FILES) + config = load_config(FILES) assert None is not config def test_load_from_directory_without_config_file(self, tmpdir): tmpdir.chdir() - config = load() + config = load_config() assert None is config diff --git a/gitman/test/test_sources.py b/gitman/test/test_models_source.py similarity index 98% rename from gitman/test/test_sources.py rename to gitman/test/test_models_source.py index c178c815..6fd2670a 100644 --- a/gitman/test/test_sources.py +++ b/gitman/test/test_models_source.py @@ -5,7 +5,7 @@ import pytest -from gitman.config import Source +from gitman.models import Source @pytest.fixture diff --git a/tests/test_api.py b/tests/test_api.py index 243d2c2b..801efaf0 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -9,7 +9,7 @@ from expecter import expect import gitman -from gitman.config import Config +from gitman.models import Config from gitman.exceptions import InvalidRepository from .utilities import strip From fccc3302dc1278e83eb869fc33f37c77d4bf9db3 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 21:07:38 -0400 Subject: [PATCH 18/20] Make the README and site index more consistent --- README.md | 31 +++++++++++-------------------- docs/index.md | 3 +-- gitman/__init__.py | 2 +- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index fabc3dd9..adc5eee8 100644 --- a/README.md +++ b/README.md @@ -4,20 +4,17 @@ [![PyPI Version](http://img.shields.io/pypi/v/GitMan.svg)](https://pypi.python.org/pypi/GitMan) [![PyPI Downloads](http://img.shields.io/pypi/dm/GitMan.svg)](https://pypi.python.org/pypi/GitMan) -Getting Started -=============== +# Getting Started GitMan 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 ------------- +## Requirements * Python 3.4+ -* Latest version of Git (with [stored credentials](http://stackoverflow.com/questions/7773181)) +* Latest version of Git (with [stored credentials](http://git-dependency-manager.info/setup/git/)) * OSX/Linux (with a decent shell for Git) -Installation ------------- +## Installation GitMan can be installed with pip: @@ -33,8 +30,7 @@ $ cd gitman $ python setup.py install ``` -Setup ------ +## Setup Create a configuration file (`gitman.yml` or `.gitman.yml`) in the root of your working tree: @@ -53,11 +49,10 @@ sources: Ignore the dependency storage location: ```sh -$ echo .gitman >> .gitignore +$ echo vendor >> .gitignore ``` -Basic Usage -=========== +# Basic Usage See the available commands: @@ -65,8 +60,7 @@ See the available commands: $ gitman --help ``` -Updating Dependencies ---------------------- +## Updating Dependencies Get the latest versions of all dependencies: @@ -89,8 +83,7 @@ where `rev` can be: * a branch: `master` * a `rev-parse` date: `'develop@{2015-06-18 10:30:59}'` -Restoring Previous Versions ---------------------------- +## Restoring Previous Versions Display the specific revisions that are currently installed: @@ -104,8 +97,7 @@ Reinstall these specific versions at a later time: $ gitman install ``` -Deleting Dependencies ---------------------- +## Deleting Dependencies Remove all installed dependencies: @@ -113,7 +105,6 @@ Remove all installed dependencies: $ gitman uninstall ``` -Advanced Options -================ +# Advanced Options See the full documentation at [git-dependency-manager.info](http://git-dependency-manager.info/interfaces/cli/). diff --git a/docs/index.md b/docs/index.md index 2644793d..1ab770c0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -46,8 +46,7 @@ Ignore the dependency storage location: $ echo vendor >> .gitignore ``` -Basic Usage ------------ +## Basic Usage Get all dependencies: diff --git a/gitman/__init__.py b/gitman/__init__.py index e6150f42..254b1bfc 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '0.8.3' +__version__ = '0.9.rc1' CLI = 'gitman' PLUGIN = 'deps' From b453983aed6e1cc56a687f087826bd8de05e9f15 Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Wed, 30 Mar 2016 21:39:51 -0400 Subject: [PATCH 19/20] Update YORM to 0.7.2 --- gitman/__init__.py | 2 +- gitman/models/source.py | 2 +- requirements.txt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gitman/__init__.py b/gitman/__init__.py index 254b1bfc..2c93cec6 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '0.9.rc1' +__version__ = '0.9.rc2' CLI = 'gitman' PLUGIN = 'deps' diff --git a/gitman/models/source.py b/gitman/models/source.py index 3af7152e..a2977fae 100644 --- a/gitman/models/source.py +++ b/gitman/models/source.py @@ -16,8 +16,8 @@ @yorm.attr(dir=String) -@yorm.attr(repo=String) @yorm.attr(link=String) +@yorm.attr(repo=String) @yorm.attr(rev=String) class Source(AttributeDictionary): """A dictionary of `git` and `ln` arguments.""" diff --git a/requirements.txt b/requirements.txt index 643bbf1c..2c849ef1 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ -YORM ~= 0.7.1 +YORM ~= 0.7.2 sh ~= 1.11 From 296752c1ff9ad242221e794c99ee4187bc9e5d9c Mon Sep 17 00:00:00 2001 From: Jace Browning Date: Thu, 31 Mar 2016 10:19:14 -0400 Subject: [PATCH 20/20] Bump version to 0.9 --- CHANGES.md | 2 +- gitman/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 08aea477..19fe3958 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,7 @@ Revision History ================ -0.9 (unreleased) +0.9 (2016/03/31) ---------------- - Added `edit` command to launch the configuration file. diff --git a/gitman/__init__.py b/gitman/__init__.py index 2c93cec6..ff453aaa 100644 --- a/gitman/__init__.py +++ b/gitman/__init__.py @@ -3,7 +3,7 @@ import sys __project__ = 'GitMan' -__version__ = '0.9.rc2' +__version__ = '0.9' CLI = 'gitman' PLUGIN = 'deps'