📖 Теория: стеки веток и Graphite

⚡ Кратко

Stacked PRs — это цепочка маленьких зависимых веток, где каждая ответвляется от предыдущей и имеет свой небольшой pull request. Graphite (gt) хранит связи «родитель → потомок» и автоматически перебазирует весь стек при изменении нижних веток.

  • trunk — основная ветка (main), от которой растут стеки
  • restack — авто-rebase всей цепочки одной командой
  • Под капотом это обычный Git: ветки, коммиты, rebase

Проблема большого pull request

Представьте задачу «добавить раздел задач (Tasks) в приложение». По-честному в ней несколько слоёв: модель базы данных, слой бизнес-логики, REST-эндпоинты, тесты. Если всё это сложить в одну ветку и один pull request, получится дифф на сотни строк. Ревьюеру тяжело: непонятно, с чего начать, легко пропустить ошибку, обсуждение растягивается на дни, а ветка тем временем устаревает относительно main.

Подход stacked PRs решает это так: каждый логический слой — отдельная маленькая ветка и отдельный небольшой PR, и эти ветки выстроены в стек — цепочку, где каждая следующая ответвляется от предыдущей.

feat/tasks-tests PR #4 ← вершина стека (gt top) │ feat/tasks-api PR #3 │ feat/tasks-service PR #2 │ feat/tasks-model PR #1 ← низ стека (gt bottom) │ main ─────────────────────── trunk

Каждый PR маленький и ревьюится независимо. PR #1 можно влить, как только он готов, не дожидаясь остальных.

💡 Зачем вообще инструмент? Стеки можно вести и в чистом Git. Но есть боль: как только вы поправили feat/tasks-model (низ стека), все ветки выше «висят» на старом коммите и их нужно перебазировать вручную — git rebase за git rebase. На стеке из 4–5 веток это утомительно и легко ошибиться. Именно это автоматизирует Graphite.

Модель Graphite

gt (Graphite CLI) — это надстройка поверх Git. Она не заменяет Git и не меняет формат репозитория: ветки и коммиты остаются обычными, команды git продолжают работать. Graphite лишь хранит дополнительные метаданные — кто чей родитель в стеке.

trunk (ствол)

trunk — это ваша основная ветка, обычно main или master. От неё растут все стеки. Вы задаёте её один раз командой gt init, и Graphite запоминает.

Родитель и потомок

Каждая ветка в стеке знает свою родительскую ветку (ту, от которой ответвилась) и своих потомков (те, что ответвились от неё). Это и есть «стек»: цепочка родитель → потомок от trunk до вершины.

restack — сердце Graphite

restack — это перестроение стека: Graphite проходит по цепочке и перебазирует (git rebase) каждую ветку на актуальную вершину её родителя. Вы делаете это явно командой gt restack — или оно случается автоматически после gt modify, когда вы поправили нижнюю ветку.

Главный выигрыш: поправил низ стека одной командой gt modify -a — и все ветки выше сами перебазировались на исправленную версию. В чистом Git это была бы серия ручных rebase.

Базовый цикл работы

  1. Создание. gt create создаёт новую ветку поверх текущей и коммитит изменения — то есть кладёт новый «этаж» на стек.
  2. Изменение. gt modify правит коммит текущей ветки и автоматически перестраивает ветки выше.
  3. Навигация. gt up/gt down двигают вас по этажам стека вместо git switch с длинными именами.
  4. Отправка. gt submit --stack пушит все ветки стека и создаёт/обновляет pull request для каждой.
  5. Синхронизация. gt sync подтягивает свежий main, перебазирует стек и предлагает удалить ветки уже влитых PR.

Graphite и чистый Git

Полезно держать в голове, что каждая команда gt — это знакомые операции Git под капотом:

GraphiteЧто делает в терминах Git
gt create -a -mновая ветка + git add + git commit + запись родителя
gt modify -agit commit --amend + rebase веток выше
gt restackсерия git rebase по цепочке родителей
gt submit --stackgit push --force-with-lease + открытие PR
gt syncgit fetch + rebase + чистка влитых веток
⚠️ force-push. Перебазирование переписывает хеши коммитов, поэтому gt submit делает force-push ваших веток. Для личных feature-веток стека это нормально. Но main в стек включать нельзя — иначе вы перепишете общую историю команды.
⚠️ Проверить по документации. Graphite активно развивается; названия и флаги команд могут отличаться в вашей версии. Источник истины — gt --help и graphite.com/docs.