首页
/ rtk 的 /tech:clean-worktrees 命令:自动清理陈旧 Git worktree 的脚本设计与实现剖析

rtk 的 /tech:clean-worktrees 命令:自动清理陈旧 Git worktree 的脚本设计与实现剖析

2026-09-05 17:40:44作者:吴年前Myrtle

本文以 rtk 仓库中的 Claude Code 斜杠命令 clean-worktrees.md 为主体,完整解析其“无人值守清理已合并 worktree”的设计:从 --dry-run 预览模式、三步式 bash 脚本(prune 引用 → 识别已合并分支 → 删除 worktree/分支)到五重安全边界。读完你可以掌握一套可直接复用的 worktree 自动化清理脚本,并理解 rtk 代理层(rtk git worktree)如何对这类 git 命令做输出压缩与失败透传。

命令定位:自动版 vs 交互式版

rtk 仓库的 .claude/commands/tech/ 目录下围绕 Git worktree 提供了一组配套命令,/tech:clean-worktrees 是其中的“批量自动清理”角色。frontmatter 中 model: haiku 声明该命令由 haiku 模型执行,description 为 “Auto-clean all stale worktrees (merged branches)”。

它与同目录的交互式命令 clean-worktree.md 的分工是:

命令 交互性 清理范围 适用场景
/tech:clean-worktree 交互式,删除前要求输入 y/Y 确认 已合并进 master 的 worktree 希望人工过目的场合
/tech:clean-worktrees 全自动,无交互(仅限已合并分支,天然安全) 同上,一次性批量 合并 PR 后、每周例行维护

值得注意的细节:自动版脚本额外增加了一条交互式版没有的保护规则——[ "$path" = "$(pwd)" ] && continue,即当前所在目录(主仓库)无论如何都不会被移除;而交互式版 clean-worktree.md 的脚本只跳过 master/main 分支。此外,仓库根目录还有一份 clean-worktrees.md,是去掉 emoji 输出的精简变体,逻辑一致。

用法与参数

原文档给出的调用方式只有两条:

/tech:clean-worktrees           # Clean all merged worktrees(清理所有已合并 worktree)
/tech:clean-worktrees --dry-run # Preview what would be deleted(预览将被删除的内容,不落盘)

从脚本实现看,参数解析只认一个开关:

DRY_RUN=false
if [[ "${ARGUMENTS:-}" == *"--dry-run"* ]]; then
  DRY_RUN=true
fi

即只要 Claude Code 传入的 $ARGUMENTS 中包含 --dry-run 子串即进入预览模式;该模式下脚本只打印 “Would delete:” 清单并 exit 0,不执行任何删除。

实现脚本逐步剖析

完整脚本以 set -euo pipefail 开头(出错即停、未定义变量报错、管道任一环节失败即失败),整体分为三步。以下逐段说明,行号对应 clean-worktrees.md 中的 Implementation 代码块。

第一步:git worktree prune -v 清理悬空引用

PRUNED=$(git worktree prune -v 2>&1)
if [ -n "$PRUNED" ]; then
  echo "$PRUNED"
  echo "✅ Stale references pruned"
else
  echo "✅ No stale references found"
fi

git worktree prune 会删除那些物理目录已经不存在、但 git 元数据中还登记着的 worktree 记录(典型成因:手动 rm -rf 过 worktree 目录、或机器迁移)。-v 让 git 打印实际被清理的条目,脚本据此向用户区分“清理过”和“本来就干净”两种输出。这一步先于扫描执行,保证后续 git worktree list 的结果不含僵尸条目。

第二步:解析 git worktree list 并筛选已合并分支

while IFS= read -r line; do
  path=$(echo "$line" | awk '{print $1}')
  branch=$(echo "$line" | grep -oE '\[.*\]' | tr -d '[]' || true)

  [ -z "$branch" ] && continue
  [ "$branch" = "master" ] && continue
  [ "$branch" = "main" ] && continue
  [ "$path" = "$(pwd)" ] && continue

  if git branch --merged master | grep -q "^[* ] ${branch}$" 2>/dev/null; then
    MERGED_COUNT=$((MERGED_COUNT + 1))
    MERGED_BRANCHES+=("$branch|$path")
    echo "  ✓ $branch (merged)"
  fi
