示例#1
0
    def from_pygit(pg_repo, pg_commit):
        """Factory to convert pygit2 objects to our own."""
        message = pg_commit.message
        encoding = pg_commit.message_encoding or 'UTF-8'
        if '\ufffd' in message:
            # Original message failed to decode using UTF-8
            _, raw_commit = pg_repo.read(pg_commit.oid)
            try:
                commit_text = raw_commit.decode('latin_1')
                encoding = 'latin_1'
                message = commit_text[commit_text.index('\n\n') + 2:]
                # convert to UTF-8 for easier comparison
                message = message.encode('UTF-8').decode('UTF-8')
            except UnicodeDecodeError:
                LOG.exception('commit message decoding failed')
        subj_end = message.find('\n')
        if 0 < subj_end:
            subject = message[:subj_end]
        else:
            subject = message

        return Commit(sha1=p4gf_pygit2.object_to_sha1(pg_commit),
                      tree=p4gf_pygit2.object_to_sha1(pg_commit.tree),
                      parent_list=[
                          p4gf_pygit2.object_to_sha1(p)
                          for p in pg_commit.parents
                      ],
                      subject=subject,
                      message=message,
                      commit_time=pg_commit.commit_time,
                      encoding=encoding,
                      author_offset=pg_commit.author.offset,
                      committer_offset=pg_commit.committer.offset)
示例#2
0
    def _path_added(self, path, fecommit):
        """Return True if the named path was introduced in the HEAD commit.

        :param self: this object
        :param path: repo path to be evaluated.
        :param fecommit: commit object from fast-export parser.

        """
        # Because git-fast-export includes the entire tree in its output,
        # regardless of whether the requested commit is the first in the
        # branch or not, we need to check the repo itself to be certain if
        # this path was truly introduced in this commit, or simply existed
        # in the tree prior to the "first" commit.
        commit = self.ctx.repo.get(fecommit['sha1'])
        if commit is None:
            # empty repository?
            LOG.debug2("_path_added() commit {} is missing".format(
                fecommit['sha1']))
            return True
        for parent in commit.parents:
            if p4gf_git.exists_in_tree(self.ctx.repo, path, parent.tree):
                LOG.debug2("_path_added() {} exists in parent tree {}".format(
                    path,
                    p4gf_util.abbrev(p4gf_pygit2.object_to_sha1(parent))))
                return False
        return True
    def _count_commits(self, since=None, until=None, count_files=False):
        """Count the number commits from since to until.

        :param since: earliest commit SHA1 to visit.
        :param until: latest commit SHA1 to visit.
        :param count_files: if True, also count the files added.

        Returns the number of commits and files introduced within the range of
        commits. The number of files will be zero unless count_files is true.

        """
        if int(since, 16) == 0:
            since = None
        if int(until, 16) == 0:
            until = None
        start = self.repo.get(until) if until else p4gf_pygit2.head_commit(self.repo)
        if start is None:
            raise RuntimeError(_("Missing starting commit"))
        sort = pygit2.GIT_SORT_TOPOLOGICAL | pygit2.GIT_SORT_TIME
        commit_count = 0
        file_count = 0
        for commit in self.repo.walk(start.oid, sort):
            if since and p4gf_pygit2.object_to_sha1(commit) == since:
                break
            commit_count += 1
            if count_files:
                if commit.parents:
                    for parent in commit.parents:
                        file_count += self._compare_commit_with_parent(parent, commit)
                else:
                    file_count += self._count_files_for_commit(commit)
        return commit_count, file_count
示例#4
0
def _expand_sha1(ctx, partial_sha1):
    """Given partial SHA1 of a git object, return complete SHA1.

    If there is no match, returns None.
    """
    try:
        obj = ctx.repo.git_object_lookup_prefix(partial_sha1)
        return p4gf_pygit2.object_to_sha1(obj) if obj else None
    except ValueError:
        return None
