def add(self, paths: Union[str, Iterable[str]], update=False): assert paths or update if isinstance(paths, str): paths = [paths] if update and not paths: self.repo.stage(list(self.repo.open_index())) return files: List[bytes] = [] for path in paths: if not os.path.isabs(path) and self._submodules: # NOTE: If path is inside a submodule, Dulwich expects the # staged paths to be relative to the submodule root (not the # parent git repo root). We append path to root_dir here so # that the result of relpath(path, root_dir) is actually the # path relative to the submodule root. fs_path = relpath(path, self.root_dir) for sm_path in self._submodules.values(): if fs_path.startswith(sm_path): path = os.path.join( self.root_dir, relpath(fs_path, sm_path), ) break if os.path.isdir(path): files.extend( os.fsencode( relpath(os.path.join(root, fpath), self.root_dir)) for root, _, fs in os.walk(path) for fpath in fs) else: files.append(os.fsencode(relpath(path, self.root_dir))) # NOTE: this doesn't check gitignore, same as GitPythonBackend.add if update: index = self.repo.open_index() if os.name == "nt": # NOTE: we need git/unix separator to compare against index # paths but repo.stage() expects to be called with OS paths self.repo.stage([ fname for fname in files if fname.replace(b"\\", b"/") in index ]) else: self.repo.stage([fname for fname in files if fname in index]) else: self.repo.stage(files)
def checkout_index( self, paths: Optional[Iterable[str]] = None, force: bool = False, ours: bool = False, theirs: bool = False, ): """Checkout the specified paths from HEAD index.""" assert not (ours and theirs) if ours or theirs: args = ["--ours"] if ours else ["--theirs"] if force: args.append("--force") args.append("--") if paths: args.extend(list(paths)) else: args.append(".") self.repo.git.checkout(*args) else: if paths: paths_list: Optional[List[str]] = [ relpath(path, self.root_dir) for path in paths ] if os.name == "nt": paths_list = [ path.replace("\\", "/") for path in paths_list # type: ignore[union-attr] ] else: paths_list = None self.repo.index.checkout(paths=paths_list, force=force)
def is_tracked(self, path: str) -> bool: rel = relpath(path, self.root_dir).replace(os.path.sep, "/").encode() rel_dir = rel + b"/" for p in self.repo.open_index(): if p == rel or p.startswith(rel_dir): return True return False
def is_ignored(self, path: "Union[str, os.PathLike[str]]") -> bool: # `is_ignored` returns `false` if excluded in `.gitignore` and # `None` if it's not mentioned at all. `True` if it is ignored. relative_path = relpath(path, self.root_dir) # if checking a directory, a trailing slash must be included if str(path)[-1] == os.sep: relative_path += os.sep return bool(self.ignore_manager.is_ignored(relative_path))
def checkout_index( self, paths: Optional[Iterable[str]] = None, force: bool = False, ours: bool = False, theirs: bool = False, ): from pygit2 import ( GIT_CHECKOUT_ALLOW_CONFLICTS, GIT_CHECKOUT_FORCE, GIT_CHECKOUT_RECREATE_MISSING, GIT_CHECKOUT_SAFE, ) assert not (ours and theirs) strategy = GIT_CHECKOUT_RECREATE_MISSING if force or ours or theirs: strategy |= GIT_CHECKOUT_FORCE else: strategy |= GIT_CHECKOUT_SAFE if ours or theirs: strategy |= GIT_CHECKOUT_ALLOW_CONFLICTS strategy = self._get_checkout_strategy(strategy) index = self.repo.index if paths: path_list: Optional[List[str]] = [ relpath(path, self.root_dir) for path in paths ] if os.name == "nt": path_list = [ path.replace("\\", "/") for path in path_list # type: ignore[union-attr] ] else: path_list = None with self.release_odb_handles(): self.repo.checkout_index(index=index, paths=path_list, strategy=strategy) if index.conflicts and (ours or theirs): for ancestor, ours_entry, theirs_entry in index.conflicts: if not ancestor: continue if ours: entry = ours_entry index.add(ours_entry) else: entry = theirs_entry path = os.path.join(self.root_dir, entry.path) with open(path, "wb") as fobj: fobj.write(self.repo.get(entry.id).read_raw()) index.add(entry.path) index.write()
def _get_key(self, path: str) -> Tuple[str, ...]: from dvc.scm.utils import relpath if os.path.isabs(path): path = relpath(path, self.root_dir) relparts = path.split(os.sep) if relparts == ["."]: return () return tuple(relparts)
def reset(self, hard: bool = False, paths: Iterable[str] = None): if paths: paths_list: Optional[List[str]] = [ relpath(path, self.root_dir) for path in paths ] if os.name == "nt": paths_list = [ path.replace("\\", "/") for path in paths_list # type: ignore[union-attr] ] else: paths_list = None self.repo.head.reset(index=True, working_tree=hard, paths=paths_list)
def reset(self, hard: bool = False, paths: Iterable[str] = None): from pygit2 import GIT_RESET_HARD, GIT_RESET_MIXED, IndexEntry self.repo.index.read(False) if paths is not None: tree = self.repo.revparse_single("HEAD").tree for path in paths: rel = relpath(path, self.root_dir) if os.name == "nt": rel = rel.replace("\\", "/") obj = tree[rel] self.repo.index.add(IndexEntry(rel, obj.oid, obj.filemode)) self.repo.index.write() elif hard: self.repo.reset(self.repo.head.target, GIT_RESET_HARD) else: self.repo.reset(self.repo.head.target, GIT_RESET_MIXED)
def _get_gitignore(self, path): ignore_file_dir = os.path.dirname(path) assert os.path.isabs(path) assert os.path.isabs(ignore_file_dir) entry = relpath(path, ignore_file_dir).replace(os.sep, "/") # NOTE: using '/' prefix to make path unambiguous if len(entry) > 0 and entry[0] != "/": entry = "/" + entry gitignore = os.path.join(ignore_file_dir, self.GITIGNORE) if not os.path.realpath(gitignore).startswith(self.root_dir + os.sep): raise FileNotInRepoError( f"'{path}' is outside of git repository '{self.root_dir}'") return entry, gitignore
def is_ignored(self, path: "Union[str, os.PathLike[str]]") -> bool: rel = relpath(path, self.root_dir) if os.name == "nt": rel.replace("\\", "/") return self.repo.path_is_ignored(rel)