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
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
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
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)
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", "" )