From 8bdf9b113806aca0df114cf03d7c5f1c76298b0d Mon Sep 17 00:00:00 2001 From: Alex Hall Date: Sat, 13 Aug 2022 21:12:36 +0200 Subject: [PATCH] Better error message when using remove on file instead of dvc file --- dvc/repo/remove.py | 17 ++++++++++++++++- dvc/stage/exceptions.py | 2 +- tests/func/test_remove.py | 17 ++++++++++++++++- 3 files changed, 33 insertions(+), 3 deletions(-) diff --git a/dvc/repo/remove.py b/dvc/repo/remove.py index 8e5401f4d..fbc1c3681 100644 --- a/dvc/repo/remove.py +++ b/dvc/repo/remove.py @@ -2,6 +2,12 @@ import logging import typing from . import locked +from dvc.dvcfile import DVC_FILE_SUFFIX +from dvc.stage.exceptions import ( + StageNotFound, + StageFileDoesNotExistError, + StageFileIsNotDvcFileError, +) if typing.TYPE_CHECKING: from dvc.repo import Repo @@ -11,7 +17,16 @@ logger = logging.getLogger(__name__) @locked def remove(self: "Repo", target: str, outs: bool = False): - stages = self.stage.from_target(target) + try: + stages = self.stage.from_target(target) + except (StageNotFound, StageFileDoesNotExistError) as e: + # If the user specified a tracked file as a target instead of a stage, + # e.g. `data.csv` instead of `data.csv.dvc`, + # give a more helpful error message. + if self.fs.exists(target + DVC_FILE_SUFFIX): + raise StageFileIsNotDvcFileError(target) from e + else: + raise for stage in stages: stage.remove(remove_outs=outs, force=outs) diff --git a/dvc/stage/exceptions.py b/dvc/stage/exceptions.py index 1761109b7..d89142ab6 100644 --- a/dvc/stage/exceptions.py +++ b/dvc/stage/exceptions.py @@ -31,7 +31,7 @@ class StageFileIsNotDvcFileError(DvcException): sname = fname + DVC_FILE_SUFFIX if is_dvc_file(sname): - msg += f" Do you mean '{sname}'?" + msg += f". Do you mean '{sname}'?" super().__init__(msg) diff --git a/tests/func/test_remove.py b/tests/func/test_remove.py index 7332b4c13..25313ac7d 100644 --- a/tests/func/test_remove.py +++ b/tests/func/test_remove.py @@ -4,7 +4,10 @@ import pytest from dvc.cli import main from dvc.fs import system -from dvc.stage.exceptions import StageFileDoesNotExistError +from dvc.stage.exceptions import ( + StageFileDoesNotExistError, + StageFileIsNotDvcFileError, +) from dvc.utils.fs import remove from dvc_objects.errors import ObjectDBError from tests.utils import get_gitignore_content @@ -32,6 +35,18 @@ def test_remove(tmp_dir, scm, dvc, run_copy, remove_outs): assert not (tmp_dir / ".gitignore").exists() +def test_remove_file_target(tmp_dir, dvc): + tmp_dir.dvc_gen("foo", "foo") + + with pytest.raises( + StageFileIsNotDvcFileError, + match="'foo' is not a .dvc file. Do you mean 'foo.dvc'?", + ): + dvc.remove("foo") + + dvc.remove("foo.dvc") + + def test_remove_non_existent_file(tmp_dir, dvc): with pytest.raises(StageFileDoesNotExistError): dvc.remove("non_existent_dvc_file.dvc")