Ejemplo n.º 1
0
 def execute_review_protection(
         change: Change[str], branch: Branch,
         existing_protection: Optional[BranchProtection],
         review_count: int) -> Change[str]:
     try:
         if branch.protected and existing_protection and existing_protection.required_pull_request_reviews:
             if review_count > 0:
                 print_debug(
                     "Replacing review protection on branch %s (%s reviews)"
                     % (highlight(branch.name), str(review_count)))
                 branch.edit_required_pull_request_reviews(
                     required_approving_review_count=review_count)
             else:
                 print_debug("Removing review protection on branch: %s" %
                             highlight(branch.name))
                 branch.remove_required_pull_request_reviews()
         elif review_count > 0:
             print_debug(
                 "Adding review protection on branch: %s (%s reviews)" %
                 (highlight(branch.name), str(review_count)))
             safe_branch_edit_protection(
                 branch, required_approving_review_count=review_count)
     except GithubException as e:
         print_error("Can't set review protection on branch %s to %s: %s" %
                     (highlight(branch.name), str(review_count), str(e)))
         return change.failure()
     return change.success()
Ejemplo n.º 2
0
 def execute_dismiss_reviews(
         change: Change[str], branch: Branch,
         required_reviews: Optional[RequiredPullRequestReviews],
         dismiss_approvals: bool) -> Change[str]:
     try:
         if branch.protected and required_reviews:
             print_debug(
                 "Setting already protected branch %s to %s stale reviews" %
                 (highlight(branch.name),
                  highlight("dismiss" if dismiss_approvals else "allow")))
             branch.edit_required_pull_request_reviews(
                 dismiss_stale_reviews=dismiss_approvals)
         else:
             print_debug(
                 "Changing branch %s to %s stale reviews" %
                 (highlight(branch.name),
                  highlight("dismiss" if dismiss_approvals else "allow")))
             safe_branch_edit_protection(
                 branch, dismiss_stale_reviews=dismiss_approvals)
     except GithubException as e:
         print_error(
             "Can't set review dismissal on branch %s to %s: %s" %
             (highlight(branch.name), str(dismiss_approvals), str(e)))
         return change.failure()
     return change.success()
Ejemplo n.º 3
0
def safe_branch_edit_protection(
        branch: Branch,
        strict: _GithubOptional[bool] = NotSet,
        contexts: _GithubOptional[List[str]] = NotSet,
        enforce_admins: _GithubOptional[bool] = NotSet,
        dismissal_users: _GithubOptional[List[str]] = NotSet,
        dismissal_teams: _GithubOptional[List[str]] = NotSet,
        dismiss_stale_reviews: _GithubOptional[bool] = NotSet,
        require_code_owner_reviews: _GithubOptional[bool] = NotSet,
        required_approving_review_count: _GithubOptional[int] = NotSet,
        user_push_restrictions: _GithubOptional[List[str]] = NotSet,
        team_push_restrictions: _GithubOptional[List[str]] = NotSet) -> None:
    try:
        prot = branch.get_protection()
    except GithubException as e:
        prot = None

    rsc = prot.required_status_checks if prot else None  # type: RequiredStatusChecks
    rpr = prot.required_pull_request_reviews if prot else None  # type: RequiredPullRequestReviews
    protupr = prot.get_user_push_restrictions() if prot else None
    if protupr is None:
        upr = NotSet
    else:
        upr = [u.login for u in protupr]
    prottpr = prot.get_team_push_restrictions() if prot else None
    if prottpr is None:
        tpr = NotSet
    else:
        tpr = [t.name for t in prottpr]

    kw = {
        'strict':
        strict if strict != NotSet else (rsc.strict if rsc else NotSet),
        'contexts':
        contexts if contexts != NotSet else (rsc.contexts if rsc else NotSet),
        'enforce_admins':
        enforce_admins if enforce_admins != NotSet else
        (prot.enforce_admins if prot else NotSet),
        'dismissal_users':
        dismissal_users if dismissal_users != NotSet else [],
        'dismissal_teams':
        dismissal_teams if dismissal_teams != NotSet else [],
        'dismiss_stale_reviews':
        dismiss_stale_reviews if dismiss_stale_reviews != NotSet else
        (rpr.dismiss_stale_reviews if rpr is not None else NotSet),
        'require_code_owner_reviews':
        require_code_owner_reviews if require_code_owner_reviews != NotSet else
        (rpr.require_code_owner_reviews if rpr is not None else NotSet),
        'required_approving_review_count':
        required_approving_review_count
        if required_approving_review_count != NotSet else
        (rpr.required_approving_review_count if rpr is not None else NotSet),
        'user_push_restrictions':
        user_push_restrictions if user_push_restrictions != NotSet else upr,
        'team_push_restrictions':
        team_push_restrictions if team_push_restrictions != NotSet else tpr,
    }
    branch.edit_protection(**kw)
