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
$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 :
- Crée la branche si elle n'existe pas (à partir de HEAD ou du commit
$fromspécifié) - Crée le répertoire de métadonnées dans
.git/worktrees/<name>/ - Écrit le fichier
.gitdans le répertoire du worktree - Écrit les fichiers de métadonnées
HEAD,commondiretgitdir - Extrait l'arborescence de la branche dans le répertoire du worktree
- É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.
$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
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Lister les worktrees
$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
$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 :
- Vérifie si le worktree est verrouillé (échoue sauf si
$forceest vrai) - Supprime le fichier
.gitdu répertoire du worktree - 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 :
$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_HEADet 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
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).
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
// 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
// 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) |