Beispiel #1
0
 def remove_ref(self, name: str, old_ref: Optional[str] = None):
     ref = self.repo.references.get(name)
     if not ref:
         raise SCMError(f"Ref '{name}' does not exist")
     if old_ref and old_ref != str(ref.target):
         raise SCMError(f"Failed to remove '{name}'")
     ref.delete()
Beispiel #2
0
    def merge(
        self,
        rev: str,
        commit: bool = True,
        msg: Optional[str] = None,
        squash: bool = False,
    ) -> Optional[str]:
        from git.exc import GitCommandError

        if commit and squash:
            raise SCMError("Cannot merge with 'squash' and 'commit'")

        if commit and not msg:
            raise SCMError("Merge commit message is required")

        merge = partial(self.git.merge, rev)
        try:
            if commit:
                merge(m=msg)
                return self.get_rev()
            merge(no_commit=True, squash=True)
        except GitCommandError as exc:
            if "CONFLICT" in str(exc):
                raise MergeConflictError("Merge contained conflicts") from exc
            raise SCMError("Merge failed") from exc
        return None
Beispiel #3
0
    def set_ref(
        self,
        name: str,
        new_ref: str,
        old_ref: Optional[str] = None,
        message: Optional[str] = None,
        symbolic: Optional[bool] = False,
    ):
        from git.exc import GitCommandError

        if old_ref and self.get_ref(name) != old_ref:
            raise SCMError(f"Failed to set ref '{name}'")
        try:
            if symbolic:
                if message:
                    self.git.symbolic_ref(name, new_ref, m=message)
                else:
                    self.git.symbolic_ref(name, new_ref)
            else:
                args = [name, new_ref]
                if old_ref:
                    args.append(old_ref)
                if message:
                    self.git.update_ref(*args, m=message, create_reflog=True)
                else:
                    self.git.update_ref(*args)
        except GitCommandError as exc:
            raise SCMError(f"Failed to set ref '{name}'") from exc
Beispiel #4
0
    def remove_ref(self, name: str, old_ref: Optional[str] = None):
        from git.exc import GitCommandError

        if old_ref and self.get_ref(name) != old_ref:
            raise SCMError(f"Failed to set ref '{name}'")
        try:
            args = [name]
            if old_ref:
                args.append(old_ref)
            self.git.update_ref(*args, d=True)
        except GitCommandError as exc:
            raise SCMError(f"Failed to set ref '{name}'") from exc
Beispiel #5
0
    def commit(self, msg: str, no_verify: bool = False):
        from dulwich.errors import CommitError
        from dulwich.porcelain import commit
        from dulwich.repo import InvalidUserIdentity

        try:
            commit(self.root_dir, message=msg, no_verify=no_verify)
        except CommitError as exc:
            raise SCMError("Git commit failed") from exc
        except InvalidUserIdentity as exc:
            raise SCMError(
                "Git username and email must be configured") from exc
Beispiel #6
0
    def branch(self, branch: str):
        from dulwich.porcelain import Error, branch_create

        try:
            branch_create(self.root_dir, branch)
        except Error as exc:
            raise SCMError(f"Failed to create branch '{branch}'") from exc
Beispiel #7
0
    def commit(self, msg: str, no_verify: bool = False):
        from git.exc import HookExecutionError

        try:
            self.repo.index.commit(msg, skip_hooks=no_verify)
        except HookExecutionError as exc:
            raise SCMError("Git pre-commit hook failed") from exc
Beispiel #8
0
    def get_refs_containing(self, rev: str, pattern: Optional[str] = None):
        import fnmatch

        from pygit2 import GitError

        def _contains(repo, ref, search_commit):
            commit, _ref = self._resolve_refish(ref)
            base = repo.merge_base(search_commit.id, commit.id)
            return base == search_commit.id

        try:
            search_commit, _ref = self._resolve_refish(rev)
        except (KeyError, GitError):
            raise SCMError(f"Invalid rev '{rev}'")

        if not pattern:
            yield from (ref for ref in self.iter_refs()
                        if _contains(self.repo, ref, search_commit))
            return

        literal = pattern.rstrip("/").split("/")
        for ref in self.iter_refs():
            if (ref.split("/")[:len(literal)] == literal
                    or fnmatch.fnmatch(ref, pattern)) and _contains(
                        self.repo, ref, search_commit):
                yield ref
