Чому git-rebase дає мені конфлікти злиття, коли все, що я роблю, - це стискання фільмів?


146

У нас є сховище 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, щоб переконатися, що він робить те, що я хотів це зробити ( це було). Знову ж , я навіть не знаю , чому б бути конфлікт, так що здавалося б, не має значення , так багато , яка стратегія використовується. Будь-яка порада чи розуміння було б корисним, але в основному я просто хочу, щоб ця сквашина працювала.

Оновлено додатковою інформацією з обговорення з Jefromi:

Перш ніж працювати над нашим масивним "реальним" сховищем, я використовував подібні скрипти в тестовому сховищі. Це було дуже просте сховище, і тест працював чисто.

Повідомлення, яке я отримую, коли воно не працює, це:

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 встановлений на істину, але очевидно, що git-клон зробив це автоматично. Я не зовсім здивований тим, враховуючи, що оригінальне джерело було на машині 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

PS: Так, я знаю, що деякі з вас плазують, коли бачите, як я використовую emacs як резервний редактор.

PPS: Ми знаємо, що нам доведеться підірвати всі наші клони наявного сховища після ребайз. (У рядку "Ви не будете перезавантажувати сховище після його опублікування".)

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


Більш релевантною, ніж використані сценарії, є остання спроба дії - схоже, це перелік змішаних підбирань та сквош, правда? І чи є якісь об'єднання злиття у відновлюваній галузі? (Хоча ви все rebase -pодно не користуєтесь )
Cascabel

Я не впевнений , що ви маєте в виду під «остаточної спробою дії», але це просто список кирки і змішаних кабачків, з останнім 400 або близько того все забрати. У цьому списку немає злиттів, хоча сам перезавантаження виконує власні злиття. Згідно з тим, що я прочитав, "rebase -p" не рекомендується в інтерактивному режимі (що, в моєму випадку, не все, що інтерактивний, звичайно). Від kernel.org/pub/software/scm/git/docs/git-rebase.html : "Тут використовується внутрішньо - інтерактивна техніка всередині, але комбінування її з опцією - інтерактивне явно не є гарною ідеєю"
Бен Скакання

Під "остаточною спробою дії" я мав на увазі список вибору / сквош, переданий назад rebase --interactive- це свого роду перелік дій для спроби git. Я сподівався, що вам вдасться звести це до однієї сквош, яка викликала конфлікти, і уникнути всіх зайвих складностей ваших помічників. Інша інформація, яка відсутня, - це коли виникають конфлікти - коли git застосовує патчі, щоб сформувати сквош, або коли він намагається рухатись повз сквош і застосовувати наступний патч? (А ви впевнені, що нічого поганого з вашим GIT_EDITOR не виходить? Ще один голос за простий тестовий випадок.)
Cascabel

Дякуємо за корисні коментарі. Я оновив питання, щоб відобразити свої відповіді.
Бен Хокінг

2
Випадок сховища іграшок зрозуміти набагато, набагато простіше, ніж ваші сценарії - і показує, що сценарії не мають до цього нічого спільного, лише той факт, що ви намагаєтесь перезавантажити гілку, що містить злиття з вирішенням конфлікту (легке злісне злиття ).
Каскабель

Відповіді:


69

Гаразд, я досить впевнений, щоб викинути відповідь. Можливо, доведеться це відредагувати, але я вважаю, що я знаю, у чому ваша проблема.

У вашому тестовому випадку репо-іграшки є злиття в ньому - ще гірше, воно зливає конфлікти. І ви відпускаєте через злиття. Без -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набір істина), то це буде шлях простіше - мерзотник матиме можливість повторно використовувати повторно шнурової повторне рішення від того, коли ви спочатку були конфлікти, і все , що вам потрібно зробити , це перевірити його щоб переконатися, що він працював правильно, додайте файли до індексу та продовжуйте. (Ви навіть можете піти на крок далі, увімкнувши rerere.autoupdate, і це додасть їх вам, тому злиття навіть не вийде з ладу). Я здогадуюсь, однак, що ви ніколи не вмикали повторно, тому вам доведеться самостійно вирішувати конфлікт. *

* Або ви можете спробувати rerere-train.shскрипт від git-contrib, який намагається "Prime [the] rerere database from существующіх комірок злиття" - в основному він перевіряє всі об'єднання, намагається їх об'єднати, і якщо злиття не вдалося, він захоплює результати і показує їх git-rerere. Це може зайняти багато часу, і я ніколи його фактично не використовував, але це може бути дуже корисно.


PS Озирнувшись на мої коментарі, я бачу, що я мав би це раніше зрозуміти. Я запитав, чи ви випускаєте відділення, що містить злиття, і ви сказали, що в списку інтерактивних реставин не було злиттів, що не те саме - там не було злиття, оскільки ви не надали -pопцію.
Каскабель

Я обов'язково підкажу це. Опублікувавши це, я також помітив у деяких випадках, що я можу просто ввести 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, він зберігає злиття. У будь-якому випадку, дякую, що допомогли мені подолати цю проблему!
Бен Хокінг

84

Якщо ви не проти створити нову гілку, ось я вирішив цю проблему:

Будучи основним:

# 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
Це було чудовою пропозицією! Дуже добре працював для швидкої консолідації декількох комітетів.
філідем

3
Це набагато безпечніше рішення. Я також додав - сквош до злиття. Буття: git merge --squash original_messy_branch
jezpez

1
Дякую, що врятували мій день та життя :) Найкраще рішення, яке кожен може отримати

Коли ви робите, чи git diff new_clean_branch..original_messy_branchповинні вони бути однаковими? Для мене вони різні.
ЛеонардШалліс

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”

Віола, ми підключили останнє зобов’язання до стартового комітету філії! Немає конфліктних питань злиття! У своїй навчальній практиці я прийшов до цього висновку на цьому етапі, чи є кращий підхід до мети.


FYI - Я просто намагався це і виявив, що здійснення замовлення mybranch-end-commit не отримує мені делетів, що відбулися в проміжних комісіях. Таким чином, він перевіряє лише ті файли, які існували в mybranch-end-commit.
приходить

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

0

Зауважте, що -Xпараметри стратегії були ігноровані при використанні в інтерактивній базі даних.

Див. Команду db2b3b820e2b28da268cc88adff076b396392dfe (липень 2013 року, git 1.8.4+),

Не ігноруйте параметри злиття в інтерактивній базі даних

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

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

Це означає, що -Xстратегія тепер працює з інтерактивною базою даних, а також з простою базою даних, і ваш початковий сценарій тепер може працювати краще.


0

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

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

Сподіваюся, що хтось допомагає!


У мене все ще є кілька конфліктів, які потрібно вручну вирішити при спробі відновлення бази даних з -p Напевно, було набагато менше конфліктів, і вони були обмежені лише файлом проекту, а не всіма файлами коду, з якими я конфліктував раніше. Мабуть, "вирішення конфліктних конфліктів або вручну поправки до об'єднань не зберігаються". - stackoverflow.com/a/35714301/727345
JonoB
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.