Когда 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 <merge-sha>. - Все коммиты 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 <commit-sha>.
Недостатки:
- Атомарные коммиты внутри PR (если автор их вычистил) теряются.
- Для bisect внутри фичи приходится ходить в саму ветку, в main -
только один коммит.
- Связь между коммитом в main и веткой PR - только через сообщение
коммита (если автоматически добавлено
(#123)).
- Связь между коммитом в main и веткой PR - только через сообщение
коммита (если автоматически добавлено
Когда выбирать: небольшим командам, которым важна чистая линейная история и где авторы 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, чтобы конфликт разрешать раз.