git rebase между слияниями приводит к конфликтам в совершенно не связанных файлах
У меня есть большой репозиторий Git, где ошибка была введена несколько месяцев назад, и я хотел бы bisect
это, сначала введя коммит в прошлое хранилища, а затем воспроизведя слияния (делая rebase
на новый коммит), как показано на диаграмме ниже.
Я понимаю, что из-за слияний Git, похоже, работает не так, как ожидалось, но я хотел бы иметь лучшее представление о том, почему это происходит (и если есть параметры, которые могли бы помочь ему работать лучше).
Файл, измененный в
patch
никогда не изменяется снова любым коммитом вmaster
Я старался
rebase
с и без--preserve-merges
и оба потерпели неудачу при некотором слиянии недалеко от фиксацииpatch
Различия, показанные в конфликтах, не имеют смысла, например, некоторые пробелы в файле, несвязанные строки в другом... Есть ли какая-то полезная информация, которую я мог бы извлечь из них, или это просто мусор, и я не должен тратить время на попытки понять это?
Существуют ли другие параметры / стратегии слияния, которые могут помочь в продвижении слияний?
Примечание: в конце концов, моя комбинация git apply; git bisect
работал, но я все еще заинтересован в альтернативных решениях или объяснениях поведения Git.
1 ответ
Основная проблема проста в описании, но ее трудно решить.
Вы на кончике master
, Эти графы коммитов хороши, но их сложно ввести:-), поэтому давайте воспользуемся очень упрощенным ASCII здесь. (Это просто переформулировка проблемы, упрощенная и с разными ярлыками.)
D
/ \
A - B ----- C F - G <-- master
\ /
E
Здесь ошибка вводится при коммите B
и, следовательно, присутствует в каждом дочернем коммите (C
через G
). Чтобы исправить это, мы хотим создать новую последовательность коммитов (я буду использовать строчные буквы вместо C'
, D'
и т. д.) с исправлением x
установлен как новый коммит:
d
/ \
A - B - x - c f - g <-- [anonymous; will become master]
\ /
e
Чтобы сделать это, git (git rebase -p
в этом случае - обратите внимание, что типичная перебазировка удаляет коммиты слияния!) должны делать новые коммиты на основе существующих коммитов. Для не слияний, таких как новый коммит c
, это не слишком сложно: rebase получает разность C
против B
а затем применяет этот патч к дереву исходного кода, прикрепленному для фиксации x
, То есть ребаз делает git cherry-pick master~3
пока на новой анонимной ветке. Пока проблем нет! Теперь rebase должен создать коммит d
, что тоже не проблема, просто нужно сделать git cherry-pick master~2
, что он делает, давая:
d
/
A - B - x - c
Изготовление e
немного сложнее, потому что его родительский коммит должен быть c
, В принципе ребазать листья d
висит, перематывает на c
и делает git cherry-pick master~1^2
копировать E
, (Я думаю, что скрипт rebase на самом деле git commit-tree
операции, чтобы не нужно было перематывать анонимную ветку, но это работает так же, просто здесь присутствует некоторая внутренняя волосатость.) Это дает нам:
d
/
A - B - x - c
\
e
Здесь мы сталкиваемся с проблемой: как перебазировать, создать коммит слияния f
? Он не может сравнить дерево для коммита F
против этого для D
Это только половина слияния. Это не может сравниться F
против E
либо: это просто вторая половина слияния. Так что это не может использовать git cherry-pick
поскольку это требует выбора одной или другой стороны исходного слияния.
Решение, которое использует rebase - запустить git merge
(точнее внутренние биты, которые git merge
работает). Таким образом, любые изменения, внесенные x
которые продолжаются через новый d
а также e
также обрабатываются правильно.
(Это создает совершенно другую проблему: если F
является "злым слиянием", т. е. слиянием, изменяющим вещи, которые не были изменены ни в одном из родителей, воспроизводимым деревом для слияния f
не будет совпадать с деревом для F
Так как git никогда не увидит изменения "злого слияния". Нужен вариант cherry-pick
он может сравнивать дерево коммитов слияния со всеми его родителями - как комбинированные различия, кроме более полного, чем стандартный комбинированный дифференциал git - и применять этот дифференциал ко всем входным деревьям. Но нет такого осьминога-вишни [git calamari-pick
?], так что это просто недоступно.)
Во всяком случае, этот тип перебазирования использует слияние, и здесь вы видите тот же конфликт слияния, который, предположительно, произошел во время первоначального слияния. Если вы просто решите это снова таким же образом - используя git rerere
например - это позволит вам продолжить. К сожалению, нет возможности включить задним числом git rerere
(хотя я думаю, что достаточно легко написать такую вещь).