
# Worktrees

Les worktrees Git permettent à plusieurs répertoires de travail de partager un même dépôt. Chaque worktree possède sa propre extraction (HEAD, index, fichiers de travail), mais partage avec le dépôt principal le magasin d’objets, la configuration et les références compactées. Pitmaster peut créer, répertorier, verrouiller et supprimer des worktrees liés, ainsi que les ouvrir comme dépôts. L’élagage, le déplacement et la réparation des worktrees ne sont pas implémentés.

## Concepts

Un dépôt git classique possède un seul arbre de travail :

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

Avec les worktrees, vous pouvez disposer de checkouts supplémentaires :

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

Le `.git` du worktree lié est un fichier (et non un répertoire) contenant un chemin relatif au worktree :
```
gitdir: ../../path/to/project/.git/worktrees/review
```

## Créer un 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()` effectue les opérations suivantes :
1. Crée la branche si elle n'existe pas (à partir de HEAD ou du commit `$from` spécifié)
2. Crée le répertoire de métadonnées dans `.git/worktrees/<name>/`
3. Écrit le fichier `.git` dans le répertoire du worktree
4. Écrit les fichiers de métadonnées `HEAD`, `commondir` et `gitdir`
5. Extrait l'arborescence de la branche dans le répertoire du worktree
6. Écrit l'index du worktree

Si la branche existe déjà, `$from` est ignoré. Contrairement à git, `addWorktree()` n'empêche pas l'utilisation d'une branche déjà extraite dans un autre worktree et n'exécute pas le hook `post-checkout`.

### Avec un nom de métadonnées explicite

Utilisez l'argument facultatif `name` lorsque plusieurs worktrees liés peuvent partager le même nom de base de checkout.

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

Ce nom de métadonnées est utilisé dans `.git/worktrees/<name>/` à la place de `basename($path)`.

### À partir d'un commit précis

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

## Lister les worktrees

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

La liste comprend toujours le worktree principal en premier, suivi des worktrees liés. Appelez `worktrees()` sur un handle du dépôt principal : sur un handle de worktree lié, l'entrée principale indique actuellement le chemin propre au worktree lié ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Supprimer un 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);
```

La suppression d'un worktree :
1. Vérifie si le worktree est verrouillé (échoue sauf si `$force` est vrai)
2. Supprime le fichier `.git` du répertoire du worktree
3. Supprime le répertoire de métadonnées de `.git/worktrees/`

Les fichiers de l'arborescence de travail ne sont pas supprimés automatiquement. La branche continue d'exister.

## Ouvrir un worktree lié

Vous pouvez ouvrir un worktree lié comme n'importe quel dépôt :

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

Les opérations sur le dépôt résolvent les chemins ainsi :
- **Objets** : lus depuis le dossier git commun et écrits dans celui-ci (`project/.git/objects/`)
- **Configuration** : lue depuis le dossier git commun (`project/.git/config`)
- **Références empaquetées** : lues depuis le dossier git commun
- **HEAD** : propre à chaque espace de travail (chacun suit sa propre branche), avec `ORIG_HEAD`, `MERGE_HEAD` et les autres références d’opération
- **Index** : propre à chaque espace de travail (chacun dispose de sa propre zone de préparation)
- **Références libres** : les branches et les étiquettes sont partagées dans le dossier commun (`project/.git/refs/`)
- **Hooks** : lus depuis le dossier git propre à chaque espace de travail ; les hooks partagés ne s’exécutent donc pas ([référence](https://github.com/inline0/pitmaster/issues/12)#12)

Stash ne prend pas encore en charge les worktrees liés : il lit l'identité depuis le répertoire propre au worktree, qui ne contient pas de configuration, et y écrit son reflog ([#14](https://github.com/inline0/pitmaster/issues/14)).

## Objet de valeur 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

Pour la gestion de bas niveau des worktrees. `WorktreeManager::add()` écrit uniquement les métadonnées et le fichier `.git` : il ne crée pas la branche, n'extrait pas les fichiers et n'écrit pas d'index. Utilisez `$repo->addWorktree()` pour obtenir un checkout utilisable. La branche doit déjà exister (ou être un nom de référence complet tel que `refs/tags/v1.0.0`, qui détache 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');
```

## Verrouiller les worktrees

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

Le verrou est stocké dans un fichier à l'emplacement `.git/worktrees/<name>/locked`. Le contenu du fichier indique le motif du verrouillage (il peut être vide).

## Détecter les worktrees

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

## Organisation des fichiers

### Métadonnées du worktree principal pour les worktrees liés

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

### Fichier .git du worktree lié

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

### Partage des ressources

| Ressource | Emplacement | Partagée ? |
|----------|----------|---------|
| Objets | `$commonDir/objects/` | Oui |
| Config | `$commonDir/config` | Oui |
| Packed-refs | `$commonDir/packed-refs` | Oui |
| Hooks | `$gitDir/hooks/` | Non : Pitmaster n'exécute pas les hooks partagés dans les worktrees liés |
| HEAD | `$gitDir/HEAD` | Non (propre à chaque worktree) |
| Index | `$gitDir/index` | Non (propre à chaque worktree) |
| Références non empaquetées | `$commonDir/refs/` | Oui (HEAD et les références d'opération telles que `MERGE_HEAD` sont propres à chaque worktree) |
