pared: Note to self: what about resetting dvcignore of CleanTree
pared: @Suor
>It excludes *.backup both within a repo and in external deps, which is a useful feature. It also excludes work-data in repo root, repo nested dirs (not intended) and external deps, like a network share (a surprising behavior).

Did you prepare some reproduction script? I prepared a little test and both statements seem not to be valid.:

```
def test_ignore_external(tmp_dir, scm, dvc):
    tmp_dir.gen(
        {
            "dir": {
                "subdir": {
                    ".dvcignore": "*.backup\nwork-data",
                    "x.backup": "x backup",
                    "work-data": "content"
                },
                "subdir_external": {"y.backup": "y.backup"},
                "work-data": "work-data",
            }
        }
    )

    result = [e for e in dvc.tree.walk("dir")]
```
If you analyze `result` content you will see that only `x.backup` and `work-content` in `subdir is ignored`.

Also, as to ignoring external dependencies: AFAIK we do not support ignoring them. (some context is here: #2161)
Suor: @pared you are right external deps/outs are not affected, we only get surprising behavior by ignoring nested, but this is what git also does. Checked current behavior with a couple of tests https://github.com/iterative/dvc/pull/3003

The principles still stand. We still need `walk_files()` in `RemoteLOCAL.list_cache_path()` all the other code should use trees either clean or unfiltered. `dvc_walk()` should be removed. Ignore tests should be changed to use either `dvc.tree.walk_files()` or `RemoteLOCAL.walk_files()` instead of `walk_files()` function.

BTW, in the PR you've linked here I've stated this design vulnerability issue :) https://github.com/iterative/dvc/pull/2161#pullrequestreview-253215049
pared: @Suor sure, I do not oppose removing dvc_walk and walk_files, just wanted to make sure we are on the same page.
pared: @efiop removed FIXME, also created #3011 
diff --git a/dvc/ignore.py b/dvc/ignore.py
index fbfcff3ac..64f79068c 100644
--- a/dvc/ignore.py
+++ b/dvc/ignore.py
@@ -3,9 +3,11 @@ from __future__ import unicode_literals
 import logging
 import os
 
+from funcy import cached_property
 from pathspec import PathSpec
 from pathspec.patterns import GitWildMatchPattern
 
+from dvc.scm.tree import BaseTree
 from dvc.utils import relpath
 
 logger = logging.getLogger(__name__)
@@ -72,13 +74,12 @@ class DvcIgnoreDirs(DvcIgnore):
 
 
 class DvcIgnoreFilter(object):
-    def __init__(self, root_dir, tree):
+    def __init__(self, tree):
         self.tree = tree
         self.ignores = {DvcIgnoreDirs([".git", ".hg", ".dvc"])}
-        self._update(root_dir)
-        for root, dirs, _ in self.tree.walk(root_dir, dvcignore=self):
-            for d in dirs:
-                self._update(os.path.join(root, d))
+        for root, dirs, files in self.tree.walk(self.tree.tree_root):
+            self._update(root)
+            dirs[:], files[:] = self(root, dirs, files)
 
     def _update(self, dirname):
         ignore_file_path = os.path.join(dirname, DvcIgnore.DVCIGNORE_FILE)
@@ -90,3 +91,39 @@ class DvcIgnoreFilter(object):
             dirs, files = ignore(root, dirs, files)
 
         return dirs, files
+
+
+class CleanTree(BaseTree):
+    def __init__(self, tree):
+        self.tree = tree
+
+    @cached_property
+    def dvcignore(self):
+        return DvcIgnoreFilter(self.tree)
+
+    @property
+    def tree_root(self):
+        return self.tree.tree_root
+
+    def open(self, path, mode="r", encoding="utf-8"):
+        return self.tree.open(path, mode, encoding)
+
+    def exists(self, path):
+        return self.tree.exists(path)
+
+    def isdir(self, path):
+        return self.tree.isdir(path)
+
+    def isfile(self, path):
+        return self.tree.isfile(path)
+
+    def walk(self, top, topdown=True):
+        for root, dirs, files in self.tree.walk(top, topdown):
+            dirs[:], files[:] = self.dvcignore(root, dirs, files)
+
+            yield root, dirs, files
+
+    def walk_files(self, top):
+        for root, _, files in self.walk(top):
+            for file in files:
+                yield os.path.join(root, file)
diff --git a/dvc/remote/local.py b/dvc/remote/local.py
index 9c68e3a80..56a138114 100644
--- a/dvc/remote/local.py
+++ b/dvc/remote/local.py
@@ -13,6 +13,7 @@ from dvc.config import Config
 from dvc.exceptions import DownloadError
 from dvc.exceptions import DvcException
 from dvc.exceptions import UploadError
+from dvc.ignore import CleanTree
 from dvc.path_info import PathInfo
 from dvc.progress import Tqdm
 from dvc.remote.base import RemoteBASE
@@ -21,6 +22,7 @@ from dvc.remote.base import STATUS_MAP
 from dvc.remote.base import STATUS_MISSING
 from dvc.remote.base import STATUS_NEW
 from dvc.scheme import Schemes
+from dvc.scm.tree import WorkingTree
 from dvc.system import System
 from dvc.utils import copyfile
 from dvc.utils import file_md5
@@ -83,7 +85,7 @@ class RemoteLOCAL(RemoteBASE):
 
     def list_cache_paths(self):
         assert self.path_info is not None
-        return walk_files(self.path_info, None)
+        return walk_files(self.path_info)
 
     def get(self, md5):
         if not md5:
@@ -138,7 +140,11 @@ class RemoteLOCAL(RemoteBASE):
         return os.path.getsize(fspath_py35(path_info))
 
     def walk_files(self, path_info):
-        for fname in walk_files(path_info, self.repo.dvcignore):
+        assert isinstance(self.repo.tree, CleanTree) and isinstance(
+            self.repo.tree.tree, WorkingTree
+        )
+
+        for fname in self.repo.tree.walk_files(path_info):
             yield PathInfo(fname)
 
     def get_file_checksum(self, path_info):
