예제 #1
0
async def run(
    ctxt: context.Context,
    sources: typing.List[context.T_PayloadEventSource],
) -> typing.Optional[check_api.Result]:
    LOG.debug("engine get context")
    ctxt.log.debug("engine start processing context")

    issue_comment_sources: typing.List[T_PayloadEventIssueCommentSource] = []

    for source in sources:
        if source["event_type"] == "issue_comment":
            issue_comment_sources.append(
                typing.cast(T_PayloadEventIssueCommentSource, source))
        else:
            ctxt.sources.append(source)

    permissions_need_to_be_updated = github_app.permissions_need_to_be_updated(
        ctxt.repository.installation.installation)
    if permissions_need_to_be_updated:
        return check_api.Result(
            check_api.Conclusion.FAILURE,
            title="Required GitHub permissions are missing.",
            summary="You can accept them at https://dashboard.mergify.com/",
        )

    if ctxt.pull["base"]["repo"]["private"]:
        if not ctxt.subscription.has_feature(
                subscription.Features.PRIVATE_REPOSITORY):
            ctxt.log.info("mergify disabled: private repository",
                          reason=ctxt.subscription.reason)
            return check_api.Result(
                check_api.Conclusion.FAILURE,
                title="Mergify is disabled",
                summary=ctxt.subscription.reason,
            )
    else:
        if not ctxt.subscription.has_feature(
                subscription.Features.PUBLIC_REPOSITORY):
            ctxt.log.info("mergify disabled: public repository",
                          reason=ctxt.subscription.reason)
            return check_api.Result(
                check_api.Conclusion.FAILURE,
                title="Mergify is disabled",
                summary=ctxt.subscription.reason,
            )

    config_file = await ctxt.repository.get_mergify_config_file()

    try:
        ctxt.configuration_changed = await _check_configuration_changes(
            ctxt, config_file)
    except MultipleConfigurationFileFound as e:
        files = "\n * " + "\n * ".join(f["path"] for f in e.files)
        # NOTE(sileht): This replaces the summary, so we will may lost the
        # state of queue/comment action. But since we can't choice which config
        # file we need to use... we can't do much.
        return check_api.Result(
            check_api.Conclusion.FAILURE,
            title=constants.CONFIGURATION_MUTIPLE_FOUND_SUMMARY_TITLE,
            summary=
            f"You must keep only one of these configuration files in the repository: {files}",
        )

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = await ctxt.repository.get_mergify_config()
    except rules.InvalidRules as e:  # pragma: no cover
        ctxt.log.info(
            "The Mergify configuration is invalid",
            summary=str(e),
            annotations=e.get_annotations(e.filename),
        )
        # Not configured, post status check with the error message
        for s in ctxt.sources:
            if s["event_type"] == "pull_request":
                event = typing.cast(github_types.GitHubEventPullRequest,
                                    s["data"])
                if event["action"] in ("opened", "synchronize"):
                    return check_api.Result(
                        check_api.Conclusion.FAILURE,
                        title="The current Mergify configuration is invalid",
                        summary=str(e),
                        annotations=e.get_annotations(e.filename),
                    )
        return None

    ctxt.log.debug("engine run pending commands")
    await commands_runner.run_pending_commands_tasks(ctxt, mergify_config)

    if issue_comment_sources:
        ctxt.log.debug("engine handle commands")
        for ic_source in issue_comment_sources:
            await commands_runner.handle(
                ctxt,
                mergify_config,
                ic_source["data"]["comment"]["body"],
                ic_source["data"]["comment"]["user"],
            )

    await _ensure_summary_on_head_sha(ctxt)

    summary = await ctxt.get_engine_check_run(constants.SUMMARY_NAME)
    if (summary and summary["external_id"] is not None
            and summary["external_id"] != ""
            and summary["external_id"] != str(ctxt.pull["number"])):
        other_ctxt = await ctxt.repository.get_pull_request_context(
            github_types.GitHubPullRequestNumber(int(summary["external_id"])))
        # NOTE(sileht): allow to override the summary of another pull request
        # only if this one is closed, but this can still confuse users as the
        # check-runs created by merge/queue action will not be cleaned.
        # TODO(sileht): maybe cancel all other mergify engine check-runs in this case?
        if not other_ctxt.closed:
            # TODO(sileht): try to report that without check-runs/statuses to the user
            # and without spamming him with comment
            ctxt.log.info(
                "sha collision detected between pull requests",
                other_pull=summary["external_id"],
            )
            return None

    if not ctxt.has_been_opened() and summary is None:
        ctxt.log.warning(
            "the pull request doesn't have a summary",
            head_sha=ctxt.pull["head"]["sha"],
        )

    ctxt.log.debug("engine handle actions")
    if ctxt.is_merge_queue_pr():
        return await queue_runner.handle(mergify_config["queue_rules"], ctxt)
    else:
        return await actions_runner.handle(
            mergify_config["pull_request_rules"], ctxt)
예제 #2
0
async def run(
    ctxt: context.Context,
    sources: typing.List[context.T_PayloadEventSource],
) -> None:
    LOG.debug("engine get context")
    ctxt.log.debug("engine start processing context")

    issue_comment_sources: typing.List[T_PayloadEventIssueCommentSource] = []

    for source in sources:
        if source["event_type"] == "issue_comment":
            issue_comment_sources.append(
                typing.cast(T_PayloadEventIssueCommentSource, source))
        else:
            ctxt.sources.append(source)

    if ctxt.client.auth.permissions_need_to_be_updated:
        await ctxt.set_summary_check(
            check_api.Result(
                check_api.Conclusion.FAILURE,
                title="Required GitHub permissions are missing.",
                summary="You can accept them at https://dashboard.mergify.io/",
            ))
        return

    if ctxt.pull["base"]["repo"][
            "private"] and not ctxt.subscription.has_feature(
                subscription.Features.PRIVATE_REPOSITORY):
        ctxt.log.info("mergify disabled: private repository")
        await ctxt.set_summary_check(
            check_api.Result(
                check_api.Conclusion.FAILURE,
                title="Mergify is disabled",
                summary=ctxt.subscription.reason,
            ))
        return

    config_file = await ctxt.repository.get_mergify_config_file()

    ctxt.configuration_changed = await _check_configuration_changes(
        ctxt, config_file)

    ctxt.log.debug("engine get configuration")
    if config_file is None:
        ctxt.log.debug("No config file using defaults")
        config_file = DEFAULT_CONFIG_FILE

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = rules.get_mergify_config(config_file)
    except rules.InvalidRules as e:  # pragma: no cover
        ctxt.log.info(
            "The Mergify configuration is invalid",
            summary=str(e),
            annotations=e.get_annotations(e.filename),
        )
        # Not configured, post status check with the error message
        for s in ctxt.sources:
            if s["event_type"] == "pull_request":
                event = typing.cast(github_types.GitHubEventPullRequest,
                                    s["data"])
                if event["action"] in ("opened", "synchronize"):
                    await ctxt.set_summary_check(
                        check_api.Result(
                            check_api.Conclusion.FAILURE,
                            title="The Mergify configuration is invalid",
                            summary=str(e),
                            annotations=e.get_annotations(e.filename),
                        ))
                    break
        return

    # Add global and mandatory rules
    mergify_config["pull_request_rules"].rules.extend(
        MERGIFY_BUILTIN_CONFIG["pull_request_rules"].rules)

    ctxt.log.debug("engine run pending commands")
    await commands_runner.run_pending_commands_tasks(ctxt, mergify_config)

    if issue_comment_sources:
        ctxt.log.debug("engine handle commands")
        for ic_source in issue_comment_sources:
            await commands_runner.handle(
                ctxt,
                mergify_config,
                ic_source["data"]["comment"]["body"],
                ic_source["data"]["comment"]["user"],
            )

    if not ctxt.sources:
        return

    await _ensure_summary_on_head_sha(ctxt)

    # NOTE(jd): that's fine for now, but I wonder if we wouldn't need a higher abstraction
    # to have such things run properly. Like hooks based on events that you could
    # register. It feels hackish otherwise.
    for s in ctxt.sources:
        if s["event_type"] == "pull_request":
            event = typing.cast(github_types.GitHubEventPullRequest, s["data"])
            if event["action"] == "closed":
                await ctxt.clear_cached_last_summary_head_sha()
                break

    ctxt.log.debug("engine handle actions")
    if ctxt.is_merge_queue_pr():
        await queue_runner.handle(mergify_config["queue_rules"], ctxt)
    else:
        await actions_runner.handle(mergify_config["pull_request_rules"], ctxt)