Варианты слияния веток в Git и их отличия

В git есть несколько вариантов слияния веток, какие? Чем отличаются.

Базовые понятия: что значит «влить ветку»

Под «слиянием веток» обычно понимается интеграция изменений из одной линии разработки в другую так, чтобы целевая ветка получила нужные правки, а история коммитов осталась в понятной форме.

Для различий между способами слияния важно разделять два вопроса:

  • «Какие изменения попадут в целевую ветку?» (итоговое содержимое файлов после операции).
  • «Как будет выглядеть история коммитов?» (граф коммитов, наличие merge-коммита, линейность, переписывание хэшей).
В Git «ветка» — это подвижный указатель на коммит, а «история» — это ориентированный граф коммитов, где каждый коммит ссылается на родителя(ей).

Merge: обычный merge и fast-forward

git merge интегрирует изменения из указанной ветки (или коммита) в текущую ветку.

Если ветки разошлись, создаётся новый merge-коммит с двумя родителями, который явно фиксирует факт объединения историй.

Схема: merge-коммит (true merge)

Ниже показан типичный случай, когда ветки разошлись и нужен merge-коммит.

A---B---C  feature
     \
      D---E---F  main

# На ветке main выполняется:
git checkout main
git merge feature

# Результат (появляется merge-коммит M):
A---B---C  feature
     \   \
      D---E---F---M  main

Fast-forward как режим merge

Если текущая ветка не имеет собственных новых коммитов после точки ветвления и целевая история является предком вливаемой, merge может выполниться как fast-forward: указатель ветки просто сдвигается вперёд, а новый merge-коммит не создаётся.

# Пример fast-forward:
# main:    A---B
# feature: A---B---C---D  (main является предком feature)

git checkout main
git merge feature
# main станет указывать на D, нового merge-коммита не будет

# Полезные режимы:
git merge --ff-only feature   # разрешать только fast-forward, иначе отказ
git merge --no-ff feature     # создавать merge-коммит даже если возможен fast-forward
При конфликте merge останавливается в «незавершённом» состоянии; требуется разрешить конфликты в файлах и затем завершить операцию или отменить её.

Rebase: перенос коммитов поверх новой базы

git rebase переносит последовательность коммитов текущей ветки на другую базу: создаётся список коммитов, затем они «проигрываются» по одному поверх выбранного коммита-основания.

Простая модель для начинающего: rebase делает историю «ровнее», но платой за это становится переписывание истории (появляются новые коммиты с новыми хэшами).

Схема: rebase для линейной истории

Смысл rebase: сохранить изменения из feature, но сделать историю линейной, без merge-коммита.

# Было:
A---B---C  feature
     \
      D---E---F---G  main

# На ветке feature:
git checkout feature
git rebase main

# Стало (коммиты feature становятся "новыми" A',B',C' поверх main):
D---E---F---G---A'---B'---C'  feature

Типичные команды:

# Перенос текущей ветки поверх main:
git rebase main

# Интерактивный rebase для упорядочивания/объединения коммитов:
git rebase -i <after-this-commit>
Rebase меняет историю: прежние коммиты заменяются новыми, поэтому при совместной работе опасно выполнять rebase над веткой, которую уже используют другие участники (часто это ветка, отправленная в общий удалённый репозиторий), так как это усложняет синхронизацию и может привести к дублированию коммитов.

Squash merge: «слить всё одним коммитом»

Squash merge означает «взять итог всех изменений ветки и оформить одним коммитом в целевой ветке», скрыв промежуточные шаги разработки.

В командной строке это часто делается как подготовка изменений без создания merge-коммита, а затем вручную создаётся один обычный коммит.

Мини-сценарий squash merge

# На целевой ветке (например, main):
git checkout main

# "Склеить" изменения ветки feature в индекс/рабочее дерево без merge-коммита:
git merge --squash feature

# Затем создать один коммит:
git commit -m "Добавлена функциональность X (squash)"

Таблица различий: merge vs rebase vs squash merge

СпособКак меняется история и коммитыКогда обычно подходит
MergeДобавляется merge-коммит (если не fast-forward), сохраняется ветвление; все коммиты из feature сохраняются как отдельные.Когда важно сохранить контекст ветки и факт слияния, удобно для командной разработки и просмотра истории.
RebaseКоммиты feature «перепроигрываются» поверх новой базы и пересоздаются, поэтому меняются хэши; история становится линейной.Когда нужна линейная история и аккуратная последовательность коммитов перед интеграцией (с учётом риска переписывания истории).
Squash mergeИтог всех изменений feature попадает в целевую ветку одним новым коммитом; отдельные коммиты feature в целевой истории не сохраняются.Когда нужен один «логический» коммит без промежуточных шагов разработки и не важна детализация коммитов feature.

Итого: корректный набор «способов интеграции ветки» — merge, rebase и squash merge; merge может быть fast-forward или с merge-коммитом, rebase пересоздаёт коммиты поверх новой базы (переписывает историю), а squash merge превращает серию коммитов в один итоговый коммит в целевой ветке.