GIT – Rebase – Как бороться с конфликтами

Вопрос:Я работаю над проектом, который имеет две ветки: master и feature Филиал feature был создан некоторое время назад и имеет множество коммитов. Поскольку была создана ветвь feature, была пара фиксаций на master В этот момент, когда я перехожу к rebase off master, я получаю конфликты. Я разрешаю их, а затем rebase --continue. Затем я снова

Вопрос:

Я работаю над проектом, который имеет две ветки: master и feature

Филиал feature был создан некоторое время назад и имеет множество коммитов.

Поскольку была создана ветвь feature, была пара фиксаций на master

В этот момент, когда я перехожу к rebase off master, я получаю конфликты. Я разрешаю их, а затем rebase —continue. Затем я снова получаю конфликты и снова разрешаю и rebase —continue. Это происходит снова и снова, и много раз кажется, что они являются теми же конфликтами, которые появляются.

В моем сознании вот что происходит:

master(commits)->a->b
feature(commits)->c->d->e->f->g

feature был разветвлен от master->a, а затем были созданы все коммиты.

Когда я rebase он перематывает назад к началу feature, где он был разветвлен с master, применяется master->b, а затем начинает применять feature->c, в этот момент он имеет конфликт. Я разрешаю (принимает изменения master) и продолжаю. Теперь он пытается применить feature->d и находит тот же конфликт. Снова я должен решить и continue. Это происходит снова и снова.

Например, вот изменения:

master->a <div id=»foo»> master->b <div id=»bar»> feature->c <div id=»fubar»> feature->d //Nothing has changed, inherited from feature->c <div id=»fubar»>

Я предполагаю, что когда он достигнет feature->c, он изменит foo на fubar, а затем замечает, что foo уже изменен на bar. Я разрешаю bar, а затем выполняет ту же логику, применяя feature->d

Мои два вопроса:

1) Является ли мое понимание того, как git работает/имеет дело с фиксацией/конфликтами/правильной корректировкой?

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

Заметьте, это очень упрощенный пример. На самом деле у меня гораздо больше коммитов и внутри каждого многочисленного файла с многочисленными конфликтами. Некоторые из них кажутся одинаковыми в течение процесса rebase —continue, а некоторые – для каждого commit.

Моя конечная цель – как можно проще очистить этот проект (отвлечь ветвь функции от текущего мастера). Я не забочусь об истории совершений.

Лучший ответ:

Меня не интересует история коммитов.

Если вы действительно не заботитесь об истории, git merge —squash позволит вам разрешить все ваши конфликты сразу и создать единую фиксацию со всеми изменениями.

Чтобы по существу сделать это —squash на месте, вы можете сделать что-то вроде этого:

git branch -m feature feature-old git checkout master -b feature git merge —squash feature-old

После того, как вы разрешите все конфликты (один раз), вы создадите единую фиксацию на feature, которая имеет master в качестве родителя.

Это, как говорится, я поклонник сохранения истории. Определенно попробуйте rerere. Вы также можете попробовать на месте rebase —interactive (например, git rebase -i $(git merge-base HEAD master)), чтобы раздавить фиксации типа fixup без полного устранения всех дискретных коммитов.

Ответ №1

Ваш ментальный образ близок, но пусть он будет точным:

…—o—*—A—B <— master C—D—…—Z <— feature

Это то, что у вас есть сейчас: имя master указывает на tip commit B (с каждой однобуквенной или o или *, представляющей фиксацию). Каждая фиксация указывает назад (слева) на ее предыдущую фиксацию. Два коммита, A и B, находятся только на master. Куча коммиттов только на feature, а именно C через Z здесь. Commit * и все предыдущие (дальнейшие левые) фиксации находятся на обеих ветвях.

То, что git rebase делает, – это фиксация *, работая назад от commit Z, а также от кончика master. Затем он знает, что ему нужно скопировать C-Z. Копии должны начинаться сразу после мастера. Если мы используем C’ для обозначения копии C, D’ для копии D и т.д., Окончательный график будет выглядеть следующим образом:

C’-D’-…-Z’ <— feature / …—o—*—A—B <— master C—D—…—Z [abandoned]

Как я думаю, вы поняли, что каждая копия совершается по одной фиксации за раз, превращая каждую фиксацию (которая является полным моментальным снимком) в набор изменений. Чтобы получить то, что изменилось в C, Git делает:

git diff <hash-of-*> <hash-of-C>

Git затем пытается применить это изменение к снимку в B, но он не применяется легко, поэтому Git пытается выполнить слияние: он сравнивает * с B и видит, что это следует изменить <div id=»foo»> на <div id=»bar»>, согласно этому; но он должен изменить <div id=»foo»> <div id=»fubar»>, согласно * -vs- C. Что первый конфликт слияния.

Итак, вы разрешите это и зафиксируете (ну, git rebase —continue совершает):

C’ <— HEAD (rebase in progress) / …—o—*—A—B <— master C—D—…—Z <— feature

