Exemple #1
0
def test_train_batch_split(repository: context.Repository) -> None:
    now = datetime.datetime.utcnow()
    t = merge_train.Train(repository, github_types.GitHubRefType("main"))
    p1_two = merge_train.EmbarkedPull(
        t, github_types.GitHubPullRequestNumber(1), get_config("2x1"), now
    )
    p2_two = merge_train.EmbarkedPull(
        t, github_types.GitHubPullRequestNumber(2), get_config("2x1"), now
    )
    p3_two = merge_train.EmbarkedPull(
        t, github_types.GitHubPullRequestNumber(3), get_config("2x1"), now
    )
    p4_five = merge_train.EmbarkedPull(
        t, github_types.GitHubPullRequestNumber(4), get_config("5x1"), now
    )

    assert ([p1_two], [p2_two, p3_two, p4_five]) == t._get_next_batch(
        [p1_two, p2_two, p3_two, p4_five], "2x1", 1
    )
    assert ([p1_two, p2_two], [p3_two, p4_five]) == t._get_next_batch(
        [p1_two, p2_two, p3_two, p4_five], "2x1", 2
    )
    assert ([p1_two, p2_two, p3_two], [p4_five]) == t._get_next_batch(
        [p1_two, p2_two, p3_two, p4_five], "2x1", 10
    )
    assert ([], [p1_two, p2_two, p3_two, p4_five]) == t._get_next_batch(
        [p1_two, p2_two, p3_two, p4_five], "5x1", 10
    )
Exemple #2
0
async def test_merge_commit_message(
    body: str,
    title: str,
    message: str,
    template: typing.Optional[str],
    context_getter: conftest.ContextGetterFixture,
) -> None:
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(43),
                                body=body,
                                title="My PR title")
    ctxt.repository._caches.branch_protections[github_types.GitHubRefType(
        "main")] = None
    ctxt._caches.pull_statuses.set([
        github_types.GitHubStatus({
            "target_url": "http://example.com",
            "context": "my CI",
            "state": "success",
            "description": "foobar",
            "avatar_url": "",
        })
    ])
    ctxt._caches.pull_check_runs.set([])
    assert await ctxt.pull_request.get_commit_message(template=template) == (
        title,
        message,
    )
Exemple #3
0
def test_embarked_pull_old_serialization() -> None:
    queue_config = rules.QueueConfig(
        priority=0,
        speculative_checks=5,
        batch_size=1,
        batch_max_wait_time=datetime.timedelta(seconds=0),
        allow_inplace_checks=True,
        disallow_checks_interruption_from_queues=[],
        checks_timeout=None,
        draft_bot_account=None,
    )
    config = queue.PullQueueConfig(
        name=rules.QueueName("foo"),
        strict_method="merge",
        update_method="merge",
        priority=0,
        effective_priority=0,
        bot_account=None,
        update_bot_account=None,
        queue_config=queue_config,
    )

    now = date.utcnow()
    old_typed = merge_train.EmbarkedPull.OldSerialized(
        github_types.GitHubPullRequestNumber(1234), config, now
    )
    old_untyped = json.loads(json.dumps(old_typed))
    ep = merge_train.EmbarkedPull.deserialize(mock.Mock(), old_untyped)
    assert ep.user_pull_request_number == 1234
    assert ep.config == config
    assert ep.queued_at == now
Exemple #4
0
async def fake_train_car_create_pull(
    inner_self: merge_train.TrainCar, queue_rule: rules.QueueRule
) -> None:
    inner_self.creation_state = "created"
    inner_self.queue_pull_request_number = github_types.GitHubPullRequestNumber(
        inner_self.still_queued_embarked_pulls[-1].user_pull_request_number + 10
    )
Exemple #5
0
async def simulator_pull(
    body: SimulatorPayload,  # noqa: B008
    repository_ctxt: context.Repository = fastapi.Depends(  # noqa: B008
        security.get_repository_context
    ),
    number: int = fastapi.Path(  # noqa: B008
        ..., description="The pull request number"
    ),
) -> SimulatorResponse:
    config = body.get_config()
    try:
        ctxt = await repository_ctxt.get_pull_request_context(
            github_types.GitHubPullRequestNumber(number)
        )
    except http.HTTPClientSideError as e:
        raise fastapi.HTTPException(status_code=e.status_code, detail=e.message)
    ctxt.sources = [{"event_type": "mergify-simulator", "data": [], "timestamp": ""}]  # type: ignore[typeddict-item]
    match = await config["pull_request_rules"].get_pull_request_rule(ctxt)
    title, summary = await actions_runner.gen_summary(
        ctxt, config["pull_request_rules"], match
    )
    return SimulatorResponse(
        title=title,
        summary=summary,
    )
