Время на прочтение
8 мин
Количество просмотров 146K
Предлагаю читателям «Хабрахабра» перевод публикации «Painless Merge Conflict Resolution in Git»
из блога blog.wuwon.id.au.
В моей повседневной работе, часто приходится иметь дело со множеством git ветвей (branch). Это могут быть ветви промежуточных релизов, ветви с устаревшим API находящиеся на поддержке для некоторых клиентов, или ветви с экспериментальными свойствами. Лёгкость создания ветвей в модели Git так и соблазняет разработчиков создавать все больше и больше ветвей, и как правило бремя от большого количества ветвей становится очень ощутимым, когда приходится все эти ветви поддерживать и периодически делать слияния (merge) с другими ветвями.
Слияния очень важны для поддержания кода в актуальном состоянии, и как правило ошибка сделанная при слиянии может привести к большей головной боли, нежели ошибка сделанная при простом коммите. К сожалению ошибки слияния далеко не редкость, потому что во-первых слияния имеют несколько родительских ветвей. Даже при анализе истории слияния ветвей, бывает очень трудно понять, какие же изменения были сделаны для разрешения конфликта. Во-вторых, отмена неудачного слияния может превратиться в большую головную боль. В-третьих, большая часть конфликтов слияния происходит при работе с чужим кодом, потому что само понятие ветвей подразумевает множество пользователей, т.е. далеко не всегда слияние производит тот же человек который работал с той или иной веткой. В сухом остатке, сделать ошибку при слиянии очень легко, её трудно исправить и трудно найти. Таким образом время потраченное на изучение и понимание процесса слияния ветвей, окупится с лихвой.
Удивительно, но я обнаружил, что многие доступные инструменты и интерфейсы предназначенные для выполнения слияний, не достаточно хорошо оснащены для эффективного выполнения этого процесса. Часто программист просто надеется что команда git merge сделает за него всю работу. Но когда все-таки происходит конфликт, то обычно стратегия слияния заключается в беглом просмотре кода вокруг строки конфликта, и интуитивном угадывании что именно данный кусок кода предпочтительней другого.
В данной статье я надеюсь продемонстрировать что процесс разрешения конфликтов может быть пошагово точным, при котором отпадает необходимость что-либо там угадывать.
Голубые Розы (Roses are Blue)
Давайте предположим что вашей команде поручили писать поэмы в отведённом для этих целей репозитории. (Какой кошмар!) А вам доверили самое главное — делать слияния последних фиксов из ветки master в ветку beta. Итак, вы переключаетесь в ветку beta и выполняете следующую команду:
$ git merge master
Auto-merging roses.txt
CONFLICT (content): Merge conflict in roses.txt
Automatic merge failed; fix conflicts and then commit the result.
Ого, это конфликт. Вы решаете просмотреть файл на который ссылается git:
$ cat roses.txt
<<<<<<< HEAD
roses are #ff0000
violets are #0000ff
all my base
are belong to you
=======
Roses are red,
Violets are blue,
All of my base
Are belong to you.
>>>>>>> master
(Listing 1)
Замечательно! Весь файл, как показывает Listing 1, находится в конфликтном состоянии. Какой же вариант файла является более корректным? Оба варианта выглядят корректно. Верхний вариант написан в хакер-стиле с элементами цветовой кодировки в стиле HTML и с использованием только строчных букв. Нижний вариант выглядит более натурально, с использованием пунктуации и заглавных букв.
Если бы это был ваш проект, вы бы могли просто выбрать один вариант и покончить с этим слиянием. Но проблема в том, что это не ваша поэма, вы никогда не читали эту поэму раньше, не были ответственны за написание или редактирование, и вы отлично понимаете что в случае не верного решения чья-то тяжёлая работа может кануть в небытие. Однако вас всё же назначили ответственным по слиянию этих веток. Что же вам делать?
Назад к Базе (Back to Base)
Хитрость заключается в том, что Listing 1 не даёт вам полную информацию, необходимую для совершения корректного слияния. На самом деле, в процессе слияния участвуют четыре важных части информации (состояния), три из которых просто необходимы для успешного разрешения конфликта. В случае Listing 1, Git предоставил вам только два состояния.
Следующая диаграмма иллюстрирует эти четыре состояния:
Состояния (B) и © относятся к текущим положениям (head) веток master и beta соответственно, эти два состояния как раз таки и отражены в Listing 1. Состояние (D) это результат слияния, то что вы хотите получить/сгенерировать в конечном итоге (в большинстве случаев Git автоматически генерирует состояние (D)). Состояние (А) на самом верху, представляет собой базу (основу) слияния веток master и beta. База слияния (A) это последний общий предок веток master и beta, и пока предположим что это база слияния уникальна. Как мы увидим позже состояние (A) играет ключевую роль в разрешении конфликтов. На диаграмме я также отразил дельты 1 и 2, которые представляют изменения между состояниями (A)-(B), и (A)-© соответственно. Зная состояния (A), (B) и © дельты 1 и 2 могут быть легко получены (вычислены). Обратите внимание, что дельты 1 и 2 могут состоять из более чем одного коммита. Но для наших целей будем считать что все дельты монолитны.
Чтобы понять, как получить состояние (D), вы должны понимать что же операция слияния пытается сделать. Состояние (D) должно представлять собой сочетание изменений, внесённых в ветку master и beta соответственно. Т.е. другими словами сочетание дельт 1 и 2. Идея проста на поверхности и большую часть времени не требует вмешательства со стороны человека, за исключением особых случаев когда дельты затрагивают наслаиваемые (пересекающиеся) части файла. В такой ситуации вам требуется помочь машине сгенерировать результат (D), путём сравнения дельт 1 и 2.
Определение Отличий (Identifying the Differences)
Для того чтобы найти изменения внесённые в каждую ветку, необходимо знать как выглядит база слияния, состояние (A). Самый простой механизм получения информации о базе слияния, это установка опции merge.conflictstyle в значение diff3
$ git config merge.conflictstyle diff3
После включения этой опции, попробуйте заново сделать слияние (git reset —hard; git merge master) и проинспектируйте конфликтующий файл ещё раз:
$ cat roses.txt
<<<<<<< HEAD
roses are #ff0000
violets are #0000ff
all my base
are belong to you
|||||||
roses are red
violets are blue
all my base
are belong to you
=======
Roses are red,
Violets are blue,
All of my base
Are belong to you.
>>>>>>> master
(Listing 2)
Теперь мы видим третий фрагмент посередине, который и является базой слияния или состояние (A). Изменения видны как на ладони: в ветке beta (HEAD) человеческие названия цветов были заменены на HTML коды, а в ветку master добавили капитализацию и пунктуацию. Основываясь на этих знаниях, мы теперь знаем что результат должен включать в себя капитализацию, пунктуацию и HTML коды цветов.
В принципе на этом можно было бы и закончить, потому что результат достигнут. Но есть решение и получше.
Графическое Слияние (GUI Merging)
Хотя и простое текстовое представление конфликта слияния делает свою работу в простых случаях, на практике конфликты могут быть более радикальными и сложными. В таких случаях могут помочь графические инструменты. Мой выбор пал на простой инструмент написанный на Python под названием meld, но может подойти любой другой графический инструмент, способный представить слияние в трёх-колоночном виде.
Для использования графического инструмента (он должен быть установлен), после того как git пожаловался что есть конфликт, введите следующую команду:
$ git mergetool
Последует вопрос какой программой для слияния вы хотели бы воспользоваться, просто введите meld и нажмите Enter. Вот как окно программы может выглядеть (подразумевается опция merge.conflictstyle не была включена):
Несмотря на то что информация представлена бок о бок, она не отображает нужные фрагменты которые были в Listing 2. Мы не видим здесь фрагмента базы слияния (состояния (A)), что мы видим это файл roses.txt.LOCAL.2760.txt в левой колонке и файл roses.txt.REMOTE.2760.txt в правой колонке и файл посередине это неудачное слияние. Т.е. по сути нам представили состояния (B), © и несостоявшееся состояние (D), но состояние (A) отсутствует…
Правда отсутствует? Давайте проверим, в старом добром терминале:
$ ls -1
roses.txt
roses.txt.BACKUP.2760.txt
roses.txt.BASE.2760.txt
roses.txt.LOCAL.2760.txt
roses.txt.REMOTE.2760.txt
Видим интересующий нас файл: roses.txt.BASE.2760.txt. Это и есть файл базы слияния. Теперь нам осталось всего лишь найти изменения внесённые в ветки master и beta, по отношению к базе. Мы можем сделать это двумя отдельными вызовами meld:
$ meld roses.txt.LOCAL.2760.txt roses.txt.BASE.2760 &
$ meld roses.txt.BASE.2760 roses.txt.REMOTE.2760.txt &
(Кто-то может подметить что было бы более разумно, поменять порядок аргументов в первом вызове, для того чтобы файл базы находился в левой колонке в обоих случаях, но именно такой порядок сохраняет подобие трёх-колоночного вида, при котором база остаётся по середине.) Результат выполнения — два окна как показано ниже:
При чтении первого окна справа налево и второго окна слева направо, становится ясно как день, какие изменения произошли в каждой ветке. Так как meld любезно подсветил все изменения, теперь практически не возможно пропустить даже мелко заметные правки (Кто-нибудь заметил добавление предлога «of» при просмотре текстового представления разрешения конфликта Listing 1 или даже Listing 2?)
Вооружившись этими знаниями, мы теперь можем вернуться к трёх-колоночному представлению и сделать изменения. Моя стратегия ручного слияния это взять весь текст из ветки с более весомыми изменениями (в данном случае master/REMOTE т.е. beta), и поверх него производить пошаговые правки, т.е. вносить изменения сделанные в другой ветке (master). Вот что получилось:
А теперь всё вместе (All Together Now)
Надеюсь, вы найдёте этот трёх-окошечный метод разрешения конфликтов, таким же полезным каким нахожу его я. Но согласитесь что запускать новые вызовы meld вручную каждый раз при разрешении конфликтов, не очень то и удобно. Выход, это настроить git таким образом чтобы все три окна открывались автоматически при вызове команды git mergetool. Для этого можно создать выполняемый скрипт, который должен находится в переменной окружения PATH (например $HOME/bin/gitmerge), со следующим содержимым:
#!/bin/sh
meld $2 $1 &
sleep 0.5
meld $1 $3 &
sleep 0.5
meld $2 $4 $3
И добавьте следующее в ваш ~/.gitconfig файл:
[merge]
tool = mymeld
[mergetool "mymeld"]
cmd = $HOME/bin/gitmerge $BASE $LOCAL $REMOTE $MERGED
Теперь, когда вы в следующий раз будете запускать команду git mergetool для разрешения конфликта, откроются все три окна:
Окно дифа между BASE и LOCAL
Окно дифа между BASE и REMOTE
Окно трёх-колоночного вида
После того как вы привыкните к такому разрешению конфликтов с использованием трёх вышеупомянутых окон, вы скорее всего обнаружите, что процесс стал более методичным и механическим. В большинстве случаев, вам даже не придётся читать и понимать куски кода из каждой ветки, для того чтобы понять какой же вариант применить для слияния. Вам больше не понадобится догадываться, потому что вы будете гораздо более уверенным в корректности вашего комита. Из-за этой уверенности, появится чувство что разрешение конфликтов превратилось в увлекательное занятие.
Бонус от переводчика
Для тех кто пользуется tmux и n?vim, предлагаю следующий скрипт gitmerge:
#!/bin/sh
sn=gitmerge
tmux new-session -d -s "$sn" -n "diff3" "nvim -d $2 $4 $3"
tmux split-window -t "$sn:1" -v "nvim -d $2 $1"
tmux split-window -t "$sn:1" -h "nvim -d $1 $3"
Примечание: если вы не используете эту опцию в своем ~/.tmux.conf, то вам надо поменять в двух последних строках "$sn:1" на "$sn:0"
Соответственно добавьте следующее в ваш ~/.gitconfig
[mergetool "gitmerge"]
cmd = $HOME/bin/gitmerge "$BASE" "$LOCAL" "$REMOTE" "$MERGED"
[merge]
tool = gitmerge
Воркфлоу разрешения конфликта будет выглядеть так:
Пока игнорируем вопрос (Was the merge successful [y/n]?) и переключаемся в сессию под названием gitmerge (сочетание TMUXPREFIX + s):
Видим наше трёх-оконное представление на одном экране. Цифрами обозначены сплиты (panes) tmux’a, буквами соответствующие состояния. Делаем правки для разрешения конфликта, т.е. редактируем состояние (D) и сохраняем. После этого возвращаемся обратно в исходную сессию tmux’a и подтверждаем что слияние произошло успешно.
git rebase master
Лично я предпочитаю и считаю более правильным делать сначала rebase master в ветке beta, и только после этого переключаться в master и делать git merge beta. В принципе воркфлоу не сильно отличается, за исключением трёх-оконного вида.
Переключаемся в сессию gitmerge
Обратите внимание, что состояния (B) и © поменялись местами:
Рекомендую всем поиграться с примером репозитария хотя бы один раз, сделать разрешение конфликта по вышеописанной схеме. Лично я больше не гадаю а что же выбрать «Accept theirs» или «Accept yours».
Конфликты слияния происходят при слиянии ветвей, имеющих конкурирующие фиксации, и Git требуется ваша помощь, чтобы принять решение относительно того, какие изменения следует включить в окончательное слияние.
Git часто может разрешать различия между ветвями и автоматически объединять их. Как правило, изменения находятся в разных строках или даже в разных файлах, что упрощает понимание слияния компьютерами. Однако иногда возникают конфликтующие изменения, которые Git не может устранить без вашей помощи. Часто конфликты слияния возникают, когда участники вносят разные изменения в одну строку одного файла или когда один участник редактирует файл, а другой — удаляет тот же файл.
Прежде чем объединить запрос на вытягивание на GitHub, необходимо разрешить все конфликты слияния. Если в запросе на вытягивание возник конфликт слияния между ветвью сравнения и базовой ветвью, вы можете просмотреть список файлов с конфликтующими изменениями над кнопкой Слияние запроса на вытягивание. Кнопка Слияние запроса на вытягивание будет отключена, пока вы не устраните все конфликты между ветвью сравнения и базовой ветвью.
Разрешение конфликтов слияния
Чтобы устранить конфликт слияния, необходимо вручную отредактировать конфликтующий файл и выбрать изменения, которые необходимо сохранить в окончательном слиянии. Существует несколько разных способов устранения конфликта слияния:
- Если конфликт слияния вызван конкурирующими изменениями строк, например при внесении разных изменений в одну строку одного файла в разных ветвях в репозитории Git, его можно разрешить на GitHub с помощью редактора конфликтов. Дополнительные сведения см. в разделе Разрешение конфликта слияния в GitHub.
- Для остальных типов конфликтов слияния необходимо разрешить конфликт слияния в локальном клоне репозитория и отправить изменение в ветвь на GitHub. Для отправки изменений можно использовать командную строку или средство, например GitHub Desktop. Дополнительные сведения см. в разделе Разрешение конфликта слияния с помощью командной строки.
Если в командной строке есть конфликт слияния, локальные изменения нельзя отправлять в GitHub, пока не будет разрешен конфликт слияния локально на компьютере. При попытке объединения ветвей в командной строке с конфликтом слияния появится сообщение об ошибке. Дополнительные сведения см. в разделе Разрешение конфликта слияния с помощью командной строки.
$ git merge BRANCH-NAME
> Auto-merging styleguide.md
> CONFLICT (content): Merge conflict in styleguide.md
> Automatic merge failed; fix conflicts and then commit the result
Дополнительные материалы
- «Сведения о слиянии запросов на вытягивание»
- «Сведения о запросах на вытягивание»
- «Разрешение конфликта слияния с помощью командной строки»
- «Разрешение конфликта слияния в GitHub»
Системы контроля версий предназначены для управления дополнениями, вносимыми в проект множеством распределенных авторов (обычно разработчиков). Иногда один и тот же контент могут редактировать сразу несколько разработчиков. Если разработчик A попытается изменить код, который редактирует разработчик B, может произойти конфликт. Для предотвращения конфликтов разработчики работают в отдельных изолированных ветках. Основная задача команды git merge заключается в слиянии отдельных веток и разрешении любых конфликтующих правок.
Общие сведения о конфликтах слияния
Слияние и конфликты являются неотъемлемой частью работы с Git. В других инструментах управления версиями, например SVN, работа с конфликтами может быть дорогой и времязатратной. Git позволяет выполнять слияния очень просто. В большинстве случаев Git самостоятельно решает, как автоматически интегрировать новые изменения.
Обычно конфликты возникают, когда два человека изменяют одни и те же строки в файле или один разработчик удаляет файл, который в это время изменяет другой разработчик. В таких случаях Git не может автоматически определить, какое изменение является правильным. Конфликты затрагивают только того разработчика, который выполняет слияние, остальная часть команды о конфликте не знает. Git помечает файл как конфликтующий и останавливает процесс слияния. В этом случае ответственность за разрешение конфликта несут разработчики.
Типы конфликтов слияния
Конфликт во время слияния может произойти в двух отдельных точках — при запуске и во время процесса слияния. Далее рассмотрим, как разрешать каждый из этих конфликтных сценариев.
Git прерывает работу в самом начале слияния
Выполнение команды слияния прерывается в самом начале, если Git обнаруживает изменения в рабочем каталоге или разделе проиндексированных файлов текущего проекта. Git не может выполнить слияние, поскольку иначе эти ожидающие изменения будут перезаписаны новыми коммитами. Такое случается из-за конфликтов не с другими разработчиками, а с ожидающими локальными изменениями. Локальное состояние необходимо стабилизировать с помощью команд git stash, git checkout, git commit или git reset. Если команда слияния прерывается в самом начале, выдается следующее сообщение об ошибке:
error: Entry '<fileName>' not uptodate. Cannot merge. (Changes in working directory)
Git прерывает работу во время слияния
Сбой В ПРОЦЕССЕ слияния говорит о наличии конфликта между текущей локальной веткой и веткой, с которой выполняется слияние. Это свидетельствует о конфликте с кодом другого разработчика. Git сделает все возможное, чтобы объединить файлы, но оставит конфликтующие участки, чтобы вы разрешили их вручную. При сбое во время выполнения слияния выдается следующее сообщение об ошибке:
error: Entry '<fileName>' would be overwritten by merge. Cannot merge. (Changes in staging area)
Создание конфликта слияния
Чтобы лучше разобраться в конфликтах слияния, в следующем разделе мы смоделируем конфликт для дальнейшего изучения и разрешения. Для запуска моделируемого примера будет использоваться интерфейс Git c Unix-подобной командной строкой.
$ mkdir git-merge-test
$ cd git-merge-test
$ git init .
$ echo "this is some content to mess with" > merge.txt
$ git add merge.txt
$ git commit -am"we are commiting the inital content"
[main (root-commit) d48e74c] we are commiting the inital content
1 file changed, 1 insertion(+)
create mode 100644 merge.txt
С помощью приведенной в этом примере последовательности команд выполняются следующие действия.
- Создается новый каталог с именем
git-merge-test, выполняется переход в этот каталог и инициализация его как нового репозитория Git. - Создается новый текстовый файл
merge.txtс некоторым содержимым. - В репозиторий добавляется файл
merge.txtи выполняется коммит.
Теперь у нас есть новый репозиторий с одной веткой main и непустым файлом merge.txt. Далее создадим новую ветку, которая будет использоваться как конфликтующая при слиянии.
$ git checkout -b new_branch_to_merge_later
$ echo "totally different content to merge later" > merge.txt
$ git commit -am"edited the content of merge.txt to cause a conflict"
[new_branch_to_merge_later 6282319] edited the content of merge.txt to cause a conflict
1 file changed, 1 insertion(+), 1 deletion(-)
Представленная выше последовательность команд выполняет следующие действия.
- Создает новую ветку с именем
new_branch_to_merge_laterи выполняет переход в нее. - Перезаписывает содержимое файла
merge.txt. - Выполняет коммит нового содержимого.
В этой новой ветке new_branch_to_merge_later мы создали коммит, который переопределил содержимое файла merge.txt.
git checkout main
Switched to branch 'main'
echo "content to append" >> merge.txt
git commit -am"appended content to merge.txt"
[main 24fbe3c] appended content to merge.tx
1 file changed, 1 insertion(+)
Эта последовательность команд выполняет переключение на ветку main, добавляет содержимое в файл merge.txt и делает коммит. После этого в нашем экспериментальном репозитории находятся два новых коммита, первый — в ветке main, а второй — в ветке new_branch_to_merge_later. Теперь запустим команду git merge new_branch_to_merge_later и посмотрим, что из этого выйдет!
$ git merge new_branch_to_merge_later
Auto-merging merge.txt
CONFLICT (content): Merge conflict in merge.txt
Automatic merge failed; fix conflicts and then commit the result.
БАХ! 💥 Возник конфликт. Хорошо, что система Git сообщила нам об этом.
Выявление конфликтов слияния
Как мы убедились на выполняемом примере, Git выводит небольшое описательное сообщение о возникновении КОНФЛИКТА. Чтобы получить более глубокое понимание проблемы, можно запустить команду git status.
$ 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: merge.txt
Вывод команды git status говорит о том, что из-за конфликта не удалось слить пути. Теперь файл merge.text отображается как измененный. Давайте изучим этот файл и посмотрим, что изменилось.
$ cat merge.txt
<<<<<<< HEAD
this is some content to mess with
content to append
=======
totally different content to merge later
>>>>>>> new_branch_to_merge_later
Для просмотра содержимого файла merge.txt воспользуемся командой cat. Видно, что в файле появились новые странные дополнения:
<<<<<<< HEAD=======>>>>>>> new_branch_to_merge_later
Эти новые строки можно рассматривать как «разделители конфликта». Строка ======= является «центром» конфликта. Все содержимое между этим центром и строкой <<<<<<< HEAD находится в текущей ветке main, на которую ссылается указатель HEAD. А все содержимое между центром и строкой >>>>>>> new_branch_to_merge_later является содержимым ветки для слияния.
Разрешение конфликтов слияния с помощью командной строки
Самый простой способ разрешить конфликт — отредактировать конфликтующий файл. Откройте файл merge.txt в привычном редакторе. В нашем примере просто удалим все разделители конфликта. Измененное содержимое файла merge.txt будет выглядеть следующим образом:
this is some content to mess with
content to append
totally different content to merge later
После редактирования файла выполните команду git add merge.txt, чтобы добавить новое объединенное содержимое в раздел проиндексированных файлов. Для завершения слияния создайте новый коммит, выполнив следующую команду:
git commit -m "merged and resolved the conflict in merge.txt"
Git обнаружит, что конфликт разрешен, и создаст новый коммит слияния для завершения процедуры слияния.
Команды Git, с помощью которых можно разрешить конфликты слияния
Общие инструменты
Команда status часто используется во время работы с Git и помогает идентифицировать конфликтующие во время слияния файлы.
При передаче аргумента --merge для команды git log будет создан журнал со списком конфликтов коммитов между ветками, для которых выполняется слияние.
Команда diff помогает найти различия между состояниями репозитория/файлов. Она полезна для выявления и предупреждения конфликтов слияния.
Инструменты для случаев, когда Git прерывает работу в самом начале слияния
Команда checkout может использоваться для отмены изменений в файлах или для изменения веток.
Команда reset может использоваться для отмены изменений в рабочем каталоге или в разделе проиндексированных файлов.
Инструменты для случаев, когда конфликты Git возникают во время слияния
При выполнении команды git merge с опцией --abort процесс слияния будет прерван, а ветка вернется к состоянию, в котором она находилась до начала слияния.
Команду git reset можно использовать для разрешения конфликтов, возникающих во время выполнения слияния, чтобы восстановить заведомо удовлетворительное состояние конфликтующих файлов.
Резюме
Конфликты слияния могут пугать. К счастью, Git предлагает мощные инструменты их поиска и разрешения. Большую часть слияний система Git способна обрабатывать самостоятельно с помощью функций автоматического слияния. Конфликт возникает, когда в двух ветках была изменена одна и та же строка в файле или когда некий файл удален в одной ветке и отредактирован в другой. Как правило, конфликты возникают при работе в команде.
Существует множество способов разрешения конфликтов слияния. В этой статье мы рассмотрели немалое количество инструментов командной строки, которые предоставляет Git. Более подробную информацию об этих инструментах см. на отдельных страницах для команд git log, git reset, git status, git checkout и git reset. Помимо этого многие сторонние инструменты также предлагают оптимизированные функции, поддерживающие работу с конфликтами слияния.
If you’ve ever worked on a team that’s working on a large codebase, you’ve likely experienced merge conflicts while creating a pull request or merging two branches.
Even if you’ve never worked with a team or on a large codebase, it is still possible to have merge conflicts as long as you have more than one branch. In the process of merging one branch with another, a merge conflict may occur.
In this article, you’ll learn about merge conflicts in Git and the types of merge conflicts you might encounter. Most importantly, you will learn how to resolve merge conflicts on GitHub and with VS Code’s 3-way merge editor.
What We’ll Cover
- What is a Merge Conflict in Git?
- What to Do When Merge Conflicts Occur
- What are the Types of Merge Conflicts in Git?
- Content Merge Conflict
- Structural Merge Conflict
- How to Resolve Merge Conflicts in Git
- How to Resolve Merge Conflicts in Git with the GitHub interface
- How to Resolve Merge Conflicts in Git with VS Code
- How to Resolve Merge Conflicts in Git with VS Code 3-way Merge Editor
- Wrapping Up
What is a Merge Conflict in Git?
In Git, a merge conflict occurs when you or any of your team members make conflicting changes to the same file from two different branches.
Merge conflicts can also occur even if you’re not working with team members. If you’ve made changes to the same file from different branches and the changes are conflicting, there will be a merge conflict.
On many occasions, Git automatically handles merging for you. But if there are conflicting changes you make to the same file, you have to resolve them manually.
A sample scenario of a merge conflict could look like this:
- you work in branch
mainand you make changes to line 1 of a mytext.txt file, sayHi world. - you switch to branch
new-featureand you make a change to the same line two ofmytext.txt, sayHello earth.
If you attempt to merge the branch new-feature to main, Git won’t be able to automatically decide which one to accept between Hi world and Hello earth. So, Git will raise a merge conflict error and tell you to manually resolve the conflict.
What to Do When Merge Conflicts Occur
It’s not the end of the world if merge conflicts occur while you’re working. Git is just telling you «I want to do this merging for you, but there’s something you have to do for me first».
When these conflicts occur and you attempt to resolve them, Git will automatically annotate the conflicting lines for you with the lesser than symbol (<), equals sign (=), and greater than symbol (>) like this:
<<<<<<<
=======
>>>>>>>
Everything between the lesser than (<) and equals signs (=) is the change in the current branch (the branch you’re merging into). Everything between the equals (=) and greater than (>) signs is the incoming change from the branch you want to merge to another branch.
It is left up to you to remove those annotations and decide how you want the conflicting lines to be – you can accept one of the changes or both of them.
So, instead of looking at merge conflicts as a stumbling block, you can just see them as some annotations you need to remove and things you need to accept or reject.
Keep reading this article so you can see how to resolve a merge conflict. But before that, let’s look at the types of merge conflicts.
What are the Types of Merge Conflicts in Git?
There are two types of merge conflicts. They are content conflict and structural conflict.
Content Merge Conflict
Content conflict occurs when the changes you make in two different branches affect the same lines of code in a file. This results in conflicting changes that cannot be automatically merged by Git.
For example, you make the change display: flex to line 2 in one branch and another change text-align: center to the same line 2 in the same file in another branch.
When this content conflict happens, Git will stop the merging process and prompt you to make adjustments to the code before moving forward.
Structural Merge Conflict
Structural conflict occurs when the changes you make in two different branches affect the same file but do not conflict with each other line-by-line. Instead, the changes affect the structure or organization of the file, such as renaming a variable, or function, or moving a block of code.
If this structural conflict happens, Git won’t be able to determine which of the changes to accept and will prompt you to decide which changes you want.
To show you how to resolve merge conflicts, I have initialized Git in a working directory (folder) by running git init. I have also created a new branch I call new-feature by running git checkout -b new-feature.
Running git branch shows that I have branches main and new-feature:
I have also pushed both branches to GitHub, so I can show you how to resolve merge conflicts on GitHub:
I have added some code to the HTML and JavaScript files in the directory. I triggered some conflicts in the two files and made some commits:
How to Resolve Merge Conflicts in Git with the GitHub Interface
Since I have pushed the new-feature branch to GitHub, GitHub will ask that I create a pull request so the new-feature branch will be merged to main:
In this case, immediately you click on “Compare and Pull”, you will see that there can’t be an automatic merging because there’s a conflict:
This means there’s a conflict you need to resolve. Create the pull request and scroll down to see where you can resolve the conflict, then click the “Resolve conflicts” button:
When you click on “Resolve conflicts”, you will get an editor, and to the right, you will see the list of files that have the conflicts:
In the editor, you will see the lines where the conflicts have occurred:
You can see that the changes in the incoming branch are between the lesser than (<) and equals (=) signs, while the changes in the branch you want to merge into are surrounded by greater than (>) and equals (=) signs.
Choose the line you want, remove the annotations, and click “Mark as resolved” in the top right corner:
Repeat the same process for any other file that have some conflict(s) too.
If you want, you can keep both lines. Just make sure you remove the annotations.
After doing that, click on “Commit merge” in the top right corner:
Now, there should not be a merge conflict any more:
How to Resolve Merge Conflicts in Git with VS Code
Many popular code editors have interfaces for resolving a merge conflict when you try to merge locally.
When you switch to the branch you want to merge into and run git merge branch-to-merge, you will be prompted to resolve some conflicts (if any). If there are conflicts to resolve, the interface looks like this in VS Code:
At this stage, if you’re not ready to resolve the conflicts, you can abort the merging by running git merge --abort.
But if you want to resolve the conflicts, you can either accept the incoming change(s), accept the current change, or accept both changes.
If you select any of the three, the merge conflict(s) will be resolved. After that, add the file and commit it the usual way you would do it:
git add .
git commit -m "<commit message>"
How to Resolve Merge Conflicts in Git with VS Code 3-way Merge Editor
You can also rebase a conflict with the VS Code 3-way merge editor.
After running git merge <branch-to-merge>, click on the “Resolve in Merge Editor” button
You will see you now have 3 views. You will see the changes in the incoming branch on the right, the changes in the branch you want to merge into on the right (the current branch), and the preview below the two:
Now start resolving the conflicts by selecting any of the available options:
- Accept Incoming
- Accept Combination (Incoming First)
- Accept Current
- Accept Combination (Current First)
Incoming is the change in the branch you want to merge into a target branch, and current is the change already in the branch you want to merge into.
Switch to each file, click on the “Resolve in Merge Editor” button, and select any of the options there.
You can also resolve the conflicts by entering the right code in each of the files.
When you are satisfied, click on “Complete Merge” in each merge editor:
You have to add the files again and commit them:
git add .
git commit -m "<commit-message>"
That’s it! If you want the 3-way merge editor to open automatically when you want to merge conflicts, click “Settings” and search for “merge editor”, then checkmark “open the merge editor for files that are currently under conflicts”.
Wrapping Up
As you’ve seen, having a Git conflict is not as scary as it seems, and resolving it is not an impossible task. You can typically resolve it right on GitHub or in your text editor. The VS Code 3-way merge editor is also a convenient way to resolve a merge conflict.
If you’ve created a PR for a large codebase and you have a merge conflict, another way you can resolve it might be rebasing with main by running git pull --rebase upstream main (as the case may be). After that, you will be presented with an interface to resolve the conflicts and the files containing the conflicts. When you are done, run git add ., git rebase --continue, and then force push to your branch.
Learn to code for free. freeCodeCamp’s open source curriculum has helped more than 40,000 people get jobs as developers. Get started
На ранних этапах моей карьеры конфликт слияния был для меня настоящим кошмаром. Думаю, я не одна такая.
Я закончила курсы программирования в 2018 году. В те времена, столкнувшись с проблемой с git, с которой не могла справиться, я просто создавала новый репозиторий и начинала все сначала.
В 2019 году я начала работать в команде с другими разработчиками и уже не могла избегать проблем, просто создавая новые репозитории. Мне пришлось посмотреть в лицо своему страху и научиться разрешать конфликты слияния.
Это было непросто. К счастью, сегодня я чувствую себя более уверенно. Конфликты по-прежнему раздражают, но теперь у меня в арсенале есть пара приемов для исправления ситуации.
Предварительные условия для разрешения конфликта слияния
Дышите глубже
Все в порядке. Конфликты случаются. Вы не первый человек, столкнувшийся с конфликтом слияния. С ними сталкиваются все разработчики, а частота инцидентов зависит от опыта. Конфликты — распространенное явление в контроле версий.
Разберитесь, почему возник конфликт слияния
Системы контроля версий вроде Git автомагически управляют контрибуциями кода. Они идентифицируют изменение, момент его появления, автора и то, в какой строке это изменение появилось. Благодаря этому разработчики могут легко отследить историю своей кодовой базы.
Но иногда Git бывает сбит с толку. Например, в следующих ситуациях:
- Двое или больше людей изменили одну и ту же строку в файле и пытаются смержить изменения в одну и ту же ветку
- Один разработчик удалил файл, а другой его отредактировал, и оба пытаются смержить свои изменения в одну ветку
- Один разработчик удалил строку, а другой ее отредактировал, и оба пытаются смержить свои изменения в одну ветку
- Разработчик взял изменения из одного коммита в ветке и попытался применить их в виде нового коммита (
cherry-pick) - Разработчик сделал rebase ветки: переместил последовательность коммитов в базовый коммит.
Git не уверен, какие изменения нужно применить, поэтому обращается за помощью к разработчику — посылает уведомление о конфликте слияния. Ваша задача — помочь Git определить, какие из изменений наиболее точны.
Разрешение конфликтов слияния
В редакторе кода или IDE
Загляните в логи
Если вы запустили git merge и возник конфликт слияния, ваш терминал или командная строка ответит вам сообщением:
CONFLICT (content): Merge conflict in [filename]
Это сообщение говорит нам, в каком конкретно файле возник конфликт.
Найдите конфликт
Откройте файл, на который указал Git, и прокрутите его, пока не найдете конфликт. Ваша IDE может подсказать вам нужное место при помощи подсветки. В примере ниже показано, как это выглядит в VS Code. Редактор подсвечивает текущее изменение и входящее.
- Текущее изменение (англ. current change) также иногда называют исходящим. Оно представляет изменения в коде, которые вы сделали в вашей локальной ветке.
- Входящее изменение (англ. incoming change) представляет изменения в коде, которые вы вытягиваете (pull) из базовой ветки, или изменения, внесенные другими разработчиками.

Решите, какие изменения нужно применить
То, хотите ли вы принять текущие изменения, входящие изменения или все изменения, зависит от ваших целей. При принятии решения вы ориентируетесь на свое понимание этих изменений.
Если вы не знаете, как поступить, лучше всего посоветоваться с командой или тем разработчиком, который написал входящие изменения.
Вы можете принять изменения, не делая коммит, и локально протестировать программу на работоспособность.
Удалите все длинные последовательности символов ==== , <<<< или >>>>
Эти символы используются для того, чтобы помочь вам определить, где возник конфликт слияния. При принятии выбранных изменений они обычно исчезают, но порой случаются сбои. Проследите за тем, чтобы случайно не включить их в коммит: это может привести к багам в программе.
Что, если я ошибся?
Если вы допустили ошибку или не уверены в том, какие изменения нужно принять, вы можете остановить процесс слияния, запустив следующую команду:
git merge --abort
После этого не надо сидеть и смотреть в пустоту. Обратитесь к коллегам (желательно, к тому, чей код конфликтует с вашим, или к тому, кому больше доверяете). Объясните ситуацию: «Слушай, у меня возник конфликт слияния. Я не знаю, какие изменения принять. У тебя есть пара минут, чтобы мне помочь?»
Если вы уверены, что конфликт разрешен, сделайте коммит изменений
После принятия нужных изменений вы можете сделать коммит. Проделайте следующие шаги:
- Сохраните файлы, в которые были внесены изменения
- Запустите
git statusи проверьте, что изменения коснулись правильных файлов - Добавьте выбранные файлы в стейджинг:
git add [имя файла] - Сделайте коммит изменений:
git commit -m «[ваше сообщение коммита]» - Запустите
git push
На GitHub
Определите, в каких файлах возник конфликт
При открытии пул-реквеста на GitHub в случае конфликта вы получите уведомление об этом. В нем также будет информация о соответствующих файлах.

Найдите конфликт
Чтобы найти конфликты, нажмите «Resolve conflicts». Это приведет вас к нужным файлам.
Определите, какие изменения нужно принять
Веб-интерфейс GitHub подсвечивает конфликтующие изменения желтым цветом и выделяет символами <<<<<, ====, >>>>.
GitHub также указывает на ветки, из которых происходят эти изменения. Это должно помочь вам решить, какие изменения следует принять. Будут это изменения из вашей ветки, из базовой или обеих — зависит от вас. При выборе опирайтесь на свои знания и мнение коллег.

Удалите строки, которые не нужны. Также удалите все лишние последовательности символов ====, <<<< или >>>>.
Когда будете уверены, что конфликт разрешен, сделайте коммит изменений
Удалив конфликтующие изменения и все символы, которые использовались для их выделения, нажмите «Mark as Resolved» и затем «Commit merge».

Больше о конфликтах слияния и их разрешении можно почитать в официальной документации GitHub.
Перевод статьи «How Do I Resolve Merge Conflicts?».





