示例#5
0
def flatten_tree_1(gwt_path_tree, tree, result_list, tree_q, repo):
    """Process a single pygit2 tree object.

    Translate any files to elements appended to result_list.
    Translate any directories to (gwt_path, Tree) tuples appended to tree_q.
    """
    for tree_entry in tree:
        gwt_element_path = os.path.join(gwt_path_tree, tree_entry.name)
        git_file = GitFile(gwt_path=gwt_element_path,
                           mode=tree_entry.filemode,
                           sha1=p4gf_pygit2.object_to_sha1(tree_entry))
        result_list.append(git_file)

        if tree_entry.filemode == 0o040000:
            tree_q.append(
                (gwt_element_path, p4gf_pygit2.tree_object(repo, tree_entry)))
示例#6
0
def process_tags(ctx, tags):
    """Add or remove tags objects from the Git Fusion mirror.

    :param ctx: P4GF context with initialized pygit2 Repository.
    :param tags: list of PreReceiveTuple objects for tags

    """
    # pylint:disable=too-many-branches
    if not tags:
        LOG.debug("process_tags() no incoming tags to process")
        return

    # Re-sync the tags since preflight_tags() synced with a different temp client.
    tags_path = "objects/repos/{repo}/tags".format(repo=ctx.config.repo_name)
    with ctx.p4gf.at_exception_level(P4.P4.RAISE_NONE):
        # Raises an exception when there are no files to sync?
        ctx.p4gfrun('sync', '-q', "//{}/{}/...".format(ctx.p4gf.client,
                                                       tags_path))

    # Decide what to do with the tag references.
    tags_to_delete = []
    tags_to_add = []
    tags_to_edit = []
    for prt in tags:
        tag = prt.ref[10:]
        if prt.old_sha1 == p4gf_const.NULL_COMMIT_SHA1:
            if prt.new_sha1 == p4gf_const.NULL_COMMIT_SHA1:
                # No idea how this happens, but it did, so guard against it.
                continue
            # Adding a new tag; if it references a commit, check that it
            # exists; for other types, it is too costly to verify
            # reachability from a known commit, so just ignore them.
            obj = _get_tag_target(ctx.repo, prt.new_sha1)
            is_commit = obj.type == pygit2.GIT_OBJ_COMMIT
            if is_commit and not ObjectType.commits_for_sha1(
                    ctx, p4gf_pygit2.object_to_sha1(obj)):
                LOG.debug("Tag '{}' of unknown commit {:7.7} not stored."
                          " Removing ref from git repo.".format(
                              tag, prt.new_sha1))
                _remove_tag_ref(tag, prt.new_sha1)
                continue
            if obj.type == pygit2.GIT_OBJ_TREE:
                continue
            if obj.type == pygit2.GIT_OBJ_BLOB:
                continue
            _add_tag(ctx, tag, prt.new_sha1, tags_to_edit, tags_to_add)
        elif prt.new_sha1 == p4gf_const.NULL_COMMIT_SHA1:
            # Removing an existing tag
            _remove_tag(ctx, tag, prt.old_sha1, tags_to_edit, tags_to_delete)

    # Seemingly nothing to do.
    if not tags_to_add and not tags_to_edit and not tags_to_delete:
        LOG.debug("process_tags() mysteriously came up empty"
                  " - probably a tag of a non-existing commit.")
        return

    # Add and remove tags as appropriate, doing so in batches.
    LOG.info(
        "adding {} tags, removing {} tags, editing {} tags from Git mirror".
        format(len(tags_to_add), len(tags_to_delete), len(tags_to_edit)))
    desc = _("Git Fusion '{repo}' tag changes").format(
        repo=ctx.config.repo_name)
    with p4gf_util.NumberedChangelist(gfctx=ctx, description=desc) as nc:
        while len(tags_to_add):
            bite = tags_to_add[:_BITE_SIZE]
            tags_to_add = tags_to_add[_BITE_SIZE:]
            ctx.p4gfrun('add', '-t', 'binary+F', bite)
        while len(tags_to_edit):
            bite = tags_to_edit[:_BITE_SIZE]
            tags_to_edit = tags_to_edit[_BITE_SIZE:]
            ctx.p4gfrun('edit', '-k', bite)
        while len(tags_to_delete):
            bite = tags_to_delete[:_BITE_SIZE]
            tags_to_delete = tags_to_delete[_BITE_SIZE:]
            ctx.p4gfrun('delete', bite)
        nc.submit()
        if nc.submitted:
            _write_last_copied_tag(ctx, nc.change_num)
    LOG.debug("process_tags() complete")
