diff --git a/.github/workflows/branch-name.yml b/.github/workflows/branch-name.yml index e80c3ac..58c449c 100644 --- a/.github/workflows/branch-name.yml +++ b/.github/workflows/branch-name.yml @@ -13,13 +13,6 @@ jobs: branch-name: runs-on: ubuntu-latest steps: - - name: Check out Issue Type mapping - uses: actions/checkout@v4 - with: - sparse-checkout: | - scripts/issue-type.mjs - sparse-checkout-cone-mode: false - - name: Validate source branch name env: BRANCH_NAME: ${{ github.head_ref }} @@ -35,34 +28,22 @@ jobs: echo "Branch name valid: ${BRANCH_NAME}" - name: Validate linked issue - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | - const path = require("node:path"); - const { pathToFileURL } = require("node:url"); - const { - BRANCH_ISSUE_PATTERN, - PR_TITLE_PATTERN, - closingReferencePattern, - expectedIssueTypeFor, - validateIssueType, - } = await import( - pathToFileURL( - path.join(process.env.GITHUB_WORKSPACE, "scripts/issue-type.mjs"), - ).href, - ); - const branch = context.payload.pull_request.head.ref; - const branchMatch = BRANCH_ISSUE_PATTERN.exec(branch); + const branchMatch = /^(?:feat|fix|refactor|docs|chore)\/(\d+)-/.exec(branch); + if (!branchMatch) { core.setFailed(`Could not extract issue number from branch '${branch}'.`); return; } - const [, type, issueNumber] = branchMatch; - const expectedIssueType = expectedIssueTypeFor(type); + const [, issueNumber] = branchMatch; const pullRequest = context.payload.pull_request; - const titleMatch = PR_TITLE_PATTERN.exec(pullRequest.title.trim()); + const titleMatch = /^\[(?:feat|fix|refactor|docs|chore)\]\s+(.+?)\s+\(#(\d+)\)$/.exec( + pullRequest.title.trim(), + ); if (!titleMatch) { core.setFailed( @@ -71,14 +52,7 @@ jobs: return; } - const [, titleType, , titleIssueNumber] = titleMatch; - if (titleType !== type) { - core.setFailed( - `PR title type '${titleType}' must match branch type '${type}'.`, - ); - return; - } - + const [, , titleIssueNumber] = titleMatch; if (titleIssueNumber !== issueNumber) { core.setFailed( `PR title issue number #${titleIssueNumber} must match branch issue #${issueNumber}.`, @@ -86,30 +60,19 @@ jobs: return; } - const text = `${pullRequest.title}\n${pullRequest.body ?? ""}`; - const closingKeyword = closingReferencePattern(issueNumber); - if (!closingKeyword.test(text)) { - core.setFailed( - `PR must include 'Fixes #${issueNumber}' (or an equivalent closing keyword).`, - ); + const closingKeyword = new RegExp( + `(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?)\\s+#${issueNumber}\\b`, + "i", + ); + if (!closingKeyword.test(pullRequest.body ?? "")) { + core.setFailed(`PR body must reference the linked issue with a closing keyword, such as 'Fixes #${issueNumber}'.`); return; } - const { data: issue } = await github.rest.issues.get({ + await github.rest.issues.get({ owner: context.repo.owner, repo: context.repo.repo, issue_number: Number(issueNumber), }); - const actualIssueType = issue.type?.name ?? null; - const issueTypeCheck = validateIssueType(type, actualIssueType); - - if (!issueTypeCheck.ok) { - core.setFailed( - `Issue #${issueNumber} must use native GitHub Issue Type '${expectedIssueType}' for branch '${type}', but found '${actualIssueType ?? "none"}'.`, - ); - return; - } - core.info( - `PR linked Issue #${issueNumber} uses native GitHub Issue Type '${actualIssueType}'. Legacy type:* labels are not required.`, - ); + core.info(`Linked issue #${issueNumber} validated.`); diff --git a/scripts/cleanup-merged.mjs b/scripts/cleanup-merged.mjs index 63fb7ad..12e594b 100644 --- a/scripts/cleanup-merged.mjs +++ b/scripts/cleanup-merged.mjs @@ -402,7 +402,7 @@ async function main() { } console.log(`Deleting local branch ${item.branch}...`); - runGit(["branch", "-d", "--", item.branch], { cwd: repoRoot }); + runGit(["branch", "-D", "--", item.branch], { cwd: repoRoot }); } console.log("Cleanup complete."); diff --git a/scripts/cleanup-merged.test.mjs b/scripts/cleanup-merged.test.mjs index dc75634..4c70cd8 100644 --- a/scripts/cleanup-merged.test.mjs +++ b/scripts/cleanup-merged.test.mjs @@ -166,3 +166,24 @@ test("prunes missing worktree registrations without deleting unmerged branches", removeRepository(repo, linkedPath); } }); + +test("deletes branches merged into the selected base even when HEAD differs", () => { + const repo = createRepository(); + + try { + git(repo, ["checkout", "-b", "chore/current"]); + git(repo, ["checkout", "--quiet", "main"]); + git(repo, ["checkout", "-b", "chore/merged-elsewhere"]); + commitFile(repo, "merged-elsewhere.txt", "merged into main\n"); + mergeBranch(repo, "chore/merged-elsewhere"); + git(repo, ["checkout", "--quiet", "chore/current"]); + + const result = runCleanup(repo, ["--base", "main", "--yes"]); + + assert.equal(result.status, 0, result.stderr); + assert.equal(branchExists(repo, "chore/merged-elsewhere"), false); + assert.equal(git(repo, ["branch", "--show-current"]), "chore/current"); + } finally { + removeRepository(repo, ""); + } +});