Skip to content

Commit a7dc589

Browse files
authored
Replace pull_request_target in PR helper with split workflows (#4878)
1 parent 69d3a9e commit a7dc589

2 files changed

Lines changed: 169 additions & 49 deletions

File tree

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
name: Publish pull request helper
2+
3+
on:
4+
workflow_run:
5+
workflows: ["Pull request helper"]
6+
types: [completed]
7+
8+
permissions:
9+
actions: read
10+
contents: read
11+
pull-requests: read
12+
13+
env:
14+
GIT_DIR: pr-helper.git
15+
GIT_CONFIG_NOSYSTEM: '1'
16+
GIT_CONFIG_GLOBAL: /dev/null
17+
GIT_TERMINAL_PROMPT: '0'
18+
19+
jobs:
20+
publish:
21+
if: >-
22+
github.event.workflow_run.event == 'pull_request' &&
23+
github.event.workflow_run.conclusion == 'success' &&
24+
github.event.workflow_run.head_repository.id == github.event.repository.id
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Validate source PR
28+
id: source
29+
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
30+
with:
31+
script: |
32+
const run = context.payload.workflow_run;
33+
const {data: artifacts} = await github.rest.actions.listWorkflowRunArtifacts({
34+
...context.repo, run_id: run.id, name: `pr-helper-${run.id}-${run.run_attempt}`
35+
});
36+
if (artifacts.total_count === 0) return;
37+
if (run.path !== '.github/workflows/pull-request-helper.yml' ||
38+
run.pull_requests.length !== 1) {
39+
throw new Error('Unexpected source workflow or missing PR');
40+
}
41+
const {data: pr} = await github.rest.pulls.get({
42+
...context.repo, pull_number: run.pull_requests[0].number
43+
});
44+
if (pr.user.login !== 'dependabot[bot]' || pr.state !== 'open' ||
45+
pr.head.repo?.id !== context.payload.repository.id ||
46+
pr.head.sha !== run.head_sha || pr.head.ref !== run.head_branch ||
47+
pr.head.ref === context.payload.repository.default_branch) {
48+
core.notice('Skipping ineligible PR or stale source SHA');
49+
return;
50+
}
51+
core.setOutput('sha', run.head_sha);
52+
core.setOutput('branch', pr.head.ref);
53+
core.setOutput('number', pr.number);
54+
55+
- name: Download source patch
56+
if: steps.source.outputs.sha != ''
57+
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
58+
with:
59+
name: pr-helper-${{ github.event.workflow_run.id }}-${{ github.event.workflow_run.run_attempt }}
60+
run-id: ${{ github.event.workflow_run.id }}
61+
github-token: ${{ github.token }}
62+
path: ${{ runner.temp }}/pr-helper-artifact
63+
digest-mismatch: error
64+
65+
- name: Apply patch and create commit
66+
if: steps.source.outputs.sha != ''
67+
id: patch
68+
env:
69+
SOURCE_SHA: ${{ steps.source.outputs.sha }}
70+
run: |
71+
patch="$RUNNER_TEMP/pr-helper-artifact/changes.patch"
72+
if [[ ! -f "$patch" || -L "$patch" || $(wc -c < "$patch") -gt 8388608 ]]; then
73+
echo "::error::Missing, nonregular or oversized patch" >&2
74+
exit 1
75+
fi
76+
if [[ ! -s "$patch" ]]; then
77+
exit 0
78+
fi
79+
if grep -q '^GIT binary patch' "$patch"; then
80+
echo "::error::Binary patches are not supported" >&2
81+
exit 1
82+
fi
83+
git init --bare --template= "$GIT_DIR"
84+
git config core.hooksPath /dev/null
85+
git remote add origin "$GITHUB_SERVER_URL/$GITHUB_REPOSITORY.git"
86+
git fetch --no-tags --depth=1 origin "$SOURCE_SHA"
87+
git read-tree "$SOURCE_SHA"
88+
git apply --cached --whitespace=nowarn "$patch"
89+
git diff --cached --raw --no-renames -z "$SOURCE_SHA" > "$GIT_DIR/changes"
90+
while IFS= read -r -d '' metadata && IFS= read -r -d '' file; do
91+
case "$file" in
92+
/*|*\\*|*:*|*[[:cntrl:]]*|.*|*/.*|*//*)
93+
echo "::error::Unsafe patch path" >&2; exit 1 ;;
94+
esac
95+
case "$file" in
96+
*.lockfile|licenses/*) ;;
97+
*) echo "::error::Patch path outside lockfiles/licenses" >&2; exit 1 ;;
98+
esac
99+
read -r old_mode new_mode _ <<< "${metadata#:}"
100+
case "$old_mode:$new_mode" in
101+
100644:100644|000000:100644|100644:000000) ;;
102+
*) echo "::error::Patch must only change regular nonexecutable files" >&2; exit 1 ;;
103+
esac
104+
done < "$GIT_DIR/changes"
105+
if [[ ! -s "$GIT_DIR/changes" ]]; then
106+
exit 0
107+
fi
108+
git config user.name 'github-actions[bot]'
109+
git config user.email '41898282+github-actions[bot]@users.noreply.github.com'
110+
commit=$(git commit-tree "$(git write-tree)" -p "$SOURCE_SHA" -m "Update dependency locks and licenses")
111+
echo "commit=$commit" >> "$GITHUB_OUTPUT"
112+
113+
- name: Generate GitHub App token
114+
if: steps.patch.outputs.commit != ''
115+
id: app-token
116+
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
117+
with:
118+
client-id: ${{ vars.AI_JAVA_HELPER_APP_CLIENT_ID }}
119+
private-key: ${{ secrets.AI_JAVA_HELPER_APP_PRIVATE_KEY }}
120+
permission-contents: write
121+
122+
- name: Push generated commit
123+
if: steps.patch.outputs.commit != ''
124+
env:
125+
SOURCE_SHA: ${{ steps.source.outputs.sha }}
126+
BRANCH: ${{ steps.source.outputs.branch }}
127+
PR_NUMBER: ${{ steps.source.outputs.number }}
128+
COMMIT: ${{ steps.patch.outputs.commit }}
129+
GH_TOKEN: ${{ github.token }}
130+
APP_TOKEN: ${{ steps.app-token.outputs.token }}
131+
run: |
132+
current=$(gh api "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER" --jq '.state + " " + .head.sha')
133+
if [[ "$current" != "open $SOURCE_SHA" ]]; then
134+
echo "::notice::Skipping closed PR or stale patch"
135+
exit 0
136+
fi
137+
# The bare repo has no checkout credentials; use the App token so this push triggers CI.
138+
GH_TOKEN="$APP_TOKEN" git -c 'credential.helper=!gh auth git-credential' \
139+
push origin "$COMMIT:refs/heads/$BRANCH"
Lines changed: 30 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,53 @@
11
name: Pull request helper
22
on:
3-
pull_request_target:
3+
pull_request:
4+
types: [opened, synchronize, reopened]
45

56
permissions:
6-
contents: write
7+
contents: read
8+
9+
concurrency:
10+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
11+
cancel-in-progress: true
712

813
jobs:
914
pull-request-helper:
10-
if: github.event.pull_request.user.login == 'dependabot[bot]'
15+
if: >-
16+
github.event.pull_request.user.login == 'dependabot[bot]' &&
17+
github.event.pull_request.head.repo.id == github.event.repository.id
1118
runs-on: ubuntu-latest
1219
steps:
13-
- name: Generate GitHub App token
14-
id: app-token
15-
uses: actions/create-github-app-token@64ab729a6294000f5aa4a4bc0b73d1f32bc45510 # v3.3.0
16-
with:
17-
client-id: ${{ vars.AI_JAVA_HELPER_APP_CLIENT_ID }}
18-
private-key: ${{ secrets.AI_JAVA_HELPER_APP_PRIVATE_KEY }}
19-
permission-contents: write
20-
21-
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
20+
- name: Checkout exact PR head
21+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
2222
with:
23-
# an app token is used (instead of GITHUB_TOKEN) so that "git push" below triggers workflows
24-
token: ${{ steps.app-token.outputs.token }}
25-
repository: ${{ github.event.pull_request.head.repo.full_name }}
26-
ref: ${{ github.event.pull_request.head.ref }}
23+
ref: ${{ github.event.pull_request.head.sha }}
24+
persist-credentials: false
2725

2826
- name: Setup Gradle
2927
uses: gradle/actions/setup-gradle@3f5f9adaf7d9fecd50b5935e54106014257a94e6 # v6.4.0
3028
with:
3129
cache-read-only: true
3230

33-
- name: Set git user
34-
run: |
35-
git config user.name github-actions[bot]
36-
git config user.email 41898282+github-actions[bot]@users.noreply.github.com
37-
38-
# - name: Spotless
39-
# env:
40-
# NUMBER: ${{ github.event.issue.number }}
41-
# run: |
42-
# ./gradlew spotlessApply
43-
# if git diff --quiet; then
44-
# exit 0 # success
45-
# fi
46-
# git commit -a -m "./gradlew spotlessApply"
31+
- name: Update lock files
32+
run: ./gradlew resolveAndLockAll --write-locks
4733

4834
- name: Update license report
49-
env:
50-
NUMBER: ${{ github.event.issue.number }}
51-
run: |
52-
./gradlew generateLicenseReport
53-
git add licenses
54-
if git diff --cached --quiet; then
55-
exit 0 # success
56-
fi
57-
git commit -m "./gradlew generateLicenseReport"
35+
run: ./gradlew generateLicenseReport --no-build-cache
5836

59-
- name: Update lock files
60-
env:
61-
NUMBER: ${{ github.event.issue.number }}
37+
- name: Create generated-file patch
38+
id: patch
6239
run: |
63-
./gradlew resolveAndLockAll --write-locks
64-
git add "*.lockfile"
40+
git add '*.lockfile' licenses/
6541
if git diff --cached --quiet; then
66-
exit 0 # success
42+
exit 0
6743
fi
68-
git commit -m "./gradlew resolveAndLockAll --write-locks"
44+
git diff --cached > "$RUNNER_TEMP/changes.patch"
45+
echo "changed=true" >> "$GITHUB_OUTPUT"
6946
70-
- name: Push
71-
run: |
72-
git push
47+
- name: Upload generated changes
48+
if: steps.patch.outputs.changed == 'true'
49+
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
50+
with:
51+
name: pr-helper-${{ github.run_id }}-${{ github.run_attempt }}
52+
path: ${{ runner.temp }}/changes.patch
53+
if-no-files-found: error

0 commit comments

Comments
 (0)