# Стратегии merge PR (GitHub) _Совместная работа · GitLab Knowledge Base_ **TL;DR:** В GitHub три кнопки для merge PR: Create a merge commit (--no-ff, с merge-коммитом), Squash and merge (один сжатый коммит без родителя из ветки), Rebase and merge (rebase + fast-forward без merge-коммита). Команды выбирают одну для всего проекта. Когда PR готов к merge, GitHub предлагает три стратегии - это не разные команды Git, а три способа влить ветку. На GitLab/Bitbucket варианты те же, только названия чуть отличаются. ## Create a merge commit Эквивалент `git merge --no-ff feature`. Создаётся merge-коммит с двумя родителями, даже если fast-forward возможен. ``` main: ... → A → B → M ← merge-коммит \ / feat: C → D ``` **Преимущества:** - История сохраняет факт ветвления - видно, где была feature. - PR откатывается одной командой: `git revert -m 1 `. - Все коммиты PR попадают в main как есть. **Недостатки:** - История захламляется merge-коммитами, особенно на активном проекте. - Линейная навигация по истории сложнее (`git log --first-parent` помогает). **Когда выбирать:** большим командам с упором на аудит и откат целых PR; командам, ценящим прозрачность «откуда что». ## Squash and merge GitHub берёт все коммиты PR, сжимает в один новый коммит, добавляет в main без merge-коммита. ``` main: ... → A → B → S ← S содержит все правки из feat ``` Старые коммиты ветки в main не попадают (остаются в самой ветке до её удаления, потом доступны через reflog некоторое время). **Преимущества:** - История main всегда линейная и чистая. - Один PR - один коммит, гранулярность совпадает с тикетами. - Внутренний шум PR (WIP, fix typo, address comments) не попадает в main. - Откат PR - обычный `git revert `. **Недостатки:** - Атомарные коммиты внутри PR (если автор их вычистил) теряются. - Для bisect внутри фичи приходится ходить в саму ветку, в main - только один коммит. - Связь между коммитом в main и веткой PR - только через сообщение коммита (если автоматически добавлено `(#123)`). **Когда выбирать:** небольшим командам, которым важна чистая линейная история и где авторы PR'ов не дисциплинированы в коммитах. По опыту многих коммерческих проектов это наиболее частый дефолт - но в больших организациях со строгим аудитом нередко предпочитают merge-commit (см. ниже). ## Rebase and merge Все коммиты PR перебазируются поверх main (новые SHA), потом fast-forward в main. Никакого merge-коммита. ``` main: ... → A → B → C' → D' ← C' и D' - переписанные коммиты feat ``` **Преимущества:** - История линейная - как у squash. - Сохраняет атомарные коммиты внутри PR - как у merge commit. - Bisect работает на всю историю включая внутренности PR'а. **Недостатки:** - SHA коммитов отличаются от тех, что были в ветке (после rebase). Если ссылались на SHA из feature до merge - ссылки сломаются. - Требует от автора PR чистых коммитов. Если коммиты - «WIP», «fix» - они так и попадут в main по одному, что хуже, чем squash. - Связь «коммит → PR» теряется, нужно искать по сообщению. **Когда выбирать:** командам, которые готовы вкладываться в чистоту коммитов через interactive rebase, и где важна одновременно линейная история и сохранение атомарных коммитов. (Заметь: ядро Linux формально не использует GitHub-кнопку «Rebase and merge» - у них своя email/maintainer-tree модель. Принцип «линейная история + атомарные коммиты» там тот же, способ интеграции другой.) ## Сравнение | Стратегия | Merge-коммит | Сохраняет коммиты PR | История | |---|---|---|---| | Merge commit | да | да | развилки | | Squash and merge | нет | нет (один новый) | линейная | | Rebase and merge | нет | да (новые SHA) | линейная | ## Какая стратегия для какой команды Прагматичные правила: - **Стартап / маленькая команда / нет дисциплины коммитов** → Squash and merge. Покрывает большинство сценариев, никого не учит rebase'у, история всегда чистая. - **Большая компания / много команд / важен аудит** → Merge commit. Возможность откатить весь PR одним revert ценнее, чем линейная история. - **Open source / профессионалы / атомарные коммиты - норма** → Rebase and merge. Сохраняет качественную внутреннюю историю PR. Главное - выбрать одну для проекта. Смесь стилей в одной истории читается плохо: то развилки, то линейность, то один коммит на PR, то десять. ## Настройка в GitHub Settings → General → Pull Requests → Merge button. Можно разрешить одну, две или все три кнопки. Лучше оставить только выбранную стратегию - меньше путаницы, меньше «сегодня я squash, завтра rebase». Дополнительные настройки: - **Always suggest updating pull request branches** - при доступном update предлагать кнопку обновить ветку на свежий main. - **Allow auto-merge** - PR смержится автоматически, как только подойдут условия (approve + CI). - **Automatically delete head branches** - удалять ветку после merge. Включить. ## Подводные камни - **Squash + branch protection «require linear history»** не конфликтуют. Squash даёт линейную историю по определению. - **Squash и сохранение метаданных.** GitHub автоматически добавляет в сообщение `(#123)` со ссылкой на PR. Полезно для навигации; проверь, что включено в настройках. - **Rebase и большие PR.** На PR с 30 коммитами rebase может спровоцировать серию конфликтов (по разу за коммит). Если main активный - лучше squash, чтобы конфликт разрешать раз. ## См. также - [git merge](/courses/git/kb/merge.md) - [git rebase](/courses/git/kb/rebase.md) - [Pull Request (PR)](/courses/git/kb/pull-request.md)