Перейти к содержимому

Рабочие деревья

Git рабочие деревья позволяют нескольким рабочим каталогам использовать один репозиторий. У каждого рабочего дерева есть собственная рабочая копия (HEAD, индекс, рабочие файлы), но хранилище объектов, конфигурация и packed-refs общие с основным репозиторием. Pitmaster позволяет создавать, перечислять, блокировать и удалять связанные рабочие деревья, а также открывать их как репозитории. Очистка, перемещение и восстановление рабочих деревьев не реализованы.

Основные понятия

Обычный репозиторий git имеет одно рабочее дерево:

Код
project/
  .git/          (directory)
  src/
  README.md

С рабочими деревьями можно создать дополнительные рабочие копии:

Код
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

.git связанного рабочего дерева — это файл, а не каталог; он содержит относительный путь к рабочему дереву:

Код
gitdir: ../../path/to/project/.git/worktrees/review

Создание рабочего дерева

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() выполняет следующие действия:

  1. Создает ветку, если ее еще нет (на основе HEAD или указанного коммита $from)
  2. Создает каталог метаданных по пути .git/worktrees/<name>/
  3. Записывает файл .git в каталог рабочего дерева
  4. Записывает файлы метаданных HEAD, commondir и gitdir
  5. Извлекает дерево ветки в каталог рабочего дерева
  6. Записывает индекс для рабочего дерева

Если ветка уже существует, $from игнорируется. В отличие от git, addWorktree() не отказывает, если ветка уже извлечена в другом рабочем дереве, и не запускает хук post-checkout.

С явным именем метаданных

Используйте необязательный аргумент name, если у нескольких связанных рабочих деревьев может совпадать имя каталога рабочей копии.

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

Это имя метаданных используется в .git/worktrees/<name>/ вместо basename($path).

На основе определенного коммита

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

Список рабочих деревьев

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

В список всегда сначала входит основное рабочее дерево, а затем связанные рабочие деревья. Вызовите worktrees() для дескриптора основного репозитория: для дескриптора связанного рабочего дерева основной элемент сейчас указывает собственный путь связанного рабочего дерева (#14).

Удаление рабочего дерева

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

Удаление рабочего дерева:

  1. Проверяет, заблокировано ли рабочее дерево (операция завершается ошибкой, если $force не равно true)
  2. Удаляет файл .git из каталога рабочего дерева
  3. Удаляет каталог метаданных из .git/worktrees/

Файлы рабочего дерева не удаляются автоматически. Ветка продолжает существовать.

Открытие связанного рабочего дерева

Связанное рабочее дерево можно открыть так же, как любой репозиторий:

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'

При выполнении операций с репозиторием пути разрешаются следующим образом:

  • Объекты: чтение и запись в общий каталог git (project/.git/objects/)
  • Конфигурация: чтение из общего каталога git (project/.git/config)
  • Packed-refs: чтение из общего каталога git
  • HEAD: отдельный для каждого рабочего дерева (каждое отслеживает собственную ветку), как и ORIG_HEAD, MERGE_HEAD и другие ссылки операций
  • Индекс: отдельный для каждого рабочего дерева (у каждого своя область подготовки)
  • Непакованные ссылки: ветки и теги общие для всех и хранятся в общем каталоге (project/.git/refs/)
  • Хуки: читаются из отдельного каталога git рабочего дерева, поэтому общие хуки не запускаются (#12)

Stash пока не поддерживает связанные рабочие деревья: он читает данные об авторе из отдельного каталога рабочего дерева, где нет конфигурации, и записывает туда reflog (#14).

Объект значения рабочего дерева

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

Для низкоуровневого управления рабочими деревьями. WorktreeManager::add() записывает только метаданные и файл .git: он не создает ветку, не извлекает файлы и не записывает индекс. Для полноценной рабочей копии используйте $repo->addWorktree(). Ветка уже должна существовать (или следует указать полное имя ссылки, например refs/tags/v1.0.0; в этом случае 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');

Блокировка рабочих деревьев

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

Блокировка хранится в виде файла по пути .git/worktrees/<name>/locked. Содержимое файла — причина блокировки (может быть пустым).

Обнаружение рабочих деревьев

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'

Структура файлов

Метаданные основного рабочего дерева для связанных рабочих деревьев

Код
.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

Файл .git связанного рабочего дерева

Код
gitdir: <relative path to>/project/.git/worktrees/<name>

Совместное использование ресурсов

Ресурс Расположение Общий?
Объекты $commonDir/objects/ Да
Конфигурация $commonDir/config Да
Packed-refs $commonDir/packed-refs Да
Хуки $gitDir/hooks/ Нет: Pitmaster не запускает общие хуки в связанных рабочих деревьях
HEAD $gitDir/HEAD Нет (отдельный для каждого рабочего дерева)
Индекс $gitDir/index Нет (отдельный для каждого рабочего дерева)
Непакованные ссылки $commonDir/refs/ Да (HEAD и ссылки операций, например MERGE_HEAD, отдельны для каждого рабочего дерева)