mrsaynothing.dev
git merge vs rebase — решение за 10 секунд

> git merge vs rebase — решение за 10 секунд▋

Merge или rebase — решает один вопрос: покидал ли коммит вашу машину? Таблица решений, случай fast-forward и спасение через reflog, когда rebase пошёл не так.

mrsaynothing· 5 октября 2026 г.· 7 мин чтения

Одну из этих двух команд вы наберёте тысячи раз за карьеру, а половина советов в интернете превращает выбор в религию — с одной стороны «rebase всё, история должна быть чистой», с другой «никогда не rebase, он уничтожает работу». Оба лагеря пропускают настоящее правило, которое умещается в десять секунд. Этот пост — решение, а не богословие, и он принадлежит кластеру Undo Anything in Git — та же семья, что и git revert vs reset, ещё одна пара «две команды, один вопрос». Сам Git был написан с нуля за десять дней в апреле 2005; спор merge против rebase длится с тех пор.

Пятиминутный чеклист

git merge vs rebase — единственный вопрос коммит готов к посадке git status -sb покажет впереди/позади эту ветку скачивает кто-то ещё? проверьте ветку, а не интуицию git log origin/feature..feature другие могли скачать → только ваша → git merge записывает, что было на самом деле git rebase линейная история, удобная ревью история выглядит не так? git reflog → reset --hard HEAD@{1}
fig 1 — дерево «merge или rebase». Кликните свою ветку.

В чём разница между git merge и git rebase?

Обе команды доставляют одинаковый код; они спорят о том, что история должна рассказывать после. Merge фиксирует разногласие письменно: один новый коммит с двумя родителями, расхождение сохранено для всех, кто позже будет читать график. Rebase делает вид, что вы стартовали от сегодняшней базы: три ваших коммита переигрываются в три новых с новыми SHA, а старые становятся недостижимыми с любой ветки.

Я запустил обе команды в одноразовом стенде на git 2.47.3 с одними и теми же пятью коммитами. Прогон merge сохранил каждый исходный SHA и добавил один узел; прогон rebase сменил личность коммитов ветки — c3 вошёл как 5d48940 и вышел как 6b2be0a. Дерево то же, история разная:

после merge c1 c2 c3 · c4 c5 merge-коммит · 2 родителя после rebase c1 c2 c5 c3' 6b2be0a c4' 67c0442 одна линия — без узла, старые SHA исчезли
fig 2 — то же дерево, две истории. Коммиты со штрихом — те же изменения с новыми ID.

проверка: git log --oneline --graph → узел |\ означает merge, прямая линия — победил rebase

Когда merge становится fast-forward?

Если main не двигался с момента создания ветки, сливать нечего — git просто сдвигает метку вперёд. Это и есть fast-forward, и потому лагерь rebase получает свою чистую линию, никого не заставляя: rebase вашей feature на main, merge превращается в fast-forward, график остаётся прямой. Из стенда:

* 67c0442 c4 feature 2      # после rebase и merge — fast-forward
* 6b2be0a c3 feature 1      # новые SHA — 5d48940 до rebase
* 1398836 c5 main moved on
* facd5a4 c2 main work
* f45b000 c1 base

Пять коммитов, ноль узлов. Цена: этих SHA со штрихом больше не существует нигде, кроме вашей машины. Запушьте — и clone коллеги хранит оригиналы: две версии «одного и того же» коммита, ровно тот беспорядок, о котором пятый раздел.

проверка: git merge --ff-only feature → в выводе Fast-forward, merge-коммит не создан

Какие коммиты всё ещё только ваши?

Ответ — одна команда, а не ощущение. git log origin/main..HEAD перечисляет коммиты, которые origin никогда не видел, — их можно ребейзить. Зеркальный git log origin/main..origin/main пуст по определению; проверять надо ветку, на которой строят другие. Если в списке затесался коммит, который другой clone уже может иметь — push, открытый pull request, ветка, запушенная и перепушенная силой, — он больше не только ваш, что бы ни говорил локальный график.