@@ -427,7 +433,9 @@ class RemoteLOCAL(RemoteBASE):
         os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
 
     def _unprotect_dir(self, path):
-        for fname in walk_files(path, self.repo.dvcignore):
+        assert isinstance(self.repo.tree, CleanTree)
+
+        for fname in self.repo.tree.walk_files(path):
             RemoteLOCAL._unprotect_file(fname)
 
     def unprotect(self, path_info):
diff --git a/dvc/repo/__init__.py b/dvc/repo/__init__.py
index 3df374160..62efa14e7 100644
--- a/dvc/repo/__init__.py
+++ b/dvc/repo/__init__.py
@@ -5,6 +5,8 @@ import os
 from contextlib import contextmanager
 from functools import wraps
 from itertools import chain
+
+from dvc.ignore import CleanTree
 from dvc.utils.compat import FileNotFoundError, fspath_py35, open as _open
 
 from funcy import cached_property
@@ -15,7 +17,6 @@ from dvc.exceptions import (
     NotDvcRepoError,
     OutputNotFoundError,
 )
-from dvc.ignore import DvcIgnoreFilter
 from dvc.path_info import PathInfo
 from dvc.remote.base import RemoteActionNotImplemented
 from dvc.utils import relpath
@@ -84,7 +85,7 @@ class Repo(object):
 
         self.scm = SCM(self.root_dir)
 
-        self.tree = WorkingTree(self.root_dir)
+        self.tree = CleanTree(WorkingTree(self.root_dir))
 
         self.tmp_dir = os.path.join(self.dvc_dir, "tmp")
         makedirs(self.tmp_dir, exist_ok=True)
@@ -391,9 +392,7 @@ class Repo(object):
         stages = []
         outs = []
 
-        for root, dirs, files in self.tree.walk(
-            self.root_dir, dvcignore=self.dvcignore
-        ):
+        for root, dirs, files in self.tree.walk(self.root_dir):
             for fname in files:
                 path = os.path.join(root, fname)
                 if not Stage.is_valid_filename(path):
@@ -487,10 +486,6 @@ class Repo(object):
 
             return _open(cache_file, mode=mode, encoding=encoding)
 
-    @cached_property
-    def dvcignore(self):
-        return DvcIgnoreFilter(self.root_dir, self.tree)
-
     def close(self):
         self.scm.close()
 
diff --git a/dvc/repo/add.py b/dvc/repo/add.py
index 8fe180d41..a94f0ef1a 100644
--- a/dvc/repo/add.py
+++ b/dvc/repo/add.py
@@ -12,7 +12,6 @@ from dvc.progress import Tqdm
 from dvc.repo.scm_context import scm_context
 from dvc.stage import Stage
 from dvc.utils import LARGE_DIR_SIZE
-from dvc.utils import walk_files
 
 logger = logging.getLogger(__name__)
 
