Exemplo n.º 1
0
def new_update(ref_name, old_rev, new_rev, all_refs, submitter_email):
    """Return the correct object for the given parameters.

    PARAMETERS
        See AbstractUpdate.__init__.

    RETURN VALUE
        An object of the correct AbstractUpdate (child) class.
    """
    # At least one of the references must be non-null...
    assert not (is_null_rev(old_rev) and is_null_rev(new_rev))

    if is_null_rev(old_rev):
        change_type = CREATE
        object_type = get_object_type(new_rev)
    elif is_null_rev(new_rev):
        change_type = DELETE
        object_type = get_object_type(old_rev)
    else:
        change_type = UPDATE
        object_type = get_object_type(new_rev)

    new_cls = None
    for key in REF_CHANGE_MAP:
        (map_ref_prefix, map_change_type, map_object_type) = key
        if ((change_type == map_change_type
             and object_type == map_object_type
             and ref_name.startswith(map_ref_prefix))):
            new_cls = REF_CHANGE_MAP[key]
            break
    if new_cls is None:
        return None

    return new_cls(ref_name, old_rev, new_rev, all_refs,
                   submitter_email)
Exemplo n.º 2
0
def new_update(ref_name, old_rev, new_rev, all_refs, submitter_email):
    """Return the correct object for the given parameters.

    PARAMETERS
        See AbstractUpdate.__init__.

    RETURN VALUE
        An object of the correct AbstractUpdate (child) class.
    """
    # At least one of the references must be non-null...
    assert not (is_null_rev(old_rev) and is_null_rev(new_rev))

    if is_null_rev(old_rev):
        change_type = CREATE
        object_type = get_object_type(new_rev)
    elif is_null_rev(new_rev):
        change_type = DELETE
        object_type = get_object_type(old_rev)
    else:
        change_type = UPDATE
        object_type = get_object_type(new_rev)

    new_cls = None
    for key in REF_CHANGE_MAP:
        (map_ref_prefix, map_change_type, map_object_type) = key
        if ((change_type == map_change_type
             and object_type == map_object_type
             and ref_name.startswith(map_ref_prefix))):
            new_cls = REF_CHANGE_MAP[key]
            break
    if new_cls is None:
        return None

    return new_cls(ref_name, old_rev, new_rev, all_refs,
                   submitter_email)
Exemplo n.º 3
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update.

        REMARKS
            This method is capable of handling both creation update.
        """
        # Annotated tag creation/updates are always allowed.
        #
        # But, if this is a pre-existing tag being updated, there are
        # pitfalls that the user should be warned about.
        if not is_null_rev(self.old_rev) and not is_null_rev(self.new_rev):
            warn_about_tag_update(self.human_readable_tag_name(), self.old_rev,
                                  self.new_rev)
Exemplo n.º 4
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update.

        REMARKS
            This method is capable of handling both creation update.
        """
        # Annotated tag creation/updates are always allowed.
        #
        # But, if this is a pre-existing tag being updated, there are
        # pitfalls that the user should be warned about.
        if not is_null_rev(self.old_rev) and not is_null_rev(self.new_rev):
            warn_about_tag_update(self.short_ref_name,
                                  self.old_rev, self.new_rev)
Exemplo n.º 5
0
    def __do_style_checks(self):
        if self.search_config_option_list('hooks.no-style-checks') is not None:
            # The respository has been configured to disable all style
            # checks on this branch.
            debug('no style check on this branch (hooks.no-style-checks)')
            return

        added = self.__added_commits
        if git_config('hooks.combined-style-checking'):
            # This project prefers to perform the style check on
            # the cumulated diff, rather than commit-per-commit.
            # Behave as if the update only added one commit (new_rev),
            # with a single parent being old_rev.  If old_rev is nul
            # (branch creation), then use the first parent of the oldest
            # added commit.
            debug('(combined style checking)')
            if not added[-1].pre_existing_p:
                base_rev = (added[0].base_rev_for_git()
                            if is_null_rev(self.old_rev) else self.old_rev)
                style_check_commit(base_rev, self.new_rev,
                                   self.email_info.project_name)
        else:
            debug('(commit-per-commit style checking)')
            # Perform the pre-commit checks, as needed...
            for commit in added:
                if not commit.pre_existing_p:
                    style_check_commit(commit.base_rev_for_git(), commit.rev,
                                       self.email_info.project_name)
Exemplo n.º 6
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update.

        REMARKS
            This method is capable of handling both creation update.
        """
        # If lightweight tags are not allowed, refuse the update.
        if not git_config('hooks.allow-lightweight-tag'):
            raise InvalidUpdate(
                "Lightweight tags (%s) are not allowed in this repository."
                % self.short_ref_name,
                "Use 'git tag [ -a | -s ]' for tags you want to propagate.")

        # If this is a pre-existing tag being updated, there are pitfalls
        # that the user should be warned about.
        if not is_null_rev(self.old_rev) and not is_null_rev(self.new_rev):
            warn_about_tag_update(self.short_ref_name,
                                  self.old_rev, self.new_rev)
Exemplo n.º 7
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update.

        REMARKS
            This method is capable of handling both creation update.
        """
        # If lightweight tags are not allowed, refuse the update.
        if not git_config('hooks.allow-lightweight-tag'):
            raise InvalidUpdate(
                "Lightweight tags (%s) are not allowed in this repository." %
                self.human_readable_tag_name(),
                "Use 'git tag [ -a | -s ]' for tags you want to propagate.")

        # If this is a pre-existing tag being updated, there are pitfalls
        # that the user should be warned about.
        if not is_null_rev(self.old_rev) and not is_null_rev(self.new_rev):
            warn_about_tag_update(self.human_readable_tag_name(), self.old_rev,
                                  self.new_rev)
