
# Worktrees

Mit Git-Worktrees kannst du mehrere Arbeitsverzeichnisse aus einem Repository nutzen. Jeder Worktree hat einen eigenen Checkout (HEAD, Index und Arbeitsdateien), teilt aber den Objektspeicher, die Konfiguration und die Packed-Refs mit dem Haupt-Repository. Pitmaster kann verknüpfte Worktrees erstellen, auflisten, sperren und entfernen sowie sie als Repositorys öffnen. Bereinigen, Verschieben und Reparieren von Worktrees wird nicht unterstützt.

## Konzepte

Ein gewöhnliches git-Repository hat einen einzelnen Arbeitsbaum:

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

Mit Worktrees kannst du zusätzliche Checkouts anlegen:

```
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
```

Die Datei `.git` des verknüpften Worktrees enthält einen relativen Pfad zum Worktree (sie ist keine Verzeichnisdatei):
```
gitdir: ../../path/to/project/.git/worktrees/review
```

## Worktree erstellen

```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()` führt Folgendes aus:
1. Erstellt den Branch, falls er nicht existiert (ausgehend von HEAD oder dem angegebenen Commit `$from`)
2. Erstellt das Metadatenverzeichnis unter `.git/worktrees/<name>/`
3. Schreibt die Datei `.git` in das Worktree-Verzeichnis
4. Schreibt die Metadatendateien `HEAD`, `commondir` und `gitdir`
5. Checkt den Baum des Branches in das Worktree-Verzeichnis aus
6. Schreibt den Index für den Worktree

Wenn der Branch bereits existiert, wird `$from` ignoriert. Anders als git verweigert `addWorktree()` nicht, einen Branch auszuchecken, der bereits in einem anderen Worktree ausgecheckt ist, und führt den Hook `post-checkout` nicht aus.

### Mit einem expliziten Metadatennamen

Verwende das optionale Argument `name`, wenn mehrere verknüpfte Worktrees denselben Checkout-Basisnamen haben können.

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

Dieser Metadatenname wird unter `.git/worktrees/<name>/` statt unter `basename($path)` verwendet.

### Von einem bestimmten Commit aus

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

## Worktrees auflisten

```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";
}
```

Die Liste enthält immer zuerst den Haupt-Worktree, gefolgt von den verknüpften Worktrees. Rufe `worktrees()` für ein Handle des Haupt-Repositorys auf: Bei einem Handle eines verknüpften Worktrees enthält der Haupteintrag derzeit den Pfad dieses verknüpften Worktrees ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Worktree entfernen

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

Einen Worktree entfernen:
1. Prüft, ob der Worktree gesperrt ist (bricht ab, sofern `$force` nicht true ist)
2. Entfernt die Datei `.git` aus dem Worktree-Verzeichnis
3. Entfernt das Metadatenverzeichnis aus `.git/worktrees/`

Die Dateien im Arbeitsverzeichnis werden nicht automatisch gelöscht. Der Branch bleibt bestehen.

## Verknüpften Worktree öffnen

Du kannst einen verknüpften Worktree wie jedes andere Repository öffnen:

```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'
```

Repository-Operationen lösen Pfade wie folgt auf:
- **Objekte**: Lesen und Schreiben im gemeinsamen git-Verzeichnis (`project/.git/objects/`)
- **Konfiguration**: Lesen aus dem gemeinsamen git-Verzeichnis (`project/.git/config`)
- **Packed-Refs**: Lesen aus dem gemeinsamen git-Verzeichnis
- **HEAD**: pro Arbeitsbaum (jeder Arbeitsbaum verfolgt seinen eigenen Branch), zusammen mit `ORIG_HEAD`, `MERGE_HEAD` und den anderen Operations-Refs
- **Index**: pro Arbeitsbaum (jeder Arbeitsbaum hat seinen eigenen Staging-Bereich)
- **Lose Refs**: Branches und Tags werden im gemeinsamen Verzeichnis geteilt (`project/.git/refs/`)
- **Hooks**: Lesen aus dem git-Verzeichnis des jeweiligen Arbeitsbaums, daher werden die gemeinsamen Hooks nicht ausgeführt ([#12](https://github.com/inline0/pitmaster/issues/12))

Stash unterstützt verknüpfte Worktrees noch nicht: Es liest die Identität aus dem Worktree-Verzeichnis, das keine Konfiguration enthält, und schreibt das Reflog dorthin ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Worktree-Wertobjekt

```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

Für die Worktree-Verwaltung auf niedriger Ebene. `WorktreeManager::add()` schreibt nur die Metadaten und die Datei `.git`: Es erstellt keinen Branch, checkt keine Dateien aus und schreibt keinen Index. Verwende `$repo->addWorktree()` für einen nutzbaren Checkout. Der Branch muss bereits existieren (oder ein vollständiger Ref-Name wie `refs/tags/v1.0.0` sein; dann wird HEAD abgetrennt).

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

## Worktrees sperren

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

Die Sperre wird als Datei unter `.git/worktrees/<name>/locked` gespeichert. Der Dateiinhalt gibt den Sperrgrund an (kann leer sein).

## Worktrees erkennen

```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'
```

## Dateiaufbau

### Metadaten des Haupt-Worktrees für verknüpfte Worktrees

```
.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
```

### .git-Datei des verknüpften Worktrees

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

### Gemeinsam genutzte Ressourcen

| Ressource | Speicherort | Gemeinsam genutzt? |
|----------|----------|---------|
| Objekte | `$commonDir/objects/` | Ja |
| Konfiguration | `$commonDir/config` | Ja |
| Packed-Refs | `$commonDir/packed-refs` | Ja |
| Hooks | `$gitDir/hooks/` | Nein: Pitmaster führt die gemeinsamen Hooks in verknüpften Worktrees nicht aus |
| HEAD | `$gitDir/HEAD` | Nein (pro Worktree) |
| Index | `$gitDir/index` | Nein (pro Worktree) |
| Lose Refs | `$commonDir/refs/` | Ja (HEAD und Operations-Refs wie `MERGE_HEAD` gelten pro Worktree) |
