11# Automatically fix license files on PRs that need updates
2- # Instead of just failing, this workflow pushes the fix and comments on the PR
2+ # Tries to auto-commit the fix, or comments with instructions if push fails
33
44name : License Check
55on :
@@ -21,17 +21,8 @@ permissions:
2121jobs :
2222 license-check :
2323 runs-on : ubuntu-latest
24- outputs :
25- needs_fix : ${{ steps.changes.outputs.changed }}
26- is_fork : ${{ steps.fork_check.outputs.is_fork }}
2724
2825 steps :
29- - name : Check if fork PR
30- id : fork_check
31- run : |
32- IS_FORK=${{ github.event.pull_request.head.repo.full_name != github.repository }}
33- echo "is_fork=${IS_FORK}" >> $GITHUB_OUTPUT
34-
3526 - name : Check out code
3627 uses : actions/checkout@v6
3728 with :
@@ -55,325 +46,66 @@ jobs:
5546
5647 - name : Check for changes
5748 id : changes
58- run : |
59- if git diff --exit-code; then
60- echo "changed=false" >> $GITHUB_OUTPUT
61- echo "✅ License files are up to date"
62- else
63- echo "changed=true" >> $GITHUB_OUTPUT
64- echo "📝 License files need updating"
65- git diff --stat
66- fi
49+ continue-on-error : true
50+ run : script/licenses-check
6751
6852 - name : Commit and push fixes
69- if : steps.changes.outputs.changed == 'true '
70- continue-on-error : true # Don't fail if push fails (e.g., on forks)
53+ if : steps.changes.outcome == 'failure '
54+ continue-on-error : true
7155 id : push
7256 run : |
73- git config --local user.name "github-actions[bot]"
74- git config --local user.email "41898282+github-actions[bot]@users.noreply.github.com"
75- git add third-party third-party-licenses.*.md
76- git commit -m "chore: regenerate third-party licenses"
77- git push
78-
79- - name : Comment on PR
80- if : steps.changes.outputs.changed == 'true'
81- uses : actions/github-script@v7
82- with :
83- script : |
84- const isFork = context.payload.pull_request.head.repo.full_name !== context.repo.owner + '/' + context.repo.repo;
85- const pushFailed = '${{ steps.push.outcome }}' === 'failure';
86-
87- let body;
88- if (isFork || pushFailed) {
89- body = `## 📜 License files need updating
90-
91- License files are out of date. Since this is from a fork, I can't push the fix directly.
92-
93- **Please run locally:**
94- \`\`\`bash
95- ./script/licenses
96- git add third-party third-party-licenses.*.md third-party/
97- git commit -m "chore : update license files"
98- git push
99- \`\`\`
57+ git config user.name "github-actions[bot]"
58+ git config user.email "github-actions[bot]@users.noreply.github.com"
59+ git add third-party-licenses.*.md third-party/
60+ git commit -m "chore: regenerate license files
10061
101- Or a maintainer can push to your branch after approving the workflow.`;
102- } else {
103- body = `## 📜 License files updated
104-
105- I noticed the third-party license files were out of date and pushed a fix to this PR.
106-
107- **What changed:** Dependencies were added, removed, or updated, which requires regenerating the license documentation.
108-
109- **What I did:** Ran \`./script/licenses\` and committed the result.
110-
111- Please pull the latest changes before pushing again.`;
112- }
113-
114- github.rest.issues.createComment({
115- owner : context.repo.owner,
116- repo : context.repo.repo,
117- issue_number : context.issue.number,
118- body
119- })
120-
121- - name : Fail check if licenses not fixed
122- if : steps.changes.outputs.changed == 'true' && steps.push.outcome == 'failure'
123- run : |
124- echo "::error::License files are out of date. Please run ./script/licenses locally."
125- exit 1
126-
127- auto-create-fix-pr :
128- runs-on : ubuntu-latest
129- needs : license-check
130- if : needs.license-check.outputs.needs_fix == 'true' && needs.license-check.outputs.is_fork == 'false'
131-
132- steps :
133- - name : Check out base PR branch
134- uses : actions/checkout@v6
135- with :
136- ref : ${{ github.event.pull_request.head.ref }}
137- fetch-depth : 0
138-
139- - name : Set up Go
140- uses : actions/setup-go@v6
141- with :
142- go-version-file : " go.mod"
143-
144- - name : Regenerate licenses
145- id : regen
146- env :
147- CI : " true"
148- run : |
149- export GOROOT=$(go env GOROOT)
150- export PATH=${GOROOT}/bin:$PATH
151- ./script/licenses
152-
153- # Compute hash of license changes only
154- LICENSE_HASH=$(git diff third-party-licenses.*.md third-party/ | sha256sum | cut -c1-8)
155- echo "license_hash=${LICENSE_HASH}" >> $GITHUB_OUTPUT
156- echo "License changes hash: ${LICENSE_HASH}"
62+ Auto-generated by license-check workflow"
63+ git push
15764
158- - name : Find existing license fix PR
159- id : find_pr
65+ - name : Check if already commented
66+ if : steps.changes.outcome == 'failure' && steps.push.outcome == 'failure'
67+ id : check_comment
16068 uses : actions/github-script@v7
16169 with :
16270 script : |
163- const basePR = context.payload.pull_request.number;
164- const baseBranch = context.payload.pull_request.head.ref;
165-
166- // Search for existing auto-fix PR
167- const { data: prs } = await github.rest.pulls.list({
71+ const { data: comments } = await github.rest.issues.listComments({
16872 owner: context.repo.owner,
16973 repo: context.repo.repo,
170- state: 'open',
171- base: baseBranch,
172- sort: 'created',
173- direction: 'desc'
74+ issue_number: context.issue.number
17475 });
17576
176- // Find PR with our marker in the title or body
177- const existingPR = prs.find(pr =>
178- pr.title.includes('🤖 Auto-fix licenses') &&
179- pr.body?.includes(`<!-- auto-fix-for-pr:${basePR} -->`)
77+ const alreadyCommented = comments.some(comment =>
78+ comment.user.login === 'github-actions[bot]' &&
79+ comment.body.includes('## ⚠️ License files need updating')
18080 );
18181
182- if (existingPR) {
183- core.setOutput('exists', 'true');
184- core.setOutput('pr_number', existingPR.number);
185- core.setOutput('pr_branch', existingPR.head.ref);
186-
187- // Extract hash from PR body
188- const hashMatch = existingPR.body?.match(/<!-- license-hash:(\w+) -->/);
189- const oldHash = hashMatch ? hashMatch[1] : '';
190- core.setOutput('old_hash', oldHash);
191-
192- core.info(`Found existing PR #${existingPR.number} with hash ${oldHash}`);
193- } else {
194- core.setOutput('exists', 'false');
195- core.info('No existing auto-fix PR found');
196- }
82+ core.setOutput('already_commented', alreadyCommented ? 'true' : 'false');
19783
198- - name : Close PR if hash changed (dependencies changed)
199- id : check_hash_changed
200- if : |
201- steps.find_pr.outputs.exists == 'true' &&
202- steps.find_pr.outputs.old_hash != '' &&
203- steps.find_pr.outputs.old_hash != steps.regen.outputs.license_hash
84+ - name : Comment with instructions if cannot push
85+ if : steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' && steps.check_comment.outputs.already_commented == 'false'
20486 uses : actions/github-script@v7
20587 with :
20688 script : |
207- const prNumber = ${{ steps.find_pr.outputs.pr_number }};
208- const oldHash = '${{ steps.find_pr.outputs.old_hash }}';
209- const newHash = '${{ steps.regen.outputs.license_hash }}';
210-
21189 await github.rest.issues.createComment({
21290 owner: context.repo.owner,
21391 repo: context.repo.repo,
214- issue_number: prNumber,
215- body: `## 🔄 Closing - dependencies changed\n\nThe base PR #${context.payload.pull_request.number} has different license requirements now.\n\n- Old hash: \`${oldHash}\`\n- New hash: \`${newHash}\`\n\nA new auto-fix PR will be created.`
216- });
217-
218- await github.rest.pulls.update({
219- owner: context.repo.owner,
220- repo: context.repo.repo,
221- pull_number: prNumber,
222- state: 'closed'
223- });
224-
225- core.setOutput('create_new', 'true');
226- core.info(`Closed PR #${prNumber} due to hash change`);
227-
228- - name : Create or update license fix PR
229- if : steps.find_pr.outputs.exists == 'false' || steps.check_hash_changed.outputs.create_new == 'true'
230- uses : actions/github-script@v7
231- with :
232- script : |
233- const basePR = context.payload.pull_request.number;
234- const baseBranch = context.payload.pull_request.head.ref;
235- const licenseHash = '${{ steps.regen.outputs.license_hash }}';
236- const branchName = `auto-fix/licenses-for-pr-${basePR}`;
237-
238- // Create new branch from base PR
239- const { data: baseRef } = await github.rest.git.getRef({
240- owner: context.repo.owner,
241- repo: context.repo.repo,
242- ref: `heads/${baseBranch}`
243- });
244-
245- try {
246- await github.rest.git.createRef({
247- owner: context.repo.owner,
248- repo: context.repo.repo,
249- ref: `refs/heads/${branchName}`,
250- sha: baseRef.object.sha
251- });
252- } catch (error) {
253- // Branch might exist, update it
254- await github.rest.git.updateRef({
255- owner: context.repo.owner,
256- repo: context.repo.repo,
257- ref: `heads/${branchName}`,
258- sha: baseRef.object.sha,
259- force: true
260- });
261- }
262-
263- // Checkout the new branch and commit license changes
264- await exec.exec('git', ['fetch', 'origin', branchName]);
265- await exec.exec('git', ['checkout', '-B', branchName, `origin/${branchName}`]);
266- await exec.exec('git', ['config', 'user.name', 'github-actions[bot]']);
267- await exec.exec('git', ['config', 'user.email', '41898282+github-actions[bot]@users.noreply.github.com']);
268-
269- // Regenerate licenses on this branch
270- process.env.CI = 'true';
271- const goRoot = (await exec.getExecOutput('go', ['env', 'GOROOT'])).stdout.trim();
272- process.env.GOROOT = goRoot;
273- process.env.PATH = `${goRoot}/bin:${process.env.PATH}`;
274- await exec.exec('./script/licenses');
275-
276- await exec.exec('git', ['add', 'third-party', 'third-party-licenses.*.md']);
277- await exec.exec('git', ['commit', '-m', `chore: auto-fix license files for PR #${basePR}`]);
278- await exec.exec('git', ['push', 'origin', branchName, '--force']);
279-
280- // Create PR
281- const { data: newPR } = await github.rest.pulls.create({
282- owner: context.repo.owner,
283- repo: context.repo.repo,
284- title: `🤖 Auto-fix licenses for PR #${basePR}`,
285- head: branchName,
286- base: baseBranch,
287- body: `## Automated License Update
288-
289- This PR automatically updates license files for PR # ${basePR}.
290-
291- # ## What happened
292- Dependencies were added/updated in # ${basePR}, which requires regenerating license documentation.
293-
294- # ## What to do
295- - **Option 1:** Merge this PR to add the license updates to # ${basePR}
296- - **Option 2:** Manually run \`./script/licenses\` in # ${basePR} and push (this PR will auto-close)
92+ issue_number: context.issue.number,
93+ body: `## ⚠️ License files need updating
29794
298- This PR will automatically close if :
299- - License files in # ${basePR} are updated manually
300- - Dependencies in # ${basePR} change (a new PR will be created)
95+ The license files are out of date. I tried to fix them automatically but don't have permission to push to this branch.
30196
302- <!-- auto-fix-for-pr:${basePR} -->
303- <!-- license-hash:${licenseHash} -->`
304- });
305-
306- core.info(`Created auto-fix PR # ${newPR.number}`);
97+ **Please run:**
98+ \`\`\`bash
99+ script/licenses
100+ git add third-party-licenses.*.md third-party/
101+ git commit -m "chore : regenerate license files"
102+ git push
103+ \`\`\`
307104
308- # After pushing the fix, check if PR now has no functional changes (just license updates)
309- # This handles the case where the PR only needed license updates and nothing else
310- - name : Check if PR is now empty
311- if : steps.changes.outputs.changed == 'true'
312- id : empty_check
313- uses : actions/github-script@v7
314- with :
315- script : |
316- const { data: pr } = await github.rest.pulls.get({
317- owner: context.repo.owner,
318- repo: context.repo.repo,
319- pull_number: context.issue.number
105+ Alternatively, enable "Allow edits by maintainers" in the PR settings so I can fix it automatically.`
320106 });
321-
322- const { data: files } = await github.rest.pulls.listFiles({
323- owner: context.repo.owner,
324- repo: context.repo.repo,
325- pull_number: context.issue.number
326- });
327-
328- // Check if ALL changes are just license files
329- const nonLicenseFiles = files.filter(f =>
330- !f.filename.startsWith('third-party-licenses.') &&
331- !f.filename.startsWith('third-party/')
332- );
333-
334- const isEmpty = nonLicenseFiles.length === 0;
335-
336- // Only close if the PR title/body suggests it wasn't meant to be about licenses
337- // or if it was created by a bot (which might auto-update dependencies)
338- const isLicenseFocused =
339- pr.title.toLowerCase().includes('licen') ||
340- pr.title.toLowerCase().includes('third-party') ||
341- pr.body?.toLowerCase().includes('update.*licen');
342-
343- const shouldClose = isEmpty && !isLicenseFocused && pr.user.type !== 'Bot';
344-
345- core.setOutput('should_close', shouldClose);
346-
347- if (isEmpty && isLicenseFocused) {
348- core.info('PR only has license files but appears to be intentionally about licenses - keeping open');
349- } else if (isEmpty && pr.user.type === 'Bot') {
350- core.info('PR is from a bot and only has license files - keeping open (might be dependabot)');
351- } else if (shouldClose) {
352- core.info('PR only contains license file changes and appears stale - will close');
353- } else {
354- core.info(`PR has ${nonLicenseFiles.length} non-license file changes - keeping open`);
355- }
356-
357- - name : Close stale license-only PR
358- if : steps.changes.outputs.changed == 'true' && steps.empty_check.outputs.should_close == 'true'
359- uses : actions/github-script@v7
360- with :
361- script : |
362- await github.rest.issues.createComment({
363- owner: context.repo.owner,
364- repo: context.repo.repo,
365- issue_number: context.issue.number,
366- body: `## 🤖 Auto-closing stale PR
367107
368- This PR now only contains license file updates with no other functional changes. The license updates have been applied, so closing this PR as complete.
369-
370- If this PR should have had other changes, please reopen it and add the intended changes.`
371- });
372-
373- await github.rest.pulls.update({
374- owner : context.repo.owner,
375- repo : context.repo.repo,
376- pull_number : context.issue.number,
377- state : ' closed'
378- });
108+ - name : Fail check if changes needed
109+ if : steps.changes.outcome == 'failure'
110+ run : exit 1
379111
0 commit comments