
# 工作树

Git 工作树允许多个工作目录共用一个仓库。每个工作树都有自己的检出内容（HEAD、索引和工作文件），但与主仓库共用对象存储、配置和 packed-refs。Pitmaster 可以创建、列出、锁定和移除关联工作树，也可以将它们作为仓库打开。目前尚未实现修剪、移动和修复工作树。

## 概念

常规的 git 仓库只有一个工作树：

```
project/
  .git/          (directory)
  src/
  README.md
```

使用 worktree，你可以拥有额外的检出目录：

```
project/                          (main worktree)
  .git/                           (the actual git directory)
    worktrees/
      review/                     (metadata for linked worktree)
        HEAD
        commondir
        gitdir

/tmp/review/                      (linked worktree)
  .git                            (file, not directory)
  src/
  README.md
```

链接 worktree 的 `.git` 是一个文件（而非目录），其中包含相对于 worktree 的路径：
```
gitdir: ../../path/to/project/.git/worktrees/review
```

## 创建 worktree

```php
$repo = Pitmaster::open('/path/to/project');

// Create a linked worktree on a new branch
$wt = $repo->addWorktree('/tmp/review', 'feature/review');

echo $wt->path;        // '/tmp/review'
echo $wt->branch;      // 'feature/review'
echo $wt->gitDir;      // '/path/to/project/.git/worktrees/review'
var_dump($wt->isMain);      // bool(false)
var_dump($wt->isDetached);  // bool(false)
```

`addWorktree()` 会执行以下操作：
1. 如果分支不存在，则创建该分支（基于 HEAD 或指定的 `$from` 提交）
2. 在 `.git/worktrees/<name>/` 创建元数据目录
3. 在 worktree 目录中写入 `.git` 文件
4. 写入 `HEAD`、`commondir` 和 `gitdir` 元数据文件
5. 将该分支的树检出到 worktree 目录
6. 为该 worktree 写入索引

如果分支已存在，则忽略 `$from`。与 git 不同，`addWorktree()` 不会拒绝已在另一个 worktree 中检出的分支，也不会运行 `post-checkout` 钩子。

### 使用显式元数据名称

当多个链接 worktree 可以共用相同的检出目录基本名称时，请使用可选的 `name` 参数。

```php
$wt = $repo->addWorktree(
    '/tmp/sandboxes/app',
    'feature/review',
    name: 'sandbox-app',
);
```

该元数据名称会用于 `.git/worktrees/<name>/`，而不是 `basename($path)`。

### 从指定提交创建

```php
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
```

## 列出 worktree

```php
$worktrees = $repo->worktrees();

foreach ($worktrees as $wt) {
    $type = $wt->isMain ? 'main' : 'linked';
    $branch = $wt->branch ?? 'detached';
    $locked = $wt->isLocked ? ' [locked]' : '';

    echo "{$wt->path} ({$type}) [{$branch}]{$locked}\n";
}
```

