Олег Марков
Разрешение конфликтов merge conflict в Git
Введение
Конфликт слияния (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):
- Общий предок двух веток (common ancestor) — версия файла, от которой разошлись ветки.
- Версия файла в вашей текущей ветке (обычно
mainилиmaster). - Версия файла в ветке, которую вы вливаете (
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
Теперь ваша задача:
- Решить, какой вариант оставить.
- При необходимости объединить части кода.
- Удалить маркеры
<<<<<<<,=======,>>>>>>>.
Например, вы решили, что в 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 и т.п.
Общий подход
- Открыть файл с конфликтом.
- Найти маркеры
<<<<<<<,=======,>>>>>>>. - Понять, что:
- верхний блок — ваша ветка;
- нижний блок — вливаемая ветка.
- Оставить нужные строки, при необходимости объединить изменения.
- Удалить маркеры.
- Сохранить файл.
- Выполнить
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
Дальше вы делаете:
Проверяете статус:
git statusОткрываете конфликтные файлы, исправляете их так же, как при merge:
# правите handler.go git add handler.goПродолжаете rebase:
git rebase --continue
Если в следующих коммитах снова будут конфликты, процесс повторится.
Важно:
git commitсамостоятельно в процессе rebase обычно не делается; вместо него используетсяgit rebase --continue.Если вы хотите отменить rebase полностью:
git rebase --abort
Эта команда вернет ветку в состояние до начала rebase.
Конфликты при git pull
Команда:
git pull
по сути выполняет два шага:
git fetch— забирает новые коммиты с сервера.git mergeилиgit rebase— пытается объединить их с вашей локальной веткой.
По умолчанию чаще всего это merge. Поэтому, когда вы видите конфликт после git pull, это тот же самый конфликт слияния, просто вызванный автоматически.
Процесс тот же:
- Посмотреть
git status. - Исправить конфликтные файлы:
- вручную;
- через IDE или mergetool.
- Выполнить
git addдля исправленных файлов. - Сделать
git commit(если это merge). - Повторить
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 не может автоматически выбрать итоговый вариант.
Вы увидели, что общий рабочий процесс всегда примерно одинаков:
- Запустить операцию (merge, rebase, pull, cherry-pick).
- Увидеть сообщение о конфликте.
- Через
git statusпонять, какие файлы затронуты. - Открыть эти файлы, найти маркеры
<<<<<<<,=======,>>>>>>>. - Принять решение:
- оставить одну из версий;
- объединить изменения;
- в редких случаях — полностью переписать участок.
- Удалить маркеры конфликта, сохранить файл.
- Сделать
git addдля всех исправленных файлов. - Завершить операцию:
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 кеширует старое состояние. Проверьте:
- Выполните
git statusв терминале:- если файл не в статусе
Uилиboth modified, значит, конфликт уже решен.
- если файл не в статусе
- В IDE выполните:
- перезагрузку индексов (Invalidate Caches / Restart) или
- просто перезапустите IDE.
Часто этого достаточно, чтобы синхронизировать состояние.
Как автоматически помечать файлы как решенные при незначительных отличиях
Можно использовать атрибуты Git и драйверы merge, например union для объединения строк в конфигурационных файлах. Для этого:
- В
.gitattributes:
*.conf merge=union
- Настроить драйвер в
git config:
git config --global merge.union.driver "union"
Комментарий
- Для таких файлов при конфликте Git просто объединит содержимое.
- Это удобно для списков, конфигов, где дублирование строк допустимо.
Постройте личный план изучения Git до уровня Middle — бесплатно!
Git — часть карты развития Frontend
100+ шагов развития
30 бесплатных лекций
300 бонусных рублей на счет
Бесплатные лекции
Все гайды по Git
Лучшие курсы по теме

Основы Git
Антон Ларичев
TypeScript с нуля
Антон Ларичев