Zum Inhalt springen

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:

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

Mit Worktrees kannst du zusätzliche Checkouts anlegen:

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

Die Datei .git des verknüpften Worktrees enthält einen relativen Pfad zum Worktree (sie ist keine Verzeichnisdatei):

Code
gitdir: ../../path/to/project/.git/worktrees/review

Worktree erstellen

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() führt Folgendes aus:

  1. Erstellt den Branch, falls er nicht existiert (ausgehend von HEAD oder dem angegebenen Commit $from)
  2. Erstellt das Metadatenverzeichnis unter .git/worktrees/<name>/
  3. Schreibt die Datei .git in das Worktree-Verzeichnis
  4. Schreibt die Metadatendateien HEAD, commondir und gitdir
  5. Checkt den Baum des Branches in das Worktree-Verzeichnis aus
  6. 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.

PHP
$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

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

Worktrees auflisten

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

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

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

Einen Worktree entfernen:

  1. Prüft, ob der Worktree gesperrt ist (bricht ab, sofern $force nicht true ist)
  2. Entfernt die Datei .git aus dem Worktree-Verzeichnis
  3. 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:

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'

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_HEAD und 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

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

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

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

Worktrees sperren

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

Die Sperre wird als Datei unter .git/worktrees/<name>/locked gespeichert. Der Dateiinhalt gibt den Sperrgrund an (kann leer sein).

Worktrees erkennen

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'

Dateiaufbau

Metadaten des Haupt-Worktrees für verknüpfte Worktrees

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

.git-Datei des verknüpften Worktrees

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