Git worktrees: несколько рабочих папок одного репозитория
Обычно один репозиторий - это одна папка с файлами и одна текущая ветка. Чтобы посмотреть другую ветку, приходится либо прятать правки в stash, либо коммитить «WIP», либо клонировать репозиторий второй раз. Git worktree даёт третий вариант: несколько рабочих папок, которые смотрят на одну и ту же базу коммитов и веток.
Типичные задачи, из-за которых заводят вторую папку:
-
Срочный баг посреди фичи
Файлы изменены, вкладки открыты, dev-сервер запущен. Stash прячет работу и часто конфликтует на pop, второй clone тяжёлый. Нужна соседняя папка, которая не трогает первую.
-
Ревью не только по диффу
Чужую ветку нужно поднять и прогнать, не бросая свою. Checkout в той же папке выкинет из контекста: пропадут незакоммиченные правки, процессы и открытые файлы.
-
Несколько AI-агентов сразу
В одной папке агенты делят одни и те же файлы, перезаписывают правки и переключают ветку друг у друга. Каждому нужна своя папка и своя ветка.
-
Собрать несколько веток локально
Документация из
mainиrelease/*, два тега на соседних портах. Второй clone дублирует историю, а worktree распаковывает только рабочие файлы.
В самом git механизм называется worktree (рабочее дерево). Слово «workspace» чаще встречается в IDE и GUI-клиентах, но речь об одном и том же: у одного репозитория появляется несколько независимых папок. Ниже - куда из такой папки уходит коммит, и симулятор основных команд.
Репозиторий, ветки, worktree
Слово «репозиторий» обычно значит папку проекта. Удобнее смотреть на три уровня:
- Репозиторий - общая история. Все коммиты и все имена веток живут здесь сразу.
- Ветки - ярлыки на коммиты:
main,feature-x,agent/a. - Worktree - папка на диске, которая смотрит на одну ветку. Именно её файлы вы редактируете.
git worktree add добавляет ещё одну папку к тому же репозиторию. На каждую ветку смотрит не больше одной папки.
Типичная раскладка для параллельных агентов: вы в shop держите feature-x, два агента работают рядом на своих ветках, ответвлённых от вашей. Кликните на папку. Оранжевая рамка - куда уйдёт следующий коммит. После checkout папка переезжает под новую ветку.
agent/a, не в вашу feature-x.
Могут ли два агента писать прямо в вашу feature-x? Нет. Каждый пишет в ту ветку, на которую смотрит его папка. Сменить ветку можно, но только на свободную.
- Коммит всегда в свою текущую ветку. Агент A в
shop-aпишет вagent/a.git commitне умеет целиться «в feature-x из соседней папки». - На любую свободную ветку сесть можно.
mainникто не открыл - агент делаетgit checkout main, папка переезжает подmain, и следующие коммиты пойдут туда.feature-xзанята вашей папкой, checkout git отклонит. - Чужую ветку можно читать, но не двигать.
git merge feature-xизshop-aсработает: читаетfeature-x, пишет результат вagent/a. - В
feature-xрезультаты собираете вы. Изshop:git merge agent/a, потомgit merge agent/b. Либо через PR. На удалённый репозиторий правило «одна ветка - один worktree» не распространяется.
git log. Своё у папки: файлы на диске и текущая ветка. Поэтому чужой коммит не появится в ваших файлах, пока вы сами не сделаете merge или checkout.Симулятор: команды и их эффект
Сверху команды репозитория: создать папку, список, prune. На карточке папки - то, что происходит внутри неё: новый файл, commit, checkout, merge, удаление. Отдельного переключателя «текущая папка» нет: нажали кнопку на карточке - терминал сам пишет cd.
git worktree add, потом создайте файл на карточке новой папки.
Что стоит попробовать в симуляторе:
- Создайте
shop-hotfix. На её карточке в поле файла оставьтеapp/price.jsили введите своё имя и нажмитеtouch. Файл появится только в этой папке, git пока его не знает (??). Затем commit: коммит сразу виден в графе справа, без push и pull. - На карточке
shopсделайтеgit merge hotfix: файл появится и здесь. Так результат соседней папки попадает в вашу ветку. - Откройте
feature-xвshop-reviewи попробуйтеgit checkout feature-xизshop. Git откажется: ветка занята. - Создайте ещё один файл в
shop-hotfixи попробуйтеgit worktree remove: без--forcegit защитит незакоммиченные правки. - Удалите
shop-oldчерезrm -rfи посмотритеgit worktree list: запись осталась и помечена какprunable.
Одна ветка - один worktree
Самое частое удивление: git не даёт сделать checkout ветки, которая уже открыта в другой папке. Причина не в вредности, а в том, что ветка - это указатель, а index и рабочие файлы у каждой папки свои. Если бы две папки смотрели на одну ветку, коммит в первой сдвинул бы указатель, а вторая папка вдруг оказалась бы «позади» собственной ветки с устаревшим index. Git не умеет надёжно разрешать такую ситуацию, поэтому запрещает её заранее.
$ git checkout feature-x fatal: 'feature-x' is already used by worktree at '/Users/me/git/shop-review' $ git branch -d feature-x error: cannot delete branch 'feature-x' used by worktree at '/Users/me/git/shop-review'
Из этого правила есть два выхода:
- Detached HEAD. Если нужен просто снимок коммита (посмотреть старый релиз, собрать версию, прогнать bisect), используйте
git worktree add --detach <путь> <коммит>. Никакая ветка не занимается. - Новая ветка. Флаг
-bсоздаёт ветку сразу при создании папки. Если написатьgit worktree add ../shop-hotfixбез указания ветки, git сам создаст ветку с именем папки (shop-hotfix) от текущего HEAD.
--force. Он позволяет открыть одну ветку в двух папках, но тогда вы сами отвечаете за то, что после коммита во второй папке первая будет в рассинхроне. На практике это нужно только для восстановления после сбоев.Три способа переключить контекст
Представьте сценарий: вы посреди фичи, файлы поменяны, ничего не закоммичено, и прилетает срочный баг в main. Переключите подход и сравните, что придётся сделать и что при этом окажется общим.
Срочный hotfix посреди незаконченной фичи
Параллельные агенты
Одна папка - это один index и один набор рабочих файлов. Если два агента правят один checkout одновременно, они наступают друг другу на staging area, конфликтуют в неотслеживаемых файлах и в худшем случае переключают ветку под ногами соседа. Git этого не запрещает: для него это просто «грязное» рабочее дерево, в котором кто-то ещё печатает.
Worktree даёт каждому агенту отдельную папку и отдельную ветку при общей истории. Практичная раскладка выглядит так:
~/git/shop/ ← вы, ветка feature-x ~/git/shop-a/ ← агент A, git worktree add -b agent/a ~/git/shop-b/ ← агент B, git worktree add -b agent/b
Что из этого следует на практике:
- Свой index.
git addодного агента не попадает в коммит другого. - Свой HEAD. Агент может делать checkout, rebase и даже bisect, не выкидывая вас из ветки. Правило «одна ветка - один worktree» здесь полезно: двум агентам нельзя сесть на одну и ту же ветку, и это лучше обнаружить сразу, чем разгребать общий указатель.
- Свои рабочие файлы. Линтер, форматтер и codegen не дерутся за одни и те же пути.
- Общая база. Коммит агента A сразу виден агенту B и вам: без clone, без push на origin «чтобы другой бот подтянул черновик».
- В вашу ветку агенты не пишут. Как показано в диаграмме выше, их коммиты уходят в
agent/aиagent/b. Вfeature-xони попадают только через вашgit mergeизshopили через PR.
Результат каждой сессии мержится как обычный PR. Это та же модель, что и hotfix посреди фичи, только «срочный баг» здесь - вторая задача, которую вы отдали другому агенту, пока первый ещё думает.
Инструменты уже подстраиваются. У Claude Code есть флаг --worktree: агент сам создаёт связанную папку и работает там. В Cursor параллельные попытки тоже удобно разводить по отдельным worktree. В «Автоматизация жизни с AI агентами» я описывал ту же схему для OpenCode и Kimaki (/new-worktree): один агент, одна папка, одна ветка, отдельный канал в Discord. Worktree снова стали заметной темой не потому, что git изменился, а потому что параллельных писателей кода стало больше одного.
Цена та же, что у любого linked worktree: node_modules, .env и порты в каждой папке свои. Для агентов это обычно закрывается тем же скриптом, которым вы поднимаете hotfix после git worktree add.
Подводные камни
Новый worktree - это чистый checkout
В него попадают только файлы из git. Всё, что лежало рядом и игнорировалось, придётся создать заново: node_modules, vendor, .env, локальные базы, кэш сборки. Для JS-проектов установка зависимостей в каждой папке - основная цена подхода. Помогают менеджеры с общим кэшем (pnpm), а .env проще копировать скриптом сразу после worktree add.
Порты, контейнеры и IDE
Два запущенных приложения от одного проекта будут спорить за один порт, один docker-compose проект и одну локальную базу. Обычно решается переменной окружения или суффиксом имени проекта. IDE откроет вторую папку как отдельный проект, что чаще плюс, чем минус: индексы и настройки не перемешиваются.
Не переносите и не удаляйте папки руками
Связанный worktree и основной репозиторий хранят абсолютные пути друг к другу. После mv ссылки ломаются. Правильные команды: git worktree move для переноса, git worktree remove для удаления, git worktree prune для очистки записей о папках, которых уже нет, и git worktree repair, если пути всё-таки разошлись. Если worktree лежит на съёмном диске, git worktree lock запретит prune удалять запись, пока диск отключён.
Общее - значит общее
git stash - это просто ссылка refs/stash, поэтому список stash один на все папки. Хуки и config тоже одни. Если нужен конфиг для конкретной папки, включите extensions.worktreeConfig и используйте git config --worktree. Сабмодули с worktree работают, но каждый worktree держит собственную копию сабмодуля, и это отдельный источник сюрпризов.
Bare-репозиторий как основа
Популярная раскладка для тех, кто живёт в worktree постоянно: git clone --bare в папку .bare, а все ветки, включая main, открываются как связанные worktree рядом. Тогда нет «главной» папки, которая отличается от остальных. Единственная тонкость: у bare-клона по умолчанию не настроен refspec для fetch, и его нужно добавить руками: git config remote.origin.fetch "+refs/heads/*:refs/remotes/origin/*".
Шпаргалка
| Команда | Что делает | Заметка |
|---|---|---|
| git worktree add <path> <branch> | Открывает существующую ветку в новой папке | Ветка не должна быть занята другим worktree |
| git worktree add -b <new> <path> [<start>] | Создаёт ветку и сразу открывает её | <start> по умолчанию - текущий HEAD |
| git worktree add <path> | Создаёт ветку с именем папки | Удобно для одноразовых экспериментов |
| git worktree add --detach <path> <commit> | Открывает коммит без ветки | Для bisect, сборки старых версий, просмотра тегов |
| git worktree list [-v] | Показывает все папки, их HEAD и статус | Отмечает prunable, locked, detached |
| git worktree remove [--force] <path> | Удаляет папку и запись о ней | Без --force откажется, если есть правки |
| git worktree prune | Убирает записи о вручную удалённых папках | Ветки при этом не удаляются |
| git worktree move <path> <new-path> | Переносит папку и обновляет ссылки | Вместо mv |
| git worktree lock / unlock | Защищает запись от prune | Для съёмных дисков и сетевых папок |
| git worktree repair | Чинит ссылки после переноса руками | Запускать из основной или из связанной папки |
Если раньше вы решали «срочный баг посреди фичи» вторым клоном или stash, попробуйте один раз сделать git worktree add. Скорее всего, обратно возвращаться не захочется.