Przejdź do treści

Worktree

Git worktree umożliwiają współdzielenie jednego repozytorium przez wiele katalogów roboczych. Każde worktree ma własną kopię roboczą (HEAD, indeks, pliki robocze), ale współdzieli z głównym repozytorium magazyn obiektów, konfigurację i spakowane refs. Pitmaster może tworzyć, wyświetlać, blokować i usuwać połączone worktree oraz otwierać je jako repozytoria. Przycinanie, przenoszenie i naprawianie worktree nie jest zaimplementowane.

Pojęcia

Zwykłe repozytorium git ma jedno drzewo robocze:

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

Dzięki worktree możesz mieć dodatkowe kopie robocze:

Kod
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

Plik .git powiązanego worktree zawiera ścieżkę względną względem worktree:

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

Tworzenie 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() wykonuje następujące czynności:

  1. Tworzy gałąź, jeśli jeszcze nie istnieje (na podstawie HEAD lub wskazanego commita $from)
  2. Tworzy katalog metadanych w .git/worktrees/<name>/
  3. Zapisuje plik .git w katalogu worktree
  4. Zapisuje pliki metadanych HEAD, commondir i gitdir
  5. Wypisuje drzewo gałęzi do katalogu worktree
  6. Zapisuje indeks worktree

Jeśli gałąź już istnieje, $from jest ignorowane. W przeciwieństwie do gita, addWorktree() nie odmawia użycia gałęzi, która jest już wypisana w innym worktree, i nie uruchamia hooka post-checkout.

Z jawną nazwą metadanych

Użyj opcjonalnego argumentu name, gdy wiele powiązanych worktree może współdzielić tę samą nazwę bazową kopii roboczej.

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

Ta nazwa metadanych jest używana w .git/worktrees/<name>/ zamiast basename($path).

Z określonego commita

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

Wyświetlanie worktree

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 zawsze zawiera najpierw główne worktree, a następnie powiązane worktree. Wywołaj worktrees() na uchwycie głównego repozytorium: w przypadku uchwytu powiązanego worktree główny wpis obecnie wskazuje własną ścieżkę powiązanego worktree (#14).

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

Usuwanie worktree:

  1. Sprawdza, czy worktree jest zablokowane (kończy się niepowodzeniem, chyba że $force ma wartość true)
  2. Usuwa plik .git z katalogu worktree
  3. Usuwa katalog metadanych z .git/worktrees/

Pliki drzewa roboczego nie są automatycznie usuwane. Gałąź nadal istnieje.

Otwieranie powiązanego worktree

Powiązane worktree można otworzyć tak samo jak każde repozytorium:

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'

Operacje na repozytorium rozwiązują ścieżki w następujący sposób:

  • Obiekty: odczytywane ze wspólnego katalogu git i do niego zapisywane (project/.git/objects/)
  • Konfiguracja: odczytywana ze wspólnego katalogu git (project/.git/config)
  • Packed-refs: odczytywane ze wspólnego katalogu git
  • HEAD: osobno dla każdego worktree (każde worktree śledzi własną gałąź), wraz z ORIG_HEAD, MERGE_HEAD i innymi referencjami operacji
  • Indeks: osobno dla każdego worktree (każde worktree ma własny obszar przygotowania)
  • Luźne referencje: gałęzie i tagi są współdzielone we wspólnym katalogu (project/.git/refs/)
  • Hooki: odczytywane z katalogu git danego worktree, więc współdzielone hooki nie są uruchamiane (#12)

Stash nie obsługuje jeszcze powiązanych worktree: odczytuje tożsamość z katalogu danego worktree, który nie zawiera konfiguracji, i zapisuje tam reflog (#14).

Obiekt wartości 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

Do niskopoziomowego zarządzania worktree. WorktreeManager::add() zapisuje tylko metadane i plik .git: nie tworzy gałęzi, nie wypisuje plików ani nie zapisuje indeksu. Użyj $repo->addWorktree(), aby uzyskać gotową kopię roboczą. Gałąź musi już istnieć (lub być podana jako pełna nazwa referencji, taka jak refs/tags/v1.0.0, która odłącza 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');

Blokowanie worktree

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

Blokada jest przechowywana w pliku .git/worktrees/<name>/locked. Zawartość pliku stanowi powód blokady (może być pusty).

Wykrywanie worktree

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'

Układ plików

Metadane głównego worktree dla powiązanych worktree

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

Plik .git powiązanego worktree

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

Współdzielenie zasobów

Zasób Lokalizacja Współdzielony?
Obiekty $commonDir/objects/ Tak
Konfiguracja $commonDir/config Tak
Packed-refs $commonDir/packed-refs Tak
Hooki $gitDir/hooks/ Nie: Pitmaster nie uruchamia współdzielonych hooków w powiązanych worktree
HEAD $gitDir/HEAD Nie (osobno dla każdego worktree)
Indeks $gitDir/index Nie (osobno dla każdego worktree)
Luźne referencje $commonDir/refs/ Tak (HEAD i referencje operacji, takie jak MERGE_HEAD, są osobne dla każdego worktree)