Инструкция

git merge: как слить ветки и разрешить конфликты

Обновлено: · Skillaria.Top

git merge объединяет историю другой ветки с текущей: если текущая ветка не ушла вперёд, Git просто передвигает указатель (fast-forward), иначе создаёт merge-коммит с двумя родителями. Перейдите в целевую ветку и выполните git merge имя-ветки; при конфликте поправьте файлы, сделайте git add и git commit или отмените всё через git merge --abort.

Что делает git merge

Команда git merge берёт изменения из указанной ветки и встраивает их в ту ветку, в которой вы сейчас находитесь. Важно запомнить направление: сливают «в себя». Чтобы влить feature/login в main, сначала переключаются на main, а потом вызывают слияние.

git switch main
git pull                 # подтянуть свежий main с сервера
git merge feature/login

Git ничего не копирует построчно «вручную». Он находит общего предка двух веток (merge base), сравнивает с ним обе версии и складывает изменения. Если обе стороны правили разные места, результат получается автоматически. Если одно и то же место — возникает конфликт, и решение принимаете вы.

Два режима: fast-forward и трёхстороннее слияние

Fast-forward — когда коммит слияния не нужен

Предположим, вы ответвили feature от main, сделали три коммита, а в main за это время никто ничего не добавил. Тогда история main — это просто начало истории feature. Git передвигает указатель main вперёд, на последний коммит feature, и новый коммит не создаётся.

$ git merge feature
Updating 3f2a1c4..9b7e0d2
Fast-forward
 src/auth.js | 42 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 42 insertions(+)

История остаётся линейной, но факт «здесь была ветка feature» в ней теряется. Иногда это хорошо, иногда нет — об этом ниже, в разделе про --no-ff.

Трёхстороннее слияние и merge-коммит

Если обе ветки ушли вперёд от общего предка, передвинуть указатель уже нельзя. Git выполняет трёхстороннее слияние: база, «наша» сторона (текущая ветка) и «их» сторона (сливаемая ветка). Результат фиксируется отдельным merge-коммитом, у которого два родителя.

$ git merge feature
Merge made by the 'ort' strategy.
 src/auth.js   | 42 ++++++++++++++++++++
 src/router.js |  3 +-
 2 files changed, 44 insertions(+), 1 deletion(-)

Git откроет редактор с сообщением вида Merge branch 'feature'. Его можно оставить или дописать, зачем делалось слияние. Посмотреть получившуюся «развилку» удобно так:

git log --oneline --graph --all

Стратегия ort используется по умолчанию начиная с Git 2.34; раньше это была recursive. Для повседневной работы менять стратегию не нужно.

Полезные флаги git merge

ФлагЧто делаетКогда пригодится
--no-ffВсегда создаёт merge-коммит, даже если возможен fast-forwardХотите видеть в истории, какие коммиты пришли из одной фичи
--ff-onlyСливает только перемоткой, иначе завершается ошибкойОбновление локальной ветки без случайных merge-коммитов
--squashСкладывает все изменения ветки в индекс, но не коммитит и не создаёт связь с веткойНужно превратить фичу в один аккуратный коммит
--no-commitВыполняет слияние, но останавливается перед коммитомХотите проверить результат или прогнать тесты до фиксации
-m "текст"Задаёт сообщение merge-коммитаСлияние в скриптах и без редактора
--abortОтменяет начатое слияние и возвращает состояние до негоКонфликтов слишком много, нужно начать заново
--continueЗавершает слияние после разрешения конфликтовАльтернатива git commit на последнем шаге
-X ours / -X theirsПри конфликтующих фрагментах автоматически берёт нашу или их версиюЗаведомо знаете, чья версия правильная (осторожно!)

--no-ff: сохранить границы фичи

Многие команды сливают фичи только с --no-ff. Тогда в графе каждая задача выглядит как «пузырь», и её можно откатить одним git revert -m 1 <merge-коммит>. При fast-forward такой точки нет, и отменять пришлось бы каждый коммит отдельно.

--squash: один коммит вместо десяти

git switch main
git merge --squash feature/login
git commit -m "Вход по e-mail и паролю"

После --squash Git не считает ветку слитой: git branch -d feature/login откажется удалять её с предупреждением «not fully merged». Удаляйте через git branch -D, когда убедились, что всё на месте. И не продолжайте работу в той же ветке после squash — следующее слияние принесёт уже влитые изменения повторно и почти наверняка даст конфликты.

Конфликт слияния: как его разрешить

Конфликт — нормальная рабочая ситуация, а не авария. Он значит одно: две ветки поменяли одни и те же строки (или одна удалила файл, который другая правила), и Git не берётся угадывать, какая версия верна.

$ git merge feature/login
Auto-merging src/config.js
CONFLICT (content): Merge conflict in src/config.js
Automatic merge failed; fix conflicts and then commit the result.

Шаг 1. Посмотреть, что конфликтует

$ git status
On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")
  (use "git merge --abort" to abort the merge)

Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both modified:   src/config.js

Шаг 2. Отредактировать файл

В конфликтном файле Git расставляет маркеры:

<<<<<<< HEAD
const TIMEOUT = 5000;
=======
const TIMEOUT = 10000;
>>>>>>> feature/login

Верхняя часть — ваша текущая ветка (HEAD), нижняя — сливаемая. Оставьте правильный вариант (или объедините оба), удалите все три строки-маркера и сохраните файл. Частая ошибка — забыть маркер где-то в середине файла; перед коммитом поищите их: git diff --check подсветит оставшиеся маркеры.

