prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Unexpected error on `dvc diff`
  ## Bug Report
  When running `dvc diff staging`, I got a KeyError, here is the traceback:
  ```
  Traceback (most recent call last):
    File "/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/main.py", line 76, in main
      ret = cmd.run()
    File "/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/command/diff.py", line 130, in run
      diff = self.repo.diff(self.args.a_rev, self.args.b_rev)
    File "/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/__init__.py", line 54, in wrapper
      return f(repo, *args, **kwargs)
    File "/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/diff.py", line 43, in diff
      missing = sorted(_filter_missing(self, deleted_or_missing))
    File "/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/diff.py", line 125, in _filter_missing
      if out.status()[str(out)] == "not in cache":
  KeyError: 'data/KPI/KPI_from_dvc/en/post_probs'
  ```
  
  It only happens when I give a specific revision.
  
  Any ideas? Could it be my data?
  
  ### Please provide information about your setup
  
  **Output of `dvc version`:**
  
  ```console
  $ dvc version
  DVC version: 1.9.1 (pip)
  ---------------------------------
  Platform: Python 3.7.3 on Linux-5.4.0-1029-aws-x86_64-with-debian-buster-sid
  Supports: http, https, s3
  Cache types: hardlink, symlink
  Cache directory: ext4 on /dev/nvme0n1p1
  Workspace directory: ext4 on /dev/nvme0n1p1
  Repo: dvc, git
  ```
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `dvc/repo/diff.py`
  Content:
  ```
  1 import logging
  2 import os
  3 
  4 from dvc.repo import locked
  5 from dvc.tree.local import LocalTree
  6 from dvc.tree.repo import RepoTree
  7 
  8 logger = logging.getLogger(__name__)
  9 
  10 
  11 @locked
  12 def diff(self, a_rev="HEAD", b_rev=None):
  13     """
  14     By default, it compares the workspace with the last commit's tree.
  15 
  16     This implementation differs from `git diff` since DVC doesn't have
  17     the concept of `index`, but it keeps the same interface, thus,
  18     `dvc diff` would be the same as `dvc diff HEAD`.
  19     """
  20 
  21     if self.scm.no_commits:
  22         return {}
  23 
  24     b_rev = b_rev if b_rev else "workspace"
  25     results = {}
  26     for rev in self.brancher(revs=[a_rev, b_rev]):
  27         if rev == "workspace" and rev != b_rev:
  28             # brancher always returns workspace, but we only need to compute
  29             # workspace paths/checksums if b_rev was None
  30             continue
  31         results[rev] = _paths_checksums(self)
  32 
  33     old = results[a_rev]
  34     new = results[b_rev]
  35 
  36     # Compare paths between the old and new tree.
  37     # set() efficiently converts dict keys to a set
  38     added = sorted(set(new) - set(old))
  39     deleted_or_missing = set(old) - set(new)
  40     if b_rev == "workspace":
  41         # missing status is only applicable when diffing local workspace
  42         # against a commit
  43         missing = sorted(_filter_missing(self, deleted_or_missing))
  44     else:
  45         missing = []
  46     deleted = sorted(deleted_or_missing - set(missing))
  47     modified = sorted(set(old) & set(new))
  48 
  49     ret = {
  50         "added": [{"path": path, "hash": new[path]} for path in added],
  51         "deleted": [{"path": path, "hash": old[path]} for path in deleted],
  52         "modified": [
  53             {"path": path, "hash": {"old": old[path], "new": new[path]}}
  54             for path in modified
  55             if old[path] != new[path]
  56         ],
  57         "not in cache": [
  58             {"path": path, "hash": old[path]} for path in missing
  59         ],
  60     }
  61 
  62     return ret if any(ret.values()) else {}
  63 
  64 
  65 def _paths_checksums(repo):
  66     """
  67     A dictionary of checksums addressed by relpaths collected from
  68     the current tree outputs.
  69 
  70     To help distinguish between a directory and a file output,
  71     the former one will come with a trailing slash in the path:
  72 
  73         directory: "data/"
  74         file:      "data"
  75     """
  76 
  77     return dict(_output_paths(repo))
  78 
  79 
  80 def _output_paths(repo):
  81     repo_tree = RepoTree(repo, stream=True)
  82     on_working_tree = isinstance(repo.tree, LocalTree)
  83 
  84     def _exists(output):
  85         if on_working_tree:
  86             return output.exists
  87         return True
  88 
  89     def _to_path(output):
  90         return (
  91             str(output)
  92             if not output.is_dir_checksum
  93             else os.path.join(str(output), "")
  94         )
  95 
  96     def _to_checksum(output):
  97         if on_working_tree:
  98             return repo.cache.local.tree.get_hash(output.path_info).value
  99         return output.hash_info.value
  100 
  101     for stage in repo.stages:
  102         for output in stage.outs:
  103             if _exists(output):
  104                 yield _to_path(output), _to_checksum(output)
  105                 if output.is_dir_checksum:
  106                     yield from _dir_output_paths(repo_tree, output)
  107 
  108 
  109 def _dir_output_paths(repo_tree, output):
  110     from dvc.config import NoRemoteError
  111 
  112     try:
  113         for fname in repo_tree.walk_files(output.path_info):
  114             yield str(fname), repo_tree.get_file_hash(fname).value
  115     except NoRemoteError:
  116         logger.warning("dir cache entry for '%s' is missing", output)
  117 
  118 
  119 def _filter_missing(repo, paths):
  120     repo_tree = RepoTree(repo, stream=True)
  121     for path in paths:
  122         metadata = repo_tree.metadata(path)
  123         if metadata.is_dvc:
  124             out = metadata.outs[0]
  125             if out.status()[str(out)] == "not in cache":
  126                 yield path
  127
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
  --- a/dvc/repo/diff.py
  +++ b/dvc/repo/diff.py
  @@ -122,5 +122,5 @@
           metadata = repo_tree.metadata(path)
           if metadata.is_dvc:
               out = metadata.outs[0]
  -            if out.status()[str(out)] == "not in cache":
  +            if out.status().get(str(out)) == "not in cache":
                   yield path
  
verification_info: {"golden_diff": "diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py\n--- a/dvc/repo/diff.py\n+++ b/dvc/repo/diff.py\n@@ -122,5 +122,5 @@\n         metadata = repo_tree.metadata(path)\n         if metadata.is_dvc:\n             out = metadata.outs[0]\n-            if out.status()[str(out)] == \"not in cache\":\n+            if out.status().get(str(out)) == \"not in cache\":\n                 yield path\n", "issue": "Unexpected error on `dvc diff`\n## Bug Report\r\nWhen running `dvc diff staging`, I got a KeyError, here is the traceback:\r\n```\r\nTraceback (most recent call last):\r\n  File \"/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/main.py\", line 76, in main\r\n    ret = cmd.run()\r\n  File \"/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/command/diff.py\", line 130, in run\r\n    diff = self.repo.diff(self.args.a_rev, self.args.b_rev)\r\n  File \"/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/__init__.py\", line 54, in wrapper\r\n    return f(repo, *args, **kwargs)\r\n  File \"/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/diff.py\", line 43, in diff\r\n    missing = sorted(_filter_missing(self, deleted_or_missing))\r\n  File \"/home/ubuntu/.local/share/virtualenvs/speech-api-EI_ft4iY/lib/python3.7/site-packages/dvc/repo/diff.py\", line 125, in _filter_missing\r\n    if out.status()[str(out)] == \"not in cache\":\r\nKeyError: 'data/KPI/KPI_from_dvc/en/post_probs'\r\n```\r\n\r\nIt only happens when I give a specific revision.\r\n\r\nAny ideas? Could it be my data?\r\n\r\n### Please provide information about your setup\r\n\r\n**Output of `dvc version`:**\r\n\r\n```console\r\n$ dvc version\r\nDVC version: 1.9.1 (pip)\r\n---------------------------------\r\nPlatform: Python 3.7.3 on Linux-5.4.0-1029-aws-x86_64-with-debian-buster-sid\r\nSupports: http, https, s3\r\nCache types: hardlink, symlink\r\nCache directory: ext4 on /dev/nvme0n1p1\r\nWorkspace directory: ext4 on /dev/nvme0n1p1\r\nRepo: dvc, git\r\n```\n", "before_files": [{"content": "import logging\nimport os\n\nfrom dvc.repo import locked\nfrom dvc.tree.local import LocalTree\nfrom dvc.tree.repo import RepoTree\n\nlogger = logging.getLogger(__name__)\n\n\n@locked\ndef diff(self, a_rev=\"HEAD\", b_rev=None):\n    \"\"\"\n    By default, it compares the workspace with the last commit's tree.\n\n    This implementation differs from `git diff` since DVC doesn't have\n    the concept of `index`, but it keeps the same interface, thus,\n    `dvc diff` would be the same as `dvc diff HEAD`.\n    \"\"\"\n\n    if self.scm.no_commits:\n        return {}\n\n    b_rev = b_rev if b_rev else \"workspace\"\n    results = {}\n    for rev in self.brancher(revs=[a_rev, b_rev]):\n        if rev == \"workspace\" and rev != b_rev:\n            # brancher always returns workspace, but we only need to compute\n            # workspace paths/checksums if b_rev was None\n            continue\n        results[rev] = _paths_checksums(self)\n\n    old = results[a_rev]\n    new = results[b_rev]\n\n    # Compare paths between the old and new tree.\n    # set() efficiently converts dict keys to a set\n    added = sorted(set(new) - set(old))\n    deleted_or_missing = set(old) - set(new)\n    if b_rev == \"workspace\":\n        # missing status is only applicable when diffing local workspace\n        # against a commit\n        missing = sorted(_filter_missing(self, deleted_or_missing))\n    else:\n        missing = []\n    deleted = sorted(deleted_or_missing - set(missing))\n    modified = sorted(set(old) & set(new))\n\n    ret = {\n        \"added\": [{\"path\": path, \"hash\": new[path]} for path in added],\n        \"deleted\": [{\"path\": path, \"hash\": old[path]} for path in deleted],\n        \"modified\": [\n            {\"path\": path, \"hash\": {\"old\": old[path], \"new\": new[path]}}\n            for path in modified\n            if old[path] != new[path]\n        ],\n        \"not in cache\": [\n            {\"path\": path, \"hash\": old[path]} for path in missing\n        ],\n    }\n\n    return ret if any(ret.values()) else {}\n\n\ndef _paths_checksums(repo):\n    \"\"\"\n    A dictionary of checksums addressed by relpaths collected from\n    the current tree outputs.\n\n    To help distinguish between a directory and a file output,\n    the former one will come with a trailing slash in the path:\n\n        directory: \"data/\"\n        file:      \"data\"\n    \"\"\"\n\n    return dict(_output_paths(repo))\n\n\ndef _output_paths(repo):\n    repo_tree = RepoTree(repo, stream=True)\n    on_working_tree = isinstance(repo.tree, LocalTree)\n\n    def _exists(output):\n        if on_working_tree:\n            return output.exists\n        return True\n\n    def _to_path(output):\n        return (\n            str(output)\n            if not output.is_dir_checksum\n            else os.path.join(str(output), \"\")\n        )\n\n    def _to_checksum(output):\n        if on_working_tree:\n            return repo.cache.local.tree.get_hash(output.path_info).value\n        return output.hash_info.value\n\n    for stage in repo.stages:\n        for output in stage.outs:\n            if _exists(output):\n                yield _to_path(output), _to_checksum(output)\n                if output.is_dir_checksum:\n                    yield from _dir_output_paths(repo_tree, output)\n\n\ndef _dir_output_paths(repo_tree, output):\n    from dvc.config import NoRemoteError\n\n    try:\n        for fname in repo_tree.walk_files(output.path_info):\n            yield str(fname), repo_tree.get_file_hash(fname).value\n    except NoRemoteError:\n        logger.warning(\"dir cache entry for '%s' is missing\", output)\n\n\ndef _filter_missing(repo, paths):\n    repo_tree = RepoTree(repo, stream=True)\n    for path in paths:\n        metadata = repo_tree.metadata(path)\n        if metadata.is_dvc:\n            out = metadata.outs[0]\n            if out.status()[str(out)] == \"not in cache\":\n                yield path\n", "path": "dvc/repo/diff.py"}], "after_files": [{"content": "import logging\nimport os\n\nfrom dvc.repo import locked\nfrom dvc.tree.local import LocalTree\nfrom dvc.tree.repo import RepoTree\n\nlogger = logging.getLogger(__name__)\n\n\n@locked\ndef diff(self, a_rev=\"HEAD\", b_rev=None):\n    \"\"\"\n    By default, it compares the workspace with the last commit's tree.\n\n    This implementation differs from `git diff` since DVC doesn't have\n    the concept of `index`, but it keeps the same interface, thus,\n    `dvc diff` would be the same as `dvc diff HEAD`.\n    \"\"\"\n\n    if self.scm.no_commits:\n        return {}\n\n    b_rev = b_rev if b_rev else \"workspace\"\n    results = {}\n    for rev in self.brancher(revs=[a_rev, b_rev]):\n        if rev == \"workspace\" and rev != b_rev:\n            # brancher always returns workspace, but we only need to compute\n            # workspace paths/checksums if b_rev was None\n            continue\n        results[rev] = _paths_checksums(self)\n\n    old = results[a_rev]\n    new = results[b_rev]\n\n    # Compare paths between the old and new tree.\n    # set() efficiently converts dict keys to a set\n    added = sorted(set(new) - set(old))\n    deleted_or_missing = set(old) - set(new)\n    if b_rev == \"workspace\":\n        # missing status is only applicable when diffing local workspace\n        # against a commit\n        missing = sorted(_filter_missing(self, deleted_or_missing))\n    else:\n        missing = []\n    deleted = sorted(deleted_or_missing - set(missing))\n    modified = sorted(set(old) & set(new))\n\n    ret = {\n        \"added\": [{\"path\": path, \"hash\": new[path]} for path in added],\n        \"deleted\": [{\"path\": path, \"hash\": old[path]} for path in deleted],\n        \"modified\": [\n            {\"path\": path, \"hash\": {\"old\": old[path], \"new\": new[path]}}\n            for path in modified\n            if old[path] != new[path]\n        ],\n        \"not in cache\": [\n            {\"path\": path, \"hash\": old[path]} for path in missing\n        ],\n    }\n\n    return ret if any(ret.values()) else {}\n\n\ndef _paths_checksums(repo):\n    \"\"\"\n    A dictionary of checksums addressed by relpaths collected from\n    the current tree outputs.\n\n    To help distinguish between a directory and a file output,\n    the former one will come with a trailing slash in the path:\n\n        directory: \"data/\"\n        file:      \"data\"\n    \"\"\"\n\n    return dict(_output_paths(repo))\n\n\ndef _output_paths(repo):\n    repo_tree = RepoTree(repo, stream=True)\n    on_working_tree = isinstance(repo.tree, LocalTree)\n\n    def _exists(output):\n        if on_working_tree:\n            return output.exists\n        return True\n\n    def _to_path(output):\n        return (\n            str(output)\n            if not output.is_dir_checksum\n            else os.path.join(str(output), \"\")\n        )\n\n    def _to_checksum(output):\n        if on_working_tree:\n            return repo.cache.local.tree.get_hash(output.path_info).value\n        return output.hash_info.value\n\n    for stage in repo.stages:\n        for output in stage.outs:\n            if _exists(output):\n                yield _to_path(output), _to_checksum(output)\n                if output.is_dir_checksum:\n                    yield from _dir_output_paths(repo_tree, output)\n\n\ndef _dir_output_paths(repo_tree, output):\n    from dvc.config import NoRemoteError\n\n    try:\n        for fname in repo_tree.walk_files(output.path_info):\n            yield str(fname), repo_tree.get_file_hash(fname).value\n    except NoRemoteError:\n        logger.warning(\"dir cache entry for '%s' is missing\", output)\n\n\ndef _filter_missing(repo, paths):\n    repo_tree = RepoTree(repo, stream=True)\n    for path in paths:\n        metadata = repo_tree.metadata(path)\n        if metadata.is_dvc:\n            out = metadata.outs[0]\n            if out.status().get(str(out)) == \"not in cache\":\n                yield path\n", "path": "dvc/repo/diff.py"}]}