Разрешение конфликтов merge conflict в Git

17 декабря 2025
Автор

Олег Марков

Введение

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

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

В этой статье вы увидите:

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

Давайте по шагам разберем, что происходит внутри и как спокойно и предсказуемо решать такие ситуации.

Что такое merge conflict и когда он возникает

Логика Git при слиянии

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

git merge feature/login

Git пытается объединить изменения из текущей ветки и ветки feature/login. Чтобы это сделать, он использует так называемое трехстороннее слияние (three-way merge):

  1. Общий предок двух веток (common ancestor) — версия файла, от которой разошлись ветки.
  2. Версия файла в вашей текущей ветке (обычно main или master).
  3. Версия файла в ветке, которую вы вливаете (feature/login).

Git сравнивает:

  • изменения от предка до вашей ветки;
  • изменения от предка до целевой ветки.

Если изменения не затрагивают одни и те же строки (или Git может их объединить автоматически), он спокойно делает merge-коммит без вашего вмешательства.

Если же изменения пересекаются — возникает merge conflict.

Простой пример возникновения конфликта

Представьте, что у вас есть файл config.txt:

# config.txt до разветвления
env=dev
timeout=30

Далее:

  • В ветке main кто-то изменил таймаут:
env=dev
timeout=60
  • В ветке feature/production кто-то изменил окружение:
env=prod
timeout=30

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

Но давайте немного усложним пример.

Пример реального конфликта

Исходный файл config.txt:

# config.txt до разветвления
env=dev
timeout=30

Теперь:

  • В ветке main:
env=dev
timeout=45
  • В ветке feature/production:
env=prod
timeout=60

Смотрите, теперь менялись обе строки в обеих ветках. Git не может понять:

  • какое окружение оставить — dev или prod;
  • какой таймаут — 45 или 60.

В момент слияния он помечает файл как конфликтный и просит вас вручную принять решение.

Как Git помечает конфликт в файле

Когда возникает конфликт, Git напрямую изменяет содержимое файла, добавляя специальные разделители. Теперь вы увидите, как это выглядит в реальном файле.

После неудачного merge ваш config.txt станет таким:

<<<<<<< HEAD
env=dev
timeout=45
=======
env=prod
timeout=60
>>>>>>> feature/production

Разберемся по частям.

  • <<<<<<< HEAD
    Начало блока с содержимым из вашей текущей ветки (HEAD — это ссылка на последний коммит текущей ветки).

  • Между <<<<<<< HEAD и ======= — версия файла из вашей ветки.

  • Между ======= и >>>>>>> feature/production — версия файла из вливаемой ветки feature/production.

  • >>>>>>> feature/production — конец конфликтного блока.

Git как бы показывает вам две альтернативы:

  • верхний блок — ваш текущий вариант;
  • нижний блок — изменения, приходящие из другой ветки.

Ваша задача — вручную отредактировать этот участок, оставить нужные строки и удалить все служебные маркеры (<<<<<<<, =======, >>>>>>>).

Какой результат ожидает Git

После редактирования файл должен стать обычным валидным текстовым (или кодовым) файлом, например:

# Решение конфликта ручное
env=prod
timeout=60

Или комбинированный вариант:

# Компромиссное решение
env=prod
timeout=45

Главное — в файле не должно остаться маркеров конфликта. Именно по их исчезновению Git понимает, что вы конфликт обработали.

Основной рабочий процесс при merge conflict

Теперь давайте разберем общую схему действий, когда вы попали в ситуацию конфликта при merge.

Шаг 1. Запустить слияние и увидеть конфликт

Вы запускаете, например:

git checkout main               # Переход в целевую ветку
git pull                        # Обновить ветку с удаленного репозитория
git merge feature/production    # Пытаемся влить ветку с изменениями

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

Auto-merging config.txt
CONFLICT (content): Merge conflict in config.txt
Automatic merge failed; fix conflicts and then commit the result.

Это нормальное сообщение. Слияние прервано, пока вы не исправите конфликт.

Шаг 2. Проверить статус репозитория

Теперь вы можете выполнить:

git status

И увидеть, например:

On branch main
You have unmerged paths.
  (fix conflicts and run "git commit")

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

no changes added to commit (use "git add" and/or "git commit -a")

Здесь Git четко указывает:

  • какие файлы находятся в конфликте (both modified);
  • что нужно сделать: исправить файлы, затем git add и git commit.

Шаг 3. Открыть файл и вручную разрешить конфликт

Вы открываете config.txt в редакторе и видите блок:

<<<<<<< HEAD
env=dev
timeout=45
=======
env=prod
timeout=60
>>>>>>> feature/production

