diff --git a/dvc/repo/tree.py b/dvc/repo/tree.py
--- a/dvc/repo/tree.py
+++ b/dvc/repo/tree.py
@@ -61,6 +61,10 @@ def open(self, path, mode="r", encoding="utf-8", remote=None):
         except OutputNotFoundError as exc:
             raise FileNotFoundError from exc
 
+        # NOTE: this handles both dirty and checkout-ed out at the same time
+        if self.repo.tree.exists(path):
+            return self.repo.tree.open(path, mode=mode, encoding=encoding)
+
         if len(outs) != 1 or (
             outs[0].is_dir_checksum and path == outs[0].path_info
         ):
@@ -252,13 +256,9 @@ def open(self, path, mode="r", encoding="utf-8", **kwargs):
             encoding = None
 
         if self.dvctree and self.dvctree.exists(path):
-            try:
-                return self.dvctree.open(
-                    path, mode=mode, encoding=encoding, **kwargs
-                )
-            except FileNotFoundError:
-                if self.isdvc(path):
-                    raise
+            return self.dvctree.open(
+                path, mode=mode, encoding=encoding, **kwargs
+            )
         return self.repo.tree.open(path, mode=mode, encoding=encoding)
 
     def exists(self, path):

diff --git a/tests/func/metrics/test_diff.py b/tests/func/metrics/test_diff.py
--- a/tests/func/metrics/test_diff.py
+++ b/tests/func/metrics/test_diff.py
@@ -153,3 +153,20 @@ def test_no_commits(tmp_dir):
     assert Git().no_commits
 
     assert Repo.init().metrics.diff() == {}
+
+
+def test_metrics_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
+    def _gen(val):
+        tmp_dir.gen({"m_temp.yaml": str(val)})
+        run_copy_metrics("m_temp.yaml", "m.yaml", metrics=["m.yaml"])
+        dvc.scm.commit(str(val))
+
+    _gen(1)
+    _gen(2)
+    _gen(3)
+
+    tmp_dir.gen({"m.yaml": "4"})
+
+    expected = {"m.yaml": {"": {"old": 3, "new": 4, "diff": 1}}}
+
+    assert dvc.metrics.diff() == expected
diff --git a/tests/func/params/test_diff.py b/tests/func/params/test_diff.py
--- a/tests/func/params/test_diff.py
+++ b/tests/func/params/test_diff.py
@@ -24,6 +24,20 @@ def test_diff(tmp_dir, scm, dvc):
     }
 
 
+def test_diff_dirty(tmp_dir, scm, dvc):
+    tmp_dir.gen("params.yaml", "foo: bar")
+    dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True)
+    scm.add(["params.yaml", "Dvcfile"])
+    scm.commit("bar")
+
+    tmp_dir.scm_gen("params.yaml", "foo: baz", commit="baz")
+    tmp_dir.gen("params.yaml", "foo: qux")
+
+    assert dvc.params.diff() == {
+        "params.yaml": {"foo": {"old": "baz", "new": "qux"}}
+    }
+
+
 def test_diff_new(tmp_dir, scm, dvc):
     tmp_dir.gen("params.yaml", "foo: bar")
     dvc.run(cmd="echo params.yaml", params=["foo"], single_stage=True)
diff --git a/tests/func/plots/test_diff.py b/tests/func/plots/test_diff.py
--- a/tests/func/plots/test_diff.py
+++ b/tests/func/plots/test_diff.py
@@ -39,3 +39,17 @@ def test_diff_dirty(tmp_dir, scm, dvc, run_copy_metrics):
     ]
     assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
     assert plot_content["encoding"]["y"]["field"] == "y"
+
+    _write_json(tmp_dir, [{"y": 7}, {"y": 8}], "metric.json")
+
+    plot_string = dvc.plots.diff(props={"fields": {"y"}})["metric.json"]
+
+    plot_content = json.loads(plot_string)
+    assert plot_content["data"]["values"] == [
+        {"y": 3, PlotData.INDEX_FIELD: 0, "rev": "HEAD"},
+        {"y": 5, PlotData.INDEX_FIELD: 1, "rev": "HEAD"},
+        {"y": 7, PlotData.INDEX_FIELD: 0, "rev": "workspace"},
+        {"y": 8, PlotData.INDEX_FIELD: 1, "rev": "workspace"},
+    ]
+    assert plot_content["encoding"]["x"]["field"] == PlotData.INDEX_FIELD
+    assert plot_content["encoding"]["y"]["field"] == "y"
diff --git a/tests/func/test_api.py b/tests/func/test_api.py
--- a/tests/func/test_api.py
+++ b/tests/func/test_api.py
@@ -114,6 +114,10 @@ def test_missing(remote_url, tmp_dir, dvc):
     # Remove cache to make foo missing
     remove(dvc.cache.local.cache_dir)
 
