Робочі дерева
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
Створення робочого дерева
$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() виконує такі дії:
- Створює гілку, якщо її ще немає (від HEAD або вказаного коміту
$from) - Створює каталог метаданих за адресою
.git/worktrees/<name>/ - Записує файл
.gitу каталог робочого дерева - Записує файли метаданих
HEAD,commondirіgitdir - Отримує дерево гілки в каталог робочого дерева
- Записує індекс для робочого дерева
Якщо гілка вже існує, $from ігнорується. На відміну від git, addWorktree() не відхиляє гілку, якщо вона вже отримана в іншому робочому дереві, і не запускає перехоплювач post-checkout.
Із заданою назвою метаданих
Використовуйте необов’язковий аргумент name, якщо кілька пов’язаних робочих дерев можуть мати однакову базову назву отриманої копії.
$wt = $repo->addWorktree(
'/tmp/sandboxes/app',
'feature/review',
name: 'sandbox-app',
);
Ця назва метаданих використовується в .git/worktrees/<name>/ замість basename($path).
Із певного коміту
$wt = $repo->addWorktree('/tmp/hotfix', 'hotfix/urgent', $repo->resolve('v1.0.0'));
Перелік робочих дерев
$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).
Видалення робочого дерева
$repo->removeWorktree('review'); // by metadata name
$repo->removeWorktree('/tmp/review'); // or by worktree path
// Force remove (even if locked)
$repo->removeWorktree('review', force: true);
Видалення робочого дерева:
- Перевіряє, чи заблоковано робоче дерево (операція завершується помилкою, якщо
$forceне має значення true) - Видаляє файл
.gitз каталогу робочого дерева - Видаляє каталог метаданих із
.git/worktrees/
Файли робочого дерева автоматично не видаляються. Гілка залишається.
Відкриття пов’язаного робочого дерева
Пов’язане робоче дерево можна відкрити так само, як будь-який репозиторій:
$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).
Об’єкт значення робочого дерева
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).
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');
Блокування робочих дерев
// 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. У файлі міститься причина блокування (може бути порожнім).
Виявлення робочих дерев
// 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, окремі для кожного робочого дерева) |