async def test_pull_behind(
        commits_tree_generator: typing.Any,
        context_getter: conftest.ContextGetterFixture) -> None:
    expected, commits = commits_tree_generator

    async def get_commits(*args: typing.Any,
                          **kwargs: typing.Any) -> typing.Any:
        # /pulls/X/commits
        for c in commits:
            yield c

    async def item(*args: typing.Any, **kwargs: typing.Any) -> typing.Any:
        # /branch/#foo
        return {"commit": {"sha": "base"}}

    async def get_compare(*args: typing.Any,
                          **kwargs: typing.Any) -> typing.Any:
        return github_types.GitHubCompareCommits(
            {"behind_by": 0 if expected else 100})

    client = mock.Mock()
    client.items.return_value = get_commits()
    client.item.side_effect = [item(), get_compare()]

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client
    assert expected == await ctxt.is_behind
async def test_signals(context_getter: conftest.ContextGetterFixture) -> None:
    signals.setup()
    assert len(signals.SIGNALS) == 3

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    with mock.patch(
            "mergify_engine.signals.NoopSignal.__call__") as signal_method:
        await signals.send(
            ctxt.repository,
            ctxt.pull["number"],
            "action.label",
            signals.EventLabelMetadata({
                "added": [],
                "removed": ["bar"]
            }),
        )
        signal_method.assert_called_once_with(
            ctxt.repository,
            ctxt.pull["number"],
            "action.label",
            {
                "added": [],
                "removed": ["bar"]
            },
        )
Exemple #8
0
async def test_team_permissions_missing(
    context_getter: conftest.ContextGetterFixture, ) -> None:
    action = request_reviews.RequestReviewsAction.get_schema()({
        "random_count": 2,
        "teams": {
            "foobar": 2,
            "@other/foobaz": 1,
        },
        "users": {
            "jd": 2,
            "sileht": 1,
        },
    }, )
    client = mock.MagicMock()
    client.get = mock.AsyncMock(side_effect=http.HTTPNotFound(
        message="not found", response=mock.ANY, request=mock.ANY))
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client
    result = await action.run(ctxt, None)
    assert result.conclusion == check_api.Conclusion.FAILURE
    assert result.title == "Invalid requested teams"
    for error in (
            "Team `foobar` does not exist or has not access to this repository",
            "Team `@other/foobaz` is not part of the organization `Mergifyio`",
    ):
        assert error in result.summary
Exemple #9
0
 async def get_pulls(self) -> typing.List[github_types.GitHubPullRequestNumber]:
     return [
         github_types.GitHubPullRequestNumber(int(pull))
         for pull in await self.repository.installation.redis.zrangebyscore(
             self._redis_queue_key, "-inf", "+inf"
         )
     ]
Exemple #10
0
async def test_get_already_merged_summary(
    merged_by: str,
    raw_config: str,
    result: str,
    context_getter: conftest.ContextGetterFixture,
) -> None:
    ctxt = await context_getter(
        github_types.GitHubPullRequestNumber(1),
        merged=True,
        merged_by=github_types.GitHubAccount({
            "id":
            github_types.GitHubAccountIdType(1),
            "login":
            github_types.GitHubLogin(merged_by),
            "type":
            "User",
            "avatar_url":
            "",
        }),
    )
    ctxt.repository._caches.branch_protections[github_types.GitHubRefType(
        "main")] = None

    file = context.MergifyConfigFile(
        type="file",
        content="whatever",
        sha=github_types.SHAType("azertyuiop"),
        path="whatever",
        decoded_content=raw_config,
    )

    config = rules.get_mergify_config(file)
    match = await config["pull_request_rules"].get_pull_request_rule(ctxt)
    assert result == await actions_runner.get_already_merged_summary(
        ctxt, match)
Exemple #11
0
def _url_parser(
    url: str,
) -> typing.Tuple[github_types.GitHubLogin,
                  typing.Optional[github_types.GitHubRepositoryName],
                  typing.Optional[github_types.GitHubPullRequestNumber], ]:

    path = [
        el for el in urllib.parse.urlparse(url).path.split("/") if el != ""
    ]

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

    try:
        owner, repo, _, pull_number = path
    except ValueError:
        pull_number = None
        try:
            owner, repo = path
        except ValueError:
            if len(path) == 1:
                owner = path[0]
                repo = None
            else:
                raise ValueError

    return (
        github_types.GitHubLogin(owner),
        None if repo is None else github_types.GitHubRepositoryName(repo),
        None if pull_number is None else github_types.GitHubPullRequestNumber(
            int(pull_number)),
    )
Exemple #12
0
async def test_get_commits_to_cherry_pick_merge(
    commits: mock.PropertyMock,
    context_getter: conftest.ContextGetterFixture,
) -> None:
    c1 = github_types.CachedGitHubBranchCommit({
        "sha":
        github_types.SHAType("c1f"),
        "parents": [],
        "commit_message":
        "foobar",
        "commit_verification_verified":
        False,
    })
    c2 = github_types.CachedGitHubBranchCommit({
        "sha":
        github_types.SHAType("c2"),
        "parents": [c1["sha"]],
        "commit_message":
        "foobar",
        "commit_verification_verified":
        False,
    })

    async def fake_commits(
    ) -> typing.List[github_types.CachedGitHubBranchCommit]:
        return [c1, c2]

    commits.return_value = fake_commits()

    client = mock.Mock()
    client.auth.get_access_token.return_value = "<token>"

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client

    base_branch = github_types.CachedGitHubBranchCommit({
        "sha":
        github_types.SHAType("base_branch"),
        "parents": [],
        "commit_message":
        "foobar",
        "commit_verification_verified":
        False,
    })
    merge_commit = github_types.CachedGitHubBranchCommit({
        "sha":
        github_types.SHAType("merge_commit"),
        "parents": [base_branch["sha"], c2["sha"]],
        "commit_message":
        "foobar",
        "commit_verification_verified":
        False,
    })

    assert await duplicate_pull._get_commits_to_cherrypick(ctxt,
                                                           merge_commit) == [
                                                               c1,
                                                               c2,
                                                           ]
