def job_refresh(owner, repo, kind, ref=None, action="user"): LOG.info("job refresh", kind=kind, ref=ref, gh_owner=owner, gh_repo=repo) try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: return with github.get_client(owner, repo, installation) as client: if kind == "repo": pulls = list(client.items("pulls")) elif kind == "branch": pulls = list(client.items("pulls", base=ref)) elif kind == "pull": pulls = [client.item(f"pulls/{ref}")] else: raise RuntimeError("Invalid kind") for p in pulls: # Mimic the github event format data = { "action": action, "repository": p["base"]["repo"], "installation": { "id": client.installation["id"] }, "pull_request": p, "sender": { "login": "******" }, } github_events.job_filter_and_dispatch.s("refresh", str(uuid.uuid4()), data).apply_async()
async def _refresh(owner, repo, action="user", **extra_data): event_type = "refresh" try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: return responses.Response("Mergify not installed", status_code=404) data = { "installation": installation, "action": action, "repository": { "name": repo, "owner": { "login": owner }, "full_name": f"{owner}/{repo}", }, "sender": { "login": "******" }, } data.update(extra_data) await github_events.job_filter_and_dispatch(event_type, str(uuid.uuid4()), data) return responses.Response("Refresh queued", status_code=202)
def process(self): pull_numbers = self.get_pulls() self.log.info("%d pulls queued", len(pull_numbers), queue=list(pull_numbers)) if not pull_numbers: return pull_number = int(pull_numbers[0]) try: installation = github.get_installation( self.owner, self.repo, self.installation_id ) except exceptions.MergifyNotInstalled: self.delete_queue() return subscription = sub_utils.get_subscription( utils.get_redis_for_cache(), self.installation_id, ) with github.get_client(self.owner, self.repo, installation) as client: data = client.item(f"pulls/{pull_number}") try: ctxt = context.Context(client, data, subscription) except exceptions.RateLimited as e: self.log.debug("rate limited", remaining_seconds=e.countdown) return except exceptions.MergeableStateUnknown as e: # pragma: no cover e.ctxt.log.warning( "pull request with mergeable_state unknown retrying later", ) self._move_pull_at_end(pull_number) return try: if ctxt.pull["base"]["ref"] != self.ref: ctxt.log.info( "pull request base branch have changed", old_branch=self.ref, new_branch=ctxt.pull["base"]["ref"], ) self._move_pull_to_new_base_branch(ctxt, self.ref) elif ctxt.pull["state"] == "closed" or ctxt.is_behind: # NOTE(sileht): Pick up this pull request and rebase it again # or update its status and remove it from the queue ctxt.log.info( "pull request needs to be updated again or has been closed", ) self.handle_first_pull_in_queue(ctxt) else: # NOTE(sileht): Pull request has not been merged or cancelled # yet wait next loop ctxt.log.info("pull request checks are still in progress") except Exception: # pragma: no cover ctxt.log.error("Fail to process merge queue", exc_info=True) self._move_pull_at_end(pull_number)
def process_queue(queue): queue_log = get_queue_logger(queue) _, installation_id, owner, repo, queue_base_branch = queue.split("~") pull_numbers = _get_pulls(queue) queue_log.debug("%d pulls queued", len(pull_numbers), queue=list(pull_numbers)) if not pull_numbers: queue_log.debug("no pull request for this queue") return pull_number = int(pull_numbers[0]) try: installation = github.get_installation(owner, repo, int(installation_id)) except exceptions.MergifyNotInstalled: _delete_queue(queue) return with github.get_client(owner, repo, installation) as client: data = client.item(f"pulls/{pull_number}") try: ctxt = mergify_context.MergifyContext(client, data) except exceptions.RateLimited as e: log = ctxt.log if ctxt else queue_log log.info("rate limited", remaining_seconds=e.countdown) return except exceptions.MergeableStateUnknown as e: # pragma: no cover e.ctxt.log.warning( "pull request with mergeable_state unknown retrying later", ) _move_pull_at_end(e.ctxt) return try: if ctxt.pull["base"]["ref"] != queue_base_branch: ctxt.log.debug( "pull request base branch have changed", old_branch=queue_base_branch, new_branch=ctxt.pull["base"]["ref"], ) _move_pull_to_new_base_branch(ctxt, queue_base_branch) elif ctxt.pull["state"] == "closed" or ctxt.is_behind: # NOTE(sileht): Pick up this pull request and rebase it again # or update its status and remove it from the queue ctxt.log.debug( "pull request needs to be updated again or has been closed", ) _handle_first_pull_in_queue(queue, ctxt) else: # NOTE(sileht): Pull request has not been merged or cancelled # yet wait next loop ctxt.log.debug("pull request checks are still in progress") except Exception: # pragma: no cover ctxt.log.error("Fail to process merge queue", exc_info=True) _move_pull_at_end(ctxt)
def run(event_type, data): """Everything starts here.""" installation_id = data["installation"]["id"] owner = data["repository"]["owner"]["login"] repo = data["repository"]["name"] try: installation = github.get_installation(owner, repo, installation_id) except exceptions.MergifyNotInstalled: return with github.get_client(owner, repo, installation) as client: _run(client, event_type, data)
def test_github_client(self): rules = { "pull_request_rules": [{ "name": "simulator", "conditions": [f"base!={self.master_branch_name}"], "actions": { "merge": {} }, }] } other_branch = self.get_full_branch_name("other") self.setup_repo(yaml.dump(rules), test_branches=[other_branch]) p1, _ = self.create_pr() p2, _ = self.create_pr() self.create_pr(base=other_branch) installation = github.get_installation(self.o_integration.login, self.r_o_integration.name) client = github.get_client(self.o_integration.login, self.r_o_integration.name, installation) pulls = list(client.items("pulls")) self.assertEqual(2, len(pulls)) pulls = list(client.items("pulls", per_page=1)) self.assertEqual(2, len(pulls)) pulls = list(client.items("pulls", per_page=1, page=2)) self.assertEqual(1, len(pulls)) pulls = list(client.items("pulls", base=other_branch, state="all")) self.assertEqual(1, len(pulls)) pulls = list(client.items("pulls", base="unknown")) self.assertEqual(0, len(pulls)) pull = client.item(f"pulls/{p1.number}") self.assertEqual(p1.number, pull["number"]) pull = client.item(f"pulls/{p2.number}") self.assertEqual(p2.number, pull["number"]) with self.assertRaises(httpx.HTTPError) as ctxt: client.item("pulls/10000000000") self.assertEqual(404, ctxt.exception.response.status_code)
def run_command_async(installation_id, pull_request_raw, sources, comment, user, rerun=False): owner = pull_request_raw["base"]["user"]["login"] repo = pull_request_raw["base"]["repo"]["name"] try: installation = github.get_installation(owner, repo, installation_id) except exceptions.MergifyNotInstalled: return with github.get_client(owner, repo, installation) as client: pull = mergify_context.MergifyContext(client, pull_request_raw) return run_command(pull, sources, comment, user, rerun)
def PullRequestUrl(v): _, owner, repo, _, pull_number = urlsplit(v).path.split("/") pull_number = int(pull_number) try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: raise PullRequestUrlInvalid( message="Mergify not installed on repository '%s'" % owner) with github.get_client(owner, repo, installation) as client: try: data = client.item(f"pulls/{pull_number}") except httpx.HTTPNotFound: raise PullRequestUrlInvalid( message=("Pull request '%s' not found" % v)) return mergify_context.MergifyContext(client, data)
def test_client_401_raise_ratelimit(httpserver): owner = "owner" repo = "repo" httpserver.expect_request("/repos/owner/repo/installation").respond_with_json( { "id": 12345, "target_type": "User", "permissions": { "checks": "write", "contents": "write", "pull_requests": "write", }, "account": {"login": "******"}, } ) httpserver.expect_request( "/app/installations/12345/access_tokens" ).respond_with_json({"token": "<token>", "expires_at": "2100-12-31T23:59:59Z"}) httpserver.expect_oneshot_request("/rate_limit").respond_with_json( {"resources": {"core": {"remaining": 5000, "reset": 1234567890}}} ) httpserver.expect_oneshot_request("/repos/owner/repo/pull/1").respond_with_json( {"message": "quota !"}, status=403 ) httpserver.expect_oneshot_request("/rate_limit").respond_with_json( {"resources": {"core": {"remaining": 0, "reset": 1234567890}}} ) with mock.patch( "mergify_engine.config.GITHUB_API_URL", httpserver.url_for("/"), ): installation = github.get_installation(owner, repo, 12345) client = github.get_client(owner, repo, installation) with pytest.raises(exceptions.RateLimited): client.item("pull/1")
def report(url): redis = utils.get_redis_for_cache() path = url.replace("https://github.com/", "") try: owner, repo, _, pull_number = path.split("/") except ValueError: print(f"Wrong URL: {url}") return slug = owner + "/" + repo try: installation = github.get_installation(owner, repo) except exceptions.MergifyNotInstalled: print("* Mergify is not installed there") return client = github.get_client(owner, repo, installation) print("* INSTALLATION ID: %s" % client.installation["id"]) cached_sub = sub_utils.get_subscription(redis, client.installation["id"]) db_sub = sub_utils._retrieve_subscription_from_db( client.installation["id"]) print("* SUBSCRIBED (cache/db): %s / %s" % (cached_sub["subscription_active"], db_sub["subscription_active"])) report_sub(client.installation["id"], slug, cached_sub, "ENGINE-CACHE") report_sub(client.installation["id"], slug, db_sub, "DASHBOARD") pull_raw = client.item(f"pulls/{pull_number}") ctxt = mergify_context.MergifyContext(client, pull_raw) print("* REPOSITORY IS %s" % "PRIVATE" if ctxt.pull["base"]["repo"]["private"] else "PUBLIC") print("* CONFIGURATION:") try: mergify_config_content = rules.get_mergify_config_content(ctxt) except rules.NoRules: # pragma: no cover print(".mergify.yml is missing") pull_request_rules = None else: print(mergify_config_content.decode()) try: mergify_config = rules.UserConfigurationSchema( mergify_config_content) except rules.InvalidRules as e: # pragma: no cover print("configuration is invalid %s" % str(e)) else: pull_request_rules_raw = mergify_config[ "pull_request_rules"].as_dict() pull_request_rules_raw["rules"].extend( engine.MERGIFY_RULE["rules"]) pull_request_rules = rules.PullRequestRules( **pull_request_rules_raw) print("* PULL REQUEST:") pprint.pprint(ctxt.to_dict(), width=160) print("is_behind: %s" % ctxt.is_behind) print("mergeable_state: %s" % ctxt.pull["mergeable_state"]) print("* MERGIFY LAST CHECKS:") checks = list(check_api.get_checks(ctxt, mergify_only=True)) for c in checks: print("[%s]: %s | %s" % (c["name"], c["conclusion"], c["output"].get("title"))) print("> " + "\n> ".join(c["output"].get("summary").split("\n"))) if pull_request_rules is not None: print("* MERGIFY LIVE MATCHES:") match = pull_request_rules.get_pull_request_rule(ctxt) summary_title, summary = actions_runner.gen_summary( ctxt, [{ "event_type": "refresh", "data": {} }], match) print("> %s" % summary_title) print(summary) return ctxt