예제 #1
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)

        client = github.get_client(self.o_integration.login)

        url = f"/repos/{self.o_integration.login}/{self.r_o_integration.name}/pulls"

        pulls = list(client.items(url))
        self.assertEqual(3, len(pulls))

        pulls = list(client.items(url, per_page=1))
        self.assertEqual(3, len(pulls))

        pulls = list(client.items(url, per_page=1, page=2))
        self.assertEqual(2, len(pulls))

        pulls = list(client.items(url, base=other_branch, state="all"))
        self.assertEqual(1, len(pulls))

        pulls = list(client.items(url, base="unknown"))
        self.assertEqual(0, len(pulls))

        pull = client.item(f"{url}/{p1.number}")
        self.assertEqual(p1.number, pull["number"])

        pull = client.item(f"{url}/{p2.number}")
        self.assertEqual(p2.number, pull["number"])

        with self.assertRaises(http.HTTPStatusError) as ctxt:
            client.item(f"{url}/10000000000")

        self.assertEqual(404, ctxt.exception.response.status_code)
예제 #2
0
    def test_github_client(self):
        rules = {
            "pull_request_rules": [{
                "name": "simulator",
                "conditions": ["base!=master"],
                "actions": {
                    "merge": {}
                },
            }]
        }
        self.setup_repo(yaml.dump(rules), test_branches=["other"])
        self.create_pr()
        self.create_pr()
        self.create_pr(base="other")

        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", state="all"))
        self.assertEqual(1, len(pulls))

        pulls = list(client.items("pulls", base="unknown"))
        self.assertEqual(0, len(pulls))

        pull = client.item("pulls/1")
        self.assertEqual(1, pull["number"])

        pull = client.item("pulls/2")
        self.assertEqual(2, pull["number"])

        with self.assertRaises(httpx.HTTPError) as ctxt:
            client.item("pulls/4")

        self.assertEqual(404, ctxt.exception.response.status_code)
예제 #3
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)
예제 #4
0
파일: worker.py 프로젝트: jd/mergify-engine
def run_engine(
    owner: str,
    repo: str,
    pull_number: int,
    sources: typing.List[context.T_PayloadEventSource],
) -> None:
    logger = daiquiri.getLogger(__name__,
                                gh_repo=repo,
                                gh_owner=owner,
                                gh_pull=pull_number)
    logger.debug("engine in thread start")
    try:
        result = asyncio.run(
            get_pull_for_engine(owner, repo, pull_number, logger))
        if result:
            subscription, pull = result
            with github.get_client(owner) as client:
                engine.run(client, pull, subscription, sources)
    finally:
        logger.debug("engine in thread end")
예제 #5
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:
        client = github.get_client(owner, repo)
    except httpx.HTTPNotFound as e:
        LOG.warning(
            "mergify not installed",
            kind=kind,
            ref=ref,
            gh_owner=owner,
            gh_repo=repo,
            error=str(e),
        )
        return

    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()
예제 #6
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": "******", "id": 12345},
        }
    )
    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("/")[:-1],
    ):
        client = github.get_client(owner, repo)
        with pytest.raises(exceptions.RateLimited):
            client.item("pull/1")

    httpserver.check_assertions()
