Merge queue и merge trains: безопасное вливание в trunk

Рейтинг: 70.2% · 15 голосов
Самый подробный курс по Git - от первого коммита до объектной базы и packfiles. Терминал и первый репозиторий, ветки и слияние, конфликты, удалённая работа и форджи (GitHub, GitLab), rebase и переписывание истории, спасение кода через reflog и fsck, stash и worktree, bisect и blame, подмодули и монорепо, хуки и подпись коммитов, внутреннее устройство и масштаб. Для новичков и профи. Актуально на 2026 (Git 2.54, дорожная карта 3.0).
Ответить
Аватара пользователя
Pavel_Git
Сообщения: 52
Зарегистрирован: 11 май 2026, 05:31

Merge queue и merge trains: безопасное вливание в trunk

Сообщение Pavel_Git »

Оглавление курса (52)
  1. Что такое контроль версий и почему победил Git
  2. Установка Git на Windows, macOS и Linux и первая настройка
  3. Терминал и как спрашивать у Git: выживание в командной строке
  4. Ментальная модель Git: три состояния, коммит-снимок и граф
  5. Первый репозиторий: init, add, commit, status
  6. Просмотр изменений: status, diff и индексация по частям
  7. История проекта: git log, диапазоны и поиск
  8. .gitignore: что не должно попасть в репозиторий
  9. .gitattributes: окончания строк, бинарники и атрибуты
  10. Ветки как указатели: branch, switch и detached HEAD
  11. Слияние веток: fast-forward против трёхстороннего merge
  12. Конфликты слияния: анатомия и уверенное разрешение
  13. Практикум: твой первый день с Git от нуля до push
  14. Удалённые репозитории: remote, fetch, базовый pull и refspec
  15. git push: безопасная отправка и защита от перезаписи
  16. Клонирование: shallow, partial clone, bundle и backfill
  17. GitHub, GitLab и форки: pull request, rulesets и защита веток
  18. Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
  19. Merge queue и merge trains: безопасное вливание в trunk (вы здесь)
  20. Культура code review и Conventional Commits
  21. git reset: три дерева и отмена индексации и коммитов
  22. git revert: безопасная отмена в общей истории
  23. Правка последнего коммита: git commit --amend
  24. git rebase: пересадка истории и золотое правило
  25. Интерактивный rebase: переписать серию коммитов
  26. git cherry-pick: перенос отдельных коммитов
  27. Stacked diffs: цепочка зависимых PR
  28. Массовое переписывание истории: git filter-repo и git replace
  29. git reflog: журнал, который помнит всё
  30. Восстановление утерянных коммитов, веток и файлов
  31. Откат опасных операций: reset --hard, сломанный rebase, плохой merge
  32. git stash: тайник для незавершённой работы
  33. git worktree: несколько рабочих деревьев одного репозитория
  34. git bisect: бинарный поиск регрессии
  35. git blame, mailmap и археология кода
  36. Теги, релизы, archive и bundle
  37. Подмодули: внешние репозитории внутри проекта
  38. Subtree, монорепозитории и sparse-checkout
  39. Git LFS и большие файлы
  40. Вспомогательные команды: clean, mv, rm, restore
  41. Псевдонимы и кастомизация .gitconfig
  42. Хуки Git: автоматизация на клиенте и сервере
  43. Подпись коммитов и тегов: SSH, GPG и Sigstore
  44. Внутреннее устройство I: объектная база Git
  45. Внутреннее устройство II: ссылки, HEAD, notes и reftable
  46. Внутреннее устройство III: индекс, packfiles, gc и обслуживание
  47. От SHA-1 к SHA-256: переход на новый хеш
  48. Огромные репозитории: partial clone, FSMonitor, Scalar
  49. Git в CI/CD: shallow, кеш, merge_group и безопасность
  50. Куда движется Git: дорожная карта 3.0 и экосистема
  51. Профессиональный setup: собираем рабочее окружение Git
  52. 30+ типичных ошибок Git и как из них выбираться
Боль: два PR зелёные по отдельности, а main красный

Представь типичный день в активной команде. Утром ты открыл PR-A: переименовал функцию getUser в fetchUser по всему модулю. Зелёный CI, ревью прошло, мержишь. Коллега в это же время открыл PR-B: добавил новый вызов getUser в другом файле. Его CI тоже зелёный - ведь когда он гонял тесты, твоего переименования ещё не было в main. Оба PR прошли проверки. Оба влиты. А main лежит: getUser больше не существует, сборка падает, и теперь вся команда сидит на сломанном trunk.

