Git rebase --preserve-merges не удается

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

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

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

Если ведущий ссылается на 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 сможет использовать commit D для разрешения конфликта слияния, с которым он сталкивается при перезагрузке.

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

Ответ 1

TL; DR

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

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

Если вы ожидаете, что вы объединитесь, то пересоедините ветвь, вы должны активировать 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 ваше репо выглядит следующим образом:

enter image description here

В фиксации A, Hello.text содержит

Hello

В commit B, Hello.text содержит

Hello
World!

И в commit 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)"

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

enter image description here

Затем вы запустите

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

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

enter image description here

Теперь вы запустите

git checkout branch
git rebase -p goodbye-branch

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

enter image description here

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

enter image description here

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

Hello
World!

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

Hello
Dave

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

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

Git rerere является вашим другом, здесь.

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

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

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

git config --global rerere.enabled true

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

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