Example #1
0
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()
Example #2
0
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)
Example #3
0
    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)
Example #4
0
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)
Example #5
0
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)
Example #6
0
    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)
Example #7
0
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)
Example #8
0
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)
Example #9
0
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")
Example #10
0
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