예제 #7
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 = pull_numbers[0]

        with github.get_client(self.owner) as client:
            ctxt = None
            try:
                sub = asyncio.run(
                    subscription.Subscription.get_subscription(client.auth.owner_id)
                )
                data = client.item(
                    f"/repos/{self.owner}/{self.repo}/pulls/{pull_number}"
                )

                ctxt = context.Context(client, data, sub)
                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.pull["number"],
                        self.get_queue(ctxt.pull["base"]["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 as exc:  # pragma: no cover
                log = self.log if ctxt is None else ctxt.log

                if exceptions.should_be_ignored(exc):
                    log.info(
                        "Fail to process merge queue, remove the pull request from the queue",
                        exc_info=True,
                    )
                    self.remove_pull(ctxt.pull["number"])

                elif exceptions.need_retry(exc):
                    log.info("Fail to process merge queue, need retry", exc_info=True)
                    if isinstance(exc, exceptions.MergeableStateUnknown):
                        # NOTE(sileht): We need GitHub to recompute the state here (by
                        # merging something else for example), so move it to the end
                        self._move_pull_at_end(pull_number)

                else:
                    log.error("Fail to process merge queue", exc_info=True)
                    self._move_pull_at_end(pull_number)
예제 #8
0
    def setUp(self):
        super(FunctionalTestBase, self).setUp()
        self.existing_labels = []
        self.pr_counter = 0
        self.git_counter = 0
        self.cassette_library_dir = os.path.join(CASSETTE_LIBRARY_DIR_BASE,
                                                 self.__class__.__name__,
                                                 self._testMethodName)

        # Recording stuffs
        if RECORD:
            if os.path.exists(self.cassette_library_dir):
                shutil.rmtree(self.cassette_library_dir)
            os.makedirs(self.cassette_library_dir)

        self.recorder = vcr.VCR(
            cassette_library_dir=self.cassette_library_dir,
            record_mode="all" if RECORD else "none",
            match_on=["method", "uri"],
            filter_headers=[
                ("Authorization", "<TOKEN>"),
                ("X-Hub-Signature", "<SIGNATURE>"),
                ("User-Agent", None),
                ("Accept-Encoding", None),
                ("Connection", None),
            ],
            before_record_response=self.response_filter,
            custom_patches=((pygithub.MainClass, "HTTPSConnection",
                             vcr.stubs.VCRHTTPSConnection), ),
        )

        if RECORD:
            github.CachedToken.STORAGE = {}
        else:
            # Never expire token during replay
            mock.patch.object(github_app,
                              "get_or_create_jwt",
                              return_value="<TOKEN>").start()
            mock.patch.object(
                github.GithubAppInstallationAuth,
                "get_access_token",
                return_value="<TOKEN>",
            ).start()

            # NOTE(sileht): httpx pyvcr stubs does not replay auth_flow as it directly patch client.send()
            # So anything occurring during auth_flow have to be mocked during replay
            def get_auth(owner=None, auth=None):
                if auth is None:
                    auth = github.get_auth(owner)
                    auth.installation = {
                        "id": config.INSTALLATION_ID,
                    }
                    auth.permissions_need_to_be_updated = False
                    auth.owner_id = config.TESTING_ORGANIZATION_ID
                return auth

            async def github_aclient(owner=None, auth=None):
                return github.AsyncGithubInstallationClient(
                    get_auth(owner, auth))

            def github_client(owner=None, auth=None):
                return github.GithubInstallationClient(get_auth(owner, auth))

            mock.patch.object(github, "get_client", github_client).start()
            mock.patch.object(github, "aget_client", github_aclient).start()

        with open(engine.mergify_rule_path, "r") as f:
            engine.MERGIFY_RULE = yaml.safe_load(f.read().replace(
                "mergify[bot]", "mergify-test[bot]"))

        mock.patch.object(branch_updater.utils, "Gitter",
                          self.get_gitter).start()
        mock.patch.object(duplicate_pull.utils, "Gitter",
                          self.get_gitter).start()

        if not RECORD:
            # NOTE(sileht): Don't wait exponentialy during replay
            mock.patch.object(context.Context._ensure_complete.retry, "wait",
                              None).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        branch_prefix_path = os.path.join(self.cassette_library_dir,
                                          "branch_prefix")

        if RECORD:
            self.BRANCH_PREFIX = datetime.datetime.utcnow().strftime(
                "%Y%m%d%H%M%S")
            with open(branch_prefix_path, "w") as f:
                f.write(self.BRANCH_PREFIX)
        else:
            with open(branch_prefix_path, "r") as f:
                self.BRANCH_PREFIX = f.read()

        self.master_branch_name = self.get_full_branch_name("master")

        self.git = self.get_gitter(LOG)
        self.addCleanup(self.git.cleanup)

        self.loop = asyncio.get_event_loop()
        self.loop.run_until_complete(web.startup())
        self.app = testclient.TestClient(web.app)

        # NOTE(sileht): Prepare a fresh redis
        self.redis_stream = redis.StrictRedis.from_url(config.STREAM_URL,
                                                       decode_responses=False)
        self.redis_stream.flushall()
        self.redis_cache = utils.get_redis_for_cache()
        self.redis_cache.flushall()
        self.subscription = subscription.Subscription(
            config.INSTALLATION_ID,
            self.SUBSCRIPTION_ACTIVE,
            "You're not nice",
            {
                "mergify-test1": config.ORG_ADMIN_GITHUB_APP_OAUTH_TOKEN,
                "mergify-test3": config.ORG_USER_PERSONAL_TOKEN,
            },
            frozenset(
                getattr(subscription.Features, f)
                for f in subscription.Features.__members__)
            if self.SUBSCRIPTION_ACTIVE else frozenset(),
        )
        self.loop.run_until_complete(
            self.subscription.save_subscription_to_cache())

        # Let's start recording
        cassette = self.recorder.use_cassette("http.json")
        cassette.__enter__()
        self.addCleanup(cassette.__exit__)

        integration = pygithub.GithubIntegration(config.INTEGRATION_ID,
                                                 config.PRIVATE_KEY)
        self.installation_token = integration.get_access_token(
            config.INSTALLATION_ID).token

        base_url = config.GITHUB_API_URL
        self.g_integration = pygithub.Github(self.installation_token,
                                             base_url=base_url)
        self.g_admin = pygithub.Github(config.ORG_ADMIN_PERSONAL_TOKEN,
                                       base_url=base_url)
        self.g_fork = pygithub.Github(self.FORK_PERSONAL_TOKEN,
                                      base_url=base_url)

        self.o_admin = self.g_admin.get_organization(
            config.TESTING_ORGANIZATION)
        self.o_integration = self.g_integration.get_organization(
            config.TESTING_ORGANIZATION)
        self.u_fork = self.g_fork.get_user()
        assert self.o_admin.login == "mergifyio-testing"
        assert self.o_integration.login == "mergifyio-testing"
        assert self.u_fork.login in ["mergify-test2", "mergify-test3"]

        self.r_o_admin = self.o_admin.get_repo(self.REPO_NAME)
        self.r_o_integration = self.o_integration.get_repo(self.REPO_NAME)
        self.r_fork = self.u_fork.get_repo(self.REPO_NAME)

        self.url_main = f"{config.GITHUB_URL}/{self.r_o_integration.full_name}"
        self.url_fork = (
            f"{config.GITHUB_URL}/{self.u_fork.login}/{self.r_o_integration.name}"
        )

        self.cli_integration = github.get_client(config.TESTING_ORGANIZATION, )

        real_get_subscription = subscription.Subscription.get_subscription

        async def fake_retrieve_subscription_from_db(owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return self.subscription
            return subscription.Subscription(
                owner_id,
                False,
                "We're just testing",
                {},
                set(),
            )

        async def fake_subscription(owner_id):
            if owner_id == config.TESTING_ORGANIZATION_ID:
                return await real_get_subscription(owner_id)
            return subscription.Subscription(
                owner_id,
                False,
                "We're just testing",
                {},
                set(),
            )

        mock.patch(
            "mergify_engine.subscription.Subscription._retrieve_subscription_from_db",
            side_effect=fake_retrieve_subscription_from_db,
        ).start()

        mock.patch(
            "mergify_engine.subscription.Subscription.get_subscription",
            side_effect=fake_subscription,
        ).start()

        mock.patch(
            "github.MainClass.Installation.Installation.get_repos",
            return_value=[self.r_o_integration],
        ).start()

        self._event_reader = EventReader(self.app)
        self._event_reader.drain()
        self.redis_stream.flushall()
예제 #9
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
예제 #10
0
def report(
    url: str,
) -> typing.Union[context.Context, github.GithubInstallationClient, None]:
    path = url.replace("https://github.com/", "")

    pull_number: typing.Optional[str]
    repo: typing.Optional[str]

    try:
        owner, repo, _, pull_number = path.split("/")
    except ValueError:
        pull_number = None
        try:
            owner, repo = path.split("/")
        except ValueError:
            owner = path
            repo = None

    try:
        client = github.get_client(owner)
    except exceptions.MergifyNotInstalled:
        print(f"* Mergify is not installed on account {owner}")
        return None

    print("* INSTALLATION ID: %s" % client.auth.installation["id"])

    cached_sub, db_sub = utils.async_run(
        subscription.Subscription.get_subscription(client.auth.owner_id),
        subscription.Subscription._retrieve_subscription_from_db(
            client.auth.owner_id),
    )

    if repo is None:
        slug = None
    else:
        slug = owner + "/" + repo

    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub.active, db_sub.active))
    print("* Features (cache):")
    for f in cached_sub.features:
        print(f"  - {f.value}")
    report_sub(client.auth.installation["id"], cached_sub, "ENGINE-CACHE",
               slug)
    report_sub(client.auth.installation["id"], db_sub, "DASHBOARD", slug)

    utils.async_run(report_worker_status(client.auth.owner))

    if repo is not None:

        repo_info = client.item(f"/repos/{owner}/{repo}")
        print(
            f"* REPOSITORY IS {'PRIVATE' if repo_info['private'] else 'PUBLIC'}"
        )

        print("* CONFIGURATION:")
        mergify_config = None
        try:
            filename, mergify_config_content = rules.get_mergify_config_content(
                client, repo)
        except rules.NoRules:  # pragma: no cover
            print(".mergify.yml is missing")
        else:
            print(f"Config filename: {filename}")
            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:
                mergify_config["pull_request_rules"].rules.extend(
                    engine.DEFAULT_PULL_REQUEST_RULES.rules)

        if pull_number is None:
            for branch in client.items(f"/repos/{owner}/{repo}/branches"):
                q = queue.Queue(
                    utils.get_redis_for_cache(),
                    client.auth.installation["id"],
                    client.auth.owner,
                    repo,
                    branch["name"],
                )
                pulls = q.get_pulls()
                if not pulls:
                    continue

                print(f"* QUEUES {branch['name']}:")

                for priority, grouped_pulls in itertools.groupby(
                        pulls, key=lambda v: q.get_config(v)["priority"]):
                    try:
                        fancy_priority = helpers.PriorityAliases(priority).name
                    except ValueError:
                        fancy_priority = priority
                    formatted_pulls = ", ".join(
                        (f"#{p}" for p in grouped_pulls))
                    print(f"** {formatted_pulls} (priority: {fancy_priority})")
        else:
            pull_raw = client.item(
                f"/repos/{owner}/{repo}/pulls/{pull_number}")
            ctxt = context.Context(
                client,
                pull_raw,
                cached_sub,
                [{
                    "event_type": "mergify-debugger",
                    "data": {}
                }],
            )

            # FIXME queues could also be printed if no pull number given
            q = queue.Queue.from_context(ctxt)
            print("* QUEUES: %s" % ", ".join([f"#{p}" for p in q.get_pulls()]))
            print("* PULL REQUEST:")
            pr_data = dict(ctxt.pull_request.items())
            pprint.pprint(pr_data, width=160)

            print("is_behind: %s" % ctxt.is_behind)

            print("mergeable_state: %s" % ctxt.pull["mergeable_state"])

            print("* MERGIFY LAST CHECKS:")
            for c in ctxt.pull_engine_check_runs:
                print("[%s]: %s | %s" %
                      (c["name"], c["conclusion"], c["output"].get("title")))
                print("> " +
                      "\n> ".join(c["output"].get("summary").split("\n")))

            if mergify_config is not None:
                print("* MERGIFY LIVE MATCHES:")
                match = mergify_config[
                    "pull_request_rules"].get_pull_request_rule(ctxt)
                summary_title, summary = actions_runner.gen_summary(
                    ctxt, match)
                print("> %s" % summary_title)
                print(summary)

            return ctxt

    return client
예제 #11
0
def report(url):
    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:
        client = github.get_client(owner, repo)
    except exceptions.MergifyNotInstalled:
        print("* Mergify is not installed there")
        return

    print("* INSTALLATION ID: %s" % client.auth.installation["id"])

    cached_sub, db_sub = utils.async_run(
        sub_utils.get_subscription(client.auth.owner_id),
        sub_utils._retrieve_subscription_from_db(client.auth.owner_id),
    )
    print("* SUBSCRIBED (cache/db): %s / %s" %
          (cached_sub["subscription_active"], db_sub["subscription_active"]))
    report_sub(client.auth.installation["id"], slug, cached_sub,
               "ENGINE-CACHE")
    report_sub(client.auth.installation["id"], slug, db_sub, "DASHBOARD")

    utils.async_run(report_worker_status(client.auth.owner))

    pull_raw = client.item(f"pulls/{pull_number}")
    ctxt = context.Context(client, pull_raw, cached_sub, [{
        "event_type": "mergify-debugger",
        "data": {}
    }])

    q = queue.Queue.from_context(ctxt)
    print("QUEUES: %s" % ", ".join([f"#{p}" for p in q.get_pulls()]))

    print("* REPOSITORY IS %s" %
          "PRIVATE" if ctxt.pull["base"]["repo"]["private"] else "PUBLIC")

    print("* CONFIGURATION:")
    try:
        filename, 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(f"Config filename: {filename}")
        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 = rules.PullRequestRules.from_list(
                pull_request_rules_raw["rules"] + engine.MERGIFY_RULE["rules"])

    print("* PULL REQUEST:")
    pr_data = dict(ctxt.pull_request.items())
    pprint.pprint(pr_data, width=160)

    print("is_behind: %s" % ctxt.is_behind)

    print("mergeable_state: %s" % ctxt.pull["mergeable_state"])

    print("* MERGIFY LAST CHECKS:")
    for c in ctxt.pull_engine_check_runs:
        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, match)
        print("> %s" % summary_title)
        print(summary)

    return ctxt
