Repository: iterative/dvc

Issue:
diff: unexpected error when diffing workspace after dvc remove
# Bug Report

## Description

`dvc diff` will raise unexpected error with no other output if both the `.dvc` file and the original data file are removed from the workspace (i.e. after running `dvc remove`).

```
$ git status                                                                                                                  ⏎
On branch master
Changes not staged for commit:
        deleted:    .gitignore
        deleted:    foo.txt.dvc

no changes added to commit

$ dvc diff -v
2020-12-28 15:43:46,270 DEBUG: Check for update is enabled.
2020-12-28 15:43:46,584 ERROR: unexpected error
------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/pmrowla/git/dvc/dvc/main.py", line 90, in main
    ret = cmd.run()
  File "/Users/pmrowla/git/dvc/dvc/command/diff.py", line 131, in run
    diff = self.repo.diff(
  File "/Users/pmrowla/git/dvc/dvc/repo/__init__.py", line 53, in wrapper
    return f(repo, *args, **kwargs)
  File "/Users/pmrowla/git/dvc/dvc/repo/diff.py", line 60, in diff
    missing = sorted(_filter_missing(self, deleted_or_missing))
  File "/Users/pmrowla/git/dvc/dvc/repo/diff.py", line 151, in _filter_missing
    metadata = repo_tree.metadata(path)
  File "/Users/pmrowla/git/dvc/dvc/tree/repo.py", line 446, in metadata
    raise FileNotFoundError
FileNotFoundError
------------------------------------------------------------
```

### Reproduce

```bash
#!/bin/bash

set -e
set -x

REPO="test_repo"

rm -rf $REPO
mkdir $REPO
pushd $REPO

git init
dvc init
echo "foo" > foo.txt
dvc add foo.txt
git add .
git commit -m "init"

dvc remove foo.txt.dvc
rm foo.txt
dvc diff -v

popd
```

This issue only affects workspace diff. If the changes after remove are `git commit`ed and then the two commits are `dvc diff`ed, the diff will work as expected. Issue can also be reproduced by doing `git rm <file>.dvc; rm <file>` instead of using `dvc remove`.


Discussion:

Resolving patch:
diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
--- a/dvc/repo/diff.py
+++ b/dvc/repo/diff.py
@@ -148,11 +148,14 @@ def _dir_output_paths(repo_tree, output, targets=None):
 def _filter_missing(repo, paths):
     repo_tree = RepoTree(repo, stream=True)
     for path in paths:
-        metadata = repo_tree.metadata(path)
-        if metadata.is_dvc:
-            out = metadata.outs[0]
-            if out.status().get(str(out)) == "not in cache":
-                yield path
+        try:
+            metadata = repo_tree.metadata(path)
+            if metadata.is_dvc:
+                out = metadata.outs[0]
+                if out.status().get(str(out)) == "not in cache":
+                    yield path
+        except FileNotFoundError:
+            pass
 
 
 def _targets_to_path_infos(repo, targets):


Test patch:
diff --git a/tests/func/test_diff.py b/tests/func/test_diff.py
--- a/tests/func/test_diff.py
+++ b/tests/func/test_diff.py
@@ -60,9 +60,12 @@ def test_no_cache_entry(tmp_dir, scm, dvc):
     }
 
 
-def test_deleted(tmp_dir, scm, dvc):
+@pytest.mark.parametrize("delete_data", [True, False])
+def test_deleted(tmp_dir, scm, dvc, delete_data):
     tmp_dir.dvc_gen("file", "text", commit="add file")
     (tmp_dir / "file.dvc").unlink()
+    if delete_data:
+        (tmp_dir / "file").unlink()
 
     assert dvc.diff() == {
         "added": [],