|
| 1 | +name: PR self-approval |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + types: |
| 6 | + - auto_merge_enabled |
| 7 | + |
| 8 | +jobs: |
| 9 | + approve: |
| 10 | + runs-on: ubuntu-latest |
| 11 | + |
| 12 | + steps: |
| 13 | + # Check out in order to get CODEOWNERS. |
| 14 | + - uses: actions/checkout@v2 |
| 15 | + with: |
| 16 | + # Always use the latest CODEOWNERS file, *not* the one from the PR! |
| 17 | + ref: ${{ github.event.repository.default_branch }} |
| 18 | + persist-credentials: false |
| 19 | + |
| 20 | + - name: Install dependencies |
| 21 | + run: pip install PyGithub |
| 22 | + |
| 23 | + # First, parse filename patterns in CODEOWNERS using git-check-ignore(1) |
| 24 | + # to get the applicable code owners for this PR. |
| 25 | + - name: Parse CODEOWNERS |
| 26 | + id: owners |
| 27 | + shell: bash -exo pipefail {0} |
| 28 | + run: | |
| 29 | + git config --global init.defaultBranch master # avoid annoying warning |
| 30 | + git init ../tmp # temporary repo for .gitignore parsing |
| 31 | + awk '!/^[[:space:]]*(#|$)/ {print $1}' CODEOWNERS > ../tmp/.gitignore |
| 32 | + # Find changed files in PR and which CODEOWNERS entries match them. |
| 33 | + curl -fsSL "${{ github.event.pull_request.diff_url }}" | |
| 34 | + sed -rn 's,^diff --git a/(.*) b/(.*)$,\1\n\2,p' | uniq | |
| 35 | + git -C ../tmp check-ignore -v --no-index --stdin | |
| 36 | + cut -d: -f2 | |
| 37 | + sed 's/$/p/' > extract-lines.sed |
| 38 | + # Extract user names applicable to this PR. Same format as CODEOWNERS, |
| 39 | + # but without the leading pattern, so we just have usernames separated |
| 40 | + # by spaces and newlines. |
| 41 | + echo "::set-output name=owners::$( |
| 42 | + grep -vE '^[[:space:]]*(#|$)' CODEOWNERS | |
| 43 | + sed -nf extract-lines.sed | |
| 44 | + sed 's/^[^[:space:]]*[[:space:]]*//' | |
| 45 | + tr '\n' ';' |
| 46 | + )" |
| 47 | +
|
| 48 | + # Finally, approve, if the author is only editing files owned by themselves. |
| 49 | + - name: Check author is allowed to self-approve |
| 50 | + shell: python |
| 51 | + env: |
| 52 | + submitter: ${{ github.event.sender.login }} |
| 53 | + pr: ${{ github.event.pull_request.number }} |
| 54 | + repo: ${{ github.event.repository.full_name }} |
| 55 | + owners: ${{ steps.owners.outputs.owners }} |
| 56 | + github_token: ${{ secrets.ALIBUILD_GITHUB_TOKEN }} |
| 57 | + run: | |
| 58 | + import functools, github, os |
| 59 | +
|
| 60 | + gh = github.Github(os.environ['github_token']) |
| 61 | + submitter = os.environ['submitter'] |
| 62 | +
|
| 63 | + @functools.lru_cache(maxsize=None) |
| 64 | + def matches_owner(user_or_team): |
| 65 | + user_or_team = user_or_team.lstrip('@') |
| 66 | + org, is_team, team_name = user_or_team.partition('/') |
| 67 | + if not is_team: |
| 68 | + return user_or_team == submitter |
| 69 | + try: |
| 70 | + gh.get_organization(org) \ |
| 71 | + .get_team_by_slug(team_name) \ |
| 72 | + .get_team_membership(submitter) |
| 73 | + except github.UnknownObjectException: |
| 74 | + return False |
| 75 | + return True |
| 76 | +
|
| 77 | + def check_lines(): |
| 78 | + auto_approve = True |
| 79 | + # owners is a string containing semicolon-separated records of |
| 80 | + # space-separated usernames. At least one username per record must |
| 81 | + # match the submitter (taking teams into account), and all lines |
| 82 | + # must have a matching username. |
| 83 | + for line in os.environ['owners'].strip(';').split(';'): |
| 84 | + line_owners = line.split() |
| 85 | + assert all(o.startswith('@') for o in line_owners), \ |
| 86 | + 'failed to parse CODEOWNERS' |
| 87 | + if not any(map(matches_owner, line_owners)): |
| 88 | + print('::warning::Not auto-approving as you are not one of', |
| 89 | + ', '.join(line_owners)) |
| 90 | + auto_approve = False |
| 91 | + return auto_approve |
| 92 | +
|
| 93 | + if check_lines(): |
| 94 | + gh.get_repo(os.environ['repo']) \ |
| 95 | + .get_pull(int(os.environ['pr'])) \ |
| 96 | + .create_review(event='APPROVE', |
| 97 | + body=f'Auto-approving on behalf of @{submitter}.') |
0 commit comments