Слияние веток в Git - полное руководство по git merge

24 апреля 2026
Автор

Олег Марков

Введение

Слияние веток в Git с помощью команды git merge — одна из ключевых операций, без которой невозможно эффективно вести разработку в команде. Как только в проекте появляются отдельные ветки для фич, багфиксов или релизов, возникает задача собрать изменения вместе и не потерять ни одну строку кода.

Здесь вы разберете, как работает git merge изнутри, чем слияние отличается от других способов интеграции изменений (например, rebase), какие бывают типы слияний, как выглядят конфликты и как их безопасно решать. Я покажу вам примеры реальных команд и ситуаций, с которыми вы столкнетесь в ежедневной работе.

Главная цель статьи — дать вам не только набор команд, но и понимание, что делает Git при слиянии, какие есть стратегии и как вы можете контролировать историю коммитов.


Базовые понятия для понимания git merge

Прежде чем писать команды, важно коротко проговорить несколько концепций, на которых основано слияние.

Что такое ветка в Git с точки зрения слияния

Ветка в Git — это просто «указатель» на определенный коммит. Когда вы создаете ветку:

git branch feature/login

Git создает новый указатель, который указывает на текущий коммит. Когда вы двигаетесь вперед по истории (делаете новые коммиты), указатель ветки смещается на новый коммит.

Для слияния это важно: когда вы делаете git merge, Git сравнивает не «ветки» как сущности, а наборы коммитов, на которые указывают эти ветки, и их общую историю.

Общий предок веток и трехстороннее слияние

Когда вы сливаете две ветки, Git ищет их общего предка (common ancestor). Это тот коммит, от которого когда-то «разошлись» ветки. Дальше Git выполняет трехстороннее слияние:

  • база — общий предок;
  • ваша текущая ветка (куда вы сливаете, часто называют target или destination);
  • ветка, которую вы вливаете (source).

Смотрите, как это выглядит на схеме:

  • Коммит C0 — общий предок.
  • Коммиты C1, C2 — продолжение ветки main.
  • Коммиты F1, F2 — продолжение ветки feature.

Когда вы выполняете:

git checkout main
git merge feature

Git сравнивает изменения:

  • от C0 до C2 (ваша ветка main),
  • от C0 до F2 (ветка feature), и создаёт новый коммит слияния M, который объединяет оба набора изменений.

Основы работы с git merge

Простое слияние без конфликтов

Для начала давайте разберемся с ситуацией, когда конфликты не возникают.

Предположим, у вас есть две ветки:

  • main — основная ветка;
  • feature/header — ветка с новой фичей.

Вы хотите влить изменения из feature/header в main.

# Переключаемся в ветку, куда будем вливать изменения
git checkout main

# Вливаем изменения из ветки feature/header
git merge feature/header

Если Git смог автоматически объединить все изменения, вы увидите примерно такое сообщение:

Updating a1b2c3d..e4f5g6h
Fast-forward
 src/header.html | 10 ++++++++++
 1 file changed, 10 insertions(+)

Обратите внимание на слова Fast-forward — это один из типов слияния, о котором поговорим отдельно.

Что происходит при простом merge

  • Git находит общий предок веток.
  • Сравнивает изменения.
  • Если нет конфликтующих изменений (одни и те же строки не были изменены по-разному), Git автоматически создает новый коммит слияния или просто перемещает указатель (fast-forward).

Fast-forward merge — «перемотка» ветки вперед

Fast-forward (FF) — это случай, когда ветка-приемник не имеет своих уникальных коммитов после того места, где ветки разошлись. Проще: main «отстает» от feature и может просто «догнать» ее.

Давайте разберемся на простом примере.

# Создаем репозиторий
git init

# Создаем файл и первый коммит
echo "v1" > version.txt
git add version.txt
git commit -m "Initial version"

# Создаем новую ветку feature
git branch feature

