Как работают стековые пул-реквесты на GitHub
Разберитесь, как работают стеки pull request на GitHub: создание через gh-stack, проверка уровней, перебазирование изменений и слияние зависимых PR.
Стековые пул-реквесты на GitHub разбивают одно крупное изменение на упорядоченную цепочку небольших пул-реквестов. Каждый из них нацелен на ветку пул-реквеста, расположенного ниже, а GitHub проводит ревью, ребейз и слияние всей цепочки как единого целого.
Большинству команд знаком и другой вариант. Вы разрабатываете фичу из трёх зависимых частей и открываете три PR, но во втором видны не только его собственные изменения, но и изменения первого. Ревьюеры сдаются, или же вы сливаете всё в один огромный PR, который никто толком не читает.
В этой статье по шагам разобрано, как устроены нативные стеки GitHub: как создать стек, как проходит ревью, что происходит при изменении PR в середине стека и к чему приводит каждый вариант слияния. Если сначала нужно освежить знания об обычных PR, см. статью о создании пул-реквестов из терминала.
Ключевые выводы
- В стеке GitHub базой каждого пул-реквеста служит ветка PR, расположенного ниже, поэтому в каждом диффе содержатся только изменения своего слоя.
- GitHub анонсировал стековые пул-реквесты в режиме публичной предварительной версии (public preview) 30 июля 2026 года. Функциональность может измениться.
- Согласно краткому руководству GitHub, для CLI-расширения gh-stack требуются GitHub CLI 2.90.0 или новее и Git 2.20 или новее.
- Если меняется промежуточный слой, все ветки над ним остаются на устаревшем коммите. Их нужно перебазировать снизу вверх и отправить с force-push.
- При слиянии самого верхнего готового PR он сливается вместе со всеми неслитыми PR под ним за одно слияние. Если слияние прерывается на полпути, уже слитые PR остаются слитыми, а остальные — открытыми. Каждый PR проверяется по правилам базовой ветки стека.
Проблема зависимых пул-реквестов
Зависимые пул-реквесты не выдерживают ревью, потому что GitHub сравнивает каждый PR с его базовой веткой. Допустим, вы открываете три PR, нацеленные на main: auth-middleware, затем api-routes, затем ui-login. Дифф второго PR содержит ещё и весь код middleware, а третий — вообще всё. Невозможно понять, какие строки на самом деле вносит конкретный PR.
Без нативных стеков разработчики обычно обходят эту проблему одним из двух способов. Либо объединяют всё в один большой PR, что замедляет ревью и скрывает ошибки. Либо вручную нацеливают каждый PR на ветку ниже, а затем при каждом изменении сами перебазируют и перенацеливают всю цепочку.
Что такое стековые PR на GitHub?
Стековый пул-реквест на GitHub — это одно звено упорядоченной цепочки. Каждый PR использует в качестве базы ветку PR, расположенного под ним, поэтому его дифф показывает только изменения, внесённые в этом слое. В обзоре стековых пул-реквестов от GitHub поясняется, что нижний PR нацелен на основную ветку стека (trunk) — обычно это main, но может быть любая ветка, например релизная. Все ветки стека должны находиться в одном репозитории, поэтому стеки не могут охватывать форки.
Вот как выглядит пример из трёх слоёв, оформленный в стек:
main
└─ auth-middleware PR → main (diff: middleware only)
└─ api-routes PR → auth-middleware (diff: routes only)
└─ ui-login PR → api-routes (diff: login UI only)
Модель по-прежнему строится на ветках и базах, которые Git и так понимает. GitHub добавляет поверх неё отслеживание: он знает порядок PR, связывает их между собой и работает с ними как с группой.
Создание стека с помощью gh CLI
Расширение gh-stack создаёт стеки и управляет ими из терминала. В кратком руководстве GitHub в качестве требований указаны GitHub CLI 2.90.0 или новее и Git 2.20 или новее. В справочнике команд CLI и README расширения указан более низкий минимум — gh 2.0, поэтому надёжнее ориентироваться на версию из краткого руководства. Кроме того, нужно пройти аутентификацию и иметь права на push в репозиторий. Если вы раньше не работали с gh, начните со статьи о создании пул-реквестов из терминала.
gh --version # 2.90.0 or later
git --version # 2.20 or later
gh auth login
gh extension install github/gh-stack
Затем постройте стек слой за слоем:
gh stack init # prompts for the first branch name, e.g. auth-middleware
git add -A
git commit -m "Auth middleware"
gh stack add api-routes
git add -A
git commit -m "API routes"
gh stack add ui-login
git add -A
git commit -m "Login UI"
gh stack push # pushes branches only
gh stack submit # opens PRs with correct bases and links them
gh stack view # branches, PR links, statuses, latest commits
gh stack init настраивает локальное отслеживание стека и создаёт первую ветку. Если не передан флаг --base, основной веткой становится ветка репозитория по умолчанию. В справочнике команд CLI отмечается, что push только загружает ветки и никак не затрагивает пул-реквесты. Именно команда submit открывает PR, назначает каждому правильную базовую ветку и объединяет их в стек.
Можно воспользоваться и веб-интерфейсом. На github.com создать стек можно, открыв PR, базой которого служит ветка более раннего PR, и выбрав Create stack. Чтобы расширить существующий стек, откройте любой входящий в него PR и выберите Add to stack в заголовке. Если ваши открытые PR уже выстроены в цепочку, GitHub покажет баннер с предложением превратить их в стек. Стеки доступны в мобильном приложении GitHub, а также ИИ-агентам для написания кода, например Copilot, через навык gh-stack. GitHub Desktop их не поддерживает.
Послойное ревью стека
Ревьюеры могут одобрять каждый слой стека по отдельности. Разные ревьюеры могут одновременно работать с разными слоями, пока автор продолжает надстраивать стек. Главное преимущество стековых пул-реквестов в том, что ревью проводится по небольшим диффам и не нужно ждать завершения всей фичи, чтобы его начать.
Открыв любой PR стека, вы видите дифф только этого слоя. В блоке слияния (merge box) находится карта стека со списком всех PR цепочки и их статусами, а число на иконке стека вверху страницы показывает, какой слой сейчас открыт.
Небольшие слои также оставляют в main более полезную историю. Каждый попадающий туда коммит сфокусирован на одной задаче, поэтому сеанс git bisect может указать на конкретное изменение, а не на слияние из тысячи строк.
Обновление PR в середине стека
Когда в ветку в середине стека попадает новый коммит, все ветки выше по-прежнему указывают на старый коммит. Эти верхние ветки нужно перебазировать на обновлённый слой и отправить с force-push. В нашем примере исправление по итогам ревью в api-routes приводит к отставанию ui-login. auth-middleware при этом не затрагивается.
При ручном управлении цепочкой это делается средствами обычного Git (основы ребейза см. в статье 10 Git commands):
# plain Git, no gh-stack
git checkout ui-login
git rebase api-routes # resolve any conflicts
git push --force-with-lease
# repeat for every branch above, bottom to top
С gh-stack, согласно руководству по стекированию изменений кода, всё сводится к двум командам:
gh stack checkout api-routes
git add -A
git commit -m "Fix API routes"
gh stack rebase --upstack # rebases only the branches above api-routes
gh stack push # per-branch --force-with-lease
Если ребейз останавливается из-за конфликта, исправьте конфликтующие файлы, выполните для них git add и запустите gh stack rebase --continue. Чтобы отказаться от операции, выполните gh stack rebase --abort — все ветки вернутся в состояние, в котором они были до ребейза.
На github.com, как только история стека перестаёт быть линейной, в блоке слияния появляется кнопка Rebase stack. Согласно руководству по управлению стековыми пул-реквестами, нажатие на неё последовательно, начиная снизу, переносит ветки на актуальную основную ветку и отправляет каждую с force-push. Коммиты, созданные таким образом на серверах GitHub, не подписываются, поэтому в репозиториях, требующих подписанных коммитов, ребейз следует выполнять локально с помощью gh stack rebase.
Ребейз переписывает верхние ветки. Если у коллеги одна из этих веток извлечена локально, ему следует остановиться и синхронизироваться с удалённым репозиторием, прежде чем делать новые коммиты. Иначе его следующий push вызовет конфликт с переписанной историей.
Слияние стека
При слиянии самого верхнего готового пул-реквеста он сливается вместе со всеми неслитыми PR под ним за одно слияние. Если на полпути что-то идёт не так, например возникает непредвиденный конфликт, слияние останавливается на этом PR: уже слитые PR под ним остаются в базовой ветке, а проблемный PR и всё, что выше, остаются открытыми. Если слить только нижние слои, PR над ними останутся открытыми, а GitHub перебазирует их и сменит их базу на базовую ветку стека. Согласно руководству по слиянию, прежде чем сливать любую часть стека, его история должна быть линейной.
| Что сливается | Что попадает в main | Что происходит с остальными |
|---|---|---|
Верхний готовый PR (ui-login) | Все три PR | Открытых не остаётся |
Нижний PR (auth-middleware) | Только этот PR | Верхние PR перебазируются и перенацеливаются на main |
Средний PR (api-routes) | Этот PR и все PR под ним | ui-login остаётся открытым и перенацеливается на main |
PR в середине стека GitHub нельзя слить отдельно. Он всегда сливается вместе с PR под ним. Согласно справочнику GitHub по стековым пул-реквестам, стеки поддерживают merge-коммиты, squash и rebase. При squash каждый PR превращается в отдельный объединённый коммит, поэтому слияние трёх PR добавляет в main три коммита.
Правила слияния берутся из базовой ветки стека, а не из ветки, на которую непосредственно нацелен каждый PR. Поэтому защита веток (branch protections) и обязательные проверки по-прежнему определяют, что попадёт в main. Стеки также работают с очередями слияния (merge queues), которые получают PR в правильном порядке. В журнале изменений от 30 июля 2026 года говорилось, что поддержка очередей слияния будет внедряться постепенно, а сейчас в руководстве GitHub по слиянию очереди слияния описаны как полностью поддерживаемые.
Стекирование до появления нативной поддержки
До того как GitHub начал нативно поддерживать стеки, для управления цепочкой веток требовался отдельный инструмент. Graphite, spr и git-branchless брали эту рутину на себя, работая вне собственной модели пул-реквестов GitHub.
Нативные стековые пул-реквесты GitHub работают с уже привычными вам ревью, обязательными проверками и защитой веток, поэтому команде не нужно осваивать ещё один инструмент. Для команд, которые перешли на инструменты стекирования лишь для того, чтобы справиться с нечитаемыми зависимыми PR, это устраняет главную причину ими пользоваться.
Заключение
Стек GitHub — это цепочка пул-реквестов, в которой каждый использует в качестве базы ветку ниже, а ребейз, перенацеливание и слияние берёт на себя GitHub. Поскольку каждый слой невелик, ревьюеры могут как следует его изучить, а изменение в середине стека требует перебазировать только ветки над ним. Чтобы попробовать, установите github/gh-stack и разбейте свою следующую многокомпонентную фичу на три слоя.
Часто задаваемые вопросы
Работает ли автослияние (auto-merge) со стековыми пул-реквестами GitHub?
Нет. В документации GitHub по слиянию указано, что автослияние для стековых пул-реквестов не поддерживается. Инструментам, выполняющим слияние через API, для стеков нужен асинхронный API слияния, поскольку старые эндпоинты слияния пул-реквестов не умеют сливать стек. Чтобы стек сливался автоматически после прохождения проверок, используйте очередь слияния: стеки поддерживают очереди слияния, и очередь принимает PR в порядке стека.
Что происходит, когда стековый пул-реквест удаляется из очереди слияния?
Все пул-реквесты над ним в стеке тоже покидают очередь. Чтобы стек оставался целым, очередь слияния позволяет группе слияния превышать настроенный максимальный размер на величину до 50 процентов. Если стек не помещается даже с этим запасом, GitHub разбивает его на несколько последовательных групп слияния.
Как обновить локальный стек после слияния нижнего пул-реквеста?
Выполните gh stack sync. За один шаг команда подтягивает последние изменения, продвигает основную ветку вперёд, перебазирует оставшиеся ветки поверх неё, отправляет их и обновляет статус каждого пул-реквеста. Добавьте --prune, чтобы удалить локальные ветки слитых пул-реквестов. Если sync сталкивается с конфликтом, она возвращает все ветки в исходное состояние; в этом случае выполните gh stack rebase, чтобы разрешить конфликт, а затем gh stack push.
Как изменить порядок, удалить или переименовать слои в существующем стеке?
Используйте gh stack modify — интерактивный терминальный интерфейс, который позволяет удалять, объединять, вставлять, переупорядочивать и переименовывать ветки. Для его работы нужны чистое рабочее дерево, линейная история, отсутствие незавершённого ребейза и пул-реквестов в очереди на слияние. В рамках одного сеанса нельзя одновременно менять порядок веток и удалять, объединять, вставлять или переименовывать их. Изменения применяются только при сохранении, а gh stack modify --abort восстанавливает стек в том виде, в каком он был до начала работы. После этого выполните gh stack submit, чтобы обновить данные на GitHub.