Repository: iterative/dvc

Issue:
status: ignoring cache that is not in local cache nor in remote
`dvc status -c` should probably exit with non-zero code if some files are not presented neither in cache, neither in remote.

It is possible that some files are not presented neither in local cache, neither in remote. In this case `dvc status -c` will print out warning and exit with zero code:

```
$dvc status -c
WARNING: Some of the cache files do not exist neither locally nor on remote. Missing cache files:
name: data.csv, md5: 746308829575e17c3331bbcb00c0898b
Data and pipelines are up to date.
```

It may happen if Alice executes `dvc add data.csv`, `git add data.csv`, `git commit` and `git push` but doesn't execute `dvc push` and Bob executes `dvc status -c data.csv` on another machine.

There should be a way in such case to exit with non-zero from `dvc status` command. It will allow to handle such errors in different scripts.

Context: https://discordapp.com/channels/485586884165107732/563406153334128681/743159360745898044



Discussion:
`dvc status -c` shouldn't exit with non-zero code by default, because missing cache is just part of the normal status report. It should exit with non-zero in case `-q` is specified (there is already logic for that), but the problem is that we ignore STATUS_MISSING in https://github.com/iterative/dvc/blob/64f038fda83c2400263b335fca6bb4f63f6ecf0f/dvc/repo/status.py#L96 . So the first step here is to just stop ignoring it there and include it into the report. This is pretty simple and just requires adjusting the lines I've linked above.

After that we might consider introducing a special option that will tell `status` to error-out if it is not up-to-date.
 Thanks, @efiop ! I'll create PR with fix as soon as possible.

Resolving patch:
diff --git a/dvc/repo/status.py b/dvc/repo/status.py
--- a/dvc/repo/status.py
+++ b/dvc/repo/status.py
@@ -93,10 +93,14 @@ def _cloud_status(
     for info in status_info.values():
         name = info["name"]
         status_ = info["status"]
-        if status_ in [cloud.STATUS_OK, cloud.STATUS_MISSING]:
+        if status_ == cloud.STATUS_OK:
             continue
 
-        prefix_map = {cloud.STATUS_DELETED: "deleted", cloud.STATUS_NEW: "new"}
+        prefix_map = {
+            cloud.STATUS_DELETED: "deleted",
+            cloud.STATUS_NEW: "new",
+            cloud.STATUS_MISSING: "missing",
+        }
 
         ret[name] = prefix_map[status_]
 


Test patch:
diff --git a/tests/func/test_data_cloud.py b/tests/func/test_data_cloud.py
--- a/tests/func/test_data_cloud.py
+++ b/tests/func/test_data_cloud.py
@@ -6,12 +6,17 @@
 from flaky.flaky_decorator import flaky
 
 from dvc.cache import NamedCache
-from dvc.cache.base import STATUS_DELETED, STATUS_NEW, STATUS_OK
+from dvc.cache.base import (
+    STATUS_DELETED,
+    STATUS_MISSING,
+    STATUS_NEW,
+    STATUS_OK,
+)
 from dvc.external_repo import clean_repos
 from dvc.main import main
 from dvc.stage.exceptions import StageNotFound
 from dvc.tree.local import LocalTree
-from dvc.utils.fs import remove
+from dvc.utils.fs import move, remove
 from dvc.utils.yaml import dump_yaml, load_yaml
 
 from .test_api import all_clouds
@@ -49,6 +54,22 @@ def test_cloud(tmp_dir, dvc, remote):  # pylint:disable=unused-argument
         expected = {md5_dir: {"name": md5_dir, "status": STATUS_NEW}}
         assert status_dir == expected
 
+        # Move cache and check status
+        # See issue https://github.com/iterative/dvc/issues/4383 for details
+        backup_dir = dvc.cache.local.cache_dir + ".backup"
+        move(dvc.cache.local.cache_dir, backup_dir)
+        status = dvc.cloud.status(info, show_checksums=True)
+        expected = {md5: {"name": md5, "status": STATUS_MISSING}}
+        assert status == expected
+
+        status_dir = dvc.cloud.status(info_dir, show_checksums=True)
+        expected = {md5_dir: {"name": md5_dir, "status": STATUS_MISSING}}
+        assert status_dir == expected
+
+        # Restore original cache:
+        remove(dvc.cache.local.cache_dir)
+        move(backup_dir, dvc.cache.local.cache_dir)
+
         # Push and check status
         dvc.cloud.push(info)
         assert os.path.exists(cache)