Repository: iterative/dvc

Issue:
get: handle non-DVC repositories
Even though #2977 solved this for `dvc import`, it looks like it is still not supported for `dvc get`. It might be a simple bug or code path is different. We need to fix and add tests for this.

**Version**:
```
VC version: 0.80.0+82cdce
Python version: 3.7.5
Platform: Darwin-18.2.0-x86_64-i386-64bit
Binary: False
Package: None
Cache: reflink - True, hardlink - True, symlink - True
Filesystem type (cache directory): ('apfs', '/dev/disk1s1')
Filesystem type (workspace): ('apfs', '/dev/disk1s1') 
```

**Reproduce:**

```bash
mkdir git-repo
cd git-repo
git init
touch test
git commit -a -m "add test"
cd ..
dvc get ./git-repo test
 ```

outputs:

`ERROR: failed to get 'test' from './git-repo' - URL './git-repo' is not a dvc repository.`


Discussion:
fabiosantoscode: DeepSource is complaining about some unused arguments in tests. I suppose I don't want to clean them up at this point.
efiop: > DeepSource is complaining about some unused arguments in tests. I suppose I shouldn't clean them up at this point.

@fabiosantoscode Yep, take it as a heads-up from DS. In this case there are no actions you should take. :slightly_smiling_face: 
fabiosantoscode: @efiop I believe I've addressed the comments.

WRT the `clone_dir` argument and the lost optimization, I removed the argument, and instead of changing the clone target, I changed the Repo's cache to `tmp_dir` which is next to the place the `get` will drop the file at.
fabiosantoscode: Cool @efiop, I've reverted the code to just clone into `tmp_dir` and addressed the remaining comments :+1: 
efiop: > WRT the clone_dir argument and the lost optimization, I removed the argument, and instead of changing the clone target, I changed the Repo's cache to tmp_dir which is next to the place the get will drop the file at.

Sorry, not sure I follow. I see that you still pass tmp_path to cached_clone , instead of keeping it as it was before and only changing the cache dir.
fabiosantoscode: @efiop I wrote that before you pointed out that it was enough to just clone the repo to `tmp_dir`, do disregard it.
fabiosantoscode: @jorgeorpinel thanks for your suggestions, it reads a lot clearer now.

As per fixing #3077, sure, let's do it :)
fabiosantoscode: @jorgeorpinel the docs for import are now updated. Check them out and see if they make sense to you.

@efiop your comments have all been addressed, I believe.

And @restyled-io has closed its PR, since the code style is on par. I ran `black dvc` locally just to confirm.
fabiosantoscode: I'll update the docs when this is confirmed to be the final help text.

Patch:
diff --git a/dvc/command/get.py b/dvc/command/get.py
index 9aba35c0d..22856f1c0 100644
--- a/dvc/command/get.py
+++ b/dvc/command/get.py
@@ -31,7 +31,9 @@ class CmdGet(CmdBaseNoRepo):
 
 
 def add_parser(subparsers, parent_parser):
-    GET_HELP = "Download/copy files or directories from DVC repository."
+    GET_HELP = (
+        "Download a file or directory from any DVC project or Git repository."
+    )
     get_parser = subparsers.add_parser(
         "get",
         parents=[parent_parser],
@@ -40,18 +42,17 @@ def add_parser(subparsers, parent_parser):
         formatter_class=argparse.RawDescriptionHelpFormatter,
     )
     get_parser.add_argument(
-        "url", help="URL of Git repository with DVC project to download from."
+        "url",
+        help="Location of DVC project or Git repository to download from",
     )
     get_parser.add_argument(
-        "path", help="Path to a file or directory within a DVC repository."
+        "path",
+        help="Path to a file or directory within the project or repository",
     )
     get_parser.add_argument(
-        "-o",
-        "--out",
-        nargs="?",
-        help="Destination path to copy/download files to.",
+        "-o", "--out", nargs="?", help="Destination path to download files to"
     )
     get_parser.add_argument(
-        "--rev", nargs="?", help="DVC repository git revision."
+        "--rev", nargs="?", help="Git revision (e.g. branch, tag, SHA)"
     )
     get_parser.set_defaults(func=CmdGet)
