# Git Worktree 支持

> 在独立工作区中运行 CodeBuddy,实现并行开发和安全隔离。

## 概述

Git worktree 允许在同一仓库中同时拥有多个工作目录,每个目录可以检出不同的分支。CodeBuddy Code 利用这一特性提供:

- **并行开发**:在不影响主工作区的情况下进行实验性开发
- **安全隔离**:AI 的所有更改都在独立目录中进行
- **零存储开销**:worktree 共享 Git 对象,不会复制整个仓库
- **子代理隔离**:支持多个 AI 子代理在独立 worktree 中并行工作
- **tmux 集成**:可选在独立的 tmux 会话中运行
- **非 Git 支持**:通过 WorktreeCreate/WorktreeRemove hooks，SVN、Perforce 等项目也能使用 worktree 隔离

## 快速开始

```bash
# 自动生成名称创建 worktree
codebuddy --worktree

# 指定名称创建 worktree
codebuddy --worktree feature-auth

# 指定基础分支创建 worktree
codebuddy --worktree --worktree-branch origin/develop  # 基于远程 develop 分支
codebuddy --worktree --worktree-branch feature/foo     # 基于本地 feature/foo 分支

# 在 tmux 会话中运行(推荐用于长时间任务)
codebuddy --worktree --tmux

# 基于 PR/MR 创建 worktree(用于代码审查)
codebuddy --worktree "#123"                                    # GitHub PR 编号
codebuddy --worktree "https://github.com/owner/repo/pull/123" # GitHub PR 链接
codebuddy --worktree "https://gitlab.com/owner/repo/-/merge_requests/456" # GitLab MR 链接
codebuddy --worktree "https://cnb.woa.com/owner/repo/-/pulls/789" # CNB PR 链接
```

### 在会话中创建

已启动 CodeBuddy Code 会话后,可以通过自然语言请求创建 worktree:

```
> start a worktree
> work in a worktree
> 启动 worktree
```

AI 会自动调用 `EnterWorktree` 工具创建隔离工作目录并切换过去。

> **注意**:只有明确提到 "worktree" 时才会触发,说 "帮我开一个分支" 或 "修复这个 bug" 不会自动创建 worktree。

## CLI 参数

| 参数 | 说明 | 示例 |
|:-----|:-----|:-----|
| `--worktree [name]` | 创建并进入 worktree | `--worktree` 或 `--worktree my-feature` |
| `--worktree-branch <branch>` | 指定基础分支（需配合 `--worktree`） | `--worktree-branch origin/develop` 或 `--worktree-branch feature/foo` |
| `--tmux` | 在 tmux 会话中运行（需要安装 tmux） | `--worktree --tmux` |
| `--tmux-classic` | 使用传统 tmux 模式（不使用 popup） | `--worktree --tmux --tmux-classic` |

## 工作流程

### 创建 Worktree

当使用 `--worktree` 参数启动时:

1. CodeBuddy 在 `.codebuddy/worktrees/` 目录下创建新的 worktree
2. 确定基础分支:
   - 如果指定了 `--worktree-branch origin/xxx`，基于远程分支 `origin/xxx`
   - 如果指定了 `--worktree-branch xxx`，基于本地分支 `xxx`
   - 如果未指定，默认基于远程默认分支（通常是 `origin/main` 或 `origin/master`）
3. 自动创建对应的分支(如 `worktree-feature-auth`)
4. 将工作目录切换到 worktree
5. 执行初始化(复制设置、创建符号链接、复制 `.worktreeinclude` 文件等)

**分支不存在时的行为**：
- 如果指定的分支（远程或本地）不存在，会打印警告并自动降级到远程默认分支继续创建
- 不会中断启动流程

### 退出时的选择

当你退出 worktree 会话时，CodeBuddy 会检测变更并提供选项：

- **保留 Worktree**：保留所有更改和分支，可以稍后继续
- **删除 Worktree**：清理 worktree 和关联分支
- **保留但退出 tmux**：（tmux 模式）保留 worktree 但关闭 tmux 会话

### 变更检测

退出前，CodeBuddy 会检测：

- 未提交的文件变更
- 未推送的提交

如果没有任何变更，worktree 会自动清理。

## 配置

在 `settings.json` 中配置 worktree 行为:

```json
{
  "worktree": {
    "symlinkDirectories": ["node_modules", ".next", ".cache", "dist"]
  }
}
```

