Repository: iterative/dvc

Issue:
checkout: throw a warning if user is trying to do `dvc checkout <branch>`
Requested by Al on discord. Currently if a user accidentally runs `dvc checkout <branch>` he gets:

```
(3.7.0-dvc) ➜  myrepo git:(ololo) ✗ dvc checkout master                                 
Error: unexpected error - 'master' does not exist.                                      
                                                                                        
Having any troubles? Hit us up at https://dvc.org/support, we are always happy to help! 
```

Let's add a static warning that says something like "Do you mean `git checkout <branch>`?`".


Discussion:
I can have a look at that one
That would be amazing @tdeboissiere ! Thank you! :slightly_smiling_face: Please let us know if you need anything. 
Sure thing !

The error message occurs during a `Stage.load` call. `Stage.load` is also called by unrelated dvc commands, such as `dvc remove`. This means I need a way to identify what dvc command led to the `Stage.load` call, in order to display the warning message only if that dvc command was a `checkout`

The most straightforward way would be to add a keyword argument such as `from_checkout=False` if True, we can then change the error message to suggest `git checkout`.

While that would work, it is not particularly elegant. Thoughts ?

**Edit**

Would it make sense to implement a mechanism to easily find out which dvc command has been launched ?
Maybe something like: 
- Call dvc checkout 
- Get the CmdCheckout instance --> set self.project.current_cmd = checkout 
- Then this current_cmd field can be used to fill the debug messages
@tdeboissiere How about we raise a special exception when dvc file doesn't exist and then would catch it either in `dvc/project.py:Project.checkout()` or in `dvc/command/checkout.py`?

Resolving patch:
diff --git a/dvc/project.py b/dvc/project.py
--- a/dvc/project.py
+++ b/dvc/project.py
@@ -566,10 +566,20 @@ def _cleanup_unused_links(self, all_stages):
     def checkout(
         self, target=None, with_deps=False, force=False, recursive=False
     ):
-
         if target and not recursive:
+            from dvc.stage import (
+                StageFileDoesNotExistError,
+                StageFileBadNameError,
+            )
+
             all_stages = self.active_stages()
-            stages = self._collect(target, with_deps=with_deps)
+            try:
+                stages = self._collect(target, with_deps=with_deps)
+            except (StageFileDoesNotExistError, StageFileBadNameError) as exc:
+                raise DvcException(
+                    str(exc)
+                    + " Did you mean 'git checkout {}'?".format(target)
+                )
         else:
             all_stages = self.active_stages(target)
             stages = all_stages
diff --git a/dvc/stage.py b/dvc/stage.py
--- a/dvc/stage.py
+++ b/dvc/stage.py
@@ -415,7 +415,7 @@ def _check_dvc_filename(fname):
         if not Stage.is_valid_filename(fname):
             raise StageFileBadNameError(
                 "bad stage filename '{}'. Stage files should be named"
-                " 'Dvcfile' or have a '.dvc' suffix(e.g. '{}.dvc').".format(
+                " 'Dvcfile' or have a '.dvc' suffix (e.g. '{}.dvc').".format(
                     os.path.relpath(fname), os.path.basename(fname)
                 )
             )


Test patch:
diff --git a/tests/test_checkout.py b/tests/test_checkout.py
--- a/tests/test_checkout.py
+++ b/tests/test_checkout.py
@@ -370,3 +370,32 @@ def test(self, mock_input, mock_isatty):
 
         with self.assertRaises(ConfirmRemoveError):
             self.dvc.checkout()
+
+
+class TestCheckoutSuggestGit(TestRepro):
+    def test(self):
+
+        try:
+            self.dvc.checkout(target="gitbranch")
+        except DvcException as exc:
+            msg = str(exc)
+            self.assertEqual(
+                msg,
+                "'gitbranch' does not exist. Did you mean 'git checkout gitbranch'?",
+            )
+
+        try:
+            self.dvc.checkout(target=self.FOO)
+        except DvcException as exc:
+            msg = str(exc)
+            self.assertEqual(
+                msg,
+                (
+                    "bad stage filename '{}'."
+                    " Stage files should be named 'Dvcfile'"
+                    " or have a '.dvc' suffix (e.g. '{}.dvc')."
+                    " Did you mean 'git checkout {}'?".format(
+                        self.FOO, self.FOO, self.FOO
+                    )
+                ),
+            )
diff --git a/tests/unit/stage.py b/tests/unit/stage.py
--- a/tests/unit/stage.py
+++ b/tests/unit/stage.py
@@ -1,6 +1,5 @@
 import mock
 from unittest import TestCase
-
 from dvc.stage import Stage