Почему git-rebase дает мне конфликты слияний, когда все, что я делаю, это сжатие коммитов? - PullRequest
117 голосов
/ 28 июня 2010

У нас есть Git-репозиторий с более чем 400 коммитами, первые пару десятков из которых были полны проб и ошибок.Мы хотим очистить эти коммиты, раздавив многих в один коммит.Естественно, git-rebase кажется правильным.Моя проблема в том, что это заканчивается конфликтами слияний, и эти конфликты не так просто разрешить.Я не понимаю, почему вообще должны быть конфликты, так как я просто подавляю коммиты (не удаляя и не переупорядочивая).Очень вероятно, что это демонстрирует, что я не совсем понимаю, как git-rebase выполняет свои действия.

Вот модифицированная версия используемых мной скриптов:


repo_squash.sh(это скрипт, который фактически запускается):


rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
GIT_EDITOR=../repo_squash_helper.sh git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

repo_squash_helper.sh (этот скрипт используется только repo_squash.sh):


if grep -q "pick " $1
then
#  cp $1 ../repo_squash_history.txt
#  emacs -nw $1
  sed -f ../repo_squash_list.txt < $1 > $1.tmp
  mv $1.tmp $1
else
  if grep -q "initial import" $1
  then
    cp ../repo_squash_new_message1.txt $1
  elif grep -q "fixing bad import" $1
  then
    cp ../repo_squash_new_message2.txt $1
  else
    emacs -nw $1
  fi
fi

repo_squash_list.txt: (этот файл используется только repo_squash_helper.sh)


# Initial import
s/pick \(251a190\)/squash \1/g
# Leaving "Needed subdir" for now
# Fixing bad import
s/pick \(46c41d1\)/squash \1/g
s/pick \(5d7agf2\)/squash \1/g
s/pick \(3da63ed\)/squash \1/g

Я оставлю содержимое "нового сообщения" вашему воображению.Первоначально я делал это без опции «--strategy их» (т. Е. Используя стратегию по умолчанию, которая, если я правильно понимаю, документация рекурсивна, но я не уверен, какая рекурсивная стратегия используется), и это тоже нет работа.Кроме того, я должен отметить, что, используя закомментированный код в repo_squash_helper.sh, я сохранил исходный файл, над которым работает скрипт sed, и запустил против него скрипт sed, чтобы убедиться, что он делает то, что хотел (это было).Опять же, я даже не знаю, почему будет конфликтом, так что, похоже, не имеет большого значения, какая стратегия используется.Любой совет или понимание были бы полезны, но в основном я просто хочу, чтобы этот сквош работал.

Обновлен с дополнительной информацией из обсуждения с Джефроми:

Перед работой над нашим огромным "настоящим" хранилищем,Я использовал похожие скрипты в тестовом хранилище.Это был очень простой репозиторий, и тест работал без ошибок.

При сбое я получаю следующее сообщение:

Finished one cherry-pick.
# Not currently on any branch.
nothing to commit (working directory clean)
Could not apply 66c45e2... Needed subdir

Это первый выбор после первой фиксации сквоша.Запуск git status дает чистый рабочий каталог.Если я тогда сделаю git rebase --continue, я получу очень похожее сообщение после еще нескольких коммитов.Если я сделаю это снова, я получу еще одно очень похожее сообщение после пары дюжин коммитов.Если я сделаю это еще раз, на этот раз он пройдет около ста коммитов и выдаст это сообщение:

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply f1de3bc... Incremental

Если я тогда запусту git status, я получу:

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
# modified:   repo/file_A.cpp
# modified:   repo/file_B.cpp
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
# both modified:      repo/file_X.cpp
#
# Changed but not updated:
#   (use "git add/rm <file>..." to update what will be committed)
#   (use "git checkout -- <file>..." to discard changes in working directory)
#
# deleted:    repo/file_Z.imp

«Оба измененных» бита звучат для меня странно, так как это был только результат выбора.Стоит также отметить, что, если я посмотрю на «конфликт», он сводится к одной строке, где одна версия начинается с символа [tab], а другая - с четырех пробелов.Это звучало так, как будто это может быть проблемой с тем, как я настроил свой конфигурационный файл, но в этом нет ничего подобного.(Я заметил, что для core.ignorecase установлено значение true, но, очевидно, git-clone сделал это автоматически. Я не совсем удивлен тем, что исходный код был на компьютере с Windows.)