列表始终先包含主 worktree，随后是链接 worktree。在主仓库的句柄上调用 `worktrees()`：在链接 worktree 的句柄上调用时，主条目目前会报告该链接 worktree 自身的路径（[#14](https://github.com/inline0/pitmaster/issues/14)）。

## 移除 worktree

```php
$repo->removeWorktree('review');          // by metadata name
$repo->removeWorktree('/tmp/review');     // or by worktree path

// Force remove (even if locked)
$repo->removeWorktree('review', force: true);
```

移除 worktree 时：
1. 检查 worktree 是否已锁定（除非 `$force` 为 true，否则操作失败）
2. 从 worktree 目录中移除 `.git` 文件
3. 从 `.git/worktrees/` 中移除元数据目录

不会自动删除工作树文件。该分支仍然存在。

## 打开链接 worktree

你可以像打开任何仓库一样打开链接 worktree：

```php
$repo = Pitmaster::open('/tmp/review');

// Pitmaster detects the .git file and follows the gitdir reference
echo $repo->isLinkedWorktree();  // true
echo $repo->gitDir();            // '/path/to/project/.git/worktrees/review'
echo $repo->commonGitDir();      // '/path/to/project/.git'
echo $repo->workDir();           // '/tmp/review'
```

仓库操作按以下方式解析路径：
- **对象**：从公共 git 目录（`project/.git/objects/`）读取并写入
- **配置**：从公共 git 目录（`project/.git/config`）读取
- **Packed-refs**：从公共 git 目录读取
- **HEAD**：每个 worktree 独有（各 worktree 跟踪自己的分支），`ORIG_HEAD`、`MERGE_HEAD` 及其他操作引用也一样
- **索引**：每个 worktree 独有（各 worktree 有自己的暂存区）
- **松散引用**：分支和标签在公共目录中共享（`project/.git/refs/`）
- **钩子**：从各 worktree 自己的 git 目录读取，因此不会运行共享钩子（[#12](https://github.com/inline0/pitmaster/issues/12)）

Stash 目前尚不支持链接 worktree：它会从各 worktree 的目录读取身份信息，但该目录没有配置文件，并会在该目录中写入其 reflog（[#14](https://github.com/inline0/pitmaster/issues/14)）。

## Worktree 值对象

```php
use Pitmaster\Worktree\Worktree;

$wt->name;          // Metadata name (null for the main worktree)
$wt->path;          // Working directory path
$wt->gitDir;        // Per-worktree git dir
$wt->branch;        // Branch name (null if detached)
$wt->head;          // ObjectId of HEAD commit
$wt->isMain;        // true for the main worktree
$wt->isDetached;    // true if HEAD is detached
$wt->isLocked;      // true if worktree is locked
$wt->lockReason;    // Lock reason, or null when there is none (an empty reason is also null)
```

## WorktreeManager

用于底层 worktree 管理。`WorktreeManager::add()` 只写入元数据和 `.git` 文件：不会创建分支、检出文件或写入索引。若要获得可用的检出目录，请使用 `$repo->addWorktree()`。分支必须已存在（或使用完整引用名称，例如 `refs/tags/v1.0.0`，此时 HEAD 会分离）。

```php
use Pitmaster\Worktree\WorktreeManager;

$manager = new WorktreeManager($repo->commonGitDir(), $mainWorkDir);

// List
$worktrees = $manager->list();

// Add
$wt = $manager->add('/tmp/review', 'feature/review');
$wt = $manager->add('/tmp/review', 'feature/review', 'review-app');

// Remove
$manager->remove('review');
$manager->remove('review', force: true);

// Lock/unlock (metadata name only)
$manager->lock('review', 'Do not delete, code review in progress');
$manager->unlock('review');
```

## 锁定 worktree

```php
// Lock (prevents removal)
$manager->lock('review', 'Code review in progress');

// Attempt to remove locked worktree
try {
    $manager->remove('review');
} catch (\RuntimeException $e) {
    // "Worktree is locked: review (Code review in progress)"
}

// Force remove ignores the lock
$manager->remove('review', force: true);

// Unlock
$manager->unlock('review');
```

锁会以文件形式存储在 `.git/worktrees/<name>/locked`。文件内容是锁定原因（可以为空）。

## 检测 worktree

```php
// Check if a path is a linked worktree
if (Pitmaster::isWorktree('/tmp/review')) {
    echo "This is a linked worktree\n";
}

// Resolve the common git dir from any path
$common = Pitmaster::commonGitDir('/tmp/review');
// '/path/to/project/.git'

// Resolve a .git file to the actual git directory
$gitDir = WorktreeManager::resolveGitFile('/tmp/review');
// '/path/to/project/.git/worktrees/review'
```

## 文件布局

### 链接 worktree 的主 worktree 元数据

```
.git/worktrees/<name>/
  HEAD         Per-worktree HEAD (symbolic ref or detached hash)
  commondir    Relative path to the shared git dir ("../..")
  gitdir       Path back to the worktree's .git file, as passed to addWorktree()
  locked       Present if locked (content = reason)
  index        Per-worktree index/staging area
```

### 链接 worktree 的 .git 文件

```
gitdir: <relative path to>/project/.git/worktrees/<name>
```

### 资源共享

| 资源 | 位置 | 是否共享？ |
|----------|----------|---------|
| 对象 | `$commonDir/objects/` | 是 |
| 配置 | `$commonDir/config` | 是 |
| Packed-refs | `$commonDir/packed-refs` | 是 |
| 钩子 | `$gitDir/hooks/` | 否：Pitmaster 不会在链接 worktree 中运行共享钩子 |
| HEAD | `$gitDir/HEAD` | 否（每个 worktree 独有） |
| 索引 | `$gitDir/index` | 否（每个 worktree 独有） |
| 松散引用 | `$commonDir/refs/` | 是（HEAD 和 `MERGE_HEAD` 等操作引用各 worktree 独有） |
