Merge vs rebase
Что делает merge, что делает rebase, когда выбирать что и почему не ребейзим публичное.
Постановка
У тебя ветка feature от main. Пока ты в ней работал, в main
прилетели чужие коммиты. Теперь твоя ветка отстала:
main: A --- B --- C --- D
\
feature: E --- F
Нужно объединить твои E, F с актуальной main. Есть два способа:
merge и rebase. Они дают одинаковый итоговый код, но разную
историю.
Merge
git switch main
git merge feature
Git создаёт новый merge-commit M, у которого два родителя:
последний коммит main и последний коммит feature.
main: A --- B --- C --- D --- M
\ /
feature: E --- F ------
Что важно:
- история сохраняется как есть, ничего не переписывается,
Mфиксирует факт слияния и кто/когда это сделал,- в логе видна параллельная работа.
Fast-forward merge
Частный случай: main не двигалась, пока ты работал. Тогда merge
просто передвигает указатель main вперёд на конец feature -
никакого M не создаётся:
История остаётся линейной. Иногда команды требуют отключать FF
(git merge --no-ff), чтобы merge-commit всё равно остался -
по нему легко найти границы фичи в логе.
Rebase
git switch feature
git rebase main
Git берёт твои коммиты E, F, отрезает их от старого основания и
прикладывает заново поверх свежего main:
было:
main: A --- B --- C --- D
\
feature: E --- F
стало:
main: A --- B --- C --- D
\
feature: E' --- F'
E' и F' — новые коммиты с новыми SHA. Содержимое то же самое, но
это другие объекты в git.
После такого feature можно влить FF-merge'ем в main — получится
линейная история без merge-коммитов.
Когда что выбирать
Два правила, которыми пользуются большинство команд:
- rebase для своей feature-ветки перед merge в main — чтобы история
mainоставалась линейной и читалась как список фич, а не как паутина параллельных коммитов, - merge для интеграции внешней работы — когда ты вливаешь чью-то ветку, merge-commit честно отмечает "вот тут слился вклад X".
Подтягивать main в свою длинную ветку можно и rebase, и merge. Trade-off:
rebase оставляет линию чистой, но переписывает SHA твоих коммитов; merge
ничего не переписывает, зато добавляет merge-коммиты в историю ветки.
Многие команды настраивают MR-стратегию централизованно: GitLab/GitHub позволяют выбрать "squash and merge", "rebase and merge", "merge commit". Тогда тебе не нужно думать — правило одно для всех.
Золотое правило: не ребейзим публичное
Если коммиты уже запушены и у кого-то локально могут лежать — не ребейзь их.
Почему: rebase создаёт новые SHA. У тебя локально старые E, F
исчезли, появились E', F'. У коллеги в его клоне старые E, F
остались. При следующем git pull git увидит две разные истории и
впадёт в конфликт. Force-push в общую ветку — расстрел проекта.
Безопасные кейсы для rebase:
- ребейзишь свою локальную ветку, которую ещё никто не клонировал,
- ребейзишь свою feature-ветку (личную), даже если она запушена, но только если точно работаешь в ней только ты.
Опасные:
- rebase в
main,develop, любой общей долгоживущей ветке, - rebase ветки, в которой работает ещё кто-то.
Как запушить ветку после rebase
Второй пункт в «безопасных» случаях — свою запушенную feature-ветку
ребейзить можно. Но обычный git push после этого откажется работать:
! [rejected] feature -> feature (non-fast-forward)
Логика Git понятна: на сервере лежат E, F, у тебя теперь E', F',
и одно не является продолжением другого. Обновить ветку можно только
перезаписью.
Первое, что советует интернет, — git push --force. Работает, но бьёт
вслепую: команда затирает удалённую ветку тем, что у тебя локально, не
глядя на то, что там сейчас. Если коллега за это время успел дописать
коммит в ту же ветку, его работа исчезнет без единого вопроса.
Правильный вариант:
git push --force-with-lease
Флаг сначала сверяется: удалённая ветка должна указывать ровно на тот
коммит, который ты видел при последнем fetch. Совпало — Git
перезаписывает. Не совпало (кто-то запушил после тебя) — push
отклоняется, и ты сначала разбираешься, что там появилось.
Правило простое: --force-with-lease вместо --force всегда, а сам
force — только в своей ветке. В main не форсят даже с проверкой,
поэтому её обычно защищают настройками репозитория, чтобы force-push
туда был технически невозможен.
Что важно запомнить
Merge сохраняет историю + добавляет merge-commit. Rebase переписывает коммиты твоей ветки поверх свежей базы.
Fast-forward merge возможен, когда базовая ветка не двигалась — тогда указатель просто прыгает вперёд.
Rebase для своей feature, merge для интеграции чужой работы.
Не ребейзим то, что уже у других. SHA меняются, история расходится.
После rebase запушенной ветки обычный push отклоняется. Обновляют её
через git push --force-with-lease: он откажется затирать чужие
коммиты, появившиеся после твоего последнего fetch. Голый --force
такой проверки не делает.
В следующем уроке — что делать, когда merge или rebase споткнулся о конфликт.
Дрилл · вопросы как на собесе
Проверь себя без подсказок
Сформулируй ответ вслух или письменно, потом раскрой и честно отметь: знал или нет.
Что создаёт git merge и что такое fast-forward?
GIT-111Почему нельзя ребейзить публичную (запушенную) ветку?
GIT-112В чём разница между git merge и git rebase? Когда что выбирать?
GIT-002Прошли материал до конца? Отметьте урок пройденным.
Прогресс сохранён в этом браузере
Почистишь историю или откроешь с телефона, и отметки пропадут. Оставь почту, они переедут в аккаунт. Пароль придумывать не нужно.
Готово. теперь в аккаунте: прогресс виден с любого устройства. Мой прогресс