git worktree: несколько рабочих деревьев одного репозитория

Рейтинг: 78.5% · 14 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

git worktree: несколько рабочих деревьев одного репозитория

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория (вы здесь)
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Боль, которую решает git worktree

Знакомая ситуация. Ты по локоть в фиче: десяток правок, половина в рабочем дереве, половина в индексе, тесты наполовину переписаны, ничего пока не коммитится потому что состояние сырое. И тут прилетает: на проде упал платёжный модуль, чини сейчас же, ветка hotfix/payments от main. Что делаешь? Классика - git stash или мусорный коммит "wip", потом git switch hotfix, фикс, потом обратно и git stash pop с молитвой что ничего не конфликтнёт. А ещё IDE переиндексирует весь проект дважды, контейнеры пересоберутся, кеш сборки протухнет. Полчаса возни вокруг пятиминутного фикса.

Второй вариант, который многие используют годами - сделать второй git clone в соседнюю папку. Работает, но это отдельная копия: своя история, свои объекты, свой .git на гигабайт. Чтобы стянуть из неё свежие коммиты обратно, надо настраивать remote на локальную папку или гонять через сервер. И диск тает.

git worktree - это третий путь, и он почти всегда правильный. Команда даёт тебе второй (третий, пятый) рабочий каталог, привязанный к тому же самому .git. Один репозиторий - несколько рабочих деревьев. В каждом дереве своя ветка, свой HEAD, свой индекс, свои незакоммиченные правки. А вот хранилище объектов (.git/objects), refs, конфиг, стек reflog и удалённые ветки - общие. Получается параллельная работа git без второго клона и без stash-плясок: текущую фичу не трогаешь вообще, она спокойно лежит в своём дереве, а hotfix чинишь в соседнем. Рабочее дерево git перестаёт быть единственным.

Изображение

Как это устроено внутри: общий object store и линкованные деревья

Чтобы понимать грабли, надо понимать механику. Когда ты делаешь git worktree add, Git не копирует репозиторий. Он создаёт новую папку с файлами проекта и кладёт туда не каталог .git, а файл .git - одну строчку-указатель.

Код: Выделить всё

$ git worktree add ../hotfix hotfix/payments
Preparing worktree (checking out 'hotfix/payments')
HEAD is now at 9c4f1ab Fix invoice rounding

$ cat ../hotfix/.git
gitdir: /home/dev/project/.git/worktrees/hotfix
Видишь? Файл .git нового дерева указывает внутрь основного репозитория, в .git/worktrees/hotfix. Заглянем туда:

Код: Выделить всё

$ ls /home/dev/project/.git/worktrees/hotfix
HEAD  commondir  gitdir  index  ORIG_HEAD  logs

$ cat /home/dev/project/.git/worktrees/hotfix/commondir
../..
$ cat /home/dev/project/.git/worktrees/hotfix/gitdir
/home/dev/project/hotfix/.git
Расклад такой. У каждого линкованного дерева своё - это HEAD (на какой ветке стоишь), index (твой staging), logs/HEAD (локальный reflog этого дерева). А файл commondir указывает на общий каталог - туда, где живут objects, refs, config. Поэтому коммит, который ты сделал в hotfix-дереве, мгновенно виден из основного: объект лёг в общий .git/objects, ветка обновилась в общем .git/refs/heads. Никакого fetch между деревьями не нужно - это физически один и тот же стор. Вот в чём преимущество над клоном: ноль дублирования объектов, фиксация в одном дереве сразу доступна в другом.

Можно проверить руками, что refs общие, а HEAD - нет:

Код: Выделить всё

$ git rev-parse --git-path HEAD
/home/dev/project/.git/worktrees/hotfix/HEAD
$ git rev-parse --git-path refs/heads/main
/home/dev/project/.git/refs/heads/main
HEAD резолвится в локальный путь дерева, а ветка - в общий. Почти всё в refs/ общее: heads, remotes, tags. Изолированы только refs/bisect, refs/worktree и refs/rewritten (чтобы параллельный bisect или rebase в разных деревьях не дрался за состояние).

Практика: git worktree add и реальный hotfix без потери прогресса

Разберём тот самый сценарий по шагам. Ты в основном дереве, в ветке feature/cart, куча несохранённого. Прилетает баг. Не коммитим, не стэшим - просто отращиваем второе дерево:

Код: Выделить всё

$ git status -s
 M src/cart/Basket.php
A  src/cart/Promo.php
?? notes-todo.txt