### 配置项

| 配置项 | 说明 | 默认值 |
|:-------|:-----|:-------|
| `symlinkDirectories` | 从主仓库符号链接到 worktree 的目录 | `[]` |

符号链接目录可以避免在每个 worktree 中重复安装依赖,节省时间和磁盘空间。

### 同步本地忽略文件(.worktreeinclude)

`.env.local`、`.env.development` 等本地配置文件通常被 `.gitignore` 排除,但新 worktree 中同样需要它们才能正常运行。

**解决方式**:在仓库根目录创建 `.worktreeinclude` 文件,列出需要复制的文件(语法与 `.gitignore` 相同):

```gitignore
# .worktreeinclude
# 列出需要复制到新 worktree 的本地文件

# 环境变量
.env.local
.env.development.local
.env.test.local

# 本地 IDE 配置
.vscode/settings.json

# 本地证书或密钥(如有)
certs/localhost.pem
```

每次创建新 worktree 时,这些文件会自动从主仓库根目录复制过去。**复用已有 worktree 时不会重新复制**。

> **提示**:`.worktreeinclude` 应该提交到 Git,这样团队成员都能受益。

## Hook-based Worktree

默认情况下，worktree 功能依赖 Git 内置的 `git worktree` 命令。对于使用 SVN、Perforce 或没有版本控制的项目，可以通过配置 **WorktreeCreate / WorktreeRemove hooks** 来使用 worktree 隔离功能。

### 决策优先级

```
1. 配置了 WorktreeCreate hook？ → 使用 hook 创建（即使在 Git 仓库中也优先使用 hook）
2. 在 Git 仓库中？              → 使用 git worktree
3. 都不满足？                   → 报错并提示配置 hook
```

**核心规则**：
- **Hook 优先于 Git**：配置了 WorktreeCreate hook 后，即使在 Git 仓库中也走 hook，不走 `git worktree`
- **创建失败不降级**：WorktreeCreate hook 失败时不会回退到 git worktree，直接报错
- **删除失败不阻塞**：WorktreeRemove hook 失败只记录警告，不阻止退出流程

### 配置方式

在 `.codebuddy/settings.json`（项目级）或 `~/.codebuddy/settings.json`（全局）中配置：

```json
{
    "hooks": {
        "WorktreeCreate": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash ~/.codebuddy/hooks/worktree-create.sh"
                    }
                ]
            }
        ],
        "WorktreeRemove": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash ~/.codebuddy/hooks/worktree-remove.sh"
                    }
                ]
            }
        ]
    }
}
```

两个 hook 独立配置——可以只配 WorktreeCreate 不配 WorktreeRemove（此时退出时不会自动清理，会提示手动删除目录）。

### 输入数据（stdin）

Hook 脚本通过 **stdin** 接收 JSON 格式的上下文数据。

**WorktreeCreate 输入**：

```json
{
    "hook_event_name": "WorktreeCreate",
    "session_id": "abc123def456",
    "cwd": "/Users/user/project",
    "transcript_path": "/Users/user/.codebuddy/sessions/abc123/transcript.md",
    "name": "feature-auth"
}
```

| 字段 | 说明 |
|:-----|:-----|
| `name` | worktree 名称（用户指定或系统自动生成） |
| `cwd` | 当前工作目录 |
| `session_id` | 当前会话 ID |
| `transcript_path` | 对话记录文件路径 |

**WorktreeRemove 输入**：

```json
{
    "hook_event_name": "WorktreeRemove",
    "session_id": "abc123def456",
    "cwd": "/Users/user/project",
    "transcript_path": "/Users/user/.codebuddy/sessions/abc123/transcript.md",
    "worktree_path": "/tmp/codebuddy-worktrees/feature-auth"
}
```

| 字段 | 说明 |
|:-----|:-----|
| `worktree_path` | 要删除的 worktree 绝对路径 |

### 输出与 Exit Code

**WorktreeCreate**：

- **stdout**：必须输出创建好的 worktree **绝对路径**（取最后一行非空行）
- **stderr**：可输出日志，不影响路径解析
- **exit code 0**：成功，解析 stdout 中的路径
- **exit code 非 0**：创建失败，报错并退出（不降级到 git worktree）

```bash
# 正确示例：日志输出到 stderr，路径输出到 stdout
echo "Initializing SVN checkout..." >&2
echo "/home/user/.codebuddy/worktrees/feature-auth"
```

