git rebase между слияниями приводит к конфликтам в совершенно не связанных файлах

У меня есть большой репозиторий Git, где ошибка была введена несколько месяцев назад, и я хотел бы bisect это, сначала введя коммит в прошлое хранилища, а затем воспроизведя слияния (делая rebase на новый коммит), как показано на диаграмме ниже.

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

  • Файл, измененный в patch никогда не изменяется снова любым коммитом в master

  • Я старался rebase с и без --preserve-mergesи оба потерпели неудачу при некотором слиянии недалеко от фиксации patch

  • Различия, показанные в конфликтах, не имеют смысла, например, некоторые пробелы в файле, несвязанные строки в другом... Есть ли какая-то полезная информация, которую я мог бы извлечь из них, или это просто мусор, и я не должен тратить время на попытки понять это?

  • Существуют ли другие параметры / стратегии слияния, которые могут помочь в продвижении слияний?

Примечание: в конце концов, моя комбинация git apply; git bisect работал, но я все еще заинтересован в альтернативных решениях или объяснениях поведения Git.

Краткое описание моих проблем с git-rebase

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 (хотя я думаю, что достаточно легко написать такую ​​вещь).

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