Если явручную исправьте файл file_X.cpp, затем вскоре произойдет сбой с другим конфликтом, на этот раз между файлом (CMakeLists.txt), который, по мнению одной версии, должен существовать, а другой - нет.Если я исправлю этот конфликт, сказав, что я хочу этот файл (что я и делаю), то через несколько коммитов я получу еще один конфликт (в этом же файле), где теперь происходят некоторые довольно нетривиальные изменения.Прохождение конфликтов составляет лишь около 25%.

Я также должен отметить, поскольку это может быть очень важно, поскольку этот проект начался в репозитории svn.Вероятно, эта исходная история была импортирована из этого хранилища svn.

Обновление № 2:

На жаворонке (под влиянием комментариев Джефроми) я решил внести изменения в свой repo_squash.sh, чтобы:

rm -rf repo_squash
git clone repo repo_squash
cd repo_squash/
git rebase --strategy theirs -i bd6a09a484b8230d0810e6689cf08a24f26f287a

А потом я просто принял исходные записи, как есть. Т.е. «ребаз» не должен был ничего менять. В итоге получились те же результаты, что были описаны ранее.

Обновление № 3:

В качестве альтернативы, если я опущу стратегию и заменю последнюю команду на:

git rebase -i bd6a09a484b8230d0810e6689cf08a24f26f287a

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

Обновление с хранилищем игрушек, воссоздающее проблему:

test_squash.sh (это файл, который вы на самом деле запускаете):

#========================================================
# Initialize directories
#========================================================
rm -rf test_squash/ test_squash_clone/
mkdir -p test_squash
mkdir -p test_squash_clone
#========================================================

#========================================================
# Create repository with history
#========================================================
cd test_squash/
git init
echo "README">README
git add README
git commit -m"Initial commit: can't easily access for rebasing"
echo "Line 1">test_file.txt
git add test_file.txt
git commit -m"Created single line file"
echo "Line 2">>test_file.txt 
git add test_file.txt 
git commit -m"Meant for it to be two lines"
git checkout -b dev
echo Meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Meaningful commit"
git checkout master
echo Conflicting meaningful code>new_file.txt
git add new_file.txt 
git commit -m"Conflicting meaningful commit"
# This will conflict
git merge dev
# Fixes conflict
echo Merged meaningful code>new_file.txt
git add new_file.txt
git commit -m"Merged dev with master"
cd ..

#========================================================
# Save off a clone of the repository prior to squashing
#========================================================
git clone test_squash test_squash_clone
#========================================================

#========================================================
# Do the squash
#========================================================
cd test_squash
GIT_EDITOR=../test_squash_helper.sh git rebase -i HEAD@{7}
#========================================================

#========================================================
# Show the results
#========================================================
git log
git gc
git reflog
#========================================================

test_squash_helper.sh (используется test_sqash.sh):

# If the file has the phrase "pick " in it, assume it's the log file
if grep -q "pick " $1
then
  sed -e "s/pick \(.*\) \(Meant for it to be two lines\)/squash \1 \2/g" < $1 > $1.tmp
  mv $1.tmp $1
# Else, assume it's the commit message file
else
# Use our pre-canned message
  echo "Created two line file" > $1
fi

P.S .: Да, я знаю, что некоторые из вас съеживаются, когда видят, что я использую emacs как запасной редактор.

P.P.S .: Мы знаем, что нам придется сдуть все наши клоны существующего хранилища после ребазинга. (Вдоль строк «не перезагружай репозиторий после его публикации».)

P.P.P.S: Кто-нибудь может сказать мне, как добавить к этому вознаграждение? Я не вижу опции на этом экране ни в режиме редактирования, ни в режиме просмотра.

Ответы [ 5 ]

62 голосов
/ 29 июня 2010

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