**WorktreeRemove**：

- 没有决定控制：任何 exit code 都不会阻止退出流程
- 失败仅记录警告日志

### 退出行为差异

与 Git worktree 不同，hook-based worktree 退出时**始终显示 keep/remove 菜单**，不做变更检测（因为非 Git 项目没有 `git status`）。

### Hook 脚本模板

**WorktreeCreate** (`~/.codebuddy/hooks/worktree-create.sh`):

```bash
#!/bin/bash
set -e

INPUT=$(cat)
NAME=$(echo "$INPUT" | jq -r '.name')
CWD=$(echo "$INPUT" | jq -r '.cwd')

WORKTREE_PATH="$HOME/.codebuddy/worktrees/$NAME"
mkdir -p "$WORKTREE_PATH"

# 在这里添加 VCS 初始化逻辑（SVN checkout、P4 sync、目录复制等）
cp -r "$CWD"/* "$WORKTREE_PATH/" 2>/dev/null || true

# 最后一行输出绝对路径到 stdout（必需）
echo "$WORKTREE_PATH"
```

**WorktreeRemove** (`~/.codebuddy/hooks/worktree-remove.sh`):

```bash
#!/bin/bash

INPUT=$(cat)
WORKTREE_PATH=$(echo "$INPUT" | jq -r '.worktree_path')

# 安全检查：确保路径在预期范围内
if [[ "$WORKTREE_PATH" != "$HOME/.codebuddy/worktrees/"* ]]; then
    echo "Refusing to remove path outside worktrees directory: $WORKTREE_PATH" >&2
    exit 1
fi

rm -rf "$WORKTREE_PATH"
```

> **提示**：脚本依赖 `jq` 解析 JSON。macOS：`brew install jq`，Linux：`apt install jq`。

### 实战示例

**SVN 项目**：

```json
{
    "hooks": {
        "WorktreeCreate": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash -c 'INPUT=$(cat); NAME=$(echo $INPUT | jq -r .name); DIR=\"$HOME/.codebuddy/worktrees/$NAME\"; svn checkout https://svn.example.com/repo/trunk \"$DIR\" >&2 && echo \"$DIR\"'"
                    }
                ]
            }
        ],
        "WorktreeRemove": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash -c 'cat | jq -r .worktree_path | xargs rm -rf'"
                    }
                ]
            }
        ]
    }
}
```

**无 VCS 的项目（目录复制）**：

```json
{
    "hooks": {
        "WorktreeCreate": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash -c 'INPUT=$(cat); NAME=$(echo $INPUT | jq -r .name); CWD=$(echo $INPUT | jq -r .cwd); DIR=\"$HOME/.codebuddy/worktrees/$NAME\"; cp -r \"$CWD\" \"$DIR\" && echo \"$DIR\"'"
                    }
                ]
            }
        ],
        "WorktreeRemove": [
            {
                "hooks": [
                    {
                        "type": "command",
                        "command": "bash -c 'cat | jq -r .worktree_path | xargs rm -rf'"
                    }
                ]
            }
        ]
    }
}
```

## tmux 集成

使用 `--tmux` 参数可以在独立的 tmux 会话中运行 CodeBuddy：

```bash
# 基本用法
codebuddy --worktree --tmux

# 使用传统模式（不使用 popup）
codebuddy --worktree --tmux --tmux-classic
```

### tmux 要求

- tmux 版本 3.2 或更高（支持 popup 功能）
- 如果版本较低，会自动回退到传统模式

### 退出 tmux 会话

- 按 `Ctrl+D` 或输入 `/exit` 退出 CodeBuddy
- tmux 会话会根据你的选择保留或关闭

## 目录结构

```
your-repo/
├── .codebuddy/
│   └── worktrees/
│       ├── feature-auth/      # worktree 目录
│       │   ├── node_modules -> ../../node_modules  # 符号链接
│       │   └── ...
│       └── fix-bug-123/
├── .worktreeinclude           # 定义需要复制的本地文件(建议提交)
└── ...
```

## 手动管理 Worktree

CodeBuddy Code 在会话退出时会自动处理 worktree 的清理。但有时需要在会话外手动管理——比如清理残留的 worktree,或者需要更灵活地控制分支和目录位置。

### 在会话外清理 Worktree

