
# Worktree

Git worktreeを使うと、複数の作業ディレクトリで1つのリポジトリを共有できます。各worktreeは独自のチェックアウト（HEAD、インデックス、作業ファイル）を持ちますが、オブジェクトストア、設定、packed-refsはメインリポジトリと共有します。Pitmasterでリンクされたworktreeの作成、一覧表示、ロック、削除、およびリポジトリとしてのオープンができます。worktreeのprune、移動、修復には対応していません。

## 概念

通常のgitリポジトリには、作業ツリーが1つあります。

```
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',
);
```

そのメタデータ名は、`basename($path)`の代わりに`.git/worktrees/<name>/`の下で使用されます。

### 特定のコミットから作成

```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`、その他の操作用refも同様
- **インデックス**: worktreeごとに管理（各worktreeが独自のステージング領域を持つ）
- **Loose ref**: ブランチとタグは共通ディレクトリ（`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`のような完全なref名を指定すると、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ごと） |
| Loose ref | `$commonDir/refs/` | はい（HEADや`MERGE_HEAD`などの操作用refはworktreeごと） |
