diff --git a/dvc/command/experiments.py b/dvc/command/experiments.py index ebe84c15d..b07c34051 100644 --- a/dvc/command/experiments.py +++ b/dvc/command/experiments.py @@ -710,12 +710,16 @@ def add_parser(subparsers, parent_parser): experiments_run_parser.add_argument( "--continue", type=str, + nargs="?", default=None, + const=":last", dest="checkpoint_continue", help=( "Continue from the specified checkpoint experiment " - "(implies --checkpoint)." + "(implies --checkpoint). If no experiment revision is provided, " + "the most recently run checkpoint experiment will be used." ), + metavar="", ) experiments_run_parser.add_argument( "--reset", diff --git a/dvc/repo/experiments/__init__.py b/dvc/repo/experiments/__init__.py index 0a455461e..e2468704c 100644 --- a/dvc/repo/experiments/__init__.py +++ b/dvc/repo/experiments/__init__.py @@ -426,13 +426,31 @@ class Experiments: workspace. """ if checkpoint_continue: - rev = self.scm.resolve_rev(checkpoint_continue) - branch = self._get_branch_containing(rev) - if not branch: - raise DvcException( - "Could not find checkpoint experiment " - f"'{checkpoint_continue}'" - ) + branch = None + if checkpoint_continue == ":last": + # Continue from most recently committed checkpoint + for head in sorted( + self.scm.repo.heads, + key=lambda h: h.commit.committed_date, + reverse=True, + ): + exp_branch = head.name + m = self.BRANCH_RE.match(exp_branch) + if m and m.group("checkpoint"): + branch = exp_branch + break + if not branch: + raise DvcException( + "No existing checkpoint experiment to continue" + ) + else: + rev = self.scm.resolve_rev(checkpoint_continue) + branch = self._get_branch_containing(rev) + if not branch: + raise DvcException( + "Could not find checkpoint experiment " + f"'{checkpoint_continue}'" + ) logger.debug( "Continuing checkpoint experiment '%s'", checkpoint_continue ) diff --git a/tests/func/experiments/test_experiments.py b/tests/func/experiments/test_experiments.py index f114dee9e..afe970481 100644 --- a/tests/func/experiments/test_experiments.py +++ b/tests/func/experiments/test_experiments.py @@ -301,7 +301,8 @@ def test_new_checkpoint(tmp_dir, scm, dvc, mocker): ).read_text().strip() == "foo: 2" -def test_continue_checkpoint(tmp_dir, scm, dvc, mocker): +@pytest.mark.parametrize("last", [True, False]) +def test_continue_checkpoint(tmp_dir, scm, dvc, mocker, last): tmp_dir.gen("checkpoint.py", CHECKPOINT_SCRIPT) tmp_dir.gen("params.yaml", "foo: 1") stage = dvc.run( @@ -326,7 +327,10 @@ def test_continue_checkpoint(tmp_dir, scm, dvc, mocker): results = dvc.experiments.run( stage.addressing, checkpoint=True, params=["foo=2"] ) - exp_rev = first(results) + if last: + exp_rev = ":last" + else: + exp_rev = first(results) dvc.experiments.run( stage.addressing, checkpoint=True, checkpoint_continue=exp_rev,