From 32bb46f34f94f1706d6ac680284d4b1c5db69f38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Peter=20Rowlands=20=28=EB=B3=80=EA=B8=B0=ED=98=B8=29?= Date: Fri, 25 Sep 2020 00:36:51 +0900 Subject: [PATCH] experiments: support cloning parent in detached-head state (#4611) * experiments: account for cloning detached-head parent repo * add test for detached-head parent --- dvc/repo/experiments/__init__.py | 17 ++++++++++++- tests/func/experiments/test_experiments.py | 28 ++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/dvc/repo/experiments/__init__.py b/dvc/repo/experiments/__init__.py index 2a5e18f42..4822ca645 100644 --- a/dvc/repo/experiments/__init__.py +++ b/dvc/repo/experiments/__init__.py @@ -178,11 +178,26 @@ class Experiments: self.scm.checkout(rev) def _checkout_default_branch(self): + from git.refs.symbolic import SymbolicReference + # switch to default branch git_repo = self.scm.repo origin_refs = git_repo.remotes["origin"].refs - ref = origin_refs["HEAD"].reference + + # origin/HEAD will point to tip of the default branch unless we + # initially cloned a repo that was in a detached-HEAD state. + # + # If we are currently detached because we cloned a detached + # repo, we can't actually tell what branch should be considered + # default, so we just fall back to the first available reference. + if "HEAD" in origin_refs: + ref = origin_refs["HEAD"].reference + else: + ref = origin_refs[0] + if not isinstance(ref, SymbolicReference): + ref = ref.reference branch_name = ref.name.split("/")[-1] + if branch_name in git_repo.heads: branch = git_repo.heads[branch_name] else: diff --git a/tests/func/experiments/test_experiments.py b/tests/func/experiments/test_experiments.py index edff2f0aa..88a24086a 100644 --- a/tests/func/experiments/test_experiments.py +++ b/tests/func/experiments/test_experiments.py @@ -208,3 +208,31 @@ def test_extend_branch(tmp_dir, scm, dvc): dvc.experiments.checkout(exp_b) assert (tmp_dir / "params.yaml").read_text().strip() == "foo: 3" assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 3" + + +def test_detached_parent(tmp_dir, scm, dvc, mocker): + tmp_dir.gen("copy.py", COPY_SCRIPT) + tmp_dir.gen("params.yaml", "foo: 1") + stage = dvc.run( + cmd="python copy.py params.yaml metrics.yaml", + metrics_no_cache=["metrics.yaml"], + params=["foo"], + name="copy-file", + ) + scm.add(["dvc.yaml", "dvc.lock", "copy.py", "params.yaml", "metrics.yaml"]) + scm.commit("v1") + detached_rev = scm.get_rev() + + tmp_dir.gen("params.yaml", "foo: 2") + dvc.reproduce(stage.addressing) + scm.add(["dvc.yaml", "dvc.lock", "copy.py", "params.yaml", "metrics.yaml"]) + scm.commit("v2") + + scm.checkout(detached_rev) + assert scm.repo.head.is_detached + dvc.reproduce(stage.addressing, experiment=True, params=["foo=3"]) + + exp_rev = dvc.experiments.scm.get_rev() + assert dvc.experiments.get_baseline(exp_rev) == detached_rev + assert (tmp_dir / "params.yaml").read_text().strip() == "foo: 3" + assert (tmp_dir / "metrics.yaml").read_text().strip() == "foo: 3"