Beispiel #9
0
    def branch(self, branch: str):
        from pygit2 import GitError

        try:
            commit = self.repo[self.repo.head.target]
            self.repo.create_branch(branch, commit)
        except GitError as exc:
            raise SCMError(f"Failed to create branch '{branch}'") from exc
Beispiel #10
0
 def pop(self):
     logger.debug("Popping from stash '%s'", self.ref)
     ref = f"{self.ref}@{{0}}"
     rev = self.scm.resolve_rev(ref)
     try:
         self.apply(rev)
     except Exception as exc:
         raise SCMError("Could not apply stash commit") from exc
     self.drop()
     return rev
Beispiel #11
0
    def _stash_drop(self, ref: str, index: int):
        from dvc.scm.git import Stash

        if ref == Stash.DEFAULT_STASH:
            raise NotImplementedError

        stash = self._get_stash(ref)
        try:
            stash.drop(index)
        except ValueError as exc:
            raise SCMError("Failed to drop stash entry") from exc
Beispiel #12
0
    def _stash_apply(self, rev: str):
        from git.exc import GitCommandError

        try:
            self.git.stash("apply", rev)
        except GitCommandError as exc:
            out = str(exc)
            if "CONFLICT" in out or "already exists" in out:
                raise MergeConflictError(
                    "Stash apply resulted in merge conflicts"
                ) from exc
            raise SCMError("Could not apply stash") from exc
Beispiel #13
0
    def merge(
        self,
        rev: str,
        commit: bool = True,
        msg: Optional[str] = None,
        squash: bool = False,
    ) -> Optional[str]:
        from pygit2 import GIT_RESET_MIXED, GitError

        if commit and squash:
            raise SCMError("Cannot merge with 'squash' and 'commit'")

        if commit and not msg:
            raise SCMError("Merge commit message is required")

        with self.release_odb_handles():
            try:
                self.repo.index.read(False)
                self.repo.merge(rev)
                self.repo.index.write()
            except GitError as exc:
                raise SCMError("Merge failed") from exc

            if self.repo.index.conflicts:
                raise MergeConflictError("Merge contained conflicts")

            if commit:
                user = self.default_signature
                tree = self.repo.index.write_tree()
                merge_commit = self.repo.create_commit(
                    "HEAD", user, user, msg, tree,
                    [self.repo.head.target, rev])
                return str(merge_commit)
            if squash:
                self.repo.reset(self.repo.head.target, GIT_RESET_MIXED)
                self.repo.state_cleanup()
                self.repo.index.write()
        return None
Beispiel #14
0
    def resolve_commit(self, rev: str) -> "GitCommit":
        from pygit2 import GitError

        try:
            commit, _ref = self._resolve_refish(rev)
        except (KeyError, GitError):
            raise SCMError(f"Invalid commit '{rev}'")
        return GitCommit(
            str(commit.id),
            commit.commit_time,
            commit.commit_time_offset,
            commit.message,
            [str(parent) for parent in commit.parent_ids],
        )
Beispiel #15
0
    def __init__(  # pylint:disable=W0231
            self,
            root_dir=os.curdir,
            search_parent_directories=True):
        from dulwich.errors import NotGitRepository
        from dulwich.repo import Repo

        try:
            if search_parent_directories:
                self.repo = Repo.discover(start=root_dir)
            else:
                self.repo = Repo(root_dir)
        except NotGitRepository as exc:
            raise SCMError(f"{root_dir} is not a git repository") from exc

        self._submodules: Dict[str, str] = self._find_submodules()
        self._stashes: dict = {}
Beispiel #16
0
    def resolve_commit(self, rev: str) -> "GitCommit":
        """Return Commit object for the specified revision."""
        from git.exc import BadName, GitCommandError
        from git.objects.tag import TagObject

        try:
            commit = self.repo.rev_parse(rev)
        except (BadName, GitCommandError):
            raise SCMError(f"Invalid commit '{rev}'")
        if isinstance(commit, TagObject):
            commit = commit.object
        return GitCommit(
            commit.hexsha,
            commit.committed_date,
            commit.committer_tz_offset,
            commit.message,
            [str(parent) for parent in commit.parents],
        )
Beispiel #17
0
    def __init__(  # pylint:disable=W0231
        self, root_dir=os.curdir, search_parent_directories=True
    ):
        import git
        from git.exc import InvalidGitRepositoryError

        try:
            self.repo = git.Repo(
                root_dir, search_parent_directories=search_parent_directories
            )
        except InvalidGitRepositoryError:
            msg = "{} is not a git repository"
            raise SCMError(msg.format(root_dir))

        # NOTE: fixing LD_LIBRARY_PATH for binary built by PyInstaller.
        # http://pyinstaller.readthedocs.io/en/stable/runtime-information.html
        env = fix_env()
        libpath = env.get("LD_LIBRARY_PATH", None)
        self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)
Beispiel #18
0
 def set_ref(
     self,
     name: str,
     new_ref: str,
     old_ref: Optional[str] = None,
     message: Optional[str] = None,
     symbolic: Optional[bool] = False,
 ):
     name_b = os.fsencode(name)
     new_ref_b = os.fsencode(new_ref)
     old_ref_b = os.fsencode(old_ref) if old_ref else None
     message_b = message.encode("utf-8") if message else None
     if symbolic:
         return self.repo.refs.set_symbolic_ref(name_b,
                                                new_ref_b,
                                                message=message_b)
     if not self.repo.refs.set_if_equals(
             name_b, old_ref_b, new_ref_b, message=message_b):
         raise SCMError(f"Failed to set '{name}'")
Beispiel #19
0
    def __init__(  # pylint:disable=W0231
            self,
            root_dir=os.curdir,
            search_parent_directories=True):
        import pygit2

        if search_parent_directories:
            ceiling_dirs = ""
        else:
            ceiling_dirs = os.path.abspath(root_dir)

        # NOTE: discover_repository will return path/.git/
        path = pygit2.discover_repository(  # pylint:disable=no-member
            root_dir, True, ceiling_dirs)
        if not path:
            raise SCMError(f"{root_dir} is not a git repository")

        self.repo = pygit2.Repository(path)

        self._stashes: dict = {}
Beispiel #20
0
    def set_ref(
        self,
        name: str,
        new_ref: str,
        old_ref: Optional[str] = None,
        message: Optional[str] = None,
        symbolic: Optional[bool] = False,
    ):
        if old_ref and old_ref != self.get_ref(name, follow=False):
            raise SCMError(f"Failed to set '{name}'")

        if message:
            self._refdb.ensure_log(name)
        if symbolic:
            self.repo.create_reference_symbolic(name,
                                                new_ref,
                                                True,
                                                message=message)
        else:
            self.repo.create_reference_direct(name,
                                              new_ref,
                                              True,
                                              message=message)
Beispiel #21
0
    def _stash_push(
        self,
        ref: str,
        message: Optional[str] = None,
        include_untracked: Optional[bool] = False,
    ) -> Tuple[Optional[str], bool]:
        from dulwich.repo import InvalidUserIdentity

        from dvc.scm.git import Stash

        if include_untracked or ref == Stash.DEFAULT_STASH:
            # dulwich stash.push does not support include_untracked and does
            # not touch working tree
            raise NotImplementedError

        stash = self._get_stash(ref)
        message_b = message.encode("utf-8") if message else None
        try:
            rev = stash.push(message=message_b)
        except InvalidUserIdentity as exc:
            raise SCMError(
                "Git username and email must be configured") from exc
        return os.fsdecode(rev), True
Beispiel #22
0
 def get_rev(self) -> str:
     rev = self.get_ref("HEAD")
     if rev:
         return rev
     raise SCMError("Empty git repo")
Beispiel #23
0
 def remove_ref(self, name: str, old_ref: Optional[str] = None):
     name_b = name.encode("utf-8")
     old_ref_b = old_ref.encode("utf-8") if old_ref else None
     if not self.repo.refs.remove_if_equals(name_b, old_ref_b):
         raise SCMError(f"Failed to remove '{name}'")
