#!/usr/bin/env node /** * 手動清掉一棵工作樹。 * * pr-watch 會在 PR 合併或關閉時自動清理,這一支是給那些**永遠不會被合併也不會被關閉** * 的 PR 用的出口——沒有它,那些工作樹只能靠使用者自己記得去刪。 * * 兩條路共用 lib 的 `removeWorktree`,不互相開子行程:守門的規則只有一份, * 自動的那條與手動的這條不該長出兩種行為。 * * 只移除工作樹,本機分支與遠端分支都留著。工作樹裡還有沒提交的東西就中止並報出路徑, * **絕不 `--force`**:工作樹重建得回來,被刪掉的未提交變更救不回來。 * * 路徑由 `owner/repo/分支名` 推導,所以輸入是這兩個而不是一條路徑——要刪哪一棵由 * 「哪顆工作包」決定,使用者不必自己去記 12 碼的雜湊目錄名。 * * 用法: * node scripts/worktree-remove.js --repo owner/name --branch <分支名> [--dry-run] */ import { ScriptError, inspectWorktree, main, parseFlags, parseRepo, removeWorktree, worktreePath, } from './lib.js'; main(async () => { const flags = parseFlags(process.argv.slice(2), { required: ['repo', 'branch'], booleans: ['dry-run'], }); const repo = parseRepo(flags.repo); const branch = flags.branch; const worktree = worktreePath(repo, branch); // 試跑與實跑走同一條守門:試跑印得出漂亮的計畫、實跑卻被擋下來,是最難查的那種落差 if (flags['dry-run']) { const state = inspectWorktree(worktree); checkRemovable(state, worktree); return { dryRun: true, repo, branch, worktree, 已經不在: state.reason === 'missing', commands: state.reason === 'removable' ? [`git worktree remove ${worktree}`] : [], }; } const result = removeWorktree(worktree); checkRemovable(result, worktree); return { repo, branch, worktree, removed: result.removed, // 本來就不在不算失敗:重跑這一支是常態,而結果一樣是「那棵樹不在了」 已經不在: result.reason === 'missing', }; }); /** 擋下來的兩種情況各有各的下一步,錯誤碼要分得開 */ function checkRemovable(result, worktree) { if (result.reason === 'dirty') { throw new ScriptError( 'WORKTREE_DIRTY', `工作樹 ${worktree} 裡還有沒提交的東西(${result.files.join('、')});` + '請先提交、暫存(git stash)或確認可以丟掉再自己刪除——' + '本工具不會加 --force,刪掉的未提交變更救不回來', ); } if (result.reason === 'foreign') { throw new ScriptError( 'NOT_A_WORKTREE', `${worktree} 上有東西,但它不是一棵 git 工作樹(可能是別的 clone 留下的);` + '請自己確認裡面沒有還沒保存的東西之後移除它', ); } }