Exemple #13
0
async def test_merge_commit_message_undefined(
        body: str, context_getter: conftest.ContextGetterFixture) -> None:
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(43),
                                body=body,
                                title="My PR title")
    with pytest.raises(context.RenderTemplateFailure) as x:
        await ctxt.pull_request.get_commit_message()
        assert str(x) == "foobar"
Exemple #14
0
async def test_signals(context_getter: conftest.ContextGetterFixture) -> None:
    signals.setup()
    assert len(signals.SIGNALS) == 3

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    with mock.patch(
            "mergify_engine_signals.noop.Signal.__call__") as signal_method:
        await signals.send(ctxt, "action.update", {"attr": "value"})
        signal_method.assert_called_once_with(ctxt, "action.update",
                                              {"attr": "value"})
Exemple #15
0
async def get_pull_request_head_sha_to_number_mapping(
    redis_cache: utils.RedisCache,
    owner_id: github_types.GitHubAccountIdType,
    repo_id: github_types.GitHubRepositoryIdType,
    sha: github_types.SHAType,
) -> typing.Optional[github_types.GitHubPullRequestNumber]:
    ret = await redis_cache.get(
        context.Context.redis_last_summary_pulls_key(owner_id, repo_id, sha), )
    if ret is None:
        return None
    return github_types.GitHubPullRequestNumber(int(ret))
Exemple #16
0
async def test_run_command_without_rerun_and_without_user(
    context_getter: conftest.ContextGetterFixture, ) -> None:

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))

    with pytest.raises(RuntimeError) as error_msg:
        await commands_runner.handle(
            ctxt=ctxt,
            mergify_config=EMPTY_CONFIG,
            comment="@Mergifyio update",
            user=None,
        )
    assert "user must be set if rerun is false" in str(error_msg.value)
Exemple #17
0
async def send(
    redis_stream: utils.RedisStream,
    redis_cache: utils.RedisCache,
) -> None:
    score = date.utcnow().timestamp()
    keys = await redis_cache.zrangebyscore(DELAYED_REFRESH_KEY, "-inf", score)
    if not keys:
        return

    pipe = await redis_stream.pipeline()
    keys_to_delete = set()
    for subkey in keys:
        (
            owner_id_str,
            owner_login,
            repository_id_str,
            repository_name,
            pull_request_number_str,
        ) = subkey.split("~")
        owner_id = github_types.GitHubAccountIdType(int(owner_id_str))
        repository_id = github_types.GitHubRepositoryIdType(
            int(repository_id_str))
        pull_request_number = github_types.GitHubPullRequestNumber(
            int(pull_request_number_str))

        LOG.info(
            "sending delayed pull request refresh",
            gh_owner=owner_login,
            gh_repo=repository_name,
            action="internal",
            source="delayed-refresh",
        )

        await worker.push(
            pipe,
            owner_id,
            owner_login,
            repository_id,
            repository_name,
            pull_request_number,
            "refresh",
            {
                "action": "internal",
                "ref": None,
                "source": "delayed-refresh",
            },  # type: ignore[typeddict-item]
        )
        keys_to_delete.add(subkey)

    await pipe.execute()
    await redis_cache.zrem(DELAYED_REFRESH_KEY, *keys_to_delete)
Exemple #18
0
 def make_pr(
         repo: github_types.GitHubRepository,
         owner: github_types.GitHubAccount
 ) -> github_types.GitHubPullRequest:
     return github_types.GitHubPullRequest({
         "id":
         github_types.GitHubPullRequestId(github_types.GitHubIssueId(0)),
         "maintainer_can_modify":
         False,
         "head": {
             "user": owner,
             "label": "",
             "ref": github_types.GitHubRefType(""),
             "sha": github_types.SHAType(""),
             "repo": repo,
         },
         "user":
         owner,
         "number":
         github_types.GitHubPullRequestNumber(
             github_types.GitHubIssueNumber(0)),
         "rebaseable":
         False,
         "draft":
         False,
         "merge_commit_sha":
         None,
         "html_url":
         "",
         "state":
         "closed",
         "mergeable_state":
         "unknown",
         "merged_by":
         None,
         "merged":
         False,
         "merged_at":
         None,
         "labels": [],
         "base": {
             "ref": github_types.GitHubRefType("main"),
             "sha": github_types.SHAType(""),
             "label": "",
             "repo": repo,
             "user": owner,
         },
     })
