Перейти до вмісту

Робочі дерева

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 та іншими посиланнями операцій
  • Індекс: окремий для кожного робочого дерева (кожне має власну область індексації)
  • Loose refs: гілки й теги спільні у загальному каталозі (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 Ні (окремо для кожного робочого дерева)
Loose refs $commonDir/refs/ Так (HEAD і посилання операцій, як-от MERGE_HEAD, окремі для кожного робочого дерева)