diff --git a/dvc/repo/experiments/exceptions.py b/dvc/repo/experiments/exceptions.py index 4f38f14c4..49856f99d 100644 --- a/dvc/repo/experiments/exceptions.py +++ b/dvc/repo/experiments/exceptions.py @@ -6,12 +6,6 @@ if TYPE_CHECKING: from .refs import ExpRefInfo -class UnchangedExperimentError(DvcException): - def __init__(self, rev): - super().__init__(f"Experiment unchanged from '{rev[:7]}'.") - self.rev = rev - - class BaselineMismatchError(DvcException): def __init__(self, rev, expected): if hasattr(rev, "hexsha"): diff --git a/dvc/repo/experiments/executor/base.py b/dvc/repo/experiments/executor/base.py index bb2b2b0e3..8d519f2b6 100644 --- a/dvc/repo/experiments/executor/base.py +++ b/dvc/repo/experiments/executor/base.py @@ -30,11 +30,7 @@ from dvc.ui import ui from dvc.utils import dict_sha256, env2bool, relpath from dvc.utils.fs import remove -from ..exceptions import ( - CheckpointExistsError, - ExperimentExistsError, - UnchangedExperimentError, -) +from ..exceptions import CheckpointExistsError, ExperimentExistsError from ..refs import EXEC_BASELINE, EXEC_BRANCH, EXEC_CHECKPOINT, ExpRefInfo if TYPE_CHECKING: @@ -522,24 +518,21 @@ class BaseExecutor(ABC): git_remote, repro_force, ) -> Tuple[Optional[str], Optional["ExpRefInfo"], bool]: - try: - is_checkpoint = any(stage.is_checkpoint for stage in stages) - if is_checkpoint and checkpoint_reset: - # For reset checkpoint stages, we need to force - # overwriting existing checkpoint refs even though - # repro may not have actually been run with --force - repro_force = True - cls.commit( - dvc.scm, - exp_hash, - exp_name=info.name, - force=repro_force, - checkpoint=is_checkpoint, - ) - if auto_push: - cls._auto_push(dvc, dvc.scm, git_remote) - except UnchangedExperimentError: - pass + is_checkpoint = any(stage.is_checkpoint for stage in stages) + if is_checkpoint and checkpoint_reset: + # For reset checkpoint stages, we need to force + # overwriting existing checkpoint refs even though + # repro may not have actually been run with --force + repro_force = True + cls.commit( + dvc.scm, + exp_hash, + exp_name=info.name, + force=repro_force, + checkpoint=is_checkpoint, + ) + if auto_push: + cls._auto_push(dvc, dvc.scm, git_remote) ref: Optional[str] = dvc.scm.get_ref(EXEC_BRANCH, follow=False) exp_ref: Optional["ExpRefInfo"] = ( ExpRefInfo.from_ref(ref) if ref else None @@ -650,18 +643,15 @@ class BaseExecutor(ABC): unchanged: Iterable["PipelineStage"], stages: Iterable["PipelineStage"], ): - try: - exp_hash = cls.hash_exp(list(stages) + list(unchanged)) - exp_rev = cls.commit( - scm, exp_hash, exp_name=name, force=force, checkpoint=True - ) + exp_hash = cls.hash_exp(list(stages) + list(unchanged)) + exp_rev = cls.commit( + scm, exp_hash, exp_name=name, force=force, checkpoint=True + ) - if env2bool(DVC_EXP_AUTO_PUSH): - git_remote = os.getenv(DVC_EXP_GIT_REMOTE) - cls._auto_push(dvc, scm, git_remote) - ui.write(f"Checkpoint experiment iteration '{exp_rev[:7]}'.") - except UnchangedExperimentError: - pass + if env2bool(DVC_EXP_AUTO_PUSH): + git_remote = os.getenv(DVC_EXP_GIT_REMOTE) + cls._auto_push(dvc, scm, git_remote) + ui.write(f"Checkpoint experiment iteration '{exp_rev[:7]}'.") @classmethod def commit( @@ -676,7 +666,6 @@ class BaseExecutor(ABC): rev = scm.get_rev() if not scm.is_dirty(untracked_files=False): logger.debug("No changes to commit") - raise UnchangedExperimentError(rev) check_conflict = False branch = scm.get_ref(EXEC_BRANCH, follow=False) diff --git a/dvc/repo/experiments/save.py b/dvc/repo/experiments/save.py index e8114c550..1e3f17ff3 100644 --- a/dvc/repo/experiments/save.py +++ b/dvc/repo/experiments/save.py @@ -2,7 +2,7 @@ import logging import os from typing import TYPE_CHECKING, List, Optional -from .exceptions import ExperimentExistsError, UnchangedExperimentError +from .exceptions import ExperimentExistsError from .refs import ExpRefInfo from .utils import check_ref_format, get_random_exp_name @@ -70,9 +70,6 @@ def save( try: if workspace is not None: repo.scm.stash.apply(workspace) - else: - if not (include_untracked or force): - raise UnchangedExperimentError(orig_head) exp_rev = _save_experiment( repo, orig_head, force, name, include_untracked diff --git a/tests/func/experiments/test_experiments.py b/tests/func/experiments/test_experiments.py index 2f0568e15..4b2a05ee2 100644 --- a/tests/func/experiments/test_experiments.py +++ b/tests/func/experiments/test_experiments.py @@ -731,3 +731,10 @@ def test_run_env(tmp_dir, dvc, scm, mocker): dvc.experiments.run(name="foo") assert (tmp_dir / "DVC_EXP_BASELINE_REV").read_text().strip() == baseline assert (tmp_dir / "DVC_EXP_NAME").read_text().strip() == "foo" + + +def test_experiment_unchanged(tmp_dir, scm, dvc, exp_stage): + dvc.experiments.run(exp_stage.addressing) + dvc.experiments.run(exp_stage.addressing) + + assert len(dvc.experiments.ls()["master"]) == 2 diff --git a/tests/func/experiments/test_save.py b/tests/func/experiments/test_save.py index 95f0375ed..1f43b85d6 100644 --- a/tests/func/experiments/test_save.py +++ b/tests/func/experiments/test_save.py @@ -4,7 +4,6 @@ from funcy import first from dvc.repo.experiments.exceptions import ( ExperimentExistsError, InvalidArgumentError, - UnchangedExperimentError, ) from dvc.repo.experiments.utils import exp_refs_by_rev from dvc.scm import resolve_rev @@ -18,10 +17,7 @@ def modified_exp_stage(exp_stage, tmp_dir): def test_exp_save_unchanged(tmp_dir, dvc, scm, exp_stage): - with pytest.raises(UnchangedExperimentError): - dvc.experiments.save() - - dvc.experiments.save(force=True) + dvc.experiments.save() @pytest.mark.parametrize("name", (None, "test"))