Git 工作树很好用,却止步于仓库边界

Dev Genius - Medium 2026-10-02T18:26:47.963709

Git 工作树(worktree)确实好用,但它的作用范围只限于单个仓库,一跨出仓库边界就失效了。

那些讲解 worktree 的文章,讲对了什么

Git 工作树(git worktree add)已经成为现代软件工程和智能体(agent)工作流中不可或缺的基础操作。大多数社区文章在讲怎么用 git 工作树服务 AI 编码智能体时,对单仓库场景下的核心机制都讲得很到位:

对单仓库工程来说,git 工作树确实提供了理想的本地执行环境。智能体在独立的文件树中操作,语言服务器能正确索引文件,构建工具原生运行,git status 也能干净地追踪改动。

但现代软件应用很少只待在一个仓库里。一旦某个功能跨多个仓库,单仓库的工作树模型就会撞上一道硬性的结构边界。

三仓库改动

设想多仓库(polyrepo)生态里一个很常见的架构任务:修改 API 契约。这个功能横跨三个独立的 git 仓库:

要用 AI 编码智能体完成这次改动,你需要一套复合工作区配置:

手动敲原生 git worktree 命令,得在三个不同的仓库路径之间走完一长串繁琐步骤:

# Manual worktree creation across three repos
git -C ~/repos/api-service worktree add -b feature-auth ../worktrees/api-feature-auth
git -C ~/repos/web-frontend worktree add -b feature-auth ../worktrees/web-feature-auth
git -C ~/repos/docs-site worktree add ../worktrees/docs-main main

# Create a temporary workspace directory and symlink worktrees
mkdir -p ~/workspaces/feature-auth
ln -s ~/worktrees/api-feature-auth ~/workspaces/feature-auth/api
ln -s ~/worktrees/web-feature-auth ~/workspaces/feature-auth/web
ln -s ~/worktrees/docs-main ~/workspaces/feature-auth/docs

每做一个功能都要手动执行一遍,又慢又重复,还容易出错。于是开发者干脆写 shell 脚本,把多仓库的 worktree 组装过程自动化。

还是自己写脚本吧

为了给 AI 智能体会话自动搭建多仓库的 worktree,开发者往往会写一段大约 60 行的 bash 脚本,下面就是一个典型例子:

#!/usr/bin/env bash
set -euo pipefail

FEATURE_NAME="${1:-}"
if [[ -z "$FEATURE_NAME" ]]; then
  echo "Error: Feature name required."
  echo "Usage: $0 <feature-name>"
  exit 1
fi

WORKSPACE_DIR="$HOME/.workspaces/$FEATURE_NAME"
mkdir -p "$WORKSPACE_DIR"

declare -A REPOS=(
  ["api"]="$HOME/repos/api-service"
  ["web"]="$HOME/repos/web-frontend"
  ["docs"]="$HOME/repos/docs-site"
)

echo "Initializing multi-repo worktrees for feature: $FEATURE_NAME"

for NAME in "${!REPOS[@]}"; do
  REPO_PATH="${REPOS[$NAME]}"
  TARGET_WORKTREE="$REPO_PATH/../worktrees/$NAME-$FEATURE_NAME"

  if [[ "$NAME" == "docs" ]]; then
    # Read-only reference repository stays checked out on main
    echo "Creating reference worktree for $NAME on main..."
    git -C "$REPO_PATH" worktree add "$TARGET_WORKTREE" main 2>/dev/null || true
  else
    # Feature repositories check out feature branch
    echo "Creating feature worktree for $NAME on branch $FEATURE_NAME..."
    git -C "$REPO_PATH" worktree add -b "$FEATURE_NAME" "$TARGET_WORKTREE" 2>/dev/null || true
  fi

  # Symlink worktree into unified workspace folder
  ln -sfn "$TARGET_WORKTREE" "$WORKSPACE_DIR/$NAME"
done

# Create combined harness instructions
cat <<EOF > "$WORKSPACE_DIR/CLAUDE.md"
# Multi-Repo Workspace Context: $FEATURE_NAME

