Стеки веток
Стек — это цепочка веток, где каждая надстраивается над предыдущей:
main → api → ui. Ревьюить три маленьких пул-реквеста лучше, чем один
гигантский.

Gitcito рисует его как маршрут: сверху стартовая ветка, ниже по одной остановке на уровень. PR каждой остановки нацелен на остановку выше, а первая приземляется на стартовую ветку. Остановка показывает свои коммиты, нужен ли ей restack и — после отправки — номер PR.
Правка маршрута
Ничего не выполняется, пока вы не нажмёте «Применить». Выбрать ветку, подвинуть остановку, убрать её с маршрута — всё это правит список на экране. Настоящая операция перебазирует ветки и переключается на них, а этого не должен делать любопытный клик. Когда маршрут читается верно, Применить маршрут выполняет его одним отменяемым шагом; Отменить возвращает рисунок к тому, что говорит репозиторий.
Маршрут нарисован в порядке слияния: верхняя ветка вливается в ту, что ниже, вплоть до ветки, на которую приземляется стек.
| Элемент | Что делает |
|---|---|
| Поле Старт | Куда приземляется стек. Поменяйте — и вся цепочка перевесится на новую ветку и переиграется. |
| Поле остановки | Меняет, какая ветка занимает это место. Ветка, сошедшая с маршрута, только отвязывается, но не удаляется. |
| ↑ / ↓ | Двигает остановку на одну позицию. |
| ✕ | Снимает остановку с маршрута; соседи смыкаются. |
| Добавить остановку | Выберите ветку, которая у вас уже есть — она встанет на вершину маршрута, — или введите ещё не существующее имя: ветка будет создана на вершине последней остановки и переключена. |
| Кнопка со стрелкой | Переключает на эту остановку. |
Все поля — с подсказками по мере ввода: печатайте, чтобы отфильтровать, ↑/↓ и Enter,
чтобы выбрать, а введённое вне списка тоже засчитывается — так что удалённая ссылка
вроде origin/main годится как стартовая ветка.
Под капотом все эти правки — одна и та же операция: маршрут целиком, отданный разом. Поэтому один жест — одна отмена (⌘Z), а не след из наполовину применённых связей.
Чего стоит правка маршрута
Всё, что меняет порядок — обмен, сдвиг, другой старт, — переигрывает цепочку: собственные коммиты каждой остановки перебазируются на новую основу. Значит, может быть конфликт, ровно как при restack. Две остановки, трогающие одни и те же строки, не поменяются местами без человека, и тогда ничего не происходит: вся правка откатывается — вершины, родительские связи и незаконченный rebase, — а Gitcito называет две столкнувшиеся остановки. Задетый мышью список не должен бросать вас посреди rebase.
Restack — вторая половина сделки: это rebase, о котором вы попросили прямо, поэтому он останавливается на конфликте и отдаёт вид разрешения — а это и есть способ получить перестановку, в которой Gitcito отказал: разрешите там, потом двигайте остановку.
Отмена переигрывает прежний маршрут. Она не воскрешает старые коммиты: новые — та же работа с другими родителями.
Отправить всё
Отправить всё отправляет каждый уровень с --force-with-lease и на этом
останавливается — это gh stack push, ничего не открывающий. Отправить стек
как PR делает тот же push, а затем работу с PR; берите Отправить всё, когда
ветки нужны на удалённом, а до ревью ещё далеко.
Отправка стека связанными пул-реквестами
Подать сначала спрашивает: сколько pull request’ов откроет, сколько
перенацелит, на каком удалённом репозитории и по строке ветка → база на
каждый — открытие PR публично и отменяется тяжело. Раздел навигации, вписанный в каждое
описание, и есть то, что делает цепочку видимой на GitHub, который о стеках не
знает ничего.
Отправка происходит на отдельном экране: сначала план, потом прогресс по шагам, а в конце — ссылки на каждый pull request и на сам стек.