Незапушенная работа — ровно то, для чего rebase существует: переиграйте её на обновлённую базу, и ваш pull request читается одной чистой последовательностью. Потому «pull с rebase» — разумный дефолт для приватных веток: git pull --rebase ставит ваши локальные коммиты поверх пришедших вместо того, чтобы вплетать merge-узел при каждой синхронизации. Случай со stash живёт в git stash a single file; вариант с форком — в sync fork with upstream.

Что ломает rebase общей ветки?

Каждый clone со старыми коммитами с этого момента расходится с вашей машиной в показаниях о реальности. Rebase даёт вашим коммитам новые SHA; запушенная ветка и переписанная больше не делят предков, поэтому следующий push потребует --force — и все, кто скачивал старую версию, будут вливать свою копию коммитов, которых «больше нет». Их дубликаты всплывут в каждом будущем merge. Это не гипотеза: merge дубликатов — классическое продолжение, и распутать его стоит полдня.

Правило умещается в одно предложение, и официальная книга несёт его как предупреждение, а не как совет:

Do not rebase commits that exist outside your repository and that people may have based work on.

— Pro Git, 2-е издание, «Git Branching — Rebasing»

Комментарии ревью, привязанные к конкретным SHA, отваливаются тем же движением — rebase открытого pull request, и контекст ревью испаряется вместе со старыми ID.

Как отменить rebase?

Старые коммиты переживают rebase — reflog — их укрытие. Rebase двигает указатели веток, объекты не удаляет. Спасение из стенда, после того как reset --hard выбросил два коммита:

$ git reflog | head -4
1398836 HEAD@{0}: reset: moving to HEAD~2
67c0442 HEAD@{1}: merge feature: Fast-forward
1398836 HEAD@{2}: checkout: moving from feature to main
67c0442 HEAD@{3}: rebase (finish): returning to refs/heads/feature

$ git reset --hard HEAD@{1}
HEAD is now at 67c0442 c4 feature 2

Одна строка — ветка восстановлена, staged и unstaged работа вместе с ней. Записи reflog по умолчанию живут около 90 дней, так что у спасения есть часы — механизм тот же, что в git undo last commit, а полное решение revert/reset живёт в git revert vs reset.

проверка: git reflog | head -3 → кончик до rebase ждёт на HEAD@{1}, в одном reset от спасения

Какая команда нужна вашей ситуации?

Таблица — весь пост в шести строках; фильтруйте по вашей ветке.

все feature общая локальная fork спасение
Ситуация Команда Почему выигрывает
Ваша feature-ветка, ни разу не запушена git rebase main Линейная история, одна чистая последовательность для ревью
Ветка, которую скачивают другие git merge Без перезаписи SHA — без дубликатов ни в одном clone
Локальный main разошёлся, ничего не запушено git pull --rebase Синхронизирует без узла; ваши коммиты остаются сверху
Fork догоняет upstream git merge upstream/main Совпадает с потоком fork sync; PR остаётся прицепленным
Hotfix, который должен сесть сейчас git merge --no-ff Видимая метка входа фикса, даже на fast-forward-способной ветке
Rebase пошёл не так git reset --hard HEAD@{1} Reflog всё ещё держит кончик до rebase (~90 дней)
Куда вписывается --squash?

git merge --squash забирает изменения ветки и ставит их в stage одной некоммиченной кучей — без merge-коммита и без связи с историей ветки. Автодополнение показывает, что «merge vs rebase vs squash» ищут ежедневно: squash — третий ответ, чтобы выбросить ветку после посадки её суммарного изменения. Он ничего не переписывает; просто отказывается записывать внутренние шаги ветки.

Может ли команда выбрать что-то одно?

Многие так делают: rebase-для-локального, merge-в-общее — самая частая политика, а кнопка merge на GitHub держит merge по умолчанию. «Rebase всё» тоже работает — пока общая ветка никогда не переписывается, это единственное предложение, которое переживает все споры о workflow.

Десятисекундная версия, в последний раз: коммит покидал вашу машину? Нет → rebase. Да → merge. Когда выигрывает не тот всё равно, у reflog есть выход — а остальная карта отмены живёт в хабе git undo.

faq

— mrsaynothing

$ Следующий гайд — на почту

Одно письмо на пост. Починил — пошёл дальше.

self-hosted · никаких третьих сторон · отписка в один клик

что это?