$ git worktree add -b hotfix/payments ../proj-hotfix origin/main
Preparing worktree (new branch 'hotfix/payments')
branch 'hotfix/payments' set up to track 'origin/main'.
HEAD is now at 9c4f1ab Fix invoice rounding
Флаг -b создал новую ветку hotfix/payments от origin/main и сразу выложил её в каталог ../proj-hotfix. Твоя feature/cart в основном дереве не пострадала: правки на месте, индекс не тронут, status тот же. Идёшь чинить:

Код: Выделить всё

$ cd ../proj-hotfix
$ git switch -c ... # уже не нужно, мы на hotfix/payments
$ git status -s
$ # правишь src/payment/Gateway.php
$ git commit -am "hotfix: retry failed charge on gateway timeout"
$ git push -u origin hotfix/payments
Запушил, открыл PR, влил. Возвращаешься в основное дерево - и продолжаешь ровно с той точки, где бросил. Никаких pop и конфликтов:

Код: Выделить всё

$ cd ../project
$ git status -s
 M src/cart/Basket.php
A  src/cart/Promo.php
?? notes-todo.txt
Когда фикс уже не нужен - сносим дерево начисто:

Код: Выделить всё

$ git worktree remove ../proj-hotfix
remove удалит и каталог на диске, и служебную запись в .git/worktrees. Если в дереве есть незакоммиченные изменения, Git откажется - добавь --force, если уверен.

Смотрим, что у нас понаращено - git worktree list:

Код: Выделить всё

$ git worktree list
/home/dev/project          a17be20 [feature/cart]
/home/dev/project-review   4f9c1de [pr-481]
/home/dev/project-release  88a0c12 (detached HEAD)
Первая строка - основное дерево (main worktree), остальные - линкованные. Видно путь, текущий коммит и ветку. Третье дерево в detached HEAD - типично для сборки релиза по тегу. Флаг --porcelain даст машинно-читаемый вывод для скриптов.

Полезные сценарии параллельной работы git

worktree расцветает там, где надо держать несколько веток одновременно git и не терять контекст.
  • Hotfix без потери прогресса - разобрали выше, главный кейс.
  • Ревью PR в отдельном дереве. Коллега прислал PR, надо реально запустить его ветку, а не читать диф в браузере. git worktree add ../review pr-481 - и у тебя живой каталог с его кодом, можно гонять тесты, кликать руками, а твоя работа стоит нетронутой в основном дереве.
  • Долгий тест или сборка. Прогон интеграционных тестов на 40 минут запускаешь в отдельном дереве на нужной ветке - и продолжаешь кодить в основном, не дожидаясь. Два процесса не дерутся за одни и те же файлы.
  • Сборка релиза по тегу. git worktree add --detach ../release v3.2.0 даёт чистое дерево ровно на теге, в detached HEAD, без создания мусорной ветки. Собрал артефакт - снёс дерево.
  • Сравнение двух версий бок о бок. Старая и новая ветка открыты в двух окнах редактора одновременно - удобно для рефакторинга и портирования.
worktree против clone и stash. stash хорош для секундной паузы внутри одной ветки, но это стек, его легко забыть, и он не даёт работать с двумя ветками реально параллельно. clone даёт полную изоляцию (включая конфиг и хуки), но это лишние объекты на диске и обмен через remote. worktree - золотая середина: общий object store, мгновенный обмен коммитами, отдельные рабочие каталоги. Если нужна именно полная изоляция истории - бери clone; во всех остальных случаях параллельной работы - worktree.

Управление: list, lock, move, prune

lock - страховка от случайного prune. Если дерево лежит на съёмном диске или сетевой шаре, которая не всегда смонтирована, Git может посчитать его "пропавшим" и вычистить служебную запись. Запираем:

Код: Выделить всё

$ git worktree lock --reason "USB-диск, монтирую по средам" ../proj-usb
$ git worktree unlock ../proj-usb
Заблокированное дерево не тронет prune и не удалит remove без --force.

move - переехать каталогом правильно. Не двигай дерево через mv: сломаешь обратную ссылку gitdir. Делай:

Код: Выделить всё

$ git worktree move ../proj-hotfix ../archive/proj-hotfix
move сам поправит и gitdir в .git/worktrees, и файл .git внутри дерева.

prune - сборка мусора по деревьям. Если ты всё-таки снёс каталог дерева через rm -rf, в .git/worktrees останется висячая запись. Чистим:

Код: Выделить всё

$ git worktree prune --dry-run --verbose
Removing worktrees/proj-hotfix: gitdir file points to non-existent location
$ git worktree prune
--dry-run покажет, что улетит, без удаления - бери в привычку. А если переехал не дерево, а сам основной репозиторий, и линки протухли - чинит git worktree repair, он переустанавливает связи в обе стороны.

