def run(self, ctxt, rule, missing_conditions): if not config.GITHUB_APP: if self.config["strict_method"] == "rebase": return ( "failure", "Misconfigured for GitHub Action", "Due to GitHub Action limitation, `strict_method: rebase` " "is only available with the Mergify GitHub App", ) ctxt.log.info("process merge", config=self.config) q = queue.Queue.from_context(ctxt) output = helpers.merge_report(ctxt, self.config["strict"]) if output: q.remove_pull(ctxt.pull["number"]) return output if self.config["strict"] in ("smart+fasttrack", "smart+ordered"): q.add_pull(ctxt, self.config) if self._should_be_merged(ctxt): try: conclusion, title, summary = self._merge(ctxt) if conclusion is not None: q.remove_pull(ctxt.pull["number"]) return conclusion, title, summary except Exception: q.remove_pull(ctxt.pull["number"]) raise else: return self._sync_with_base_branch(ctxt)
def cancel(self, ctxt, rule, missing_conditions) -> check_api.Result: self._set_effective_priority(ctxt) q = queue.Queue.from_context(ctxt) if ctxt.pull["state"] == "closed": output = helpers.merge_report(ctxt, self.config["strict"]) if output: q.remove_pull(ctxt.pull["number"]) return output # We just rebase the pull request, don't cancel it yet if CIs are # running. The pull request will be merge if all rules match again. # if not we will delete it when we received all CIs termination if self.config["strict"] and self._required_statuses_in_progress( ctxt, missing_conditions): if self._should_be_merged(ctxt, q): # Just wait for CIs to finish return helpers.get_strict_status(ctxt, rule, missing_conditions, need_update=ctxt.is_behind) else: # Something got merged in the base branch in the meantime: rebase it again return self._sync_with_base_branch(ctxt, rule, missing_conditions, q) q.remove_pull(ctxt.pull["number"]) return self.cancelled_check_report
def _merge(self, pull, installation_id): if self.config["method"] != "rebase" or pull.g_pull.raw_data[ "rebaseable"]: method = self.config["method"] elif self.config["rebase_fallback"]: method = self.config["rebase_fallback"] else: return ( "action_required", "Automatic rebasing is not possible, manual intervention required", "", ) kwargs = pull.get_merge_commit_message() or {} try: pull.g_pull.merge(sha=pull.g_pull.head.sha, merge_method=method, **kwargs) except github.GithubException as e: # pragma: no cover if pull.g_pull.is_merged(): pull.log.info("merged in the meantime") else: return self._handle_merge_error(e, pull, installation_id) else: pull.log.info("merged") pull.g_pull.update() return helpers.merge_report(pull, self.config["strict"])
def _merge(pull, method): kwargs = pull.get_merge_commit_message() or {} try: pull.g_pull.merge(sha=pull.g_pull.head.sha, merge_method=method, **kwargs) except github.GithubException as e: # pragma: no cover if pull.g_pull.is_merged(): LOG.info("merged in the meantime", pull=pull) else: if e.status != 405: message = "Mergify fails to merge the pull request" elif pull.g_pull.mergeable_state == "blocked": message = ("Branch protection settings are blocking " "automatic merging\nSee: %s" % BRANCH_PROTECTION_FAQ_URL) else: message = ("Repository settings are blocking automatic " "merging") log_method = LOG.error if e.status >= 500 else LOG.info log_method("merge fail", status=e.status, mergify_message=message, error_message=e.data["message"], pull=pull) return ("failure", message, "GitHub error message: `%s`" % e.data["message"]) else: LOG.info("merged", pull=pull) pull.g_pull.update() return helpers.merge_report(pull)
def _merge(self, ctxt): if self.config["method"] != "rebase" or ctxt.pull["rebaseable"]: method = self.config["method"] elif self.config["rebase_fallback"]: method = self.config["rebase_fallback"] else: return ( "action_required", "Automatic rebasing is not possible, manual intervention required", "", ) data = ctxt.get_merge_commit_message() or {} data["sha"] = ctxt.pull["head"]["sha"] data["merge_method"] = method try: ctxt.client.put(f"pulls/{ctxt.pull['number']}/merge", json=data) except httpx.HTTPClientSideError as e: # pragma: no cover ctxt.update() if ctxt.pull["merged"]: ctxt.log.info("merged in the meantime") else: return self._handle_merge_error(e, ctxt) else: ctxt.update() ctxt.log.info("merged") return helpers.merge_report(ctxt, self.config["strict"])
def _merge(pull, method): try: pull.g_pull.merge(sha=pull.g_pull.head.sha, merge_method=method) except github.GithubException as e: # pragma: no cover if pull.g_pull.is_merged(): LOG.info("merged in the meantime", pull=pull) elif e.status == 405: LOG.error("merge fail", error=e.data["message"], pull=pull) if pull.g_pull.mergeable_state == "blocked": return ("failure", "Branch protection settings are " "blocking automatic merging", e.data["message"]) else: return ("failure", "Repository settings are blocking automatic " "merging", e.data["message"]) elif 400 <= e.status < 500: LOG.error("merge fail", error=e.data["message"], pull=pull) return ("failure", "Mergify fails to merge the pull request", e.data["message"]) else: raise else: LOG.info("merged", pull=pull) pull.g_pull.update() return helpers.merge_report(pull)
def _handle_first_pull_in_queue(queue, pull): _, installation_id, owner, reponame, branch = queue.split("~") old_checks = [c for c in check_api.get_checks(pull.g_pull) if (c.name.endswith(" (merge)") and c._rawData['app']['id'] == config.INTEGRATION_ID)] merge_output = helpers.merge_report(pull) mergeable_state_output = helpers.output_for_mergeable_state(pull, True) if merge_output or mergeable_state_output: remove_pull(pull) conclusion, title, summary = merge_output or mergeable_state_output else: LOG.debug("updating base branch of pull request", pull=pull) redis = utils.get_redis_for_cache() method = redis.get(_get_update_method_cache_key(pull)) or "merge" conclusion, title, summary = helpers.update_pull_base_branch( pull, installation_id, method) if pull.g_pull.state == "closed": remove_pull(pull) elif conclusion == "failure": _move_pull_at_end(pull) status = "completed" if conclusion else "in_progress" for c in old_checks: check_api.set_check_run( pull.g_pull, c.name, status, conclusion, output={"title": title, "summary": summary})
def run( self, installation_id, installation_token, event_type, data, pull, missing_conditions, ): pull.log.debug("process merge", config=self.config) output = helpers.merge_report(pull, self.config["strict"]) if output: if self.config["strict"] == "smart": queue.remove_pull(pull) return output if self.config["strict"] and pull.is_behind(): return self._sync_with_base_branch(pull, installation_id) else: try: return self._merge(pull, installation_id) finally: if self.config["strict"] == "smart": queue.remove_pull(pull)
def run(self, ctxt, rule, missing_conditions) -> check_api.Result: if not config.GITHUB_APP: if self.config["strict_method"] == "rebase": return check_api.Result( check_api.Conclusion.FAILURE, "Misconfigured for GitHub Action", "Due to GitHub Action limitation, `strict_method: rebase` " "is only available with the Mergify GitHub App", ) if self.config[ "update_bot_account"] and not ctxt.subscription.has_feature( subscription.Features.MERGE_BOT_ACCOUNT): return check_api.Result( check_api.Conclusion.ACTION_REQUIRED, "Merge with `update_bot_account` set are disabled", ctxt.subscription.missing_feature_reason( ctxt.pull["base"]["repo"]["owner"]["login"]), ) if self.config[ "merge_bot_account"] and not ctxt.subscription.has_feature( subscription.Features.MERGE_BOT_ACCOUNT): return check_api.Result( check_api.Conclusion.ACTION_REQUIRED, "Merge with `merge_bot_account` set are disabled", ctxt.subscription.missing_feature_reason( ctxt.pull["base"]["repo"]["owner"]["login"]), ) self._set_effective_priority(ctxt) ctxt.log.info("process merge", config=self.config) q = queue.Queue.from_context(ctxt) result = helpers.merge_report(ctxt, self.config["strict"]) if result: q.remove_pull(ctxt.pull["number"]) return result if self.config["strict"] in ("smart+fasttrack", "smart+ordered"): q.add_pull(ctxt, self.config) if self._should_be_merged(ctxt, q): try: result = self._merge(ctxt, rule, missing_conditions, q) if result.conclusion is not check_api.Conclusion.PENDING: q.remove_pull(ctxt.pull["number"]) return result except Exception: q.remove_pull(ctxt.pull["number"]) raise else: return self._sync_with_base_branch(ctxt, rule, missing_conditions, q)
def _handle_first_pull_in_queue(queue, pull): _, installation_id, owner, reponame, branch = queue.split("~") old_checks = [ c for c in check_api.get_checks(pull.g_pull, mergify_only=True) if c.name.endswith(" (merge)") ] output = helpers.merge_report(pull, True) if output: conclusion, title, summary = output LOG.debug( "pull request closed in the meantime", pull=pull, conclusion=conclusion, title=title, summary=summary, ) remove_pull(pull) else: LOG.debug("updating base branch of pull request", pull=pull) redis = utils.get_redis_for_cache() method = redis.get(_get_update_method_cache_key(pull)) or "merge" conclusion, title, summary = helpers.update_pull_base_branch( pull, installation_id, method) if pull.g_pull.state == "closed": LOG.debug( "pull request closed in the meantime", pull=pull, conclusion=conclusion, title=title, summary=summary, ) remove_pull(pull) elif conclusion == "failure": LOG.debug("base branch update failed", pull=pull, title=title, summary=summary) _move_pull_at_end(pull) status = "completed" if conclusion else "in_progress" for c in old_checks: check_api.set_check_run( pull.g_pull, c.name, status, conclusion, output={ "title": title, "summary": summary }, )
def _merge(self, ctxt): if self.config["method"] != "rebase" or ctxt.pull["rebaseable"]: method = self.config["method"] elif self.config["rebase_fallback"]: method = self.config["rebase_fallback"] else: return ( "action_required", "Automatic rebasing is not possible, manual intervention required", "", ) data = {} try: commit_title_and_message = self._get_commit_message( ctxt.pull_request, self.config["commit_message"], ) except context.RenderTemplateFailure as rmf: return ( "action_required", "Invalid commit message", str(rmf), ) if commit_title_and_message is not None: title, message = commit_title_and_message if title: data["commit_title"] = title if message: data["commit_message"] = message data["sha"] = ctxt.pull["head"]["sha"] data["merge_method"] = method try: ctxt.client.put( f"{ctxt.base_url}/pulls/{ctxt.pull['number']}/merge", json=data ) except http.HTTPClientSideError as e: # pragma: no cover ctxt.update() if ctxt.pull["merged"]: ctxt.log.info("merged in the meantime") else: return self._handle_merge_error(e, ctxt) else: ctxt.update() ctxt.log.info("merged") return helpers.merge_report(ctxt, self.config["strict"])
def handle_first_pull_in_queue(self, ctxt): old_checks = [ c for c in ctxt.pull_engine_check_runs if c["name"].endswith(" (merge)") ] output = helpers.merge_report(ctxt, True) if output: conclusion, title, summary = output ctxt.log.info( "pull request closed in the meantime", conclusion=conclusion, title=title, summary=summary, ) self.remove_pull(ctxt.pull["number"]) else: ctxt.log.info("updating base branch of pull request") config = self.get_config(ctxt.pull["number"]) conclusion, title, summary = helpers.update_pull_base_branch( ctxt, config["strict_method"], config["bot_account"], ) if ctxt.pull["state"] == "closed": ctxt.log.info( "pull request closed in the meantime", conclusion=conclusion, title=title, summary=summary, ) self.remove_pull(ctxt.pull["number"]) elif conclusion == "failure": ctxt.log.info("base branch update failed", title=title, summary=summary) self._move_pull_at_end(ctxt.pull["number"]) status = "completed" if conclusion else "in_progress" for c in old_checks: check_api.set_check_run( ctxt, c["name"], status, conclusion, output={ "title": title, "summary": summary }, )
def run(self, pull, sources, missing_conditions): pull.log.debug("process merge", config=self.config) output = helpers.merge_report(pull, self.config["strict"]) if output: if self.config["strict"] == "smart": queue.remove_pull(pull) return output if self.config["strict"] and pull.is_behind: return self._sync_with_base_branch(pull) else: try: return self._merge(pull) finally: if self.config["strict"] == "smart": queue.remove_pull(pull)
def run(self, installation_id, installation_token, event_type, data, pull, missing_conditions): LOG.debug("process merge", config=self.config, pull=pull) output = helpers.merge_report(pull) if output: return output output = helpers.output_for_mergeable_state(pull, self.config["strict"]) if output: return output if self.config["strict"] and pull.is_behind(): # NOTE(sileht): Almost ready, one last rebase/update if not pull.base_is_modifiable(): return ("failure", "Pull request can't be updated with latest " "base branch changes, owner doesn't allow " "modification", "") elif self.config["strict"] == "smart": queue.add_pull(pull, self.config["strict_method"]) return (None, "Base branch will be updated soon", "The pull request base branch will " "be updated soon, and then merged.") else: return helpers.update_pull_base_branch( pull, installation_id, self.config["strict_method"]) else: # NOTE(sileht): Ready to merge! if self.config["strict"] == "smart": queue.remove_pull(pull) if (self.config["method"] != "rebase" or pull.g_pull.raw_data['rebaseable']): return self._merge(pull, self.config["method"]) elif self.config["rebase_fallback"]: return self._merge(pull, self.config["rebase_fallback"]) else: return ("action_required", "Automatic rebasing is not " "possible, manual intervention required", "")
def cancel(self, ctxt, rule, missing_conditions): q = queue.Queue.from_context(ctxt) if ctxt.pull["state"] == "closed": output = helpers.merge_report(ctxt, self.config["strict"]) if output: q.remove_pull(ctxt.pull["number"]) return output # We just rebase the pull request, don't cancel it yet if CIs are # running. The pull request will be merge if all rules match again. # if not we will delete it when we received all CIs termination if self.config["strict"] and self._required_statuses_in_progress( ctxt, missing_conditions ): return helpers.get_strict_status( ctxt, rule, missing_conditions, need_update=ctxt.is_behind ) q.remove_pull(ctxt.pull["number"]) return self.cancelled_check_report
def handle_first_pull_in_queue(self, ctxt): old_checks = [ c for c in ctxt.pull_engine_check_runs if c["name"].endswith(" (merge)") ] result = helpers.merge_report(ctxt, True) if result: ctxt.log.info( "pull request closed in the meantime", result=result, ) self.remove_pull(ctxt.pull["number"]) else: ctxt.log.info("updating base branch of pull request") config = self.get_config(ctxt.pull["number"]) result = helpers.update_pull_base_branch( ctxt, config["strict_method"], config["update_bot_account"] or config["bot_account"], ) if ctxt.pull["state"] == "closed": ctxt.log.info( "pull request closed in the meantime", result=result, ) self.remove_pull(ctxt.pull["number"]) elif result.conclusion == check_api.Conclusion.FAILURE: ctxt.log.info( "base branch update failed", result=result, ) self._move_pull_at_end(ctxt.pull["number"]) for c in old_checks: check_api.set_check_run(ctxt, c["name"], result)
def _merge( self, ctxt: context.Context, rule: rules.Rule, missing_conditions: typing.List[filter.Filter], q: queue.Queue, ) -> check_api.Result: if self.config["method"] != "rebase" or ctxt.pull["rebaseable"]: method = self.config["method"] elif self.config["rebase_fallback"]: method = self.config["rebase_fallback"] else: return check_api.Result( check_api.Conclusion.ACTION_REQUIRED, "Automatic rebasing is not possible, manual intervention required", "", ) data = {} try: commit_title_and_message = self._get_commit_message( ctxt.pull_request, self.config["commit_message"], ) except context.RenderTemplateFailure as rmf: return check_api.Result( check_api.Conclusion.ACTION_REQUIRED, "Invalid commit message", str(rmf), ) if commit_title_and_message is not None: title, message = commit_title_and_message if title: data["commit_title"] = title if message: data["commit_message"] = message data["sha"] = ctxt.pull["head"]["sha"] data["merge_method"] = method bot_account = self.config["merge_bot_account"] if bot_account: oauth_token = ctxt.subscription.get_token_for(bot_account) if not oauth_token: return check_api.Result( check_api.Conclusion.FAILURE, f"Unable to rebase: user `{bot_account}` is unknown. ", f"Please make sure `{bot_account}` has logged in Mergify dashboard.", ) else: oauth_token = None try: ctxt.client.put( f"{ctxt.base_url}/pulls/{ctxt.pull['number']}/merge", oauth_token=oauth_token, # type: ignore json=data, ) except http.HTTPClientSideError as e: # pragma: no cover ctxt.update() if ctxt.pull["merged"]: ctxt.log.info("merged in the meantime") else: return self._handle_merge_error(e, ctxt, rule, missing_conditions, q) else: ctxt.update() ctxt.log.info("merged") result = helpers.merge_report(ctxt, self.config["strict"]) if result: return result else: return check_api.Result( check_api.Conclusion.FAILURE, "Unexpected after merge pull request state", "The pull request have been merged, but GitHub API still report it open", )