Git Merge ошибочно определяет конфликты в блоках

В моем репозитории есть один файл 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. Может ли это помочь?

paulinho 21.12.2020 16:44

В то время как алгоритм сравнения определяет диапазон каждого изменения, именно стратегия слияния выбирает способ объединения этих диапазонов. Теоретически возможно написать новую стратегию слияния, но это очень сложно: Git получает новую стратегию слияния сейчас (возможно, в этом или следующем году) впервые почти за 20 лет. Также можно написать драйвер слияния для использования с существующей стратегией слияния по умолчанию, что намного более реалистично. Это был бы путь для вашего конкретного случая.

torek 21.12.2020 16:57

@paulinho Интересно, новая стратегия слияния ORT Git 2.30 Q1 2021 изменит что-нибудь здесь («Якобы двойник Recursive»).

VonC 21.12.2020 16:58
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
4
462
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Конфликт слияния всегда возникает, когда вы объединяете две ветки, которые изменили один и тот же файл. В этом примере у вас возник конфликт слияния, потому что ветвь A изменила data.csv, а ветвь B также изменила data.csv. Чтобы разрешить этот конфликт, вы должны решить, какие строки вы хотите сохранить, а какие удалить между <<<<<<< HEAD и >>>>>>> A. Кроме того, вы должны удалить <<<<< << ГОЛОВА, ======= и >>>>>>> А.
После этого запустите команду git add data.csv, чтобы разрешить конфликт, а затем запустите git commit, чтобы завершить слияние.

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

paulinho 21.12.2020 16:54
Ответ принят как подходящий

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

Если вы не определяете драйвер слияния самостоятельно, Git использует собственный встроенный драйвер. Этот встроенный в основном идентичен команде git merge-file. (Он может быть точно таким же, поскольку они созданы из различных общих исходных файлов в Git. Обратите внимание, что встроенный «низкоуровневый» драйвер слияния в ll-merge.c — это то место, где можно выбрать запуск настроенного драйвера слияния или использование встроенного в код, на самом деле происходит.)

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

  • имя пути, по которому драйвер может найти базовую версию файла слияния;
  • имя пути, по которому драйвер может найти текущую (--ours) версию файла и в которое драйвер должен записать окончательную объединенную версию файла; и
  • путь, по которому драйвер может найти другую (--theirs) версию файла.

Задача драйвера состоит в том, чтобы прочитать три входные версии, как он выберет, а затем записать правильный результат слияния, полученный так, как ему нравится, в среднее из этих трех имен пути. Имена путей будут именами временных файлов: не думайте, что любое из этих трех имен файлов имеет какой-либо смысл или имеет какое-либо отношение к историческим именам объединяемых файлов.

Дополнительные данные, которые вы можете передать своей собственной программе, включают желаемый пользователем размер маркера конфликта (по умолчанию 7) и имя пути, в которое в конечном итоге будет скопирован результат слияния. То есть, предположим, мы объединяем файл, имя которого в базе слияния — orig.wrongsuffix, имя в коммите --oursours.csv, а имя в коммите --theirsrenamed-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 и повторно запустите все слияния за последние пятнадцать лет с правилом автоматического слияния, примыкающим к изменениям, вы обнаружите, что это дает очень плохие результаты в много случаев. Ни одно правило не может быть идеальным, вы должны где-то провести черту, перекрытие или примыкание — это то, что поднимает минимум ненужной суеты, достаточно близко к тому, чтобы никогда не совершать достойных порицания ошибок на практике.

Другие вопросы по теме