@@ -68,7 +67,7 @@ def _find_all_targets(repo, target, recursive):
     if os.path.isdir(target) and recursive:
         return [
             fname
-            for fname in walk_files(target, repo.dvcignore)
+            for fname in repo.tree.walk_files(target)
             if not repo.is_dvc_internal(fname)
             if not Stage.is_stage_file(fname)
             if not repo.scm.belongs_to_scm(fname)
diff --git a/dvc/repo/brancher.py b/dvc/repo/brancher.py
index acca98c2c..7213cb37a 100644
--- a/dvc/repo/brancher.py
+++ b/dvc/repo/brancher.py
@@ -1,5 +1,6 @@
 from funcy import group_by
 
+from dvc.ignore import CleanTree
 from dvc.scm.tree import WorkingTree
 
 
@@ -34,7 +35,7 @@ def brancher(  # noqa: E302
 
     scm = self.scm
 
-    self.tree = WorkingTree(self.root_dir)
+    self.tree = CleanTree(WorkingTree(self.root_dir))
     yield "working tree"
 
     if all_commits:
@@ -58,7 +59,7 @@ def brancher(  # noqa: E302
     # code which might expect the tree on which exception was raised to
     # stay in place. This behavior is a subject to change.
     for sha, names in group_by(scm.resolve_rev, revs).items():
-        self.tree = scm.get_tree(sha)
+        self.tree = CleanTree(scm.get_tree(sha))
         yield ", ".join(names)
 
     self.tree = saved_tree
diff --git a/dvc/repo/diff.py b/dvc/repo/diff.py
index 6b681bedc..b9c35f2a8 100644
--- a/dvc/repo/diff.py
+++ b/dvc/repo/diff.py
@@ -4,6 +4,7 @@ import os
 from errno import ENOENT
 
 import dvc.logger as logger
+from dvc.ignore import CleanTree
 from . import locked
 from dvc.scm.base import FileNotInCommitError
 from dvc.scm.git import DIFF_A_REF
@@ -137,9 +138,9 @@ def _is_dir(path, a_outs, b_outs):
 
 
 def _get_diff_outs(self, diff_dct):
-    self.tree = diff_dct[DIFF_A_TREE]
+    self.tree = CleanTree(diff_dct[DIFF_A_TREE])
     a_outs = {str(out): out for st in self.stages for out in st.outs}
-    self.tree = diff_dct[DIFF_B_TREE]
+    self.tree = CleanTree(diff_dct[DIFF_B_TREE])
     b_outs = {str(out): out for st in self.stages for out in st.outs}
     outs_paths = set(a_outs.keys())
     outs_paths.update(b_outs.keys())
diff --git a/dvc/scm/git/tree.py b/dvc/scm/git/tree.py
index 0601c322d..d775a5433 100644
--- a/dvc/scm/git/tree.py
+++ b/dvc/scm/git/tree.py
@@ -136,7 +136,7 @@ class GitTree(BaseTree):
         if not topdown:
             yield os.path.normpath(tree.abspath), dirs, nondirs
 
-    def walk(self, top, topdown=True, dvcignore=None):
+    def walk(self, top, topdown=True):
         """Directory tree generator.
 
         See `os.walk` for the docs. Differences:
diff --git a/dvc/scm/tree.py b/dvc/scm/tree.py
index 0931a59d0..5e28beae9 100644
--- a/dvc/scm/tree.py
+++ b/dvc/scm/tree.py
@@ -1,7 +1,6 @@
 import os
 
-from dvc.utils import dvc_walk
-from dvc.utils.compat import open
+from dvc.utils.compat import open, fspath
 
 
 class BaseTree(object):
@@ -23,7 +22,7 @@ class BaseTree(object):
     def isfile(self, path):
         """Test whether a path is a regular file"""
 
-    def walk(self, top, topdown=True, dvcignore=None):
+    def walk(self, top, topdown=True):
         """Directory tree generator.
 
         See `os.walk` for the docs. Differences:
@@ -58,7 +57,7 @@ class WorkingTree(BaseTree):
         """Test whether a path is a regular file"""
         return os.path.isfile(path)
 
-    def walk(self, top, topdown=True, dvcignore=None):
+    def walk(self, top, topdown=True):
         """Directory tree generator.
 
         See `os.walk` for the docs. Differences:
@@ -66,12 +65,12 @@ class WorkingTree(BaseTree):
         - it could raise exceptions, there is no onerror argument
         """
 
-        assert dvcignore
+        top = fspath(top)
 
         def onerror(e):
             raise e
 
-        for root, dirs, files in dvc_walk(
-            os.path.abspath(top), dvcignore, topdown=topdown, onerror=onerror
+        for root, dirs, files in os.walk(
+            top, topdown=topdown, onerror=onerror
         ):
             yield os.path.normpath(root), dirs, files
diff --git a/dvc/state.py b/dvc/state.py
index 9803e40c2..5f10654d1 100644
--- a/dvc/state.py
+++ b/dvc/state.py
@@ -378,7 +378,7 @@ class State(object):  # pylint: disable=too-many-instance-attributes
         assert os.path.exists(fspath_py35(path_info))
 
         actual_mtime, actual_size = get_mtime_and_size(
-            path_info, self.repo.dvcignore
+            path_info, self.repo.tree
         )
         actual_inode = get_inode(path_info)
 
@@ -410,9 +410,7 @@ class State(object):  # pylint: disable=too-many-instance-attributes
         if not os.path.exists(path):
             return None
 
-        actual_mtime, actual_size = get_mtime_and_size(
-            path, self.repo.dvcignore
-        )
+        actual_mtime, actual_size = get_mtime_and_size(path, self.repo.tree)
         actual_inode = get_inode(path)
 
         existing_record = self.get_state_record_for_inode(actual_inode)
@@ -439,7 +437,7 @@ class State(object):  # pylint: disable=too-many-instance-attributes
         if not os.path.exists(path):
             return
 
-        mtime, _ = get_mtime_and_size(path, self.repo.dvcignore)
+        mtime, _ = get_mtime_and_size(path, self.repo.tree)
         inode = get_inode(path)
         relative_path = relpath(path, self.root_dir)
 
@@ -469,7 +467,7 @@ class State(object):  # pylint: disable=too-many-instance-attributes
                 continue
 
             actual_inode = get_inode(path)
-            actual_mtime, _ = get_mtime_and_size(path, self.repo.dvcignore)
+            actual_mtime, _ = get_mtime_and_size(path, self.repo.tree)
 
             if inode == actual_inode and mtime == actual_mtime:
                 logger.debug("Removing '{}' as unused link.".format(path))
diff --git a/dvc/utils/__init__.py b/dvc/utils/__init__.py
index 92fd34a8a..b8db58e1a 100644
--- a/dvc/utils/__init__.py
+++ b/dvc/utils/__init__.py
@@ -288,25 +288,8 @@ def to_yaml_string(data):
     return stream.getvalue()
 
 
-def dvc_walk(top, dvcignore, topdown=True, onerror=None, followlinks=False):
-    """
-    Proxy for `os.walk` directory tree generator.
-    Utilizes DvcIgnoreFilter functionality.
-    """
-    top = fspath_py35(top)
-
-    for root, dirs, files in os.walk(
-        top, topdown=topdown, onerror=onerror, followlinks=followlinks
-    ):
-
-        if dvcignore:
-            dirs[:], files[:] = dvcignore(root, dirs, files)
-
-        yield root, dirs, files
-
-
-def walk_files(directory, dvcignore):
-    for root, _, files in dvc_walk(directory, dvcignore):
+def walk_files(directory):
+    for root, _, files in os.walk(fspath(directory)):
         for f in files:
             yield os.path.join(root, f)
 
diff --git a/dvc/utils/fs.py b/dvc/utils/fs.py
index 26ee05506..4f1a8013a 100644
--- a/dvc/utils/fs.py
+++ b/dvc/utils/fs.py
@@ -15,7 +15,6 @@ from dvc.utils import dict_md5
 from dvc.utils import fspath
 from dvc.utils import fspath_py35
 from dvc.utils import relpath
-from dvc.utils import walk_files
 from dvc.utils.compat import str
 
 
@@ -35,11 +34,15 @@ def get_inode(path):
     return inode
 
 
-def get_mtime_and_size(path, dvcignore):
+def get_mtime_and_size(path, tree):
+    from dvc.ignore import CleanTree
+
+    assert isinstance(tree, CleanTree)
+
     if os.path.isdir(fspath_py35(path)):
         size = 0
         files_mtimes = {}
-        for file_path in walk_files(path, dvcignore):
+        for file_path in tree.walk_files(path):
             try:
                 stat = os.stat(file_path)
             except OSError as exc:

dvc: dvc_walk() and related things are broken by design
A few things:
- when we collect stages in a branch dvcignore is not used at all, i.e. we go into `.dvc` and other ignored dirs
- `dvc_walk()` and `walk_files()` always walk the working dir, but might accept `dvcignore` built from a branch. The results will make no sense.

We may approach this from two sides:
- wrap dvcignore into a tree, i.e. `dvctree = TreeWithIgnores(some_tree); dvctree.walk(...)`.
- check that we only use `dvcignore` built from working dir if we `os.walk()`.
diff --git a/tests/func/test_checkout.py b/tests/func/test_checkout.py
index 797e42470..2baa455f9 100644
--- a/tests/func/test_checkout.py
+++ b/tests/func/test_checkout.py
@@ -18,8 +18,7 @@ from dvc.stage import Stage
 from dvc.stage import StageFileBadNameError
 from dvc.stage import StageFileDoesNotExistError
 from dvc.system import System
-from dvc.utils import relpath
-from dvc.utils import walk_files
+from dvc.utils import relpath, walk_files
 from dvc.utils.compat import is_py2
 from dvc.utils.stage import dump_stage_file
 from dvc.utils.stage import load_stage_file
@@ -137,7 +136,7 @@ class CheckoutBase(TestDvcGit):
         paths = [
             path
             for output in stage["outs"]
-            for path in walk_files(output["path"], self.dvc.dvcignore)
+            for path in self.dvc.tree.walk_files(output["path"])
         ]
 
         return [
@@ -520,6 +519,4 @@ def test_partial_checkout(tmp_dir, dvc, target):
     tmp_dir.dvc_gen({"dir": {"subdir": {"file": "file"}, "other": "other"}})
     shutil.rmtree("dir")
     dvc.checkout([target])
-    assert list(walk_files("dir", None)) == [
-        os.path.join("dir", "subdir", "file")
-    ]
+    assert list(walk_files("dir")) == [os.path.join("dir", "subdir", "file")]
diff --git a/tests/func/test_ignore.py b/tests/func/test_ignore.py
index 3bf878a4a..c581cfd74 100644
--- a/tests/func/test_ignore.py
+++ b/tests/func/test_ignore.py
@@ -5,10 +5,11 @@ import shutil
 import pytest
 
 from dvc.exceptions import DvcIgnoreInCollectedDirError
-from dvc.ignore import DvcIgnore, DvcIgnoreDirs, DvcIgnorePatterns
+from dvc.ignore import DvcIgnore, DvcIgnoreDirs, DvcIgnorePatterns, CleanTree
 from dvc.scm.tree import WorkingTree
-from dvc.utils import walk_files, relpath
-from dvc.utils.compat import fspath, fspath_py35
+from dvc.utils import relpath
+from dvc.utils.compat import fspath_py35
+from dvc.utils.compat import fspath
 from dvc.utils.fs import get_mtime_and_size
 from dvc.remote import RemoteLOCAL
 
@@ -20,10 +21,10 @@ def test_ignore(tmp_dir, dvc, monkeypatch):
     tmp_dir.gen({"dir": {"ignored": "text", "other": "text2"}})
     tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "dir/ignored")
 
-    assert _files_set("dir", dvc.dvcignore) == {"dir/other"}
+    assert _files_set("dir", dvc.tree) == {"dir/other"}
 
     monkeypatch.chdir("dir")
-    assert _files_set(".", dvc.dvcignore) == {"./other"}
+    assert _files_set(".", dvc.tree) == {"./other"}
 
 
 def test_ignore_unicode(tmp_dir, dvc):
@@ -35,27 +36,27 @@ def test_ignore_unicode(tmp_dir, dvc):
 
     tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "dir/тест")
 
-    assert _files_set("dir", dvc.dvcignore) == {"dir/other"}
+    assert _files_set("dir", dvc.tree) == {"dir/other"}
 
 
 def test_rename_ignored_file(tmp_dir, dvc):
     tmp_dir.gen({"dir": {"ignored": "...", "other": "text"}})
 
     tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "ignored*")
-    mtime, size = get_mtime_and_size("dir", dvc.dvcignore)
+    mtime, size = get_mtime_and_size("dir", dvc.tree)
 
     shutil.move("dir/ignored", "dir/ignored_new")
-    new_mtime, new_size = get_mtime_and_size("dir", dvc.dvcignore)
+    new_mtime, new_size = get_mtime_and_size("dir", dvc.tree)
 
     assert new_mtime == mtime and new_size == size
 
 
 def test_rename_file(tmp_dir, dvc):
     tmp_dir.gen({"dir": {"foo": "foo", "bar": "bar"}})
-    mtime, size = get_mtime_and_size("dir", dvc.dvcignore)
+    mtime, size = get_mtime_and_size("dir", dvc.tree)
 
     shutil.move("dir/foo", "dir/foo_new")
-    new_mtime, new_size = get_mtime_and_size("dir", dvc.dvcignore)
+    new_mtime, new_size = get_mtime_and_size("dir", dvc.tree)
 
     assert new_mtime != mtime and new_size == size
 
@@ -64,20 +65,20 @@ def test_remove_ignored_file(tmp_dir, dvc):
     tmp_dir.gen({"dir": {"ignored": "...", "other": "text"}})
     tmp_dir.gen(DvcIgnore.DVCIGNORE_FILE, "dir/ignored")
 
-    mtime, size = get_mtime_and_size("dir", dvc.dvcignore)
+    mtime, size = get_mtime_and_size("dir", dvc.tree)
 
     os.remove("dir/ignored")
-    new_mtime, new_size = get_mtime_and_size("dir", dvc.dvcignore)
+    new_mtime, new_size = get_mtime_and_size("dir", dvc.tree)
 
     assert new_mtime == mtime and new_size == size
 
 
 def test_remove_file(tmp_dir, dvc):
     tmp_dir.gen({"dir": {"foo": "foo", "bar": "bar"}})
-    mtime, size = get_mtime_and_size("dir", dvc.dvcignore)
+    mtime, size = get_mtime_and_size("dir", dvc.tree)
 
     os.remove("dir/foo")
-    new_mtime, new_size = get_mtime_and_size("dir", dvc.dvcignore)
+    new_mtime, new_size = get_mtime_and_size("dir", dvc.tree)
 
     assert new_mtime != mtime and new_size != size
 
@@ -99,7 +100,7 @@ def test_ignore_collecting_dvcignores(tmp_dir, dvc, dname):
     ignore_file = tmp_dir / dname / DvcIgnore.DVCIGNORE_FILE
     ignore_file.write_text("foo")
 
-    assert dvc.dvcignore.ignores == {
+    assert dvc.tree.dvcignore.ignores == {
         DvcIgnoreDirs([".git", ".hg", ".dvc"]),
         DvcIgnorePatterns(fspath(top_ignore_file), WorkingTree(dvc.root_dir)),
     }
@@ -112,14 +113,18 @@ def test_ignore_on_branch(tmp_dir, scm, dvc):
     tmp_dir.scm_gen(DvcIgnore.DVCIGNORE_FILE, "foo", commit="add ignore")
 
     scm.checkout("master")
-    assert _files_set(".", dvc.dvcignore) == {"./foo", "./bar"}
+    assert _files_set(".", dvc.tree) == {"./foo", "./bar"}
 
-    dvc.tree = scm.get_tree("branch")
-    assert _files_set(".", dvc.dvcignore) == {"./bar"}
+    tree = CleanTree(scm.get_tree("branch"))
+
+    assert _files_set(".", tree) == {
+        to_posixpath(os.path.join(tree.tree_root, DvcIgnore.DVCIGNORE_FILE)),
+        to_posixpath(os.path.join(tree.tree_root, "bar")),
+    }
 
 
-def _files_set(root, dvcignore):
-    return {to_posixpath(f) for f in walk_files(root, dvcignore)}
+def _files_set(root, tree):
+    return {to_posixpath(f) for f in tree.walk_files(root)}
 
 
 def test_match_nested(tmp_dir, dvc):
diff --git a/tests/func/test_tree.py b/tests/func/test_tree.py
index c2ae316c2..ee38be6ae 100644
--- a/tests/func/test_tree.py
+++ b/tests/func/test_tree.py
@@ -3,7 +3,7 @@ from __future__ import unicode_literals
 
 from os.path import join
 
-from dvc.ignore import DvcIgnoreFilter
+from dvc.ignore import CleanTree
 from dvc.scm import SCM
 from dvc.scm.git import GitTree
 from dvc.scm.tree import WorkingTree
@@ -109,9 +109,8 @@ class AssertWalkEqualMixin(object):
 class TestWalkInNoSCM(AssertWalkEqualMixin, TestDir):
     def test(self):
         tree = WorkingTree(self._root_dir)
-        dvcignore = DvcIgnoreFilter(self.root_dir, tree)
         self.assertWalkEqual(
-            tree.walk(self._root_dir, dvcignore=dvcignore),
+            tree.walk(self._root_dir),
             [
                 (
                     self._root_dir,
@@ -129,48 +128,26 @@ class TestWalkInNoSCM(AssertWalkEqualMixin, TestDir):
 
     def test_subdir(self):
         tree = WorkingTree(self._root_dir)
-        dvcignore = DvcIgnoreFilter(self.root_dir, tree)
         self.assertWalkEqual(
-            tree.walk(join("data_dir", "data_sub_dir"), dvcignore=dvcignore),
-            [
-                (
-                    join(self._root_dir, "data_dir", "data_sub_dir"),
-                    [],
-                    ["data_sub"],
-                )
-            ],
+            tree.walk(join("data_dir", "data_sub_dir")),
+            [(join("data_dir", "data_sub_dir"), [], ["data_sub"])],
         )
 
 
 class TestWalkInGit(AssertWalkEqualMixin, TestGit):
     def test_nobranch(self):
-        tree = WorkingTree(self._root_dir)
-        dvcignore = DvcIgnoreFilter(self._root_dir, tree)
+        tree = CleanTree(WorkingTree(self._root_dir))
         self.assertWalkEqual(
-            tree.walk(".", dvcignore=dvcignore),
+            tree.walk("."),
             [
-                (
-                    self._root_dir,
-                    ["data_dir"],
-                    ["bar", "тест", "code.py", "foo"],
-                ),
-                (join(self._root_dir, "data_dir"), ["data_sub_dir"], ["data"]),
-                (
-                    join(self._root_dir, "data_dir", "data_sub_dir"),
-                    [],
-                    ["data_sub"],
-                ),
+                (".", ["data_dir"], ["bar", "тест", "code.py", "foo"]),
+                (join("data_dir"), ["data_sub_dir"], ["data"]),
+                (join("data_dir", "data_sub_dir"), [], ["data_sub"]),
             ],
         )
         self.assertWalkEqual(
-            tree.walk(join("data_dir", "data_sub_dir"), dvcignore=dvcignore),
-            [
-                (
-                    join(self._root_dir, "data_dir", "data_sub_dir"),
-                    [],
-                    ["data_sub"],
-                )
-            ],
+            tree.walk(join("data_dir", "data_sub_dir")),
+            [(join("data_dir", "data_sub_dir"), [], ["data_sub"])],
         )
 
     def test_branch(self):
diff --git a/tests/unit/remote/test_remote_dir.py b/tests/unit/remote/test_remote_dir.py
index 6c868a1a6..b6d6c337b 100644
--- a/tests/unit/remote/test_remote_dir.py
+++ b/tests/unit/remote/test_remote_dir.py
@@ -142,7 +142,7 @@ def test_download_dir(remote, tmpdir):
     remote.download(remote.path_info / "data", to_info)
     assert os.path.isdir(path)
     data_dir = tmpdir / "data"
-    assert len(list(walk_files(path, None))) == 7
+    assert len(list(walk_files(path))) == 7
     assert (data_dir / "alice").read_text(encoding="utf-8") == "alice"
     assert (data_dir / "alpha").read_text(encoding="utf-8") == "alpha"
     assert (data_dir / "subdir-file.txt").read_text(
diff --git a/tests/unit/utils/test_fs.py b/tests/unit/utils/test_fs.py
index 809212907..bc1547629 100644
--- a/tests/unit/utils/test_fs.py
+++ b/tests/unit/utils/test_fs.py
@@ -5,7 +5,7 @@ import pytest
 from mock import patch
 
 import dvc
-from dvc.ignore import DvcIgnoreFilter
+from dvc.ignore import CleanTree
 from dvc.path_info import PathInfo
 from dvc.scm.tree import WorkingTree
 from dvc.system import System
@@ -23,9 +23,9 @@ from tests.utils import spy
 
 class TestMtimeAndSize(TestDir):
     def test(self):
-        dvcignore = DvcIgnoreFilter(self.root_dir, WorkingTree(self.root_dir))
-        file_time, file_size = get_mtime_and_size(self.DATA, dvcignore)
-        dir_time, dir_size = get_mtime_and_size(self.DATA_DIR, dvcignore)
+        tree = CleanTree(WorkingTree(self.root_dir))
+        file_time, file_size = get_mtime_and_size(self.DATA, tree)
+        dir_time, dir_size = get_mtime_and_size(self.DATA_DIR, tree)
 
         actual_file_size = os.path.getsize(self.DATA)
         actual_dir_size = os.path.getsize(self.DATA) + os.path.getsize(
@@ -127,11 +127,9 @@ def test_get_inode(repo_dir):
 def test_path_object_and_str_are_valid_types_get_mtime_and_size(
     path, repo_dir
 ):
-    dvcignore = DvcIgnoreFilter(
-        repo_dir.root_dir, WorkingTree(repo_dir.root_dir)
-    )
-    time, size = get_mtime_and_size(path, dvcignore)
-    object_time, object_size = get_mtime_and_size(PathInfo(path), dvcignore)
+    tree = CleanTree(WorkingTree(repo_dir.root_dir))
+    time, size = get_mtime_and_size(path, tree)
+    object_time, object_size = get_mtime_and_size(PathInfo(path), tree)
     assert time == object_time
     assert size == object_size
 

pip install -e .[all]
parse_log_pytest
pytest
pytest pytest-timeout pytest-cov pytest-xdist pytest-mock flaky mock xmltodict awscli google-compute-engine Pygments collective.checkdocs flake8 psutil flake8-docstrings pydocstyle jaraco.windows mock-ssh-server moto rangehttpserver
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
aliyun-python-sdk-core==2.16.0
aliyun-python-sdk-core-v3==2.13.33
aliyun-python-sdk-kms==2.16.5
appdirs==1.4.4
atpublic==3.1.2
attrs @ file:///croot/attrs_1668696182826/work
autocommand==2.2.2
awscli==1.31.13
azure-common==1.1.28
azure-storage-blob==2.1.0
azure-storage-common==2.1.0
bcrypt==4.2.1
boto==2.49.0
boto3==1.33.13
botocore==1.33.13
cachetools==5.5.2
certifi @ file:///croot/certifi_1671487769961/work/certifi
cffi==1.15.1
charset-normalizer==3.4.1
collective.checkdocs==0.2
colorama==0.4.4
configobj==5.0.9
configparser==5.3.0
coverage==7.2.7
crcmod==1.7
cryptography==44.0.2
decorator==5.1.1
distro==1.9.0
docutils==0.16
-e git+https://github.com/iterative/dvc.git@0a3a9bf58cc2cc84e45c6cf9efc48c59e3ce1b9a#egg=dvc
execnet==2.0.2
flake8==5.0.4
flake8-docstrings==1.7.0
flaky==3.8.1
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
flufl.lock==7.1.1
funcy==2.0
future==1.0.0
gitdb==4.0.12
gitdb2==4.0.2
GitPython==3.1.44
google-api-core==1.34.1
google-api-python-client==2.166.0
google-auth==2.38.0
google-auth-httplib2==0.2.0
google-cloud-core==1.5.0
google-cloud-storage==1.19.0
google-compute-engine==2.8.13
google-crc32c==1.5.0
google-resumable-media==2.7.2
googleapis-common-protos==1.69.2
grandalf==0.6
httplib2==0.22.0
humanize==4.6.0
idna==3.10
importlib-metadata==4.2.0
importlib-resources==5.12.0
inflect==3.0.2
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
jaraco.classes==3.2.3
jaraco.collections==4.2.0
jaraco.context==4.3.0
jaraco.functools==3.7.0
jaraco.structures==2.1.0
jaraco.text==3.11.1
jaraco.ui==2.3.0
jaraco.windows==5.7.0
Jinja2==3.1.6
jmespath==0.10.0
jsonpath-ng==1.7.0
MarkupSafe==2.1.5
mccabe==0.7.0
mock==5.2.0
mock-ssh-server==0.9.1
more-itertools==9.1.0
moto==4.2.14
nanotime==0.5.2
networkx==2.3
numpy==1.21.6
oauth2client==4.1.3
oss2==2.6.1
packaging @ file:///croot/packaging_1671697413597/work
paramiko==3.5.1
path==16.6.0
pathspec==0.11.2
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
ply==3.11
protobuf==3.20.3
psutil==7.0.0
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pyarrow==0.15.1
pyasn1==0.5.1
pyasn1-modules==0.3.0
pycodestyle==2.9.1
pycparser==2.21
pycryptodome==3.22.0
pydantic==1.10.21
pydocstyle==6.3.0
PyDrive==1.3.1
pyflakes==2.5.0
Pygments==2.17.2
PyNaCl==1.5.0
pyparsing==3.1.4
pytest==7.1.2
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-timeout==2.3.1
pytest-xdist==3.5.0
python-dateutil==2.8.0
PyYAML==5.1.2
rangehttpserver==1.4.0
requests==2.31.0
responses==0.23.3
rsa==4.7.2
ruamel.yaml==0.18.10
ruamel.yaml.clib==0.2.8
s3transfer==0.8.2
shortuuid==1.0.13
six==1.17.0
smmap==5.0.2
snowballstemmer==2.2.0
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
tqdm==4.67.1
treelib==1.7.1
types-PyYAML==6.0.12.12
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
uritemplate==4.1.1
urllib3==1.26.20
voluptuous==0.14.1
Werkzeug==2.2.3
xmltodict==0.14.2
zc.lockfile==3.0.post1
zipp @ file:///croot/zipp_1672387121353/work

name: dvc
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - aliyun-python-sdk-core==2.16.0
      - aliyun-python-sdk-core-v3==2.13.33
      - aliyun-python-sdk-kms==2.16.5
      - appdirs==1.4.4
      - atpublic==3.1.2
      - autocommand==2.2.2
      - awscli==1.31.13
      - azure-common==1.1.28
      - azure-storage-blob==2.1.0
      - azure-storage-common==2.1.0
      - bcrypt==4.2.1
      - boto==2.49.0
      - boto3==1.33.13
      - botocore==1.33.13
      - cachetools==5.5.2
      - cffi==1.15.1
      - charset-normalizer==3.4.1
      - collective-checkdocs==0.2
      - colorama==0.4.4
      - configobj==5.0.9
      - configparser==5.3.0
      - coverage==7.2.7
      - crcmod==1.7
      - cryptography==44.0.2
      - decorator==5.1.1
      - distro==1.9.0
      - docutils==0.16
      - dvc==0.78.1+0a3a9b
      - execnet==2.0.2
      - flake8==5.0.4
      - flake8-docstrings==1.7.0
      - flaky==3.8.1
      - flufl-lock==7.1.1
      - funcy==2.0
      - future==1.0.0
      - gitdb==4.0.12
      - gitdb2==4.0.2
      - gitpython==3.1.44
      - google-api-core==1.34.1
      - google-api-python-client==2.166.0
      - google-auth==2.38.0
      - google-auth-httplib2==0.2.0
      - google-cloud-core==1.5.0
      - google-cloud-storage==1.19.0
      - google-compute-engine==2.8.13
      - google-crc32c==1.5.0
      - google-resumable-media==2.7.2
      - googleapis-common-protos==1.69.2
      - grandalf==0.6
      - httplib2==0.22.0
      - humanize==4.6.0
      - idna==3.10
      - importlib-metadata==4.2.0
      - importlib-resources==5.12.0
      - inflect==3.0.2
      - jaraco-classes==3.2.3
      - jaraco-collections==4.2.0
      - jaraco-context==4.3.0
      - jaraco-functools==3.7.0
      - jaraco-structures==2.1.0
      - jaraco-text==3.11.1
      - jaraco-ui==2.3.0
      - jaraco-windows==5.7.0
      - jinja2==3.1.6
      - jmespath==0.10.0
      - jsonpath-ng==1.7.0
      - markupsafe==2.1.5
      - mccabe==0.7.0
      - mock==5.2.0
      - mock-ssh-server==0.9.1
      - more-itertools==9.1.0
      - moto==4.2.14
      - nanotime==0.5.2
      - networkx==2.3
      - numpy==1.21.6
      - oauth2client==4.1.3
      - oss2==2.6.1
      - paramiko==3.5.1
      - path==16.6.0
      - pathspec==0.11.2
      - ply==3.11
      - protobuf==3.20.3
      - psutil==7.0.0
      - pyarrow==0.15.1
      - pyasn1==0.5.1
      - pyasn1-modules==0.3.0
      - pycodestyle==2.9.1
      - pycparser==2.21
      - pycryptodome==3.22.0
      - pydantic==1.10.21
      - pydocstyle==6.3.0
      - pydrive==1.3.1
      - pyflakes==2.5.0
      - pygments==2.17.2
      - pynacl==1.5.0
      - pyparsing==3.1.4
      - pytest-cov==4.1.0
      - pytest-mock==3.11.1
      - pytest-timeout==2.3.1
      - pytest-xdist==3.5.0
      - python-dateutil==2.8.0
      - pyyaml==5.1.2
      - rangehttpserver==1.4.0
      - requests==2.31.0
      - responses==0.23.3
      - rsa==4.7.2
      - ruamel-yaml==0.18.10
      - ruamel-yaml-clib==0.2.8
      - s3transfer==0.8.2
      - shortuuid==1.0.13
      - six==1.17.0
      - smmap==5.0.2
      - snowballstemmer==2.2.0
      - tqdm==4.67.1
      - treelib==1.7.1
      - types-pyyaml==6.0.12.12
      - uritemplate==4.1.1
      - urllib3==1.26.20
      - voluptuous==0.14.1
      - werkzeug==2.2.3
      - xmltodict==0.14.2
      - zc-lockfile==3.0.post1
prefix: /opt/conda/envs/dvc

tests/func/test_checkout.py::TestCheckout::test tests/func/test_checkout.py::TestCheckoutSingleStage::test tests/func/test_checkout.py::TestCheckoutCorruptedCacheFile::test tests/func/test_checkout.py::TestCheckoutCorruptedCacheDir::test tests/func/test_checkout.py::TestCmdCheckout::test tests/func/test_checkout.py::TestRemoveFilesWhenCheckout::test tests/func/test_checkout.py::TestCheckoutCleanWorkingDir::test tests/func/test_checkout.py::TestCheckoutCleanWorkingDir::test_force tests/func/test_checkout.py::TestCheckoutSelectiveRemove::test tests/func/test_checkout.py::TestGitIgnoreBasic::test tests/func/test_checkout.py::TestGitIgnoreWhenCheckout::test tests/func/test_checkout.py::TestCheckoutMissingMd5InStageFile::test tests/func/test_checkout.py::TestCheckoutEmptyDir::test tests/func/test_checkout.py::TestCheckoutNotCachedFile::test tests/func/test_checkout.py::TestCheckoutWithDeps::test tests/func/test_checkout.py::TestCheckoutDirectory::test tests/func/test_checkout.py::TestCheckoutHook::test tests/func/test_checkout.py::TestCheckoutSuggestGit::test tests/func/test_checkout.py::TestCheckoutTargetRecursiveShouldNotRemoveOtherUsedFiles::test tests/func/test_checkout.py::TestCheckoutRecursiveNotDirectory::test tests/func/test_checkout.py::TestCheckoutMovedCacheDirWithSymlinks::test tests/func/test_checkout.py::test_checkout_no_checksum tests/func/test_checkout.py::test_checkout_relink[hardlink-is_hardlink] tests/func/test_checkout.py::test_checkout_relink[symlink-is_symlink] tests/func/test_checkout.py::test_partial_checkout[dir/subdir] tests/func/test_checkout.py::test_partial_checkout[dir/subdir/file] tests/func/test_ignore.py::test_ignore tests/func/test_ignore.py::test_ignore_unicode tests/func/test_ignore.py::test_rename_ignored_file tests/func/test_ignore.py::test_rename_file tests/func/test_ignore.py::test_remove_ignored_file tests/func/test_ignore.py::test_remove_file tests/func/test_ignore.py::test_dvcignore_in_out_dir tests/func/test_ignore.py::test_ignore_collecting_dvcignores[dir] tests/func/test_ignore.py::test_ignore_collecting_dvcignores[dir/subdir] tests/func/test_ignore.py::test_ignore_on_branch tests/func/test_ignore.py::test_match_nested tests/func/test_ignore.py::test_ignore_external tests/func/test_tree.py::TestWorkingTree::test_exists tests/func/test_tree.py::TestWorkingTree::test_isdir tests/func/test_tree.py::TestWorkingTree::test_isfile tests/func/test_tree.py::TestWorkingTree::test_open tests/func/test_tree.py::TestGitTree::test_exists tests/func/test_tree.py::TestGitTree::test_isdir tests/func/test_tree.py::TestGitTree::test_isfile tests/func/test_tree.py::TestGitTree::test_open tests/func/test_tree.py::TestGitSubmoduleTree::test_exists tests/func/test_tree.py::TestGitSubmoduleTree::test_isdir tests/func/test_tree.py::TestGitSubmoduleTree::test_isfile tests/func/test_tree.py::TestGitSubmoduleTree::test_open tests/func/test_tree.py::TestWalkInNoSCM::test tests/func/test_tree.py::TestWalkInNoSCM::test_subdir tests/func/test_tree.py::TestWalkInGit::test_branch tests/func/test_tree.py::TestWalkInGit::test_nobranch tests/unit/remote/test_remote_dir.py::test_isdir[S3Mocked] tests/unit/remote/test_remote_dir.py::test_exists[S3Mocked] tests/unit/remote/test_remote_dir.py::test_walk_files[S3Mocked] tests/unit/remote/test_remote_dir.py::test_copy_preserve_etag_across_buckets[S3Mocked] tests/unit/remote/test_remote_dir.py::test_makedirs[S3Mocked] tests/unit/remote/test_remote_dir.py::test_isfile[S3Mocked] tests/unit/remote/test_remote_dir.py::test_download_dir[S3Mocked] tests/unit/utils/test_fs.py::TestMtimeAndSize::test tests/unit/utils/test_fs.py::TestContainsLink::test_path_object_and_str_are_valid_arg_types tests/unit/utils/test_fs.py::TestContainsLink::test_should_call_recursive_on_no_condition_matched tests/unit/utils/test_fs.py::TestContainsLink::test_should_raise_exception_on_base_path_not_in_path tests/unit/utils/test_fs.py::TestContainsLink::test_should_return_false_on_no_more_dirs_below_path tests/unit/utils/test_fs.py::TestContainsLink::test_should_return_false_on_path_eq_to_base_path tests/unit/utils/test_fs.py::TestContainsLink::test_should_return_false_when_base_path_is_symlink tests/unit/utils/test_fs.py::TestContainsLink::test_should_return_true_on_symlink_in_path tests/unit/utils/test_fs.py::test_get_inode tests/unit/utils/test_fs.py::test_path_object_and_str_are_valid_types_get_mtime_and_size[data_dir/data] tests/unit/utils/test_fs.py::test_path_object_and_str_are_valid_types_get_mtime_and_size[data_dir] tests/unit/utils/test_fs.py::test_move tests/unit/utils/test_fs.py::test_remove tests/unit/utils/test_fs.py::test_path_isin_positive tests/unit/utils/test_fs.py::test_path_isin_on_same_path tests/unit/utils/test_fs.py::test_path_isin_on_common_substring_path tests/unit/utils/test_fs.py::test_path_isin_accepts_pathinfo tests/unit/utils/test_fs.py::test_path_isin_with_absolute_path
tests/func/test_checkout.py::test_checkout_relink_protected[hardlink] tests/func/test_checkout.py::test_checkout_relink_protected[symlink] tests/func/test_checkout.py::test_checkout_relink_protected[copy]
dvc/ignore.py dvc/utils/fs.py dvc/repo/brancher.py dvc/scm/tree.py dvc/utils/__init__.py dvc/state.py dvc/scm/git/tree.py dvc/remote/local.py dvc/repo/add.py dvc/repo/__init__.py dvc/repo/diff.py
dvc/ignore.py dvc/utils/fs.py dvc/repo/brancher.py dvc/scm/tree.py dvc/utils/__init__.py dvc/state.py dvc/scm/git/tree.py dvc/remote/local.py dvc/repo/add.py dvc/repo/__init__.py dvc/repo/diff.py