В моем репозитории есть один файл data.csv
, представляющий базу данных в формате CSV. Для примера предположим, что содержимое data.csv
1,2,3
2,3,4
4,5,6
Изначально у меня есть только главная ветка, и я создаю две ветки A и B, где я изменяю data.csv
независимо друг от друга. Я заметил, что иногда алгоритм 3-way diff выявляет конфликты, которые, на мой взгляд, вообще не должны быть конфликтами. Например, если A изменяет файл, чтобы
1,4,5
2,3,4
4,5,6
и B изменяет файл, чтобы он был
1,2,3
2,6,7
4,5,6
Когда я выдаю git merge A
из ветки B, вместо автоматического слияния этих версий он фактически сообщает о следующем конфликте:
<<<<<<< HEAD
1,2,3
2,6,7
=======
1,4,5
2,3,4
>>>>>>> A
4,5,6
Но мне кажется, что на самом деле эти версии должны автоматически объединяться с логикой 3-way diff на построчном уровне, поскольку A изменяет только первую строку, а B только изменяет вторую.
Мои вопросы: Почему это происходит? И есть ли способ заставить Git выполнять более мелкозернистый diff (например, построчный)? (Или, в качестве альтернативы, есть ли способы заставить Git понять, что эти изменения на самом деле можно объединять автоматически?)
@matt Спасибо за ссылку. Это отвечает на мой вопрос, почему это происходит, но мне интересно, есть ли способ заставить git изменить свой алгоритм для построчной проверки этих изменений. Например, я заметил, что можно указать параметр diff-algorithm
для git merge
. Может ли это помочь?
В то время как алгоритм сравнения определяет диапазон каждого изменения, именно стратегия слияния выбирает способ объединения этих диапазонов. Теоретически возможно написать новую стратегию слияния, но это очень сложно: Git получает новую стратегию слияния сейчас (возможно, в этом или следующем году) впервые почти за 20 лет. Также можно написать драйвер слияния для использования с существующей стратегией слияния по умолчанию, что намного более реалистично. Это был бы путь для вашего конкретного случая.
@paulinho Интересно, новая стратегия слияния ORT Git 2.30 Q1 2021 изменит что-нибудь здесь («Якобы двойник Recursive»).
Конфликт слияния всегда возникает, когда вы объединяете две ветки, которые изменили один и тот же файл. В этом примере у вас возник конфликт слияния, потому что ветвь A изменила data.csv, а ветвь B также изменила data.csv. Чтобы разрешить этот конфликт, вы должны решить, какие строки вы хотите сохранить, а какие удалить между <<<<<<< HEAD и >>>>>>> A. Кроме того, вы должны удалить <<<<< << ГОЛОВА, ======= и >>>>>>> А.
После этого запустите команду git add data.csv, чтобы разрешить конфликт, а затем запустите git commit, чтобы завершить слияние.
Привет @MohitNatani, насколько я понимаю, изменение одного и того же файла не всегда вызывает конфликт слияния. Файлы должны быть изменены в одной и той же строке по-разному. Посмотрите мой комментарий к исходному сообщению, чтобы узнать, что мне все еще интересно, и если вы знаете ответ на эти вопросы, я был бы признателен, если бы вы обновили свой ответ!
Как я упомянул в комментарии, сегодня вы могли бы справиться с этим, написав драйвер слияния. Написать хороший драйвер слияния непросто, но вы сможете поэкспериментировать с ним и применить его только к определенным файлам.
Если вы не определяете драйвер слияния самостоятельно, Git использует собственный встроенный драйвер. Этот встроенный в основном идентичен команде git merge-file. (Он может быть точно таким же, поскольку они созданы из различных общих исходных файлов в Git. Обратите внимание, что встроенный «низкоуровневый» драйвер слияния в ll-merge.c
— это то место, где можно выбрать запуск настроенного драйвера слияния или использование встроенного в код, на самом деле происходит.)
Обратите внимание, что вашему драйверу слияния требуется как минимум три входа (вы можете дать ему до пяти входов):
--ours
) версию файла и в которое драйвер должен записать окончательную объединенную версию файла; и--theirs
) версию файла.Задача драйвера состоит в том, чтобы прочитать три входные версии, как он выберет, а затем записать правильный результат слияния, полученный так, как ему нравится, в среднее из этих трех имен пути. Имена путей будут именами временных файлов: не думайте, что любое из этих трех имен файлов имеет какой-либо смысл или имеет какое-либо отношение к историческим именам объединяемых файлов.
Дополнительные данные, которые вы можете передать своей собственной программе, включают желаемый пользователем размер маркера конфликта (по умолчанию 7) и имя пути, в которое в конечном итоге будет скопирован результат слияния. То есть, предположим, мы объединяем файл, имя которого в базе слияния — orig.wrongsuffix
, имя в коммите --ours
— ours.csv
, а имя в коммите --theirs
— renamed-wrongly.csv
. Три входных файла, скорее всего, будут иметь имена файлов в форме .git-tmp-1234567
или подобные. Учитывая существующие стратегии recursive
или resolve
, выходные данные драйвера в конечном итоге попадут в файл с именем ours.csv
, хотя из-за конфликта переименования/переименования (мы исправили имя, и они пытались исправить имя), слияние остановится с конфликт, даже если наш драйвер слияния сможет создать результат слияния.
Чтобы указать на успешное слияние, т. е. на то, что слияние не должно останавливаться из-за конфликтов, обнаруженных вашим собственным драйвером слияния, ваш драйвер слияния должен возвращать статус успешного завершения после завершения. Другими словами, из кода C вызовите exit(0)
; из Python используйте sys.exit(0)
или эквивалент; из Go используйте os.Exit(0)
; и так далее. Чтобы указать, что, несмотря на все усилия вашего драйвера, ваш код не смог создать правильный результат слияния и, следовательно, мог оставить или не оставить маркеры конфликта слияния в своем выходном файле, укажите ненулевое состояние выхода (предпочтительно небольшое ненулевое значение, такое как 1; есть несколько специальных значений около 125-127 для использования в таких вещах, как git bisect
, которые могут быть специально обработаны и в других частях Git; по традиционным причинам программирования Unix значения не должны превышать 127).
Чтобы указать Git использовать ваш драйвер слияния, вам нужно сделать две вещи:
.git/config
или $HOME/.gitconfig
или другую запись, которая определяет драйвер, сообщая Git, как его запустить;.gitattributes
(сначала создав файл, если необходимо), сообщающую Git, например, использовать ваш драйвер для этого конкретного .csv
файла.Инструкции по их определению находятся в документации gitattributes.
Правило перекрытия или примыкания существует не просто так. Вы можете найти случаи, когда это не нужно, но, ура, для dvcs, если вы вытащите, скажем, историю linux и повторно запустите все слияния за последние пятнадцать лет с правилом автоматического слияния, примыкающим к изменениям, вы обнаружите, что это дает очень плохие результаты в много случаев. Ни одно правило не может быть идеальным, вы должны где-то провести черту, перекрытие или примыкание — это то, что поднимает минимум ненужной суеты, достаточно близко к тому, чтобы никогда не совершать достойных порицания ошибок на практике.
Отвечает ли это на ваш вопрос? Почему изменение соседних строк, но независимое изменение вызывает конфликт слияния git?