Когда именно возникает конфликт мерзкого слияния

Я использую git для отслеживания изменений в моих документах LaTeX. Я склонен хранить отзывы от соавторов в отдельной ветке и объединить их позже. Пока что все кажется волшебным образом сливающимся должным образом, но я хотел бы знать, когда именно возникает конфликт слияния, чтобы я мог получить реальное доверие к процессу слияния (я не хотел бы, чтобы текст получался в шутку, конечно).

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

1 ответ

Решение

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

Возможно, вы захотите сначала просмотреть этот ответ на связанный вопрос, для справки. Теперь я буду предполагать, что у нас есть base в качестве (единой) базы слияния (возможно, мы устанавливаем имя base помечая конкретный коммит, например, git tag base $(git merge-base HEAD other)) а также HEAD как наш коммит, с другим именем ветви other называя другой коммит.

Далее мы смотрим на две разницы:

git diff base HEAD
git diff base <other>

Если мы видим, что все три версии файла F различны (так что F появляется в обоих выходных данных, а изменения различаются), то мы должны, по сути, работать с diff-hunk-by-diff-hunk. В тех случаях, когда различные различия перекрывают друг друга, но вносят различные изменения, Git объявляет конфликт. Но - кажется, это ваш вопрос - что именно означает "делать разные изменения" ?

Я думаю, что это лучше всего показывает пример. Например:

$ git diff base HEAD
diff --git a/basefile b/basefile
index df781c1..e4f9e4b 100644
--- a/basefile
+++ b/basefile
@@ -4,6 +4,7 @@
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
+# added line in b1
 # 1. Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright

а также:

$ git diff base b2
diff --git a/basefile b/basefile
index df781c1..c96620e 100644
--- a/basefile
+++ b/basefile
@@ -4,7 +4,6 @@
 # Redistribution and use in source and binary forms, with or without
 # modification, are permitted provided that the following conditions
 # are met:
-# 1. Redistributions of source code must retain the above copyright
 #    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the

Обратите внимание, что хотя эти изменения не затрагивают одну и ту же строку, в некотором смысле они также затрагивают одну и ту же линию. Я добавил строку 7 (подталкивая старую строку 7 к строке 8) и удалил старую строку 7. Это, по-видимому, "та же самая" строка. Так:

$ git merge b2
Auto-merging basefile
CONFLICT (content): Merge conflict in basefile
Automatic merge failed; fix conflicts and then commit the result.

Давайте прервем это слияние и рассмотрим верхушку ветви b3 вместо этого (база слияния b1 а также b3 такие же, как база слияния b1 а также b2 в моей настройке).

$ git merge --abort
$ git diff base b3
diff --git a/basefile b/basefile
index df781c1..e2b8567 100644
--- a/basefile
+++ b/basefile
@@ -5,7 +5,6 @@
 # modification, are permitted provided that the following conditions
 # are met:
 # 1. Redistributions of source code must retain the above copyright
-#    notice, this list of conditions and the following disclaimer.
 # 2. Redistributions in binary form must reproduce the above copyright
 #    notice, this list of conditions and the following disclaimer in the
 #    documentation and/or other materials provided with the distribution.
 $ git merge --no-edit b3
Auto-merging basefile
Merge made by the 'recursive' strategy.
 basefile | 1 -
 1 file changed, 1 deletion(-)

На этот раз не было никакого конфликта, даже при том, что оба куска различий касались той же самой общей области. Второй diff удалил строку, которая не "касалась" добавленной строки, поэтому Git посчитал это безопасным.

Если вы поэкспериментируете больше, таким же образом, вы обнаружите, какие именно, казалось бы, перекрывающиеся изменения успешно объединены, а какие приводят к конфликту. Очевидно, что изменения, которые непосредственно перекрываются, например, когда оба удаляют исходную строку 42 и вставляют другую новую строку 42, будут конфликтовать. Но все изменения всегда представлены как "удалить некоторые существующие строки, хотя, возможно, их ноль", за которыми следует "добавить несколько новых строк, хотя, возможно, их ноль". Изменение - даже то, которое изменяет, добавляет или удаляет только одно слово в строке - удаляет ненулевое число существующих строк и добавляет ненулевое число новых строк. Чистое удаление (из одной или нескольких полных строк) добавляет ноль строк, а чистое удаление удаляет ноль строк. В конце концов, все сводится к следующему: "Касались ли наши и их изменения один и тот же номер строки?" Контекст становится почти неактуальным, за исключением того, что при удалении нулевых строк или вставке нулевых строк сам контекст в некотором смысле "является" строками. (Я не уверен, какой смысл имеет это утверждение, поэтому, если оно непонятно, это моя вина.;-))

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

Трехстороннее слияние - это не патч

Обратите внимание, что это отличается от применения патча, который выполняется без общей базовой версии для запуска. В случае патча контекст используется гораздо интенсивнее: заголовок diff hunk предоставляет место для поиска контекста, но поскольку он может применяться к другой версии файла, контекст позволяет нам (и Git) сделать то же самое изменение в другой строке, пока контекст все еще совпадает.

patch Утилита использует здесь другой алгоритм (фактор "максимального размытия", смотря на +/- столько строк). Git не делает нечетких факторов; он будет искать весь путь до начала или конца файла, если это необходимо. Тем не менее, он имеет обычную опцию настройки пробела, прежде чем решить, что контекст не соответствует.

(Когда используешь git apply чтобы применить патч, вы можете добавить -3 или же --3way чтобы позволить Git прочитать index строки, которые предоставляют частичные или полные хэш-идентификаторы файловых объектов. Левый хэш-идентификатор - это хэш предыдущей версии файла: обратите внимание, что во всех приведенных выше различиях "базовая" версия basefile имеет идентификатор df781c1, Если Git может найти уникальный блоб по этому идентификатору, он может притвориться, что это база слияния, и сравнить только одну базу слияния с HEAD, обработав сам патч как другой diff, и сделайте тройное слияние таким образом. Это иногда позволяет git apply где преуспеть patch потерпит неудачу.)

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