+    api.read("foo")
+
+    remove("foo")
+
     with pytest.raises(FileMissingError):
         api.read("foo")
 
diff --git a/tests/func/test_update.py b/tests/func/test_update.py
--- a/tests/func/test_update.py
+++ b/tests/func/test_update.py
@@ -52,9 +52,9 @@ def test_update_import_after_remote_updates_to_dvc(tmp_dir, dvc, erepo_dir):
     new_rev = None
     with erepo_dir.branch("branch", new=False), erepo_dir.chdir():
         erepo_dir.scm.repo.index.remove(["version"])
-        erepo_dir.dvc_gen("version", "updated")
-        erepo_dir.scm.add(["version", "version.dvc"])
-        erepo_dir.scm.commit("upgrade to DVC tracking")
+        erepo_dir.dvc_gen(
+            "version", "updated", commit="upgrade to DVC tracking"
+        )
         new_rev = erepo_dir.scm.get_rev()
 
     assert old_rev != new_rev
diff --git a/tests/unit/repo/test_repo_tree.py b/tests/unit/repo/test_repo_tree.py
--- a/tests/unit/repo/test_repo_tree.py
+++ b/tests/unit/repo/test_repo_tree.py
@@ -26,6 +26,24 @@ def test_open(tmp_dir, dvc):
         assert fobj.read() == "foo"
 
 
+def test_open_dirty_hash(tmp_dir, dvc):
+    tmp_dir.dvc_gen("file", "file")
+    (tmp_dir / "file").write_text("something")
+
+    tree = RepoTree(dvc)
+    with tree.open("file", "r") as fobj:
+        assert fobj.read() == "something"
+
+
+def test_open_dirty_no_hash(tmp_dir, dvc):
+    tmp_dir.gen("file", "file")
+    (tmp_dir / "file.dvc").write_text("outs:\n- path: file\n")
+
+    tree = RepoTree(dvc)
+    with tree.open("file", "r") as fobj:
+        assert fobj.read() == "file"
+
+
 def test_open_in_history(tmp_dir, scm, dvc):
     tmp_dir.gen("foo", "foo")
     dvc.add("foo")
diff --git a/tests/unit/repo/test_tree.py b/tests/unit/repo/test_tree.py
--- a/tests/unit/repo/test_tree.py
+++ b/tests/unit/repo/test_tree.py
@@ -26,6 +26,24 @@ def test_open(tmp_dir, dvc):
         assert fobj.read() == "foo"
 
 
+def test_open_dirty_hash(tmp_dir, dvc):
+    tmp_dir.dvc_gen("file", "file")
+    (tmp_dir / "file").write_text("something")
+
+    tree = DvcTree(dvc)
+    with tree.open("file", "r") as fobj:
+        assert fobj.read() == "something"
+
+
+def test_open_dirty_no_hash(tmp_dir, dvc):
+    tmp_dir.gen("file", "file")
+    (tmp_dir / "file.dvc").write_text("outs:\n- path: file\n")
+
+    tree = DvcTree(dvc)
+    with tree.open("file", "r") as fobj:
+        assert fobj.read() == "file"
+
+
 def test_open_in_history(tmp_dir, scm, dvc):
     tmp_dir.gen("foo", "foo")
     dvc.add("foo")

make sure metrics/plots can compare dirty repo with the last commit by default
>  Which versions get compared by default? without sending arguments
> I expected the present metrics file to be plotted on top of the last commit but if I manually change this metrics file, the changes are not reflected in the plot.
>  Only if I run dvc repro in a way that generates a different metrics file does it pick up the changes and plots them.
>  Where is this version coming from? I haven't committed anything to Git.
>  On DVC 1.0

https://discordapp.com/channels/485586884165107732/563406153334128681/719342575835676733

tests/unit/repo/test_tree.py::test_open_dirty_no_hash tests/unit/repo/test_repo_tree.py::test_open_dirty_no_hash
tests/func/metrics/test_diff.py::test_metrics_diff_no_metrics tests/func/metrics/test_diff.py::test_no_commits tests/func/params/test_diff.py::test_no_commits tests/func/test_api.py::test_get_url_requires_dvc tests/func/test_api.py::test_open_scm_controlled tests/func/params/test_diff.py::test_diff_no_params