From 90e82834580917bb6c7f5f79f1e56a732b436825 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 28 Jan 2026 11:25:10 +0100 Subject: [PATCH] .patches: ignore already applied patches, remove broken cleanup workflow Signed-off-by: Patrik Oldsberg --- .github/workflows/cleanup_patch-files.yml | 160 ---------------------- .patches/README.md | 2 + scripts/patch-release-for-pr.js | 91 ++++++++++-- 3 files changed, 79 insertions(+), 174 deletions(-) delete mode 100644 .github/workflows/cleanup_patch-files.yml diff --git a/.github/workflows/cleanup_patch-files.yml b/.github/workflows/cleanup_patch-files.yml deleted file mode 100644 index 4d95459ab6..0000000000 --- a/.github/workflows/cleanup_patch-files.yml +++ /dev/null @@ -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 diff --git a/.patches/README.md b/.patches/README.md index 19b599d6bb..290cbf7fc5 100644 --- a/.patches/README.md +++ b/.patches/README.md @@ -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 ` 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: diff --git a/scripts/patch-release-for-pr.js b/scripts/patch-release-for-pr.js index 71957e5f3e..7a407b760b 100755 --- a/scripts/patch-release-for-pr.js +++ b/scripts/patch-release-for-pr.js @@ -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}`;