diff --git a/dvc/repo/experiments/executor/base.py b/dvc/repo/experiments/executor/base.py index 2d1e670438..7f9a21f240 100644 --- a/dvc/repo/experiments/executor/base.py +++ b/dvc/repo/experiments/executor/base.py @@ -265,6 +265,7 @@ def save( force: bool = False, include_untracked: Optional[List[str]] = None, ) -> ExecutorResult: + from dvc.dvcfile import LOCK_FILE from dvc.repo import Repo exp_hash: Optional[str] = None @@ -283,6 +284,10 @@ def save( include_untracked.extend( dvc.index._plot_sources # pylint: disable=protected-access ) + # dvc repro automatically stages dvc.lock. Running redundant `git add` + # on it causes an error when exiting the detached head context. + if LOCK_FILE in dvc.scm.untracked_files(): + include_untracked.append(LOCK_FILE) try: stages = dvc.commit([], force=True, relink=False) diff --git a/tests/func/experiments/test_save.py b/tests/func/experiments/test_save.py index c77c1bc0d7..e06ca43b6e 100644 --- a/tests/func/experiments/test_save.py +++ b/tests/func/experiments/test_save.py @@ -134,5 +134,22 @@ def test_untracked_top_level_files_are_included_in_exp(tmp_dir, scm, dvc): dvc.reproduce(stage.addressing) exp = dvc.experiments.save() fs = scm.get_fs(exp) - for file in ["metrics.json", "params.yaml", "plots.csv"]: + for file in ["metrics.json", "params.yaml", "plots.csv", "dvc.lock"]: assert fs.exists(file) + + +def test_untracked_dvclock_is_included_in_exp(tmp_dir, scm, dvc): + stage = dvc.stage.add( + cmd="echo foo", + name="foo", + ) + scm.add_commit(["dvc.yaml"], message="add dvc.yaml") + dvc.reproduce(stage.addressing) + + # dvc.reproduce automatically stages `dvc.lock` + # force it to be untracked + scm.reset() + + exp = dvc.experiments.save() + fs = scm.get_fs(exp) + assert fs.exists("dvc.lock")