Отправить стек как пул-реквесты делает в один клик то, за что инструменты для стеков берут деньги:
- Пушит каждый уровень с
--force-with-lease(свежим веткам это не мешает, а перестроенным — необходимо). - Открывает пул-реквест для каждого уровня, где его нет — каждый на базе
родительской ветки, а не
main, поэтому каждое ревью показывает только свои коммиты. Заголовок и описание берутся из коммитов самого уровня. - Перенацеливает существующие пул-реквесты, чья база уехала.
- Записывает в тело каждого пул-реквеста раздел навигации по стеку, чтобы ревьюер на любом уровне видел всю цепочку и место этого пул-реквеста в ней.
Действие идемпотентно: нажимайте его после каждого перестроения, нового уровня или слитого пул-реквеста, и оно сходится — ничего не дублируется, трогается только то, что уехало.
Когда нижний пул-реквест слит, та же кнопка убирает за ним: потомок слитого уровня переподвешивается на основную ветку, уровень убирается из стека, его локальная ветка удаляется (это безопасно — основная ветка доказуемо её содержит), цепочка перестраивается, а все оставшиеся пул-реквесты перенацеливаются. Сливайте снизу вверх, нажимайте «Отправить», повторяйте.
На GitHub это ещё и настоящий стек
Сцепленные базы понимает любой хостинг, а на GitLab, Bitbucket и Azure DevOps это всё, что есть. У GitHub больше: с выходом превью stacked pull requests стек стал объектом на сервере. Как только pull request’ы созданы, Gitcito регистрирует их как стек — снизу вверх, — и вы получаете карту стека в интерфейсе PR, каскадный rebase на стороне сервера и merge верхнего PR, который приземляет все уровни под ним.
Если репозиторий не в этом превью или токен не умеет управлять стеками, вызов тихо пропускается: цепочка и её раздел навигации держатся сами, ровно как на других хостингах.
Перестроение стека
Когда меняется нижняя ветка — вы поправили замечания ревью в api — все ветки
над ней оказываются построены не на той базе. Перестроить каскадно
перебазирует всю цепочку через rebase --onto, так что переписывание родителя
не дублирует коммиты в его потомках. После перестроения нажмите Отправить
ещё раз: переписанные уровни будут запушены принудительно, и пул-реквесты
обновятся на месте.
Ограничения
- Отправка пока работает только с GitHub (создание работает на всех четырёх хостах, но перенацеливание и обновление тела требуют API GitHub).
- Уборка после слияния низа видит обычные слияния и слияния через ребейз по происхождению, а squash-слияния — спрашивая GitHub, приземлился ли пул-реквест ветки; так что с токеном GitHub убирается любой стиль слияния. На других хостах или без токена уровень, слитый через squash, всё ещё придётся убрать из стека вручную. И сначала сделайте fetch — проверка происхождения читает основную ветку по состоянию на ваш последний fetch.
- Раздел стека в теле пул-реквеста поддерживается между скрытыми маркерами — ваше собственное описание над ним сохраняется.
- Переупорядочивание и смена ствола переписывают историю на каждом затронутом уровне. Ветки ваши, и неотправленные уровни ничего не стоят, но уровень, уже находящийся на ревью, получит force-push при следующей отправке.
- Уровень сдвигается на одну позицию за раз. Два обмена — это два rebase, и остановка на полпути остаётся читаемым состоянием; перетаскивание сразу на три позиции — нет.
- Остановка перебазируется, поэтому ветка, на которую приземляется стек,
никогда не бывает ещё и остановкой — как и защищённая ветка (
mainиmaster, пока вы не поменяли список). И то и другое отклоняется, а не переписывает молча общую историю. - Перед тем как что-то открывать, отправка спрашивает удалённый репозиторий, какие ветки действительно дошли, и называет недостающие. На отсутствующий head GitHub отвечает сухим «Validation Failed», от которого никому не легче. Ветку, на которую приземляется стек, тоже проверяют: если она только локальная, отправка предложит отправить её и продолжить.
Где хранятся связи
Ссылки на родителя хранятся в конфигурации git, поэтому они путешествуют вместе с репозиторием и переживают повторное клонирование. Ничего не лежит ни в каком сервисе.
См. также: Интерактивный ребейз · Хостинги и пул-реквесты