diff --git a/dvc/repo/add.py b/dvc/repo/add.py index 02ffa73465..2d07d9439c 100644 --- a/dvc/repo/add.py +++ b/dvc/repo/add.py @@ -43,7 +43,14 @@ def check_recursive_and_fname(args): def transform_targets(args): - args.targets = ensure_list(args.targets) + from funcy import count_reps + + counts = count_reps(ensure_list(args.targets)) + dupes = [key for key, count in counts.items() if count > 1] + if dupes: + msg = ", ".join(f"[b]{key}[/]" for key in dupes) + ui.error_write(f"ignoring duplicated targets: {msg}", styled=True) + args.targets = list(counts) def check_arg_combinations(args): diff --git a/dvc/ui/__init__.py b/dvc/ui/__init__.py index 6ca054fabc..bce0c836ad 100644 --- a/dvc/ui/__init__.py +++ b/dvc/ui/__init__.py @@ -61,6 +61,8 @@ def error_write( style: str = None, sep: str = None, end: str = None, + styled: bool = True, + force: bool = True, ) -> None: return self.write( *objects, @@ -68,6 +70,8 @@ def error_write( sep=sep, end=end, stderr=True, + force=force, + styled=styled, ) def write( diff --git a/tests/func/test_add.py b/tests/func/test_add.py index 9273951e2a..0b61645639 100644 --- a/tests/func/test_add.py +++ b/tests/func/test_add.py @@ -1229,3 +1229,12 @@ def test_add_does_not_remove_stage_file_on_failure( assert str(exc_info.value) == exc_msg assert (tmp_dir / "foo.dvc").exists() assert (tmp_dir / stage.path).read_text() == dvcfile_contents + + +def test_add_ignore_duplicated_targets(tmp_dir, dvc, capsys): + tmp_dir.gen({"foo": "foo", "bar": "bar", "foobar": "foobar"}) + stages = dvc.add(["foo", "bar", "foobar", "bar", "foo"]) + + _, err = capsys.readouterr() + assert len(stages) == 3 + assert "ignoring duplicated targets: foo, bar" in err