Worktrunk 深度指南:wt remove 的 worktree 移除命令、安全分支清理与后台回收机制
【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk
wt remove是 Worktrunk(wtCLI)中负责回收 git worktree 的命令:默认作用于当前 worktree,若分支的改动已经并入默认分支则连同分支一并删除。读完本篇,你将掌握它的完整命令行参数、六层“分支是否可安全删除”的判定算法、后台移除与.git/wt/trash/回收站机制、--reap进程清理守卫,以及面向自动化调用的 JSON 输出契约,并能在源码层面理解其“先验证、再审批、后执行”的内部调用链。
命令基本用法
wt remove接受零个或多个目标:不带参数时移除当前所在的 worktree,带参数时按分支名或 worktree 路径移除指定目标。以下示例继承自官方参考文档 remove.md。
移除当前 worktree(会先触发pre-remove钩子,如将云主机缩容到 0):
$ wt remove ◎ Running pre-remove project:cleanup flyctl scale count 0 Scaling app to 0 machines ◎ Removing api worktree & branch in background (same commit as main, _) ○ Switched to worktree for main @ ~/repo移除指定 worktree / 分支,支持一次传多个:
$ wt remove feature-branch $ wt remove old-feature another-branch保留分支,只移除 worktree:
$ wt remove --no-delete-branch feature-branch强制删除未合并的分支:
$ wt remove -D experimental分支清理:六层安全删除判定
wt remove的核心价值在于它替你回答“删掉这个分支会不会丢工作”。默认情况下,只有当分支若合并到默认分支不会带来任何新增改动时,分支才会被删除。这一判定同时兼容未变的 git 历史,也兼容 squash-merge 或 rebase 工作流(提交历史不同但文件改动一致)。
具体而言,Worktrunk 按成本从低到高依次检查六个条件,任一命中即认为分支可删:
- Same commit(同一提交)— 分支 HEAD 与默认分支相等。在
wt list中显示为_。 - Ancestor(祖先)— 分支位于目标分支的历史之内(fast-forward 或 rebase 之后的情形)。显示
⊂。 - No added changes(无新增改动)— 三点 diff(
target...branch)为空。显示⊂。 - Trees match(树相同)— 分支的 tree SHA 与目标的 tree SHA 相等。显示
⊂。 - Merge adds nothing(合并无增益)— 模拟合并产生的 tree 与目标一致。可处理 squash-merged 分支在合并后目标分支又推进、且改动位于不同文件的情形。显示
⊂。 - Patch-id match(补丁指纹匹配)— 分支的全部 diff 与目标上的某个 squash-merge 提交完全匹配。这是模拟合并因目标分支后来修改了分支碰过的文件而产生冲突时的兜底手段。显示
⊂。
两个重要的边界限制:
- 默认分支遍历有上限。为了让单次检查保持快速,第 6 层判定只回溯有限深度;如果合并点之后又落地了数百个提交才发生的 squash merge,会落在上限之外,此时必须显式用
-D才能移除。 - “同提交”检查使用本地默认分支;其余检查中的“target”指默认分支,或当默认分支的 upstream(例如
origin/main)严格领先于本地时,使用 upstream 作为 target。
满足上述条件且工作树为空的分支,会在wt list中以暗色显示,提示其可安全删除。
需要特别理解的是:这六层判定回答的是“删除是否丢失工作”,而另一种失败属于拓扑约束——若某分支被第二个 worktree checkout(只能通过git worktree add --force构造出来),删除该 ref 会让那个 worktree 无法解析HEAD,这正是git branch -d拒绝删除的原因。此类分支无论-D与否都会被保留,命令会指明存活的 checkout 位置。从源码看,这一保护在 src/git/repository/worktrees.rs 的worktree_for_branch中对重复 checkout 的告警逻辑中有所体现:Worktrunk 自身从不创建这种状态,遇到时只会解析到 git 列出的第一个 worktree 并告警。
两个 force 标志:作用域不同
Worktrunk 为两种“破坏性场景”提供了两个独立的 force 标志,切勿混用:
| 标志 | 作用域 | 适用场景 |
|---|---|---|
--force(-f) | Worktree | worktree 存在未提交的改动 |
--force-delete(-D) | Branch | 分支存在未合并的提交 |
$ wt remove feature --force # 移除有未提交改动的 worktree $ wt remove feature -D # 删除未合并的分支 $ wt remove feature --force -D # 两者都用--force会移除含已暂存、已修改和未跟踪文件的脏 worktree;不加此标志时,worktree 有任何未提交改动即移除失败。- 若希望无论合并状态如何都保留分支,使用
--no-delete-branch。注意它与-D互斥:src/commands/remove.rs 在入口处显式校验--force-delete与delete-branch=false(来自--no-delete-branch或[remove] delete-branch = false配置)的组合并直接报错。
delete_branch的取值优先级为:CLI 标志 > 项目/用户配置中的[remove] delete-branch(默认true),见 src/commands/remove.rs。
后台移除与回收站机制
移除默认在后台执行——命令立即返回。整个流程为:worktree 目录被重命名进.git/wt/trash/(同一文件系统内的即时 rename)、git 元数据被修剪(prune)、分支被删除,最后由一个脱离(detached)的rm -rf完成物理清理。跨文件系统的 worktree 会回退到git worktree remove。运行日志位于.git/wt/logs/{branch}/internal/remove.log。需要阻塞等待完成时使用--foreground。
每次wt remove执行后,.git/wt/trash/中超过 24 小时的条目会由一个脱离的rm -rf清扫——这是对“上一次后台移除被中断(SIGKILL、重启、磁盘写满)而遗留孤儿目录”的最终兜底。
从源码结构看,src/commands/remove.rs 中的removal_execution函数将--foreground映射为RemovalExecution::Foreground,默认则是RemovalExecution::Background(BackgroundFallbackMode::Detached);命令头部注释(src/commands/remove.rs)完整描述了两种执行路径的差异:
- 前台:停止 fsmonitor → rename 进
.git/wt/trash/<name>-<timestamp>/→ 修剪元数据 → 删分支 → 对暂存目录做同步remove_dir_all。 - 后台:停止 fsmonitor → rename + 修剪 + 同步删分支 → 在暂存目录上 spawn 脱离的
rm -rf;跨文件系统或锁定(locked)的 worktree 在脱离进程内回退到git worktree remove。
此外,主输出打印完成后还会 fire-and-forget 地运行一次仓库级内部清扫(run_internal_sweep):清掉过期的 trash 条目,并终止 worktree 已不存在的孤儿git fsmonitor--daemon进程,确保它永不阻塞用户可见的进度输出。
元数据修剪的实现值得注意:prune_worktree_entry(src/git/repository/worktrees.rs)选择git worktree remove <path>而非git worktree prune。原因是后者不接受路径过滤,会遍历.git/worktrees/下所有条目——一个“此刻恰好不存在”的 worktree(未挂载的卷、网络挂载断开、进行到一半的mv)与真正被删除的无法区分,批量 prune 会误删旁观者的管理目录(index、ORIG_HEAD、per-worktree reflog、进行中的 rebase/merge 全部随之丢失,且git worktree repair无法重建)。指定路径将一次移除的爆炸半径限制在其意图之内。
--reap:移除前清理残留进程(experimental)
在并行 AI agent 工作流中,一个 worktree 往往跑着post-start启动的 dev server、文件监视器或 language server。--reap会在移除 worktree 前终止这些进程,释放它们占用的端口和文件句柄:
$ wt remove --reap feature ◎ Reaping 2 processes under feature worktree ┃ 51234 node ┃ 51240 esbuild ✓ Reaped 2 processes ◎ Removing feature worktree & branch in background (same commit as main, _)进程发现基于工作目录:任何 cwd 位于 worktree 路径之下的进程都会被选中,先SIGTERM,幸存者在随后收到SIGKILL。
为避免误杀用户本意要保留的工作,--reap内置两道保守守卫:
- 交互进程一律豁免。持有控制终端的进程——交互式 shell,或带未保存缓冲的
vim等终端编辑器——永远不会被 reap;只有脱离型进程才是候选。 - 仅按工作目录发现。在 worktree 内启动之后又变更了目录的进程,或被重新父化到
init的守护进程,不再报告位于 worktree 之下的目录,因而不会被发现。要可靠地清理这类进程,请用wt step tether启动它们——tether 会在 worktree 移除时杀掉整个进程组(配置方法见 step 参考文档)。
Reaping 发生在 worktree 目录被触碰之前,因此与前台/后台模式以及--force标志相互独立。仅支持 Unix;Windows 上--reap会被直接拒绝(src/commands/remove.rs 中通过#[cfg(not(unix))]显式bail!,理由是没有廉价的 Windows 等价物,宁可报错也不静默空转)。发现、控制终端/自身排除以及SIGTERM→SIGKILL升级逻辑位于 src/git/reap.rs,命令层只负责渲染用户可见的进度输出(src/commands/remove.rs)。
JSON 输出:面向自动化调用的结果契约
--format=json在移除完成后向 stdout 打印每个目标一个对象:worktree 移除输出{kind, branch, path, branch_outcome, branch_checked_out_at},仅删分支(branch-only)移除则用pruned字段替代path。
branch_outcome字段命名了分支的最终去向,使调用方能区分“移除拒绝了删除请求”与“根本未请求删除”:
| 值 | 含义 |
|---|---|
deleted | 分支已删除 |
deferred | 移交给脱离的后台进程处理,本次运行永远看不到其结果;--foreground模式不会报告此值 |
not_attempted | 未尝试删除:detached worktree、兄弟 checkout,或--no-delete-branch |
retained_unmerged | 被拒绝:分支未并入目标 |
retained_checked_out | 被拒绝:最终拓扑读取发现有活着的 worktree 正 checkout 该分支 |
retained_raced | 被 compare-and-swap 拒绝——分支在合并判定与删除之间移动了;应重新读取 ref 后重试 |
retained_failed | 删除命令本身失败 |
从源码看,该值对应RemovalPlan::to_json(fate)的第二个参数BranchFate,在 src/commands/remove.rs(单目标路径)与 src/commands/remove.rs(多目标路径,fate 按执行顺序与 JSON 输出顺序严格一致)中被收集。
Hooks 集成
pre-remove钩子在 worktree 被删除之前运行(此时仍可访问 worktree 文件,适合做停服、备份等收尾);post-remove钩子在移除之后运行。钩子的配置、审批模型(用户需显式批准项目声明的钩子命令)见 hook 参考文档。
从源码看,钩子审批遵循“先验证、后审批”的门禁(Approve at the Gate):handle_remove_command先调用prepare_worktree_removal完成全部校验(clean 检查、分支删除安全检查、force 标志处理),通过后才构建并批准钩子计划(src/commands/remove.rs);--no-hooks或用户拒绝审批都会得到空计划,移除继续但跳过全部项目钩子。
Detached HEAD worktree 的处理
Detached worktree 没有分支名,此时应传 worktree 路径而非分支名:
$ wt remove /path/to/worktree多目标移除的执行顺序与部分成功
当一次传入多个目标时,Worktrunk 采取“先全量验证、再执行”的策略,并把计划分三类有序执行:
- 其他 worktree(
others)先行; - 仅分支(branch-only)的删除次之;
- 当前 worktree 最后执行——这与示例输出中“Removing … in background”完成后“Switched to worktree for main @ ~/repo”的行为一致。
每个目标的错误被独立收集而非立即中断,支持部分成功(src/commands/remove.rs)。输入还会去重:同一 worktree 的分支名拼写与路径拼写会被解析为同一规范标识后只规划一次(src/commands/remove.rs 及测试validation_deduplicates_branch_and_path_aliases)。另一处细节:若解析时目标还是已注册 worktree、而准备阶段发现其目录已消失,该计划会降级为 BranchOnly 并被归入 branch-only 桶执行,而不是保留验证前的陈旧分类(测试validation_buckets_missing_worktree_by_returned_plan,src/commands/remove.rs)。
完整命令参考
以下是wt remove的完整参数参考(继承自 remove.md 的命令参考小节):
wt remove - Remove worktree; delete branch if merged Defaults to the current worktree. Usage: wt remove [OPTIONS] [BRANCHES]... Arguments: [BRANCHES]... Branch name or worktree path [default: current] Options: --no-delete-branch Keep branch after removal -D, --force-delete Delete unmerged branches --foreground Run removal in foreground (block until complete) --reap Kill processes started in the worktree [experimental] Before removal, terminate processes whose working directory is under the worktree — dev servers, watchers, language servers. Processes holding a controlling terminal (interactive shells, terminal editors) are left alone. Unix only. -f, --force Force worktree removal Remove a dirty worktree, including staged, modified, and untracked files. Without this flag, removal fails if the worktree has any uncommitted changes. -h, --help Print help (see a summary with '-h') Automation: --no-hooks Skip hooks --format <FORMAT> Output format JSON prints structured result to stdout after removal completes. [default: text] [possible values: text, json] Global Options: -C <path> Working directory for this command --config <path> User config file path --config-set <toml> Override config with inline TOML, e.g. --config-set list.full=true (repeatable) -v, --verbose... Verbose output (-v: info logs + hook/alias template variables on stderr; -vv: also debug logs and raw subprocess output written to .git/wt/logs/). Set WORKTRUNK_VERBOSE=0|1|2 to apply the same level everywhere — including shell completion, which no flag can reach -y, --yes Skip approval prompts使用前提说明:[BRANCHES]...接受分支名或 worktree 路径,缺省为当前 worktree;-C <path>改变的是命令的工作目录(仓库定位),而非 worktree 选择——若已带分支参数再叠加-C会重复命名同一 worktree(这一寻址规则详见 skills/worktrunk/SKILL.md 的“Which worktree a command acts on”一节)。
相关源码与测试入口
- 命令入口与执行编排:src/commands/remove.rs
- worktree 解析、重复 checkout 告警与元数据修剪:src/git/repository/worktrees.rs
- 后台移除的物理清理(rename 进 trash、prune、rm -rf):src/git/remove.rs
--reap的进程发现与信号升级:src/git/reap.rs- 集成测试:tests/integration_tests/remove.rs、tests/integration_tests/step_prune.rs
【免费下载链接】worktrunkWorktrunk is a CLI for Git worktree management, designed for parallel AI agent workflows项目地址: https://gitcode.com/GitHub_Trending/wo/worktrunk
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考