Слияние и 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 без обновления maingit pull origin main перед merge
  • Паниковать при конфликтах в rebase — можно git rebase --abort и начать заново
  • Squash всей ветки с 30 коммитами — теряется контекст, лучше сгруппировать логически

Практика

  1. Создайте ветку, сделайте коммиты в ней и в main — выполните merge
  2. Повторите, но используйте rebase вместо merge — сравните историю
  3. Попробуйте git merge --no-ff — увидьте merge-коммит
  4. Сделайте 5 коммитов и объедините 3 из них через git rebase -i
  5. Создайте конфликт при merge и отмените через git merge --abort

Связанные темы

Ресурсы