erepo: pull each time

Fixes #3062.
This commit is contained in:
Alexander Schepanovski
2020-01-23 23:09:57 +07:00
parent a0419173ba
commit 7168a9c50a
4 changed files with 58 additions and 59 deletions
+39 -9
View File
@@ -1,9 +1,11 @@
import logging
import os
import tempfile
from contextlib import contextmanager
from distutils.dir_util import copy_tree
import threading
from funcy import retry, suppress, memoize, cached_property
from funcy import retry, suppress, wrap_with, cached_property
from dvc.compat import fspath
from dvc.repo import Repo
@@ -16,6 +18,9 @@ from dvc.utils.fs import remove, fs_copy
from dvc.scm import SCM
logger = logging.getLogger(__name__)
@contextmanager
def external_repo(url, rev=None):
path = _cached_clone(url, rev)
@@ -40,8 +45,8 @@ def external_repo(url, rev=None):
def clean_repos():
# Outside code should not see cache while we are removing
repo_paths = list(_cached_clone.memory.values())
_cached_clone.memory.clear()
repo_paths = list(REPOS_CACHE.values())
REPOS_CACHE.clear()
for path in repo_paths:
_remove(path)
@@ -137,19 +142,28 @@ class ExternalGitRepo:
raise PathMissingError(path, self.url)
@memoize
REPOS_CACHE = {}
@wrap_with(threading.Lock())
def _cached_clone(url, rev):
"""Clone an external git repo to a temporary directory.
Returns the path to a local temporary directory with the specified
revision checked out.
"""
if (url, rev) in REPOS_CACHE:
path = REPOS_CACHE[url, rev]
_git_pull(path, rev)
return path
new_path = tempfile.mkdtemp("dvc-erepo")
if url in _cached_clone.memory:
if url in REPOS_CACHE:
# Copy and an existing clean clone
# This one unlike shutil.copytree() works with an existing dir
copy_tree(_cached_clone.memory[url], new_path)
copy_tree(REPOS_CACHE[url], new_path)
_git_pull(new_path, None)
else:
# Create a new clone
_clone_repo(url, new_path)
@@ -157,21 +171,37 @@ def _cached_clone(url, rev):
# Save clean clone dir so that we will have access to a default branch
clean_clone_path = tempfile.mkdtemp("dvc-erepo")
copy_tree(new_path, clean_clone_path)
_cached_clone.memory[url] = clean_clone_path
REPOS_CACHE[url] = clean_clone_path
# Check out the specified revision
if rev is not None:
_git_checkout(new_path, rev)
REPOS_CACHE[url, rev] = new_path
return new_path
def _git_checkout(repo_path, revision):
def _git_checkout(repo_path, rev):
from dvc.scm import Git
git = Git(repo_path)
try:
git.checkout(revision)
git.checkout(rev)
finally:
git.close()
def _git_pull(repo_path, rev):
import git
# Do not try to pull in a detached mode
if rev and git.Repo.re_hexsha_only.search(rev):
return
git = git.Repo(repo_path)
try:
msg = git.git.pull()
logger.debug("external repo: git pull: {}", msg)
finally:
git.close()
+13
View File
@@ -23,3 +23,16 @@ def test_external_repo(erepo_dir):
assert fd.read() == "branch"
assert mock.call_count == 1
def test_source_change(erepo_dir):
url = fspath(erepo_dir)
with external_repo(url) as repo:
old_rev = repo.scm.get_rev()
erepo_dir.scm_gen("file", "text", commit="a change")
with external_repo(url) as repo:
new_rev = repo.scm.get_rev()
assert old_rev != new_rev
+1 -14
View File
@@ -4,7 +4,6 @@ from mock import patch
from dvc.main import main
from dvc.compat import fspath
from dvc.external_repo import clean_repos
from tests.basic_env import TestDvc
@@ -33,19 +32,12 @@ def test_status_non_dvc_repo_import(tmp_dir, dvc, git_dir):
dvc.imp(fspath(git_dir), "file", "file", rev="branch")
status = dvc.status(["file.dvc"])
assert status == {}
# Caching in external repos doesn't see upstream updates within single
# cli call, so we need to clean the caches to see the changes.
clean_repos()
assert dvc.status(["file.dvc"]) == {}
with git_dir.branch("branch", new=False):
git_dir.scm_gen("file", "second version", commit="update file")
status, = dvc.status(["file.dvc"])["file.dvc"]
assert status == {
"changed deps": {"file ({})".format(git_dir): "update available"}
}
@@ -68,12 +60,7 @@ def test_status_before_and_after_dvc_init(tmp_dir, dvc, git_dir):
assert old_rev != new_rev
# Caching in external repos doesn't see upstream updates within single
# cli call, so we need to clean the caches to see the changes.
clean_repos()
status, = dvc.status(["file.dvc"])["file.dvc"]
assert status == {
"changed deps": {
"file ({})".format(fspath(git_dir)): "update available"
+5 -36
View File
@@ -3,7 +3,6 @@ import os
from dvc.stage import Stage
from dvc.compat import fspath
from dvc.external_repo import clean_repos
@pytest.mark.parametrize("cached", [True, False])
@@ -16,43 +15,21 @@ def test_update_import(tmp_dir, dvc, erepo_dir, cached):
stage = dvc.imp(fspath(erepo_dir), "version", "version", rev="branch")
imported = tmp_dir / "version"
assert imported.is_file()
assert imported.read_text() == "branch"
assert stage.deps[0].def_repo == {
"url": fspath(erepo_dir),
"rev": "branch",
"rev_lock": old_rev,
}
assert (tmp_dir / "version").read_text() == "branch"
assert stage.deps[0].def_repo["rev_lock"] == old_rev
# Update version file
with erepo_dir.branch("branch", new=False), erepo_dir.chdir():
gen("version", "updated", "update version content")
new_rev = erepo_dir.scm.get_rev()
assert old_rev != new_rev
# Caching in external repos doesn't see upstream updates within single
# cli call, so we need to clean the caches to see the changes.
clean_repos()
status, = dvc.status([stage.path])["version.dvc"]
changed_dep, = list(status["changed deps"].items())
assert changed_dep[0].startswith("version ")
assert changed_dep[1] == "update available"
dvc.update(stage.path)
assert dvc.status([stage.path]) == {}
assert imported.is_file()
assert imported.read_text() == "updated"
assert (tmp_dir / "version").read_text() == "updated"
stage = Stage.load(dvc, stage.path)
assert stage.deps[0].def_repo == {
"url": fspath(erepo_dir),
"rev": "branch",
"rev_lock": new_rev,
}
assert stage.deps[0].def_repo["rev_lock"] == new_rev
def test_update_import_after_remote_updates_to_dvc(tmp_dir, dvc, erepo_dir):
@@ -82,10 +59,6 @@ def test_update_import_after_remote_updates_to_dvc(tmp_dir, dvc, erepo_dir):
assert old_rev != new_rev
# Caching in external repos doesn't see upstream updates within single
# cli call, so we need to clean the caches to see the changes.
clean_repos()
status, = dvc.status([stage.path])["version.dvc"]
changed_dep, = list(status["changed deps"].items())
assert changed_dep[0].startswith("version ")
@@ -122,10 +95,6 @@ def test_update_before_and_after_dvc_init(tmp_dir, dvc, git_dir):
assert old_rev != new_rev
# Caching in external repos doesn't see upstream updates within single
# cli call, so we need to clean the caches to see the changes.
clean_repos()
assert dvc.status([stage.path]) == {
"file.dvc": [
{