RTK 中 `/tech:remove-worktree` 命令解析:Git Worktree 的安全移除与分支清理
本文以 RTK(Rust Token Killer)仓库自带的 Claude Code 自定义命令 remove-worktree 为主体,完整讲解该命令的用法、脚本实现、逐段工作原理与安全机制。读完后你可以:独立安全地移除指定分支的 worktree(目录 + git 引用 + 本地/远程分支),理解脚本中每道防护(保护分支、未合并确认、主仓库保护、强删兜底)对应的底层 git 语义,并掌握脚本失败时的手动恢复手段。
一、背景:RTK 的 worktree 工作流,remove 是收尾环节
RTK 仓库为并行开发提供了一套基于 git worktree 的 Claude Code 命令族(位于 .claude/commands/tech/),覆盖 worktree 的完整生命周期:
| 命令 | 文档 | 职责 |
|---|---|---|
/tech:worktree |
worktree.md | 创建隔离 worktree,分支名 feature/new-filter 映射到目录 .worktrees/feature-new-filter,并在后台运行 cargo check |
/tech:worktree-status |
worktree-status.md | 查询后台 cargo check 结果(读 /tmp/worktree-cargo-check-*.log) |
/tech:remove-worktree |
remove-worktree.md | 本文主题:移除单个指定 worktree 及其分支 |
/tech:clean-worktree |
clean-worktree.md | 交互式批量清理已合并 worktree |
/tech:clean-worktrees |
clean-worktrees.md | 自动批量清理(仅已合并,无交互,支持 --dry-run) |
创建端约定:分支名必须含斜杠(如 fix/session-bug),斜杠在目录名中被替换为连字符,worktree 统一落在仓库根的 .worktrees/ 下;该目录已写入 .gitignore 第 50 行的 .worktrees/,确保 worktree 内容不污染 git 状态。正因为 worktree 是"用完即弃"的轻量工作区,移除端 /tech:remove-worktree 必须足够安全——它要同时清掉三样东西:物理目录、git 的 worktree 元数据、以及(可选的)本地和远程分支。
另外注意一个细节:/tech:worktree 脚本通过 git rev-parse --git-common-dir 定位主仓库根再创建 worktree,这意味着从 worktree 内部执行命令也能正确解析仓库根。remove 脚本中的"主仓库保护"检查正是同一问题的镜像:必须防止把当前主仓库目录当作普通 worktree 删掉。
二、命令用法与 Frontmatter 约定
文档定义在 remove-worktree.md,其 frontmatter 遵循 Claude Code 自定义命令规范:
---
model: haiku
description: Remove a specific worktree (directory + git reference + branch)
argument-hint: "<branch-name>"
---
model: haiku:指定执行该命令的模型档位;argument-hint:向用户提示命令接受一个参数——分支名。
基本用法就是传分支名:
/tech:remove-worktree feature/new-filter
/tech:remove-worktree fix/session-bug
命令的实际逻辑由文档内嵌的 bash 脚本承载(Agent 从 $ARGUMENTS 取分支名执行)。下面先给出完整脚本,再逐段解析。
三、完整实现脚本
#!/bin/bash
set -euo pipefail
BRANCH_NAME="$ARGUMENTS"
if [ -z "$BRANCH_NAME" ]; then
echo "❌ Usage: /tech:remove-worktree <branch-name>"
echo ""
echo "Example:"
echo " /tech:remove-worktree feature/new-filter"
exit 1
fi
echo "🔍 Checking worktree: $BRANCH_NAME"
echo ""
# 检查 worktree 是否存在于 git
if ! git worktree list | grep -q "$BRANCH_NAME"; then
echo "❌ Worktree not found: $BRANCH_NAME"
echo ""
echo "Available worktrees:"
git worktree list
exit 1
fi
# 从 git 中获取 worktree 路径
WORKTREE_FULL_PATH=$(git worktree list | grep "$BRANCH_NAME" | awk '{print $1}')
# 安全检查:绝不删除主仓库
if [ "$WORKTREE_FULL_PATH" = "$(pwd)" ]; then
echo "❌ Cannot remove main repository worktree"
exit 1
fi
# 安全检查:绝不删除 master 或 main
if [ "$BRANCH_NAME" = "master" ] || [ "$BRANCH_NAME" = "main" ]; then
echo "❌ Cannot remove $BRANCH_NAME (protected branch)"
exit 1
fi
echo "📂 Worktree path: $WORKTREE_FULL_PATH"
echo "🌿 Branch: $BRANCH_NAME"
echo ""
# 检查分支是否已合并
IS_MERGED=false
if git branch --merged master | grep -q "^[* ] ${BRANCH_NAME}$"; then
IS_MERGED=true
echo "✅ Branch is merged into master (safe to delete)"
else
echo "⚠️ Branch is NOT merged into master"
fi
echo ""
# 未合并时要求确认
if [ "$IS_MERGED" = false ]; then
echo "⚠️ This will DELETE unmerged work. Continue? [y/N]"
read -r confirm
if [ "$confirm" != "y" ] && [ "$confirm" != "Y" ]; then
echo "Aborted."
exit 0
fi
fi
# 移除 worktree
echo "🗑️ Removing worktree..."
if git worktree remove "$WORKTREE_FULL_PATH" 2>/dev/null; then
echo "✅ Worktree removed: $WORKTREE_FULL_PATH"
else
echo "⚠️ Git remove failed, forcing removal..."
rm -rf "$WORKTREE_FULL_PATH"
git worktree prune
echo "✅ Worktree forcefully removed"
fi
# 删除本地分支
echo ""
echo "🌿 Deleting branch..."
if [ "$IS_MERGED" = true ]; then
if git branch -d "$BRANCH_NAME" 2>/dev/null; then
echo "✅ Branch deleted (local): $BRANCH_NAME"
else
echo "⚠️ Local branch already deleted or not found"
fi
else
if git branch -D "$BRANCH_NAME" 2>/dev/null; then
echo "✅ Branch force-deleted (local): $BRANCH_NAME"
else
echo "⚠️ Local branch already deleted or not found"
fi
fi
# 删除远程分支(如存在)
echo ""
echo "🌐 Checking remote branch..."
if git ls-remote --heads origin "$BRANCH_NAME" | grep -q "$BRANCH_NAME"; then
echo "⚠️ Remote branch exists. Delete it? [y/N]"
read -r confirm_remote
if [ "$confirm_remote" = "y" ] || [ "$confirm_remote" = "Y" ]; then
if git push origin --delete "$BRANCH_NAME" --no-verify 2>/dev/null; then
echo "✅ Remote branch deleted: $BRANCH_NAME"
else
echo "❌ Failed to delete remote branch (may require permissions)"
fi
else
echo "⏭️ Skipped remote branch deletion"
fi
else
echo "ℹ️ No remote branch found"
fi
echo ""
echo "✅ Cleanup complete!"
echo ""
echo "📊 Remaining worktrees:"
git worktree list
四、逐段原理:每一步对应的 git 机制
4.1 定位目标:git worktree list 的解析
git worktree list 每行格式大致为 <路径> <commit> [<分支名>](主仓库行不带方括号)。脚本先 grep -q "$BRANCH_NAME" 判断存在性,再用 awk '{print $1}' 取第一列得到 worktree 的物理路径。找不到时打印全量列表并以退出码 1 终止——这是典型的 fail-fast 设计,避免后续步骤在一个不存在的目标上空转。
4.2 三道安全检查
- 主仓库保护:
git worktree list的第一行就是主仓库本身。若解析出的路径恰好等于$(pwd),脚本拒绝执行。防止"误把主 checkout 当普通 worktree 删掉"。 - 保护分支:
master/main直接拒删,属于硬编码的白名单黑名单。 - 未合并确认:
git branch --merged master列出所有已合并进master的本地分支,配合grep -q "^[* ] ${BRANCH_NAME}$"精确匹配整行(*表示当前分支、空格表示普通分支),避免feature/fix误匹配feature/fix-v2这类前缀陷阱。未合并分支必须先获得y/Y交互确认,否则脚本以退出码 0 优雅退出("Aborted.")。
适用前提提示:脚本以
master作为合并基准分支硬编码。从仓库当前状态看(主分支为develop,如git worktree list所示),在 RTK 这类以其他分支为默认分支的仓库中,git branch --merged master的判定可能失真(倾向判定为"未合并"),此时脚本会退化为"每次都要求确认"的保守行为——更安全但稍显啰嗦。复用此脚本到其他仓库时,应把master替换为该仓库的实际默认分支。
4.3 移除 worktree:优雅删除 + 强删兜底
git worktree remove "$WORKTREE_FULL_PATH"
git worktree remove 一步完成两件事:删除工作目录、清理主仓库 .git/worktrees/<name>/ 下的元数据(git 为每个附加 worktree 存放的 HEAD、refs 等元信息)。它对"有未提交修改"的 worktree 默认拒绝删除,需要 --force 才能强删——这正是兜底分支存在的原因:
rm -rf "$WORKTREE_FULL_PATH" # 直接删目录
git worktree prune # 回收指向已消失目录的元数据引用
git worktree prune 会扫描 git worktree list 中登记但目录已不存在(或被手工 rm 掉)的条目并清除。这条兜底路径也解释了为什么 prune 在批量清理命令 clean-worktrees.md 里被放在第 1 步:它先回收"目录被外部手段(如 rm -rf)删掉后残留的孤儿引用",再处理合并过的 worktree。
需要留意一个边界情形:若 worktree 存在未提交的本地修改,git worktree remove 会失败并触发强删兜底,但这些未提交内容会随之丢失——这正是前面"未合并需确认"提示 This will DELETE unmerged work 的语义所在。若你确定工作已推送或合并,可放心强删。
4.4 分支删除:-d 与 -D 的分岔
- 已合并:
git branch -d(小写)——git 自身会校验该分支确实已合入上游,未合入则拒绝,形成第二道保险; - 未合并(用户已确认):
git branch -D(大写)——强制删除。
两条路径在分支不存在时都会静默失败(2>/dev/null),脚本打印 "already deleted or not found" 而不中断,使整个流程幂等——对同一分支重复执行命令不会产生错误。
4.5 远程分支:探测 + 可选删除
git ls-remote --heads origin "$BRANCH_NAME"
git push origin --delete "$BRANCH_NAME" --no-verify
git ls-remote --heads 只查询远端引用而不拉取对象,开销小,适合"先探测后询问"的交互模式。远程删除同样要求用户二次确认(默认 N),且使用 --no-verify 跳过 push 钩子——分支删除操作通常不需要也不应触发本地的提交/推送校验脚本。失败时(如无权限)脚本只报错不中断,本地清理结果不受影响。
最后打印 git worktree list 展示剩余 worktree,给用户一个可核对的收尾视图。
五、安全特性小结
原文档 remove-worktree.md 列出的安全特性可汇总为:
- 永不删除
master/main(保护分支黑名单); - 未合并分支必须交互确认,已合并分支走
git branch -d安全删除; - 主仓库保护:路径等于当前工作目录时拒绝执行;
- 一次清理三处状态:目录、git worktree 引用、分支(本地默认 + 远程可选);
- git 删除失败时兜底:
rm -rf+git worktree prune保证不会留下"目录没了、引用还在"的孤儿状态。
六、手动覆盖与故障恢复
当脚本不可用或需要精确控制时,等价的手动序列为(文档 "Manual Override" 一节):
git worktree remove --force <path> # 强制移除 worktree(--force 忽略未提交修改)
git branch -D <branch> # 强制删除本地分支
git push origin --delete <branch> --no-verify # 删除远程分支
对应到 /tech:worktree 约定的命名规则(分支 feature/new-filter → 目录 .worktrees/feature-new-filter),一次完整的手动清理是:
git worktree remove --force .worktrees/feature-new-filter
git worktree prune
git branch -D feature/new-filter
git push origin --delete feature/new-filter --no-verify
创建端文档 worktree.md 的 Cleanup 一节也给出了非强制版本:
git worktree remove .worktrees/feature-new-filter
git worktree prune
七、与批量清理命令的分工
| 场景 | 推荐命令 | 特点 |
|---|---|---|
| 结束单个任务、移除指定分支的 worktree | /tech:remove-worktree <branch> |
精确到单个分支;未合并需确认;可选删远程分支 |
| 合并 PR 后批量回收 | /tech:clean-worktrees [--dry-run] |
全自动,只碰已合并分支,远程分支仅报告不删 |
| 交互式盘点 + 清理 | /tech:clean-worktree |
先展示状态,人工确认后删除 |
可以推断出这套命令族的设计意图:remove-worktree 负责"点"(单个任务收尾,含远程分支决策),clean-worktrees 负责"面"(定期维护,无交互),两者共享同一套安全基线——保护 master/main、保护主仓库、只强删已确认目标、rm -rf + prune 兜底。
八、复用建议
这套脚本不依赖 RTK 本身,任何使用 git worktree 的仓库都可以直接借用,只需调整三处:
- 将
git branch --merged master中的master改为仓库实际默认分支(可先用git symbolic-ref refs/remotes/origin/HEAD查询); - worktree 目录约定(脚本按"分支名出现在
git worktree list中"来匹配,与.worktrees/约定无强耦合); - 若仓库无
origin远程(纯本地仓库),可省略 4.5 节的远程探测段,git ls-remote会失败但2>/dev/null已使其无害。
配合创建端 worktree.md(含 --fast/--no-check 参数与后台 cargo check)、状态端 worktree-status.md,本文的移除端构成了一个完整的"创建 → 验证 → 移除"闭环,可作为 AI 辅助并行开发工作流的参考实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00