# Переключаемся в ветку feature и делаем изменения
git checkout feature
echo "v2" > version.txt
git commit -am "Update to v2"

Теперь история такая:

  • main указывает на коммит "Initial version";
  • feature указывает на коммит "Update to v2".

Если вы выполните:

git checkout main
git merge feature

Git просто передвинет указатель main на тот же коммит, на который указывает feature. Новый коммит не создается — это и есть fast-forward merge.

Пример вывода

Updating 1a2b3c4..5d6e7f8
Fast-forward
 version.txt | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

Merge с коммитом слияния (non fast-forward)

Когда у целевой ветки есть свои изменения, которые не присутствуют в вливаемой ветке, Git не может просто переместить указатель. В этом случае создается merge-коммит — специальный коммит с двумя родителями.

Давайте наглядно:

# Исходно вы на main
git checkout main

# Делаем коммит на main
echo "main v2" > main.txt
git add main.txt
git commit -m "Main changes"

# Создаем ветку feature от main
git branch feature

# Переключаемся в feature
git checkout feature

# Делаем изменения в feature
echo "feature v1" > feature.txt
git add feature.txt
git commit -m "Feature changes"

Теперь:

  • main имеет свои изменения (main.txt),
  • feature имеет свои (feature.txt).

Если вы выполните:

git checkout main
git merge feature

Git создаст новый коммит слияния. Вывод может быть такого вида:

Merge made by the 'ort' strategy.
 feature.txt | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 feature.txt

История теперь содержит merge-коммит, у которого два родителя:

  • один — последний коммит main,
  • второй — последний коммит feature.

Типы и режимы слияния

Теперь давайте разберем разные варианты поведения git merge, которые вы можете настраивать.

Слияние с запретом fast-forward — флаг --no-ff

Иногда вы хотите всегда иметь явный merge-коммит, даже если с точки зрения Git возможно fast-forward слияние. Это важно, когда вы хотите сохранить «ветвистость» истории и явно видеть, где заканчивалась работа над фичей.

Для этого используется опция:

git checkout main
git merge --no-ff feature/login

Что делает этот флаг:

  • даже если main можно просто перемотать вперед,
  • Git все равно создаст отдельный merge-коммит.

Плюс такого подхода — в истории хорошо видно, какие коммиты относятся к конкретной задаче или ветке. Минус — история становится более «шумной» из-за большого количества merge-коммитов.


Слияние с запретом автоматического коммита — флаг --no-commit

Если вы хотите сначала посмотреть, какие именно изменения подготовит Git, и только потом решить, делать ли коммит слияния, используйте:

git checkout main
git merge --no-commit feature/search

Git:

  • выполнит слияние,
  • запишет изменения в рабочую директорию и индекс (staging area),
  • но не создаст коммит.

Дальше вы можете:

  • просмотреть изменения:

    git status
    git diff --cached
    
  • подправить файлы, если нужно;
  • сделать свой коммит слияния:

    git commit -m "Merge feature/search into main with manual adjustments"
    

Такой подход полезен, если вы ожидаете, что после слияния придется что-то немного доправить вручную.


Отмена слияния до коммита — флаг --abort

Представьте, что вы начали слияние, появились конфликты, а вы поняли, что сейчас разруливать их неудобно, или вы вообще выбрали не ту ветку.

Вы можете откатиться к состоянию до начала merge:

git merge feature/report

# Конфликты, вы передумали
git merge --abort

Git вернет репозиторий к состоянию, в котором он был до запуска merge. Это удобно, когда вы хотите полностью «забыть», что вообще начинали слияние.


Конфликты при слиянии: почему возникают и как решать

Теперь давайте разберем наиболее волнительную часть — конфликты.

Когда возникают конфликты

Конфликт возникает, когда:

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

Например:

  • Ветка main изменила строку title = "App" на title = "Main App";
  • Ветка feature изменила ту же строку на title = "Feature App".

Git не может угадать, что вы имели в виду, и просит вас принять решение вручную.

