Слияние и rebase
Объединение веток через merge и rebase: стратегии, различия, interactive rebase и squash
Зачем нужно
- Объединять работу из feature-веток в основную
- Поддерживать чистую историю коммитов
- Выбирать правильную стратегию для разных ситуаций
Где используется
- При завершении работы над фичей
- При обновлении feature-ветки свежими изменениями из main
- При подготовке PR к merge
Предпосылки
Git Merge
Fast-Forward Merge
Когда main не менялся с момента создания ветки — Git просто передвигает указатель:
До:
main: A ── B
\
feature: C ── D
git switch main
git merge feature
После (fast-forward):
main: A ── B ── C ── D
↑ main и feature
git switch main
git merge feature
# Fast-forward — никакого merge-коммита
Merge Commit (Three-Way Merge)
Когда main изменился — Git создаёт merge-коммит с двумя родителями:
До:
main: A ── B ── E
\
feature: C ── D
git switch main
git merge feature
После:
main: A ── B ── E ── M (merge commit)
\ /
feature: C ── D
git switch main
git merge feature
# Merge made by the 'ort' strategy.
Запретить fast-forward
# Всегда создавать merge-коммит
git merge --no-ff feature
# Полезно для сохранения истории: видно что была отдельная ветка
Git Rebase
Rebase перемещает коммиты ветки на вершину другой ветки:
До:
main: A ── B ── E
\
feature: C ── D
git switch feature
git rebase main
После:
main: A ── B ── E
\
feature: C' ── D'
Коммиты C и D пересоздаются с новыми хешами (C' и D').
git switch feature
git rebase main
# Затем — fast-forward merge
git switch main
git merge feature
# Результат: линейная история
# A ── B ── E ── C' ── D'
Merge vs Rebase
| Merge | Rebase | |
|---|---|---|
| История | Сохраняет полную (нелинейная) | Линейная, "чистая" |
| Merge-коммит | Да (при three-way) | Нет |
| Перезапись истории | Нет | Да (новые хеши) |
| Безопасность | Безопасно всегда | Опасно для shared-веток |
| Конфликты | Один раз при merge | Могут быть на каждом коммите |
Золотое правило rebase
Никогда не делайте rebase коммитов, которые уже отправлены в remote и доступны другим.
# БЕЗОПАСНО — rebase своей локальной ветки
git switch my-feature
git rebase main
# ОПАСНО — rebase shared-ветки
git switch main
git rebase feature # НЕ ДЕЛАЙТЕ ТАК!
Interactive Rebase
Позволяет редактировать, объединять, переименовывать и удалять коммиты:
# Интерактивный rebase последних 4 коммитов
git rebase -i HEAD~4
Откроется редактор:
pick a1b2c3d Добавить компонент Header
pick f4e5d6a Исправить отступы в Header
pick 7b8c9d0 Добавить навигацию
pick e1f2a3b Исправить опечатку в навигации
# Rebase commands:
# p, pick = использовать коммит
# r, reword = использовать коммит, но изменить сообщение
# e, edit = использовать коммит, но остановиться для изменений
# s, squash = слить с предыдущим, сохранив оба сообщения
# f, fixup = слить с предыдущим, отбросив сообщение
# d, drop = удалить коммит
Squash — объединение коммитов
# Объединить исправления с основным коммитом:
pick a1b2c3d Добавить компонент Header
squash f4e5d6a Исправить отступы в Header
pick 7b8c9d0 Добавить навигацию
fixup e1f2a3b Исправить опечатку в навигации
Результат: 2 коммита вместо 4.
Reword — переименование коммита
reword a1b2c3d Добавить компонент Header
pick 7b8c9d0 Добавить навигацию
Практический пример: подготовка PR
# Перед созданием PR — очистить историю
git rebase -i main
# Объединить "мелкие" коммиты, переименовать непонятные
# Результат: чистая история, удобная для review
Стратегии merge на GitHub
При merge Pull Request GitHub предлагает три варианта:
1. Merge commit
# Создаёт merge-коммит
# Сохраняет все коммиты ветки + merge-коммит
# Нелинейная история
2. Squash and merge
# Объединяет все коммиты ветки в один
# Линейная история в main
# Теряются отдельные коммиты ветки
3. Rebase and merge
# Перебазирует коммиты на main
# Линейная история
# Сохраняет отдельные коммиты
Что выбрать?
| Ситуация | Стратегия |
|---|---|
| Большая фича с осмысленными коммитами | Merge commit или Rebase and merge |
| Мелкая задача, много промежуточных коммитов | Squash and merge |
| Важно сохранить полную историю | Merge commit |
| Нужна линейная история | Rebase and merge |
Abort — отмена в процессе
# Отменить merge при конфликтах
git merge --abort
# Отменить rebase при конфликтах
git rebase --abort
Частые ошибки
- Rebase shared-ветки — ломает историю для всех коллег
- Не знать разницу merge vs rebase — использовать неподходящий инструмент
- Делать merge без обновления main —
git pull origin mainперед merge - Паниковать при конфликтах в rebase — можно
git rebase --abortи начать заново - Squash всей ветки с 30 коммитами — теряется контекст, лучше сгруппировать логически
Практика
- Создайте ветку, сделайте коммиты в ней и в main — выполните merge
- Повторите, но используйте rebase вместо merge — сравните историю
- Попробуйте
git merge --no-ff— увидьте merge-коммит - Сделайте 5 коммитов и объедините 3 из них через
git rebase -i - Создайте конфликт при merge и отмените через
git merge --abort