Цель урока
После урока вы умеете создавать параллельные рабочие деревья через git worktree, держать в них разные ветки одновременно, убирать их и чистить мёртвые ссылки через prune. Понимаете, что объекты и refs лежат в одном .git/, а HEAD и index — своё для каждого дерева.
| Параметр | Значение |
|---|---|
| Bloom | Применение, Анализ |
| SFIA | Уровень 2–3 |
| Время | 25–35 минут |
| Артефакт | Алиасы wta/wtl/wtr + конвенция ~/code/<repo>/ + <repo>-<ветка>/ |
| Проверка | Мини-тест + параллельная работа feature + hotfix без stash |
Теория за 3 минуты
Обычный репозиторий выглядит так: один каталог my-repo/, внутри .git/ с объектами и refs, рядом ваши файлы. В каждый момент времени checked out одна ветка — это ваш HEAD.
git worktree снимает это ограничение. Вы можете дополнительно выдать отдельный каталог (рабочее дерево), который использует тот же .git/, но держит свой HEAD и свой index. В этом каталоге — другая ветка.
Что общее: объекты (.git/objects/), refs (.git/refs/), конфиг. Что раздельное: HEAD, index, stage, working copy.
Физически служебные файлы дополнительных worktrees лежат в .git/worktrees/<name>/ главной репы. Поэтому worktree add дешёвый — не клонирует объекты заново.
Главный случай применения: срочный hotfix в main, когда у вас в основном каталоге грязная feature/X с 20 правками. Без worktree — git stash, checkout main, пишешь hotfix, checkout feature, stash pop. С worktree — соседний каталог, отдельное IDE-окно, оба состояния живы параллельно.
Ограничение: одна ветка не может быть checked out сразу в двух worktree (Git защищается). Нужна либо другая ветка, либо --force, либо --detach.
Практика 1: параллельный hotfix без stash
Шаг 1. Собираем репо с начатой feature
mkdir -p demo-wt && cd demo-wt
git init -q
git config user.email s@e.com
git config user.name Student
echo "v1" > app.txt && git add . && git commit -q -m "feat: initial"
git checkout -q -b feature/profile
echo "// profile WIP" > profile.js
git add profile.js
# СТАРТОВАЛИ feature, но не дошли до коммита — index грязный
git status
В основном каталоге висит незакоммиченный profile.js. В классической схеме вы бы сейчас делали git stash, чтобы пойти чинить main.
Шаг 2. Добавляем worktree под hotfix
# вернуться в parent, потому что worktree add требует path вне текущего
cd ..
git -C demo-wt worktree add ./demo-wt-hotfix -b hotfix/login main
Команда:
- создала соседний каталог
demo-wt-hotfix/ - в нём HEAD указывает на новую ветку
hotfix/login, которая отходит отmain - в основном
demo-wt/ничего не поменялось — там по-прежнемуfeature/profile+ грязный index
Проверьте:
cd demo-wt-hotfix
git status # on branch hotfix/login, clean
ls # app.txt — копия файлов main, без profile.js
Шаг 3. Делаем hotfix параллельно
# пишем hotfix в соседнем каталоге
echo "v1 + hotfix" > app.txt
git add . && git commit -q -m "fix: urgent login"
git push -q 2>/dev/null || true # если бы был remote
# возвращаемся в основной — всё на месте
cd ../demo-wt
git status # всё ещё feature/profile с грязным profile.js
cat profile.js # содержимое сохранилось
Мы только что сделали hotfix, не трогая feature-ветку и не теряя WIP-правки. Оба каталога — валидные git-репозитории на один .git/.
Шаг 4. Убираем worktree, когда закончили
cd ..
git -C demo-wt worktree list
# /path/demo-wt <sha> [feature/profile]
# /path/demo-wt-hotfix <sha> [hotfix/login]
git -C demo-wt worktree remove ./demo-wt-hotfix
git -C demo-wt worktree list
# остался только главный
worktree remove требует чистое дерево (как и любой безопасный git). Если там грязь — закоммитьте или remove --force.
Практика 2: review чужого PR в отдельном каталоге
Типичная задача — быстро прокликать чей-то PR, запустить его тесты, сходить в код, и вернуться к своей работе. Без worktree — опять stash-дэнс.
cd demo-wt
# создаём worktree, привязанный к ветке review (имитация чужого PR)
git checkout -q -b other-pr main
echo "// PR content" > pr.txt && git add . && git commit -q -m "feat: pr content"
git checkout -q feature/profile
cd ..
git -C demo-wt worktree add ./demo-wt-review other-pr
Теперь:
- в
demo-wt/вы наfeature/profile - в
demo-wt-review/— код веткиother-pr, можно открыть в отдельной IDE-вкладке, запустить тесты, посмотреть diff
cd demo-wt-review
ls # виден pr.txt
git log --oneline -1
Закончили — убираем:
cd ..
git -C demo-wt worktree remove ./demo-wt-review
Частая ошибка: попытка добавить worktree с уже checked-out веткой:
git -C demo-wt worktree add ./another feature/profile
# fatal: 'feature/profile' is already checked out at '/path/demo-wt'
Решения: новая ветка (-b name), --detach (анонимный HEAD на том же коммите), либо --force (рискованно — index начнёт расходиться, Git не гарантирует согласованность).
Практика 3: worktree prune и зачем он нужен
Если вы удалите каталог worktree руками (без git worktree remove), в .git/worktrees/ останется осиротевшая запись.
cd demo-wt
git worktree add ../tmp-wt -b throwaway
rm -rf ../tmp-wt # удалили каталог напрямую
git worktree list # запись всё ещё показывается, но "prunable"
git worktree prune --verbose # чистит осиротевшие
git worktree list # остался только main
worktree prune — garbage collector для worktree-ссылок. Запускается автоматически при git gc, вручную нужен когда вы хотите немедленно освободить имена веток и слоты.
Когда нужен worktree lock
Если ваше дерево живёт на съёмном диске или в сетевом каталоге, который иногда отключается, Git может посчитать его «prunable» в момент недоступности. Защита:
git worktree add ../external-wt -b research
git worktree lock ../external-wt --reason "research на внешнем HDD"
# теперь prune его не тронет, даже если каталог временно пропал
git worktree unlock ../external-wt # когда хотите снять защиту
Артефакт: алиасы и конвенция каталогов
Конвенция, которую удобно держать во всех репозиториях:
~/code/
├── myrepo/ ← главный каталог, в нём обычно main или текущая feature
├── myrepo-hotfix/ ← worktree для срочных фиксов
└── myrepo-review/ ← worktree для чужих PR
Алиасы в ~/.gitconfig:
[alias]
# короткие worktree-операции
wt = worktree
wta = worktree add
wtl = worktree list
wtr = worktree remove
# добавить worktree рядом с текущим репо: git wth hotfix/login main
# создаст ../$(basename pwd)-<ветка>/
wth = "!f() { \
repo=$(basename $(pwd)); \
branch=$1; base=${2:-main}; \
short=$(echo $branch | tr '/' '-'); \
git worktree add \"../${repo}-${short}\" -b \"$branch\" \"$base\"; \
}; f"
Использование:
cd ~/code/myrepo
git wth hotfix/login # создаст ~/code/myrepo-hotfix-login с веткой hotfix/login от main
git wth review/pr-42 origin/main # review чужого PR от текущего main на remote
git wtl # посмотреть все активные
git wtr ../myrepo-hotfix-login # убрать когда закончили
Когда НЕ нужен worktree
- Проект маленький, ветки короткие,
stashсправляется → не плодите каталоги. - Сборка тяжёлая и требует каталог-специфичного
node_modules/target/на каждый worktree — диск съест. В таком случае иногда выгоднее один каталог +stash. - CI-контейнер, который клонирует репо в чистый каталог на каждый запуск — worktree там не нужен.
Правило: worktree хорош для долгоживущих параллельных контекстов локально. Если контекст short-lived и лёгкий — stash проще.
📝 Документирование
Напишите в NOTES.md учебного репозитория:
- Своими словами: что общего у двух worktree и что своё у каждого.
- Пример из вашей работы — случай, где worktree сэкономил бы переключение: какая была грязная ветка, куда пришлось бы stash’ить, сколько времени ушло.
- Когда НЕ стоит — одна причина против worktree из вашего контекста.
- Ваша конвенция каталогов — как вы назовёте hotfix / review worktrees в своих проектах.
Мини-тест
- Вы на
feature/X, index грязный. Что произойдёт, если сделатьgit worktree add ../hot feature/X? Какой флаг позволит обойти это? - Где физически живут
HEADиindexвторого worktree? - Что такое «prunable» worktree и когда нужен
worktree prune? - Зачем
worktree lockи в каком сценарии без него вы потеряете ссылку?
Ответы — в конце.
Что дальше
- Challenge → сломанный репозиторий с 10 проблемами. Задачи P9 и P10 — параллельный hotfix + feature и prune осиротевших worktree-записей.
- Системно с нуля → «Курс молодого бойца» DevIT Academy — git в рабочем потоке, не как набор команд.
Ответы на мини-тест
- Git откажется:
'feature/X' is already checked out at <path>. Обойти — новой веткой (-b feature/X-parallel),--detach(анонимный HEAD на том же коммите без ветки) или--force(не рекомендуется — index обоих деревьев может разойтись). - В
.git/worktrees/<name>/главной репы. Там лежатHEAD,index,gitdir(указатель на путь к каталогу дерева). Объекты и refs — общие, в главном.git/. - «Prunable» — worktree, каталог которого уже удалён на диске, но запись в
.git/worktrees/осталась.worktree pruneудаляет такие записи, освобождая имена веток и слоты. Запускается вручную или автоматически приgit gc. - Если worktree на съёмном диске или сетевом каталоге — в момент отключения Git сочтёт его prunable и может подчистить.
worktree lockговорит «этот worktree не мёртв, не трогать», даже если сейчас недоступен.