Aller au contenu

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 :

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

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

Code
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 :

Code
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).

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#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).

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

Code
.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é

Code
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)