Exemplo n.º 8
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update."""
        reject_retired_branch_update(self.short_ref_name, self.all_refs)

        # Check that this is either a fast-forward update, or else that
        # forced-updates are allowed for that branch.  If old_rev is
        # null, then it's a new branch, and so fast-forward checks are
        # irrelevant.
        if not is_null_rev(self.old_rev):
            check_fast_forward(self.ref_name, self.old_rev, self.new_rev)
Exemplo n.º 9
0
def new_update(ref_name, old_rev, new_rev, all_refs, submitter_email):
    """Return the correct object for the given parameters.

    PARAMETERS
        See AbstractUpdate.__init__.

    RETURN VALUE
        An object of the correct AbstractUpdate (child) class.
    """
    if is_null_rev(old_rev) and is_null_rev(new_rev):
        # This happens when the user is trying to delete a specific
        # reference which does not exist in the repository.
        #
        # Note that this seems to only happen when the user passes
        # the full reference name in the delete-push. When using
        # a branch name (i.e. 'master' instead of 'refs/heads/master'),
        # git itself notices that the branch doesn't exist and returns
        # an error even before calling the hooks for validation.
        raise InvalidUpdate(
            "unable to delete '{}': remote ref does not exist".format(
                ref_name))

    if is_null_rev(old_rev):
        change_type = UpdateKind.create
        object_type = get_object_type(new_rev)
    elif is_null_rev(new_rev):
        change_type = UpdateKind.delete
        object_type = get_object_type(old_rev)
    else:
        change_type = UpdateKind.update
        object_type = get_object_type(new_rev)

    ref_kind = get_ref_kind(ref_name)
    if ref_kind is None:
        raise_unrecognized_ref_name(ref_name)

    new_cls = REF_CHANGE_MAP.get((ref_kind, change_type, object_type), None)
    if new_cls is None:
        return None

    return new_cls(ref_name, ref_kind, object_type, old_rev, new_rev, all_refs,
                   submitter_email)
Exemplo n.º 10
0
    def validate_ref_update(self):
        """See AbstractUpdate.validate_ref_update."""
        reject_retired_branch_update(self.short_ref_name,
                                     self.all_refs)

        # Check that this is either a fast-forward update, or else that
        # forced-updates are allowed for that branch.  If old_rev is
        # null, then it's a new branch, and so fast-forward checks are
        # irrelevant.
        if not is_null_rev(self.old_rev):
            check_fast_forward(self.ref_name, self.old_rev, self.new_rev)
Exemplo n.º 11
0
    def __check_commit_p(self, commit):
        """Return True if checks on the commit should be done; False if not.

        The purpose of this routine is to centralize the logic being used
        to determine whether a given commit should be subject to the various
        checks we apply to new commits, or not.

        commit: A CommitInfo object.
        """
        if commit.pre_existing_p:
            # This commit already exists in the repository, so we should
            # normally not check it. Otherwise, we could run the risk of
            # failing a check for a commit which was fine before but no
            # longer follows more recent policies. This would cause problems
            # when trying to create new references, for instance.
            #
            # Also, if we started checking pre-existing commits, this could
            # add up very quickly in situation where new branches are created
            # from branches that already have many commits.

            if is_null_rev(self.old_rev):
                # It is possible that the user may have requested that all
                # new commits in our reference be checked (see below) but,
                # since this is a new branch, we ignore that option for
                # pre-existing commits (otherwise, the same commits would be
                # perpetually be re-checked each time a new branch is created).
                return False

            elif (
                self.search_config_option_list("hooks.force-precommit-checks")
                is not None
            ):
                # The user explicitly requested that all new commits on
                # this reference much always be checked.
                return True

            return False

        if commit.is_revert():
            # We have decided that revert commits should not be subject
            # to any check (QB08-047). This allows users to quickly revert
            # a commit if need be, without having to worry about bumping
            # into any check of any kind.
            debug(
                "revert commit detected,"
                " all checks disabled for this commit: %s" % commit.rev
            )
            return False

        # All other commits should be checked.
        return True
Exemplo n.º 12
0
    def __ensure_fast_forward(self):
        """Raise InvalidUpdate if the update is not a fast-forward update.
        """
        if is_null_rev(self.old_rev):
            # Git Notes creation, and thus necessarily a fast-forward.
            return

        # Non-fast-foward updates are characterized by the fact that
        # there is at least one commit that is accessible from the old
        # revision which would no longer be accessible from the new
        # revision.
        if git.rev_list("%s..%s" % (self.new_rev, self.old_rev)) == "":
            return

        raise InvalidUpdate('Your Git Notes are not up to date.', '',
                            'Please update your Git Notes and push again.')
Exemplo n.º 13
0
    def __ensure_fast_forward(self):
        """Raise InvalidUpdate if the update is not a fast-forward update.
        """
        if is_null_rev(self.old_rev):
            # Git Notes creation, and thus necessarily a fast-forward.
            return

        # Non-fast-foward updates are characterized by the fact that
        # there is at least one commit that is accessible from the old
        # revision which would no longer be accessible from the new
        # revision.
        if git.rev_list("%s..%s" % (self.new_rev, self.old_rev)) == "":
            return

        raise InvalidUpdate(
            'Your Git Notes are not up to date.',
            '',
            'Please update your Git Notes and push again.')
Exemplo n.º 14
0
    def __get_lost_commits(self):
        """Return a list of CommitInfo objects lost after our update.

        RETURN VALUE
            A list of CommitInfo objects, or the empty list if
            the update did not cause any commit to be lost.
        """
        if is_null_rev(self.old_rev):
            # We are creating a new reference, so we cannot possibly
            # be losing commits.
            return []

        # The list of lost commits is computed by listing all commits
        # accessible from the old_rev, but not from any of the references.

        exclude = ['^%s' % self.all_refs[rev] for rev in self.all_refs.keys()]
        commit_list = commit_info_list(self.old_rev, *exclude)

        return commit_list
Exemplo n.º 15
0
    def __get_lost_commits(self):
        """Return a list of CommitInfo objects lost after our update.

        RETURN VALUE
            A list of CommitInfo objects, or the empty list if
            the update did not cause any commit to be lost.
        """
        if is_null_rev(self.old_rev):
            # We are creating a new reference, so we cannot possibly
            # be losing commits.
            return []

        # The list of lost commits is computed by listing all commits
        # accessible from the old_rev, but not from any of the references.

        exclude = ['^%s' % self.all_refs[rev]
                   for rev in self.all_refs.keys()]
        commit_list = commit_info_list(self.old_rev, *exclude)

        return commit_list
Exemplo n.º 16
0
    def __do_style_checks(self):
        if self.search_config_option_list("hooks.no-style-checks") is not None:
            # The respository has been configured to disable all style
            # checks on this branch.
            debug("no style check on this branch (hooks.no-style-checks)")
            return

        added = self.commits_to_check
        if not added:
            # There might be some new commits (e.g. it could be commits
            # explicitly excluded, such as "revert" commits), but none
            # of those commits need to be checked, and consequently
            # no style-checking to be done.
            return

        if git_config("hooks.combined-style-checking"):
            # This project prefers to perform the style check on
            # the cumulated diff, rather than commit-per-commit.
            # Behave as if the update only added one commit (new_rev),
            # with a single parent being old_rev.  If old_rev is nul
            # (branch creation), then use the first parent of the oldest
            # added commit.
            debug("(combined style checking)")
            if not added[-1].pre_existing_p:
                base_rev = (
                    added[0].base_rev_for_git()
                    if is_null_rev(self.old_rev)
                    else self.old_rev
                )
                style_check_commit(base_rev, self.new_rev, self.email_info.project_name)
        else:
            debug("(commit-per-commit style checking)")
            # Perform the pre-commit checks, as needed...
            for commit in added:
                style_check_commit(
                    commit.base_rev_for_git(), commit.rev, self.email_info.project_name
                )
Exemplo n.º 17
0
    def pre_commit_checks(self):
        """Run the pre-commit checks on this update's new commits.

        Determine the list of new commits introduced by this
        update, and perform the pre-commit-checks on them as
        appropriate.  Raise InvalidUpdate if one or more style
        violation was detected.
        """
        if is_null_rev(self.new_rev):
            # We are deleting a reference, so there cannot be any
            # new commit.
            return

        # Check to see if any of the entries in hooks.no-precommit-check
        # might be matching our reference name...
        for exp in git_config('hooks.no-precommit-check'):
            exp = exp.strip()
            if re.match(exp, self.ref_name):
                # Pre-commit checks are explicitly disabled on this branch.
                debug("(hooks.no-precommit-check match: `%s')" % exp)
                syslog('Pre-commit checks disabled for %(rev)s on %(repo)s'
                       ' by hooks.no-precommit-check config (%(ref_name)s)'
                       % {'rev': self.new_rev,
                          'repo': self.email_info.project_name,
                          'ref_name': self.ref_name,
                          })
                return

        if self.__no_cvs_check_user_override():
            # Just return. All necessary traces have already been
            # handled by the __no_cvs_check_user_override method.
            return

        added = self.added_commits
        if not added:
            # There are no new commits, so nothing further to check.
            return

        # Determine whether we should be doing RH style checking...
        do_rh_style_checks = (
            self.search_config_option_list('hooks.no-rh-style-checks')
            is None)

        # Perform the revision-history of all new commits, unless
        # specifically disabled by configuration.
        #
        # Done separately from the rest of the pre-commit checks,
        # which check the files changed by the commits, because of
        # the case where hooks.combined-style-checking is true;
        # we do not want to forget checking the revision history
        # of some of the commits.
        if do_rh_style_checks:
            for commit in added:
                if not commit.pre_existing_p:
                    check_revision_history(commit.rev)

        reject_merge_commits = (
            self.search_config_option_list('hooks.reject-merge-commits')
            is not None)
        if reject_merge_commits:
            for commit in added:
                reject_commit_if_merge(commit, self.ref_name)

        # Perform the filename-collision checks.  These collisions
        # can cause a lot of confusion and fustration to the users,
        # so do not provide the option of doing the check on the
        # final commit only (following hooks.combined-style-checking).
        # Do it on all new commits.
        for commit in added:
            if not commit.pre_existing_p:
                check_filename_collisions(commit.rev)

        if git_config('hooks.combined-style-checking'):
            # This project prefers to perform the style check on
            # the cumulated diff, rather than commit-per-commit.
            # Behave as if the update only added one commit (new_rev),
            # with a single parent being old_rev.  If old_rev is nul
            # (branch creation), then use the first parent of the oldest
            # added commit.
            debug('(combined style checking)')
            if not added[-1].pre_existing_p:
                base_rev = (
                    added[0].base_rev_for_git() if is_null_rev(self.old_rev)
                    else self.old_rev)
                check_commit(base_rev, self.new_rev,
                             self.email_info.project_name)
        else:
            debug('(commit-per-commit style checking)')
            # Perform the pre-commit checks, as needed...
            for commit in added:
                if not commit.pre_existing_p:
                    check_commit(commit.base_rev_for_git(), commit.rev,
                                 self.email_info.project_name)
Exemplo n.º 18
0
    def __get_added_commits(self):
        """Return a list of CommitInfo objects added by our update.

        RETURN VALUE
            A list of CommitInfo objects, or the empty list if
            the update did not introduce any new commit.
        """
        if is_null_rev(self.new_rev):
            return []

        # Compute the list of commits that are not accessible from
        # any of the references.  These are the commits which are
        # new in the repository.
        #
        # Note that we do not use the commit_info_list function for
        # that, because we only need the commit hashes, and a list
        # of commit hashes is more convenient for what we want to do
        # than a list of CommitInfo objects.

        exclude = [
            '^%s' % self.all_refs[ref_name]
            for ref_name in self.all_refs.keys() if ref_name != self.ref_name
        ]
        if not is_null_rev(self.old_rev):
            exclude.append('^%s' % self.old_rev)

        new_repo_revs = git.rev_list(self.new_rev,
                                     *exclude,
                                     reverse=True,
                                     _split_lines=True)

        # If this is a reference creation (base_rev is null), try to
        # find a commit which can serve as base_rev.  We try to find
        # a pre-existing commit making the base_rev..new_rev list
        # as short as possible.
        base_rev = self.old_rev
        if is_null_rev(base_rev):
            if len(new_repo_revs) > 0:
                # The ref update brings some new commits.  The first
                # parent of the oldest of those commits, if it exists,
                # seems like a good candidate.  If it does not exist,
                # we are pushing an entirely new headless branch, and
                # base_rev should remain null.
                parents = commit_parents(new_repo_revs[0])
                if parents:
                    base_rev = parents[0]
            else:
                # This reference update does not bring any new commits
                # at all. This means new_rev is already accessible
                # through one of the references, thus making it a good
                # base_rev as well.
                base_rev = self.new_rev

        # Expand base_rev..new_rev to compute the list of commits which
        # are new for the reference.  If there is no actual base_rev
        # (Eg. a headless branch), then expand to all commits accessible
        # from that reference.
        if not is_null_rev(base_rev):
            commit_list = commit_info_list(self.new_rev, '^%s' % base_rev)
            base_rev = commit_rev(base_rev)
        else:
            commit_list = commit_info_list(self.new_rev)
            base_rev = None

        # Iterate over every commit, and set their pre_existing_p attribute.
        for commit in commit_list:
            commit.pre_existing_p = commit.rev not in new_repo_revs

        debug('update base: %s' % base_rev)

        return commit_list
Exemplo n.º 19
0
    def pre_commit_checks(self):
        """Run the pre-commit checks on this update's new commits.

        Determine the list of new commits introduced by this
        update, and perform the pre-commit-checks on them as
        appropriate.  Raise InvalidUpdate if one or more style
        violation was detected.
        """
        if is_null_rev(self.new_rev):
            # We are deleting a reference, so there cannot be any
            # new commit.
            return

        # Check to see if any of the entries in hooks.no-precommit-check
        # might be matching our reference name...
        for exp in git_config('hooks.no-precommit-check'):
            exp = exp.strip()
            if re.match(exp, self.ref_name):
                # Pre-commit checks are explicitly disabled on this branch.
                debug("(hooks.no-precommit-check match: `%s')" % exp)
                return

        if self.__no_cvs_check_user_override():
            # Just return. All necessary traces have already been
            # handled by the __no_cvs_check_user_override method.
            return

        # Create a list of commits that were added, but with revert
        # commits being filtered out. We have decided that revert commits
        # should not be subject to any check (QB08-047).  This allows
        # users to quickly revert a commit if need be, without having
        # to worry about bumping into any check of any kind.
        added = self.added_commits
        for commit in added:
            if is_revert_commit(commit.rev):
                debug('revert commit detected,'
                      ' all checks disabled for this commit: %s' % commit.rev)
                added.remove(commit)

        if not added:
            # There are no new commits, so nothing further to check.
            return

        # Determine whether we should be doing RH style checking...
        do_rh_style_checks = (
            self.search_config_option_list('hooks.no-rh-style-checks') is None)

        # Perform the revision-history of all new commits, unless
        # specifically disabled by configuration.
        #
        # Done separately from the rest of the pre-commit checks,
        # which check the files changed by the commits, because of
        # the case where hooks.combined-style-checking is true;
        # we do not want to forget checking the revision history
        # of some of the commits.
        if do_rh_style_checks:
            for commit in added:
                if not commit.pre_existing_p:
                    check_revision_history(commit.rev)

        reject_merge_commits = (
            self.search_config_option_list('hooks.reject-merge-commits')
            is not None)
        if reject_merge_commits:
            for commit in added:
                reject_commit_if_merge(commit, self.ref_name)

        # Perform the filename-collision checks.  These collisions
        # can cause a lot of confusion and fustration to the users,
        # so do not provide the option of doing the check on the
        # final commit only (following hooks.combined-style-checking).
        # Do it on all new commits.
        for commit in added:
            if not commit.pre_existing_p:
                check_filename_collisions(commit.rev)

        self.__do_style_checks()
Exemplo n.º 20
0
    def pre_commit_checks(self):
        """Run the pre-commit checks on this update's new commits.

        Determine the list of new commits introduced by this
        update, and perform the pre-commit-checks on them as
        appropriate.  Raise InvalidUpdate if one or more style
        violation was detected.
        """
        if is_null_rev(self.new_rev):
            # We are deleting a reference, so there cannot be any
            # new commit.
            return

        # Check to see if any of the entries in hooks.no-precommit-check
        # might be matching our reference name...
        for exp in git_config('hooks.no-precommit-check'):
            exp = exp.strip()
            if re.match(exp, self.ref_name):
                # Pre-commit checks are explicitly disabled on this branch.
                debug("(hooks.no-precommit-check match: `%s')" % exp)
                return

        if self.__no_cvs_check_user_override():
            # Just return. All necessary traces have already been
            # handled by the __no_cvs_check_user_override method.
            return

        # Perform the check against merge commits early, for a couple
        # of reasons: (1) this is a relatively inexpensive check to be
        # performing, so might as well do it now and error out early
        # if the update is violating this check; and (2) we want to
        # perform this check on all commits new for this reference,
        # including any commit that we would otherwise exclude for
        # the other validation checks (such as revert commits, for instance).
        # Otherwise, we cannot guaranty that a given reference which
        # is configured to disallow merge commits stays free of merge
        # commits.
        reject_merge_commits = (
            self.search_config_option_list('hooks.reject-merge-commits')
            is not None)
        if reject_merge_commits:
            # See comment just above explaining why, for this check,
            # we iterate over self.new_commits_for_ref rather than
            # self.commits_to_check.
            for commit in self.new_commits_for_ref:
                reject_commit_if_merge(commit, self.ref_name)

        # Determine whether we should be doing RH style checking...
        do_rh_style_checks = (
            self.search_config_option_list('hooks.no-rh-style-checks')
            is None)

        # Perform the revision-history of all new commits, unless
        # specifically disabled by configuration.
        #
        # Done separately from the rest of the pre-commit checks,
        # which check the files changed by the commits, because of
        # the case where hooks.combined-style-checking is true;
        # we do not want to forget checking the revision history
        # of some of the commits.
        if do_rh_style_checks:
            for commit in self.commits_to_check:
                check_revision_history(commit)

        # Perform the filename-collision checks.  These collisions
        # can cause a lot of confusion and fustration to the users,
        # so do not provide the option of doing the check on the
        # final commit only (following hooks.combined-style-checking).
        # Do it on all new commits.
        for commit in self.commits_to_check:
            check_filename_collisions(commit)

        # Perform the filepath length checks. File paths which
        # are too long can cause trouble on some file systems,
        # so check every single commit to avoid introducing
        # any commits which would violate this check.
        for commit in self.commits_to_check:
            check_filepath_length(commit)

        self.call_project_specific_commit_checker()

        self.__do_style_checks()
Exemplo n.º 21
0
def pre_receive(refs_data):
    """Implement the pre-receive hook.

    PARAMETERS
        refs_data: An OrderedDict, indexed by the name of the ref
            being updated, and containing 2-elements tuple.  This tuple
            contains the previous revision, and the new revision of the
            reference.
    """
    # Enforce a rule that, if the CONFIG_REF reference is being updated,
    # then it must be the only reference being updated.
    #
    # Rationale:
    # ----------
    #
    # The CONFIG_REF reference has special significance for us, as
    # this is the reference where we load the repository's configuration
    # from. And when a user pushes an update to that reference, it really
    # makes better sense to immediately take the changes it brings into
    # account. For instance, if we realized that the hooks are configured
    # with the wrong mailing-list address, and we push a change to fix
    # that, we want the corresponding email to be sent to the correct
    # address. Similarly, it allows us, during the initial repository
    # setup phase, to enable the hooks prior to adding the hooks's config,
    # followed by pushing the initial config as usual, so as to get
    # the benefits of having the hooks perform the necessary validation
    # checks and send emails when relevant.
    #
    # Now, the reason why we need to enforce that CONFIG_REF updates
    # be done on their own is because the update hook gets called
    # once per reference being updated. What this means is that
    # the update script doesn't have enough context to determine
    # with certainty where to get the correct configuration from.
    # Enforcing CONFIG_REF updates to be done on their own push
    # solves that problem.
    if len(refs_data) > 1 and CONFIG_REF in refs_data:
        err_msg = ["You are trying to push multiple references at the same time:"]
        err_msg.extend("  - {}".format(ref_name) for ref_name in sorted(refs_data))
        err_msg.extend(
            [
                "",
                "Updates to the {CONFIG_REF} reference must be pushed".format(
                    CONFIG_REF=CONFIG_REF
                ),
                "on their own. Please push this reference first, and then",
                "retry pushing the remaining references.",
            ]
        )
        raise InvalidUpdate(*err_msg)

    # Verify that we are not trying to delete the CONFIG_REF reference.
    # This is not allowed, because this would delete the repository's
    # configuration. We do this extra early so as to provide the user
    # with a clear message rather than hitting an error later on, when
    # we may not have enough context to generate a clear error message.
    if CONFIG_REF in refs_data:
        _, new_rev = refs_data[CONFIG_REF]
        if is_null_rev(new_rev):
            raise InvalidUpdate(
                "Deleting the reference {CONFIG_REF} is not allowed.".format(
                    CONFIG_REF=CONFIG_REF
                ),
                "",
                "This reference provides important configuration information",
                "and thus must not be deleted.",
            )
Exemplo n.º 22
0
    def __get_added_commits(self):
        """Return a list of CommitInfo objects added by our update.

        RETURN VALUE
            A list of CommitInfo objects, or the empty list if
            the update did not introduce any new commit.
        """
        if is_null_rev(self.new_rev):
            return []

        # Compute the list of commits that are not accessible from
        # any of the references.  These are the commits which are
        # new in the repository.
        #
        # Note that we do not use the commit_info_list function for
        # that, because we only need the commit hashes, and a list
        # of commit hashes is more convenient for what we want to do
        # than a list of CommitInfo objects.

        exclude = ['^%s' % self.all_refs[ref_name]
                   for ref_name in self.all_refs.keys()
                   if ref_name != self.ref_name]
        if not is_null_rev(self.old_rev):
            exclude.append('^%s' % self.old_rev)

        new_repo_revs = git.rev_list(self.new_rev, *exclude, reverse=True,
                                     _split_lines=True)

        # If this is a reference creation (base_rev is null), try to
        # find a commit which can serve as base_rev.  We try to find
        # a pre-existing commit making the base_rev..new_rev list
        # as short as possible.
        base_rev = self.old_rev
        if is_null_rev(base_rev):
            if len(new_repo_revs) > 0:
                # The ref update brings some new commits.  The first
                # parent of the oldest of those commits, if it exists,
                # seems like a good candidate.  If it does not exist,
                # we are pushing an entirely new headless branch, and
                # base_rev should remain null.
                parents = commit_parents(new_repo_revs[0])
                if parents:
                    base_rev = parents[0]
            else:
                # This reference update does not bring any new commits
                # at all. This means new_rev is already accessible
                # through one of the references, thus making it a good
                # base_rev as well.
                base_rev = self.new_rev

        # Expand base_rev..new_rev to compute the list of commits which
        # are new for the reference.  If there is no actual base_rev
        # (Eg. a headless branch), then expand to all commits accessible
        # from that reference.
        if not is_null_rev(base_rev):
            commit_list = commit_info_list(self.new_rev, '^%s' % base_rev)
            base_rev = commit_rev(base_rev)
        else:
            commit_list = commit_info_list(self.new_rev)
            base_rev = None

        # Iterate over every commit, and set their pre_existing_p attribute.
        for commit in commit_list:
            commit.pre_existing_p = commit.rev not in new_repo_revs

        debug('update base: %s' % base_rev)

        return commit_list