TL; DR
Рабочий процесс git rebase не защищает вас от людей, плохо разбирающихся в разрешении конфликтов, или от людей, привыкших к рабочему процессу SVN, как это предложено в Предотвращение катастроф Git: история Gory . Это только делает разрешение конфликтов более утомительным для них и затрудняет восстановление после плохого разрешения конфликтов. Вместо этого используйте diff3, чтобы не было так сложно в первую очередь.
Рабочий процесс переназначения не лучше для разрешения конфликтов!
Я очень сторонник перебазирования за очистку истории. Однако, если я когда-либо столкнулся с конфликтом, я немедленно прекращаю ребазирование и вместо этого выполняю слияние! Меня действительно убивает, что люди рекомендуют рабочий процесс ребазирования в качестве лучшей альтернативы рабочему процессу слияния для разрешения конфликтов (который является именно о чём был этот вопрос).
Если во время слияния он пойдет "все в ад", то при перебазировании он пойдет "все в ад", и, возможно, намного больше в ад! И вот почему:
Причина № 1: Разрешать конфликты один раз, а не один раз для каждого коммита
Когда вы перебазируете вместо слияния, вам придется выполнять разрешение конфликтов столько раз, сколько вы совершаете перебазирование, для одного и того же конфликта!
Реальный сценарий
Я разветвляюсь от мастера, чтобы провести рефакторинг сложного метода в ветке. Моя работа по рефакторингу состоит из 15 коммитов, так как я работаю над ее рефакторингом и получаю обзоры кода. Часть моего рефакторинга включает исправление смешанных вкладок и пробелов, которые присутствовали в master раньше. Это необходимо, но, к сожалению, оно будет конфликтовать с любыми изменениями, внесенными впоследствии в этот метод в master. Конечно же, пока я работаю над этим методом, кто-то вносит простое, законное изменение в тот же метод в основной ветке, который должен быть объединен с моими изменениями.
Когда пришло время объединить мою ветку с master, у меня есть два варианта:
git merge:
Я получаю конфликт. Я вижу изменения, которые они сделали, чтобы освоить и объединить их с (конечным продуктом) моей ветки. Готово.
git rebase:
Я получаю конфликт с моим первым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
Я получаю конфликт с моим вторым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
Я получил конфликт с моим третьим коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим четвертым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим пятым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим шестым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим седьмым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим восьмым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим девятым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим десятым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим одиннадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим двенадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим тринадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим четырнадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
У меня конфликт с моим пятнадцатым коммитом. Я разрешаю конфликт и продолжаю перебазирование.
Вы, должно быть, шутите, если это ваш предпочитаемый рабочий процесс. Все, что требуется, - это исправление пробелов, которое конфликтует с одним изменением, внесенным в master, и каждый коммит будет конфликтовать и должен быть разрешен. И это простой сценарий с конфликтом только пробелов. Не дай Бог, у вас есть реальный конфликт, связанный с серьезными изменениями кода между файлами, и вам нужно разрешить , что несколько раз.
Со всем дополнительным разрешением конфликтов, которое вам нужно сделать, это просто увеличивает вероятность того, что вы совершите ошибку . Но ошибки в git хороши, так как вы можете отменить, верно? За исключением, конечно ...
Причина № 2: Отмена не отменяется!
Я думаю, что мы все можем согласиться с тем, что урегулирование конфликта может быть трудным, а также что некоторые люди очень плохи в этом. Он может быть очень подвержен ошибкам, поэтому он так хорош, что git позволяет легко его отменять!
Когда вы объединяете ветку, git создает коммит слияния, который можно отменить или изменить, если разрешение конфликта идет плохо. Даже если вы уже отправили неудачный коммит слияния в публичное / авторитетное репо, вы можете использовать git revert
, чтобы отменить изменения, внесенные слиянием, и правильно повторить слияние в новом коммите слияния.
Когда вы перебазируете ветку, в вероятном случае, если разрешение конфликта сделано неправильно, вы облажались. Каждый коммит теперь содержит неверное слияние, и вы не можете просто повторить ребаз *. В лучшем случае вы должны вернуться и внести поправки в каждый из затронутых коммитов. Не смешно.
После перебазирования невозможно определить, что изначально было частью коммитов, а что было введено в результате плохого разрешения конфликта.
* Может быть возможно отменить ребазинг, если вы можете выкопать старые ссылки из внутренних журналов git или если вы создадите третью ветку, которая указывает на последний коммит перед ребазингом.
Уберись от разрешения конфликта: используй diff3
Возьмем этот конфликт, например:
<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch
Глядя на конфликт, невозможно сказать, что изменилось в каждой ветви или каково ее намерение. По моему мнению, это самая главная причина, по которой разрешение конфликтов является запутанным и трудным.
diff3 на помощь!
git config --global merge.conflictstyle diff3
Когда вы используете diff3, каждый новый конфликт будет иметь 3-й раздел, объединенный общий предок.
<<<<<<< HEAD
TextMessage.send(:include_timestamp => true)
||||||| merged common ancestor
EmailMessage.send(:include_timestamp => true)
=======
EmailMessage.send(:include_timestamp => false)
>>>>>>> feature-branch
Сначала изучите объединенного общего предка. Затем сравните каждую сторону, чтобы определить намерение каждой ветви. Вы можете видеть, что HEAD изменил EmailMessage на TextMessage. Его цель - изменить используемый класс на TextMessage, передавая те же параметры. Вы также можете видеть, что намерение ветви функции - передать false вместо true для опции: include_timestamp. Чтобы объединить эти изменения, объедините намерение обоих:
TextMessage.send(:include_timestamp => false)
В целом:
- Сравните общего предка с каждой ветвью и определите, какая ветвь имеет самое простое изменение
- Примените это простое изменение к версии кода другой ветви, чтобы оно содержало как более простые, так и более сложные изменения
- Удалите все секции кода конфликта, кроме той, в которую вы только что слили изменения вместе, в
Альтернатива: разрешить вручную, применяя изменения ветви
Наконец, некоторые конфликты ужасны для понимания даже с diff3. Это происходит, особенно когда diff находит общие линии, которые не являются семантически общими (например, обе ветви имели пустую строку в одном и том же месте!). Например, одна ветвь изменяет отступ тела класса или изменяет порядок похожих методов. В этих случаях лучшей стратегией разрешения может быть изучение изменения с любой стороны слияния и ручное применение diff к другому файлу.
Давайте посмотрим, как мы можем разрешить конфликт в сценарии, где объединение origin/feature1
, где lib/message.rb
конфликтует.
Решите, является ли наша ветвь, извлеченная в данный момент (HEAD
, или --ours
), или ветвью, которую мы объединяем (origin/feature1
, или --theirs
), более простым изменением, чтобы применить. Использование diff с тройной точкой (git diff a...b
) показывает изменения, которые произошли в b
с момента его последнего отклонения от a
, или, другими словами, сравнивает общего предка a и b с b.
git diff HEAD...origin/feature1 -- lib/message.rb # show the change in feature1
git diff origin/feature1...HEAD -- lib/message.rb # show the change in our branch
Проверьте более сложную версию файла. Это удалит все маркеры конфликта и будет использовать выбранную вами сторону.
git checkout --ours -- lib/message.rb # if our branch's change is more complicated
git checkout --theirs -- lib/message.rb # if origin/feature1's change is more complicated
С проверкой сложного изменения поднимите различие более простого изменения (см. Шаг 1). Примените каждое изменение из этого diff к конфликтующему файлу.