Теперь ваша задача:

  1. Решить, какой вариант оставить.
  2. При необходимости объединить части кода.
  3. Удалить маркеры <<<<<<<, =======, >>>>>>>.

Например, вы решили, что в main должно быть:

env=prod
timeout=60

Вы приводите файл к этому виду:

env=prod
timeout=60

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

Шаг 4. Отметить файл как «конфликт решен»

После того как вы отредактировали файл, Git еще не знает, что конфликт решен. Ему нужно это явно сообщить через git add.

git add config.txt

Команда git add в этой ситуации означает: файл отредактирован, текущая версия — мой окончательный выбор.

Шаг 5. Завершить merge коммитом

Теперь вы делаете коммит, который и станет merge-коммитом:

git commit

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

Merge branch 'feature/production'

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

Шаг 6. Проверка истории и дальнейшие действия

После успешного коммита можно посмотреть историю:

git log --oneline --graph --decorate --all

Вы увидите новый merge-коммит, объединяющий ветки.

Если это была локальная ветка, и вы хотите обновить удаленный репозиторий:

git push

На этом сценарий merge с конфликтом завершен.

Типы конфликтов и примеры

Конфликт содержимого (content conflict)

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

Пример: файл user_service.go:

Исходная версия:

// user_service.go
type User struct {
    Name  string
    Email string
}

В ветке feature/add-phone:

// user_service.go
type User struct {
    Name   string
    Email  string
    Phone  string   // новое поле
}

В ветке refactor/user-entity:

// user_service.go
type User struct {
    FullName string     // переименование поля
    Email    string
}

Теперь вы увидите, как Git покажет конфликт:

type User struct {
<<<<<<< HEAD
    FullName string     // версия из вашей ветки
    Email    string
=======
    Name   string       // версия из другой ветки
    Email  string
    Phone  string       // новое поле
>>>>>>> feature/add-phone
}

Здесь вам нужно вручную принять решение:

  • как назвать поле (Name/FullName);
  • нужно ли добавить Phone.

Например, вы можете прийти к такому решению:

type User struct {
    FullName string    // итоговое имя поля
    Email    string
    Phone    string    // сохраняем новое поле
}

Конфликт удаления / изменения (modify/delete conflict)

Ситуация, когда:

  • в одной ветке файл был удален;
  • в другой — этот же файл изменен.

Пример:

  • В ветке cleanup файл old_config.yml удалили.
  • В ветке bugfix тот же файл поправили.

При слиянии Git скажет: «Я не знаю, что делать — удалить файл или сохранить новую версию».

Тут вы должны решить:

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

Обычно Git в git status покажет что-то вроде:

both deleted: old_config.yml

И предложит вам руками принять решение (оставить файл или удалить).

Конфликт перемещения (rename conflict)

Происходит, когда:

  • в одной ветке файл переименовали;
  • в другой — тоже переименовали, но в другое имя или по-другому его изменили.

Пример:

  • В ветке feature/service файл service.go переименовали в user_service.go.
  • В ветке feature/api файл service.go переименовали в account_service.go.

Git не знает, какое новое имя считать правильным, и просит вас принять решение.

Для таких случаев помогают команды git status и иногда ручная правка индекса через git add нужных версий.

Разрешение конфликтов через текстовый редактор

Чаще всего вы будете решать конфликты прямо в редакторе кода: VS Code, GoLand, IntelliJ IDEA, Vim, Neovim и т.п.

Общий подход

  1. Открыть файл с конфликтом.
  2. Найти маркеры <<<<<<<, =======, >>>>>>>.
  3. Понять, что:
    • верхний блок — ваша ветка;
    • нижний блок — вливаемая ветка.
  4. Оставить нужные строки, при необходимости объединить изменения.
  5. Удалить маркеры.
  6. Сохранить файл.
  7. Выполнить git add для файла.

Пример с Go-кодом

Предположим, у вас есть файл handler.go. Исходная версия:

// handler.go
func HandleRequest() {
    // TODO: implement
}

В ветке feature/logging:

// handler.go
func HandleRequest() {
    log.Println("request received") // логирование запросов
}

В ветке feature/metrics:

// handler.go
func HandleRequest() {
    metrics.IncRequests() // метрика количества запросов
}

При merge вы увидите:

func HandleRequest() {
<<<<<<< HEAD
    log.Println("request received")     // ваша ветка
=======
    metrics.IncRequests()              // другая ветка
>>>>>>> feature/metrics
}

Смотрите, здесь логично объединить поведение. Результат может выглядеть так:

func HandleRequest() {
    log.Println("request received")     // логируем запрос
    metrics.IncRequests()              // увеличиваем счетчик метрик
}

Так вы сохраняете логику обеих веток.

