Megamerges в Jujutsu: работа с пятью ветками сразу через один octopus merge
----------------------------------------------------------------------------
Когда в рабочем дне одновременно живут три-пять веток - фичи, багфиксы, чужие изменения, зависимые PR и свежие эксперименты - обычный Git-воркфлоу быстро превращается в череду бесконечных `checkout`, `rebase` и борьбы с контекстом. В Jujutsu (jj) под это есть отдельный подход - megamerge, построенный вокруг octopus merge.
Суть идеи, предложенной Айзеком Корбри: вы создаёте один большой merge-коммит с тремя и более родителями (octopus merge), в который включаете сразу все актуальные рабочие ветки. Дальше вы всегда работаете поверх суммы всех своих изменений, а не поверх одной из веток по очереди. Это резко снижает стоимость переключения задач и убирает большую часть боли от параллельной разработки.
---
Коротко о Jujutsu и его модели
Jujutsu (jj) - система контроля версий, совместимая с Git по протоколу, но с иной моделью работы с историей:
- конфликты - сущность первого класса, они явно хранятся и удобнее разбираются;
- операции `commit` и `rebase` считаются базовыми, вокруг них построена большая часть UX;
- есть разделение на immutable- и mutable-коммиты: одни считаются "застывшей" историей, другие можно свободно переписывать;
- вместо веток в гитовом понимании используются bookmarks - указатели на коммиты, которые не двигаются автоматически за рабочей копией;
- revset - язык запросов к графу коммитов (аналог git-revisions, но более выразительный);
- WIP (work in progress) - незавершённые изменения, часто живут в виде mutable-коммитов над вашей основной работой.
Jujutsu визуализирует историю по-своему. Типичные обозначения в `jj log`:
- `@` - текущая рабочая копия;
- `○` - mutable-коммит;
- `◆` - immutable-коммит (часто это `trunk`, основная линия разработки);
- псевдографика (`├─╮` и т.п.) показывает связи между коммитами.
---
Merge-коммит в jj: ничего "особенного"
В традиционном гитовом восприятии merge-коммит - нечто "особое", с отдельной логикой и правилами. В модели Jujutsu такого разделения нет. Merge-коммит - просто обычный коммит, у которого больше одного родителя. Он:
- не обязан быть пустым;
- может содержать изменения, как любой другой коммит;
- может иметь более двух родителей.
Коммиты с тремя и более родителями принято неформально называть octopus merge. На первый взгляд может казаться, что сценарии для них экзотические: "Когда мне вообще понадобится сливать больше двух веток сразу?" На практике как раз многородительские merge-коммиты и позволяют построить мощный megamerge-воркфлоу.
---
Что такое megamerge и зачем он нужен
В megamerge-подходе вы почти никогда не работаете "поверх одной конкретной ветки". Вместо этого вы:
1. Берёте все ветки и коммиты, которые вам сейчас важны:
- фичевые ветки;
- багфиксы;
- ветки, которые уже отправлены на ревью и пока "висят";
- чужие ветки, от которых зависит ваша работа;
- локальные экспериментальные/приватные коммиты без букмарка.
2. Создаёте один octopus merge-коммит, у которого каждый из этих коммитов - родитель.
Именно этот merge-коммит и называют megamerge. Важно:
- megamerge никогда не пушится на удалённый репозиторий;
- в remote отправляются только обычные рабочие ветки (bookmarks), которые входят в megamerge как родители;
- всё, что вы разрабатываете дальше, находится поверх megamerge, то есть уже учитывает все параллельные изменения.
В результате вы получаете единое "агрегированное состояние" проекта, включающее все ваши ветки сразу. Любая новая работа автоматически совместима с ними по содержимому репозитория (по крайней мере, до первых конфликтов).
---
Как создать megamerge: базовый шаг
Базовый сценарий выглядит так:
1. Определите, какие ветки должны войти в megamerge. Например:
- `bugfix/login-timeout`
- `feature/new-dashboard`
- `refactor/auth-flow`
- `pr/1234-wip`
- локальный эксперимент без букмарка, но с известным ревсетом.
2. Соберите их в один merge-коммит, указав каждый как родителя. Автор подхода предпочитает:
- дать этому коммиту понятное имя (описание);
- оставить его содержимое пустым (то есть он не меняет файлы, а просто связывает родителей).
На выходе получается пустой коммит, находящийся поверх всех перечисленных веток сразу, но являющийся их общим потомком. Именно над ним вы начинаете делать свою повседневную работу: писать код, разбивать изменения на логические части, создавать дополнительные ветки - всё это будет опираться уже на объединённое состояние.
---
Megamerge как "грунтовка" под WIP
Всё, что живёт выше megamerge, удобно рассматривать как WIP-зону:
- вы можете создавать от megamerge отдельные ветки для разных экспериментов;
- легко сплитить коммиты на более мелкие и осмысленные;
- держать рядом несколько направлений работы, не боясь, что они "разъедутся" по разным базам.
Ключевое ощущение: megamerge становится вашим личным "мини-trunk'ом", в который включены все актуальные изменения, но который при этом не видит удалённый репозиторий. То есть:
- внешний мир видит только чистые ветки;
- вы работаете в более богатом и согласованном контексте.
---
Как изменения попадают обратно в ветки: роль `absorb`
Рано или поздно WIP над megamerge дозревает до состояния, когда его надо "разнести" по настоящим веткам:
- что-то должно попасть в багфикс-ветку;
- что-то - в фичу;
- что-то - в зависимую ветку, принадлежавшую коллеге;
- часть изменений может раствориться по нескольким коммитам сразу.
Ручное разбрасывание таких кусков по дереву коммитов заняло бы кучу времени. Здесь вступает в игру ключевая команда Jujutsu - `absorb`.
`absorb` делает за вас почти всю скучную работу:
- анализирует каждую изменённую строку или хунк;
- ищет, в каком нижнем mutable-коммите по дереву эта строка появилась впервые;
- автоматически относит изменение именно к тому коммиту.
На практике это выглядит как "честная магия":
- никаких тёмных эвристик;
- понятная логика: строка меняется там же, где она родилась;
- результат - аккуратные, логичные патчи в нужных местах истории.
Благодаря `absorb` megamerge-воркфлоу становится вообще реализуемым на ежедневной основе: вы можете работать "над всеми ветками сразу", а затем за один проход аккуратно разложить изменения по их естественным точкам.
Важно помнить, что:
- `absorb` не всегда может принять идеальное решение, особенно в случаях тяжёлых рефакторингов, переименований файлов или массивной перекомпоновки кода;
- часть работы всё равно остаётся за вами: где-то нужно вручную скорректировать патч, где-то - явно указать целевой коммит.
Но даже с этим ограничением объём рутины снижается радикально.
---
Aliases и автоматика: `jj stage`, `jj stack`, `jj restack`
Сама по себе команда `absorb` уже даёт мощный выигрыш, но Jujutsu позволяет собрать поверх неё удобные алиасы, превращающие рутинный воркфлоу в несколько коротких команд. Чаще всего в контексте megamerge всплывают:
- `jj stage` - удобная оболочка вокруг выборочного индексирования и последующего `absorb`. Позволяет быстро:
- подобрать нужные куски изменений (по файлам, хункам);
- "привинтить" их к нужным коммитам, почти не задумываясь о структуре дерева.
- `jj stack` - инструмент для работы со стеком коммитов (серией изменений). Упрощает:
- просмотр и перестановку коммитов;
- разбиение крупного WIP на аккуратные логические шаги;
- подготовку стека для ревью и отправки в удалённый репозиторий.
- `jj restack` - автоматический "переподъём" стека поверх обновлённого `trunk` или другой базы. Особенно полезен, когда trunk активно меняется, а ваш megamerge должен идти в ногу с ним.
Комбинация этих алиасов даёт тот самый "скользящий" рабочий процесс:
1. Работаете над фичами поверх megamerge.
2. Через `jj stage` и `absorb` разбрасываете изменения по нужным веткам.
3. При обновлении trunk запускаете `jj restack`, и весь ваш стек (включая megamerge) аккуратно переподнимается вверх.
---
Почему megamerge удобнее постоянных rebase
Если вы привыкли к гитовой практике "каждую ветку держим свежей через rebase на `main`", megamerge поначалу может казаться странным. Но у него есть несколько заметных преимуществ:
1. Меньше переключений контекста.
Вместо:
- `git checkout feature-a`
- поработать
- `git checkout bugfix-b`
- снова собраться с мыслями
вы всё время живёте в одной рабочей среде, где уже присутствуют и `feature-a`, и `bugfix-b`, и ещё пара зависимых веток.
2. Реальные конфликты проявляются раньше.
Конфликты между ветками, которые вы ведёте параллельно, проявляются сразу в megamerge, а не спустя неделю при попытке смерджить PR. Это даёт:
- меньше сюрпризов;
- более контекстный взгляд на конфликт (вы помните, что только что писали).
3. Менее хрупкая история.
Вместо частого `rebase` каждой ветки на изменившийся trunk вы:
- обновляете trunk;
- один раз `restack`'аете megamerge и связанные с ним ветки;
- продолжаете работать в уже перестроенном общем контексте.
4. Чистая внешняя история.
В удалённом репозитории видны аккуратные ветки без следов ваших промежуточных "акробатик":
- megamerge остаётся локальным артефактом;
- ваши WIP-коммиты спокойно живут поверх него и не засоряют публичный лог.
---
Как держать megamerge в актуальном состоянии
Чтобы megamerge был полезен каждый день, его нужно регулярно "освежать":
1. Trunk обновился.
- Подтягиваете новые изменения в локальный `trunk` (или аналогичный bookmark).
- Запускаете `jj restack` на стеке, в который входит megamerge.
- В результате megamerge и его WIP-коммиты оказываются поверх обновлённого trunk без ручной пляски с rebase.
2. Одна из веток-предков изменилась.
- Если в родительской ветке появились новые коммиты, вы можете:
- обновить соответствующий bookmark;
- пересобрать megamerge или снова прогнать restack.
- В типовой конфигурации алиасов это сводится к одной-двум коротким командам.
3. Часть работы завершена и влитa.
- Когда фича-ветка или багфикс оказываются в trunk и больше не нужны как отдельная линия:
- вы можете удалить её как родителя из будущих megamerge-коммитов;
- или, если стек перерабатывается, пересоздать megamerge уже без этой ветки.
Таким образом, megamerge живёт как "скользящий" коммит: его родители и позиция в истории меняются вместе с реальным состоянием проекта.
---
Типичная рабочая конфигурация (TL;DR)
Для удобного megamerge-воркфлоу обычно настраивают:
- алиас для быстрой сборки megamerge из списка активных веток;
- алиас `jj stage`, который:
- показывает изменения;
- позволяет выбрать, что именно идти `absorb`'ить;
- алиас `jj stack` для работы с серией коммитов (просмотр, перестановка, сплит);
- алиас `jj restack`, который умеет:
- подтягивать стек поверх нового trunk;
- аккуратно переносить megamerge и его WIP.
Ключевые элементы:
- один octopus merge-коммит со всеми важными ветками в качестве родителей;
- `absorb` вместо ручного squash и механической правки патчей;
- автоматический `restack` на trunk, чтобы не тратить время на каждое отдельное rebase.
---
Практические советы по использованию megamerge
1. Начинайте с малого.
Не обязательно сразу собирать megamerge из пяти веток. Попробуйте:
- взять две-три, которые вы и так часто трогаете;
- построить небольшой octopus merge;
- пожить с ним пару дней, наблюдая, как меняется ощущение от работы.
2. Давайте megamerge-ам понятные описания.
Пусть комментарий к коммиту ясно говорит, какие ветки в него входят и для чего он собран. Это упростит:
- отладку;
- возврат к старым состояниям;
- понимание истории через несколько недель.
3. Относитесь к megamerge как к инструменту, а не цели.
Megamerge - это не часть "официальной" истории проекта, а ваш рабочий костяк. Не страшно:
- удалить его;
- пересоздать;
- собрать по-новому, когда изменились задачи.
4. Регулярно прогоняйте `absorb`.
Не копите гигантский WIP над megamerge неделями:
- раз в день (или перед окончанием рабочего блока) прогоняйте `absorb`;
- распределяйте изменения по веткам;
- держите стек относительно чистым и понятным.
5. Чётко разделяйте, что пушится, а что нет.
Важно не перепутать:
- megamerge, WIP-коммиты и временные ветки - локальны;
- только подготовленные ветки, привязанные к задачам/PR, уходят в remote.
---
Частые вопросы (FAQ)
Нужно ли всем в команде переходить на megamerge одновременно?
Нет. Megamerge отлично работает как личный воркфлоу. Остальные участники могут продолжать пользоваться привычным Git-процессом. Снаружи они увидят только аккуратные ветки и коммиты, которые вы им отправляете.
Не усложняет ли megamerge историю?
Публичную - нет, если вы не пушите megamerge и WIP-слой. Локальная история действительно становится богаче, но это ваша зона комфорта и экспериментов. За счёт aliасов и `absorb` управление ею ощущается проще, чем ручной juggling веток и rebase.
Что если `absorb` распределит изменения "не туда"?
Вы всегда можете:
- просмотреть результат;
- откатить конкретный absorb-операцию;
- вручную поправить целевой коммит.
На практике проблемы возникают редко и чаще всего связаны с крупными переписыванием кода, где даже человеку трудно однозначно сказать, "где родилась" конкретная строка.
Можно ли использовать megamerge в репозиториях с очень активным trunk?
Да, как раз в таких репозиториях `jj restack` и megamerge дают максимум пользы. Главное - не забывать регулярно:
- обновлять локальный trunk;
- прогонять restack;
- своевременно убирать из megamerge ветки, которые уже влиты или устарели.
---
Выводы
Megamerge в Jujutsu - это не экзотический трюк с графом коммитов, а рабочий способ организовать параллельную разработку так, чтобы:
- меньше времени тратить на переключение веток и rebase;
- раньше видеть конфликты между своими собственными задачами;
- работать всегда поверх актуального контекста, в который входят все важные изменения;
- при этом сохранять внешнюю историю чистой и понятной для команды.
Один octopus merge с несколькими родителями, автоматическое разнесение изменений через `absorb` и регулярный `restack` на trunk позволяют экономить часы, которые раньше уходили на механические действия и восстановление концентрации. Если вы уже знакомы с Jujutsu на базовом уровне и ведёте несколько веток одновременно, megamerge-подход почти наверняка окупится уже в первую неделю использования.