- api/: Rust backend service (editable feature worktree)
- web/: TypeScript web application (editable feature worktree)
- docs/: Documentation site (reference context only, DO NOT EDIT)
EOF

echo "Multi-repo workspace materialized at: $WORKSPACE_DIR"
cd "$WORKSPACE_DIR" && claude

这个脚本会自动创建 worktree,把各个目录软链接到一个统一的工作区文件夹里,生成一份基础的 CLAUDE.md,然后启动智能体的执行框架。

用来跑本地简单演示,这段脚本没问题。但要是拿到生产级的工程环境里用,就会暴露出四个根本性的短板。

脚本做不到的四件事

这种临时写的 shell 脚本,虽然能靠软链接把目录拼到一起,但要让多个仓库的智能体安全、稳定地运行,它缺少必要的技术底座。

保护对参考仓库的写入——脚本的做法却是:仅靠一条软性提示规则来约束。让分支与生命周期对齐——脚本的做法却是:留下脏工作树和孤儿树。把指令真正落地——脚本的做法却是:弄脏每个仓库里已被跟踪的文件。交付功能——脚本的做法却是:把每个分支各自推送。1. POSIX 文件系统写保护

shell 脚本用普通用户写权限(0755/0644)在磁盘上创建 worktree。要限制对 docs/ 的修改,脚本完全依赖 CLAUDE.md 里的一句文本指令:“docs/:仅供参考,切勿编辑”。在上下文负载较高或多步推理任务中,语言模型会不定时违反提示词指令。智能体在修构建或 lint 错误时,会直接改 docs/ 里的文件,从而改动参考仓库的 git 状态。脚本没法在操作系统层面强制实施写限制。

2. 会话生命周期与分支对齐

智能体会话结束或崩溃时,脚本会在多个仓库目录里留下活跃的 worktree 和悬空分支。如果会话中途失败:

开发者必须逐个进入每个仓库目录,手动运行 git worktree prune,再删掉孤儿功能分支。另外,如果某个仓库创建分支失败(比如 main 上有未提交的冲突),脚本会让整个工作区处于部分初始化、跨仓库不一致的状态,而且没有原子回滚。

3. 干净的指令落地

脚本通过在工作区根目录写一个静态 CLAUDE.md 来注入指令。但真实仓库里本来就有各自厂商原生的指令文件(例如 api/CLAUDE.md 和 web/CLAUDE.md)。脚本没法跨仓库合并或综合这些指令。

如果脚本直接把指令文件写进各个代码仓库的 worktree(工作树),git status 里就会多出未跟踪的脏文件,智能体可能不小心把它们提交上去。

4. 多仓库功能交付

智能体在 api/ 和 web/ 里改完代码后,要交付这个功能,就得验证状态、往多个仓库推送功能分支,还要建立跨仓库的 Pull Request 引用。而 shell 脚本把交付全甩给了手动 git 操作。它没法验证挂载的多个仓库是否已原子化地准备好一个功能,没法追踪这次会话实际改了哪些仓库,也没法触发多仓库同步推送分支(deliver)。

注意,多仓库交付协调的是多个仓库之间的推送操作,而不是去搞复杂的依赖排序或自动合并顺序。

用 chmod 守护写入,而不是靠提示词

自定义多仓库 worktree 脚本最核心的漏洞,就是靠 LLM 遵守提示词来做写保护。软性提示词和软件钩子在上下文饱和、智能体复杂推理或直接执行 shell 命令时,迟早会失效。可靠的写保护需要在操作系统层面用 POSIX 文件权限掩码(chmod)强制文件不可修改。

if mode & 0o222 != 0 {
    chmod(path, mode & !0o222)?;
}

在编排式视图目录(View Directory)架构中:

未提升的上下文 worktree:当某个参考仓库(比如 docs/)被挂载进会话工作区时,通过 clear_write_bits 递归地去掉它 worktree 中所有文件和目录的写位(0o222)。

内核确定性强制:如果智能体试图修改未提升仓库里的文件——不管是使用文件编辑工具、bash 输出重定向(>),还是自动代码修改器——操作系统内核都会立刻以 EACCES(权限拒绝)错误拒绝这次写入。

查看原文