Разрешение конфликтов с помощью Git mergetool

Если вручную редактировать маркеры неудобно, вы можете использовать графические или текстовые merge-инструменты: vimdiff, meld, kdiff3, встроенный тул в IDE.

Включение mergetool

Сначала вы можете настроить инструмент по умолчанию. Например, Meld:

git config --global merge.tool meld
git config --global mergetool.prompt false  # необязательно - отключаем лишние вопросы

Теперь при конфликте вы можете запустить:

git mergetool

Git по очереди откроет все конфликтующие файлы в выбранном инструменте.

Обычно mergetool показывает вам:

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

После того как вы сохраняете результат в инструменте, Git считает конфликт в этом файле решенным (но git add вам все равно нужно будет сделать вручную либо инструмент сделает это сам, зависит от настроек).

Разрешение конфликтов при rebase

Merge conflict возникает не только при git merge, но и при git rebase.

Что такое rebase в контексте конфликтов

git rebase «переигрывает» ваши коммиты поверх другой ветки. С точки зрения конфликтов, каждое применение коммита — это мини-merge с возможным конфликтом.

Команда:

git rebase main

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

Процесс разрешения конфликта при rebase

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

Пример:

git checkout feature/login
git rebase main

Если появляется конфликт, Git скажет что-то вроде:

CONFLICT (content): Merge conflict in handler.go
error: could not apply 1234abcd... Add logging

Дальше вы делаете:

  1. Проверяете статус:

     git status
     
  2. Открываете конфликтные файлы, исправляете их так же, как при merge:

     # правите handler.go
     git add handler.go
     
  3. Продолжаете rebase:

     git rebase --continue
     

Если в следующих коммитах снова будут конфликты, процесс повторится.

Важно:

  • git commit самостоятельно в процессе rebase обычно не делается; вместо него используется git rebase --continue.
  • Если вы хотите отменить rebase полностью:

      git rebase --abort
      

Эта команда вернет ветку в состояние до начала rebase.

Конфликты при git pull

Команда:

git pull

по сути выполняет два шага:

  1. git fetch — забирает новые коммиты с сервера.
  2. git merge или git rebase — пытается объединить их с вашей локальной веткой.

По умолчанию чаще всего это merge. Поэтому, когда вы видите конфликт после git pull, это тот же самый конфликт слияния, просто вызванный автоматически.

Процесс тот же:

  1. Посмотреть git status.
  2. Исправить конфликтные файлы:
    • вручную;
    • через IDE или mergetool.
  3. Выполнить git add для исправленных файлов.
  4. Сделать git commit (если это merge).
  5. Повторить git push, если нужно обновить удаленный репозиторий.

Если вы не хотите merge при git pull, а предпочитаете rebase, можно настроить:

git config --global pull.rebase true

Тогда git pull будет вести себя как git fetch + git rebase.

Как отменить или перезапустить процесс разрешения конфликта

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

Отмена merge

Если вы запустили merge и еще не сделали коммит, а хотите все отменить:

git merge --abort

Эта команда:

  • вернет все файлы к состоянию до git merge;
  • отменит все изменения, возникшие при неудачном merge.

Если по какой-то причине команда недоступна, можно использовать:

git reset --merge

Но чаще git merge --abort достаточно.

Отмена rebase

Если вы в процессе git rebase и хотите выйти без изменений:

git rebase --abort

Ветка вернется в состояние до начала rebase.

Отмена конкретного файла

Если вы отредактировали файл с конфликтом и хотите вернуть его в состояние «как при конфликте»:

git checkout --ours   path/to/file   # взять версию из вашей ветки
git checkout --theirs path/to/file   # взять версию из другой ветки

Пояснения:

  • --ours — версия текущей ветки (HEAD).
  • --theirs — версия вливаемой ветки.

После этой команды можно сделать git add и тем самым считать конфликт решенным в пользу одной из сторон.

Будьте внимательны: эти команды затирают текущие несохраненные изменения в файле.

Полезные команды для анализа конфликтов

Здесь соберу несколько команд, которые помогают вам лучше понять, что происходит при конфликте.

Просмотр конфликтующих участков

Для вывода конфликтов:

git diff

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

Альтернатива более детальная:

git diff --name-only --diff-filter=U
  • покажет список только тех файлов, которые находятся в состоянии «unmerged» (есть конфликт).

Подробный просмотр по файлу

Вы можете посмотреть отдельные версии конфликтующего файла:

git show :1:path/to/file   # общий предок
git show :2:path/to/file   # ваша версия (HEAD)
git show :3:path/to/file   # версия из другой ветки

Эта тройка (:1:, :2:, :3:) — специальные ссылки на временные версии файла в конфликте. Полезно, когда нужно понять историю изменений.