Как выглядит конфликт в файле

Смотрите, я покажу вам, как Git помечает конфликтующие участки. Допустим, файл config.txt после merge содержит:

app_name = My App
<<<<<<< HEAD
title = Main App
=======
title = Feature App
>>>>>>> feature/header
version = 1.0.0

Комментарии, которые добавил Git:

  • <<<<<<< HEAD — начало вашего варианта (ветка, куда вы сливаете);
  • ======= — разделитель между вариантами;
  • >>>>>>> feature/header — конец варианта из вливаемой ветки feature/header.

Ваша задача — вручную отредактировать этот фрагмент, удалить служебные строки и оставить итоговый вариант, который вам нужен. Например:

app_name = My App
title = Main App with feature header
version = 1.0.0

Здесь вы явно решили, как объединить оба изменения.


Типовой процесс решения конфликтов

Давайте разберем пошагово, что вам нужно делать, когда конфликты возникли.

Предположим, вы выполняете:

git checkout main
git merge feature/api

И видите что-то вроде:

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

Теперь по шагам:

  1. Посмотреть статус:

    git status
    

    Вы увидите файлы в состоянии both modified (оба изменили).

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

    Там вы увидите блоки с <<<<<<<, =======, >>>>>>>.

  3. Отредактировать файлы:

    • убрать маркеры конфликтов,
    • вручную привести код к нужному виду.
  4. Пометить конфликты как решенные, добавив файл в индекс:

    git add src/api.js
    
  5. Когда все конфликтные файлы будут добавлены, сделать коммит слияния:

    git commit
    

    Git сам предложит шаблон сообщения вида:

    Merge branch 'feature/api'
    

    Вы можете его оставить или изменить.


Использование инструментов для решения конфликтов

Решать конфликты можно и вручную, но в реальных проектах изменения бывают сложными. Для этого у Git есть поддержка merge-tool — внешних инструментов.

Пример запуска встроенного механизма:

git mergetool

Git откроет указанный в настройках инструмент сравнения (например, kdiff3, meld, VS Code merge view и т.п.). Там вы увидите:

  • базовую версию;
  • вашу версию;
  • версию из другой ветки;
  • и финальный результат, который нужно сохранить.

Пример минимальной настройки (в .gitconfig):

[merge]
    tool = code
[mergetool "code"]
    cmd = code --wait --merge $REMOTE $LOCAL $BASE $MERGED

Комментарии:

  • Здесь мы настраиваем VS Code как merge-tool.
  • После этого git mergetool запустит VS Code в режиме слияния.

Стратегии слияния в Git

Git поддерживает несколько стратегий слияния. В большинстве случаев вам хватает стратегии по умолчанию, но бывают ситуации, когда другие режимы полезны.

Стратегия по умолчанию: ort (раньше recursive)

В современных версиях Git (2.34+) по умолчанию используется стратегия ort. Раньше основная стратегия называлась recursive. Для понимания статьи важно лишь знать:

  • это интеллектуальная стратегия трехстороннего слияния;
  • она умеет неплохо разруливать разнесенные изменения;
  • она работает по умолчанию, если вы не задаете явно другую стратегию.

Вы можете явно указать:

git merge -s ort feature/payment

но обычно это не требуется.


Стратегия ours — игнорировать изменения из вливаемой ветки

Стратегия ours может показаться странной на первый взгляд, но она полезна в определенных сценариях.

Смотрите, что она делает:

  • создает merge-коммит,
  • но при этом в содержимом файлов сохраняет только версию текущей ветки (HEAD),
  • изменения из вливаемой ветки фактически игнорируются.

Пример:

git checkout main
git merge -s ours legacy-branch -m "Merge legacy-branch but keep main changes"

Когда это бывает нужно:

  • вы хотите «закрыть» старую ветку, чтобы Git считал, что она влита,
  • но на самом деле ее код вам уже не нужен (например, старая архитектура, которую вы переписали, и хотите сохранить только новую).

