Worktree
Git I worktree consentono a più directory di lavoro di condividere un unico repository. Ogni worktree ha il proprio checkout (HEAD, indice, file di lavoro), ma condivide l'archivio degli oggetti, la configurazione e packed-refs con il repository principale. Pitmaster può creare, elencare, bloccare e rimuovere worktree collegati, oltre che aprirli come repository. Le operazioni di pulizia, spostamento e riparazione dei worktree non sono implementate.
Concetti
Un repository git normale ha un'unica directory di lavoro:
project/
.git/ (directory)
src/
README.md
Con i worktree, potete avere checkout aggiuntivi:
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
Il .git del worktree collegato è un file (non una directory) che contiene un percorso relativo al worktree:
gitdir: ../../path/to/project/.git/worktrees/review
Creare un 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() esegue le seguenti operazioni:
- Crea il branch se non esiste (da HEAD o dal commit
$fromspecificato) - Crea la directory dei metadati in
.git/worktrees/<name>/ - Scrive il file
.gitnella directory del worktree - Scrive i file di metadati
HEAD,commondiregitdir - Estrae l'albero del branch nella directory del worktree
- Scrive l'indice del worktree
Se il branch esiste già, $from viene ignorato. A differenza di git, addWorktree() non rifiuta un branch già estratto in un altro worktree e non esegue l'hook post-checkout.
Con un nome esplicito per i metadati
Usate l'argomento facoltativo name quando più worktree collegati possono condividere lo stesso nome di base del checkout.
$wt = $repo->addWorktree(
'/tmp/sandboxes/app',
'feature/review',
name: 'sandbox-app',
);
Il nome dei metadati viene usato in .git/worktrees/<name>/ al posto di basename($path).
Da un commit specifico
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Elencare i worktree
$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";
}
L'elenco include sempre prima il worktree principale, seguito da quelli collegati. Chiamate worktrees() su un handle del repository principale: con un handle di un worktree collegato, la voce principale riporta attualmente il percorso del worktree collegato stesso (#14).
Rimuovere un 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);
Rimozione di un worktree:
- Verifica se il worktree è bloccato (fallisce a meno che
$forcenon sia true) - Rimuove il file
.gitdalla directory del worktree - Rimuove la directory dei metadati da
.git/worktrees/
I file della directory di lavoro non vengono eliminati automaticamente. Il branch continua a esistere.
Aprire un worktree collegato
Potete aprire un worktree collegato come qualsiasi altro repository:
$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'
Le operazioni sul repository risolvono i percorsi così:
- Oggetti: letti e scritti nella directory git comune (
project/.git/objects/) - Configurazione: letta dalla directory git comune (
project/.git/config) - Packed-refs: lette dalla directory git comune
- HEAD: specifico del worktree (ognuno tiene traccia del proprio branch), insieme a
ORIG_HEAD,MERGE_HEADe agli altri riferimenti delle operazioni - Indice: specifico del worktree (ognuno ha la propria area di staging)
- Riferimenti sciolti: branch e tag sono condivisi nella directory comune (
project/.git/refs/) - Hook: letti dalla directory git specifica del worktree; gli hook condivisi non vengono eseguiti (#12)
Stash non gestisce ancora i worktree collegati: legge l'identità dalla directory specifica del worktree, che non contiene configurazione, e vi scrive il reflog (#14).
Oggetto valore del 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
Per la gestione dei worktree a basso livello. WorktreeManager::add() scrive soltanto i metadati e il file .git: non crea il branch, non estrae i file e non scrive un indice. Usate $repo->addWorktree() per ottenere un checkout utilizzabile. Il branch deve già esistere (oppure essere un nome completo di riferimento come refs/tags/v1.0.0, che scollega 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');
Bloccare i worktree
// 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');
Il blocco viene memorizzato in un file in .git/worktrees/<name>/locked. Il contenuto del file è il motivo del blocco (può essere vuoto).
Rilevare i worktree
// 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'
Struttura dei file
Metadati del worktree principale per i worktree collegati
.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
File .git del worktree collegato
gitdir: <relative path to>/project/.git/worktrees/<name>
Condivisione delle risorse
| Risorsa | Posizione | Condivisa? |
|---|---|---|
| Oggetti | $commonDir/objects/ |
Sì |
| Configurazione | $commonDir/config |
Sì |
| Packed-refs | $commonDir/packed-refs |
Sì |
| Hook | $gitDir/hooks/ |
No: Pitmaster non esegue gli hook condivisi nei worktree collegati |
| HEAD | $gitDir/HEAD |
No (specifico del worktree) |
| Indice | $gitDir/index |
No (specifico del worktree) |
| Riferimenti sciolti | $commonDir/refs/ |
Sì (HEAD e riferimenti delle operazioni come MERGE_HEAD sono specifici del worktree) |