Treci la conținut

Worktree-uri

Git Worktree-urile permit mai multor directoare de lucru să partajeze un singur depozit. Fiecare worktree are propria extragere (HEAD, index, fișiere de lucru), dar partajează stocarea obiectelor, configurația și packed-refs cu depozitul principal. Pitmaster poate crea, lista, bloca și elimina worktree-uri asociate și le poate deschide ca depozite. Curățarea, mutarea și repararea worktree-urilor nu sunt implementate.

Concepte

Un depozit git obișnuit are un singur arbore de lucru:

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

Cu ajutorul worktree-urilor, puteți avea checkout-uri suplimentare:

Cod
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

Fișierul .git al worktree-ului asociat conține o cale relativă la worktree:

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

Crearea unui worktree

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() face următoarele:

  1. Creează ramura dacă nu există (din HEAD sau din commitul $from specificat)
  2. Creează directorul de metadate în .git/worktrees/<name>/
  3. Scrie fișierul .git în directorul worktree-ului
  4. Scrie fișierele de metadate HEAD, commondir și gitdir
  5. Face checkout la arborele ramurii în directorul worktree-ului
  6. Scrie indexul pentru worktree

Dacă ramura există deja, $from este ignorat. Spre deosebire de git, addWorktree() nu refuză o ramură care are deja checkout într-un alt worktree și nu rulează hook-ul post-checkout.

Cu un nume explicit pentru metadate

Folosiți argumentul opțional name când mai multe worktree-uri asociate pot avea același nume de bază pentru checkout.

PHP
$wt = $repo->addWorktree(
    '/tmp/sandboxes/app',
    'feature/review',
    name: 'sandbox-app',
);

Numele pentru metadate este folosit în .git/worktrees/<name>/ în loc de basename($path).

Dintr-un commit specific

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

Listarea worktree-urilor

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

Lista include întotdeauna mai întâi worktree-ul principal, urmat de worktree-urile asociate. Apelați worktrees() pe un handle pentru depozitul principal: pe un handle pentru un worktree asociat, intrarea principală raportează momentan propria cale a worktree-ului asociat (#14).

Eliminarea unui worktree

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

La eliminarea unui worktree:

  1. Verifică dacă worktree-ul este blocat (operația eșuează dacă $force nu este true)
  2. Elimină fișierul .git din directorul worktree-ului
  3. Elimină directorul de metadate din .git/worktrees/

Fișierele din arborele de lucru nu sunt șterse automat. Ramura continuă să existe.

Deschiderea unui worktree asociat

Puteți deschide un worktree asociat la fel ca orice depozit:

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'

Operațiile asupra depozitului rezolvă căile astfel:

  • Obiecte: citite din și scrise în directorul comun git (project/.git/objects/)
  • Configurație: citită din directorul comun git (project/.git/config)
  • Packed-refs: citite din directorul comun git
  • HEAD: specific fiecărui worktree (fiecare worktree își urmărește propria ramură), împreună cu ORIG_HEAD, MERGE_HEAD și celelalte referințe de operație
  • Index: specific fiecărui worktree (fiecare worktree are propria zonă de staging)
  • Referințe loose: ramurile și etichetele sunt partajate în directorul comun (project/.git/refs/)
  • Hook-uri: citite din directorul git specific worktree-ului, așadar hook-urile partajate nu rulează (#12)

Stash nu gestionează încă worktree-urile asociate: citește identitatea din directorul worktree-ului, care nu are configurație, și scrie reflog-ul acolo (#14).

Obiectul valoare Worktree

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

Pentru gestionarea worktree-urilor la nivel inferior. WorktreeManager::add() scrie doar metadatele și fișierul .git: nu creează ramura, nu face checkout la fișiere și nu scrie un index. Folosiți $repo->addWorktree() pentru un checkout utilizabil. Ramura trebuie să existe deja (sau să fie un nume complet de referință, precum refs/tags/v1.0.0, care detașează HEAD).

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

Blocarea worktree-urilor

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

Blocarea este stocată ca fișier în .git/worktrees/<name>/locked. Conținutul fișierului este motivul blocării (poate fi gol).

Detectarea worktree-urilor

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'

Structura fișierelor

Metadatele worktree-ului principal pentru worktree-urile asociate

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

Fișierul .git al worktree-ului asociat

Cod
gitdir: <relative path to>/project/.git/worktrees/<name>

Partajarea resurselor

Resursă Locație Partajată?
Obiecte $commonDir/objects/ Da
Configurație $commonDir/config Da
Packed-refs $commonDir/packed-refs Da
Hook-uri $gitDir/hooks/ Nu: Pitmaster nu rulează hook-urile partajate în worktree-urile asociate
HEAD $gitDir/HEAD Nu (specific fiecărui worktree)
Index $gitDir/index Nu (specific fiecărui worktree)
Referințe loose $commonDir/refs/ Da (HEAD și referințele de operație, precum MERGE_HEAD, sunt specifice fiecărui worktree)