Стратегия theirs — почему это не опция merge

Новички часто ищут стратегию theirs, чтобы «принять все изменения из другой ветки». В git merge такой стратегии нет. Вместо этого:

  • theirs работает в контексте git checkout или git restore при решении конфликтов, когда вы можете явно принять версию файла из конкретной стороны.

Пример, как принять их версию при конфликте:

# Принять версию файла из другой ветки (theirs) для конкретного файла
git checkout --theirs path/to/file   # в новых версиях лучше использовать git restore

# Или с новым синтаксисом
git restore --source=MERGE_HEAD -- path/to/file

# Потом не забываем добавить файл в индекс
git add path/to/file

Здесь вы фактически говорите: «В конфликте по этому файлу выбери полностью версию из вливаемой ветки».


Merge нескольких веток за один раз

Вы можете сливать не только одну ветку за раз, но и несколько:

git checkout main
git merge feature/auth feature/profile bugfix/header

Git последовательно вольет указанные ветки в текущую:

  • сначала feature/auth,
  • затем feature/profile,
  • затем bugfix/header.

Однако такой подход может усложнить разбор конфликтов. Чаще в командах предпочитают сливать ветки по одной, чтобы легче было понять происхождение конфликтов.


Разница между git merge и git rebase

Очень частый вопрос: когда использовать merge, а когда rebase. Давайте коротко, но по сути.

Как работает git merge

  • сохраняет историю ветвления;
  • создает merge-коммиты;
  • не переписывает существующие коммиты;
  • удобно, когда важна полная история изменений, включая то, как ветки реально развивались.

История выглядит «ветвистой», но правдивой.

Как работает git rebase

  • переписывает историю:
    • берет ваши коммиты и как бы «накладывает» их поверх другой ветки;
  • не создает merge-коммитов (в простых сценариях);
  • делает историю линейной, но меняет хэши коммитов.

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

  • не делайте rebase публичных веток, которые уже кто-то забрал себе (pull), иначе коллегам придется разруливать рассинхронизацию истории.

Когда выбирать merge

Используйте git merge, если:

  • вы работаете в команде и хотите сохранять честную историю ветвления;
  • вы завершили фичу и хотите влить ее в main, сохранив отдельный merge-коммит (часто используют в Git Flow и похожих процессах);
  • вам важно избежать переписывания истории в общих ветках.

Практические сценарии слияния веток

Теперь давайте посмотрим на несколько реальных кейсов и команд, которые вы будете применять очень часто.

Слияние фич-ветки в main

Один из самых типичных сценариев.

  1. Вы создаете ветку от main:

    git checkout main
    git pull origin main      # подтягиваем свежие изменения с сервера
    git checkout -b feature/search
    
  2. Работаете в ветке, делаете коммиты:

    # Изменили файлы
    git add .
    git commit -m "Implement basic search"
    
  3. Завершив работу, переходите на main и сливаете:

    git checkout main
    git pull origin main          # сначала обновить main
    git merge --no-ff feature/search
    
  4. Разрешаете конфликты, если есть, и пушите:

    git push origin main
    
  5. После успешного слияния фич-ветку можно удалить:

    git branch -d feature/search        # локально
    git push origin --delete feature/search   # на сервере
    

Слияние обновлений main в вашу фич-ветку

Это обратный сценарий. Вы работаете над фичей, в main за это время появляются изменения. Чтобы уменьшить риск конфликтов при финальном слиянии, полезно периодически подтягивать main в свою ветку.

Последовательность:

# Находитесь в своей фич-ветке
git checkout feature/report

# Подтягиваем свежий main с сервера
git checkout main
git pull origin main

# Возвращаемся в фичу и вливаем main
git checkout feature/report
git merge main

Теперь фич-ветка содержит актуальные изменения из main, и позже, когда вы будете сливать feature/report обратно в main, конфликтов может быть меньше.


Отмена merge-коммита

