Skip to content

Commit 245924f

Browse files
committed
Allow code owners to self-approve pull requests
This action runs when auto-merge is enabled, and approves the PR using the alibuild user if the submitter of the PR is the code owner of all code in the PR.
1 parent 2afc4cb commit 245924f

File tree

1 file changed

+96
-0
lines changed

1 file changed

+96
-0
lines changed
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
name: Allow code owners to self-approve PRs
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+
- name: Check author is allowed to self-approve
49+
shell: python
50+
env:
51+
submitter: ${{ github.event.sender.login }}
52+
pr: ${{ github.event.pull_request.number }}
53+
repo: ${{ github.event.repository.full_name }}
54+
owners: ${{ steps.owners.outputs.owners }}
55+
github_token: ${{ secrets.ALIBUILD_GITHUB_TOKEN }}
56+
run: |
57+
import functools, github, os
58+
59+
gh = github.Github(os.environ['github_token'])
60+
submitter = os.environ['submitter']
61+
62+
@functools.lru_cache(maxsize=None)
63+
def matches_owner(user_or_team):
64+
user_or_team = user_or_team.lstrip('@')
65+
org, is_team, team_name = user_or_team.partition('/')
66+
if not is_team:
67+
return user_or_team == submitter
68+
try:
69+
gh.get_organization(org) \
70+
.get_team_by_slug(team_name) \
71+
.get_team_membership(submitter)
72+
except github.UnknownObjectException:
73+
return False
74+
return True
75+
76+
def check_lines():
77+
auto_approve = True
78+
# owners is a string containing semicolon-separated records of
79+
# space-separated usernames. At least one username per record must
80+
# match the submitter (taking teams into account), and all lines
81+
# must have a matching username.
82+
for line in os.environ['owners'].strip(';').split(';'):
83+
line_owners = line.split()
84+
assert all(o.startswith('@') for o in line_owners), \
85+
'failed to parse CODEOWNERS'
86+
if not any(map(matches_owner, line_owners)):
87+
print('::warning::Not auto-approving as you are not one of',
88+
', '.join(line_owners))
89+
auto_approve = False
90+
return auto_approve
91+
92+
if check_lines():
93+
gh.get_repo(os.environ['repo']) \
94+
.get_pull(int(os.environ['pr'])) \
95+
.create_review(event='APPROVE',
96+
body=f'Auto-approving on behalf of @{submitter}.')

0 commit comments

Comments
 (0)