.patches: ignore already applied patches, remove broken cleanup workflow

Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
Patrik Oldsberg
2026-01-28 11:25:10 +01:00
parent 508b51c3e2
commit 90e8283458
3 changed files with 79 additions and 174 deletions
-160
View File
@@ -1,160 +0,0 @@
name: Cleanup Patch Files
on:
push:
branches:
- 'patch/v*'
concurrency:
group: cleanup-patch-files
cancel-in-progress: false
jobs:
cleanup:
name: Cleanup Patch Files
runs-on: ubuntu-latest
if: github.repository == 'backstage/backstage'
permissions:
contents: write
steps:
- name: Harden Runner
uses: step-security/harden-runner@df199fb7be9f65074067a9eb93f12bb4c5547cf2 # v2.13.3
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
fetch-depth: 0
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
- name: Configure Git
run: |
git config --global user.email noreply@backstage.io
git config --global user.name 'Github patch cleanup workflow'
- name: Extract PR numbers from commit messages
id: extract-pr-numbers
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
with:
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
script: |
const { execSync } = require('child_process');
// Extract PR numbers from commits pushed to the patch branch
// Commits have messages in format: "Patch from PR #123"
const beforeSha = context.payload.before;
const afterSha = context.payload.after;
let commitRange;
if (beforeSha && beforeSha !== '0000000000000000000000000000000000000000') {
commitRange = `${beforeSha}..${afterSha}`;
} else {
// First push to branch, check recent commits
commitRange = `-n 20 ${afterSha}`;
}
// Get commit messages from git log
let commitMessages;
try {
commitMessages = execSync(
`git log --format=%B ${commitRange}`,
{ encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }
);
} catch (error) {
console.log('No commits found in range');
commitMessages = '';
}
// Extract PR numbers from commit messages matching "Patch from PR #123"
const prNumbers = new Set();
const prPattern = /Patch from PR #(\d+)/g;
let match;
while ((match = prPattern.exec(commitMessages)) !== null) {
prNumbers.add(parseInt(match[1], 10));
}
// Convert to sorted array
const prNumbersArray = Array.from(prNumbers).sort((a, b) => a - b);
if (prNumbersArray.length > 0) {
console.log(`Found PR numbers: ${prNumbersArray.join(', ')}`);
core.setOutput('pr_numbers', JSON.stringify(prNumbersArray));
core.setOutput('has_pr_numbers', 'true');
} else {
console.log('No PR numbers found in commit messages');
core.setOutput('pr_numbers', '[]');
core.setOutput('has_pr_numbers', 'false');
}
- name: Checkout master
if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true'
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
ref: master
token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
- name: Delete patch files
if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true'
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
env:
PR_NUMBERS: ${{ steps.extract-pr-numbers.outputs.pr_numbers }}
REF_NAME: ${{ github.ref_name }}
with:
github-token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }}
script: |
const { execSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const prNumbers = JSON.parse(process.env.PR_NUMBERS);
const patchesDir = path.join(process.cwd(), '.patches');
if (!fs.existsSync(patchesDir)) {
console.log('No .patches directory found, nothing to clean up');
return;
}
const deletedFiles = [];
// Delete patch files for each PR number
for (const prNum of prNumbers) {
const patchFile = path.join(patchesDir, `pr-${prNum}.txt`);
if (fs.existsSync(patchFile)) {
fs.unlinkSync(patchFile);
deletedFiles.push(patchFile);
console.log(`Deleted ${patchFile}`);
} else {
console.log(`Patch file ${patchFile} not found, skipping`);
}
}
if (deletedFiles.length === 0) {
console.log('No patch files to delete');
return;
}
- name: Commit and push changes
if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true'
run: |
# Check if there are any changes to commit
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
git add .patches/
git commit -m "chore: remove applied patch files after patch release merge
Removed patch files for PRs included in patch release to ${{ github.ref_name }}"
git push origin master
- name: Summary
if: steps.extract-pr-numbers.outputs.has_pr_numbers == 'true'
run: |
echo "## Patch Files Cleanup Summary" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Patch release to ${{ github.ref_name }} has been merged." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Patch files have been removed from the master branch." >> $GITHUB_STEP_SUMMARY
+2
View File
@@ -8,6 +8,8 @@ The [sync_patch-release.yml](/.github/workflows/sync_patch-release.yml) workflow
To add a PR to the set of patches, run `yarn patch-pr <pr-number> <description>` in the root of the repository.
Once a patch has been applied and merged, manually delete the corresponding patch file from this directory. The patch script will automatically skip patches that have already been applied to the target branch, so it's safe to re-run the script even if some patches are already present.
## GitHub Workflow
A GitHub workflow automatically keeps a "Patch Release" PR in sync with the patches listed in this directory. When you add, modify, or remove patch files, the workflow will:
+77 -14
View File
@@ -244,7 +244,11 @@ async function main(args) {
await run('git', 'checkout', '-b', branchName);
}
const appliedPrNumbers = [];
for (const prNumber of prNumbers) {
console.log(`Processing PR #${prNumber}...`);
const { data } = await octokit.pulls.get({
owner,
repo,
@@ -271,19 +275,71 @@ async function main(args) {
'--reverse',
'--pretty=%H',
);
for (const logSha of logLines.split(/\r?\n/)) {
await run('git', 'cherry-pick', '-n', logSha);
const commitMessage = `Patch from PR #${prNumber}`;
// Check if this patch has already been applied by looking for the commit message
try {
const existingCommit = await run(
'git',
'log',
'--grep',
commitMessage,
'--format=%H',
'-1',
);
if (existingCommit) {
console.log(
`Patch from PR #${prNumber} has already been applied, skipping...`,
);
continue;
}
} catch {
// No existing commit found, proceed with cherry-pick
}
await run(
'git',
'commit',
'--signoff',
'--no-verify',
'-m',
`Patch from PR #${prNumber}`,
);
let hasChanges = false;
for (const logSha of logLines.split(/\r?\n/)) {
try {
await run('git', 'cherry-pick', '-n', logSha);
hasChanges = true;
} catch (error) {
// Check if the cherry-pick failed because changes are already applied
const status = await run('git', 'status', '--porcelain');
if (!status) {
console.log(
`Commit ${logSha} appears to be already applied, skipping...`,
);
continue;
}
throw error;
}
}
if (!hasChanges) {
console.log(
`All commits from PR #${prNumber} are already applied, skipping...`,
);
continue;
}
await run('git', 'commit', '--signoff', '--no-verify', '-m', commitMessage);
appliedPrNumbers.push(prNumber);
}
if (appliedPrNumbers.length === 0) {
console.log('All patches have already been applied, nothing to do.');
return;
}
console.log(
`Applied ${appliedPrNumbers.length} patch(es): ${appliedPrNumbers.join(
', ',
)}`,
);
console.log('Running "yarn install" ...');
await run('yarn', 'install');
@@ -311,12 +367,17 @@ async function main(args) {
await run('git', 'push', 'origin', '-u', branchName);
}
// Generate PR body
// Generate PR body using only applied patches
let body;
if (descriptions) {
const descriptionList = descriptions
// Filter descriptions to only include applied patches
const appliedDescriptions = descriptions.filter((_, index) =>
appliedPrNumbers.includes(prNumbers[index]),
);
const descriptionList = appliedDescriptions
.map((desc, index) => {
const prNumber = prNumbers[index];
const prNumber = appliedPrNumbers[index];
const prLink = `https://github.com/${owner}/${repo}/pull/${prNumber}`;
return `- ${desc} ([#${prNumber}](${prLink}))`;
})
@@ -329,7 +390,9 @@ async function main(args) {
const params = new URLSearchParams({
expand: 1,
body: body,
title: `Patch release of ${prNumbers.map(nr => `#${nr}`).join(', ')}`,
title: `Patch release of ${appliedPrNumbers
.map(nr => `#${nr}`)
.join(', ')}`,
});
const url = `https://github.com/backstage/backstage/compare/${patchBranch}...${branchName}?${params}`;