Это не баг тестов и не чья-то халатность. Это фундаментальная дыра классической модели "проверяй PR против main, потом мержи". CI каждого PR проверял старое состояние main, а не то, во что main превратится ПОСЛЕ серии вливаний. Конфликт тут не текстовый - git merge склеил бы файлы без единого collision-маркера. Это семантический (логический) конфликт: два изменения по отдельности корректны, а вместе ломают код. Git про такое не знает в принципе - он сравнивает байты, а не смысл.

Чем чаще команда мержит и чем ближе вы к trunk based merge (короткие ветки, десятки вливаний в день в один main), тем выше шанс этой гонки. На потоке в 50-100 PR в день "зелёный по отдельности" ломает main регулярно. Лечится это не дисциплиной, а механикой - очередью, которая проверяет кандидатов в том порядке и в том сочетании, в каком они реально лягут. Это merge queue (она же merge train в мире GitLab), и в 2026 это, пожалуй, ключевой сдвиг в code review - не про комментарии к строкам, а про то, КАК зелёный PR безопасно доезжает до trunk.

Изображение

Как устроена github merge queue: спекулятивная сборка

Идея простая до гениальности. Вместо того чтобы каждый PR гонять против текущего main и мержить "вслепую", очередь выстраивает кандидатов друг за другом и проверяет каждого против того состояния main, в котором он реально окажется, когда дойдёт его черёд.

Когда ты вместо обычного "Merge" жмёшь "Merge when ready", PR попадает не в main, а в хвост очереди. GitHub берёт кандидатов по порядку (FIFO) и собирает для каждого спекулятивный временный коммит - merge group:
  • кандидат в голове очереди собирается поверх HEAD ветки main;
  • следующий кандидат собирается поверх main + предыдущий кандидат;
  • третий - поверх main + первый + второй, и так далее.
Если в очереди PR-A, PR-B, PR-C, то проверяется A против main, B против main+A, C против main+A+B. То есть каждый кандидат тестируется ИМЕННО в том окружении, в котором он окажется после вливания всех, кто стоит впереди. Тот самый сценарий из начала урока тут ловится: PR-B (новый вызов getUser) собирается поверх main+PR-A (переименование), CI на этой комбинации падает, и B вылетает из очереди ДО того, как испортит trunk. Это и есть безопасное слияние: красным становится временный merge-group-коммит, а не main.

Код: Выделить всё

Очередь: [A] -> [B] -> [C]

base(A) = main
base(B) = main + A      (CI ловит конфликт A<->B здесь)
base(C) = main + A + B

A зелёный -> вливается в main
B красный -> выпадает, C перебазируется на main+A
Когда кандидат в голове очереди прошёл CI, его merge-group-коммит становится новым main, кандидат покидает очередь, а все, кто стоял за ним, при необходимости пересобираются поверх обновлённого main. Если кандидат упал - он выпадает из очереди (его убирают), а очередь перестраивает merge group для всех, кто стоял позади него.

Событие merge_group ci: где гоняются проверки

Тут спотыкаются почти все при первой настройке. Твой обычный workflow в GitHub Actions висит на событиях pull_request и push. Но merge queue собирает СВОЙ временный коммит (merge group), и для него GitHub генерирует отдельное событие - merge_group. Если твой required status check не подписан на merge_group, то при попадании PR в очередь проверка просто не запустится. GitHub будет вечно ждать чек, которого нет, и PR зависнет в очереди.

Поэтому правило железное: каждый workflow, который ты делаешь обязательным для слияния, должен слушать и merge_group тоже.

Код: Выделить всё

# .github/workflows/ci.yml
name: CI
on:
  pull_request:
  merge_group:        # <- БЕЗ этой строки очередь зависнет

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: make test
Логика разделения труда такая: на pull_request гоняешь быстрые проверки, чтобы дать автору фидбек в самом PR (линтер, юнит-тесты). А на merge_group имеет смысл вешать тяжёлый, дорогой и медленный набор - интеграционные тесты, e2e, сборку артефактов. Их нет смысла гонять на каждый push в фичеветку: они нужны ровно в момент, когда кандидат реально собирается лечь в trunk вместе с соседями. Так ты экономишь раннеры и при этом проверяешь именно ту комбинацию изменений, что поедет в main.

