Files
dvc/dvc/scm/git/tree.py
T
Andrew Grigorev 6fbdf1b5b0 dvc.repo.tree -> dvc.scm.tree
Tree objects are used as `Repo().tree`, so it seemed logical to put them
inside `dvc/repo/tree.py`. But what is more important - `dvc.repo.Repo`
depends on `dvc.scm` and it is not convinient to use `dvc.repo.tree` in
`dvc.scm.git`.

Fix #1769
2019-03-22 19:53:41 +03:00

115 lines
3.4 KiB
Python

import errno
import os
from dvc.utils.compat import StringIO
from dvc.scm.tree import BaseTree
# see git-fast-import(1)
GIT_MODE_DIR = 0o40000
GIT_MODE_FILE = 0o644
class GitTree(BaseTree):
"""Proxies the repo file access methods to Git objects"""
def __init__(self, git, rev):
"""Create GitTree instance
Args:
git (dvc.scm.Git):
branch:
"""
self.git = git
self.rev = rev
def open(self, path):
relpath = os.path.relpath(path, self.git.working_dir)
obj = self.git_object_by_path(path)
if obj is None:
msg = "No such file in branch '{}'".format(self.rev)
raise IOError(errno.ENOENT, msg, relpath)
if obj.mode == GIT_MODE_DIR:
raise IOError(errno.EISDIR, "Is a directory", relpath)
# GitPython's obj.data_stream is a fragile thing, it is better to
# read it immediately, also it needs to be to decoded if we follow
# the `open()` behavior (since data_stream.read() returns bytes,
# and `open` with default "r" mode returns str)
return StringIO(obj.data_stream.read().decode("utf-8"))
def exists(self, path):
return self.git_object_by_path(path) is not None
def isdir(self, path):
obj = self.git_object_by_path(path)
if obj is None:
return False
return obj.mode == GIT_MODE_DIR
def isfile(self, path):
obj = self.git_object_by_path(path)
if obj is None:
return False
# according to git-fast-import(1) file mode could be 644 or 755
return obj.mode & GIT_MODE_FILE == GIT_MODE_FILE
@staticmethod
def _is_tree_and_contains(obj, path):
if obj.mode != GIT_MODE_DIR:
return False
# see https://github.com/gitpython-developers/GitPython/issues/851
# `return (i in tree)` doesn't work so here is a workaround:
for i in obj:
if i.name == path:
return True
return False
def git_object_by_path(self, path):
path = os.path.relpath(os.path.realpath(path), self.git.working_dir)
assert path.split(os.sep, 1)[0] != ".."
tree = self.git.tree(self.rev)
if not path or path == ".":
return tree
for i in path.split(os.sep):
if not self._is_tree_and_contains(tree, i):
# there is no tree for specified path
return None
tree = tree[i]
return tree
def _walk(self, tree, topdown=True):
dirs, nondirs = [], []
for i in tree:
if i.mode == GIT_MODE_DIR:
dirs.append(i.name)
else:
nondirs.append(i.name)
if topdown:
yield os.path.normpath(tree.path), dirs, nondirs
for i in dirs:
for x in self._walk(tree[i], topdown=True):
yield x
if not topdown:
yield os.path.normpath(tree.path), dirs, nondirs
def walk(self, top, topdown=True):
"""Directory tree generator.
See `os.walk` for the docs. Differences:
- no support for symlinks
- it could raise exceptions, there is no onerror argument
"""
tree = self.git_object_by_path(top)
if tree is None:
raise IOError(errno.ENOENT, "No such file")
for x in self._walk(tree, topdown):
yield x