Жаргон ответ
Это происходит, когда существует несколько баз слияния и слияние баз слияния вызывает конфликт слияния. Кандидаты базы слияния - это LCA коммитов, которые вы выбираете для слияния, в подграфе, вызванном коммитами:
ОПРЕДЕЛЕНИЕ 3.1. Пусть G = (V; E) DAG и пусть x; y ∈ V . Пусть G x; y будет подграфом G , индуцированным множеством всех общих предков x и y . Определите SLCA (x; y) как набор узлов (листков) с нулевой степенью выхода в G x; у . Самые низкие общие предки x и y являются элементами SLCA (x; y) .
(см. https://www3.cs.stonybrook.edu/~bender/pub/JALG05-daglca.pdf). Здесь G - это ориентированный ациклический граф, сформированный коммитами в вашем хранилище, а x и y - это два коммита, которые вы выбираете для слияния. Я на самом деле предпочитаю определение 3.2, хотя оно использует posets , и может быть даже более жаргоном: оно кажется более подходящим (и фактически используется для генеалогии, это то, что делает Git).
Рекурсивная стратегия слияния , -s recursive
, использует все базы слияния, объединяет каждую базу слияния и фиксирует результат вместе с конфликтами слияния и использует этот временный коммит в качестве новой базы слияния. Так что это ответ на первую часть вопроса («Что может привести к тому, что у базового файла ... возникнут конфликты слияния»).
Чтобы избежать этого, у вас есть несколько вариантов, но давайте сначала опишем проблему более понятно.
Длинный, но более полезный ответ
Вы упоминаете, что использовали git merge-base
. По умолчанию git merge-base
выбирает один из кандидатов на фиксацию на основе наилучшего слияния и печатает этот хэш-идентификатор. Если вы запустите git merge-base --all
в двух коммитах с ветвями, вы увидите, что есть несколько кандидатов на лучшее слияние с базой.
В типичном простом шаблоне ветвления и слияния мы имеем:
o--o--o <-- branch-A
/
...--o--o--*
\
o--o--o <-- branch-B
Общая база слияния - как указано в 3.1 или 3.2 в цитируемой статье; с 3.2 вы можете просто вернуться к двум подсказкам веток, пока не найдете коммит, который находится в обеих ветвях - это, конечно, коммит *
, а Git просто нужно diff *
против двух коммитов подсказок ветки-A и ветки -B соответственно.
Не все графики такие аккуратные и простые. Самый простой способ получить две базы слияния - это перекрестное слияние в цепочке истории, как показано здесь:
...--o--o--*---o--o--o <-- branch-C
\ /
X
/ \
...--o--o--*---o--o--o <-- branch-D
Обратите внимание, что обе помеченные коммиты находятся на обеих ветвях , а обе коммиты одинаково близки к двум подсказкам ветвей . Запуск git merge-base --all branch-C branch-D
напечатает два идентификатора хеша. Какой коммит должен использовать Git в качестве базы слияния?
Git ответ по умолчанию: Давайте использовать их все! Git будет работать, в действительности:
git merge <hash-of-first-base> <hash-of-second-base>
как рекурсивное (внутреннее) слияние. Это слияние может иметь конфликты!
Если слияние вызывает конфликты, Git не останавливается и получает помощь от вас, пользователя. Это просто передает конфликтующий результат. Это становится входом для внешнего git merge
, который вы прямо запрашиваете:
...--o--o--*---o--o--o <-- branch-C
\ /
M <-- temporarily-committed merge result
/ \
...--o--o--*---o--o--o <-- branch-D
Временная фиксация на самом деле не в графе, но для целей вашего внешнего слияния она может быть и так.
Как избежать проблемы
Теперь, когда мы видим, как возникает проблема, способы ее устранения понятнее - не совсем ясно , но ясно er , чем они были, по крайней мере:
Метод 1: избегать перекрестных слияний.
Избегая всего, что создает несколько баз слияний, вы никогда не попадете в ситуацию с самого начала. Это перекрестное слияние было создано в какой-то момент наличием branch-C
и branch-D
в этой ситуации:
o--o--o <-- branch-C
/
...--o
\
o--o--o <-- branch-D
Кто-то, скажем, человек С, побежал git checkout branch-C; git merge branch-D
, чтобы получить:
o--o--o---M1 <-- branch-C
/ /
...--o /
\ /
o--o--o <-- branch-D
Тогда некоторыеодин - вероятно, кто-то другой, у которого не было нового коммита их branch-C
; давайте назовем этого человека D - побежал git checkout branch-D; git merge <commit that was the previous tip of branch-C before the new merge was just added>
, чтобы получить:
o--o--o <-- branch-C
/ \
...--o \
\ \
o--o--o---M2 <-- branch-D
Как только люди C и D поделились своими новыми коммитами, результат был:
o--o--o---M1 <-- branch-C
/ \ /
...--o X
\ / \
o--o--o---M2 <-- branch-D
и бомба замедленного действия была установлена. Он не работал до тех пор, пока вы позже не попытались объединить коммиты по линии, но это то, что его установило.
Метод 2: если вы делаете такие слияния, будьте осторожны с ними . Редактировать: это на самом деле не поможет: Git все еще используя коммиты, которые приходят до коммитов M1
и M2
, и повторно объединяют их. Тем не менее, стоит немного подумать. Я оставлю часть этого текста в.
Слияния в основном симметричны. Обратите внимание, что когда люди С и D запускали свои две команды git merge
, они имели одинаковые коммиты , образующие тот же граф . Они начались с той же базы слияния , с теми же двумя коммитами с ветвлением. Два сравнения, полученные путем сравнения этой базы слияния с каждым кончиком ветви, были одинаковыми.
Таким образом, лица С и D должны были разрешить какой-то конфликт, тот же, который вы видели в своей собственной базе слияния-слияния. Возможно, они сделали это автоматически: например, человек C мог запустить git merge -X ours
, чтобы предпочесть его изменение коммита tip, для найденного вами конфликтного файла в M1
, тогда как у человека D может быть также запустить git merge -X ours
, чтобы предпочесть ее изменение коммита чаевых в M2
.
Эти асимметричные результаты были безвредны, за исключением самой бомбы замедленного действия и того факта, что вы позже выполнили рекурсивное слияние. Вы видите конфликт. Вам решать это - опять же - но на этот раз не наш простой / их трюк, если это сработало для людей С и D.
Метод 3 (простой, но, возможно, неправильный): используйте другую стратегию слияния.
Рекурсивная стратегия слияния, -s recursive
, - это та, которая берет этот граф, находит все кандидатов на базовую фиксацию слияния, а если их несколько, объединяет их и использует результат в качестве ввод для вашего слияния. Существует стратегия под названием resol (-s resolve
), которая разделяет почти весь свой код со стратегией recursive . Разница в том, что когда (или после) вычисления всех баз слияний требуется всего лишь одна из нескольких баз.
В этом случае, с двумя кандидатами на слияние, M1 и M2, -s resolve
просто выберет M1 или M2, по-видимому, случайным образом (не на самом деле случайным образом, просто в зависимости от того, какой из них появится первым, но не контролируемым образом: очевидная причина выбора одного против другого). Git будет использовать этот коммит в качестве базы слияния.
Проблема здесь очевидна: при использовании одного коммита - человека С или человека D - вы игнорируете разрешение конфликта, выбранное другим человеком. Эти два человека оставили вам бомбу замедленного действия, и ваш ответ с -s resolve
- позволил бомбе уничтожить один из двух результатов, и я не посмотрел, кто был действительно прав, или стоит ли вместо этого что-то сделать лучше .
В любом случае не существует единственного правильного ответа, который совпадает с любым конфликтом слияния. Проблема в том, что вы сейчас разрешаете конфликт, который, вероятно, должен быть разрешен в какой-то момент в прошлом, когда были сделаны эти два конфликтующих слияния.