Exemple #19
0
async def test_run_command_with_user(
    user_id: int,
    permission: str,
    comment: str,
    result: typing.Optional[str],
    context_getter: conftest.ContextGetterFixture,
) -> None:

    user = github_types.GitHubAccount(
        {
            "id": github_types.GitHubAccountIdType(user_id),
            "login": github_types.GitHubLogin("wall-e"),
            "type": "Bot",
            "avatar_url": "https://avatars.githubusercontent.com/u/583231?v=4",
        }, )

    client = mock.Mock()
    client.item = mock.AsyncMock()
    client.item.return_value = {
        "permission": permission,
        "user": user,
    }
    client.post = mock.AsyncMock()

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client

    await commands_runner.handle(
        ctxt=ctxt,
        mergify_config=EMPTY_CONFIG,
        comment="unrelated",
        user=None,
        rerun=True,
    )
    assert len(client.post.call_args_list) == 0

    await commands_runner.handle(
        ctxt=ctxt,
        mergify_config=EMPTY_CONFIG,
        comment=comment,
        user=user,
    )

    if result is None:
        assert len(client.post.call_args_list) == 0
    else:
        assert len(client.post.call_args_list) == 1
        assert result in client.post.call_args_list[0][1]["json"]["body"]
Exemple #20
0
async def _get_github_pulls_from_sha(
    installation: context.Installation,
    repo_name: github_types.GitHubRepositoryName,
    sha: github_types.SHAType,
    pulls: typing.List[github_types.GitHubPullRequest],
) -> typing.List[github_types.GitHubPullRequestNumber]:
    cache_key = f"sha~{installation.owner_login}~{repo_name}~{sha}"
    pull_number = await installation.redis.get(cache_key)
    if pull_number is None:
        for pull in pulls:
            if pull["head"]["sha"] == sha:
                await installation.redis.set(cache_key,
                                             pull["number"],
                                             ex=SHA_EXPIRATION)
                return [pull["number"]]
        return []
    else:
        return [github_types.GitHubPullRequestNumber(int(pull_number))]
async def test_datadog(context_getter: conftest.ContextGetterFixture) -> None:
    signals.setup()
    assert len(signals.SIGNALS) == 3

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))

    with mock.patch("datadog.statsd.increment") as increment:
        await signals.send(
            ctxt.repository,
            ctxt.pull["number"],
            "action.label",
            {
                "added": [],
                "removed": ["bar"]
            },
        )
        increment.assert_called_once_with("engine.signals.action.count",
                                          tags=["event:label"])
Exemple #22
0
async def test_run_command_with_rerun_and_without_user(
    context_getter: conftest.ContextGetterFixture, ) -> None:

    client = mock.Mock()
    client.post = mock.AsyncMock()

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client

    await commands_runner.handle(
        ctxt=ctxt,
        mergify_config=EMPTY_CONFIG,
        comment="@mergifyio something",
        user=None,
        rerun=True,
    )
    assert len(client.post.call_args_list) == 1
    assert ("Sorry but I didn't understand the command."
            in client.post.call_args_list[0][1]["json"]["body"])
Exemple #23
0
async def test_get_rule_checks_status(
    conditions: typing.Any,
    conclusion: check_api.Conclusion,
    context_getter: conftest.ContextGetterFixture,
    fake_client: mock.Mock,
) -> None:
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = fake_client
    rules = pull_request_rule_from_list([{
        "name": "hello",
        "conditions": conditions,
        "actions": {},
    }])
    match = await rules.get_pull_request_rule(ctxt)
    evaluated_rule = match.matching_rules[0]
    assert (await
            checks_status.get_rule_checks_status(ctxt.log, ctxt.repository,
                                                 [ctxt.pull_request],
                                                 evaluated_rule)) == conclusion
Exemple #24
0
async def test_disabled(context_getter: conftest.ContextGetterFixture) -> None:
    action = request_reviews.RequestReviewsAction.get_schema()({
        "random_count": 2,
        "teams": {
            "foobar": 2,
            "foobaz": 1,
        },
        "users": {
            "jd": 2,
            "sileht": 1,
        },
    }, )
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    result = await action.run(ctxt, None)
    assert result.conclusion == check_api.Conclusion.ACTION_REQUIRED
    assert result.title == "Random request reviews are disabled"
    assert result.summary == (
        "⚠ The [subscription](https://dashboard.mergify.com/github/Mergifyio/subscription) "
        "needs to be updated to enable this feature.")
Exemple #25
0
async def test_run_command_with_wrong_arg(
    context_getter: conftest.ContextGetterFixture, ) -> None:

    client = mock.Mock()
    client.post = mock.AsyncMock()

    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client

    await commands_runner.handle(
        ctxt=ctxt,
        mergify_config=EMPTY_CONFIG,
        comment="@mergifyio squash invalid-arg",
        rerun=True,
        user=None,
    )

    assert len(client.post.call_args_list) == 1
    assert client.post.call_args_list[0][1]["json"]["body"].startswith(
        "Sorry but I didn't understand the arguments of the command `squash`")
