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:
project/
.git/ (directory)
src/
README.md
Cu ajutorul worktree-urilor, puteți avea checkout-uri suplimentare:
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:
gitdir: ../../path/to/project/.git/worktrees/review
Crearea unui worktree
$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:
- Creează ramura dacă nu există (din HEAD sau din commitul
$fromspecificat) - Creează directorul de metadate în
.git/worktrees/<name>/ - Scrie fișierul
.gitîn directorul worktree-ului - Scrie fișierele de metadate
HEAD,commondirșigitdir - Face checkout la arborele ramurii în directorul worktree-ului
- 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.
$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
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Listarea worktree-urilor
$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
$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:
- Verifică dacă worktree-ul este blocat (operația eșuează dacă
$forcenu este true) - Elimină fișierul
.gitdin directorul worktree-ului - 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:
$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
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).
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
// 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
// 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
.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
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) |