Поиск всех конфликтных файлов

Короткая команда:

git status --short

Файлы с конфликтами будут отмечены как UU (unmerged both modified) или другими комбинациями U.

Пример:

UU  config.txt

Это означает, что файл имеет конфликт и требует ручного вмешательства.

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

Здесь соберу несколько практик, которые помогают вам реже сталкиваться с конфликтами и проще их решать.

Делайте маленькие и тематически цельные коммиты

Когда коммит:

  • небольшой;
  • меняет одну логическую часть системы;

конфликты проще:

  • понять, что в нем происходит;
  • воспроизвести и исправить.

Большие коммиты, меняющие много файлов одновременно, при конфликте становятся гораздо сложнее для понимания.

Чаще синхронизируйтесь с основной веткой

Если вы работаете в ветке feature:

  • периодически выполняйте git fetch и обновляйте main;
  • делайте git merge main или git rebase main в вашу ветку, пока она еще не разрослась.

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

Согласовывайте форматирование кода в команде

Разные автоформаттеры, разные стили и привычки по оформлению кода могут вызывать лишние конфликты «по пробелам» и отступам.

Решение:

  • используйте единый форматтер (go fmt, prettier, black и т.п.);
  • не мешайте в одном коммите логические изменения и массовое форматирование.

Договаривайтесь о разделении областей ответственности

Если в команде есть четкое понимание:

  • кто отвечает за какие модули;
  • кто меняет какие файлы и когда,

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

Заключение

Merge conflict в Git — это нормальная часть командной разработки, а не критическая ошибка. Он возникает, когда разные ветки меняют одни и те же строки, и Git не может автоматически выбрать итоговый вариант.

Вы увидели, что общий рабочий процесс всегда примерно одинаков:

  1. Запустить операцию (merge, rebase, pull, cherry-pick).
  2. Увидеть сообщение о конфликте.
  3. Через git status понять, какие файлы затронуты.
  4. Открыть эти файлы, найти маркеры <<<<<<<, =======, >>>>>>>.
  5. Принять решение:
    • оставить одну из версий;
    • объединить изменения;
    • в редких случаях — полностью переписать участок.
  6. Удалить маркеры конфликта, сохранить файл.
  7. Сделать git add для всех исправленных файлов.
  8. Завершить операцию:
    • git commit для merge;
    • git rebase --continue для rebase;
    • или при необходимости --abort, чтобы откатить процесс.

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

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

Как быстро принять все изменения только из удаленной ветки при конфликте pull

Если при git pull возник конфликт, и вы хотите целиком взять версию с сервера, сделайте так:

git fetch origin
git reset --hard origin/main   # заменяем локальную ветку main версией с сервера

Комментарий

  • Эта команда удалит все локальные незафиксированные изменения в ветке main.
  • Используйте только если уверены, что локальные изменения не нужны.

Как принять все изменения только из своей ветки при merge

Если вы уже запустили git merge other-branch, но хотите полностью игнорировать изменения из other-branch, сделайте:

git merge --abort                    # откатываем merge
git merge -s ours other-branch       # стратегия ours - берем только нашу версию

Комментарий

  • Стратегия ours создает merge-коммит, но все файлы будут как в вашей ветке.
  • Это полезно, если нужно формально считать ветку слитой, но ее содержимое не использовать.

Как посмотреть историю конкретного конфликтующего участка

Иногда нужно понять, кто и зачем поменял строки, которые сейчас конфликтуют. Используйте:

git blame path/to/file

Комментарий

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

Что делать, если IDE показывает конфликт, а Git считает файл чистым

Вероятно, IDE кеширует старое состояние. Проверьте:

  1. Выполните git status в терминале:
    • если файл не в статусе U или both modified, значит, конфликт уже решен.
  2. В IDE выполните:
    • перезагрузку индексов (Invalidate Caches / Restart) или
    • просто перезапустите IDE.

Часто этого достаточно, чтобы синхронизировать состояние.

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

Можно использовать атрибуты Git и драйверы merge, например union для объединения строк в конфигурационных файлах. Для этого:

  1. В .gitattributes:
*.conf merge=union
  1. Настроить драйвер в git config:
git config --global merge.union.driver "union"

Комментарий

  • Для таких файлов при конфликте Git просто объединит содержимое.
  • Это удобно для списков, конфигов, где дублирование строк допустимо.
Стрелочка влевоСлияние веток в Git - полное руководство по git mergeОтмена слияния в Git с помощью git merge --abortСтрелочка вправо

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

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

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

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

Все гайды по Git

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

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

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

Основы Git

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

TypeScript с нуля

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

Next.js - с нуля

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

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