Шаг 3. Отметить решение и завершить слияние

git add src/config.js
git commit          # или git merge --continue

Если конфликт целиком решается выбором одной стороны, не нужно править файл руками:

git checkout --ours   src/config.js   # оставить версию текущей ветки
git checkout --theirs src/config.js   # взять версию сливаемой ветки
git add src/config.js

Удобнее в редакторе

VS Code, IDE от JetBrains и другие редакторы показывают конфликт в три колонки и дают кнопки «принять текущее / входящее / оба». Можно подключить внешний инструмент через git mergetool. Полезная настройка — git config --global merge.conflictStyle zdiff3: в маркерах появится ещё и исходная версия из общего предка, и становится понятно, кто что поменял.

Передумали — отменить слияние

git merge --abort

Команда вернёт рабочую папку и индекс в состояние до git merge. Поэтому перед слиянием лучше иметь чистое рабочее дерево: закоммитьте или отложите незавершённые правки через git stash, иначе их будет сложно отделить от результатов слияния.

Как отменить уже сделанный merge

Если merge-коммит ещё не отправлен на сервер, проще всего откатить ветку назад:

git reset --hard ORIG_HEAD

ORIG_HEAD Git записывает перед опасными операциями, в том числе перед слиянием, — это положение ветки до merge. Команда --hard выбросит незакоммиченные изменения, так что проверьте git status.

Если слияние уже в общем репозитории, историю не переписывают. Создают обратный коммит:

git revert -m 1 a1b2c3d

Флаг -m 1 говорит, что «основной» считается первый родитель (ветка, в которую сливали). Учтите подвох: после такого revert Git считает коммиты фичи уже влитыми. Если позже вы захотите влить исправленную фичу снова, сначала придётся сделать revert самого revert-коммита.

Merge в командной работе

git pull — это fetch плюс merge

Обычный git pull скачивает изменения и сливает удалённую ветку в локальную. Если вы и коллега коммитили параллельно, появится коммит «Merge branch 'main' of ...». Чтобы таких технических слияний не было, настройте git config --global pull.ff only (pull падает, если перемотка невозможна, и вы решаете сами) или используйте git pull --rebase.

Pull request и кнопка Merge

На GitHub и GitLab слияние обычно происходит через pull/merge request. Кнопка «Create a merge commit» эквивалентна git merge --no-ff, «Squash and merge» — слиянию со сжатием, «Rebase and merge» — перебазированию. Выбор режима — договорённость команды, а сам механизм тот же, что и в консоли.

Влить main в свою ветку

Долгоживущая ветка расходится с main, и конфликты копятся. Регулярно подтягивайте основную ветку к себе:

git switch feature/login
git fetch origin
git merge origin/main

Так конфликты решаются маленькими порциями по мере появления, а финальное слияние в main проходит гладко.

Типичные ошибки

  • Слили не в ту сторону. Выполнили git merge main, находясь в main, или влили фичу в другую фичу. Проверяйте текущую ветку командой git branch --show-current перед слиянием.
  • Слияние с устаревшей веткой. Слили локальный main, который неделю не обновлялся. Сначала git pull, потом merge.
  • «Your local changes would be overwritten by merge». В рабочей папке есть незакоммиченные правки в тех же файлах. Закоммитьте их или уберите в stash.
  • «refusing to merge unrelated histories». У веток нет общего предка — например, локальный репозиторий создан через git init, а удалённый уже содержит README. Если объединение действительно нужно, добавьте --allow-unrelated-histories.
  • Закоммитили маркеры конфликта. Сборка падает на <<<<<<<. Проверяйте git diff --check и запускайте тесты до коммита.

Итог

git merge — безопасный способ объединить ветки: он не переписывает существующие коммиты, а лишь добавляет новый (или перематывает указатель). Сливайте в текущую ветку, держите рабочее дерево чистым, используйте --no-ff, если важны границы фич, и --squash, если нужен один коммит. Конфликты решаются в три шага: правка файла, git add, git commit, а git merge --abort всегда вернёт вас к исходной точке. Слияние веток и разрешение конфликтов подробно отрабатываются на практике в курсе по Git.

Частые вопросы

Чем git merge отличается от git pull?

git pull сначала скачивает изменения с сервера (git fetch), а затем сливает удалённую ветку в текущую через git merge или, при настройке, через rebase. git merge работает только с тем, что уже есть локально.

Можно ли слить сразу несколько веток?

Да, git merge feature-a feature-b выполнит слияние-осьминог (octopus) с несколькими родителями, но только если нет конфликтов. На практике ветки обычно сливают по одной.

Как посмотреть, какие ветки уже слиты в текущую?

Команда git branch --merged покажет ветки, полностью вошедшие в текущую, а git branch --no-merged — ещё не слитые. Слитые ветки можно спокойно удалять через git branch -d.

Как заранее увидеть, что принесёт слияние?

Выполните git log main..feature, чтобы увидеть коммиты, которых нет в main, и git diff main...feature, чтобы увидеть изменения ветки относительно общего предка.

Что такое ORIG_HEAD после merge?

Это ссылка, которую Git сохраняет перед слиянием: она указывает на положение ветки до merge. Через git reset --hard ORIG_HEAD можно откатить локальное слияние, пока оно не отправлено на сервер.

Разберите тему на видео

Эта тема подробно разобрана с практикой в курсе «Git с нуля до уверенного разработчика».

Перейти к курсу