Repository: iterative/dvc

Issue:
push all commits unexpected error in 1.0
```bash
$ dvc -V
1.0.0a1+b8aa8a
```

```bash
$ dvc push --all-commits
ERROR: unexpected error
```

```bash
$ dvc push --all-commits -v
2020-05-18 02:52:04,782 DEBUG: PRAGMA user_version;
2020-05-18 02:52:04,783 DEBUG: fetched: [(3,)]
2020-05-18 02:52:04,783 DEBUG: CREATE TABLE IF NOT EXISTS state (inode INTEGER PRIMARY KEY, mtime TEXT NOT NULL, size TEXT NOT NULL, md5 TEXT NOT NULL, timestamp TEXT NOT NULL)
2020-05-18 02:52:04,783 DEBUG: CREATE TABLE IF NOT EXISTS state_info (count INTEGER)
2020-05-18 02:52:04,783 DEBUG: CREATE TABLE IF NOT EXISTS link_state (path TEXT PRIMARY KEY, inode INTEGER NOT NULL, mtime TEXT NOT NULL)
2020-05-18 02:52:04,783 DEBUG: INSERT OR IGNORE INTO state_info (count) SELECT 0 WHERE NOT EXISTS (SELECT * FROM state_info)
2020-05-18 02:52:04,784 DEBUG: PRAGMA user_version = 3;
2020-05-18 02:52:04,788 DEBUG: Assuming '/Users/dmitry/src/viewer-metrics-gen/repo-johnnie/.dvc/cache/52/a6e6a3af83f1bd127a249cec82d9fb.dir' is unchanged since it is read-only
2020-05-18 02:52:04,788 DEBUG: Assuming '/Users/dmitry/src/viewer-metrics-gen/repo-johnnie/.dvc/cache/52/a6e6a3af83f1bd127a249cec82d9fb.dir' is unchanged since it is read-only
2020-05-18 02:52:04,833 DEBUG: SELECT count from state_info WHERE rowid=?
2020-05-18 02:52:04,833 DEBUG: fetched: [(221,)]
2020-05-18 02:52:04,834 DEBUG: UPDATE state_info SET count = ? WHERE rowid = ?
2020-05-18 02:52:04,836 ERROR: unexpected error
------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/dmitry/src/dvc/dvc/main.py", line 48, in main
    ret = cmd.run()
  File "/Users/dmitry/src/dvc/dvc/command/data_sync.py", line 58, in run
    run_cache=self.args.run_cache,
  File "/Users/dmitry/src/dvc/dvc/repo/__init__.py", line 28, in wrapper
    ret = f(repo, *args, **kwargs)
  File "/Users/dmitry/src/dvc/dvc/repo/push.py", line 32, in push
    used_run_cache=used_run_cache,
  File "/Users/dmitry/src/dvc/dvc/repo/__init__.py", line 290, in used_cache
    filter_info=filter_info,
  File "/Users/dmitry/src/dvc/dvc/stage/__init__.py", line 513, in get_used_cache
    cache.update(out.get_used_cache(*args, **kwargs))
  File "/Users/dmitry/src/dvc/dvc/output/base.py", line 452, in get_used_cache
    self.checksum, self._collect_used_dir_cache(**kwargs),
  File "/Users/dmitry/src/dvc/dvc/output/base.py", line 366, in _collect_used_dir_cache
    if self.cache.changed_cache_file(self.checksum):
  File "/Users/dmitry/src/dvc/dvc/remote/base.py", line 804, in changed_cache_file
    if self.is_protected(cache_info):
  File "/Users/dmitry/src/dvc/dvc/remote/local.py", line 711, in is_protected
    if not self.exists(path_info):
  File "/Users/dmitry/src/dvc/dvc/remote/local.py", line 95, in exists
    assert is_working_tree(self.repo.tree)
AssertionError
------------------------------------------------------------
```
fetch giving unexpected error for all tags and all commits
```
$ dvc --version
DVC version: 0.94.0
Python version: 3.7.6
Platform: Linux-5.3.0-51-generic-x86_64-with-debian-buster-sid
Binary: False
Package: pip
Supported remotes: http, https
```
After cloning example-get-started repo it gives unexpected error for `fetch -aT`.
I have tried this on windows too and it shows same error. It previously worked when I tried on version 0.92.x.
```
$ git clone https://github.com/iterative/example-get-started
$ cd example-get-started
$ dvc fetch -aT
ERROR: unexpected error
```
```
$ dvc fetch -aT -v
2020-05-22 23:02:45,092 DEBUG: PRAGMA user_version;                     
2020-05-22 23:02:45,092 DEBUG: fetched: [(3,)]
2020-05-22 23:02:45,092 DEBUG: CREATE TABLE IF NOT EXISTS state (inode INTEGER PRIMARY KEY, mtime TEXT NOT NULL, size TEXT NOT NULL, md5 TEXT NOT NULL, timestamp TEXT NOT NULL)
2020-05-22 23:02:45,093 DEBUG: CREATE TABLE IF NOT EXISTS state_info (count INTEGER)
2020-05-22 23:02:45,093 DEBUG: CREATE TABLE IF NOT EXISTS link_state (path TEXT PRIMARY KEY, inode INTEGER NOT NULL, mtime TEXT NOT NULL)
2020-05-22 23:02:45,093 DEBUG: INSERT OR IGNORE INTO state_info (count) SELECT 0 WHERE NOT EXISTS (SELECT * FROM state_info)
2020-05-22 23:02:45,093 DEBUG: PRAGMA user_version = 3;
2020-05-22 23:02:45,117 DEBUG: Assuming '/home/hardik/src/example-get-started/.dvc/cache/42/c7025fc0edeb174069280d17add2d4.dir' is unchanged since it is read-only
2020-05-22 23:02:45,117 DEBUG: Assuming '/home/hardik/src/example-get-started/.dvc/cache/42/c7025fc0edeb174069280d17add2d4.dir' is unchanged since it is read-only
2020-05-22 23:02:45,117 DEBUG: Assuming '/home/hardik/src/example-get-started/.dvc/cache/68/36f797f3924fb46fcfd6b9f6aa6416.dir' is unchanged since it is read-only
2020-05-22 23:02:45,118 DEBUG: Assuming '/home/hardik/src/example-get-started/.dvc/cache/68/36f797f3924fb46fcfd6b9f6aa6416.dir' is unchanged since it is read-only
2020-05-22 23:02:45,184 DEBUG: SELECT count from state_info WHERE rowid=?
2020-05-22 23:02:45,184 DEBUG: fetched: [(7,)]
2020-05-22 23:02:45,184 DEBUG: UPDATE state_info SET count = ? WHERE rowid = ?
2020-05-22 23:02:45,197 ERROR: unexpected error
------------------------------------------------------------
Traceback (most recent call last):
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/main.py", line 49, in main
    ret = cmd.run()
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/command/data_sync.py", line 75, in run
    recursive=self.args.recursive,
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/repo/__init__.py", line 30, in wrapper
    ret = f(repo, *args, **kwargs)
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/repo/__init__.py", line 536, in fetch
    return self._fetch(*args, **kwargs)
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/repo/fetch.py", line 45, in _fetch
    recursive=recursive,
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/repo/__init__.py", line 295, in used_cache
    filter_info=filter_info,
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/stage/__init__.py", line 761, in get_used_cache
    cache.update(out.get_used_cache(*args, **kwargs))
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/output/base.py", line 449, in get_used_cache
    self.checksum, self._collect_used_dir_cache(**kwargs),
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/output/base.py", line 363, in _collect_used_dir_cache
    if self.cache.changed_cache_file(self.checksum):
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/remote/base.py", line 805, in changed_cache_file
    if self.is_protected(cache_info):
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/remote/local.py", line 719, in is_protected
    if not self.exists(path_info):
  File "/home/hardik/miniconda3/lib/python3.7/site-packages/dvc/remote/local.py", line 97, in exists
    assert is_working_tree(self.repo.tree)
AssertionError
------------------------------------------------------------

Having any troubles? Hit us up at https://dvc.org/support, we are always happy to help!
```


