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
$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:
- Erstellt den Branch, falls er nicht existiert (ausgehend von HEAD oder dem angegebenen Commit
$from) - Erstellt das Metadatenverzeichnis unter
.git/worktrees/<name>/ - Schreibt die Datei
.gitin das Worktree-Verzeichnis - Schreibt die Metadatendateien
HEAD,commondirundgitdir - Checkt den Baum des Branches in das Worktree-Verzeichnis aus
- 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.
$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
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Worktrees auflisten
$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).
Worktree entfernen
$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:
- Prüft, ob der Worktree gesperrt ist (bricht ab, sofern
$forcenicht true ist) - Entfernt die Datei
.gitaus dem Worktree-Verzeichnis - 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:
$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_HEADund 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)
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).
Worktree-Wertobjekt
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).
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
// 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
// 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) |