diff --git a/dvc/command/imp.py b/dvc/command/imp.py
index 9315f0374..2a8d515ff 100644
--- a/dvc/command/imp.py
+++ b/dvc/command/imp.py
@@ -30,7 +30,8 @@ class CmdImport(CmdBase):
 
 def add_parser(subparsers, parent_parser):
     IMPORT_HELP = (
-        "Download data from DVC repository and take it under DVC control."
+        "Download a file or directory from any DVC project or Git repository"
+        "and take it under DVC control."
     )
 
     import_parser = subparsers.add_parser(
@@ -41,15 +42,17 @@ def add_parser(subparsers, parent_parser):
         formatter_class=argparse.RawTextHelpFormatter,
     )
     import_parser.add_argument(
-        "url", help="URL of Git repository with DVC project to download from."
+        "url",
+        help="Location of DVC project or Git repository to download from",
     )
     import_parser.add_argument(
-        "path", help="Path to data within DVC repository."
+        "path",
+        help="Path to a file or directory within the project or repository",
     )
     import_parser.add_argument(
-        "-o", "--out", nargs="?", help="Destination path to put data to."
+        "-o", "--out", nargs="?", help="Destination path to download files to"
     )
     import_parser.add_argument(
-        "--rev", nargs="?", help="DVC repository git revision."
+        "--rev", nargs="?", help="Git revision (e.g. branch, tag, SHA)"
     )
     import_parser.set_defaults(func=CmdImport)
diff --git a/dvc/exceptions.py b/dvc/exceptions.py
index e48014e95..a551e35db 100644
--- a/dvc/exceptions.py
+++ b/dvc/exceptions.py
@@ -240,11 +240,6 @@ class DvcIgnoreInCollectedDirError(DvcException):
         )
 
 