Иногда вы влили ветку, но потом поняли, что это было преждевременно (например, появились критические баги). Тогда нужно откатить merge-коммит.

Вариант через git revert:

  1. Находите хэш merge-коммита:

    git log --oneline
    

    Предположим, хэш — abc1234.

  2. Откатываете именно merge-коммит, указав родителя:

    git revert -m 1 abc1234
    

    Здесь:

    • -m 1 означает, что в качестве «основной» линии истории используется первый родитель коммита (обычно это ветка main перед слиянием).

Это создаст новый коммит, который отменит изменения, внесенные merge-коммитом, но сохранит историю (и сам merge-коммит тоже останется в истории).


Безопасные практики при слиянии веток

Работайте в чистем рабочем каталоге

Перед запуском git merge убедитесь, что:

git status

показывает:

  • нет незакоммиченных изменений (working tree clean),
  • нет файлов в состоянии «staged but not committed», если вы не планируете делать коммит прямо сейчас.

Если у вас есть незавершенные изменения, у вас два варианта:

  • закоммитить их;
  • временно спрятать с помощью stash:

    git stash
    # потом после merge
    git stash pop
    

Сначала обновляйте целевую ветку с сервера

Очень распространенный паттерн:

git checkout main
git pull origin main        # сначала обновляем main
git merge featureX
git push origin main

Так вы уменьшаете вероятность, что в удаленной ветке main уже есть какие-то изменения, о которых вы не знаете, и усложняете себе жизнь при пуше.


Пишите понятные сообщения к merge-коммитам

По умолчанию git merge создает такое сообщение:

Merge branch 'feature/payment'

Многие команды оставляют его как есть, но иногда полезно добавить уточнение, особенно если merge решает что-то нетривиальное:

git commit

И в редакторе изменить сообщение, например, на:

Merge branch 'feature/payment' into main

Resolve API version conflicts and align error handling

Так будущим читателям истории будет легче понять, почему merge был важен.


Восстановление после неудачного merge

Ошибки при слиянии — нормальная часть работы. Важно понимать, как их безопасно откатить.

Если вы еще не сделали коммит

Если вы запустили merge, получили конфликты или видите результат, который вам не нравится, и при этом коммит слияния еще не сделан, у вас есть два пути.

Полностью отменить слияние

git merge --abort

Git вернет состояние ветки к тому коммиту, где вы были до merge.

Отменить изменения в отдельных файлах

Если вы хотите только один (или несколько) файлов вернуть к состоянию HEAD:

git restore path/to/file

или в старом синтаксисе:

git checkout -- path/to/file

Если merge-коммит уже сделан и запушен

Если merge уже попал в общую историю, рекомендуется не переписывать историю (не делать reset --hard, rebase и т.п.). Вместо этого используйте revert:

git revert -m 1 <hash_merge_commit>

Это создаст новый коммит, который отменит изменения merge-коммита, но оставит сам merge в истории.


Распространенные ошибки и как их избегать

Случайный merge не в ту ветку

Иногда вы забываете переключиться на нужную ветку и делаете merge не туда. Типичный сценарий:

# Вы думали, что сейчас на main, а на самом деле еще в feature/old
git merge feature/new

Если вы это заметили до пуша на сервер:

  • просто откатите ветку с помощью reset:

    git reset --hard HEAD~1
    

    Этот вариант подходит, если:

    • вы уверены, что merge-коммит был последним действием,
    • никто еще не успел забрать эту ветку.

Если уже успели запушить — лучше использовать revert, как описано выше, чтобы не ломать историю.


Попытка пуша после merge, когда удаленная ветка ушла вперед

Еще одна типичная ситуация:

  • вы сделали git merge локально,
  • пытаетесь сделать git push,
  • а удаленная ветка уже содержит новые коммиты.

Git скажет вам что-то вроде:

To origin/main
 ! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '...'

Решение:

  1. Сначала подтянуть изменения с сервера:

    git pull --rebase origin main
    

    или (при строгом использовании merge):

    git pull origin main
    
  2. Разрешить конфликты, если будут.
  3. Потом снова сделать push:

    git push origin main
    

Краткое резюме по git merge

  • git merge — основной механизм объединения изменений из одной ветки в другую.
  • При merge Git использует трехстороннее слияние, сравнивая обе ветки и их общего предка.
  • Существует два ключевых типа результата:
    • fast-forward — когда целевая ветка просто «перематывается» вперед без нового коммита;
    • non fast-forward — создается merge-коммит с двумя родителями.
  • Флаги:
    • --no-ff — принудительно создать merge-коммит, даже если возможен fast-forward;
    • --no-commit — подготовить изменения слияния без немедленного создания коммита;
    • --abort — откатить начатое, но не завершенное слияние.
  • Конфликты при merge — нормальное явление, если одна и та же строка была изменена в разных ветках. Решаются вручную или с помощью mergetool.
  • Стратегии merge (ort, ours и др.) позволяют гибко управлять тем, какие именно изменения попадут в итог.
  • Для командной работы merge предпочтителен там, где важна честная история и прозрачность того, как развивался код.

Частозадаваемые технические вопросы по теме и ответы

Как сделать так, чтобы при merge автоматически выбиралась версия из другой ветки (theirs) для всех файлов

Вы можете после начала merge принять «их» версию для всех конфликтных файлов таким способом:

# Начали слияние и получили конфликты
git merge feature/api

# Принять их версии файлов для всех конфликтов
git diff --name-only --diff-filter=U | xargs git checkout --theirs

# Добавить все решенные файлы в индекс
git add .

# Завершить merge-коммит
git commit

Комментарии:

  • git diff --name-only --diff-filter=U выводит список конфликтных файлов;
  • git checkout --theirs подставляет версию из вливаемой ветки.

Как объединить несколько последовательных merge-коммитов в один

Если в истории накопилось несколько соседних merge-коммитов, их напрямую «объединить» нельзя, но вы можете переписать историю с помощью интерактивного rebase (если ветка еще не ушла в общий репозиторий):

git rebase -i <hash_коммита_до_серии_merge>

Важно:

  • интерактивный rebase плохо работает с merge-коммитами;
  • для уже опубликованных веток лучше не применять этот подход, чтобы не ломать историю коллегам.

Почему git merge иногда автоматически переименовывает файлы

Если в одной ветке файл был переименован, а в другой — изменен, стратегия merge может автоматически сопоставить эти изменения и решить, что это один и тот же файл. Git делает это по содержимому и эвристикам переименования. Вы можете управлять порогом определения переименований флагом:

git merge -X find-renames=75% other-branch

Здесь 75% — порог схожести содержимого.


Как отключить автоматический запуск mergetool при каждом merge

Если у вас в конфиге включен автоматический запуск mergetool и это мешает, проверьте настройки:

git config --global --unset merge.tool
git config --global --unset mergetool.prompt

После этого mergetool будет запускаться только вручную по команде:

git mergetool

Как ограничить глубину истории при merge, чтобы он был быстрее на огромном репозитории

Для очень больших репозиториев можно ограничить глубину поиска общего предка:

git merge --depth=50 other-branch

Однако:

  • такой режим используется редко,
  • может привести к менее корректному анализу истории,
  • лучше оптимизировать репозиторий другими способами (shallow clone, sparse checkout) и применять merge в обычном режиме.
Стрелочка влевоПеребазирование в Git - команда git rebase от основ до практикиРазрешение конфликтов merge conflict в GitСтрелочка вправо

Постройте личный план изучения Git до уровня Middle — бесплатно!

Git — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Git

Открыть базу знаний

Лучшие курсы по теме

изображение курса

Основы Git

Антон Ларичев
AI-тренажерыAI-тренажеры
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.8
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажерыAI-тренажеры
Практика в студииПрактика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий