diff --git a/dvc/exceptions.py b/dvc/exceptions.py index 5d6975f396..f3153e3186 100644 --- a/dvc/exceptions.py +++ b/dvc/exceptions.py @@ -31,14 +31,19 @@ def __init__(self, output, stages): assert isinstance(output, str) assert all(hasattr(stage, "relpath") for stage in stages) + msg = "" + stage_names = [s.addressing for s in stages] + stages_str = " ".join(stage_names) if len(stages) == 1: - msg = "output '{}' is already specified in {}.".format( - output, first(stages) - ) + stage_name = first(stages) + msg = f"output '{output}' is already specified in {stage_name}." else: msg = "output '{}' is already specified in stages:\n{}".format( - output, "\n".join(f"\t- {s.addressing}" for s in stages) + output, "\n".join(f"\t- {s}" for s in stage_names) ) + msg += ( + f"\nUse `dvc remove {stages_str}` to stop tracking the overlapping output." + ) super().__init__(msg) self.stages = stages self.output = output diff --git a/dvc/repo/stage.py b/dvc/repo/stage.py index 2856b21f54..29765ddf8e 100644 --- a/dvc/repo/stage.py +++ b/dvc/repo/stage.py @@ -5,7 +5,11 @@ from functools import wraps from typing import Iterable, List, NamedTuple, Optional, Set, Tuple, Union -from dvc.exceptions import NoOutputOrStageError, OutputNotFoundError +from dvc.exceptions import ( + NoOutputOrStageError, + OutputDuplicationError, + OutputNotFoundError, +) from dvc.repo import lock_repo from dvc.ui import ui from dvc.utils import as_posix, parse_target @@ -176,7 +180,12 @@ def create( check_stage_exists(self.repo, stage, stage.path) - self.repo.check_graph(stages={stage}) + try: + self.repo.check_graph(stages={stage}) + except OutputDuplicationError as exc: + # Don't include the stage currently being added. + exc.stages.remove(stage) + raise OutputDuplicationError(exc.output, exc.stages) from None restore_fields(stage) return stage diff --git a/tests/func/test_stage.py b/tests/func/test_stage.py index 209c8f8c17..242155d321 100644 --- a/tests/func/test_stage.py +++ b/tests/func/test_stage.py @@ -4,6 +4,7 @@ from dvc.annotations import Annotation from dvc.dvcfile import SingleStageFile +from dvc.exceptions import OutputDuplicationError from dvc.fs import LocalFileSystem from dvc.output import Output from dvc.repo import Repo, lock_repo @@ -333,3 +334,14 @@ def test_stage_run_checkpoint(tmp_dir, dvc, mocker, checkpoint): mock_cmd_run.assert_called_with( stage, checkpoint_func=callback, dry=False, run_env=None ) + + +def test_stage_add_duplicated_output(tmp_dir, dvc): + tmp_dir.dvc_gen("foo", "foo") + dvc.add("foo") + + with pytest.raises( + OutputDuplicationError, + match="Use `dvc remove foo.dvc` to stop tracking the overlapping output.", + ): + dvc.stage.add(name="duplicated", cmd="echo bar > foo", outs=["foo"])