예제 #12
0
def run(event_type, data):
    """Everything starts here."""
    installation_id = data["installation"]["id"]
    owner = data["repository"]["owner"]["login"]
    repo = data["repository"]["name"]

    client = github.get_client(owner, repo, installation_id)

    raw_pull = get_github_pull_from_event(client, event_type, data)

    if not raw_pull:  # pragma: no cover
        LOG.info(
            "No pull request found in the event %s, ignoring",
            event_type,
            gh_owner=owner,
            gh_repo=repo,
        )
        return

    pull = mergify_pull.MergifyPull(client, raw_pull)
    # Override pull_request with the updated one
    data["pull_request"] = pull.data

    pull.log.info("Pull request found in the event %s", event_type)

    if ("base" not in pull.data or "repo" not in pull.data["base"]
            or len(list(pull.data["base"]["repo"].keys())) < 70):
        pull.log.warning(
            "the pull request payload looks suspicious",
            event_type=event_type,
            data=data,
        )

    if (event_type == "status"
            and pull.data["head"]["sha"] != data["sha"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got status of an old commit)", )
        return

    elif (event_type in ["status", "check_suite", "check_run"]
          and pull.data["merged"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got status of a merged pull request)", )
        return
    elif (event_type in ["check_suite", "check_run"]
          and pull.data["head"]["sha"] !=
          data[event_type]["head_sha"]):  # pragma: no cover
        pull.log.info(
            "No need to proceed queue (got %s of an old "
            "commit)",
            event_type,
        )
        return

    if check_configuration_changes(pull.g_pull):
        pull.log.info("Configuration changed, ignoring", )
        return

    # BRANCH CONFIGURATION CHECKING
    try:
        mergify_config = rules.get_mergify_config(pull.g_pull.base.repo)
    except rules.NoRules:  # pragma: no cover
        pull.log.info("No need to proceed queue (.mergify.yml is missing)", )
        return
    except rules.InvalidRules as e:  # pragma: no cover
        # Not configured, post status check with the error message
        if event_type == "pull_request" and data["action"] in [
                "opened", "synchronize"
        ]:
            check_api.set_check_run(
                pull.g_pull,
                "Summary",
                "completed",
                "failure",
                output={
                    "title": "The Mergify configuration is invalid",
                    "summary": str(e),
                },
            )
        return

    # Add global and mandatory rules
    mergify_config["pull_request_rules"].rules.extend(
        rules.load_pull_request_rules_schema(MERGIFY_RULE["rules"]))

    subscription = sub_utils.get_subscription(utils.get_redis_for_cache(),
                                              installation_id)

    if pull.data["base"]["repo"][
            "private"] and not subscription["subscription_active"]:
        check_api.set_check_run(
            pull.g_pull,
            "Summary",
            "completed",
            "failure",
            output={
                "title": "Mergify is disabled",
                "summary": subscription["subscription_reason"],
            },
        )
        return

    # CheckRun are attached to head sha, so when user add commits or force push
    # we can't directly get the previous Mergify Summary. So we copy it here, then
    # anything that looks at it in next celery tasks will find it.
    if event_type == "pull_request" and data["action"] == "synchronize":
        copy_summary_from_previous_head_sha(pull.g_pull, data["before"])

    sources = [{"event_type": event_type, "data": data}]

    commands_runner.spawn_pending_commands_tasks(pull, sources)

    if event_type == "issue_comment":
        commands_runner.run_command(pull, sources, data["comment"]["body"],
                                    data["comment"]["user"])
    else:
        actions_runner.handle(mergify_config["pull_request_rules"], pull,
                              sources)
예제 #13
0
    def setUp(self):
        super(FunctionalTestBase, self).setUp()
        self.pr_counter = 0
        self.git_counter = 0
        self.cassette_library_dir = os.path.join(
            CASSETTE_LIBRARY_DIR_BASE, self.__class__.__name__, self._testMethodName
        )

        # Recording stuffs
        if RECORD:
            if os.path.exists(self.cassette_library_dir):
                shutil.rmtree(self.cassette_library_dir)
            os.makedirs(self.cassette_library_dir)

        self.recorder = vcr.VCR(
            cassette_library_dir=self.cassette_library_dir,
            record_mode="all" if RECORD else "none",
            match_on=["method", "uri"],
            filter_headers=[
                ("Authorization", "<TOKEN>"),
                ("X-Hub-Signature", "<SIGNATURE>"),
                ("User-Agent", None),
                ("Accept-Encoding", None),
                ("Connection", None),
            ],
            before_record_response=self.response_filter,
            custom_patches=(
                (pygithub.MainClass, "HTTPSConnection", vcr.stubs.VCRHTTPSConnection),
            ),
        )

        github_app_client = github_app._Client()

        mock.patch.object(github_app, "get_client", lambda: github_app_client).start()
        mock.patch.object(
            branch_updater.utils, "Gitter", lambda: self.get_gitter()
        ).start()
        mock.patch.object(
            duplicate_pull.utils, "Gitter", lambda: self.get_gitter()
        ).start()

        if not RECORD:
            # NOTE(sileht): Don't wait exponentialy during replay
            mock.patch.object(
                mergify_context.MergifyContext._ensure_complete.retry, "wait", None
            ).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        reponame_path = os.path.join(self.cassette_library_dir, "reponame")

        if RECORD:
            REPO_UUID = str(uuid.uuid4())
            with open(reponame_path, "w") as f:
                f.write(REPO_UUID)
        else:
            with open(reponame_path, "r") as f:
                REPO_UUID = f.read()

        self.name = "repo-%s-%s" % (REPO_UUID, self._testMethodName)

        self.git = self.get_gitter()
        self.addCleanup(self.git.cleanup)

        web.app.testing = True
        self.app = web.app.test_client()

        # NOTE(sileht): Prepare a fresh redis
        self.redis = utils.get_redis_for_cache()
        self.redis.flushall()
        self.subscription = {
            "tokens": {"mergifyio-testing": config.MAIN_TOKEN},
            "subscription_active": False,
            "subscription_reason": "You're not nice",
        }
        sub_utils.save_subscription_to_cache(
            self.redis, config.INSTALLATION_ID, self.subscription,
        )

        # Let's start recording
        cassette = self.recorder.use_cassette("http.json")
        cassette.__enter__()
        self.addCleanup(cassette.__exit__)

        integration = pygithub.GithubIntegration(
            config.INTEGRATION_ID, config.PRIVATE_KEY
        )
        self.installation_token = integration.get_access_token(
            config.INSTALLATION_ID
        ).token

        self.g_integration = pygithub.Github(
            self.installation_token, base_url="https://api.%s" % config.GITHUB_DOMAIN
        )
        self.g_admin = pygithub.Github(
            config.MAIN_TOKEN, base_url="https://api.%s" % config.GITHUB_DOMAIN
        )
        self.g_fork = pygithub.Github(
            config.FORK_TOKEN, base_url="https://api.%s" % config.GITHUB_DOMAIN
        )

        self.o_admin = self.g_admin.get_organization(config.TESTING_ORGANIZATION)
        self.o_integration = self.g_integration.get_organization(
            config.TESTING_ORGANIZATION
        )
        self.u_fork = self.g_fork.get_user()
        assert self.o_admin.login == "mergifyio-testing"
        assert self.o_integration.login == "mergifyio-testing"
        assert self.u_fork.login == "mergify-test2"

        self.r_o_admin = self.o_admin.create_repo(self.name)
        self.r_o_integration = self.o_integration.get_repo(self.name)
        self.url_main = "https://%s/%s" % (
            config.GITHUB_DOMAIN,
            self.r_o_integration.full_name,
        )
        self.url_fork = "https://%s/%s/%s" % (
            config.GITHUB_DOMAIN,
            self.u_fork.login,
            self.r_o_integration.name,
        )

        installation = {"id": config.INSTALLATION_ID}
        try:
            self.cli_integration = github.get_client(
                config.TESTING_ORGANIZATION, self.name, installation
            )
        except httpx.HTTPNotFound:
            self.cli_integration = github.get_client(
                config.TESTING_ORGANIZATION, self.name, installation
            )

        real_get_subscription = sub_utils.get_subscription

        def fake_retrieve_subscription_from_db(install_id):
            if int(install_id) == config.INSTALLATION_ID:
                return self.subscription
            else:
                return {
                    "tokens": {},
                    "subscription_active": False,
                    "subscription_reason": "We're just testing",
                }

        def fake_subscription(r, install_id):
            if int(install_id) == config.INSTALLATION_ID:
                return real_get_subscription(r, install_id)
            else:
                return {
                    "tokens": {},
                    "subscription_active": False,
                    "subscription_reason": "We're just testing",
                }

        mock.patch(
            "mergify_engine.branch_updater.sub_utils.get_subscription",
            side_effect=fake_subscription,
        ).start()

        mock.patch(
            "mergify_engine.branch_updater.sub_utils._retrieve_subscription_from_db",
            side_effect=fake_retrieve_subscription_from_db,
        ).start()

        mock.patch(
            "mergify_engine.sub_utils.get_subscription", side_effect=fake_subscription,
        ).start()

        mock.patch(
            "github.MainClass.Installation.Installation.get_repos",
            return_value=[self.r_o_integration],
        ).start()

        self._event_reader = EventReader(self.app)
        self._event_reader.drain()
예제 #14
0
    def setUp(self):
        super(FunctionalTestBase, self).setUp()
        self.pr_counter = 0
        self.git_counter = 0
        self.cassette_library_dir = os.path.join(
            CASSETTE_LIBRARY_DIR_BASE, self.__class__.__name__, self._testMethodName
        )

        # Recording stuffs
        if RECORD:
            if os.path.exists(self.cassette_library_dir):
                shutil.rmtree(self.cassette_library_dir)
            os.makedirs(self.cassette_library_dir)

        self.recorder = vcr.VCR(
            cassette_library_dir=self.cassette_library_dir,
            record_mode="all" if RECORD else "none",
            match_on=["method", "uri"],
            filter_headers=[
                ("Authorization", "<TOKEN>"),
                ("X-Hub-Signature", "<SIGNATURE>"),
                ("User-Agent", None),
                ("Accept-Encoding", None),
                ("Connection", None),
            ],
            before_record_response=self.response_filter,
            custom_patches=(
                (pygithub.MainClass, "HTTPSConnection", vcr.stubs.VCRHTTPSConnection),
            ),
        )

        if RECORD:
            github.CachedToken.STORAGE = {}
        else:
            # Never expire token during replay
            mock.patch.object(
                github_app.GithubBearerAuth, "get_or_create_jwt", return_value="<TOKEN>"
            ).start()
            mock.patch.object(
                github.GithubInstallationAuth,
                "get_access_token",
                return_value="<TOKEN>",
            ).start()
            github.CachedToken.STORAGE = {}
            github.CachedToken(
                installation_id=config.INSTALLATION_ID,
                token="<TOKEN>",
                expiration=datetime.datetime.utcnow() + datetime.timedelta(minutes=10),
            )

        github_app_client = github_app._Client()

        mock.patch.object(github_app, "get_client", lambda: github_app_client).start()
        mock.patch.object(branch_updater.utils, "Gitter", self.get_gitter).start()
        mock.patch.object(duplicate_pull.utils, "Gitter", self.get_gitter).start()

        if not RECORD:
            # NOTE(sileht): Don't wait exponentialy during replay
            mock.patch.object(
                context.Context._ensure_complete.retry, "wait", None
            ).start()

        # Web authentification always pass
        mock.patch("hmac.compare_digest", return_value=True).start()

        branch_prefix_path = os.path.join(self.cassette_library_dir, "branch_prefix")

        if RECORD:
            self.BRANCH_PREFIX = datetime.datetime.utcnow().strftime("%Y%m%d%H%M%S")
            with open(branch_prefix_path, "w") as f:
                f.write(self.BRANCH_PREFIX)
        else:
            with open(branch_prefix_path, "r") as f:
                self.BRANCH_PREFIX = f.read()

        self.master_branch_name = self.get_full_branch_name("master")

        self.git = self.get_gitter(LOG)
        self.addCleanup(self.git.cleanup)

        self.app = testclient.TestClient(web.app)

        # NOTE(sileht): Prepare a fresh redis
        self.redis = utils.get_redis_for_cache()
        self.redis.flushall()
        self.subscription = {
            "tokens": {"mergifyio-testing": config.MAIN_TOKEN},
            "subscription_active": False,
            "subscription_reason": "You're not nice",
        }
        loop = asyncio.get_event_loop()
        loop.run_until_complete(
            sub_utils.save_subscription_to_cache(
                config.INSTALLATION_ID, self.subscription,
            )
        )

        # Let's start recording
        cassette = self.recorder.use_cassette("http.json")
        cassette.__enter__()
        self.addCleanup(cassette.__exit__)

        integration = pygithub.GithubIntegration(
            config.INTEGRATION_ID, config.PRIVATE_KEY
        )
        self.installation_token = integration.get_access_token(
            config.INSTALLATION_ID
        ).token

        base_url = config.GITHUB_API_URL
        self.g_integration = pygithub.Github(self.installation_token, base_url=base_url)
        self.g_admin = pygithub.Github(config.MAIN_TOKEN, base_url=base_url)
        self.g_fork = pygithub.Github(config.FORK_TOKEN, base_url=base_url)

        self.o_admin = self.g_admin.get_organization(config.TESTING_ORGANIZATION)
        self.o_integration = self.g_integration.get_organization(
            config.TESTING_ORGANIZATION
        )
        self.u_fork = self.g_fork.get_user()
        assert self.o_admin.login == "mergifyio-testing"
        assert self.o_integration.login == "mergifyio-testing"
        assert self.u_fork.login == "mergify-test2"

        # NOTE(sileht): The repository have been manually created in mergifyio-testing
        # organization and then forked in mergify-test2 user account
        self.name = "functional-testing-repo"

        self.r_o_admin = self.o_admin.get_repo(self.name)
        self.r_o_integration = self.o_integration.get_repo(self.name)
        self.r_fork = self.u_fork.get_repo(self.name)

        self.url_main = f"{config.GITHUB_URL}/{self.r_o_integration.full_name}"
        self.url_fork = (
            f"{config.GITHUB_URL}/{self.u_fork.login}/{self.r_o_integration.name}"
        )

        installation = {"id": config.INSTALLATION_ID}
        self.cli_integration = github.get_client(
            config.TESTING_ORGANIZATION, self.name, installation
        )

        real_get_subscription = sub_utils.get_subscription

        def fake_retrieve_subscription_from_db(install_id):
            if int(install_id) == config.INSTALLATION_ID:
                return self.subscription
            else:
                return {
                    "tokens": {},
                    "subscription_active": False,
                    "subscription_reason": "We're just testing",
                }

        def fake_subscription(r, install_id):
            if int(install_id) == config.INSTALLATION_ID:
                return real_get_subscription(r, install_id)
            else:
                return {
                    "tokens": {},
                    "subscription_active": False,
                    "subscription_reason": "We're just testing",
                }

        mock.patch(
            "mergify_engine.branch_updater.sub_utils.get_subscription",
            side_effect=fake_subscription,
        ).start()

        mock.patch(
            "mergify_engine.branch_updater.sub_utils._retrieve_subscription_from_db",
            side_effect=fake_retrieve_subscription_from_db,
        ).start()

        mock.patch(
            "mergify_engine.sub_utils.get_subscription", side_effect=fake_subscription,
        ).start()

        mock.patch(
            "github.MainClass.Installation.Installation.get_repos",
            return_value=[self.r_o_integration],
        ).start()

        self._event_reader = EventReader(self.app)
        self._event_reader.drain()
        self._worker_thread = WorkerThread()
        self._worker_thread.start()