Внутри merge_group в контексте (github.event.merge_group) лежат base_sha и head_sha - база и голова спекулятивного коммита. checkout по умолчанию выкачивает уже собранную merge group, так что тесты бегут по будущему состоянию trunk, а не по голой фичеветке.

Настройка через repository rulesets, а не classic protection

Большой сдвиг 2026: GitHub уводит команды со старой "classic branch protection" на repository rulesets - они в GA и стали рекомендуемым способом защиты веток. Rulesets гибче: их можно держать на уровне организации, накладывать слоями, включать в режиме evaluate (логировать нарушения, не блокируя), таргетить ветки по шаблону fnmatch. Merge queue включается именно как правило ruleset.

Пошагово, для ветки main:
  • Settings -> Rules -> Rulesets -> New branch ruleset, таргет - ветка main (по имени или шаблону).
  • Включи правило "Require merge queue".
  • Включи "Require status checks to pass" и добавь туда ровно те проверки, что слушают merge_group. Это связка, без которой очередь бессмысленна: required status checks - это и есть критерий, по которому кандидат проходит или выпадает.
  • В настройках самой очереди задай параметры группировки.
Ключевые параметры очереди, и как они меняют поведение:
  • Maximum group size (напр. 5) - сколько кандидатов GitHub склеивает в одну merge group и гоняет одним прогоном CI. Прошла группа целиком - все 5 вливаются разом, экономия раннеров кратная. Упала - очередь делит группу пополам и перепроверяет половины (бинарный поиск виновника), пока не вычислит сломавший PR и не выкинет его.
  • Minimum group size + wait time - очередь придержит старт CI, пока не наберётся минимум кандидатов ИЛИ не выйдет таймаут (что раньше). Минимум 3, ожидание 5 минут: стартуем либо когда в очереди 3 PR, либо через 5 минут. Это размен между утилизацией раннеров и задержкой вливания.
  • Only merge non-failing и таймауты на чек - страховка от флапающих тестов, чтобы один нестабильный прогон не держал всю очередь.
После включения кнопка в PR меняется с "Merge" на "Merge when ready". Жмёшь - и PR уезжает в очередь, дальше всё автоматически.

merge train gitlab: тот же принцип, другой словарь

В GitLab та же механика называется merge train, и понимать различие словаря полезно, если ты живёшь в обеих экосистемах. Поезд (train) - это очередь merge request'ов в целевую ветку. Каждый вагон проверяется не на изоляции, а на merged results: изменения MR склеиваются с целевой веткой И со всеми MR, что стоят впереди в поезде. Первый MR проверяется против целевой ветки, второй - против цель+первый, и так далее - один в один спекулятивная сборка GitHub.

Поезд трогается, когда очередь пуста и ты жмёшь Merge (или Set to auto-merge). Подъезжает следующий MR - запускается его merge train pipeline поверх предыдущего, и пайплайны идут параллельно. Падает пайплайн в середине поезда - этот MR не вливается, GitLab выкидывает его из поезда и перезапускает пайплайны для всех вагонов, что стояли позади (ведь у них поменялась база - сломанного впереди больше нет). Слова разные - merge train против merge queue, merged results против merge group, - суть идентична: проверяем кандидата против будущего состояния trunk, а не прошлого.

Грабли и когда очередь избыточна
  • Забыл merge_group в workflow. Классика. PR влетает в очередь и виснет навсегда, потому что required-чек не стартовал. Всегда дублируй триггер: on: [pull_request, merge_group].
  • Флапающие тесты. Нестабильный тест в очереди опаснее, чем в PR: ложное падение выкидывает невиновный кандидат и заставляет пересобирать хвост. Очередь беспощадно вскрывает флаки - чините их, иначе trunk-based merge превратится в лотерею. Помогают retry на нестабильных шагах и "only merge non-failing".
  • Слишком большой maximum group size при дорогом CI. Группа 10 при частых падениях - это лавина бинарного поиска и долгие пересборки. На шумном тесте начинай с группы 2-3.
  • Очередь не спасёт от плохого ревью. Она ловит логические конфликты между PR и регрессии в сборке, но не заменяет глаза человека на дизайне и читаемости. Это слой поверх review, а не вместо него.
  • Когда оно избыточно. Команда из 2-3 человек, вливаний несколько в день, гонок практически нет - очередь добавит задержку (PR ждёт прогона merge_group перед вливанием) и сложность настройки без видимой выгоды. Окупается, когда параллельных PR в один trunk реально много и main ломается от их сочетаний. Нет потока - нет проблемы - не тащи очередь ради моды.
