async def cancel(self, ctxt: context.Context, rule: "rules.EvaluatedRule") -> check_api.Result: # FIXME(sileht): we should use the computed update_bot_account in TrainCar.update_pull(), # not the original one try: await action_utils.render_bot_account( ctxt, self.config["update_bot_account"], option_name="update_bot_account", required_feature=subscription.Features.MERGE_BOT_ACCOUNT, missing_feature_message= "Queue with `update_bot_account` set is unavailable", ) except action_utils.RenderBotAccountFailure as e: return check_api.Result(e.status, e.title, e.reason) self._set_effective_priority(ctxt) q = await merge_train.Train.from_context(ctxt) car = q.get_car(ctxt) await self._update_merge_queue_summary(ctxt, rule, q, car) result = await self.merge_report(ctxt) if result is None: # We just rebase the pull request, don't cancel it yet if CIs are # running. The pull request will be merged if all rules match again. # if not we will delete it when we received all CIs termination if await self._should_be_queued(ctxt, q): if await self._should_be_cancel(ctxt, rule, q): result = actions.CANCELLED_CHECK_REPORT else: result = await self.get_queue_status(ctxt, rule, q) else: result = await self.get_unqueue_status(ctxt, q) if result.conclusion is not check_api.Conclusion.PENDING: await q.remove_pull(ctxt) # The car may have been removed newcar = q.get_car(ctxt) # NOTE(sileht): Only refresh if the car still exists if (newcar and newcar.creation_state == "created" and newcar.queue_pull_request_number is not None and self.need_draft_pull_request_refresh() and not ctxt.has_been_only_refreshed()): # NOTE(sileht): It's not only refreshed, so we need to # update the associated transient pull request. # This is mandatory to filter out refresh to avoid loop # of refreshes between this PR and the transient one. await utils.send_pull_refresh( ctxt.repository.installation.redis.stream, ctxt.pull["base"]["repo"], pull_request_number=newcar.queue_pull_request_number, action="internal", source="forward from queue action (cancel)", ) return result
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 if self.config["method"] == "fast-forward": if self.config["update_method"] != "rebase": return check_api.Result( check_api.Conclusion.FAILURE, f"`update_method: {self.config['update_method']}` is not compatible with fast-forward merge method", "`update_method` must be set to `rebase`.", ) elif self.config["commit_message_template"] is not None: return check_api.Result( check_api.Conclusion.FAILURE, "Commit message can't be changed with fast-forward merge method", "`commit_message_template` must not be set if `method: fast-forward` is set.", ) elif self.queue_rule.config["batch_size"] > 1: return check_api.Result( check_api.Conclusion.FAILURE, "batch_size > 1 is not compatible with fast-forward merge method", "The merge `method` or the queue configuration must be updated.", ) elif self.queue_rule.config["speculative_checks"] > 1: return check_api.Result( check_api.Conclusion.FAILURE, "speculative_checks > 1 is not compatible with fast-forward merge method", "The merge `method` or the queue configuration must be updated.", ) elif not self.queue_rule.config["allow_inplace_checks"]: return check_api.Result( check_api.Conclusion.FAILURE, "allow_inplace_checks=False is not compatible with fast-forward merge method", "The merge `method` or the queue configuration must be updated.", ) protection = await ctxt.repository.get_branch_protection( ctxt.pull["base"]["ref"]) if (protection and "required_status_checks" in protection and "strict" in protection["required_status_checks"] and protection["required_status_checks"]["strict"]): if self.queue_rule.config["batch_size"] > 1: return check_api.Result( check_api.Conclusion.FAILURE, "batch_size > 1 is not compatible with branch protection setting", "The branch protection setting `Require branches to be up to date before merging` must be unset.", ) elif self.queue_rule.config["speculative_checks"] > 1: return check_api.Result( check_api.Conclusion.FAILURE, "speculative_checks > 1 is not compatible with branch protection setting", "The branch protection setting `Require branches to be up to date before merging` must be unset.", ) # FIXME(sileht): we should use the computed update_bot_account in TrainCar.update_pull(), # not the original one try: await action_utils.render_bot_account( ctxt, self.config["update_bot_account"], option_name="update_bot_account", required_feature=subscription.Features.MERGE_BOT_ACCOUNT, missing_feature_message= "Queue with `update_bot_account` set is unavailable", ) except action_utils.RenderBotAccountFailure as e: return check_api.Result(e.status, e.title, e.reason) try: merge_bot_account = await action_utils.render_bot_account( ctxt, self.config["merge_bot_account"], option_name="merge_bot_account", required_feature=subscription.Features.MERGE_BOT_ACCOUNT, missing_feature_message= "Queue with `merge_bot_account` set is unavailable", # NOTE(sileht): we don't allow admin, because if branch protection are # enabled, but not enforced on admins, we may bypass them required_permissions=["write", "maintain"], ) except action_utils.RenderBotAccountFailure as e: return check_api.Result(e.status, e.title, e.reason) q = await merge_train.Train.from_context(ctxt) car = q.get_car(ctxt) await self._update_merge_queue_summary(ctxt, rule, q, car) 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_queued 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, check_api.Conclusion.NEUTRAL, ]: 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", "", ), ) self._set_effective_priority(ctxt) result = await self.merge_report(ctxt) if result is None: if await self._should_be_queued(ctxt, q): await q.add_pull( ctxt, typing.cast(queue.PullQueueConfig, self.config)) try: qf = await freeze.QueueFreeze.get(ctxt.repository, self.config["name"]) if await self._should_be_merged(ctxt, q, qf): result = await self._merge(ctxt, rule, q, merge_bot_account) else: result = await self.get_queue_status(ctxt, rule, q, qf) except Exception: await q.remove_pull(ctxt) raise else: result = await self.get_unqueue_status(ctxt, q) if result.conclusion is not check_api.Conclusion.PENDING: await q.remove_pull(ctxt) # NOTE(sileht): Only refresh if the car still exists and is the same as # before we run the action new_car = q.get_car(ctxt) if (car and car.queue_pull_request_number is not None and new_car and new_car.creation_state == "created" and new_car.queue_pull_request_number is not None and new_car.queue_pull_request_number == car.queue_pull_request_number and self.need_draft_pull_request_refresh() and not ctxt.has_been_only_refreshed()): # NOTE(sileht): It's not only refreshed, so we need to # update the associated transient pull request. # This is mandatory to filter out refresh to avoid loop # of refreshes between this PR and the transient one. await utils.send_pull_refresh( ctxt.repository.installation.redis.stream, ctxt.pull["base"]["repo"], pull_request_number=new_car.queue_pull_request_number, action="internal", source="forward from queue action (run)", ) return result