-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
This is useful as it is in `dvc ls` implementation, but this is also a prerequisite for `RepoTree` that will combine git(workspace) files and dvc files into one tree, allowing things like `dvc metrics` and `external_repo` to have a universal interface to access whatever file/dir they want without cumbersome git/dvc path detection.
- Loading branch information
Showing
4 changed files
with
178 additions
and
106 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,116 @@ | ||
import errno | ||
|
||
from dvc.scm.tree import BaseTree | ||
from dvc.path_info import PathInfo | ||
from dvc.exceptions import OutputNotFoundError | ||
|
||
|
||
class DvcTree(BaseTree): | ||
def __init__(self, repo): | ||
self.repo = repo | ||
|
||
def _find_outs(self, path, *args, **kwargs): | ||
outs = self.repo.find_outs_by_path(path, *args, **kwargs) | ||
|
||
def _is_cached(out): | ||
return out.use_cache | ||
|
||
outs = list(filter(_is_cached, outs)) | ||
if not outs: | ||
raise OutputNotFoundError(path, self.repo) | ||
|
||
return outs | ||
|
||
def open(self, path, mode="r", encoding="utf-8"): | ||
try: | ||
outs = self._find_outs(path, strict=False) | ||
except OutputNotFoundError as exc: | ||
raise FileNotFoundError from exc | ||
|
||
if len(outs) != 1 or outs[0].isdir(): | ||
raise IOError(errno.EISDIR) | ||
|
||
out = outs[0] | ||
if not out.changed_cache(): | ||
return open(out.cache_path.fspath, mode=mode, encoding=encoding) | ||
|
||
raise FileNotFoundError | ||
|
||
def exists(self, path): | ||
try: | ||
self._find_outs(path, strict=False, recursive=True) | ||
return True | ||
except OutputNotFoundError: | ||
return False | ||
|
||
def isdir(self, path): | ||
if not self.exists(path): | ||
return False | ||
|
||
outs = self._find_outs(path, strict=False, recursive=True) | ||
|
||
if len(outs) != 1 or outs[0].path_info.fspath != path: | ||
return True | ||
|
||
return outs[0].isdir() | ||
|
||
def isfile(self, path): | ||
if not self.exists(path): | ||
return False | ||
|
||
return not self.isdir(path) | ||
|
||
def _walk(self, root, trie, topdown=True): | ||
dirs = set() | ||
files = [] | ||
|
||
root_len = len(root.parts) | ||
for key, out in trie.iteritems(prefix=root.parts): | ||
if key == root.parts: | ||
continue | ||
|
||
name = key[root_len] | ||
if len(key) > root_len + 1 or out.isdir(): | ||
dirs.add(name) | ||
continue | ||
|
||
files.append(name) | ||
|
||
if topdown: | ||
yield root.fspath, list(dirs), files | ||
|
||
for dname in dirs: | ||
yield from self._walk(root / dname, trie) | ||
else: | ||
assert False | ||
|
||
def walk(self, top, topdown=True): | ||
from pygtrie import Trie | ||
|
||
assert topdown | ||
|
||
if not self.exists(top): | ||
raise FileNotFoundError | ||
|
||
if not self.isdir(top): | ||
raise NotADirectoryError | ||
|
||
root = PathInfo(top) | ||
outs = self._find_outs(top, recursive=True, strict=False) | ||
|
||
trie = Trie() | ||
|
||
for out in outs: | ||
trie[out.path_info.parts] = out | ||
|
||
yield from self._walk(root, trie, topdown=topdown) | ||
|
||
def isdvc(self, path): | ||
try: | ||
return len(self._find_outs(path)) == 1 | ||
except OutputNotFoundError: | ||
pass | ||
return False | ||
|
||
def isexec(self, path): | ||
return False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters