Ejemplo n.º 1
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")

        try:
            self.repo.merge(rev)
        except GitError as exc:
            raise SCMError("Merge failed") from exc

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

        if commit:
            user = self.repo.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()
        return None
Ejemplo n.º 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
Ejemplo n.º 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
Ejemplo n.º 4
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()
Ejemplo n.º 5
0
    def push_refspec(
        self,
        url: str,
        src: Optional[str],
        dest: str,
        force: bool = False,
        on_diverged: Optional[Callable[[str, str], bool]] = None,
    ):
        from dulwich.client import 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)
        except Exception as exc:
            raise SCMError(
                f"'{url}' is not a valid Git remote or URL"
            ) from exc

        def update_refs(refs):
            new_refs = {}
            for ref, value in zip(dest_refs, values):
                if ref in refs:
                    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

        def progress(msg):
            logger.trace("git send_pack: %s", msg)

        try:
            client.send_pack(
                path,
                update_refs,
                self.repo.object_store.generate_pack_data,
                progress=progress,
            )
        except (NotGitRepository, SendPackError) as exc:
            raise SCMError("Git failed to push '{src}' to '{url}'") from exc
Ejemplo n.º 6
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
Ejemplo n.º 7
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
Ejemplo n.º 8
0
    def push_refspec(self, url: str, src: Optional[str], dest: str):
        from dulwich.client import get_transport_and_path
        from dulwich.objects import ZERO_SHA

        if src is not None and src.endswith("/"):
            src_b = os.fsencode(src)
            keys = self.repo.refs.subkeys(src_b)
            values = [self.repo.refs[b"".join([src_b, key])] for key in keys]
            dest_refs = [b"".join([os.fsencode(dest), key]) for key in keys]
        else:
            if src is None:
                values = [ZERO_SHA]
            else:
                values = [self.repo.refs[os.fsencode(src)]]
            dest_refs = [os.fsencode(dest)]

        def update_refs(refs):
            for ref, value in zip(dest_refs, values):
                refs[ref] = value
            return refs

        try:
            client, path = get_transport_and_path(url)
        except Exception as exc:
            raise SCMError("Could not get remote client") from exc

        def progress(msg):
            logger.trace("git send_pack: %s", msg)

        client.send_pack(
            path,
            update_refs,
            self.repo.object_store.generate_pack_data,
            progress=progress,
        )
Ejemplo n.º 9
0
    def _get_diff_trees(self, a_ref, b_ref):
        """Private method for getting the trees and commit hashes of 2 git
        references. Requires `gitdb` module (from gitpython package).

        Args:
            a_ref (str): git reference
            b_ref (str): second git reference. If None, uses HEAD

        Returns:
            tuple: tuple with elements: (trees, commits)
        """
        from gitdb.exc import BadObject, BadName

        trees = {DIFF_A_TREE: None, DIFF_B_TREE: None}
        commits = []
        if b_ref is None:
            b_ref = self.repo.head.commit
        try:
            a_commit = self.repo.git.rev_parse(a_ref, short=True)
            b_commit = self.repo.git.rev_parse(b_ref, short=True)
            # See https://gitpython.readthedocs.io
            # /en/2.1.11/reference.html#git.objects.base.Object.__str__
            commits.append(a_commit)
            commits.append(b_commit)
            trees[DIFF_A_TREE] = self.get_tree(commits[0])
            trees[DIFF_B_TREE] = self.get_tree(commits[1])
        except (BadName, BadObject) as exc:
            raise SCMError("git problem") from exc
        return trees, commits
Ejemplo n.º 10
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.repo.resolve_refish(ref)
            base = repo.merge_base(search_commit.id, commit.id)
            return base == search_commit.id

        try:
            search_commit, _ref = self.repo.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
Ejemplo n.º 11
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
Ejemplo n.º 12
0
    def __init__(self, root_dir=os.curdir, search_parent_directories=True):
        """Git class constructor.
        Requires `Repo` class from `git` module (from gitpython package).
        """
        super().__init__(root_dir)

        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(None)
        libpath = env.get("LD_LIBRARY_PATH", None)
        self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)

        self.ignored_paths = []
        self.files_to_track = set()
