Worktrees
Git worktrees maken het mogelijk meerdere werkdirectories te delen binnen één repository. Elke worktree heeft een eigen checkout (HEAD, index, werkbestanden), maar deelt de objectopslag, configuratie en packed-refs met de hoofdrepository. Pitmaster kan gekoppelde worktrees aanmaken, weergeven, vergrendelen en verwijderen, en ze als repositories openen. Opschonen, verplaatsen en repareren van worktrees zijn niet geïmplementeerd.
Concepten
Een reguliere git-repository heeft één werkdirectory:
project/
.git/ (directory)
src/
README.md
Met worktrees kun je extra checkouts hebben:
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
Het bestand .git van de gekoppelde worktree bevat (in plaats van een map) een pad relatief aan de worktree:
gitdir: ../../path/to/project/.git/worktrees/review
Een worktree aanmaken
$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() doet het volgende:
- Maakt de branch aan als deze niet bestaat (op basis van HEAD of de opgegeven commit
$from) - Maakt de metadatamap aan in
.git/worktrees/<name>/ - Schrijft het bestand
.gitin de worktree-map - Schrijft de metadatabestanden
HEAD,commondirengitdir - Checkt de tree van de branch uit naar de worktree-map
- Schrijft de index voor de worktree
Als de branch al bestaat, wordt $from genegeerd. In tegenstelling tot git weigert addWorktree() geen branch die al in een andere worktree is uitgecheckt en voert het de hook post-checkout niet uit.
Met een expliciete metadatanaam
Gebruik het optionele argument name wanneer meerdere gekoppelde worktrees dezelfde checkout-basismapnaam kunnen hebben.
$wt = $repo->addWorktree(
'/tmp/sandboxes/app',
'feature/review',
name: 'sandbox-app',
);
Die metadatanaam wordt gebruikt onder .git/worktrees/<name>/ in plaats van basename($path).
Vanaf een specifieke commit
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Worktrees weergeven
$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";
}
De lijst bevat altijd eerst de hoofd-worktree, gevolgd door gekoppelde worktrees. Roep worktrees() aan op een handle voor de hoofdrepository: bij een handle voor een gekoppelde worktree geeft de hoofdvermelding momenteel het eigen pad van die gekoppelde worktree weer (#14).
Een worktree verwijderen
$repo->removeWorktree('review'); // by metadata name
$repo->removeWorktree('/tmp/review'); // or by worktree path
// Force remove (even if locked)
$repo->removeWorktree('review', force: true);
Een worktree verwijderen:
- Controleert of de worktree vergrendeld is (mislukt tenzij
$forcetrue is) - Verwijdert het bestand
.gituit de worktree-map - Verwijdert de metadatamap uit
.git/worktrees/
De bestanden in de werkdirectory worden niet automatisch verwijderd. De branch blijft bestaan.
Een gekoppelde worktree openen
Je kunt een gekoppelde worktree net als elke repository openen:
$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'
Repositorybewerkingen lossen paden als volgt op:
- Objecten: gelezen uit en geschreven naar de gemeenschappelijke git-map (
project/.git/objects/) - Configuratie: gelezen uit de gemeenschappelijke git-map (
project/.git/config) - Packed-refs: gelezen uit de gemeenschappelijke git-map
- HEAD: per worktree (elke worktree houdt de eigen branch bij), samen met
ORIG_HEAD,MERGE_HEADen de andere bewerkingsrefs - Index: per worktree (elke worktree heeft een eigen staginggebied)
- Losse refs: branches en tags worden gedeeld in de gemeenschappelijke map (
project/.git/refs/) - Hooks: gelezen uit de git-map per worktree, dus de gedeelde hooks worden niet uitgevoerd (#12)
Stash ondersteunt gekoppelde worktrees nog niet: het leest de identiteit uit de map per worktree, die geen configuratie bevat, en schrijft de reflog daar (#14).
Worktree-waardeobject
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
Voor werktreebeheer op laag niveau. WorktreeManager::add() schrijft alleen de metadata en het bestand .git: het maakt geen branch aan, checkt geen bestanden uit en schrijft geen index. Gebruik $repo->addWorktree() voor een bruikbare checkout. De branch moet al bestaan (of een volledige ref-naam zijn zoals refs/tags/v1.0.0, waarmee HEAD wordt losgekoppeld).
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 vergrendelen
// 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');
De vergrendeling wordt opgeslagen als bestand in .git/worktrees/<name>/locked. De inhoud van het bestand is de reden voor de vergrendeling (mag leeg zijn).
Worktrees detecteren
// 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'
Bestandsindeling
Metadata van de hoofd-worktree voor gekoppelde 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
Het .git-bestand van een gekoppelde worktree
gitdir: <relative path to>/project/.git/worktrees/<name>
Resources delen
| Resource | Locatie | Gedeeld? |
|---|---|---|
| Objecten | $commonDir/objects/ |
Ja |
| Configuratie | $commonDir/config |
Ja |
| Packed-refs | $commonDir/packed-refs |
Ja |
| Hooks | $gitDir/hooks/ |
Nee: Pitmaster voert de gedeelde hooks niet uit in gekoppelde worktrees |
| HEAD | $gitDir/HEAD |
Nee (per worktree) |
| Index | $gitDir/index |
Nee (per worktree) |
| Losse refs | $commonDir/refs/ |
Ja (HEAD en bewerkingsrefs zoals MERGE_HEAD zijn per worktree) |