Ejemplo n.º 4
0
 def execute_remove_all_status_checks(
         change: Change[str], branch: Branch,
         existing_checks: Set[str]) -> Change[str]:
     print_debug("Removing all status checks from branch %s" %
                 highlight(branch.name))
     try:
         if existing_checks:
             branch.remove_required_status_checks()
     except GithubException as e:
         print_error(str(e))
         return change.failure()
     else:
         return change.success()
Ejemplo n.º 5
0
def _set_dismiss_stale_approvals(branch: Branch,
                                 dismiss_approvals: bool = True
                                 ) -> List[Change[str]]:
    def execute_dismiss_reviews(
            change: Change[str], branch: Branch,
            required_reviews: Optional[RequiredPullRequestReviews],
            dismiss_approvals: bool) -> Change[str]:
        try:
            if branch.protected and required_reviews:
                print_debug(
                    "Setting already protected branch %s to %s stale reviews" %
                    (highlight(branch.name),
                     highlight("dismiss" if dismiss_approvals else "allow")))
                branch.edit_required_pull_request_reviews(
                    dismiss_stale_reviews=dismiss_approvals)
            else:
                print_debug(
                    "Changing branch %s to %s stale reviews" %
                    (highlight(branch.name),
                     highlight("dismiss" if dismiss_approvals else "allow")))
                safe_branch_edit_protection(
                    branch, dismiss_stale_reviews=dismiss_approvals)
        except GithubException as e:
            print_error(
                "Can't set review dismissal on branch %s to %s: %s" %
                (highlight(branch.name), str(dismiss_approvals), str(e)))
            return change.failure()
        return change.success()

    change_needed = False
    rpr = None  # type: Optional[RequiredPullRequestReviews]

    if branch.protected:
        prot = branch.get_protection()
        rpr = prot.required_pull_request_reviews
        if rpr.dismiss_stale_reviews == dismiss_approvals:
            print_debug(
                "Branch %s already %s stale reviews" %
                (highlight(branch.name),
                 highlight("dismisses" if dismiss_approvals else "allows")))
            change_needed = False
        else:
            change_needed = True
    else:
        change_needed = True

    if change_needed:
        change = Change(meta=ChangeMetadata(
            executor=execute_dismiss_reviews,
            params=[branch, rpr, dismiss_approvals]),
                        action=ChangeActions.REPLACE
                        if branch.protected else ChangeActions.ADD,
                        before="%s stale reviews" %
                        ("Allow" if dismiss_approvals else "Dismiss"),
                        after="%s stale reviews" %
                        ("Dismiss" if dismiss_approvals else "Allow"),
                        cosmetic_prefix="Protect branch<%s>" % branch.name)
        return [change]
    return []
Ejemplo n.º 6
0
 def execute_test_protection(change: Change[str], branch: Branch,
                             existing_checks: Set[str],
                             known_checks: Set[str]) -> Change[str]:
     print_debug("[%s] Changing status checks on branch '%s' to [%s]" %
                 (highlight(repo.name), highlight(branch.name),
                  highlight(", ".join(list(known_checks)))))
     try:
         if existing_checks:
             branch.edit_required_status_checks(strict=True,
                                                contexts=list(known_checks))
         else:
             safe_branch_edit_protection(
                 branch,
                 strict=True,
                 contexts=list(known_checks),
             )
     except GithubException as e:
         print_error(
             "Can't edit required status checks on repo %s branch %s: %s" %
             (repo.name, branch.name, str(e)))
         return change.failure()
     return change.success()