直接使用 Git 命令清理:

```bash
# 查看当前所有 worktree
git worktree list

# 删除指定 worktree(worktree 内没有未提交改动时)
git worktree remove .codebuddy/worktrees/feature-auth

# 强制删除(有未提交改动时)
git worktree remove --force .codebuddy/worktrees/feature-auth

# 同时删除对应的分支
git branch -D worktree-feature-auth

# 清理失效的 worktree 引用(目录已删但 Git 还记着)
git worktree prune
```

### 用 Git 直接创建 Worktree

有时需要检出已有分支,或者把 worktree 放到仓库目录之外,可以绕开 CodeBuddy Code 直接用 Git 创建,然后再在里面启动会话:

```bash
# 新建分支并创建 worktree(放在仓库外)
git worktree add ../project-feature-a -b feature-a

# 检出已有分支
git worktree add ../project-bugfix bugfix-123

# 进入 worktree,启动 CodeBuddy Code
cd ../project-feature-a && codebuddy

# 用完后清理
git worktree remove ../project-feature-a
```

这种方式适合需要把 worktree 放在特定位置、或者复用现有分支的场景。

## 子代理隔离

当让 CodeBuddy Code 启动多个子代理并行工作时,可以让每个子代理运行在独立的 worktree 中,避免文件冲突。

### `isolation: worktree` 是什么

在自定义 Agent 的 frontmatter 中加上 `isolation: worktree`,每次用 `Task` 工具启动这个 Agent 时,系统会自动为它创建一个独立的 worktree,而不是在主仓库目录里直接运行。

这意味着:
- 多个子代理可以同时修改同名文件,互不影响
- 主仓库目录保持干净,子代理的临时改动不会污染它
- 子代理完成后,worktree 按需保留或删除

### 创建隔离型自定义 Agent

在 `.codebuddy/agents/` 目录下创建 Markdown 文件:

```markdown

你在一个独立的 git worktree 中运行。
请专注完成分配给你的任务,完成后汇报结果。
```

### 使用效果

主 Agent 用 `Task` 工具启动该 Agent 时,会自动为它创建独立的 worktree。多个子代理可以同时工作,即使修改了相同的文件也不会冲突。

子代理完成后:
- **没有变更**:worktree 自动删除
- **有变更**:worktree 保留,主 Agent 会收到 worktree 位置信息

### 验证隔离效果

```
> 使用 isolated-worker agent,在当前目录创建 test.txt,写入 "hello"
```

检查结果:

```bash
# 主仓库里没有这个文件
ls test.txt
# → No such file or directory ✓

# 文件在子代理的 worktree 里
ls .codebuddy/worktrees/agent-xxx/test.txt
# → 存在 ✓
```

## 典型使用场景

### 场景一:基于特定分支开发

**问题**:需要在 develop 分支上开发新功能，而不是基于 main 分支。

**解决方案**:

```bash
# 基于远程 develop 分支创建 worktree
codebuddy --worktree feature-xxx --worktree-branch origin/develop

# 基于本地分支创建（保留本地未推送的提交）
codebuddy --worktree feature-yyy --worktree-branch my-local-branch
```

### 场景二:并行处理紧急 Bug

**问题**:正在开发新功能,代码改了一半,突然需要修紧急 bug。

**解决方案**:

```bash
# 终端 1:继续开发新功能
codebuddy --worktree feature-payment

# 终端 2:专门修 bug,互不干扰
codebuddy --worktree hotfix-login-crash
```

两个 worktree 同时打开,互不影响。bug 修完提交 PR,再回到功能开发。

### 场景三:高风险重构

**问题**:想尝试大规模重构,但不确定能否成功,不想污染主仓库。

**解决方案**:

```bash
codebuddy --worktree refactor-esm
```

在会话中:
```
> 帮我把 src/core 目录从 CommonJS 迁移到 ESM
```

结果:
- **成功**:提交,推 PR,合并
- **失败**:退出时选择 "Remove worktree",所有改动一键丢弃

### 场景四:审查 PR

**问题**:想在本地跑一下同事的 PR 代码,但不想污染工作目录。

**解决方案**:

```bash
codebuddy --worktree "#456"
```

在会话中:
```
> 帮我看看这次 PR 改了什么,有没有潜在问题
> 运行一下测试
```

审查完直接退出,worktree 自动清理。

### 场景五:并行任务

**问题**:有多个独立任务(写文档、加测试、开发新接口),想分别推进。

**解决方案**:

```bash
codebuddy --worktree task-docs --tmux
codebuddy --worktree task-tests --tmux
codebuddy --worktree task-new-api --tmux
```

每个 worktree 让 AI 专注做一件事,配合 tmux 实现真正并行工作。

### 场景六:子代理协作

**问题**:大型任务需要多个 AI 并行处理不同模块。

**解决方案**:

首先创建隔离型 Agent(见上文 "子代理隔离" 章节),然后:

```
> 使用三个并行的 api-worker 子代理:
  第一个处理 src/api/user/ 目录
  第二个处理 src/api/order/ 目录
  第三个处理 src/api/product/ 目录
```

三个子代理各自在独立 worktree 中工作,即使修改了共享文件也不会冲突。

## 常见问题

**Q: `--worktree-branch` 应该传远程分支还是本地分支？**

严格区分：
- `--worktree-branch origin/xxx` → 使用远程分支（会先 fetch 最新代码）
- `--worktree-branch xxx` → 使用本地分支（保留本地未推送的提交）

如果指定的分支不存在，会打印警告并降级到远程默认分支，不会中断启动。

**Q:worktree 会占用很多磁盘空间吗?**

不会。所有 worktree 共享 Git 对象数据库,只需要存储工作文件本身。如果配置了 `symlinkDirectories`,大型目录也会共享,几乎不占额外空间。

**Q:复用已有 worktree 时,.env.local 等文件还会重新复制吗?**

不会。复用时直接进入已有目录,跳过初始化。如需同步新配置,需删除 worktree 后重建:

```bash
git worktree remove --force .codebuddy/worktrees/my-feature
git branch -D worktree-my-feature
codebuddy --worktree my-feature
```

**Q:worktree 里的提交会影响主仓库吗?**

不会。worktree 有独立分支,提交只在该分支上。要合并到主分支,需走正常 PR 流程或手动 `git merge`。

**Q:创建 worktree 中途断开了,留下残缺目录怎么办?**

```bash
git worktree remove --force .codebuddy/worktrees/<名称>
git branch -D worktree-<名称>
```

然后重新创建即可。

**Q:symlinkDirectories 配置了但没生效?**

复用已有 worktree 时不会重新初始化。删除旧 worktree 重建一次即可。

**Q:子代理隔离是安全沙箱吗?**

不是。子代理在文件层面隔离,但权限与主 Agent 相同,理论上可操作 worktree 之外的文件。这个功能的目的是防止多个 Agent 之间的**文件冲突**,而非安全限制。

**Q：配置了 WorktreeCreate hook 后还能用 Git Worktree 吗？**

不能同时使用。Hook 优先级高于 Git——一旦配置了 WorktreeCreate hook，所有 worktree 操作都走 hook，即使在 Git 仓库中也是如此。要恢复使用 git worktree，删除 settings.json 中的 WorktreeCreate 配置即可。

**Q：只配了 WorktreeCreate 没配 WorktreeRemove 会怎样？**

创建正常工作，但退出时不会自动清理。系统会提示 worktree 目录保留在哪里，需要手动删除。

**Q：Hook 脚本执行失败了怎么办？**

WorktreeCreate 失败会直接报错，不会降级到 git worktree，检查脚本的 stderr 输出排查问题。WorktreeRemove 失败只记录警告，不影响退出流程。

**Q：在非 Git 项目中使用 hook-based worktree，退出时为什么没有自动清理？**

hook-based worktree 退出时始终显示 keep/remove 菜单，不进行变更检测（因为非 Git 项目没有 `git status`）。需要手动选择是否删除。

## 注意事项

- **Git 仓库或 Hook**：`--worktree` 需要 Git 仓库或配置 WorktreeCreate hook（非 Git 项目如 SVN、Perforce 可通过 hook 支持）
- **分支命名**：自动创建的分支以 `worktree-` 为前缀
- **清理**：长时间未使用的 worktree 不会自动清理，需要手动删除或使用 `/rewind` 命令
- **符号链接**：某些工具可能不支持符号链接的目录，请根据项目需求配置

## 相关文档

- [CLI 参考](./cli-reference.md) - 完整的命令行参数说明
- [Hooks 参考](./hooks.md) - Hook 系统详细文档
- [设置配置](./settings.md) - 配置文件说明