В вашем тестовом репозитории для игрушек есть слияние, а еще хуже - с конфликтами. И вы перебираете через слияние. Без -p (который не полностью работает с -i), слияния игнорируются. Это означает, что что бы вы ни делали в своем разрешении конфликта , его там нет , когда ребаз пытается попытаться выбрать следующий коммит, поэтому его исправление может не применяться. (Я считаю, что это показано как конфликт слияния, потому что git cherry-pick может применить патч, выполнив трехстороннее слияние между исходным коммитом, текущим коммитом и общим предком.)

К сожалению, как мы отметили в комментариях, -i и -p (сохранить слияния) не очень хорошо ладят. Я знаю, что редактирование / переписывание работает, а переупорядочение - нет. Тем не менее, я считаю , что он хорошо работает с тыквами. Это не задокументировано, но сработало для тестовых случаев, которые я опишу ниже. Если ваше дело намного сложнее, у вас может быть много проблем с выполнением того, что вы хотите, хотя это все еще возможно. (Мораль истории: очистить с помощью rebase -i до слияния.)

Итак, давайте предположим, что у нас есть очень простой случай, когда мы хотим сложить вместе A, B и C:

- o - A - B - C - X - D - E - F (master)
   \             /
    Z -----------

Теперь, как я уже сказал, если в X не было конфликтов, git rebase -i -p работает так, как вы ожидаете.

Если возникают конфликты, все становится немного сложнее. Это будет хорошо давить, но тогда, когда он попытается воссоздать слияние, конфликты повторится снова. Вам придется разрешить их снова, добавить их в индекс, а затем использовать git rebase --continue для продолжения. (Конечно, вы можете разрешить их снова, проверив версию из исходного коммита слияния.)

Если в вашем репо активировано rerere (rerere.enabled установлено в значение true), это будет намного проще - git сможет re использовать re reded re решения от того, когда у вас изначально были конфликты, и все, что вам нужно сделать, это проверить его, чтобы убедиться, что он работает, добавить файлы в индекс и продолжить (Вы даже можете сделать еще один шаг, включив rerere.autoupdate, и он добавит их для вас, так что объединение не потерпит неудачу). Я предполагаю, однако, что вы никогда не включали rerere, поэтому вам придется самостоятельно разрешать конфликты. *

* Или, вы можете попробовать скрипт rerere-train.sh от git-contrib, который пытается «восстановить базу данных из существующих коммитов слияния» - в основном он проверяет все коммиты слияния, пытается объединить их, и если объединение не удастся, он захватит результаты и покажет их git-rerere. Это может занять много времени, и я никогда не использовал его, но это может быть очень полезно.

44 голосов
/ 27 июня 2017

Если вы не возражаете против создания новой ветки, вот как я справился с проблемой:

Нахождение на мастере:

# create a new branch
git checkout -b new_clean_branch

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
git reset --soft master        

git commit -m "Squashed changes from original_messy_branch"
3 голосов
/ 06 сентября 2015

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

git reset –hard mybranch-start-commit
git checkout mybranch-end-commit . // files only of the latest commit
git add -a
git commit -m”New Message intermediate commits discarded”

альт, мы подключили последний коммит к стартовому коммиту ветки! Нет проблем слияния! В своей учебной практике на этом этапе я пришел к такому выводу: есть ли лучший подход для этой цели?

0 голосов
/ 13 ноября 2015

Я столкнулся с более простой, но похожей проблемой, где у меня было 1) разрешен конфликт слияния на локальном филиале, 2) продолжал работать, добавляя много маленьких коммитов, 3) хотел перебазировать и ударить конфликты слияния.

Для меня git rebase -p -i master сработало. Он сохранил первоначальный коммит по разрешению конфликтов и позволил мне раздавить остальных сверху.

Надеюсь, это кому-нибудь поможет!

0 голосов
/ 12 июля 2013

Обратите внимание, что -X и параметры стратегии игнорировались при использовании в интерактивной перебазировке.

См. commit db2b3b820e2b28da268cc88adff076b396392dfe (июль 2013 г., git 1.8.4+),

Не игнорируйте параметры слияния в интерактивном перебазировании

Стратегия слияния и ее параметры могут быть указаны в git rebase, но с -- interactive они полностью игнорируются.

Подписано-off-by: Arnaud Fontaine

Это означает, что -X и стратегия теперь работают с интерактивной перебазировкой, а также простой перебазировкой, и ваш первоначальный сценарий теперь может работать лучше.

...