Discussion:
Would be automatically solved (or close to it) by https://github.com/iterative/dvc/issues/3811 
This is fixed after the changes to #3811, but we should have test cases for push with `all-commits`/`all-branches`/`all-tags` enabled
Seems very similar to #3812 on first glance (noted by the author of this issue in https://github.com/iterative/dvc.org/issues/528#issuecomment-632835927).
After changes for #3811 we still fail with a different error:
```
❯ dvc fetch -aT
ERROR: unexpected error - [Errno 2] No such file

...
2020-05-25 15:00:59,632 DEBUG: fetched: [(0,)]
2020-05-25 15:00:59,635 ERROR: unexpected error - [Errno 2] No such file
------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/pmrowla/git/dvc/dvc/main.py", line 53, in main
    ret = cmd.run()
  File "/Users/pmrowla/git/dvc/dvc/command/data_sync.py", line 79, in run
    run_cache=self.args.run_cache,
  File "/Users/pmrowla/git/dvc/dvc/repo/__init__.py", line 25, in wrapper
    ret = f(repo, *args, **kwargs)
  File "/Users/pmrowla/git/dvc/dvc/repo/__init__.py", line 527, in fetch
    return self._fetch(*args, **kwargs)
  File "/Users/pmrowla/git/dvc/dvc/repo/fetch.py", line 51, in _fetch
    used_run_cache=used_run_cache,
  File "/Users/pmrowla/git/dvc/dvc/repo/__init__.py", line 302, in used_cache
    filter_info=filter_info,
  File "/Users/pmrowla/git/dvc/dvc/stage/__init__.py", line 516, in get_used_cache
    cache.update(out.get_used_cache(*args, **kwargs))
  File "/Users/pmrowla/git/dvc/dvc/output/base.py", line 470, in get_used_cache
    self.checksum, self.collect_used_dir_cache(**kwargs),
  File "/Users/pmrowla/git/dvc/dvc/output/base.py", line 391, in collect_used_dir_cache
    self.get_dir_cache(jobs=jobs, remote=remote)
  File "/Users/pmrowla/git/dvc/dvc/output/base.py", line 358, in get_dir_cache
    if self.cache.changed_cache_file(self.checksum):
  File "/Users/pmrowla/git/dvc/dvc/remote/base.py", line 881, in changed_cache_file
    actual = self.get_checksum(cache_info)
  File "/Users/pmrowla/git/dvc/dvc/remote/base.py", line 350, in get_checksum
    checksum = self.state.get(path_info)
  File "/Users/pmrowla/git/dvc/dvc/state.py", line 400, in get
    actual_mtime, actual_size = get_mtime_and_size(path, self.repo.tree)
  File "/Users/pmrowla/git/dvc/dvc/utils/fs.py", line 53, in get_mtime_and_size
    base_stat = tree.stat(path)
  File "/Users/pmrowla/git/dvc/dvc/ignore.py", line 151, in stat
    return self.tree.stat(path)
  File "/Users/pmrowla/git/dvc/dvc/scm/git/tree.py", line 161, in stat
    raise OSError(errno.ENOENT, "No such file")
FileNotFoundError: [Errno 2] No such file
------------------------------------------------------------
```
Issue is that state currently belongs to Repo, and always uses `repo.tree`. If `repo.tree` is a `GitTree` (because brancher is being used) state lookups for local cache paths will fail because local cache doesn't exist in the `GitTree`.