Exemple #26
0
async def test_team_permissions_ok(
    context_getter: conftest.ContextGetterFixture, ) -> None:
    action = request_reviews.RequestReviewsAction.get_schema()({
        "random_count": 2,
        "teams": {
            "foobar": 2,
            "foobaz": 1,
        },
        "users": {
            "jd": 2,
            "sileht": 1,
        },
    }, )
    client = mock.MagicMock()
    client.get = mock.AsyncMock(return_value={})
    ctxt = await context_getter(github_types.GitHubPullRequestNumber(1))
    ctxt.repository.installation.client = client
    result = await action.run(ctxt, None)
    assert result.summary == ""
    assert result.title == "No new reviewers to request"
    assert result.conclusion == check_api.Conclusion.SUCCESS
Exemple #27
0
async def test_train_batch_max_wait_time(
    repository: context.Repository, context_getter: conftest.ContextGetterFixture
) -> None:
    with freeze_time("2021-09-22T08:00:00") as freezed_time:
        t = merge_train.Train(repository, github_types.GitHubRefType("main"))
        await t.load()

        config = get_config("batch-wait-time")

        await t.add_pull(await context_getter(1), config)
        await t.refresh()
        assert [] == get_cars_content(t)
        assert [1] == get_waiting_content(t)

        # Enought PR to batch!
        await t.add_pull(await context_getter(2), config)
        await t.refresh()
        assert [[1, 2]] == get_cars_content(t)
        assert [] == get_waiting_content(t)

        await t.add_pull(await context_getter(3), config)
        await t.refresh()
        assert [[1, 2]] == get_cars_content(t)
        assert [3] == get_waiting_content(t)

        d = await delayed_refresh._get_current_refresh_datetime(
            repository, github_types.GitHubPullRequestNumber(3)
        )
        assert d is not None
        assert d == freezed_time().replace(
            tzinfo=datetime.timezone.utc
        ) + datetime.timedelta(minutes=5)

    with freeze_time("2021-09-22T08:05:02"):
        await t.refresh()
        assert [[1, 2], [1, 2, 3]] == get_cars_content(t)
        assert [] == get_waiting_content(t)
Exemple #28
0
async def report(
    url: str,
) -> typing.Union[context.Context, github.AsyncGithubInstallationClient, None]:
    redis_links = redis_utils.RedisLinks(max_idle_time=0)

    try:
        owner_login, repo, pull_number = _url_parser(url)
    except ValueError:
        print(f"{url} is not valid")
        return None

    try:
        installation_json = await github.get_installation_from_login(
            owner_login)
        client = github.aget_client(installation_json)
    except exceptions.MergifyNotInstalled:
        print(f"* Mergify is not installed on account {owner_login}")
        return None

    # Do a dumb request just to authenticate
    await client.get("/")

    print(f"* INSTALLATION ID: {installation_json['id']}")

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

    owner_id = installation_json["account"]["id"]
    cached_sub = await subscription.Subscription.get_subscription(
        redis_links.cache, owner_id)
    db_sub = await subscription.Subscription._retrieve_subscription_from_db(
        redis_links.cache, owner_id)

    cached_tokens = await user_tokens.UserTokens.get(redis_links.cache,
                                                     owner_id)
    if config.SAAS_MODE:
        db_tokens = typing.cast(
            user_tokens.UserTokens,
            (await user_tokens.UserTokensSaas._retrieve_from_db(
                redis_links.cache, owner_id)),
        )
    else:
        db_tokens = cached_tokens

    print("* Features (db):")
    for v in sorted(f.value for f in db_sub.features):
        print(f"  - {v}")
    print("* Features (cache):")
    for v in sorted(f.value for f in cached_sub.features):
        print(f"  - {v}")

    installation = context.Installation(installation_json, cached_sub, client,
                                        redis_links)

    await report_dashboard_synchro(installation.installation["id"], cached_sub,
                                   cached_tokens, "ENGINE-CACHE", slug)
    await report_dashboard_synchro(installation.installation["id"], db_sub,
                                   db_tokens, "DASHBOARD", slug)

    await report_worker_status(owner_login)

    if repo is not None:
        repository = await installation.get_repository_by_name(repo)

        print(
            f"* REPOSITORY IS {'PRIVATE' if repository.repo['private'] else 'PUBLIC'}"
        )

        print(f"* DEFAULT BRANCH: {repository.repo['default_branch']}")

        print("* CONFIGURATION:")
        mergify_config = None
        config_file = await repository.get_mergify_config_file()
        if not config_file:
            print(".mergify.yml is missing")
        else:
            print(f"Config filename: {config_file['path']}")
            print(config_file["decoded_content"])
            try:
                mergify_config = await repository.get_mergify_config()
            except rules.InvalidRules as e:  # pragma: no cover
                print(f"configuration is invalid {str(e)}")

        if pull_number is None:
            async for branch in typing.cast(
                    typing.AsyncGenerator[github_types.GitHubBranch, None],
                    client.items(
                        f"/repos/{owner_login}/{repo}/branches",
                        resource_name="branches",
                        page_limit=100,
                    ),
            ):
                q = merge_train.Train(repository, branch["name"])
                await q.load()
                await report_queue("TRAIN", q)

        else:
            repository = await installation.get_repository_by_name(
                github_types.GitHubRepositoryName(repo))
            try:
                ctxt = await repository.get_pull_request_context(
                    github_types.GitHubPullRequestNumber(int(pull_number)))
            except http.HTTPNotFound:
                print(f"Pull request `{url}` does not exist")
                return client

            # FIXME queues could also be printed if no pull number given
            # TODO(sileht): display train if any
            q = await merge_train.Train.from_context(ctxt)
            print(
                f"* TRAIN: {', '.join([f'#{p}' for p in await q.get_pulls()])}"
            )
            print("* PULL REQUEST:")
            pr_data = await ctxt.pull_request.items()
            pprint.pprint(pr_data, width=160)

            is_behind = await ctxt.is_behind
            print(f"is_behind: {is_behind}")

            print(f"mergeable_state: {ctxt.pull['mergeable_state']}")

            print("* MERGIFY LAST CHECKS:")
            for c in await ctxt.pull_engine_check_runs:
                print(
                    f"[{c['name']}]: {c['conclusion']} | {c['output'].get('title')} | {c['html_url']}"
                )
                print("> " + "\n> ".join(
                    ("No Summary", ) if c["output"]["summary"] is None else
                    c["output"]["summary"].split("\n")))

            if mergify_config is not None:
                print("* MERGIFY LIVE MATCHES:")
                pull_request_rules = mergify_config["pull_request_rules"]
                match = await pull_request_rules.get_pull_request_rule(ctxt)
                summary_title, summary = await actions_runner.gen_summary(
                    ctxt, pull_request_rules, match)
                print(f"[Summary]: success | {summary_title}")
                print("> " + "\n> ".join(summary.strip().split("\n")))

            return ctxt

    return client
async def push_to_worker(
    redis_links: redis_utils.RedisLinks,
    event_type: github_types.GitHubEventType,
    event_id: str,
    event: github_types.GitHubEvent,
    score: typing.Optional[str] = None,
) -> None:

    pull_number = None
    ignore_reason = None

    if event_type == "pull_request":
        event = typing.cast(github_types.GitHubEventPullRequest, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        pull_number = event["pull_request"]["number"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        elif event["action"] in ("opened", "synchronize"):
            try:
                await engine.create_initial_summary(redis_links.cache, event)
            except Exception as e:
                _log_on_exception(e, "fail to create initial summary")
        elif (
            event["action"] == "edited"
            and event["sender"]["id"] == config.BOT_USER_ID
            and event["pull_request"]["head"]["ref"].startswith(
                constants.MERGE_QUEUE_BRANCH_PREFIX
            )
        ):
            ignore_reason = "mergify merge-queue description update"

    elif event_type == "refresh":
        event = typing.cast(github_types.GitHubEventRefresh, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]

        if event["pull_request_number"] is not None:
            pull_number = event["pull_request_number"]

    elif event_type == "pull_request_review_comment":
        event = typing.cast(github_types.GitHubEventPullRequestReviewComment, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        if event["pull_request"] is not None:
            pull_number = event["pull_request"]["number"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

    elif event_type == "pull_request_review":
        event = typing.cast(github_types.GitHubEventPullRequestReview, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        pull_number = event["pull_request"]["number"]

    elif event_type == "issue_comment":
        event = typing.cast(github_types.GitHubEventIssueComment, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        pull_number = github_types.GitHubPullRequestNumber(event["issue"]["number"])

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        elif "pull_request" not in event["issue"]:
            ignore_reason = "comment is not on a pull request"

        elif event["action"] != "created":
            ignore_reason = f"comment has been {event['action']}"

        elif event["comment"]["user"]["id"] == config.BOT_USER_ID:
            ignore_reason = "comment by Mergify[bot]"

        else:
            # NOTE(sileht): nothing important should happen in this hook as we don't retry it
            try:
                await commands_runner.on_each_event(event)
            except Exception as e:
                _log_on_exception(e, "commands_runner.on_each_event failed")

    elif event_type == "status":
        event = typing.cast(github_types.GitHubEventStatus, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        pull_number = await get_pull_request_head_sha_to_number_mapping(
            redis_links.cache, owner_id, repo_id, event["sha"]
        )

    elif event_type == "push":
        event = typing.cast(github_types.GitHubEventPush, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        elif not event["ref"].startswith("refs/heads/"):
            ignore_reason = f"push on {event['ref']}"

        elif event["repository"]["archived"]:  # pragma: no cover
            ignore_reason = "repository archived"

        await context.Repository.clear_config_file_cache(redis_links.cache, repo_id)

    elif event_type == "check_suite":
        event = typing.cast(github_types.GitHubEventCheckSuite, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        elif event["action"] != "rerequested":
            ignore_reason = f"check_suite/{event['action']}"

        elif (
            event[event_type]["app"]["id"] == config.INTEGRATION_ID
            and event["action"] != "rerequested"
            and event[event_type].get("external_id") != check_api.USER_CREATED_CHECKS
        ):
            ignore_reason = f"mergify {event_type}"

        pull_number = await get_pull_request_head_sha_to_number_mapping(
            redis_links.cache, owner_id, repo_id, event["check_suite"]["head_sha"]
        )

    elif event_type == "check_run":
        event = typing.cast(github_types.GitHubEventCheckRun, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]

        if event["repository"]["archived"]:
            ignore_reason = "repository archived"

        elif (
            event[event_type]["app"]["id"] == config.INTEGRATION_ID
            and event["action"] != "rerequested"
            and event[event_type].get("external_id") != check_api.USER_CREATED_CHECKS
        ):
            ignore_reason = f"mergify {event_type}"

        pull_number = await get_pull_request_head_sha_to_number_mapping(
            redis_links.cache, owner_id, repo_id, event["check_run"]["head_sha"]
        )

    elif event_type == "organization":
        event = typing.cast(github_types.GitHubEventOrganization, event)
        owner_login = event["organization"]["login"]
        owner_id = event["organization"]["id"]
        repo_name = None
        repo_id = None
        ignore_reason = "organization event"

        if event["action"] == "deleted":
            await context.Installation.clear_team_members_cache_for_org(
                redis_links.cache, event["organization"]
            )
            await context.Repository.clear_team_permission_cache_for_org(
                redis_links.cache, event["organization"]
            )

        if event["action"] in ("deleted", "member_added", "member_removed"):
            await context.Repository.clear_user_permission_cache_for_org(
                redis_links.cache, event["organization"]
            )

    elif event_type == "member":
        event = typing.cast(github_types.GitHubEventMember, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        ignore_reason = "member event"

        await context.Repository.clear_user_permission_cache_for_user(
            redis_links.cache,
            event["repository"]["owner"],
            event["repository"],
            event["member"],
        )

    elif event_type == "membership":
        event = typing.cast(github_types.GitHubEventMembership, event)
        owner_login = event["organization"]["login"]
        owner_id = event["organization"]["id"]
        repo_name = None
        repo_id = None
        ignore_reason = "membership event"

        if "slug" in event["team"]:
            await context.Installation.clear_team_members_cache_for_team(
                redis_links.cache, event["organization"], event["team"]["slug"]
            )
            await context.Repository.clear_team_permission_cache_for_team(
                redis_links.cache, event["organization"], event["team"]["slug"]
            )
        else:
            # Deleted team
            await context.Installation.clear_team_members_cache_for_org(
                redis_links.cache,
                event["organization"],
            )
            await context.Repository.clear_team_permission_cache_for_org(
                redis_links.cache, event["organization"]
            )

        await context.Repository.clear_user_permission_cache_for_org(
            redis_links.cache, event["organization"]
        )

    elif event_type == "team":
        event = typing.cast(github_types.GitHubEventTeam, event)
        owner_login = event["organization"]["login"]
        owner_id = event["organization"]["id"]
        repo_id = None
        repo_name = None
        ignore_reason = "team event"

        if event["action"] in ("edited", "deleted"):
            await context.Installation.clear_team_members_cache_for_team(
                redis_links.cache, event["organization"], event["team"]["slug"]
            )
            await context.Repository.clear_team_permission_cache_for_team(
                redis_links.cache, event["organization"], event["team"]["slug"]
            )

        if event["action"] in (
            "edited",
            "added_to_repository",
            "removed_from_repository",
            "deleted",
        ):
            if "repository" in event:
                await context.Repository.clear_user_permission_cache_for_repo(
                    redis_links.cache, event["organization"], event["repository"]
                )
                await context.Repository.clear_team_permission_cache_for_repo(
                    redis_links.cache, event["organization"], event["repository"]
                )
            else:
                await context.Repository.clear_user_permission_cache_for_org(
                    redis_links.cache, event["organization"]
                )
                await context.Repository.clear_team_permission_cache_for_org(
                    redis_links.cache, event["organization"]
                )

    elif event_type == "team_add":
        event = typing.cast(github_types.GitHubEventTeamAdd, event)
        owner_login = event["repository"]["owner"]["login"]
        owner_id = event["repository"]["owner"]["id"]
        repo_id = event["repository"]["id"]
        repo_name = event["repository"]["name"]
        ignore_reason = "team_add event"

        await context.Repository.clear_user_permission_cache_for_repo(
            redis_links.cache, event["repository"]["owner"], event["repository"]
        )
        await context.Repository.clear_team_permission_cache_for_repo(
            redis_links.cache, event["organization"], event["repository"]
        )

    else:
        owner_login = "******"
        owner_id = "<unknown>"
        repo_name = "<unknown>"
        repo_id = "<unknown>"
        ignore_reason = "unexpected event_type"

    if ignore_reason is None:
        msg_action = "pushed to worker"
        slim_event = _extract_slim_event(event_type, event)

        await worker.push(
            redis_links.stream,
            owner_id,
            owner_login,
            repo_id,
            repo_name,
            pull_number,
            event_type,
            slim_event,
            score,
        )
    else:
        slim_event = None
        msg_action = f"ignored: {ignore_reason}"

    LOG.debug(
        "GithubApp event %s",
        msg_action,
        event_type=event_type,
        event_id=event_id,
        sender=event["sender"]["login"],
        gh_owner=owner_login,
        gh_repo=repo_name,
        event=slim_event,
    )

    if ignore_reason:
        raise IgnoredEvent(event_type, event_id, ignore_reason)
Exemple #30
0
async def test_get_commits_to_cherry_pick_rebase(
    commits: mock.PropertyMock,
    redis_cache: utils.RedisCache,
) -> None:
    gh_owner = github_types.GitHubAccount({
        "login":
        github_types.GitHubLogin("user"),
        "id":
        github_types.GitHubAccountIdType(0),
        "type":
        "User",
        "avatar_url":
        "",
    })

    gh_repo = github_types.GitHubRepository({
        "full_name":
        "user/name",
        "name":
        github_types.GitHubRepositoryName("name"),
        "private":
        False,
        "id":
        github_types.GitHubRepositoryIdType(0),
        "owner":
        gh_owner,
        "archived":
        False,
        "url":
        "",
        "html_url":
        "",
        "default_branch":
        github_types.GitHubRefType("ref"),
    })

    c1 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c1f"),
        "parents": [],
        "commit": {
            "message": "foobar"
        },
    })
    c2 = github_types.GitHubBranchCommit({
        "sha": github_types.SHAType("c2"),
        "parents": [c1],
        "commit": {
            "message": "foobar"
        },
    })
    commits.return_value = [c1, c2]

    client = mock.Mock()
    client.auth.get_access_token.return_value = "<token>"
    client.items.side_effect = fake_get_github_pulls_from_sha

    installation = context.Installation(
        github_types.GitHubAccountIdType(123),
        github_types.GitHubLogin("user"),
        subscription.Subscription(redis_cache, 0, False, "", frozenset()),
        client,
        redis_cache,
    )
    repository = context.Repository(installation, gh_repo)
    ctxt = await context.Context.create(
        repository,
        {
            "labels": [],
            "draft": False,
            "merge_commit_sha": github_types.SHAType(""),
            "title": "",
            "commits": 1,
            "rebaseable": False,
            "maintainer_can_modify": False,
            "id": github_types.GitHubPullRequestId(0),
            "number": github_types.GitHubPullRequestNumber(6),
            "merged": True,
            "state": "closed",
            "html_url": "<html_url>",
            "changed_files": 1,
            "base": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "ref": github_types.GitHubRefType("ref"),
                "repo": {
                    "full_name": "user/ref",
                    "name": github_types.GitHubRepositoryName("name"),
                    "private": False,
                    "id": github_types.GitHubRepositoryIdType(0),
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                    "archived": False,
                    "url": "",
                    "html_url": "",
                    "default_branch": github_types.GitHubRefType(""),
                },
            },
            "head": {
                "label": "",
                "sha": github_types.SHAType("sha"),
                "ref": github_types.GitHubRefType("fork"),
                "user": {
                    "login": github_types.GitHubLogin("user"),
                    "id": github_types.GitHubAccountIdType(0),
                    "type": "User",
                    "avatar_url": "",
                },
                "repo": {
                    "full_name": "fork/other",
                    "name": github_types.GitHubRepositoryName("other"),
                    "private": False,
                    "archived": False,
                    "url": "",
                    "html_url": "",
                    "default_branch": github_types.GitHubRefType(""),
                    "id": github_types.GitHubRepositoryIdType(0),
                    "owner": {
                        "login": github_types.GitHubLogin("user"),
                        "id": github_types.GitHubAccountIdType(0),
                        "type": "User",
                        "avatar_url": "",
                    },
                },
            },
            "user": {
                "login": github_types.GitHubLogin("user"),
                "id": github_types.GitHubAccountIdType(0),
                "type": "User",
                "avatar_url": "",
            },
            "merged_by": None,
            "merged_at": None,
            "mergeable_state": "clean",
        },
    )

    base_branch = github_types.GitHubBranchCommitParent(
        {"sha": github_types.SHAType("base_branch")})
    rebased_c1 = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("rebased_c1"),
        "parents": [base_branch],
        "commit": {
            "message": "hello c1"
        },
    })
    rebased_c2 = github_types.GitHubBranchCommit({
        "sha":
        github_types.SHAType("rebased_c2"),
        "parents": [rebased_c1],
        "commit": {
            "message": "hello c2"
        },
    })

    async def fake_get_github_commit_from_sha(url, api_version=None):
        if url.endswith("/commits/rebased_c1"):
            return rebased_c1
        if url.endswith("/commits/rebased_c2"):
            return rebased_c2
        raise RuntimeError(f"Unknown URL {url}")

    client.item.side_effect = fake_get_github_commit_from_sha

    assert await duplicate_pull._get_commits_to_cherrypick(ctxt,
                                                           rebased_c2) == [
                                                               rebased_c1,
                                                               rebased_c2,
                                                           ]