forked from civicrm/civicrm-core
-
Notifications
You must be signed in to change notification settings - Fork 1
283 lines (240 loc) · 13.1 KB
/
Copy pathpr-commands.yml
File metadata and controls
283 lines (240 loc) · 13.1 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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
name: PR Commands
on:
issue_comment:
types: [created]
jobs:
pr_commands:
name: Execute PR Command
if: |
github.event.issue.pull_request != null &&
(startsWith(github.event.comment.body, '/squash') || startsWith(github.event.comment.body, '/rebase') || startsWith(github.event.comment.body, '/lintroll') || startsWith(github.event.comment.body, '/port') || startsWith(github.event.comment.body, '/SQUASH') || startsWith(github.event.comment.body, '/REBASE') || startsWith(github.event.comment.body, '/LINTROLL') || startsWith(github.event.comment.body, '/PORT'))
runs-on: ubuntu-latest
permissions:
pull-requests: write
issues: write
contents: write
steps:
- name: Check Authorization
id: auth
run: |
COMMENT_AUTHOR="${{ github.event.comment.user.login }}"
PR_AUTHOR="${{ github.event.issue.user.login }}"
AUTHOR_ASSOCIATION="${{ github.event.comment.author_association }}"
IS_AUTHORIZED=false
if [ "$COMMENT_AUTHOR" = "$PR_AUTHOR" ]; then
IS_AUTHORIZED=true
elif [[ "$AUTHOR_ASSOCIATION" =~ ^(OWNER|MEMBER|COLLABORATOR)$ ]]; then
IS_AUTHORIZED=true
fi
if [ "$IS_AUTHORIZED" = "false" ]; then
echo "Error: User $COMMENT_AUTHOR is not authorized to run commands on this PR."
exit 1
fi
- name: Generate GitHub App Token
id: generate-token
if: ${{ secrets.CIVIBOT_APP_ID != '' }}
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.CIVIBOT_APP_ID }}
private-key: ${{ secrets.CIVIBOT_PRIVATE_KEY }}
- name: Add Eyes Reaction
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f content='eyes'
- name: Fetch PR Details
id: pr_info
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
PR_JSON=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json headRefName,headRepository,headRepositoryOwner,baseRefName,state,mergeCommit)
STATE=$(echo "$PR_JSON" | jq -r '.state')
MERGE_COMMIT=$(echo "$PR_JSON" | jq -r '.mergeCommit.oid // empty')
HEAD_REF=$(echo "$PR_JSON" | jq -r '.headRefName')
HEAD_OWNER=$(echo "$PR_JSON" | jq -r '.headRepositoryOwner.login // empty')
HEAD_REPO=$(echo "$PR_JSON" | jq -r '.headRepository.name // empty')
BASE_REF=$(echo "$PR_JSON" | jq -r '.baseRefName')
echo "state=$STATE" >> $GITHUB_OUTPUT
echo "merge_commit=$MERGE_COMMIT" >> $GITHUB_OUTPUT
echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT
echo "head_owner=$HEAD_OWNER" >> $GITHUB_OUTPUT
echo "head_repo=$HEAD_REPO" >> $GITHUB_OUTPUT
echo "base_ref=$BASE_REF" >> $GITHUB_OUTPUT
- name: Checkout upstream
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
- name: Parse and Execute Command
id: exec
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
# Configure Git
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
STATE="${{ steps.pr_info.outputs.state }}"
MERGE_COMMIT="${{ steps.pr_info.outputs.merge_commit }}"
if [ "$STATE" = "MERGED" ]; then
git fetch origin pull/${{ github.event.issue.number }}/head
git checkout -b pr-branch FETCH_HEAD
else
# Connect remote head_repo
git remote add head_repo "https://x-access-token:${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}@github.com/${{ steps.pr_info.outputs.head_owner }}/${{ steps.pr_info.outputs.head_repo }}.git"
git fetch head_repo ${{ steps.pr_info.outputs.head_ref }}
git checkout -b pr-branch head_repo/${{ steps.pr_info.outputs.head_ref }}
fi
COMMENT_BODY="${{ github.event.comment.body }}"
CMD=$(echo "$COMMENT_BODY" | head -n 1 | xargs | tr '[:upper:]' '[:lower:]')
if [ "$STATE" = "MERGED" ] && [[ ! "$CMD" =~ ^/port ]]; then
echo "Error: Only /port command is allowed on merged pull requests."
exit 1
fi
if [[ "$CMD" =~ ^/squash ]]; then
echo "Running squash..."
base_branch="${{ steps.pr_info.outputs.base_ref }}"
head_branch="${{ steps.pr_info.outputs.head_ref }}"
git fetch origin $base_branch
merge_base=$(git merge-base origin/$base_branch HEAD)
first_commit=$(git log --reverse --format="%H" origin/$base_branch..HEAD | head -n 1)
if [ -z "$first_commit" ]; then
echo "No commits found to squash."
exit 1
fi
commit_count=$(git log --oneline origin/$base_branch..HEAD | wc -l)
if [ "$commit_count" -le 1 ]; then
echo "Only one commit found. Nothing to squash."
exit 0
fi
git reset --soft $merge_base
git commit --reuse-message=$first_commit
git push --force-with-lease head_repo HEAD:$head_branch
elif [[ "$CMD" =~ ^/(rebase|port)[[:space:]]+([^[:space:]]+) ]]; then
ACTION="${BASH_REMATCH[1]}"
NEW_BASE="${BASH_REMATCH[2]}"
echo "TARGET_BASE=$NEW_BASE" >> $GITHUB_ENV
echo "Running $ACTION to $NEW_BASE..."
base_branch="${{ steps.pr_info.outputs.base_ref }}"
head_branch="${{ steps.pr_info.outputs.head_ref }}"
git fetch origin $base_branch
git fetch origin $NEW_BASE
# Determine the upstream commit for rebase
if [ "$STATE" = "MERGED" ]; then
# Check if the PR head is already an ancestor of the base branch (standard merge)
if git merge-base --is-ancestor HEAD origin/$base_branch; then
UPSTREAM="$MERGE_COMMIT^1"
else
UPSTREAM=$(git merge-base origin/$base_branch HEAD)
fi
else
UPSTREAM="origin/$base_branch"
fi
if [ "$ACTION" = "rebase" ] && git merge-base --is-ancestor origin/$NEW_BASE HEAD; then
echo "Branch is already fast-forward mergeable with $NEW_BASE. Changing PR base branch..."
gh pr edit ${{ github.event.issue.number }} --base $NEW_BASE --repo ${{ github.repository }}
else
if ! git rebase --onto origin/$NEW_BASE $UPSTREAM HEAD; then
echo "REBASE_FAILED=true" >> $GITHUB_ENV
CONFLICTS=$(git diff --name-only --diff-filter=U)
echo "CONFLICTS<<EOF" >> $GITHUB_ENV
echo "$CONFLICTS" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
git rebase --abort
exit 1
fi
if [ "$ACTION" = "rebase" ]; then
git push --force-with-lease head_repo HEAD:$head_branch
gh pr edit ${{ github.event.issue.number }} --base $NEW_BASE --repo ${{ github.repository }}
else
PR_TITLE=$(gh pr view ${{ github.event.issue.number }} --repo ${{ github.repository }} --json title --jq '.title')
PORT_BRANCH="port-${{ github.event.issue.number }}-$NEW_BASE"
git push --force origin HEAD:refs/heads/$PORT_BRANCH
EXISTING_PR=$(gh pr list --repo ${{ github.repository }} --head "$PORT_BRANCH" --base "$NEW_BASE" --json url --jq '.[0].url')
if [ -n "$EXISTING_PR" ]; then
NEW_PR_URL="$EXISTING_PR"
COMMENT_BODY="\`$NEW_BASE\` port $NEW_PR_URL has been updated to match the latest changes in this PR."
else
NEW_PR_URL=$(gh pr create \
--repo ${{ github.repository }} \
--base "$NEW_BASE" \
--head "$PORT_BRANCH" \
--title "Port: \"$PR_TITLE\" to $NEW_BASE" \
--body "Port of #${{ github.event.issue.number }} to $NEW_BASE." \
--label "port")
printf -v COMMENT_BODY "\`$NEW_BASE\` port: $NEW_PR_URL\n\nIf this PR changes, you can update the port by running \`/port $NEW_BASE\` again."
fi
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body "$COMMENT_BODY"
fi
fi
elif [[ "$CMD" =~ ^/lintroll ]]; then
echo "Running lintroll..."
base_branch="${{ steps.pr_info.outputs.base_ref }}"
head_branch="${{ steps.pr_info.outputs.head_ref }}"
git fetch origin $base_branch
# Switch PHP version to match composer.json if pre-installed
PHP_VERSION=$(jq -r '.config.platform.php // "8.1"' composer.json | cut -d. -f1-2)
if [ -x "/usr/bin/php$PHP_VERSION" ]; then
echo "Switching PHP to version $PHP_VERSION..."
sudo update-alternatives --set php /usr/bin/php$PHP_VERSION
else
echo "PHP version $PHP_VERSION is not pre-installed. Using default: $(php -v | head -n 1)"
fi
# Setup buildkit/civilint
echo "Cloning civicrm-buildkit..."
git clone -b master https://github.com/civicrm/civicrm-buildkit.git $GITHUB_WORKSPACE/civicrm-buildkit
cd $GITHUB_WORKSPACE/civicrm-buildkit
# Prune composer.json to only keep phpcs and coder
jq '.require = {"drupal/coder": .require["drupal/coder"], "squizlabs/php_codesniffer": .require["squizlabs/php_codesniffer"]}' composer.json > composer.json.tmp && mv composer.json.tmp composer.json
composer install --no-interaction --no-plugins --no-scripts
npm install jshint
BUILDKIT_DIR=$GITHUB_WORKSPACE/civicrm-buildkit
cd $GITHUB_WORKSPACE
EXEC_CMD='files=$(git diff-tree --no-commit-id --name-only -r HEAD); if [ -n "$files" ]; then echo "$files" | '"$BUILDKIT_DIR"'/bin/civilint --fix -; fi; git diff --quiet || git commit -a --amend --no-edit'
if ! git rebase --exec "$EXEC_CMD" origin/$base_branch; then
echo "LINTROLL_FAILED=true" >> $GITHUB_ENV
git rebase --abort
exit 1
fi
git push --force-with-lease head_repo HEAD:$head_branch
else
echo "Unknown command: $CMD"
exit 1
fi
- name: Add Success Reaction
if: success()
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f content='+1'
- name: Add Failure Reaction and Comment
if: failure()
env:
GH_TOKEN: ${{ steps.generate-token.outputs.token || secrets.GITHUB_TOKEN }}
run: |
# 1. Add reaction
gh api \
--method POST \
-H "Accept: application/vnd.github+json" \
-H "X-GitHub-Api-Version: 2022-11-28" \
/repos/${{ github.repository }}/issues/comments/${{ github.event.comment.id }}/reactions \
-f content='confused'
# 2. Compile failure message
if [ "${{ env.REBASE_FAILED }}" = "true" ]; then
printf -v MSG "### ❌ Rebase Failed due to Conflicts\n\nThe rebase onto \`%s\` failed due to merge conflicts. The following files have conflicts:\n\`\`\`\n%s\n\`\`\`\n\nPlease resolve these conflicts locally and force-push your changes:\n\`\`\`bash\n# Fetch latest branches\ngit fetch origin\n# Rebase locally\ngit rebase --onto origin/%s origin/%s\n# Resolve conflicts in your editor, then run:\ngit add <conflicted-files>\ngit rebase --continue\n# Force push to your branch\ngit push --force-with-lease\n\`\`\`" "${{ env.TARGET_BASE }}" "${{ env.CONFLICTS }}" "${{ env.TARGET_BASE }}" "${{ steps.pr_info.outputs.base_ref }}"
elif [ "${{ env.LINTROLL_FAILED }}" = "true" ]; then
MSG="### ❌ Lintroll Failed due to Conflicts"$'\n\n'"The automated formatting rebase failed due to merge conflicts. Please run \`civilint --fix\` and resolve any conflicts locally, then force-push your changes."
else
MSG="### ❌ Command Execution Failed"$'\n\n'"The workflow failed to execute the command. Please check the action run logs for details."
fi
# 3. Post comment
gh pr comment ${{ github.event.issue.number }} --repo ${{ github.repository }} --body "$MSG"