Git worktrees: несколько рабочих папок одного репозитория
Обычно один репозиторий - это одна папка с файлами и одна текущая ветка. Чтобы посмотреть другую ветку, приходится либо прятать правки в stash, либо коммитить «WIP», либо клонировать репозиторий второй раз. Git worktree даёт третий вариант: несколько рабочих папок, которые смотрят на одну и ту же базу коммитов и веток.
В самом git механизм называется worktree (рабочее дерево). Слово «workspace» чаще встречается в IDE и GUI-клиентах, но речь об одном и том же: у одного .git появляется несколько независимых checkout'ов. Ниже - интерактивное объяснение: что у них общее, что своё, и симулятор основных команд прямо в статье.
Репозиторий - это две разные вещи
Когда мы говорим «репозиторий», обычно имеем в виду папку проекта. Но внутри неё живут две сущности с разной природой:
~/git/shop/ ├── .git/ ← база данных: объекты, ветки, теги, конфиг, хуки │ ├── objects/ все коммиты, деревья и содержимое файлов │ ├── refs/ указатели: heads (ветки), tags, remotes │ ├── HEAD «на какой ветке я сейчас» │ └── index staging area, снимок для следующего коммита ├── app/ ← рабочее дерево: файлы, которые вы редактируете ├── package.json └── node_modules/ (не в git, просто лежит рядом)
Первая - база данных в .git/. Она хранит все коммиты и все ветки сразу, независимо от того, какая ветка сейчас выбрана. Вторая - рабочее дерево: распакованный снимок одного коммита плюс ваши незакоммиченные правки. Между ними стоят два маленьких файла: HEAD (какой коммит распакован) и index (что попадёт в следующий коммит).
Ключевая мысль: рабочее дерево - это представление базы, а не сама база. Ничто не мешает построить несколько таких представлений от одной базы. Именно это делает git worktree add: создаёт новую папку, свой HEAD и свой index для неё, а объекты и ветки оставляет общими.
Что общее, а что своё
Нажмите на любой элемент, чтобы увидеть, где он лежит в основном и в связанном (linked) worktree и почему он именно там.
.git при двух worktreeОбщее для всех worktree
Своё у каждого worktree
--.git/worktrees/<имя>/ основного репозитория, а в самой папке лежит не каталог .git, а файл-указатель.Симулятор: команды и их эффект
Слева терминал, справа состояние диска и граф коммитов. Переключатель «cwd» выбирает, из какой папки выполняется команда: это важно, потому что часть команд ведёт себя по-разному в зависимости от того, где вы стоите. Недоступные в текущем состоянии команды приглушены.
git worktree add.
Что стоит попробовать в симуляторе:
- Создайте
shop-hotfix, перейдите в него через cwd и сделайте коммит. Затем вернитесь вshopи выполнитеgit log hotfix: коммит виден сразу, без push и pull, потому что объекты и ветки общие. - Попробуйте сделать
git checkout feature-x, когда эта ветка уже занята папкойshop-review. Git откажется. - Сделайте папку
shop-hotfix«грязной», вернитесь вshopи попробуйте удалить её через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 посреди незаконченной фичи
Где это реально пригодилось
!Hotfix без потери контекста
Фича остаётся в своей папке с открытыми вкладками редактора, запущенным dev-сервером и незакоммиченными правками. Hotfix делается рядом и мержится, не трогая её.
⇄Code review с запуском
Ветку коллеги можно открыть в отдельной папке, запустить тесты или приложение и параллельно продолжать свою работу. Полезно, когда ревью требует не только чтения диффа.
≈Сравнение версий вживую
Два worktree на разных тегах позволяют поднять старую и новую версию на соседних портах и сравнить поведение, а не читать changelog.
⌕bisect и долгие тесты
git bisect постоянно переключает рабочее дерево. В detached worktree он не мешает основной папке, а долгий тестовый прогон не блокирует редактирование.
AIПараллельные агенты
Несколько AI-агентов в одной папке ломают друг другу index и рабочие файлы. Каждому агенту - свой worktree и своя ветка, а результат мержится как обычный PR. У Claude Code для этого есть флаг --worktree, и это одна из причин, почему worktree снова стали заметной темой.
CIЛокальная сборка нескольких веток
Собрать документацию из main и release/* в одном скрипте проще, когда каждая ветка распакована в свою папку, а объекты при этом не дублируются.
Как это стыкуется с автоматизацией на агентах, я подробнее описывал в статье «Автоматизация жизни с AI агентами». Worktree там - базовая единица изоляции: один агент, одна папка, одна ветка.
Подводные камни
Новый 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. Скорее всего, обратно возвращаться не захочется.