Exemplo n.º 1
0
    def call_project_specific_commit_checker(self):
        """Call hooks.commit-extra-checker for all added commits (if set).

        Raise InvalidUpdate with the associated error message if the hook
        returned nonzero. Just print the hook's output otherwise.
        """
        commit_checker_hook = ThirdPartyHook("hooks.commit-extra-checker")
        if not commit_checker_hook.defined_p:
            return

        for commit in self.commits_to_check:
            hook_exe, p, out = commit_checker_hook.call(
                hook_input=json.dumps(self.commit_data_for_hook(commit)),
                hook_args=(self.ref_name, commit.rev),
            )
            if p.returncode != 0:
                raise InvalidUpdate(
                    "The following commit was rejected by your"
                    " hooks.commit-extra-checker script"
                    " (status: {p.returncode})".format(p=p),
                    "commit: {commit.rev}".format(commit=commit),
                    *out.splitlines())
            else:
                sys.stdout.write(out)
Exemplo n.º 2
0
    def call_project_specific_commit_checker(self):
        """Call hooks.commit-extra-checker for all added commits (if set).

        Raise InvalidUpdate with the associated error message if the hook
        returned nonzero. Just print the hook's output otherwise.
        """
        commit_checker_hook = ThirdPartyHook("hooks.commit-extra-checker")
        if not commit_checker_hook.defined_p:
            return

        for commit in self.commits_to_check:
            hook_exe, p, out = commit_checker_hook.call(
                hook_input=json.dumps(self.commit_data_for_hook(commit)),
                hook_args=(self.ref_name, commit.rev),
            )
            if p.returncode != 0:
                # Python-2.x compatibility: When the list of arguments in a call
                # are so long that they get formatted over multiple lines, black
                # adds a comma at the end of the last argument. Unfortunately,
                # it looks like Python 2.x doesn't like that comma when one of
                # the parameter is a non-keyworded variable-length argument list
                # (aka *args).
                #
                # So, work around this issue until the transition to Python 3.x
                # by building a list with all the unnamed arguments, and then
                # use that list to make the call, thus ensuring that the call
                # is short-enough to fit in a single line.
                invalid_update_msg = [
                    "The following commit was rejected by your"
                    " hooks.commit-extra-checker script"
                    " (status: {p.returncode})".format(p=p),
                    "commit: {commit.rev}".format(commit=commit),
                ] + out.splitlines()
                raise InvalidUpdate(*invalid_update_msg)
            else:
                sys.stdout.write(out)
Exemplo n.º 3
0
    def __maybe_get_email_custom_contents(self, commit,
                                          default_subject,
                                          default_body,
                                          default_diff):
        """Return an EmailCustomContents for the given commit, if applicable.

        For projects that define the hooks.commit-email-formatter config
        option, this method calls the script it points to, and builds
        an EmailCustomContents object from the data returned by the script.

        Errors are handled as gracefully as possible, by returning
        an EmailCustomContents which only changes the body of the email
        by adding a warning section describing the error that occurred.

        Returns None if the hooks.commit-email-formatter is not defined
        by the project.

        PARAMETERS
            commit: A CommitInfo object.
            default_subject: The email's subject to use by default
                unless overriden by the commit-email-formatter hook.
            default_body: The email's body to use by default, unless
                overriden by the commit-email-formatter hook.
            default_diff: The email's "Diff:" section to use by default,
                unless overriden by the commit-email-formatter hook.
        """
        email_contents_hook = ThirdPartyHook("hooks.commit-email-formatter")
        if not email_contents_hook.defined_p:
            return None

        hooks_data = self.commit_data_for_hook(commit)
        hooks_data["email_default_subject"] = default_subject
        hooks_data["email_default_body"] = default_body
        hooks_data["email_default_diff"] = default_diff

        hook_exe, p, out = email_contents_hook.call(
            hook_input=json.dumps(hooks_data),
            hook_args=(self.ref_name, commit.rev),
        )

        def standard_email_due_to_error(err_msg):
            """Return an EmailCustomContents with the given err_msg.

            This function allows us to perform consistent error handling
            when trying to call the hooks.commit-email-formatter script.
            It returns an EmailCustomContents where nothing is changed
            (and therefore the standard email gets sent) except for
            the addition of a warning section at the end of the email's
            body (just before the "Diff:" section). This warning section
            indicates that an error was detected, and provides information
            about it.

            PARAMETERS
                err_msg: A description of the error that occurred (a string).
            """
            appendix = "WARNING:\n" \
                "{err_msg}\n" \
                "Falling back to default email format.\n" \
                "\n" \
                "$ {hook_exe} {update.ref_name}" \
                " {commit.rev}\n" \
                "{out}\n".format(
                    err_msg=err_msg, hook_exe=hook_exe, update=self,
                    commit=commit, out=out)
            return EmailCustomContents(
                appendix=indent(appendix, "| "),
                diff=default_diff)

        if p.returncode != 0:
            return standard_email_due_to_error(
                "hooks.commit-email-formatter returned nonzero:"
                " {p.returncode}.".format(p=p))

        try:
            contents_data = json.loads(out)
        except ValueError:
            return standard_email_due_to_error(
                "hooks.commit-email-formatter returned invalid JSON.")

        if not isinstance(contents_data, dict):
            return standard_email_due_to_error(
                "hooks.commit-email-formatter output is not JSON dict.")

        return EmailCustomContents(
            subject=contents_data.get("email_subject"),
            body=contents_data.get("email_body"),
            diff=contents_data.get("diff", default_diff))