Типичные грабли
  • Одну ветку нельзя открыть в двух деревьях. Это главное ограничение. Git защищает тебя от двух деревьев с одной веткой - иначе два рабочих процесса дёргали бы один указатель ветки и поймали бы рассинхрон.

    Код: Выделить всё

    $ git worktree add ../second feature/cart
    fatal: 'feature/cart' is already checked out at '/home/dev/project'
    
    Решения: открой другую ветку, или заведи новую (-b), или возьми detached HEAD на тот же коммит через --detach, если ветка не нужна.
  • rm -rf вместо remove оставляет висячую запись. Лечится prune, но лучше всегда remove.
  • mv вместо move рвёт ссылку gitdir, дерево "теряется". Лечится repair или move заранее.
  • Один stash на все деревья - это миф. reflog общий, но stash в обычном виде применяется к текущему дереву; не рассчитывай ловить состояние одного дерева в другом через stash.
  • Submodule и ignored-файлы не переезжают сами. Новое дерево чистое: node_modules, .env, vendor - всё надо поставить заново. Это не баг, это отдельный каталог.
  • IDE и build-кеш. Каждое дерево - отдельный проект для IDE; индексируется отдельно. Это и плюс (параллельность), и минус (память).
Мини-лаба: повтори руками
  • Создай песочницу и пару коммитов:

    Код: Выделить всё

    git init -b main wt-lab && cd wt-lab
    echo v1 > app.txt && git add app.txt && git commit -m "init"
    git switch -c feature && echo work > app.txt && git commit -am "feature wip"
    
  • Не коммить остальное - сэмулируй "сырое" состояние: echo dirty >> app.txt (не коммить).
  • Отрасти hotfix-дерево от main, не трогая feature:

    Код: Выделить всё

    git worktree add -b hotfix ../wt-hotfix main
    
  • Зайди в ../wt-hotfix, сделай фикс и коммит. Вернись - убедись, что dirty в app.txt на месте (git status -s).
  • Посмотри устройство: cat ../wt-hotfix/.git и ls .git/worktrees/hotfix. Найди commondir и gitdir.
  • Попробуй открыть main вторым деревом - поймай fatal. Затем сделай git worktree list, git worktree remove ../wt-hotfix, git worktree prune --dry-run.
Контрольные вопросы
  • Что лежит в файле .git линкованного дерева и куда он указывает?
  • Какие части состояния у деревьев свои, а какие общие? Где физически хранятся объекты коммита, сделанного в линкованном дереве?
  • Почему Git запрещает открыть одну ветку в двух деревьях и как это обойти для сборки по коммиту?
  • Чем remove отличается от rm -rf и зачем тогда нужен prune?
Итог

git worktree add даёт второе рабочее дерево на тот же .git: общий object store, отдельные HEAD и индекс, мгновенный обмен коммитами без второго клона и без stash. Это штатный инструмент для hotfix без потери прогресса, ревью PR, долгих тестов и сборки релизов. Помни одно жёсткое правило (одна ветка - одно дерево) и убирай за собой через remove, lock, move и prune, а не через сырые mv и rm. Освоишь - и переключения веток ради "быстро глянуть" уйдут из твоей рутины почти полностью.
👍2 ❤️1 🔥1 😄 🤔1
Аватара пользователя
regexadmin
Сообщения: 1
Зарегистрирован: 20 май 2026, 05:37

Re: git worktree: несколько рабочих деревьев одного репозитория

Сообщение regexadmin »

Сидел годами на втором clone под ревью, диск пух как на дрожжах. Перешёл на worktree - объекты общие, коммиты сразу видны в основном дереве, экономия места реальная. Спасибо за разбор commondir/gitdir, наконец понял почему ничего не дублируется.
👍 ❤️ 🔥 😄 🤔
Аватара пользователя
Gotbeer
Сообщения: 1
Зарегистрирован: 03 июн 2026, 18:43

Re: git worktree: несколько рабочих деревьев одного репозитория

Сообщение Gotbeer »

Поймал ту самую fatal already checked out когда хотел открыть main второй раз. Помог --detach на нужный коммит для сборки релиза. Вопрос: а submodule в новом дереве реально надо git submodule update заново гонять каждый раз?
👍 ❤️ 🔥 😄 🤔
Ответить
← Предыдущая глава
git stash: тайник для незавершённой работы
Следующая глава →
git bisect: бинарный поиск регрессии

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: git clone: клонирование репозиторияgit stash: спрятать незавершённые измененияgit worktree: несколько рабочих деревьев

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость