open atlas
↑ К треку
Git: от нуля до сеньора GIT · 08 · 03

Как на самом деле работают merge и rebase

Merge находит ближайшего общего предка (merge base), делает трёхстороннее слияние base/ours/theirs и пишет один коммит с двумя родителями — настоящая история. Rebase переигрывает твои коммиты на новой базе как новые SHA — линейная, но переписанная история.

GIT Senior ◷ 19 min
Уровень
ОсновыJuniorMiddleSenior

Две ветки разошлись. Их можно примирить через git merge или git rebase, и священные войны о том, что выбрать, по большей части идут от людей, которые ни разу не посмотрели, что каждая из команд делает с графом объектов. Этот урок — про механизм, а не про флаги команд. Как только ты увидишь, что merge пишет коммит с двумя родителями, а rebase переигрывает твои коммиты как новые объекты, любое запутанное поведение — повторяющиеся конфликты, «почему сменились мои SHA», золотое правило — становится очевидным.

К концу ты сможешь объяснить merge base, почему трёхстороннее слияние его требует, и что именно rebase переписывает и почему это даёт линейную историю.

Цель

После этого урока ты сможешь объяснить merge base (ближайшего общего предка), описать, как трёхстороннее слияние объединяет base/ours/theirs в один коммит с двумя родителями, противопоставить это тому, как rebase переигрывает коммиты как новые SHA на новой базе, и сформулировать компромисс и золотое правило rebase.

1

Обе команды начинают с поиска merge base — ближайшего общего предка. Когда feature и main разошлись, они всё ещё разделяют историю до какого-то коммита. Merge base — это самый недавний коммит, достижимый из обеих вершин:

git merge-base main feature      # -> SHA, где две ветки в последний раз совпадали

Это точка отсчёта, относительно которой меряется всё остальное: она говорит git, какие изменения уникально твои, а какие уникально их с момента расхождения. Ошибись с базой — и не отличишь реальную правку от «это всегда тут было».

2

Merge делает трёхстороннее слияние: base против ours против theirs. Двусторонний дифф двух вершин не скажет, кто изменил строку, — только что они различаются. Поэтому git сравнивает три снимка: merge base, ours (текущая ветка) и theirs (вливаемая ветка). Для каждого участка файла:

  • изменён только на одной стороне → берём эту сторону автоматически;
  • изменён на обеих сторонах одинаково → берём;
  • изменён на обеих сторонах по-разному → конфликт, ты его разрешаешь.

База — это то, что делает «изменена только одна сторона» разрешимым; без неё каждое различие выглядело бы конфликтом.

3

Merge пишет ОДИН коммит с ДВУМЯ родителями, сохраняя форму ветвления. Результат успешного слияния — новый merge-коммит, чей первый родитель — старая вершина main, а второй — вершина feature:

git switch main
git merge feature                # -> создаёт merge-коммит, родители: [main, feature]

История теперь явно ветвится и сходится обратно. Ничего не переписано: обе исходные ветки существуют дословно, и граф фиксирует, что слияние произошло, когда и что сошлось вместе. Это настоящая история — включая грязную реальность того, что две линии работы шли параллельно.

4

Rebase переигрывает твои коммиты один за другим на новой базе как совершенно новые коммиты. Вместо соединения веток rebase перемещает твою. git switch feature; git rebase main концептуально делает:

  1. Вычисляет патч каждого коммита feature с момента merge base.
  2. Сбрасывает feature на вершину main.
  3. Заново применяет эти патчи по порядку, создавая новый коммит на каждый — новый родитель, новая метка времени, значит, новый SHA.

Старые коммиты брошены (достижимы только через reflog). Результат — прямая линия: история main, затем твои коммиты сверху, как будто ты всё время начинал с текущего main.

git switch feature
git rebase main                  # переигрывает коммиты feature на вершину main
Граничные случаи

