Ответ 1
Причина в том, что git merge
использует по умолчанию стратегию, называемую рекурсивной. Он способен выполнять трехстороннее слияние: с помощью патча с одной стороны и применения этого патча к другой стороне для создания нового файла. Он также делает это рекурсивно, в более сложных ситуациях, когда происходит много разветвлений и слияний, и существует несколько оснований слияния для объединения двух коммитов.
Недавно я столкнулся с такой же ситуацией:
$ git merge-base --all 90d64557 05b3dd8f
711d1ad9772e42d64e5ecd592bee95fd63590b86
f303c59666877696feef62844cfbb7960d464fc1
$
С двумя основаниями слияния невозможно трехстороннее слияние. Поэтому "рекурсивная" стратегия разрешила это, сначала перейдя в слияние этих двух коммитов:
$ git merge-base 711d1ad9 f303c596
3f5db59435ffa4a453e5e82348f478547440e7eb
$
ОК, только одна база слияния, поэтому может начаться трехстороннее слияние. какие изменения с обеих сторон?
$ git diff --stat 3f5db594 711d1ad9
normalize/coll.py | 116 ++++++++++++++++++++++++++++++++++++++-----------
normalize/visitor.py | 49 ++++++++++-----------
tests/test_property.py | 10 +++--
3 files changed, 120 insertions(+), 55 deletions(-)
$ git diff --stat 3f5db594 f303c596
normalize/identity.py | 38 +++++++++++++++++++++++++++++++-------
tests/test_property.py | 2 ++
2 files changed, 33 insertions(+), 7 deletions(-)
$
Эти два различия также вносили изменения в один и тот же файл, поэтому их нельзя разрешить с помощью простой стратегии просто взять новую версию каждого файла с каждой стороны (слияние индексов). Вместо этого git берет новый файл с одной стороны и пытается применить патч с другой стороны к нему. Результатом является комбинированный коммит, который можно моделировать следующим образом:
$ git checkout -b tmp
Switched to a new branch 'tmp'
$ git reset --hard f303c59666877696feef62844cfbb7960d464fc1
HEAD is now at f303c59 record_id: throw a more helpful error if record identity is not hashable
$ git merge 711d1ad9772e42d64e5ecd592bee95fd63590b86
Auto-merging tests/test_property.py
Merge made by the 'recursive' strategy.
normalize/coll.py | 116 ++++++++++++++++++++++++++++++++++++++-----------
normalize/visitor.py | 49 ++++++++++-----------
tests/test_property.py | 10 +++--
3 files changed, 120 insertions(+), 55 deletions(-)
$ git diff --stat 3f5db594 HEAD tests/test_property.py
tests/test_property.py | 12 +++++++++---
1 file changed, 9 insertions(+), 3 deletions(-)
$
Затем он возвращается к исходному 3-стороннему слиянию, используя этот результат слияния как отправную точку; и это также включает изменения в один и тот же файл:
$ git diff --stat HEAD 90d64557| grep selector
normalize/selector.py | 17 +--
tests/test_selector.py | 19 ++--
$ git diff --stat HEAD 05b3dd8f| grep selector
normalize/selector.py | 29 +++++++++++++++++------------
tests/test_selector.py | 9 +++++++++
$
Однако снова изменения относятся к разным разделам файла, и поэтому использование diff и применение его к другой стороне успешно.
Таким образом, C git смог разрешить это слияние, сначала выполнив трехстороннее слияние на двух основаниях слияния двух исходных точек, а затем выполнив другое трехстороннее слияние на двух исходных коммитах, объединенных и промежуточный результат первого слияния.
Автоматическое разрешение Github этого не делает. Это не обязательно неспособно, и я не уверен, какая часть рекурсивной стратегии она реализует, но это заблуждение на стороне осторожности, и это именно то, что вы ожидаете от этой большой зеленой кнопки: -).