Ga naar de inhoud

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:

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

Met worktrees kun je extra checkouts hebben:

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

Het bestand .git van de gekoppelde worktree bevat (in plaats van een map) een pad relatief aan de worktree:

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

Een worktree aanmaken

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() doet het volgende:

  1. Maakt de branch aan als deze niet bestaat (op basis van HEAD of de opgegeven commit $from)
  2. Maakt de metadatamap aan in .git/worktrees/<name>/
  3. Schrijft het bestand .git in de worktree-map
  4. Schrijft de metadatabestanden HEAD, commondir en gitdir
  5. Checkt de tree van de branch uit naar de worktree-map
  6. 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.

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

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

Worktrees weergeven

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

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

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

Een worktree verwijderen:

  1. Controleert of de worktree vergrendeld is (mislukt tenzij $force true is)
  2. Verwijdert het bestand .git uit de worktree-map
  3. 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:

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'

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

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

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

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 vergrendelen

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

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

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'

Bestandsindeling

Metadata van de hoofd-worktree voor gekoppelde 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

Het .git-bestand van een gekoppelde worktree

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