State should really belong to local cache, or to the `WorkingTree` used by local cache.

Resolving patch:
diff --git a/dvc/state.py b/dvc/state.py
--- a/dvc/state.py
+++ b/dvc/state.py
@@ -7,6 +7,7 @@
 from urllib.parse import urlencode, urlunparse
 
 from dvc.exceptions import DvcException
+from dvc.scm.tree import WorkingTree
 from dvc.utils import current_timestamp, relpath, to_chunks
 from dvc.utils.fs import get_inode, get_mtime_and_size, remove
 
@@ -91,6 +92,7 @@ class State:  # pylint: disable=too-many-instance-attributes
     def __init__(self, repo):
         self.repo = repo
         self.root_dir = repo.root_dir
+        self.tree = WorkingTree(self.root_dir)
 
         state_config = repo.config.get("state", {})
         self.row_limit = state_config.get("row_limit", self.STATE_ROW_LIMIT)
@@ -364,9 +366,7 @@ def save(self, path_info, checksum):
         assert checksum is not None
         assert os.path.exists(path_info)
 
-        actual_mtime, actual_size = get_mtime_and_size(
-            path_info, self.repo.tree
-        )
+        actual_mtime, actual_size = get_mtime_and_size(path_info, self.tree)
         actual_inode = get_inode(path_info)
 
         existing_record = self.get_state_record_for_inode(actual_inode)
@@ -397,7 +397,7 @@ def get(self, path_info):
         if not os.path.exists(path):
             return None
 
-        actual_mtime, actual_size = get_mtime_and_size(path, self.repo.tree)
+        actual_mtime, actual_size = get_mtime_and_size(path, self.tree)
         actual_inode = get_inode(path)
 
         existing_record = self.get_state_record_for_inode(actual_inode)
@@ -423,7 +423,7 @@ def save_link(self, path_info):
         if not os.path.exists(path_info):
             return
 
-        mtime, _ = get_mtime_and_size(path_info, self.repo.tree)
+        mtime, _ = get_mtime_and_size(path_info, self.tree)
         inode = get_inode(path_info)
         relative_path = relpath(path_info, self.root_dir)
 
@@ -450,7 +450,7 @@ def get_unused_links(self, used):
                 continue
 
             actual_inode = get_inode(path)
-            actual_mtime, _ = get_mtime_and_size(path, self.repo.tree)
+            actual_mtime, _ = get_mtime_and_size(path, self.tree)
 
             if (inode, mtime) == (actual_inode, actual_mtime):
                 logger.debug("Removing '%s' as unused link.", path)


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
@@ -912,3 +912,23 @@ def test_pull_stats(tmp_dir, dvc, caplog, setup_remote):
     with caplog.at_level(level=logging.INFO, logger="dvc"):
         main(["pull"])
     assert "Everything is up to date." in caplog.text
+
+
+@pytest.mark.parametrize(
+    "key,expected", [("all_tags", 2), ("all_branches", 3), ("all_commits", 3)]
+)
+def test_push_pull_all(tmp_dir, scm, dvc, setup_remote, key, expected):
+    setup_remote(dvc)
+    tmp_dir.dvc_gen({"foo": "foo"}, commit="first")
+    scm.tag("v1")
+    dvc.remove("foo.dvc")
+    tmp_dir.dvc_gen({"bar": "bar"}, commit="second")
+    scm.tag("v2")
+    with tmp_dir.branch("branch", new=True):
+        dvc.remove("bar.dvc")
+        tmp_dir.dvc_gen({"baz": "baz"}, commit="branch")
+
+    assert dvc.push(**{key: True}) == expected
+
+    clean(["foo", "bar", "baz"], dvc)
+    assert dvc.pull(**{key: True})["fetched"] == expected