-class UrlNotDvcRepoError(DvcException):
-    def __init__(self, url):
-        super().__init__("URL '{}' is not a dvc repository.".format(url))
-
-
 class GitHookAlreadyExistsError(DvcException):
     def __init__(self, hook_name):
         super().__init__(
diff --git a/dvc/repo/get.py b/dvc/repo/get.py
index 17607bbfa..3c417556c 100644
--- a/dvc/repo/get.py
+++ b/dvc/repo/get.py
@@ -7,10 +7,9 @@ from dvc.exceptions import (
     DvcException,
     NotDvcRepoError,
     OutputNotFoundError,
-    UrlNotDvcRepoError,
     PathMissingError,
 )
-from dvc.external_repo import external_repo
+from dvc.external_repo import external_repo, cached_clone
 from dvc.path_info import PathInfo
 from dvc.stage import Stage
 from dvc.utils import resolve_output
@@ -42,37 +41,47 @@ def get(url, path, out=None, rev=None):
     # and won't work with reflink/hardlink.
     dpath = os.path.dirname(os.path.abspath(out))
     tmp_dir = os.path.join(dpath, "." + str(shortuuid.uuid()))
+    repo_dir = None
     try:
-        with external_repo(cache_dir=tmp_dir, url=url, rev=rev) as repo:
-            # Try any links possible to avoid data duplication.
-            #
-            # Not using symlink, because we need to remove cache after we are
-            # done, and to make that work we would have to copy data over
-            # anyway before removing the cache, so we might just copy it
-            # right away.
-            #
-            # Also, we can't use theoretical "move" link type here, because
-            # the same cache file might be used a few times in a directory.
-            repo.cache.local.cache_types = ["reflink", "hardlink", "copy"]
-
-            try:
-                output = repo.find_out_by_relpath(path)
-            except OutputNotFoundError:
-                output = None
-
-            if output and output.use_cache:
-                _get_cached(repo, output, out)
-            else:
+        try:
+            with external_repo(cache_dir=tmp_dir, url=url, rev=rev) as repo:
+                # Try any links possible to avoid data duplication.
+                #
+                # Not using symlink, because we need to remove cache after we
+                # are done, and to make that work we would have to copy data
+                # over anyway before removing the cache, so we might just copy
+                # it right away.
+                #
+                # Also, we can't use theoretical "move" link type here, because
+                # the same cache file might be used a few times in a directory.
+                repo.cache.local.cache_types = ["reflink", "hardlink", "copy"]
+
+                try:
+                    output = repo.find_out_by_relpath(path)
+                except OutputNotFoundError:
+                    output = None
+
+                if output and output.use_cache:
+                    _get_cached(repo, output, out)
+                    return
+
                 # Either an uncached out with absolute path or a user error
-                if os.path.isabs(path):
-                    raise FileNotFoundError
 
-                fs_copy(os.path.join(repo.root_dir, path), out)
+                repo_dir = repo.root_dir
+
+        except NotDvcRepoError:
+            # Not a DVC repository, continue below and copy from git
+            pass
+
+        if os.path.isabs(path):
+            raise FileNotFoundError
+
+        if not repo_dir:
+            repo_dir = cached_clone(url, rev=rev)
 
+        fs_copy(os.path.join(repo_dir, path), out)
     except (OutputNotFoundError, FileNotFoundError):
         raise PathMissingError(path, url)
-    except NotDvcRepoError:
-        raise UrlNotDvcRepoError(url)
     finally:
         remove(tmp_dir)
 
diff --git a/dvc/scm/git/__init__.py b/dvc/scm/git/__init__.py
index 3872c8e72..7c432855e 100644
--- a/dvc/scm/git/__init__.py
+++ b/dvc/scm/git/__init__.py
@@ -229,8 +229,7 @@ class Git(Base):
         # it is equivalent to `bool(self.repo.git.ls_files(path))` by
         # functionality, but ls_files fails on unicode filenames
         path = relpath(path, self.root_dir)
-        # There are 4 stages, see BaseIndexEntry.stage
-        return any((path, i) in self.repo.index.entries for i in (0, 1, 2, 3))
+        return path in [i[0] for i in self.repo.index.entries]
 
     def is_dirty(self):
         return self.repo.is_dirty()


Test patch:
diff --git a/tests/func/test_get.py b/tests/func/test_get.py
index cb2d05076..cdbbe4bbd 100644
--- a/tests/func/test_get.py
+++ b/tests/func/test_get.py
@@ -5,7 +5,6 @@ import pytest
 
 from dvc.cache import Cache
 from dvc.config import Config
-from dvc.exceptions import UrlNotDvcRepoError
 from dvc.repo.get import GetDVCFileError, PathMissingError
 from dvc.repo import Repo
 from dvc.system import System
@@ -87,9 +86,10 @@ def test_get_repo_rev(tmp_dir, erepo_dir):
 def test_get_from_non_dvc_repo(tmp_dir, erepo_dir):
     erepo_dir.scm.repo.index.remove([erepo_dir.dvc.dvc_dir], r=True)
     erepo_dir.scm.commit("remove dvc")
+    erepo_dir.scm_gen({"some_file": "contents"}, commit="create file")
 
-    with pytest.raises(UrlNotDvcRepoError):
-        Repo.get(fspath(erepo_dir), "some_file.zip")
+    Repo.get(fspath(erepo_dir), "some_file", "file_imported")
+    assert (tmp_dir / "file_imported").read_text() == "contents"
 
 
 def test_get_a_dvc_file(tmp_dir, erepo_dir):
@@ -136,6 +136,14 @@ def test_absolute_file_outside_repo(tmp_dir, erepo_dir):
         Repo.get(fspath(erepo_dir), "/root/")
 
 
+def test_absolute_file_outside_git_repo(tmp_dir, erepo_dir):
+    erepo_dir.scm.repo.index.remove([erepo_dir.dvc.dvc_dir], r=True)
+    erepo_dir.scm.commit("remove dvc")
+
+    with pytest.raises(PathMissingError):
+        Repo.get(fspath(erepo_dir), "/root/")
+
+
 def test_unknown_path(tmp_dir, erepo_dir):
     with pytest.raises(PathMissingError):
         Repo.get(fspath(erepo_dir), "a_non_existing_file")
@@ -164,10 +172,6 @@ def test_get_from_non_dvc_master(tmp_dir, erepo_dir, caplog):
         erepo_dir.dvc.scm.repo.index.remove([".dvc"], r=True)
         erepo_dir.dvc.scm.commit("remove .dvc")
 
-    # sanity check
-    with pytest.raises(UrlNotDvcRepoError):
-        Repo.get(fspath(erepo_dir), "some_file")
-
     caplog.clear()
     dst = "file_imported"
     with caplog.at_level(logging.INFO, logger="dvc"):
diff --git a/tests/func/test_repro.py b/tests/func/test_repro.py
index 4d9645743..e7ea6e735 100644
--- a/tests/func/test_repro.py
+++ b/tests/func/test_repro.py
@@ -2,10 +2,10 @@ import filecmp
 import getpass
 import os
 import posixpath
+import pathlib
 import re
 import shutil
 import uuid
-from pathlib import Path
 from subprocess import PIPE
 from subprocess import Popen
 from urllib.parse import urljoin
@@ -17,7 +17,6 @@ from flaky.flaky_decorator import flaky
 from google.cloud import storage as gc
 from mock import patch
 
-from dvc.compat import fspath
 from dvc.exceptions import CyclicGraphError
 from dvc.exceptions import ReproductionError
 from dvc.exceptions import StagePathAsOutputError
@@ -1373,190 +1372,210 @@ class TestShouldDisplayMetricsOnReproWithMetricsOption(TestDvc):
 
 
 @pytest.fixture
-def repro_dir(tmp_dir, dvc, run_copy):
-    # Creates repo with following structure:
-    #    data_dir/dir_file              origin_data
-    #         |       |                   |
-    #         |       |              origin_copy.dvc
-    # unrelated2.dvc  |               |       |
-    #                 |               |    unrelated1.dvc
-    #    dir/subdir/dir_file_copy.dvc |
-    #                  |              |
-    #                  |        dir/origin_copy_2.dvc
-    #                  |            |
-    #                   \          /
-    #                    \        /
-    #                   dir/Dvcfile
-    tmp_dir.gen(
-        {
-            "origin_data": "origin data content",
-            "data_dir": {"dir_file": "dir file content"},
-            "dir": {"subdir": {}},
-        }
+def repro_dir(dvc_repo, repo_dir):
+    repo_dir.dname = "dir"
+    os.mkdir(repo_dir.dname)
+    repo_dir.emptydname = "emptydir"
+    os.mkdir(repo_dir.emptydname)
+    subdname = os.path.join(repo_dir.dname, "subdir")
+    os.mkdir(subdname)
+
+    repo_dir.source = "source"
+    repo_dir.source_stage = repo_dir.source + ".dvc"
+    stage = dvc_repo.run(
+        fname=repo_dir.source_stage,
+        outs=[repo_dir.source],
+        deps=[repo_dir.FOO],
     )
-
-    stages = {}
-
-    origin_copy = tmp_dir / "origin_copy"
-    stage = run_copy("origin_data", fspath(origin_copy))
     assert stage is not None
-    assert origin_copy.read_text() == "origin data content"
-    stages["origin_copy"] = stage
-
-    origin_copy_2 = tmp_dir / "dir" / "origin_copy_2"
-    stage = run_copy(
-        fspath(origin_copy),
-        fspath(origin_copy_2),
-        fname=fspath(origin_copy_2) + ".dvc",
+    assert filecmp.cmp(repo_dir.source, repo_dir.FOO, shallow=False)
+
+    repo_dir.unrelated1 = "unrelated1"
+    repo_dir.unrelated1_stage = repo_dir.unrelated1 + ".dvc"
+    stage = dvc_repo.run(
+        fname=repo_dir.unrelated1_stage,
+        outs=[repo_dir.unrelated1],
+        deps=[repo_dir.source],
     )
     assert stage is not None
-    assert origin_copy_2.read_text() == "origin data content"
-    stages["origin_copy_2"] = stage
-
-    dir_file_path = tmp_dir / "data_dir" / "dir_file"
-    dir_file_copy = tmp_dir / "dir" / "subdir" / "dir_file_copy"
-    stage = run_copy(
-        fspath(dir_file_path),
-        fspath(dir_file_copy),
-        fname=fspath(dir_file_copy) + ".dvc",
-    )
-    assert stage is not None
-    assert dir_file_copy.read_text() == "dir file content"
-    stages["dir_file_copy"] = stage
 
-    last_stage = tmp_dir / "dir" / "Dvcfile"
-    stage = dvc.run(
-        fname=fspath(last_stage),
-        deps=[fspath(origin_copy_2), fspath(dir_file_copy)],
+    repo_dir.unrelated2 = "unrelated2"
+    repo_dir.unrelated2_stage = repo_dir.unrelated2 + ".dvc"
+    stage = dvc_repo.run(
+        fname=repo_dir.unrelated2_stage,
+        outs=[repo_dir.unrelated2],
+        deps=[repo_dir.DATA],
     )
     assert stage is not None
-    stages["last_stage"] = stage
-
-    # Unrelated are to verify that reproducing `dir` will not trigger them too
-    assert run_copy(fspath(origin_copy), "unrelated1") is not None
-    assert run_copy(fspath(dir_file_path), "unrelated2") is not None
-
-    yield stages
 
+    repo_dir.first = os.path.join(repo_dir.dname, "first")
+    repo_dir.first_stage = repo_dir.first + ".dvc"
 
-def _rewrite_file(path_elements, new_content):
-    if isinstance(path_elements, str):
-        path_elements = [path_elements]
-    file = Path(os.sep.join(path_elements))
-    file.unlink()
-    file.write_text(new_content)
+    stage = dvc_repo.run(
+        fname=repo_dir.first_stage,
+        deps=[repo_dir.source],
+        outs=[repo_dir.first],
+        cmd="python {} {} {}".format(
+            repo_dir.CODE, repo_dir.source, repo_dir.first
+        ),
+    )
+    assert stage is not None
+    assert filecmp.cmp(repo_dir.first, repo_dir.FOO, shallow=False)
+
+    repo_dir.second = os.path.join(subdname, "second")
+    repo_dir.second_stage = repo_dir.second + ".dvc"
+    stage = dvc_repo.run(
+        fname=repo_dir.second_stage,
+        outs=[repo_dir.second],
+        deps=[repo_dir.DATA],
+    )
+    assert stage is not None
+    assert filecmp.cmp(repo_dir.second, repo_dir.DATA, shallow=False)
 
+    repo_dir.third_stage = os.path.join(repo_dir.dname, "Dvcfile")
+    stage = dvc_repo.run(
+        fname=repo_dir.third_stage, deps=[repo_dir.first, repo_dir.second]
+    )
+    assert stage is not None
 
-def _read_out(stage):
-    return Path(stage.outs[0].fspath).read_text()
+    yield repo_dir
 
 
-def test_recursive_repro_default(dvc, repro_dir):
+def test_recursive_repro_default(dvc_repo, repro_dir):
     """
     Test recursive repro on dir after a dep outside this dir has changed.
     """
-    _rewrite_file("origin_data", "new origin data content")
+    os.unlink(repro_dir.FOO)
+    shutil.copyfile(repro_dir.BAR, repro_dir.FOO)
 
-    stages = dvc.reproduce("dir", recursive=True)
-
-    # Check that the dependency ("origin_copy") and the dependent stages
-    # inside the folder have been reproduced ("origin_copy_2", "last_stage")
-    assert stages == [
-        repro_dir["origin_copy"],
-        repro_dir["origin_copy_2"],
-        repro_dir["last_stage"],
-    ]
-    assert _read_out(repro_dir["origin_copy"]) == "new origin data content"
-    assert _read_out(repro_dir["origin_copy_2"]) == "new origin data content"
+    stages = dvc_repo.reproduce(repro_dir.dname, recursive=True)
+    # Check that the dependency ("source") and the dependent stages
+    # inside the folder have been reproduced ("first", "third")
+    assert len(stages) == 3
+    names = [stage.relpath for stage in stages]
+    assert repro_dir.source_stage in names
+    assert repro_dir.first_stage in names
+    assert repro_dir.third_stage in names
+    assert filecmp.cmp(repro_dir.source, repro_dir.BAR, shallow=False)
+    assert filecmp.cmp(repro_dir.first, repro_dir.BAR, shallow=False)
 
 
-def test_recursive_repro_single(dvc, repro_dir):
+def test_recursive_repro_single(dvc_repo, repro_dir):
     """
     Test recursive single-item repro on dir
     after a dep outside this dir has changed.
     """
-    _rewrite_file("origin_data", "new origin content")
-    _rewrite_file(["data_dir", "dir_file"], "new dir file content")
+    os.unlink(repro_dir.FOO)
+    shutil.copyfile(repro_dir.BAR, repro_dir.FOO)
 
-    stages = dvc.reproduce("dir", recursive=True, single_item=True)
+    os.unlink(repro_dir.DATA)
+    shutil.copyfile(repro_dir.BAR, repro_dir.DATA)
+
+    stages = dvc_repo.reproduce(
+        repro_dir.dname, recursive=True, single_item=True
+    )
     # Check that just stages inside given dir
     # with changed direct deps have been reproduced.
-    # This means that "origin_copy_2" stage should not be reproduced
-    # since it depends on "origin_copy".
-    # Also check that "dir_file_copy" stage was reproduced before "last_stage"
-    assert stages == [repro_dir["dir_file_copy"], repro_dir["last_stage"]]
-    assert _read_out(repro_dir["dir_file_copy"]) == "new dir file content"
+    # This means that "first" stage should not be reproduced
+    # since it depends on "source".
+    # Also check that "second" stage was reproduced before "third" stage
+    assert len(stages) == 2
+    assert repro_dir.second_stage == stages[0].relpath
+    assert repro_dir.third_stage == stages[1].relpath
+    assert filecmp.cmp(repro_dir.second, repro_dir.BAR, shallow=False)
 
 
-def test_recursive_repro_single_force(dvc, repro_dir):
+def test_recursive_repro_single_force(dvc_repo, repro_dir):
     """
     Test recursive single-item force repro on dir
     without any dependencies changing.
     """
-    stages = dvc.reproduce("dir", recursive=True, single_item=True, force=True)
-    # Check that all stages inside given dir have been reproduced
-    # Also check that "dir_file_copy" stage was reproduced before "last_stage"
-    # and that "origin_copy" stage was reproduced before "last_stage" stage
+    stages = dvc_repo.reproduce(
+        repro_dir.dname, recursive=True, single_item=True, force=True
+    )
     assert len(stages) == 3
-    assert set(stages) == {
-        repro_dir["origin_copy_2"],
-        repro_dir["dir_file_copy"],
-        repro_dir["last_stage"],
-    }
-    assert stages.index(repro_dir["origin_copy_2"]) < stages.index(
-        repro_dir["last_stage"]
+    names = [stage.relpath for stage in stages]
+    # Check that all stages inside given dir have been reproduced
+    # Also check that "second" stage was reproduced before "third" stage
+    # and that "first" stage was reproduced before "third" stage
+    assert repro_dir.first_stage in names
+    assert repro_dir.second_stage in names
+    assert repro_dir.third_stage in names
+    assert names.index(repro_dir.first_stage) < names.index(
+        repro_dir.third_stage
     )
-    assert stages.index(repro_dir["dir_file_copy"]) < stages.index(
-        repro_dir["last_stage"]
+    assert names.index(repro_dir.second_stage) < names.index(
+        repro_dir.third_stage
     )
 
 
-def test_recursive_repro_empty_dir(tmp_dir, dvc):
+def test_recursive_repro_empty_dir(dvc_repo, repro_dir):
     """
     Test recursive repro on an empty directory
     """
-    (tmp_dir / "emptydir").mkdir()
-
-    stages = dvc.reproduce("emptydir", recursive=True, force=True)
-    assert stages == []
+    stages = dvc_repo.reproduce(
+        repro_dir.emptydname, recursive=True, force=True
+    )
+    assert len(stages) == 0
 
 
-def test_recursive_repro_recursive_missing_file(dvc):
+def test_recursive_repro_recursive_missing_file(dvc_repo):
     """
     Test recursive repro on a missing file
     """
     with pytest.raises(StageFileDoesNotExistError):
-        dvc.reproduce("notExistingStage.dvc", recursive=True)
+        dvc_repo.reproduce("notExistingStage.dvc", recursive=True)
     with pytest.raises(StageFileDoesNotExistError):
-        dvc.reproduce("notExistingDir/", recursive=True)
+        dvc_repo.reproduce("notExistingDir/", recursive=True)
 
 
-def test_recursive_repro_on_stage_file(dvc, repro_dir):
+def test_recursive_repro_on_stage_file(dvc_repo, repro_dir):
     """
     Test recursive repro on a stage file instead of directory
     """
-    stages = dvc.reproduce(
-        repro_dir["origin_copy_2"].relpath, recursive=True, force=True
+    stages = dvc_repo.reproduce(
+        repro_dir.first_stage, recursive=True, force=True
     )
-    assert stages == [repro_dir["origin_copy"], repro_dir["origin_copy_2"]]
+    assert len(stages) == 2
+    names = [stage.relpath for stage in stages]
+    assert repro_dir.source_stage in names
+    assert repro_dir.first_stage in names
 
 
-def test_dvc_formatting_retained(tmp_dir, dvc, run_copy):
-    tmp_dir.dvc_gen("foo", "foo content")
-    stage = run_copy("foo", "foo_copy", fname="foo_copy.dvc")
-    stage_path = tmp_dir / stage.relpath
+@pytest.fixture
+def foo_copy(repo_dir, dvc_repo):
+    stages = dvc_repo.add(repo_dir.FOO)
+    assert len(stages) == 1
+    foo_stage = stages[0]
+    assert foo_stage is not None
+
+    fname = "foo_copy"
+    stage_fname = fname + ".dvc"
+    dvc_repo.run(
+        fname=stage_fname,
+        outs=[fname],
+        deps=[repo_dir.FOO, repo_dir.CODE],
+        cmd="python {} {} {}".format(repo_dir.CODE, repo_dir.FOO, fname),
+    )
+    return {"fname": fname, "stage_fname": stage_fname}
+
+
+def test_dvc_formatting_retained(dvc_repo, foo_copy):
+    root = pathlib.Path(dvc_repo.root_dir)
+    stage_file = root / foo_copy["stage_fname"]
 
     # Add comments and custom formatting to DVC-file
-    lines = list(map(_format_dvc_line, stage_path.read_text().splitlines()))
+    lines = list(map(_format_dvc_line, stage_file.read_text().splitlines()))
     lines.insert(0, "# Starting comment")
     stage_text = "".join(l + "\n" for l in lines)
-    stage_path.write_text(stage_text)
+    stage_file.write_text(stage_text)
 
     # Rewrite data source and repro
-    (tmp_dir / "foo").write_text("new foo")
-    dvc.reproduce("foo_copy.dvc", force=True)
+    (root / "foo").write_text("new_foo")
+    dvc_repo.reproduce(foo_copy["stage_fname"])
 
-    assert _hide_md5(stage_text) == _hide_md5(stage_path.read_text())
+    # All differences should be only about md5
+    assert _hide_md5(stage_text) == _hide_md5(stage_file.read_text())
 
 
 def _format_dvc_line(line):
@@ -1611,12 +1630,10 @@ class TestReproDownstream(TestDvc):
         assert evaluation[2].relpath == "E.dvc"
 
 
-def test_ssh_dir_out(tmp_dir, dvc):
+def test_ssh_dir_out(dvc_repo):
     if not _should_test_ssh():
         pytest.skip()
 
-    tmp_dir.gen({"foo": "foo content"})
-
     # Set up remote and cache
     remote_url = get_ssh_url()
     assert main(["remote", "add", "upstream", remote_url]) == 0
@@ -1626,7 +1643,7 @@ def test_ssh_dir_out(tmp_dir, dvc):
     assert main(["config", "cache.ssh", "sshcache"]) == 0
 
     # Recreating to reread configs
-    repo = DvcRepo(dvc.root_dir)
+    repo = DvcRepo(dvc_repo.root_dir)
 
     url_info = URLInfo(remote_url)
     mkdir_cmd = "mkdir dir-out;cd dir-out;echo 1 > 1.txt; echo 2 > 2.txt"