示例#7
0
def preflight_tags(ctx, tags, heads):
    """Validate the incoming tags.

    :param ctx: P4GF context with initialized pygit2 Repository.
    :param tags: list of PreReceiveTuple objects for tags
    :param heads: list of commit references for heads (non-tags)

    Warnings are printed to stderr so the user knows about them.

    Returns None if successful and an error string otherwise.

    """
    if not tags:
        LOG.debug("preflight_tags() no incoming tags to process")
        return None

    LOG.debug("preflight_tags() beginning...")
    tags_path = "objects/repos/{repo}/tags".format(repo=ctx.config.repo_name)
    with ctx.p4gf.at_exception_level(P4.P4.RAISE_NONE):
        # Raises an exception when there are no files to sync?
        ctx.p4gfrun('sync', '-q', "//{}/{}/...".format(ctx.p4gf.client,
                                                       tags_path))

    regex = re.compile(r'[*@#,]|\.\.\.|%%')
    for prt in tags:
        tag = prt.ref[10:]
        # Screen the tags to ensure their names won't cause problems
        # sometime in the future (i.e. when we create Perforce labels).
        # Several of these characters are not allowed in Git tag names
        # anyway, but better to check in case that changes in the future.
        # In particular git disallows a leading '-', but we'll check for it
        # anyway Otherwise allow internal '-'
        if regex.search(tag) or tag.startswith('-'):
            return _("illegal characters (@#*,...%%) in tag name: '{tag}'"
                     ).format(tag=tag)
        if prt.old_sha1 == p4gf_const.NULL_COMMIT_SHA1:
            if prt.new_sha1 == p4gf_const.NULL_COMMIT_SHA1:
                # No idea how this happens, but it did, so guard against it.
                sys.stderr.write(
                    _('Ignoring double-zero pre-receive-tuple line'))
                continue
            # Adding a new tag; if it references a commit, check that it
            # exists; for other types, it is too costly to verify
            # reachability from a known commit, so just ignore them.
            obj = _get_tag_target(ctx.repo, prt.new_sha1)
            is_commit = obj.type == pygit2.GIT_OBJ_COMMIT
            if is_commit and not _is_reachable(p4gf_pygit2.object_to_sha1(obj),
                                               heads):
                # Do not fail in preflight but allow the push to proceed.
                # Later this tag is ignored without error and not added to the object cache.
                # The tag ref however has already been added to the git repo, but
                # will be removed later in process_tags.
                msg = _(
                    "Tag '{tag}' of unknown commit {sha1:7.7} not stored in "
                    "Perforce nor in git.\n"
                    "You must push a branch containing the target commit either prior to"
                    " or with the push of the tag.\n").format(
                        tag=tag, sha1=prt.new_sha1)
                LOG.debug(msg)
                sys.stderr.write(msg)
            if obj.type == pygit2.GIT_OBJ_TREE:
                msg = _("Tag '{tag}' of tree will not be stored in Perforce\n"
                        ).format(tag=tag)
                sys.stderr.write(msg)
                continue
            if obj.type == pygit2.GIT_OBJ_BLOB:
                msg = _("Tag '{tag}' of blob will not be stored in Perforce\n"
                        ).format(tag=tag)
                sys.stderr.write(msg)
                continue

            fpath = os.path.join(ctx.gitlocalroot,
                                 _client_path(ctx, prt.new_sha1))
            if os.path.exists(fpath):
                # Overwriting an existing tag? Git prohibits that.
                # But, another lightweight tag of the same object is okay.
                # Sanity check if this is a lightweight tag of an annotated
                # tag and reject with a warning.
                with open(fpath, 'rb') as f:
                    contents = f.read()
                try:
                    zlib.decompress(contents)
                    msg = _(
                        "Tag '{tag}' of annotated tag will not be stored in Perforce\n"
                    )
                    sys.stderr.write(msg.format(tag=tag))
                except zlib.error:
                    pass

        elif prt.new_sha1 != p4gf_const.NULL_COMMIT_SHA1:
            # Older versions of Git allowed moving a tag reference, while
            # newer ones seemingly do not. We will take the new behavior as
            # the correct one and reject such changes.
            return _(
                'Updates were rejected because the tag already exists in the remote.'
            )