-
Notifications
You must be signed in to change notification settings - Fork 5.5k
162 lines (139 loc) · 5.7 KB
/
Copy pathcheck_approvals.yml
File metadata and controls
162 lines (139 loc) · 5.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
name: Check Approvals
on:
pull_request:
types: [opened, synchronize, ready_for_review]
pull_request_review:
types: [submitted]
jobs:
check_owners:
name: Check OWNERS approval
runs-on: ubuntu-latest
# Skip draft PRs.
if: github.event.pull_request.draft == false && github.repository_owner == 'warpdotdev'
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
fetch-depth: 0
# We don't actually need the contents of the files, just their names.
filter: blob:none
- name: Get changed files
id: changed_files
run: |
HEAD_SHA=${{ github.event.pull_request.head.sha }}
BASE_SHA=${{ github.event.pull_request.base.sha }}
MERGE_BASE=$(git merge-base "$BASE_SHA" "$HEAD_SHA")
CHANGED_FILES=$(git diff --name-only "$MERGE_BASE" "$HEAD_SHA" | tr '\n' ' ')
echo "files=$CHANGED_FILES" >> "$GITHUB_OUTPUT"
- name: Get PR approvers
id: approvers
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
PR_AUTHOR=${{ github.event.pull_request.user.login }}
# Get all approved reviews.
REVIEWERS=$(gh pr view "$PR_NUMBER" --json reviews --jq '[.reviews[] | select(.state == "APPROVED") | .author.login] | unique | join(" ")')
# Combine PR author (self-approves) with reviewers.
ALL_APPROVERS="$PR_AUTHOR $REVIEWERS"
echo "approvers=$ALL_APPROVERS" >> "$GITHUB_OUTPUT"
echo "has_reviewers=$( [[ -n "$REVIEWERS" ]] && echo true || echo false )" >> "$GITHUB_OUTPUT"
echo "PR author: $PR_AUTHOR"
echo "Reviewers who approved: $REVIEWERS"
echo "All approvers: $ALL_APPROVERS"
- name: Check OWNERS approval
env:
CHANGED_FILES: ${{ steps.changed_files.outputs.files }}
APPROVERS: ${{ steps.approvers.outputs.approvers }}
run: |
set -e
# Convert approvers to an array.
read -ra APPROVER_ARRAY <<< "$APPROVERS"
# Function to get owners for a file by walking up the directory tree.
get_owners_for_file() {
local file="$1"
local dir
dir=$(dirname "$file")
local owners=()
# Walk up the directory tree.
while [[ "$dir" != "." && "$dir" != "/" ]]; do
if [[ -f "$dir/OWNERS" ]]; then
# Read owners file, skip comments and empty lines.
while IFS= read -r line || [[ -n "$line" ]]; do
# Skip comments and empty lines.
line=$(echo "$line" | sed 's/#.*//' | xargs)
if [[ -n "$line" ]]; then
owners+=("$line")
fi
done < "$dir/OWNERS"
fi
dir=$(dirname "$dir")
done
# Check root directory.
if [[ -f "OWNERS" ]]; then
while IFS= read -r line || [[ -n "$line" ]]; do
line=$(echo "$line" | sed 's/#.*//' | xargs)
if [[ -n "$line" ]]; then
owners+=("$line")
fi
done < "OWNERS"
fi
# Return unique owners.
printf '%s\n' "${owners[@]}" | sort -u | tr '\n' ' ' | sed 's/ $//'
}
# Function to check if any approver is in the owners list.
has_owner_approval() {
local owners="$1"
for approver in "${APPROVER_ARRAY[@]}"; do
if echo "$owners" | grep -qw "$approver"; then
return 0
fi
done
return 1
}
MISSING_APPROVAL=()
FILES_CHECKED=0
for file in $CHANGED_FILES; do
# Skip if file doesn't exist (deleted files).
FILES_CHECKED=$((FILES_CHECKED + 1))
OWNERS=$(get_owners_for_file "$file")
if [[ -z "$OWNERS" ]]; then
# No OWNERS files in ancestry - automatically passes.
echo "✓ $file (no OWNERS requirement)"
continue
fi
if has_owner_approval "$OWNERS"; then
echo "✓ $file"
else
echo "✗ $file (owners: $OWNERS)"
MISSING_APPROVAL+=("$file")
fi
done
echo ""
echo "Files checked: $FILES_CHECKED"
echo "Approvers: ${APPROVER_ARRAY[*]}"
# Save missing files for comment step (must happen before exit).
echo "MISSING_FILES<<EOF" >> "$GITHUB_ENV"
for file in "${MISSING_APPROVAL[@]}"; do
OWNERS=$(get_owners_for_file "$file")
echo "- \`$file\` (needs approval from: $OWNERS)" >> "$GITHUB_ENV"
done
echo "EOF" >> "$GITHUB_ENV"
if [[ ${#MISSING_APPROVAL[@]} -gt 0 ]]; then
echo ""
echo "::error::The following files are missing OWNERS approval:"
for file in "${MISSING_APPROVAL[@]}"; do
OWNERS=$(get_owners_for_file "$file")
echo "::error:: - $file (needs approval from: $OWNERS)"
done
exit 1
fi
echo ""
echo "All files have required OWNERS approval."
- name: Comment on PR about missing approvals
if: failure() && github.event_name == 'pull_request_review' && steps.approvers.outputs.has_reviewers == 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
PR_NUMBER=${{ github.event.pull_request.number }}
gh pr comment "$PR_NUMBER" --body "The following files still need approval from their OWNERS:\n\n$MISSING_FILES"