Почему git-rebase вызывает у меня конфликты слияния, когда все, что я делаю, это сжимаю коммиты?

151

У нас есть репозиторий 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 theirs» (т. Е. Используя стратегию по умолчанию, которая, если я правильно понимаю документацию, является рекурсивной, но я не уверен, какая рекурсивная стратегия используется), а также не т работать. Кроме того, я должен указать, что, используя закомментированный код в 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

Бит "оба модифицированных" звучит для меня странно, так как это был всего лишь результат выбора. Также стоит отметить, что если я смотрю на «конфликт», он сводится к одной строке, в которой одна версия начинается с символа [табуляции], а другая - с четырех пробелов. Похоже, это может быть проблема с тем, как я настроил свой файл конфигурации, но в нем нет ничего подобного. (Я заметил, что для 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

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

Обновление № 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

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

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

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

Бен Хокинг
источник
Более актуальным, чем используемые сценарии, является последнее предпринятое действие - оно наверняка будет списком смешанных пиков и сквошей, верно? И есть ли какие-нибудь коммиты слияния в перебазированной ветке? (Хотя вы все rebase -pравно не используете )
Cascabel
Я не уверен , что вы имеете в виду под «окончательной попыткой действия», но это просто список кирки и смешанных кабачков, с последним 400 или около того все забрать. В этом списке нет слияний, хотя сам процесс слияния выполняет свои собственные слияния. Судя по тому, что я читал, "rebase -p" не рекомендуется использовать в интерактивном режиме (который в моем случае, конечно, не такой уж интерактивный). Из kernel.org/pub/software/scm/git/docs/git-rebase.html : «Здесь используется механизм --interactive внутри, но явно комбинировать его с параметром --interactive, как правило, не очень хорошая идея»
Бен Hocking
Под «последней попыткой действия» я имел в виду список переданных действий выбора / сквоша rebase --interactive- это своего рода список действий, которые git должен предпринять. Я надеялся, что вы сможете свести это к одному сквошу, вызывающему конфликты, и избежать всей дополнительной сложности ваших вспомогательных скриптов. Другая недостающая информация - когда возникают конфликты - когда git применяет патчи для формирования сквоша или когда он пытается пройти мимо сквоша и применить следующий патч? (И вы уверены, что с вашим кладжем GIT_EDITOR ничего плохого не произойдет? Еще одно голосование за простой тестовый пример.)
Cascabel
Спасибо за полезные комментарии. Я обновил вопрос, чтобы отразить свои ответы.
Бен Хокинг,
2
Случай с игрушечным репозиторием намного проще для понимания, чем ваши сценарии - и показывает, что сценарии не имеют к нему никакого отношения, а только тот факт, что вы пытаетесь перебазировать ветку, содержащую слияние с разрешением конфликтов (умеренно опасное слияние ).
Cascabel

Ответы:

71

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

В вашем тестовом примере игрушечного репо есть слияние - хуже того, слияние с конфликтами. И вы перебазируете через слияние. Без -p(что не совсем работает -i) слияния игнорируются. Это означает, что все, что вы делали при разрешении конфликтов, отсутствует, когда rebase пытается выбрать следующий коммит, поэтому его патч может не применяться. (Я считаю, что это показано как конфликт слияния, потому что 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набор истина), то это будет путь проще - мерзавец будет иметь возможность повторно использовать повторно шнуровой повторное решение от того, когда вы изначально были конфликты, и все , что вам нужно сделать , это проверить его чтобы убедиться, что все работает правильно, добавьте файлы в индекс и продолжайте. (Вы можете даже пойти на шаг дальше, включив rerere.autoupdate, и он добавит их за вас, так что слияние даже не завершится ошибкой). Однако я предполагаю, что вы никогда не включали rerere, поэтому вам придется решать конфликт самостоятельно. *

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

Каскабель
источник
PS Оглядываясь на мои комментарии, я понимаю, что должен был уловить это раньше. Я спросил, перебазируете ли вы ветку, содержащую слияния, и вы сказали, что в интерактивном списке перебазирования нет слияний, что не одно и то же - там не было слияний, потому что вы не указали -pопцию.
Cascabel
Я обязательно попробую. После публикации этого сообщения я также заметил, что в некоторых случаях я могу просто набирать git commit -a -m"Some message"и git rebase --continue, и это будет продолжаться. Это работает даже без этой -pопции, но с ней работает даже лучше -p(поскольку я не делаю переупорядочения, кажется, что -pэто нормально). В любом случае, я буду держать вас в курсе.
Бен Хокинг,
Установка git config --global rerere.enabled trueи git config --global rerere.autoupdate trueперед запуском тестового примера действительно решает основную проблему. Однако, что интересно, он не сохраняет слияния даже при указании --preserve-merges. Однако, если у меня их нет, и я набираю git commit -a -m"Meaningful commit"и git rebase --continueуказываю --preserve-merges, слияния сохраняются. В любом случае, спасибо за помощь в решении этой проблемы!
Бен Хокинг,
90

Если вы не против создать новую ветку, я решил проблему следующим образом:

Находясь на главной:

# 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 main        

git commit -m "Squashed changes from original_messy_branch"
Глидка
источник
3
Самое быстрое и эффективное решение :)
IslamTaha
2
Это было отличное предложение! Очень хорошо работал для быстрого объединения нескольких коммитов.
philidem 05
3
Это гораздо более безопасное решение. Я также добавил --squash к слиянию. Бытие: git merge --squash original_messy_branch
jezpez
1
Спасибо, что спасли мне день и жизнь :) Лучшее решение, которое может получить каждый
Я просто не мог раздавить / отредактировать какой-либо коммит из-за слияния в истории более чем 40 коммитов. Это простое решение заняло у меня 10 секунд. Я проделал эту работу в новой ветке, и когда все было в порядке (я сделал копию на всякий случай), я сбросил грязную ветку в эту красивую сжатую фиксацию. Отлично! :)
Джереми Кочой
5

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

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”

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

user28186
источник
К вашему сведению - я просто пробовал это и обнаружил, что выполнение проверки mybranch-end-commit не дает мне удалений, которые произошли в промежуточных коммитах. Таким образом, он проверяет только файлы, которые существовали в mybranch-end-commit.
ahains 05
1

Основываясь на отличном ответе @ hlidka, приведенном выше, который сводит к минимуму ручное вмешательство, я хотел добавить версию, которая сохраняет любые новые коммиты на мастере, которые не находятся в ветке для сквоша.

Как я полагаю, их можно легко потерять на git resetшаге в этом примере.

# create a new branch 
# ...from the commit in master original_messy_branch was originally based on. eg 5654da06
git checkout -b new_clean_branch 5654da06

# apply all changes
git merge original_messy_branch

# forget the commits but have the changes staged for commit
# ...base the reset on the base commit from Master
git reset --soft 5654da06       

git commit -m "Squashed changes from original_messy_branch"

# Rebase onto HEAD of master
git rebase origin/master

# Resolve any new conflicts from the new commits
JonoB
источник
0

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

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

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

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

Подписано: Арно Фонтен

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

VonC
источник
0

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

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

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

Abaldwinhunter
источник
У меня все еще есть несколько конфликтов, которые нужно разрешить вручную при попытке перебазирования с помощью -p. По общему признанию, конфликтов было намного меньше, и они были ограничены файлом проекта, а не всеми файлами кода, с которыми я конфликтовал раньше. Видимо «Разрешение конфликтов слияния или ручные поправки к коммитам слияния не сохраняются». - stackoverflow.com/a/35714301/727345
JonoB