Beispiel #24
0
    def push_refspec(
        self,
        url: str,
        src: Optional[str],
        dest: str,
        force: bool = False,
        on_diverged: Optional[Callable[[str, str], bool]] = None,
        progress: Callable[["GitProgressEvent"], None] = None,
        **kwargs,
    ):
        from dulwich.client import HTTPUnauthorized, get_transport_and_path
        from dulwich.errors import NotGitRepository, SendPackError
        from dulwich.porcelain import (
            DivergedBranches,
            check_diverged,
            get_remote_repo,
        )

        dest_refs, values = self._push_dest_refs(src, dest)

        try:
            _remote, location = get_remote_repo(self.repo, url)
            client, path = get_transport_and_path(location, **kwargs)
        except Exception as exc:
            raise SCMError(
                f"'{url}' is not a valid Git remote or URL") from exc

        def update_refs(refs):
            from dulwich.objects import ZERO_SHA

            new_refs = {}
            for ref, value in zip(dest_refs, values):
                if ref in refs and value != ZERO_SHA:
                    local_sha = self.repo.refs[ref]
                    remote_sha = refs[ref]
                    try:
                        check_diverged(self.repo, remote_sha, local_sha)
                    except DivergedBranches:
                        if not force:
                            overwrite = False
                            if on_diverged:
                                overwrite = on_diverged(
                                    os.fsdecode(ref), os.fsdecode(remote_sha))
                            if not overwrite:
                                continue
                new_refs[ref] = value
            return new_refs

        try:
            from dvc.scm.progress import GitProgressReporter

            client.send_pack(
                path,
                update_refs,
                self.repo.object_store.generate_pack_data,
                progress=GitProgressReporter(progress) if progress else None,
            )
        except (NotGitRepository, SendPackError) as exc:
            raise SCMError("Git failed to push '{src}' to '{url}'") from exc
        except HTTPUnauthorized:
            raise AuthError(url)
Beispiel #25
0
    def fetch_refspecs(
        self,
        url: str,
        refspecs: Iterable[str],
        force: Optional[bool] = False,
        on_diverged: Optional[Callable[[str, str], bool]] = None,
        progress: Callable[["GitProgressEvent"], None] = None,
        **kwargs,
    ):
        from dulwich.client import get_transport_and_path
        from dulwich.objectspec import parse_reftuples
        from dulwich.porcelain import (
            DivergedBranches,
            check_diverged,
            get_remote_repo,
        )

        fetch_refs = []

        def determine_wants(remote_refs):
            fetch_refs.extend(
                parse_reftuples(
                    remote_refs,
                    self.repo.refs,
                    [os.fsencode(refspec) for refspec in refspecs],
                    force=force,
                ))
            return [
                remote_refs[lh] for (lh, _, _) in fetch_refs
                if remote_refs[lh] not in self.repo.object_store
            ]

        try:
            _remote, location = get_remote_repo(self.repo, url)
            client, path = get_transport_and_path(location, **kwargs)
        except Exception as exc:
            raise SCMError(
                f"'{url}' is not a valid Git remote or URL") from exc

        from dvc.scm.progress import GitProgressReporter

        fetch_result = client.fetch(
            path,
            self.repo,
            progress=GitProgressReporter(progress) if progress else None,
            determine_wants=determine_wants,
        )
        for (lh, rh, _) in fetch_refs:
            try:
                if rh in self.repo.refs:
                    check_diverged(self.repo, self.repo.refs[rh],
                                   fetch_result.refs[lh])
            except DivergedBranches:
                if not force:
                    overwrite = False
                    if on_diverged:
                        overwrite = on_diverged(
                            os.fsdecode(rh),
                            os.fsdecode(fetch_result.refs[lh]))
                    if not overwrite:
                        continue
            self.repo.refs[rh] = fetch_result.refs[lh]
Beispiel #26
0
 def default_signature(self):
     try:
         return self.repo.default_signature
     except KeyError as exc:
         raise SCMError(
             "Git username and email must be configured") from exc
Beispiel #27
0
 def drop(self, index: int = 0):
     if index < 0 or index >= len(self):
         raise SCMError(f"Invalid stash ref '{self.ref}@{{{index}}}'")
     logger.debug("Dropping '%s@{%d}'", self.ref, index)
     self.scm._stash_drop(  # pylint: disable=protected-access
         self.ref, index)
Beispiel #28
0
 def pull(self, **kwargs):
     infos = self.repo.remote().pull(**kwargs)
     for info in infos:
         if info.flags & info.ERROR:
             raise SCMError(f"pull failed: {info.note}")
Beispiel #29
0
 def push(self):
     infos = self.repo.remote().push()
     for info in infos:
         if info.flags & info.ERROR:
             raise SCMError(f"push failed: {info.summary}")