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
кэш.