예제 #1
0
async def have_unexpected_changes(
    ctxt: context.Context, car: merge_train.TrainCar
) -> bool:
    if ctxt.pull["base"]["sha"] != car.initial_current_base_sha:
        ctxt.log.info(
            "train car has an unexpected base sha change",
            base_sha=ctxt.pull["base"]["sha"],
            initial_current_base_sha=car.initial_current_base_sha,
        )
        return True

    if ctxt.has_been_synchronized():
        ctxt.log.info(
            "train car has unexpectedly been synchronized",
        )
        return True

    unexpected_event = first(
        (source for source in ctxt.sources),
        key=lambda s: s["event_type"] == "pull_request"
        and typing.cast(github_types.GitHubEventPullRequest, s["data"])["action"]
        in ["closed", "reopened"],
    )
    if unexpected_event:
        ctxt.log.debug(
            "train car received an unexpected event",
            unexpected_event=unexpected_event,
        )
        return True

    return False
예제 #2
0
    async def _should_be_cancel(self, ctxt: context.Context,
                                rule: "rules.EvaluatedRule") -> bool:
        # It's closed, it's not going to change
        if ctxt.pull["state"] == "closed":
            return True

        if ctxt.has_been_synchronized():
            return True

        q = await merge_train.Train.from_context(ctxt)
        car = q.get_car(ctxt)
        if car and car.state == "updated":
            # NOTE(sileht) check first if PR should be removed from the queue
            pull_rule_checks_status = await self.get_pull_rule_checks_status(
                ctxt, rule)
            if pull_rule_checks_status == check_api.Conclusion.FAILURE:
                return True

            # NOTE(sileht): This car have been updated/rebased, so we should not cancel
            # the merge until we have a check that doesn't pass
            queue_rule_evaluated = await self.queue_rule.get_pull_request_rule(
                ctxt)
            queue_rule_checks_status = await merge_train.get_queue_rule_checks_status(
                ctxt, queue_rule_evaluated)
            return queue_rule_checks_status == check_api.Conclusion.FAILURE

        return True
예제 #3
0
    async def _should_be_cancel(self, ctxt: context.Context,
                                rule: "rules.EvaluatedRule") -> bool:
        # It's closed, it's not going to change
        if ctxt.pull["state"] == "closed":
            return True

        if ctxt.has_been_synchronized():
            return True

        pull_rule_checks_status = await self.get_pull_rule_checks_status(
            ctxt, rule)
        return pull_rule_checks_status == check_api.Conclusion.FAILURE
예제 #4
0
    async def run(self, ctxt: context.Context,
                  rule: "rules.EvaluatedRule") -> check_api.Result:
        subscription_status = await self._subscription_status(ctxt)
        if subscription_status:
            return subscription_status

        q = await merge_train.Train.from_context(ctxt)
        car = q.get_car(ctxt)
        if car and car.state == "updated":
            # NOTE(sileht): This car doesn't have tmp pull, so we have the
            # MERGE_QUEUE_SUMMARY and train reset here
            queue_rule_evaluated = await self.queue_rule.get_pull_request_rule(
                ctxt)
            need_reset = ctxt.has_been_synchronized() or await ctxt.is_behind
            if need_reset:
                status = check_api.Conclusion.PENDING
                ctxt.log.info("train will be reset")
                await q.reset()
            else:
                status = await merge_train.get_queue_rule_checks_status(
                    ctxt, queue_rule_evaluated)
            await car.update_summaries(status,
                                       status,
                                       queue_rule_evaluated,
                                       will_be_reset=need_reset)

        if ctxt.user_refresh_requested() or ctxt.admin_refresh_requested():
            # NOTE(sileht): user ask a refresh, we just remove the previous state of this
            # check and the method _should_be_queue will become true again :)
            check = await ctxt.get_engine_check_run(
                constants.MERGE_QUEUE_SUMMARY_NAME)
            if check and check_api.Conclusion(check["conclusion"]) not in [
                    check_api.Conclusion.SUCCESS,
                    check_api.Conclusion.PENDING,
            ]:
                await check_api.set_check_run(
                    ctxt,
                    constants.MERGE_QUEUE_SUMMARY_NAME,
                    check_api.Result(
                        check_api.Conclusion.PENDING,
                        "The pull request has been refreshed and is going to be re-embarked soon",
                        "",
                    ),
                )

        return await super().run(ctxt, rule)
예제 #5
0
    async def run(
        self, ctxt: context.Context, rule: rules.EvaluatedRule
    ) -> check_api.Result:

        if self.config["message"] is None:
            message_raw = DEFAULT_MESSAGE[self.config["when"]]
        else:
            message_raw = typing.cast(str, self.config["message"])

        try:
            message = await ctxt.pull_request.render_template(message_raw)
        except context.RenderTemplateFailure as rmf:
            return check_api.Result(
                check_api.Conclusion.FAILURE,
                "Invalid dismiss reviews message",
                str(rmf),
            )

        if self.config["when"] == WHEN_SYNCHRONIZE and not ctxt.has_been_synchronized():
            return check_api.Result(
                check_api.Conclusion.SUCCESS,
                "Nothing to do, pull request has not been synchronized",
                "",
            )

        # FIXME(sileht): Currently sender id is not the bot by the admin
        # user that enroll the repo in Mergify, because branch_updater uses
        # his access_token instead of the Mergify installation token.
        # As workaround we track in redis merge commit id
        # This is only true for method="rebase"
        if (
            self.config["when"] == WHEN_SYNCHRONIZE
            and not await ctxt.has_been_synchronized_by_user()
        ):
            return check_api.Result(
                check_api.Conclusion.SUCCESS, "Updated by Mergify, ignoring", ""
            )

        requested_reviewers_login = [
            rr["login"] for rr in ctxt.pull["requested_reviewers"]
        ]

        to_dismiss = set()
        for review in (await ctxt.consolidated_reviews())[1]:
            conf = self.config.get(review["state"].lower(), False)
            if conf is True:
                to_dismiss.add(review["id"])
            elif conf == FROM_REQUESTED_REVIEWERS:
                if review["user"]["login"] in requested_reviewers_login:
                    to_dismiss.add(review["id"])
            elif isinstance(conf, list):
                if review["user"]["login"] in conf:
                    to_dismiss.add(review["id"])

        if not to_dismiss:
            return check_api.Result(
                check_api.Conclusion.SUCCESS, "Nothing to dismiss", ""
            )

        errors = set()
        for review_id in to_dismiss:
            try:
                await ctxt.client.put(
                    f"{ctxt.base_url}/pulls/{ctxt.pull['number']}/reviews/{review_id}/dismissals",
                    json={"message": message},
                )
            except http.HTTPClientSideError as e:  # pragma: no cover
                errors.add(f"GitHub error: [{e.status_code}] `{e.message}`")

        if errors:
            return check_api.Result(
                check_api.Conclusion.PENDING,
                "Unable to dismiss review",
                "\n".join(errors),
            )
        else:
            await signals.send(ctxt, "action.dismiss_reviews")
            return check_api.Result(
                check_api.Conclusion.SUCCESS, "Review dismissed", ""
            )