Мини-лаба: ставим merge queue руками

Понадобится репозиторий на GitHub, где ты админ. По шагам:
  • Создай файл .github/workflows/ci.yml с триггерами on: [pull_request, merge_group] и одним шагом, который что-то проверяет (хоть run: echo ok && exit 0).
  • Settings -> Rules -> Rulesets -> New branch ruleset, таргет - main. Включи "Require status checks to pass", добавь свою джобу CI. Включи "Require merge queue". Сохрани.
  • Открой два PR, которые по отдельности зелёные, но конфликтуют логически. Простейший вариант: PR-A удаляет строку echo ok из ci.yml и пишет echo a, PR-B рассчитывает на старую строку через grep. Или проще - сделай так, чтобы тест в одном PR ожидал переменную, которую другой PR переименовывает.
  • Нажми "Merge when ready" на обоих. Зайди в Settings -> Merge queue (или вкладку очереди в PR) и смотри, как кандидаты выстраиваются, как для второго собирается merge group поверх первого, и как падение на merge_group выкидывает виновника из очереди, не тронув main.
  • Открой Actions и найди прогоны с event: merge_group - это и есть проверки спекулятивных коммитов, отдельные от pull_request.
Что увидел: main НИ РАЗУ не покраснел, хотя один из PR был объективно ломающим. Очередь поймала конфликт на временном коммите. Это и есть цель.

Контрольные вопросы
  • Почему два PR, зелёные на pull_request, могут вместе сломать main, и какой именно тип конфликта тут срабатывает (текстовый или семантический)?
  • Против какого состояния merge queue проверяет третий кандидат C, если в очереди стоят A, B, C? Запиши формулу базы.
  • Что произойдёт, если required status check подписан только на pull_request, но не на merge_group, и PR попал в очередь?
  • Зачем нужен параметр maximum group size и что делает очередь, когда группа из 5 кандидатов падает целиком?
Итог

Классическое "проверь против main, потом мержи" имеет гонку: CI видит прошлое trunk, а не будущее, и логические конфликты между параллельными PR прорываются в main зелёными. Merge queue (GitHub, через repository rulesets) и merge train (GitLab) закрывают эту дыру спекулятивной сборкой: каждый кандидат проверяется против состояния trunk, в котором реально окажется - с учётом всех, кто впереди. Ключевая деталь настройки - событие merge_group ci, на которое обязаны слушать твои required-чеки. Это must-have для trunk based merge на высокой частоте и честно избыточно для маленькой команды. Не путай инструмент с серебряной пулей: очередь чинит интеграцию кандидатов, а не качество кода - ревью и зелёные стабильные тесты по-прежнему на тебе.
👍 ❤️3 🔥1 😄 🤔1
Аватара пользователя
raspberryninja
Сообщения: 1
Зарегистрирован: 26 май 2026, 17:27

Re: Merge queue и merge trains: безопасное вливание в trunk

Сообщение raspberryninja »

Вот это про merge_group наконец дошло. У меня PR неделю висел в очереди молча, чек просто не стартовал - триггер забыл добавить. Спасибо, теперь понятно почему.
👍1 ❤️1 🔥1 😄 🤔1
Аватара пользователя
elastic10
Сообщения: 1
Зарегистрирован: 14 май 2026, 11:28

Re: Merge queue и merge trains: безопасное вливание в trunk

Сообщение elastic10 »

А если очередь длинная и кто-то посередине падает - правда весь хвост пересобирается заново? На дорогом e2e это же дикие траты раннеров, или maximum group size как раз про это?
👍 ❤️1 🔥 😄 🤔1
Ответить
← Предыдущая глава
Рабочие процессы команды: GitFlow, GitHub Flow, trunk-based
Следующая глава →
Культура code review и Conventional Commits

Все главы курса «Git профессионально: от первого коммита до внутреннего устройства»

Поделиться темой: ✈ Telegram VK
Похожие запросы: Разрешение конфликтов слияния в Gitgit merge или rebase: что выбратьРабочие процессы Git: GitFlow, trunk-basedgit merge: слияние ветокGit в CI/CD

Вернуться в «Git профессионально: от первого коммита до внутреннего устройства»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость