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

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

Трёхпанельная раскладка показывает обе версии и общего предка

Когда git вызывает редактор как инструмент слияния, конфликтный файл раскладывается по нескольким панелям. Классическая раскладка трёхпанельная сверху и итоговый файл снизу. Левая панель содержит локальную версию - ту, что в текущей ветке. Правая - удалённую, из сливаемой ветки. Посередине нередко показывают общего предка, базовую версию, от которой обе разошлись. А отдельное окно с итоговым файлом - то, что в конце попадёт в дерево, с маркерами конфликта внутри.

Запускается всё штатным механизмом git для внешних инструментов слияния. Сначала редактор настраивают как инструмент слияния в конфигурации git, затем при возникшем конфликте вызывают слияние.

git config --global merge.tool vimdiff
git mergetool      # откроет конфликтный файл в редакторе

Эти три источника - локальная версия, удалённая и базовая - дают полную картину конфликта. Видно, что было в общем предке, как разошлись стороны, и куда складывать итог. Цель работы - наполнить итоговое окно правильным содержимым, перенося в него нужные куски из боковых панелей.

Команды переноса забирают и отдают изменения между панелями

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

:diffget LOCAL     забрать версию из локальной панели
:diffget REMOTE    забрать версию из удалённой панели
:diffget BASE      забрать версию из базовой панели

Логика проста: курсор стоит в итоговом окне на конфликтном куске, и команда забора с указанием панели вставляет в этот кусок выбранную версию. Имена панелей соответствуют сторонам конфликта - локальная, удалённая, базовая. Удобно, что имена допускают сокращение: достаточно нескольких первых букв, чтобы редактор понял, какая панель имеется в виду, что экономит набор при частом использовании.

Для двухпанельного сравнения есть короткие команды из двух букв - забора и отдачи, без указания имени, потому что вторая панель там всего одна и однозначна. Но в трёхпанельной раскладке источников несколько, поэтому панель приходится называть, и короткие двухбуквенные формы там не работают по назначению.

Навигация по конфликтам прыгает между расхождениями

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

]c     перейти к следующему расхождению
[c     перейти к предыдущему расхождению

Это превращает обход конфликтов в последовательное движение: прыгнул к первому расхождению, разрешил его командой забора, прыгнул к следующему. Так файл проходится от начала до конца без прокрутки и поиска вручную. В большом файле с десятком конфликтов это заметно ускоряет работу - курсор сам встаёт на каждый спорный кусок по очереди.

Удобные сочетания превращают разрешение в пару нажатий

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

" разрешение конфликтов: забрать локальную или удалённую версию
nnoremap <localleader>1 :diffget LOCAL<CR>
nnoremap <localleader>2 :diffget BASE<CR>
nnoremap <localleader>3 :diffget REMOTE<CR>

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

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

Завершение слияния и переход к следующему файлу

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

:wqa     сохранить итог и закрыть все панели

После записи и выхода git подхватывает разрешённый файл и, если конфликтных файлов было несколько, сразу открывает следующий. Так слияние проходится файл за файлом, пока не разрешены все конфликты. Промежуточные файлы в верхнем ряду временны, и случайно испортить дерево, сохранив их, нельзя.

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

:cq      выйти с ошибкой, не сохраняя - git поймёт, что файл не разрешён

Трёхпанельный режим сложен, и есть путь проще

Стоит честно сказать: трёхпанельное сравнение - не самое простое для восприятия. Глазу тяжело уследить за различиями между тремя версиями сразу, особенно когда отличия не рядом, а разбросаны. Стандартные команды переноса между тремя окнами тоже не самые удобные. Поэтому существует подход, сводящий конфликт к двухпанельному сравнению.

Идея в том, что сами маркеры конфликта - это, по сути, и есть двухпанельное сравнение двух версий. Двухпанельный вид нагляднее: он подсвечивает лишь относящиеся к делу различия, и решение становится яснее. Базовая версия и история каждой стороны полезны как справка о намерениях, но для самого разрешения двух панелей часто достаточно. Существуют дополнения, превращающие конфликтный файл в чистое двухпанельное сравнение, что многие находят проще трёхпанельной раскладки по умолчанию.

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

Что складывается в рабочий процесс

Картина выстраивается ясно. Режим сравнения раскладывает конфликтный файл по панелям - локальная версия, удалённая, общий предок и итоговое окно. Запускается всё штатным механизмом git для инструментов слияния. Разрешение идёт переносом изменений: стоя в итоговом окне, командой забора подтягивают нужную версию из боковой панели по имени, а имена допускают сокращение.

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

Главная мысль: разрешение конфликта в редакторе - это не правка маркеров вручную, а перенос готовых кусков между панелями. Трёхпанельная раскладка даёт полную картину, но тяжела для глаза, и многие предпочитают свести конфликт к двухпанельному сравнению, где различия нагляднее, а команды короче. Стоит освоить команду забора, навигацию по расхождениям и пару удобных сочетаний, и слияние с конфликтами перестаёт быть мучением - изменения перетекают между версиями по нажатию, а итог собирается из нужных кусков обеих сторон.