diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py index 176bda17b..3831d695c 100644 --- a/dvc/repo/__init__.py +++ b/dvc/repo/__init__.py @@ -62,8 +62,6 @@ class Repo(object): self.metrics = Metrics(self) - self.files_to_git_add = [] - self._ignore() self.updater.check() @@ -92,17 +90,6 @@ class Repo(object): root_dir = Repo.find_root(root) return os.path.join(root_dir, Repo.DVC_DIR) - def remind_to_git_add(self): - if not self.files_to_git_add: - return - - logger.info( - "\n" - "To track the changes with git run:\n" - "\n" - "\tgit add {files}".format(files=" ".join(self.files_to_git_add)) - ) - @staticmethod def init(root_dir=os.curdir, no_scm=False, force=False): from dvc.repo.init import init diff --git a/dvc/repo/add.py b/dvc/repo/add.py index be91a9b45..27caffc27 100644 --- a/dvc/repo/add.py +++ b/dvc/repo/add.py @@ -5,6 +5,7 @@ from dvc.stage import Stage from dvc.utils import walk_files from dvc.exceptions import RecursiveAddingWhileUsingFilename + @scm_context def add(repo, target, recursive=False, no_commit=False, fname=None): if recursive and fname: diff --git a/dvc/repo/imp.py b/dvc/repo/imp.py index 39e1e00f3..fc4c2acdf 100644 --- a/dvc/repo/imp.py +++ b/dvc/repo/imp.py @@ -1,3 +1,7 @@ +from dvc.scm import scm_context + + +@scm_context def imp(self, url, out, resume=False): from dvc.stage import Stage @@ -9,13 +13,9 @@ def imp(self, url, out, resume=False): self.check_dag(self.stages() + [stage]) - self.files_to_git_add = [] - with self.state: stage.run(resume=resume) stage.dump() - self.remind_to_git_add() - return stage diff --git a/dvc/repo/move.py b/dvc/repo/move.py index c186fbc1c..360532ed9 100644 --- a/dvc/repo/move.py +++ b/dvc/repo/move.py @@ -1,6 +1,7 @@ import os from dvc.exceptions import MoveNotDataSourceError +from dvc.scm import scm_context def _expand_target_path(from_path, to_path): @@ -9,6 +10,7 @@ def _expand_target_path(from_path, to_path): return to_path +@scm_context def move(self, from_path, to_path): """ Renames an output file and modifies the stage associated @@ -64,5 +66,3 @@ def move(self, from_path, to_path): out.move(to_out) stage.dump() - - self.remind_to_git_add() diff --git a/dvc/repo/reproduce.py b/dvc/repo/reproduce.py index 0bc6d8656..762af2275 100644 --- a/dvc/repo/reproduce.py +++ b/dvc/repo/reproduce.py @@ -4,6 +4,7 @@ import os import dvc.logger as logger from dvc.exceptions import ReproductionError +from dvc.scm import scm_context def _reproduce_stage(stages, node, force, dry, interactive, no_commit): @@ -27,6 +28,7 @@ def _reproduce_stage(stages, node, force, dry, interactive, no_commit): return [stage] +@scm_context def reproduce( self, target=None, @@ -65,8 +67,6 @@ def reproduce( else: targets.append(target) - self.files_to_git_add = [] - ret = [] with self.state: for target in targets: @@ -82,8 +82,6 @@ def reproduce( ) ret.extend(stages) - self.remind_to_git_add() - return ret diff --git a/dvc/repo/run.py b/dvc/repo/run.py index d2bfffc96..889310dfb 100644 --- a/dvc/repo/run.py +++ b/dvc/repo/run.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals +from dvc.scm import scm_context + +@scm_context def run( self, cmd=None, @@ -53,13 +56,10 @@ def run( self.check_dag(self.stages() + [stage]) - self.files_to_git_add = [] with self.state: if not no_exec: stage.run(no_commit=no_commit) stage.dump() - self.remind_to_git_add() - return stage diff --git a/dvc/scm/__init__.py b/dvc/scm/__init__.py index 2ae851edb..e3f2f4b63 100644 --- a/dvc/scm/__init__.py +++ b/dvc/scm/__init__.py @@ -28,15 +28,14 @@ def SCM(root_dir, repo=None): # pylint: disable=invalid-name def scm_context(method): def run(*args, **kw): repo = args[0] - assert type(repo) is dvc.repo.Repo - scm = repo.scm + assert isinstance(repo, dvc.repo.Repo) try: result = method(*args, **kw) - scm.reset_ignores() - repo.remind_to_git_add() + repo.scm.reset_ignores() + repo.scm.remind_to_track() return result except Exception as e: - scm.cleanup_ignores() + repo.scm.cleanup_ignores() raise e return run diff --git a/dvc/scm/base.py b/dvc/scm/base.py index fba7b2711..07611853a 100644 --- a/dvc/scm/base.py +++ b/dvc/scm/base.py @@ -151,3 +151,14 @@ class Base(object): """ Method to reset in-memory ignore storing mechanism. """ + + def remind_to_track(self): + """ + Method to remind user to track newly created files handled by scm + """ + + def track_file(self, path): + """ + Method to add file to mechanism that will remind user + to track new files + """ diff --git a/dvc/scm/git.py b/dvc/scm/git.py index 96d30dff8..b2bb76cca 100644 --- a/dvc/scm/git.py +++ b/dvc/scm/git.py @@ -40,6 +40,7 @@ class Git(Base): self.git.git.update_environment(LD_LIBRARY_PATH=libpath) self.ignored_paths = [] + self.files_to_track = [] @staticmethod def is_repo(root_dir): @@ -97,8 +98,7 @@ class Git(Base): with open(gitignore, "a") as fobj: fobj.write(content) - if self.repo is not None: - self.repo.files_to_git_add.append(os.path.relpath(gitignore)) + self.track_file(os.path.relpath(gitignore)) self.ignored_paths.append(path) @@ -116,8 +116,7 @@ class Git(Base): with open(gitignore, "w") as fobj: fobj.writelines(filtered) - if self.repo is not None: - self.repo.files_to_git_add.append(os.path.relpath(gitignore)) + self.track_file(os.path.relpath(gitignore)) def add(self, paths): # NOTE: GitPython is not currently able to handle index version >= 3. @@ -185,3 +184,17 @@ class Git(Base): def reset_ignores(self): self.ignored_paths = [] + + def remind_to_track(self): + if not self.files_to_track: + return + + logger.info( + "\n" + "To track the changes with git run:\n" + "\n" + "\tgit add {files}".format(files=" ".join(self.files_to_track)) + ) + + def track_file(self, path): + self.files_to_track.append(path) diff --git a/dvc/stage.py b/dvc/stage.py index ef2f7c8dc..4b428e103 100644 --- a/dvc/stage.py +++ b/dvc/stage.py @@ -552,7 +552,7 @@ class Stage(object): with open(fname, "w") as fd: yaml.safe_dump(d, fd, default_flow_style=False) - self.repo.files_to_git_add.append(os.path.relpath(fname)) + self.repo.scm.track_file(os.path.relpath(fname)) def _compute_md5(self): from dvc.output.local import OutputLOCAL diff --git a/tests/unit/scm/__init__.py b/tests/unit/scm/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/unit/scm/scm.py b/tests/unit/scm/scm.py new file mode 100644 index 000000000..40061bd3a --- /dev/null +++ b/tests/unit/scm/scm.py @@ -0,0 +1,36 @@ +from dvc.repo import Repo +from dvc.scm import scm_context, Base +from unittest import TestCase +import mock + + +class TestScmContext(TestCase): + def setUp(self): + self.repo_mock = mock.Mock(spec=Repo) + self.scm_mock = mock.Mock(spec=Base) + self.repo_mock.scm = self.scm_mock + + def test_should_successfully_perform_method(self): + method = mock.Mock() + wrapped = scm_context(method) + + wrapped(self.repo_mock) + + self.assertEqual(1, method.call_count) + self.assertEqual(1, self.scm_mock.reset_ignores.call_count) + self.assertEqual(1, self.scm_mock.remind_to_track.call_count) + + self.assertEqual(0, self.scm_mock.cleanup_ignores.call_count) + + def test_should_throw_and_cleanup(self): + method = mock.Mock(side_effect=Exception("some problem")) + wrapped = scm_context(method) + + with self.assertRaises(Exception): + wrapped(self.repo_mock) + + self.assertEqual(1, method.call_count) + self.assertEqual(1, self.scm_mock.cleanup_ignores.call_count) + + self.assertEqual(0, self.scm_mock.reset_ignores.call_count) + self.assertEqual(0, self.scm_mock.remind_to_track.call_count)