Ejemplo n.º 7
0
def create_plans(
    stash_api,
    github_api,
    stash_projects,
    github_organizations,
    branch_text,
    *,
    exactly_branch_name=False,
    assure_has_prs=True,
):
    """
    Go over all the branches in all Stash and GitHub repositories searching for branches and PRs that match the given branch text.

    :rtype: List[MergePlan]
    """
    # Plans for Stash repos:
    stash_repos = []
    for project in stash_projects:
        stash_repos += stash_api.fetch_repos(project)

    plans = []
    has_prs = False
    for repo in stash_repos:
        slug = repo["slug"]
        project = repo["project"]["key"]
        branches = list(
            Branch(b["id"], b["displayId"], b["latestCommit"])
            for b in stash_api.fetch_branches(project, slug, branch_text)
        )
        if exactly_branch_name:
            branches = [
                branch for branch in branches if branch.display_id == branch_text
            ]
        if branches:
            plan = MergePlan(project, slug)
            plans.append(plan)
            plan.branches = branches
            branch_ids = [x.branch_id for x in plan.branches]
            prs = list(stash_api.fetch_pull_requests(project, slug))
            for pr in prs:
                if pr["fromRef"]["id"] in branch_ids:
                    has_prs = True
                    plan.pull_requests.append(pr)
            if plan.pull_requests:
                plan.to_branch = plan.pull_requests[0]["toRef"]["id"]

    # Plans for Github repos:
    github_branch_text = branch_text
    if len(plans) > 0 and len(plans[0].branches) > 0:
        # if we already found the branch name on Stash, we can use its name here
        github_branch_text = plans[0].branches[0].display_id

    organization_to_repos = defaultdict(list)
    for organization in github_organizations:
        organization_to_repos[organization] += github_api.fetch_repos(organization)

    futures = dict()
    for organization in organization_to_repos.keys():
        with ThreadPoolExecutor(max_workers=16) as executor:
            for repo in organization_to_repos[organization]:
                repo_name = repo.name
                f = executor.submit(
                    github_api.fetch_branches,
                    organization,
                    repo_name,
                    branch_name=github_branch_text,
                )

                futures[f] = repo_name

            for f in as_completed(futures.keys()):
                repo_name = futures[f]
                branches = f.result()
                if not branches:
                    continue

                plan = MergePlan(organization, repo_name, comes_from_github=True)
                plans.append(plan)
                # b.name is passed twice because github uses the same `id` and `display_id`
                plan.branches = [Branch(b.name, b.name, b.commit.sha) for b in branches]

                prs = github_api.fetch_pull_requests(organization, repo_name)

                for pr in prs:
                    if pr.head.ref == github_branch_text:
                        has_prs = True
                        plan.pull_requests.append(pr)
                if plan.pull_requests:
                    plan.to_branch = "refs/heads/{}".format(
                        plan.pull_requests[0].base.ref
                    )

    if not plans:
        raise CheckError(
            'Could not find any branch with text `"{}"` in any repositories of Stash projects: {} nor '
            "Github organizations: {}.".format(
                branch_text,
                ", ".join("`{}`".format(x) for x in stash_projects),
                ", ".join("`{}`".format(x) for x in github_organizations),
            )
        )

    if assure_has_prs and not has_prs:
        raise CheckError('No PRs are open with text `"{}"`'.format(branch_text))
    return plans
Ejemplo n.º 8
0
def _protect_branch(branch: Branch,
                    required_review_count: int) -> List[Change[str]]:
    def execute_review_protection(
            change: Change[str], branch: Branch,
            existing_protection: Optional[BranchProtection],
            review_count: int) -> Change[str]:
        try:
            if branch.protected and existing_protection and existing_protection.required_pull_request_reviews:
                if review_count > 0:
                    print_debug(
                        "Replacing review protection on branch %s (%s reviews)"
                        % (highlight(branch.name), str(review_count)))
                    branch.edit_required_pull_request_reviews(
                        required_approving_review_count=review_count)
                else:
                    print_debug("Removing review protection on branch: %s" %
                                highlight(branch.name))
                    branch.remove_required_pull_request_reviews()
            elif review_count > 0:
                print_debug(
                    "Adding review protection on branch: %s (%s reviews)" %
                    (highlight(branch.name), str(review_count)))
                safe_branch_edit_protection(
                    branch, required_approving_review_count=review_count)
        except GithubException as e:
            print_error("Can't set review protection on branch %s to %s: %s" %
                        (highlight(branch.name), str(review_count), str(e)))
            return change.failure()
        return change.success()

    change_needed = False
    prot = None
    current_reqcount = 0

    # The Github API will gladly return a required review count > 0 for a branch that had a required review
    # count previously, but it has now been turned off. So we need to correlate a bunch of information to find
    # out whether the branch actually requires reviews or not.
    if branch.protected:
        prot = branch.get_protection()
        if prot and prot.required_pull_request_reviews:
            rpr = prot.required_pull_request_reviews  # type: RequiredPullRequestReviews
            if rpr.required_approving_review_count == required_review_count:
                print_debug(
                    "Branch %s already requires %s reviews" % (highlight(
                        branch.name), highlight(str(required_review_count))))
                change_needed = False
            else:
                current_reqcount = rpr.required_approving_review_count
                change_needed = True
        else:
            if required_review_count == 0 and (
                    prot is None
                    or prot.required_pull_request_reviews is None):
                print_debug(
                    "Branch %s required no review and requested count is %s" %
                    (highlight(branch.name), highlight("zero")))
                change_needed = False
            else:
                change_needed = True
    else:
        change_needed = True

    if change_needed:
        change = Change(
            meta=ChangeMetadata(executor=execute_review_protection,
                                params=[branch, prot, required_review_count]),
            action=ChangeActions.REPLACE
            if branch.protected else ChangeActions.ADD,
            before="Require %s reviews" %
            current_reqcount if branch.protected else "No protection",
            after="Require %s reviews" % required_review_count,
            cosmetic_prefix="Protect branch<%s>:" % branch.name)
        return [change]
    return []