done < <(git worktree list)

这段是整个命令的筛选核心,逐条拆解:

  • 数据格式git worktree list 每行形如 /path/to/worktree abc1234 [branch](detached 时方括号内是 commit 描述)。脚本用 awk '{print $1}' 取路径,用 grep -oE '\[.*\]'tr -d '[]' 提取方括号内的分支名;|| true 防止 grep 无匹配时触发 set -e 退出。
  • 三重跳过规则:分支名为空的行(detached HEAD)跳过;mastermain 作为受保护分支跳过;路径等于当前工作目录(主仓库本身)跳过。
  • 合并判定git branch --merged master | grep -q "^[* ] ${branch}$"git branch --merged 列出所有已并入 master 的本地分支,^[* ] 同时兼容当前分支(* 前缀)和普通分支(空格前缀),末尾 $ 锚定避免 feature/a 误匹配 feature/a-bugfix
  • 候选记录:命中的 worktree 以 分支|路径 形式存入 MERGED_BRANCHES 数组,供第三步遍历。

一个隐含前提值得注意:合并判定硬编码以 master 为集成基线。可以推断,若项目实际以 main 为合并目标,此脚本只能保护 main 不被删、却不会把“已并入 main 的分支”识别为 merged。迁移脚本时应把两处 master 替换为实际基线分支。

若无任何命中,脚本直接打印 “No merged worktrees found”、附上 git worktree list 现状并 exit 0——零副作用退出。

第三步:删除 worktree 与本地分支,报告远端状态

非 dry-run 模式下,对每个候选执行“三连击”:

# 1) 删 worktree,失败则强制兜底
if git worktree remove "$path" 2>/dev/null; then
  echo "  ✅ Worktree removed"
else
  echo "  ⚠️  Git remove failed, forcing..."
  rm -rf "$path" 2>/dev/null || true
  git worktree prune 2>/dev/null || true
  echo "  ✅ Worktree forcefully removed"
fi

# 2) 删本地分支(-d 只会删已合并分支,与前置判定自洽)
if git branch -d "$branch" 2>/dev/null; then
  echo "  ✅ Local branch deleted"
else
  echo "  ⚠️  Local branch already deleted"
fi

# 3) 探测远端分支:只报告,不自动删
if git ls-remote --heads origin "$branch" 2>/dev/null | grep -q "$branch"; then
  echo "  🌐 Remote branch exists: $branch"
  echo "     (Skipping auto-delete - use /tech:remove-worktree for manual removal)"
fi

三个设计点:

  1. force 兜底git worktree remove 在目录含未跟踪文件时会拒绝。脚本退化为 rm -rf + git worktree prune 手工清元数据。这一兜底之所以“安全”,是因为目标已经被第二步判定为 merged——前提是第二步的合并判定可信。
  2. 分支删除用 -d 而非 -Dgit branch -d 本身会拒绝删除未合并分支,形成第二道保险;失败分支被统一解释为“已删除”,不计为错误。
  3. 远端只报告不删除:删除远端分支会影响他人(fork 跟踪、CI 引用),脚本通过 git ls-remote --heads origin 探测后仅提示“可用 /tech:remove-worktree 手动处理”,把破坏半径限定在本地。

循环结束后打印汇总:Removed: $REMOVED_COUNT worktree(s)、剩余 worktree 列表,以及 .worktrees/ 目录占用:

WORKTREES_SIZE=$(du -sh .worktrees/ 2>/dev/null | awk '{print $1}' || echo "N/A")
echo "💾 Worktrees disk usage: $WORKTREES_SIZE"

从脚本结构看,声明的 FAILED_COUNT 在删除循环中并未被任何分支自增,因此汇总中的 “Failed” 行实际不会触发——失败信息是通过每个 worktree 的 “⚠️ … forcing…” 现场输出传达的。这属于可读性冗余而非功能缺陷,但复写脚本时可考虑让 force 兜底时递增该计数。

