Как отменить неотправленное слияние
Если вы выполнили слияние, но ещё не оттолкнули его, отменить слияние очень просто.
Используйте команду git reset
, чтобы вернуться к ревизии до слияния, тем самым фактически отменив его.
$ git reset --hard <commit-hash-before-merge>
Если у вас под рукой нет хэша коммита перед слиянием, вы можете воспользоваться командой git log
, чтобы узнать его, или использовать следующий вариант команды reset:
$ git reset --hard HEAD~1
Таким образом, используя HEAD~1
, вы говорите git’у вернуться к коммиту перед текущей ревизией HEAD
, который должен быть коммитом перед слиянием.
Примечание: В обоих случаях нам нужно использовать опцию --hard
. Это означает, что все локальные незафиксированные изменения будут отброшены — если у вас есть ценные незафиксированные изменения, обязательно используйте git stash перед этим.
После выполнения вышеуказанного git reset
вы заметите, что все изменения после слияния будут отменены, и вы сможете продолжить работу в обычном режиме.
Как отменить сдвинутое слияние
Отмена сдвинутого слияния немного сложнее. Прежде чем приступить к этому, нам нужно узнать немного больше о коммитах и слияниях git.
Что такое родительский коммит?
У каждого коммита есть родительский коммит. Когда вы git commit
, текущий коммит становится родительским для нового коммита, который вы создаёте.
Чтобы просмотреть родительский коммит, используйте git show --pretty=raw <commit-hash>
.
Что такое коммит слияния?
Когда ветвь с именем feature
объединяется с ветвью master
, создаётся новый «merge commit» на ветви master
. Коммит слияния такой же, как и обычный коммит, за исключением того, что у него есть 2 родителя. В данном случае двумя родителями коммита слияния будут предыдущая голова master
и голова feature
.
При запуске git show
новый коммит отобразит обоих родителей:
$ git show
commit ae2058cf5cafe807af44114d15bac65fc4efd714 (HEAD -> master)
Merge: bf75d61d8f1 12d62bfa0e0
Как работает git revert?
git revert <commit-hash>
Команда revert в git принимает хэш коммита и сравнивает изменения с родительским. Вычисляется дельта или разница, и её отрицание применяется как новый коммит.
Что происходит, если мы хотим вернуть коммит слияния? Коммит слияния имеет двух родителей, и git не знает автоматически, кто из родителей был основной веткой, а кто — веткой, которую вы хотите отменить.
Отмена коммита слияния
$ git revert -m 1 <merge-commit-hash>
Давайте рассмотрим подробнее, что делает эта команда:
- Опция
-m 1
говорит git’у, что мы хотим сохранить родительскую сторону слияния (то есть ветвь, в которую мы слились). - Наконец, не забудьте указать хэш фактического коммита слияния.
Опция -m
указывает номер родителя. Это делается потому, что у коммита слияния есть более одного родителя, и git не знает автоматически, какой родитель был основным, а какой — ответвлением, которое вы хотите отменить.
Когда вы просматриваете коммит слияния в выводе git log
, вы увидите список его родителей в строке, начинающейся с Merge:
.
commit ae2058cf5cafe807af44114d15bac65fc4efd714
Merge: bf75d61d8f1 12d62bfa0e0
Author: Michael Seymour <michael@example.com>
Date: Sun May 08 13:52:29 2022 +0100
Merge branch 'my-branch'
В этой ситуации git revert ae2058cf5ca -m 1
вернёт вам дерево, каким оно было в bf75d61d8f1
, а git revert -m 2
восстановит дерево, каким оно было в 12d62bfa0e0
.
Чтобы лучше понять родительские коммиты, вы можете выполнить команду:
Почему мы не можем использовать git reset в этот раз?
git revert
обеспечит создание нового коммита для отмены последствий этого нежелательного слияния. Это противоположно git reset
, где мы фактически «удаляем» коммит из истории. Следовательно, git revert
является лучшим решением в случаях, когда вы уже переместили репозиторий на удалённое место, так как изменение истории удалённого репозитория может вызвать проблемы у других разработчиков, использующих этот репозиторий.
Ссылки
- https://www.git-tower.com/learn/git/faq/undo-git-merge
- https://stackoverflow.com/q/7099833
- https://levelup.gitconnected.com/reverting-a-merge-commit-7de2e9114c7d
- https://git-school.github.io/visualizing-git/#free