Ejemplo n.º 13
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
     )
Ejemplo n.º 14
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
Ejemplo n.º 15
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
Ejemplo n.º 16
0
    def fetch_refspecs(
        self,
        url: str,
        refspecs: Iterable[str],
        force: Optional[bool] = False,
        on_diverged: Optional[Callable[[str, str], bool]] = None,
    ):
        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)
        except Exception as exc:
            raise SCMError(
                f"'{url}' is not a valid Git remote or URL") from exc

        def progress(msg):
            logger.trace("git fetch: %s", msg)

        fetch_result = client.fetch(path,
                                    self.repo,
                                    progress=progress,
                                    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]
Ejemplo n.º 17
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
Ejemplo n.º 18
0
 def _install_hook(self, name, cmd):
     hook = os.path.join(self.root_dir, self.GIT_DIR, "hooks", name)
     if os.path.isfile(hook):
         msg = "git hook '{}' already exists."
         raise SCMError(msg.format(os.path.relpath(hook)))
     with open(hook, "w+") as fobj:
         fobj.write("#!/bin/sh\nexec dvc {}\n".format(cmd))
     os.chmod(hook, 0o777)
Ejemplo n.º 19
0
    def commit(self, msg: str, no_verify: bool = False):
        from dulwich.errors import CommitError
        from dulwich.porcelain import commit

        try:
            commit(self.root_dir, message=msg, no_verify=no_verify)
        except CommitError as exc:
            raise SCMError("Git commit failed") from exc
Ejemplo n.º 20
0
    def _stash_apply(self, rev: str):
        from git.exc import GitCommandError

        try:
            self.git.stash("apply", rev)
        except GitCommandError as exc:
            if "CONFLICT" in str(exc):
                raise MergeConflictError(
                    "Stash apply resulted in merge conflicts") from exc
            raise SCMError("Could not apply stash") from exc
Ejemplo n.º 21
0
 def pop(self):
     logger.debug("Popping from stash '%s'", self.ref)
     ref = "{0}@{{0}}".format(self.ref)
     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
Ejemplo n.º 22
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
Ejemplo n.º 23
0
    def drop(self, index: int = 0):
        ref = "{0}@{{{1}}}".format(self.ref, index)
        if index < 0 or index >= len(self):
            raise SCMError(f"Invalid stash ref '{ref}'")
        logger.debug("Dropping '%s'", ref)
        self.scm.reflog_delete(ref, updateref=True)

        # if we removed the last reflog entry, delete the ref and reflog
        if len(self) == 0:
            self.scm.remove_ref(self.ref)
            parts = self.ref.split("/")
            reflog = os.path.join(self.scm.root_dir, ".git", "logs", *parts)
            remove(reflog)
Ejemplo n.º 24
0
    def resolve_commit(self, rev: str) -> "GitCommit":
        from pygit2 import GitError

        try:
            commit, _ref = self.repo.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],
        )
Ejemplo n.º 25
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._stashes: dict = {}
Ejemplo n.º 26
0
    def iter_remote_refs(self, url: str, base: Optional[str] = None):
        from dulwich.client import get_transport_and_path
        from dulwich.porcelain import get_remote_repo

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

        if base:
            yield from (os.fsdecode(ref) for ref in client.get_refs(path)
                        if ref.startswith(os.fsencode(base)))
        else:
            yield from (os.fsdecode(ref) for ref in client.get_refs(path))
Ejemplo n.º 27
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 symbolic:
            ref = self.repo.create_reference_symbolic(name, new_ref, True)
        else:
            ref = self.repo.create_reference_direct(name, new_ref, True)
        if message:
            ref.set_target(new_ref, message)
Ejemplo n.º 28
0
    def __init__(self, root_dir=os.curdir, project=None):
        super(Git, self).__init__(root_dir, project=project)

        import git
        from git.exc import InvalidGitRepositoryError

        try:
            self.repo = git.Repo(root_dir)
        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(None)
        libpath = env.get("LD_LIBRARY_PATH", None)
        self.repo.git.update_environment(LD_LIBRARY_PATH=libpath)
Ejemplo n.º 29
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],
        )
Ejemplo n.º 30
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}'")