安全机制逐条核对

原文档 “Safety Features” 一节列出的五条,均可在脚本中找到对应实现:

安全特性 脚本对应实现
只动已合并分支,绝不触碰未合并工作 第二步 git branch --merged master 前置过滤;第三步 git branch -d 二次确认
受保护分支 [ "$branch" = "master" ] && continue[ "$branch" = "main" ] && continue
主仓库永不被删 [ "$path" = "$(pwd)" ] && continue
远端分支只报告不自动删 git ls-remote 探测后仅 echo 提示
dry-run 预览 --dry-run 时打印 “Would delete:” 清单后 exit 0

配套的 remove-worktree.md 命令则处理自动版刻意不覆盖的场景:删除单个指定 worktree(含未合并分支的交互式确认、可选的远端分支删除 git push origin --delete),可视为自动版的逃生舱口。

使用时机与配套命令

原文档给出的三个触发时机:

  • 合并 PR 进 master 之后:feature 分支的生命周期结束,worktree 成为纯磁盘垃圾;
  • 每周例行维护:积累的陈旧 worktree 会膨胀 .worktrees/ 占用;
  • 创建新 worktree 之前:先跑一次 /tech:clean-worktrees --dry-run 预览,避免“worktree already exists”冲突。

它与 worktree.md(创建工作树 + 后台 cargo check)、worktree-status.md(查后台检查状态)构成完整闭环:/tech:worktree 创建 → /tech:worktree-status 查看 → 开发合并 → /tech:clean-worktrees 收尾。

对于未合并分支的手动强删,原文档指路 /tech:remove-worktree <branch>;裸 git 等价操作为:

git worktree remove --force <path>
git branch -D <branch_name>
git worktree prune

rtk 代理层视角:rtk git worktree 如何压缩输出

rtk 本身的定位是“减少 LLM token 消耗的 CLI 代理”,而上述清理脚本的每一步(prunelistremove)都经过 rtk git 时会被专门处理。从 git.rs 的源码看,调用链由 gt_cmd.rs"worktree" => run(GitCommand::Worktree, ...) 路由到 run_worktreegit.rs#L2135),分两种模式:

  1. 动作模式:参数含 add/remove/prune/lock/unlock/move 之一时,原样透传给真实 git;成功仅输出一个 ok,失败则打印 FAILED: git worktree ... 及 stderr 并透传原始退出码——这保证清理脚本里 set -euo pipefailif git worktree remove ... 的成败判断在 rtk 代理下依然成立。
  2. 列表模式(默认):执行 git worktree list 后由 filter_worktree_listgit.rs#L2208)压缩输出——按空白切分 路径/短hash/[分支] 三段,把绝对路径开头的 $HOME 替换为 ~,缩短每行长度;随后再经过 never_worse 守护,确保“过滤后输出绝不会比原始输出更长”,否则回退为原始输出。

测试侧有对应覆盖:test_filter_worktree_list 验证路径截断与格式归一,test_run_worktree_list_propagates_failure(对应 issue #2497)验证在仓库外执行 git worktree list 的非零退出码必须向上传播——这正是清理脚本依赖的失败语义。

换言之,若你在 rtk 环境里运行本命令,git worktree list 的每行路径会短一截(~ 前缀),而脚本解析只取 $1 路径与方括号分支名,两种长度下行为一致;prune -v-v 输出在动作模式透传下也原样可见。

小结

/tech:clean-worktrees 的价值不在脚本本身有多复杂,而在把“可安全自动执行”的边界划得非常清楚:合并判定(--merged)+ 受保护分支 + 主仓库豁免三重前置过滤决定“能不能删”,-d 软删与远端只报告决定“删多狠”,--dry-run 决定“删不删”。这套“前置过滤 + 软操作 + 预览模式”的三层结构,是写任何批量清理类 Agent 命令时都值得照搬的模板。完整脚本与参数说明见 clean-worktrees.md,交互式版本见 clean-worktree.md,代理层实现见 git.rs

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.78 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
987
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384