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

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
shop (main) + shop-hotfix (linked)

Общее для всех worktree

Своё у каждого worktree

В основном worktree-
В связанном worktree-
Выберите элемент слева или справа.
Правило разделения простое. Всё, что описывает историю (объекты, ветки, теги, stash, конфиг, хуки), общее. Всё, что описывает текущее состояние одной папки (HEAD, index, незавершённый rebase или merge, reflog HEAD), своё. Связанный worktree хранит своё в .git/worktrees/<имя>/ основного репозитория, а в самой папке лежит не каталог .git, а файл-указатель.

Симулятор: команды и их эффект

Слева терминал, справа состояние диска и граф коммитов. Переключатель «cwd» выбирает, из какой папки выполняется команда: это важно, потому что часть команд ведёт себя по-разному в зависимости от того, где вы стоите. Недоступные в текущем состоянии команды приглушены.

Worktree simulator
cwd - текущая папка
Terminalzsh

    
Дискпапки и .git
Историяgit log --graph --decorate
start Один клон, одна папка, ветка main. Начните с git worktree add.

Что стоит попробовать в симуляторе:

  1. Создайте shop-hotfix, перейдите в него через cwd и сделайте коммит. Затем вернитесь в shop и выполните git log hotfix: коммит виден сразу, без push и pull, потому что объекты и ветки общие.
  2. Попробуйте сделать git checkout feature-x, когда эта ветка уже занята папкой shop-review. Git откажется.
  3. Сделайте папку shop-hotfix «грязной», вернитесь в shop и попробуйте удалить её через git worktree remove: без --force git защитит незакоммиченные правки.
  4. Удалите папку 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, хуки и конфиг общие. Коммит в одной папке сразу виден в остальных.
    Свой снимокУ каждой папки свои HEAD, index и состояние незавершённых операций. Rebase в одной не мешает работе в другой.
    Одна ветка - одна папкаВетку нельзя открыть дважды. Для чтения истории берите detached HEAD, для работы - новую ветку.

    Если раньше вы решали «срочный баг посреди фичи» вторым клоном или stash, попробуйте один раз сделать git worktree add. Скорее всего, обратно возвращаться не захочется.