Repository: iterative/dvc

Issue:
Cyclic import in dvc.scm.git.tree
#1709 dvc.scm.git.tree imports dvc.repo.tree, and this introduced a cyclic import which fires when `dvc.scm` is imported before `dvc.repo`:

### To reproduce

```
> python -c "import dvc.scm"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
  File "dvc/scm/__init__.py", line 6, in <module>
    from dvc.scm.git import Git
  File "dvc/scm/git/__init__.py", line 15, in <module>
    from dvc.scm.git.tree import GitTree
  File "dvc/scm/git/tree.py", line 6, in <module>
    from dvc.repo.tree import BaseTree
  File "dvc/repo/__init__.py", line 10, in <module>
    class Repo(object):
  File "dvc/repo/__init__.py", line 15, in Repo
    from dvc.repo.add import add
  File "dvc/repo/add.py", line 3, in <module>
    from dvc.scm import scm_context
ImportError: cannot import name scm_context
```


Discussion:

Patch:
diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py
index ac55cb255..24ad3ea00 100644
--- a/dvc/repo/__init__.py
+++ b/dvc/repo/__init__.py
@@ -1,6 +1,7 @@
 from __future__ import unicode_literals
 
 import os
+
 import dvc.prompt as prompt
 import dvc.logger as logger
 
@@ -38,7 +39,7 @@ class Repo(object):
         from dvc.data_cloud import DataCloud
         from dvc.updater import Updater
         from dvc.repo.metrics import Metrics
-        from dvc.repo.tree import WorkingTree
+        from dvc.scm.tree import WorkingTree
 
         root_dir = self.find_root(root_dir)
 
diff --git a/dvc/repo/add.py b/dvc/repo/add.py
index 2a7182b75..af1234ff7 100644
--- a/dvc/repo/add.py
+++ b/dvc/repo/add.py
@@ -1,6 +1,6 @@
 import os
 
-from dvc.scm import scm_context
+from dvc.repo.scm_context import scm_context
 from dvc.stage import Stage
 from dvc.utils import walk_files
 from dvc.exceptions import RecursiveAddingWhileUsingFilename
diff --git a/dvc/repo/brancher.py b/dvc/repo/brancher.py
index 6a4cfca13..bc4f3e38a 100644
--- a/dvc/repo/brancher.py
+++ b/dvc/repo/brancher.py
@@ -25,7 +25,7 @@ def brancher(  # noqa: E302
     scm = self.scm
 
     if self.scm.is_dirty():
-        from dvc.repo.tree import WorkingTree
+        from dvc.scm.tree import WorkingTree
 
         self.tree = WorkingTree()
         yield "Working Tree"
diff --git a/dvc/repo/imp.py b/dvc/repo/imp.py
index fc4c2acdf..e317eb008 100644
--- a/dvc/repo/imp.py
+++ b/dvc/repo/imp.py
@@ -1,4 +1,4 @@
-from dvc.scm import scm_context
+from dvc.repo.scm_context import scm_context
 
 
 @scm_context
diff --git a/dvc/repo/move.py b/dvc/repo/move.py
index 360532ed9..9240880a7 100644
--- a/dvc/repo/move.py
+++ b/dvc/repo/move.py
@@ -1,7 +1,7 @@
 import os
 
 from dvc.exceptions import MoveNotDataSourceError
-from dvc.scm import scm_context
+from dvc.repo.scm_context import scm_context
 
 
 def _expand_target_path(from_path, to_path):
diff --git a/dvc/repo/reproduce.py b/dvc/repo/reproduce.py
index 762af2275..83b1f85e4 100644
--- a/dvc/repo/reproduce.py
+++ b/dvc/repo/reproduce.py
@@ -4,7 +4,7 @@ import os
 
 import dvc.logger as logger
 from dvc.exceptions import ReproductionError
-from dvc.scm import scm_context
+from dvc.repo.scm_context import scm_context
 
 
 def _reproduce_stage(stages, node, force, dry, interactive, no_commit):
diff --git a/dvc/repo/run.py b/dvc/repo/run.py
index 889310dfb..24cc8283d 100644
--- a/dvc/repo/run.py
+++ b/dvc/repo/run.py
@@ -1,6 +1,6 @@
 from __future__ import unicode_literals
 
-from dvc.scm import scm_context
+from dvc.repo.scm_context import scm_context
 
 
 @scm_context
diff --git a/dvc/repo/scm_context.py b/dvc/repo/scm_context.py
new file mode 100644
index 000000000..583a8b3f8
--- /dev/null
+++ b/dvc/repo/scm_context.py
@@ -0,0 +1,12 @@
+def scm_context(method):
+    def run(repo, *args, **kw):
+        try:
+            result = method(repo, *args, **kw)
+            repo.scm.reset_ignores()
+            repo.scm.remind_to_track()
+            return result
+        except Exception as e:
+            repo.scm.cleanup_ignores()
+            raise e
+
+    return run
diff --git a/dvc/scm/__init__.py b/dvc/scm/__init__.py
index c5f8d20d7..5a6347fb3 100644
--- a/dvc/scm/__init__.py
+++ b/dvc/scm/__init__.py
@@ -5,8 +5,6 @@ from __future__ import unicode_literals
 from dvc.scm.base import Base
 from dvc.scm.git import Git
 
-import dvc
-
 
 # just a sugar to point that this is an actual implementation for a dvc
 # project under no SCM control
@@ -29,19 +27,3 @@ def SCM(root_dir, repo=None):  # pylint: disable=invalid-name
         return Git(root_dir, repo=repo)
 
     return NoSCM(root_dir, repo=repo)
-
-
-def scm_context(method):
-    def run(*args, **kw):
-        repo = args[0]
-        assert isinstance(repo, dvc.repo.Repo)
-        try:
-            result = method(*args, **kw)
-            repo.scm.reset_ignores()
-            repo.scm.remind_to_track()
-            return result
-        except Exception as e:
-            repo.scm.cleanup_ignores()
-            raise e
-
-    return run
diff --git a/dvc/scm/git/tree.py b/dvc/scm/git/tree.py
index d9c56293e..d6aff9151 100644
--- a/dvc/scm/git/tree.py
+++ b/dvc/scm/git/tree.py
@@ -3,7 +3,7 @@ import os
 
 from dvc.utils.compat import StringIO
 
-from dvc.repo.tree import BaseTree
+from dvc.scm.tree import BaseTree
 
 
 # see git-fast-import(1)


Test patch:
diff --git a/dvc/repo/tree.py b/dvc/scm/tree.py
similarity index 100%
rename from dvc/repo/tree.py
rename to dvc/scm/tree.py
diff --git a/tests/test_repo.py b/tests/test_repo.py
index 0a99533af..aa3306301 100644
--- a/tests/test_repo.py
+++ b/tests/test_repo.py
@@ -1,7 +1,7 @@
 from tests.basic_env import TestDvc
 
 from dvc.scm.git import GitTree
-from dvc.repo.tree import WorkingTree
+from dvc.scm.tree import WorkingTree
 
 
 class TestCollect(TestDvc):
diff --git a/tests/test_tree.py b/tests/test_tree.py
index de81f8093..6f6ec1153 100644
--- a/tests/test_tree.py
+++ b/tests/test_tree.py
@@ -6,7 +6,7 @@ from os.path import join
 
 from dvc.scm import SCM
 from dvc.scm.git import GitTree
-from dvc.repo.tree import WorkingTree
+from dvc.scm.tree import WorkingTree
 
 from tests.basic_env import TestDir, TestGit
 
diff --git a/tests/unit/scm/test_scm.py b/tests/unit/scm/test_scm.py
index 4a7ede011..484c6f396 100644
--- a/tests/unit/scm/test_scm.py
+++ b/tests/unit/scm/test_scm.py
@@ -1,5 +1,6 @@
 from dvc.repo import Repo
-from dvc.scm import scm_context, NoSCM
+from dvc.repo.scm_context import scm_context
+from dvc.scm import NoSCM
 from unittest import TestCase
 import mock