и Git продолжается копирование D, путем изменения D vs C, чтобы получить патч.

На этот раз не должно быть изменений <div id=»foo»> to <div id=»fubar»>, поэтому здесь не следует создавать конфликт слияния. Чтобы точно узнать, попробуйте git show -ing commit D, который также сравнивает его с C, создавая diff.

Однако вы можете быть укушены изменениями белого пространства или аналогичными. Стоит внимательно изучить каждый вывод git diff и проверять такие атрибуты, как атрибуты конца строки, если вы используете их (преобразования CRLF). Конец строки часто влияет на каждую строку в каждом файле, но вы можете получить случаи однострочных проблем, в зависимости от того, кто использует какой редактор, например.

Я подозреваю более вероятную проблему: git diff синхронизируется на неправильных строках. Он находит некоторые тривиальные вещи, которые совпадают, например строки, которые читают }, и использует их, чтобы решить, что два файла снова синхронизированы, а затем выберет “неправильные изменения”. Если это так, то лечение, если оно есть, – дать команду Git использовать более умный diff. В частности, diff patience пытается не синхронизировать по тривиальным строкам, а скорее только по значительным (то есть единовременным) линиям. Это может или не может помочь на самом деле git diff —diff-algorithm=patience (или git show с тем же аргументом), что и ваши коммиты могут вам рассказать. Независимо от того, можете ли вы использовать rebase для использования другого алгоритма diff, зависит от вашей версии Git.

Помимо тайны почему Git, похоже, повторяет изменение, когда оно должно иметь только другие изменения в C -vs- D, одна вещь, которую вы можете сделать, чтобы помочь, – использовать git rerere, чтобы получить Git для повторного использования записанного разрешения (отсюда и название). В принципе, когда Git попадает в конфликт слиянием, если rerere включен, Git записывает конфликтующие части в свою базу данных, а затем, когда вы git add разрешенный файл, Git записывает разрешение (в паре с исходный конфликт). Затем, в следующий раз, когда Git попадает в конфликт слиянием, он проверяет сохраненные данные reerere: если конфликт для одного и того же патча, он вытаскивает записанное разрешение и использует это без взаимодействия с вами.

Вы можете раздавить часть или все функции вместе, перезарядив feature atop commit * (используя интерактивную rebase). Поскольку эти патчи не конфликтуют с самим собой – по крайней мере, пока они воспроизводятся “по порядку”, что позволяет сократить количество коммитов, требующих разрешения. Независимо от того, хотите ли вы это сделать, вам нужно: в общем, если вы переустанавливаете, вы все равно получаете новую замену, поэтому вы также можете сделать новые “максимально возможными” для будущей отладки или другого исследования кода. Ясно, что было бы лучше, если бы пять “делали что-то интересное, но в значительной степени автономно” совершали вместо 50 “делали фрагмент одной вещи, делали другой фрагмент, исправляли предыдущий фрагмент, делали еще один фрагмент, ОК, к следующей вещи, oops исправить крошечную ошибку,…”, так что это очень хорошие кандидаты на раздачу. Но обычно лучше иметь пять “делает одну вещь”, чем совершает “делает пять вещей”: это означает, что если в одной из пяти ошибок есть ошибка, вы можете исправить это, не беспокоясь о других четырех.

Ответ №2

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

После того, как вы переупорядочиваете и управляете фиксацией с помощью Git rebase, если возникает конфликт слиянием, Git сообщит вам об этом со следующим сообщением, напечатанным на терминале:

error: could not apply fa39187… something to add to patch A When you have resolved this problem, run «git rebase —continue». If you prefer to skip this patch, run «git rebase —skip» instead. To check out the original branch and stop rebasing, run «git rebase —abort». Could not apply fa39187f3c3dfd2ab5faa38ac01cf3de7ce2e841… Change fake file

Здесь Git сообщает вам, какая фиксация вызывает конфликт (fa39187). Вам предоставляется три варианта:

  • Вы можете запустить git rebase –abort, чтобы полностью отменить rebase. Git вернет вас в ваше состояние ветвления, как это было до того, как была вызвана ошибка Git.
  • Вы можете запустить git rebase -skip, чтобы полностью пропустить фиксацию. Это означает, что ни одно из изменений, внесенных проблематичной фиксацией, не будет включено. Очень редко вы выбираете этот вариант.
  • Вы можете исправить конфликт.

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

Ответ №3

У меня была аналогичная проблема, и оказалось, что я разрешал конфликты с —ours, когда я должен был использовать —theirs.

Причина в том, что —ours – это ваши изменения в merge, но в rebase это —theirs, потому что git эффективно удаляет ваши коммиты и повторяет их копии.

Поэтому, когда вы разрешаете конфликт с вашей версией с помощью rebase do:

git checkout —theirs some.file
git add some.file

но при разрешении конфликта с вашей версией с помощью merge выполните:

git checkout —ours some.file
git add some.file

Оцените статью
Добавить комментарий