Почему конфликты rebase могут повторяться на каждый коммит. Merge разрешает объединённое различие один раз, в одном merge-коммите. Rebase заново применяет твои коммиты по отдельности, поэтому если коммит 2 и коммит 4 оба трогают один конфликтный участок, ты можешь разрешить конфликт на коммите 2 и снова наткнуться на родственный на коммите 4 — по разу на каждый переигранный коммит. Это поэкоммитное переигрывание ровно и есть причина, по которой долгие rebase ощущаются повторяющимися, и это та проблема, ради которой существует инструмент следующего урока (rerere) — записывать и переигрывать твои разрешения.

5

Компромисс: настоящая история против чистой линейной — и золотое правило. Merge хранит точную, ветвящуюся запись ценой более загруженного графа. Rebase даёт опрятную, удобную для bisect прямую линию ценой переписывания коммитов в новые SHA. Это переписывание безопасно на коммитах, которые есть только у тебя, — и опасно на разделяемых:

Золотое правило rebase: никогда не делай rebase коммитов, существующих за пределами твоего локального репозитория (на которых другие могли основать работу).

Переписывание публичных коммитов даёт всем остальным расходящуюся историю и вынуждает к запутанным force-push и повторным слияниям. Делай rebase своей приватной фиче-ветки до того, как поделишься; merge — когда она уже публична.

Разбор примера

Одно расхождение, оба исхода.

Начинаем от базового коммита B. main добавляет коммит M; твой feature добавляет F1, затем F2. Ветки разошлись, и git merge-base main feature возвращает B.

Путь merge:

git switch main && git merge feature
# новый коммит X с родителями [M, F2]; граф: B -> M -> X и B -> F1 -> F2 -> X

F1, F2 и M не тронуты; X фиксирует, что они сошлись. git log --graph показывает видимый ромб.

Путь rebase:

git switch feature && git rebase main
# F1 -> F1'  (родитель теперь M, новый SHA)
# F2 -> F2'  (родитель теперь F1', новый SHA)
# граф: B -> M -> F1' -> F2'   (одна прямая линия)

Содержимое итогового дерева одинаково в обоих случаях — те же файлы. Но merge хранит четыре коммита плюс merge-коммит в развилке, тогда как rebase оставляет чистую линию из трёх коммитов, где F1 и F2 заменены копиями с новыми SHA F1'/F2'. Если бы feature уже была запушена, эти переписанные SHA столкнулись бы с копиями, которые держат другие, — нарушено золотое правило.

Частая ошибка

«Rebase избегает конфликтов» — это миф. Rebase и merge поднимают одни и те же конфликтующие правки — ни тот, ни другой не может волшебно объединить два несовместимых изменения одной строки. Rebase часто ощущается как больше работы с конфликтами, потому что переигрывает коммит за коммитом и может заново показать родственный конфликт несколько раз, тогда как merge разрешает всё расхождение за один проход. Выбирай rebase ради чистой истории, а не чтобы увернуться от конфликтов.

Проверь себя
Викторина

В чём ключевое структурное различие между коммитами, которые создают merge и rebase?

Итог

Merge и rebase оба начинают с merge base — ближайшего общего предка двух вершин веток. Merge запускает трёхстороннее слияние base/ours/theirs и пишет один коммит с двумя родителями, сохраняя ветвящуюся историю ровно такой, какой она была. Rebase вместо этого переигрывает твои коммиты на новой базе, создавая совершенно новые коммиты с новыми SHA и отбрасывая оригиналы, давая линейную историю ценой её переписывания. Компромисс — настоящая история против чистой, управляемый золотым правилом: никогда не делай rebase коммитов, которые могут уже быть у других. Дальше ты встретишь хуки — скрипты, которые git запускает вокруг этих операций, чтобы навязать правила твоей команды.

Практика

Начни сверху. Задачи идут от простого к сложному: вспомнить факт, применить к случаю, затем senior-уровень. Открой, попробуй, потом открой ответ.

вспомнитьприменитьуглубить0 из 4 завершено

Что-то непонятно?

Задай вопрос по этому уроку. Вопросы анонимны и попадают напрямую автору — урок станет лучше.

хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.