コード例 #1
0
def test_failing_checks(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
    check_run: CheckRun,
) -> None:
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiredStatusCheckContexts = ["ci/backend", "wip-app"]
    context.context = "ci/backend"
    context.state = StatusState.SUCCESS
    check_run.name = "wip-app"
    check_run.conclusion = CheckConclusionState.FAILURE
    with pytest.raises(NotQueueable, match="failing required status checks"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[check_run],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #2
0
def test_require_automerge_label_false(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    """
    If the automerge_label is missing, but we have require_automerge_label set
    to false, enqueue the PR for merge
    """
    pull_request.labels = []
    config.merge.automerge_label = "automerge"
    config.merge.require_automerge_label = False
    mergeable(
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        review_requests_count=0,
        reviews=[review],
        contexts=[context],
        check_runs=[],
        valid_signature=False,
        valid_merge_methods=[MergeMethod.merge, MergeMethod.squash],
    )
コード例 #3
0
def test_regression_error_before_update(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    check_run: CheckRun,
) -> None:
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/backend", "wip-app"]
    branch_protection.requiresStrictStatusChecks = True
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    contexts = [StatusContext(context="ci/backend", state=StatusState.SUCCESS)]
    check_run.name = "wip-app"
    check_run.conclusion = CheckConclusionState.SUCCESS
    with pytest.raises(NeedsBranchUpdate):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=1,
            reviews=[review],
            check_runs=[check_run],
            contexts=contexts,
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #4
0
def test_app_id(
    pull_request: PullRequest, config: V1, branch_protection: BranchProtectionRule
) -> None:
    config.app_id = "123"
    with pytest.raises(MissingAppID):
        mergeable(
            app_id="1234",
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[],
        )
    # try without passing an app_id
    with pytest.raises(MissingAppID):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[],
        )
コード例 #5
0
def test_passing_checks(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
    check_run: CheckRun,
) -> None:
    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/backend", "wip-app"]
    context.context = "ci/backend"
    context.state = StatusState.SUCCESS
    check_run.name = "wip-app"
    check_run.conclusion = CheckConclusionState.SUCCESS
    mergeable(
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        review_requests_count=0,
        reviews=[review],
        contexts=[context],
        check_runs=[check_run],
        valid_signature=False,
        valid_merge_methods=[MergeMethod.squash],
    )
コード例 #6
0
def test_dont_update_before_block(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    """
    Regression test for when Kodiak would update a PR that is not mergeable.
    We were raising the NeedsBranchUpdate exception too early.
    """
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresStrictStatusChecks = True
    with pytest.raises(NeedsBranchUpdate):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #7
0
ファイル: pull_request.py プロジェクト: rgeraldporter/kodiak
    async def mergeability(
        self,
        merging: bool = False
    ) -> typing.Tuple[MergeabilityResponse,
                      typing.Optional[EventInfoResponse]]:
        self.log.info("get_event")
        self.event = await self.get_event()
        if self.event is None:
            self.log.info("no event")
            return MergeabilityResponse.NOT_MERGEABLE, None
        if not self.event.head_exists:
            self.log.info("branch deleted")
            return MergeabilityResponse.NOT_MERGEABLE, None
        try:
            self.log.info("check mergeable")
            mergeable(
                config=self.event.config,
                app_id=conf.GITHUB_APP_ID,
                pull_request=self.event.pull_request,
                branch_protection=self.event.branch_protection,
                review_requests_count=self.event.review_requests_count,
                reviews=self.event.reviews,
                contexts=self.event.status_contexts,
                check_runs=self.event.check_runs,
                valid_signature=self.event.valid_signature,
                valid_merge_methods=self.event.valid_merge_methods,
            )
            self.log.info("okay")
            return MergeabilityResponse.OK, self.event
        except (NotQueueable, MergeConflict, BranchMerged) as e:
            if (isinstance(e, MergeConflict)
                    and self.event.config.merge.notify_on_conflict):
                await self.notify_pr_creator()

            if (isinstance(e, BranchMerged)
                    and self.event.config.merge.delete_branch_on_merge):
                await self.client.delete_branch(
                    branch=self.event.pull_request.headRefName)

            await self.set_status(summary="🛑 cannot merge", detail=str(e))
            return MergeabilityResponse.NOT_MERGEABLE, self.event
        except MissingAppID:
            return MergeabilityResponse.NOT_MERGEABLE, self.event
        except MissingGithubMergeabilityState:
            self.log.info("missing mergeability state, need refresh")
            return MergeabilityResponse.NEED_REFRESH, self.event
        except WaitingForChecks:
            if merging:
                await self.set_status(summary="â›´ attempting to merge PR",
                                      detail="waiting for checks")
            return MergeabilityResponse.WAIT, self.event
        except NeedsBranchUpdate:
            if merging:
                await self.set_status(summary="â›´ attempting to merge PR",
                                      detail="updating branch")
            return MergeabilityResponse.NEEDS_UPDATE, self.event
コード例 #8
0
def test_regression_mishandling_multiple_reviews_okay_reviews(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    check_run: CheckRun,
    context: StatusContext,
) -> None:
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND
    branch_protection.requiresApprovingReviews = True
    branch_protection.requiredApprovingReviewCount = 1
    first_review_date = datetime(2010, 5, 15)
    latest_review_date = first_review_date + timedelta(minutes=20)
    reviews = [
        PRReview(
            state=PRReviewState.CHANGES_REQUESTED,
            createdAt=first_review_date,
            author=PRReviewAuthor(login="******"),
            authorAssociation=CommentAuthorAssociation.CONTRIBUTOR,
        ),
        PRReview(
            state=PRReviewState.COMMENTED,
            createdAt=latest_review_date,
            author=PRReviewAuthor(login="******"),
            authorAssociation=CommentAuthorAssociation.CONTRIBUTOR,
        ),
        PRReview(
            state=PRReviewState.APPROVED,
            createdAt=latest_review_date,
            author=PRReviewAuthor(login="******"),
            authorAssociation=CommentAuthorAssociation.CONTRIBUTOR,
        ),
        PRReview(
            state=PRReviewState.APPROVED,
            createdAt=latest_review_date,
            author=PRReviewAuthor(login="******"),
            authorAssociation=CommentAuthorAssociation.CONTRIBUTOR,
        ),
    ]
    with pytest.raises(NeedsBranchUpdate):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=1,
            reviews=reviews,
            check_runs=[check_run],
            contexts=[context],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #9
0
def test_config_merge_optimistic_updates(
    pull_request: PullRequest, config: V1, branch_protection: BranchProtectionRule
) -> None:
    """
    If optimisitc_updates are enabled, branch updates should be prioritized over
    waiting for running status checks to complete.

    Otherwise, status checks should be checked before updating.
    """
    branch_protection.requiredApprovingReviewCount = 0

    branch_protection.requiresStrictStatusChecks = True
    pull_request.mergeStateStatus = MergeStateStatus.BEHIND

    branch_protection.requiresStatusChecks = True
    branch_protection.requiredStatusCheckContexts = ["ci/lint", "ci/test"]
    contexts: List[StatusContext] = []

    config.merge.optimistic_updates = True
    with pytest.raises(NeedsBranchUpdate):
        mergeable(
            app_id="1234",
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=contexts,
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
    config.merge.optimistic_updates = False
    with pytest.raises(WaitingForChecks):
        mergeable(
            app_id="1234",
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=contexts,
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #10
0
def test_unknown_blockage(
    pull_request: PullRequest, config: V1, branch_protection: BranchProtectionRule
) -> None:
    branch_protection.requiredApprovingReviewCount = 0
    branch_protection.requiresStatusChecks = False
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    with pytest.raises(NotQueueable, match="determine why PR is blocked"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #11
0
def test_missing_branch_protection(pull_request: PullRequest, config: V1) -> None:
    """
    We don't want to do anything if branch protection is missing
    """

    branch_protection = None
    with pytest.raises(NotQueueable, match="missing branch protection"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #12
0
def test_passing(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    mergeable(
        config=config,
        pull_request=pull_request,
        branch_protection=branch_protection,
        review_requests_count=0,
        reviews=[review],
        contexts=[context],
        check_runs=[],
        valid_signature=False,
        valid_merge_methods=[MergeMethod.squash],
    )
コード例 #13
0
def test_missing_mergeability_state(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    with pytest.raises(MissingGithubMergeabilityState):
        pull_request.mergeable = MergeableState.UNKNOWN
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #14
0
def test_block_on_reviews_requested(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    config.merge.block_on_reviews_requested = True
    with pytest.raises(NotQueueable, match="reviews requested"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=1,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #15
0
def test_closed(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    with pytest.raises(NotQueueable, match="closed"):
        pull_request.state = PullRequestState.CLOSED
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #16
0
def test_need_update(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    with pytest.raises(NeedsBranchUpdate):
        pull_request.mergeStateStatus = MergeStateStatus.BEHIND
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #17
0
def test_requires_signature(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiresCommitSignatures = True
    with pytest.raises(NotQueueable, match="missing required signature"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #18
0
def test_missing_automerge_label(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    pull_request.labels = ["bug"]
    config.merge.automerge_label = "automerge"
    with pytest.raises(NotQueueable, match="missing automerge_label"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.merge, MergeMethod.squash],
        )
コード例 #19
0
def test_blocking_review(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    review.state = PRReviewState.CHANGES_REQUESTED
    with pytest.raises(NotQueueable, match="blocking review"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #20
0
def test_requires_commit_signatures(
    pull_request: PullRequest, config: V1, branch_protection: BranchProtectionRule
) -> None:
    """
    If requiresCommitSignatures is enabled in branch protections, kodiak cannot
    function because it cannot create a signed commit to merge the PR.
    """
    branch_protection.requiresCommitSignatures = True
    with pytest.raises(NotQueueable, match='"Require signed commits" not supported.'):
        mergeable(
            app_id="1234",
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #21
0
def test_missing_required_context(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    pull_request.mergeStateStatus = MergeStateStatus.BLOCKED
    branch_protection.requiredStatusCheckContexts = ["ci/backend", "ci/frontend"]
    context.context = "ci/backend"
    with pytest.raises(WaitingForChecks, match="missing required status checks"):
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #22
0
def test_blacklist_title_match(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    # a PR with a blacklisted title should not be mergeable
    with pytest.raises(NotQueueable, match="blacklist_title") as e_info:
        config.merge.blacklist_title_regex = "^WIP:.*"
        pull_request.title = "WIP: add fleeb to plumbus"
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.merge, MergeMethod.squash],
        )
    assert config.merge.blacklist_title_regex in str(e_info.value)
コード例 #23
0
def test_blacklisted(
    pull_request: PullRequest,
    config: V1,
    branch_protection: BranchProtectionRule,
    review: PRReview,
    context: StatusContext,
) -> None:
    # a PR with a blacklisted label should not be mergeable
    with pytest.raises(NotQueueable, match="blacklist"):
        pull_request.labels = ["automerge", "dont-merge"]
        config.merge.automerge_label = "automerge"
        config.merge.blacklist_labels = ["dont-merge"]
        mergeable(
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[review],
            contexts=[context],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.merge, MergeMethod.squash],
        )
コード例 #24
0
def test_merge_state_status_draft(
    pull_request: PullRequest, config: V1, branch_protection: BranchProtectionRule
) -> None:
    """
    If optimisitc_updates are enabled, branch updates should be prioritized over
    waiting for running status checks to complete.

    Otherwise, status checks should be checked before updating.
    """
    pull_request.mergeStateStatus = MergeStateStatus.DRAFT

    with pytest.raises(NotQueueable, match="draft state"):
        mergeable(
            app_id="1234",
            config=config,
            pull_request=pull_request,
            branch_protection=branch_protection,
            review_requests_count=0,
            reviews=[],
            contexts=[],
            check_runs=[],
            valid_signature=False,
            valid_merge_methods=[MergeMethod.squash],
        )
コード例 #25
0
ファイル: pull_request.py プロジェクト: wuub/kodiak
async def evaluate_pr(
    install: str,
    owner: str,
    repo: str,
    number: int,
    merging: bool,
    dequeue_callback: Callable[[], Awaitable[None]],
    requeue_callback: Callable[[], Awaitable[None]],
    queue_for_merge_callback: QueueForMergeCallback,
    is_active_merging: bool,
) -> None:
    skippable_check_timeout = 4
    api_call_retries_remaining = 5
    api_call_errors = []  # type: List[APICallError]
    log = logger.bind(install=install, owner=owner, repo=repo, number=number)
    while True:
        log.info("get_pr")
        try:
            pr = await asyncio.wait_for(
                get_pr(
                    install=install,
                    owner=owner,
                    repo=repo,
                    number=number,
                    dequeue_callback=dequeue_callback,
                    requeue_callback=requeue_callback,
                    queue_for_merge_callback=queue_for_merge_callback,
                ),
                timeout=30,
            )
            if pr is None:
                log.info("failed to get_pr")
                return
            try:
                await asyncio.wait_for(
                    mergeable(
                        api=pr,
                        subscription=pr.event.subscription,
                        config=pr.event.config,
                        config_str=pr.event.config_str,
                        config_path=pr.event.config_file_expression,
                        app_id=conf.GITHUB_APP_ID,
                        repository=pr.event.repository,
                        pull_request=pr.event.pull_request,
                        branch_protection=pr.event.branch_protection,
                        review_requests=pr.event.review_requests,
                        reviews=pr.event.reviews,
                        contexts=pr.event.status_contexts,
                        check_runs=pr.event.check_runs,
                        commits=pr.event.commits,
                        valid_signature=pr.event.valid_signature,
                        valid_merge_methods=pr.event.valid_merge_methods,
                        merging=merging,
                        is_active_merge=is_active_merging,
                        skippable_check_timeout=skippable_check_timeout,
                        api_call_errors=api_call_errors,
                        api_call_retries_remaining=api_call_retries_remaining,
                    ),
                    timeout=30,
                )
                log.info("evaluate_pr successful")
            except RetryForSkippableChecks:
                if skippable_check_timeout > 0:
                    skippable_check_timeout -= 1
                    log.info("waiting for skippable checks to pass")
                    await asyncio.sleep(RETRY_RATE_SECONDS)
                    continue
            except PollForever:
                log.info("polling")
                await asyncio.sleep(POLL_RATE_SECONDS)
                continue
            except ApiCallException as e:
                # if we have some api exception, it's likely a temporary error that
                # can be resolved by calling GitHub again.
                if api_call_retries_remaining:
                    api_call_errors.append(
                        APICallError(
                            api_name=e.method,
                            http_status=str(e.status_code),
                            response_body=str(e.response),
                        )
                    )
                    api_call_retries_remaining -= 1
                    log.info("problem contacting remote api. retrying")
                    continue
                log.exception("api_call_retries_remaining")
            return
        except asyncio.TimeoutError:
            # On timeout we add the PR to the back of the queue to try again.
            log.exception("mergeable_timeout")
            await requeue_callback()
コード例 #26
0
ファイル: pull_request.py プロジェクト: appscodelabs/kodiak
async def evaluate_pr(
    install: str,
    owner: str,
    repo: str,
    number: int,
    merging: bool,
    dequeue_callback: Callable[[], Awaitable],
    requeue_callback: Callable[[], Awaitable],
    queue_for_merge_callback: Callable[[], Awaitable[Optional[int]]],
    is_active_merging: bool,
) -> None:
    skippable_check_timeout = 4
    api_call_retry_timeout = 5
    api_call_retry_method_name: Optional[str] = None
    log = logger.bind(install=install, owner=owner, repo=repo, number=number)
    while True:
        log.info("get_pr")
        pr = await asyncio.wait_for(
            get_pr(
                install=install,
                owner=owner,
                repo=repo,
                number=number,
                dequeue_callback=dequeue_callback,
                requeue_callback=requeue_callback,
                queue_for_merge_callback=queue_for_merge_callback,
            ),
            timeout=10,
        )
        if pr is None:
            log.info("failed to get_pr")
            return
        try:
            await asyncio.wait_for(
                mergeable(
                    api=pr,
                    subscription=pr.event.subscription,
                    config=pr.event.config,
                    config_str=pr.event.config_str,
                    config_path=pr.event.config_file_expression,
                    app_id=conf.GITHUB_APP_ID,
                    repository=pr.event.repository,
                    pull_request=pr.event.pull_request,
                    branch_protection=pr.event.branch_protection,
                    review_requests=pr.event.review_requests,
                    reviews=pr.event.reviews,
                    contexts=pr.event.status_contexts,
                    check_runs=pr.event.check_runs,
                    commit_authors=pr.event.commit_authors,
                    valid_signature=pr.event.valid_signature,
                    valid_merge_methods=pr.event.valid_merge_methods,
                    merging=merging,
                    is_active_merge=is_active_merging,
                    skippable_check_timeout=skippable_check_timeout,
                    api_call_retry_timeout=api_call_retry_timeout,
                    api_call_retry_method_name=api_call_retry_method_name,
                ),
                timeout=10,
            )
            log.info("evaluate_pr successful")
        except RetryForSkippableChecks:
            if skippable_check_timeout > 0:
                skippable_check_timeout -= 1
                log.info("waiting for skippable checks to pass")
                await asyncio.sleep(RETRY_RATE_SECONDS)
                continue
        except PollForever:
            log.info("polling")
            await asyncio.sleep(POLL_RATE_SECONDS)
            continue
        except ApiCallException as e:
            # if we have some api exception, it's likely a temporary error that
            # can be resolved by calling GitHub again.
            if api_call_retry_timeout:
                api_call_retry_method_name = e.method
                api_call_retry_timeout -= 1
                log.info("problem contacting remote api. retrying")
                continue
            log.exception("api_call_retry_timeout")
        return
コード例 #27
0
ファイル: pull_request.py プロジェクト: jpalmieri/kodiak
    async def mergeability(
        self,
        merging: bool = False
    ) -> Tuple[MergeabilityResponse, Optional[EventInfoResponse]]:
        self.log.info("get_event")
        self.event = await self.get_event()
        if self.event is None:
            self.log.info("no event")
            return MergeabilityResponse.NOT_MERGEABLE, None
        # PRs from forks will always appear deleted because the v4 api doesn't
        # return head information for forks like the v3 api does.
        if not self.event.pull_request.isCrossRepository and not self.event.head_exists:
            self.log.info("branch deleted")
            return MergeabilityResponse.NOT_MERGEABLE, None
        if not isinstance(self.event.config, V1):

            await self.set_status(
                "🚨 Invalid configuration",
                detail='Click "Details" for more info.',
                markdown_content=get_markdown_for_config(
                    self.event.config,
                    self.event.config_str,
                    self.event.config_file_expression,
                ),
            )
            return MergeabilityResponse.NOT_MERGEABLE, None
        try:
            self.log.info("check mergeable")
            mergeable(
                config=self.event.config,
                app_id=conf.GITHUB_APP_ID,
                pull_request=self.event.pull_request,
                branch_protection=self.event.branch_protection,
                review_requests=self.event.review_requests,
                reviews=self.event.reviews,
                contexts=self.event.status_contexts,
                check_runs=self.event.check_runs,
                valid_signature=self.event.valid_signature,
                valid_merge_methods=self.event.valid_merge_methods,
            )
            self.log.info("okay")
            return MergeabilityResponse.OK, self.event
        except MissingSkippableChecks as e:
            self.log.info("skippable checks", checks=e.checks)
            await self.set_status(
                summary="🛑 not waiting for dont_wait_on_status_checks",
                detail=repr(e.checks),
            )
            return MergeabilityResponse.SKIPPABLE_CHECKS, self.event
        except (NotQueueable, MergeConflict, BranchMerged) as e:
            if (isinstance(e, MergeConflict)
                    and self.event.config.merge.notify_on_conflict):
                await self.notify_pr_creator()

            if (isinstance(e, BranchMerged)
                    and self.event.config.merge.delete_branch_on_merge):
                await self.client.delete_branch(
                    branch=self.event.pull_request.headRefName)

            await self.set_status(summary="🛑 cannot merge", detail=str(e))
            return MergeabilityResponse.NOT_MERGEABLE, self.event
        except MergeBlocked as e:
            await self.set_status(summary=f"🛑 {e}")
            return MergeabilityResponse.NOT_MERGEABLE, self.event
        except MissingAppID:
            return MergeabilityResponse.NOT_MERGEABLE, self.event
        except MissingGithubMergeabilityState:
            self.log.info("missing mergeability state, need refresh")
            return MergeabilityResponse.NEED_REFRESH, self.event
        except WaitingForChecks as e:
            if merging:
                await self.set_status(
                    summary="â›´ attempting to merge PR",
                    detail=f"waiting for checks: {e.checks!r}",
                )
            return MergeabilityResponse.WAIT, self.event
        except NeedsBranchUpdate:
            if self.event.pull_request.isCrossRepository:
                await self.set_status(
                    summary=
                    '🚨 forks cannot be updated via the github api. Click "Details" for more info',
                    markdown_content=messages.FORKS_CANNOT_BE_UPDATED,
                )
                return MergeabilityResponse.NOT_MERGEABLE, self.event
            if merging:
                await self.set_status(summary="â›´ attempting to merge PR",
                                      detail="updating branch")
            return MergeabilityResponse.NEEDS_UPDATE, self.event