Как подробно объяснил Линус (https://mirrors.edge.kernel.org/pub/software/scm/git/docs/howto/revert-a-faulty-merge.txt):
Отмена обычного коммита просто эффективно отменяет то, что сделал этот коммит, и довольно проста. Но отмена коммита слияния также отменяет данные , что фиксация изменилась, но это абсолютно ничего не влияет на history , которое имело слияние.
Таким образом, слияние все еще будет существовать, и оно все равно будет рассматриваться как присоединение кдве ветви вместе, и будущие слияния увидят, что слияние является последним общим состоянием - и возвращение, которое отменило введенное слияние, не повлияет на это вообще.
Таким образом, «возврат» отменяет изменения данных, ноэто не"отмена" в том смысле, что он не отменяет влияния коммита на историю репозитория.
Хорошо, так что это объясняет, почему revert
не эффективная стратегия, но что мы можем сделать? Давайте рассмотрим следующее:
p---Q---r---s---M---t---u--- dev
\ /
A---B---C-------D---E feature
- ABC - это функция / ветка релиза
- M - плохое слияние, былигоИзменения от AB были отброшены, но C был сохранен
- DE позже работает на
feature
- Остальные несвязанные изменения в основной ветке
dev
Пока M существует в dev
, предполагается, что история ABC была интегрирована, хотя дельты AB отсутствуют.Чтобы восстановить их без изменения истории dev
, нам нужно воссоздать дельты в альтернативной истории (т.е. новые идентификаторы фиксации).
Если коммитов всего несколько, вы можете по отдельности cherrypick
каждый на dev
, поскольку cherrypicking копирует данные в новый идентификатор фиксации.Это, однако, не подходит для больших или сложных ветвей истории.
Следующий вариант - использовать rebase --no-ff
для воссоздания новой ветви feature
, из которой можно объединить потерянные изменения.
git checkout E
git rebase --no-ff Q
, что создает следующее:
A'--B'--C'-------D'--E' feature-fixed
/ \
p---Q---r---s---M---t---u---M2 dev
\ /
A---B---C--------D---E feature
Первоначальное слияние M, скорее всего, стало проблемой только из-за конфликтов слияния.Одна из проблем этого подхода заключается в том, что вы не только должны правильно разрешить исходный конфликт в ABC, но теперь у вас есть новый возможный источник конфликта в DE и TU, с которым нужно бороться.В чрезвычайной ситуации это может быть сложно определить, что происходит.
Предпочтительное решение:
p---Q---r---S-------M---t---u-----M3 dev
\ \ / /
A---B---\---C----D---E / feature
\ \ /
----M2---------- fix
Более простая стратегия с использованием инструментов, с которыми вы, вероятно, знакомы, состоит в том, чтобы правильно воссоздать слияние с помощью коммита squash
(M2).Это создает новый идентификатор фиксации (новую историю), и, таким образом, дельта из AB может быть успешно интегрирована обратно в основную линию.Этот метод также исключает возможные источники конфликта слияния, позволяя сначала исправить ошибку, а затем заняться изменениями в восходящем направлении.
Метод:
Ветвь от dev
до неудачного слияния (M).
git checkout -b fix S
Теперь у вас есть чистый лист, с которого вы можете выполнить исправленное слияние.Флаг сквоша сведет эти изменения в один коммит, но, что более важно, он сгенерирует новый идентификатор фиксации
git merge --squash C
Вероятно, в этот момент вам потребуется разрешить конфликты, однако M2 теперь представляет все данные Mдолжен был изначально содержаться.Теперь вы можете объединить это с dev, как обычно
git checkout dev
git merge fix
Снова могут возникнуть конфликты слияния, но после этого момента (M3) вы восстановили свои недостающие данные.Теперь вы можете действовать как обычно, например, вы можете объединить DE из feature
в dev
или любые другие ваши обычные операции.Это также означает, что другим членам команды не нужно переустанавливать свою локальную ветвь dev
, поскольку она восстановится при следующем выполнении pull
.