Git rebase --preserve-merges терпит неудачу

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

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

A---B--
\      \
 ---C---D
 \
  ---E

Где master относится к B, ветвь относится к D, а до свидания ветвь относится к E.

git init
echo Hello > Hello.txt
git add Hello.txt
git commit -m "Create Hello.txt (commit A)"
git tag start

echo World! >> Hello.txt
git commit -am "Change to Hello World (commit B)"

git checkout start
git checkout -b branch
echo Dave >> Hello.txt
git commit -am "Change to Hello Dave (commit C)"

git merge master
echo Hello World, Dave! > Hello.txt
git add Hello.txt
git commit -m "Merge branch master into branch (commit D)"

git checkout start
git checkout -b goodbye-branch
echo Goodbye > Goodbye.txt
git add Goodbye.txt
git commit -m "Add Goodbye.txt (commit E)"

До этого момента все прошло нормально. Был конфликт слияния, но мы решили его. Теперь мы пытаемся перебазировать ветвь на E, чтобы получить следующее дерево коммитов:

A---E----B'
     \    \
      C'---D'

git checkout branch
git rebase -p goodbye-branch

Это заканчивается следующей ошибкой:

Auto-merging Hello.txt
CONFLICT (content): Merge conflict in Hello.txt
Automatic merge failed; fix conflicts and then commit the result.
Error redoing merge f567809e2cc91244cc7fdac210e1771dc75e4d86

Файл содержит следующий контент:

Hello
<<<<<<< HEAD
Dave
=======
World!
>>>>>>> 0437403c97f33f229e41ec9584ce891a50052e48

Что я делаю неправильно? Я ожидаю, что git сможет использовать коммит D для разрешения конфликта слияния, с которым он сталкивается при перебазировании.

Я использую Git 1.9.4.msysgit.1, которая является самой последней версией прямо сейчас.

1 ответ

TL; DR

--preserve-merges флаг просто говорит git-rebase попытаться воссоздать коммиты слияния вместо их игнорирования. Не дает git rebase способность помнить, как были разрешены конфликты слияния, т.е. он не записывает разрешения конфликтов для будущего использования. Что вы хотите использовать для этого rerere,

В вашем игрушечном примере конфликт, возникающий во время перебазировки, точно такой же, как и тот, который вы решили во время предыдущего слияния. Если вы активировали rerere до слияния вам не пришлось бы снова разрешать этот конфликт во время перебазирования.

Если вы ожидаете слияния, а затем перебазируете ветку, вам следует активировать rerere так что в будущем вам нужно будет разрешить данный конфликт слияния только один раз, а не несколько раз.

Детальное объяснение

Давайте разберем ваш игрушечный пример.

git init
echo Hello > Hello.txt
git add Hello.txt
git commit -m "Create Hello.txt (commit A)"
git tag start

echo World! >> Hello.txt
git commit -am "Change to Hello World (commit B)"

git checkout start
git checkout -b branch
echo Dave >> Hello.txt
git commit -am "Change to Hello Dave (commit C)"

Все идет нормально. Прямо перед вашим первым git merge команда, ваш репо выглядит так:

введите описание изображения здесь

В коммит А, Hello.text содержит

Hello

В коммит Б, Hello.text содержит

Hello
World!

И в коммите C, Hello.text содержит

Hello
Dave

Теперь, когда вы пытаетесь объединить master в branch запустив

git merge master

Git сообщает о конфликте слияния, потому что не может самостоятельно выяснить, является ли содержимое Hello.txt после слияния должно быть

Hello
World!
Dave

или же

Hello
Dave
World!

или что-то другое...

Вы разрешаете этот конфликт, перезаписывая содержимое Hello.txt с Hello World, Dave!, внесение ваших изменений и завершение фиксации слияния.

echo "Hello World, Dave!" > Hello.txt
git add Hello.txt
git commit -m "Merge branch master into branch (commit D)"

Ваш репо теперь выглядит так:

введите описание изображения здесь

Тогда ты бежишь

git checkout start
git checkout -b goodbye-branch
echo Goodbye > Goodbye.txt
git add Goodbye.txt
git commit -m "Add Goodbye.txt (commit E)"

На этом этапе ваше репо выглядит следующим образом:

введите описание изображения здесь

Теперь ты бежишь

git checkout branch
git rebase -p goodbye-branch

но испытать конфликт. Прежде чем объяснить, почему возникает этот конфликт, давайте посмотрим, как будет выглядеть ваш репо, если это git-rebase Операция прошла успешно (т.е. без конфликтов):

введите описание изображения здесь

Теперь давайте посмотрим, почему вы столкнулись с тем же конфликтом в Hello.txt как во время вашего первого слияния; Goodbye.txt Здесь нет проблем. Фактически ребаз может быть разложен в последовательность более элементарных операций (checkout с и cherry-pick с); Подробнее об этом на http://think-like-a-git.net/sections/rebase-from-the-ground-up.html. Короче говоря... В середине вашего git rebase операция, ваш репо будет выглядеть следующим образом:

введите описание изображения здесь

Ситуация очень похожа на ситуацию перед вашим первым слиянием: в коммите B', Hello.text содержит

Hello
World!

И в коммит C', Hello.text содержит

Hello
Dave

Затем Git пытается создать слияние B'и C', но конфликт слияния возникает по той же причине, что и первый конфликт слияния, с которым вы столкнулись: Git не может выяснить, Dave линия должна идти до или после World! линия. Поэтому операция rebase останавливается, и Git просит вас разрешить конфликт слияния, прежде чем он сможет завершить восстановление.

Что вы можете с этим поделать: использовать rerere

Git и rerere твой друг, здесь.

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

[...] если вы хотите взять ветку, которую вы слили, и исправить кучу конфликтов, а затем решить вместо этого перебазировать ее - вам, вероятно, больше не придется повторять те же самые конфликты.

Если rerere был включен,

git config --global rerere.enabled true

перед слиянием Git записал бы, как вы разрешили конфликт слияния при создании коммита D, и применил бы то же разрешение, когда столкнулся с тем же конфликтом во время последующей перебазировки. Конфликт все равно прервал бы операцию перебазирования, но он был бы разрешен автоматически. Все, что вам нужно было сделать, это git rebase --continue,

Тем не менее, похоже, rerere не было активировано до слияния, а это значит, что Git, должно быть, не вел записи о том, как вы разрешили конфликт в первый раз. На этом этапе вы можете либо активировать rerere Теперь разрешите все эти конфликты вручную или используйте rerere-train.sh сценарий (см. также этот блог), чтобы использовать существующую историю для предварительного заполнения rerere кэш.

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