Exemplo n.º 4
0
def style_check_files(filename_list, commit_rev, project_name):
    """Check a file for style violations if appropriate.

    Raise InvalidUpdate if one or more style violations are detected.

    PARAMETERS
        filename_list: The name of the file to check (an iterable).
        commit_rev: The associated commit sha1.  This piece of information
            helps us find the correct version of the files to be checked,
            as well as the .gitattributes files which are used to determine
            whether pre-commit-checks should be applied or not.
        project_name: The name of the project (same as the attribute
            in updates.emails.EmailInfo).
    """
    debug(
        "style_check_files (commit_rev=%s):\n%s" %
        (commit_rev, "\n".join([" - `%s'" % fname
                                for fname in filename_list])),
        level=3,
    )

    config_file = git_config("hooks.style-checker-config-file")

    # Auxilary list of files we need to fetch from the same reference
    # for purposes other than checking their contents.
    aux_files = []
    if config_file is not None and config_file not in filename_list:
        if not file_exists(commit_rev, config_file):
            info = (STYLE_CHECKER_CONFIG_FILE_MISSING_ERR_MSG % {
                "config_filename": config_file,
                "commit_rev": commit_rev
            }).splitlines()
            raise InvalidUpdate(*info)
        aux_files.append(config_file)

    # Get a copy of all the files and save them in our scratch dir.
    # In order to allow us to call the style-checker using
    # the full path (from the project's root directory) of
    # the files being checked, we re-create the path to those
    # filenames, and then copy the files at the same path.
    #
    # Providing the path as part of the filename argument is useful,
    # because it allows the messages printed by the style-checker
    # to be unambiguous in the situation where the same project
    # has multiple files sharing the same name. More generally,
    # it can also be useful to quickly locate a file in the project
    # when trying to make the needed corrections outlined by the
    # style-checker.
    for filename in itertools.chain(filename_list, aux_files):
        path_to_filename = "%s/%s" % (utils.scratch_dir,
                                      os.path.dirname(filename))
        if not os.path.exists(path_to_filename):
            os.makedirs(path_to_filename)
        git.show(
            "%s:%s" % (commit_rev, filename),
            _outfile="%s/%s" % (utils.scratch_dir, filename),
        )

    # Call the style-checker.

    # For testing purposes, provide a back-door allowing the user
    # to override the style-checking program to be used.  That way,
    # the testsuite has a way to control what the program returns,
    # and easily test all execution paths without having to maintain
    # some sources specifically designed to trigger the various
    # error conditions.
    style_checker_hook = ThirdPartyHook("hooks.style-checker")
    if "GIT_HOOKS_STYLE_CHECKER" in os.environ:
        style_checker_hook.hook_exe = os.environ["GIT_HOOKS_STYLE_CHECKER"]
    style_checker_hook_args = []
    if config_file is not None:
        style_checker_hook_args.extend(["--config", config_file])
    style_checker_hook_args.append(project_name)

    _, p, out = style_checker_hook.call(
        hook_input="\n".join(filename_list),
        hook_args=style_checker_hook_args,
        cwd=utils.scratch_dir,
    )

    if p.returncode != 0:
        info = ["pre-commit check failed for commit: %s" % commit_rev
                ] + out.splitlines()
        raise InvalidUpdate(*info)

    # If we reach this point, it means that the style-checker returned
    # zero (success). Print any output, it might be a non-fatal warning.
    if out:
        warn(*out.splitlines())