Каков наилучший способ вернуть утраченные изменения ...
Каков лучший способ съесть яйцо? Начать с большого конца или с малого конца? ( ref )
Или, другими словами, лучший способ не существует. Есть только способы, которые работают или не работают. Эти способы могут быть проще или сложнее, или могут быть лучше для вашей конкретной ситуации, или нет.
Два относительно простых варианта:
- cherry-pick диапазон фиксирует, используя
git cherry-pick
, или - . Выберите из слияния, используя
git revert -m
(но это немного сложно объяснить).
Cherry-picking
Сбор вишни - это, по сути, процесс копирования коммита. По сути, мы Git сравниваем коммит с его родительским коммитом. То есть, предположим, что у нас есть серия коммитов, подобных этой:
...--o--o <-- master
\
o--o--P--C--o--o <-- feature (HEAD)
При коммите P
(родитель), работая над веткой feature
, мы обнаруживаем некоторую неприятную ошибку и исправляем ее в child. совершить C
. Мы хотели бы сделать немедленное исправление master
. Мы могли бы git checkout master; git merge <hash-of-C>
слиться с C
, но это привело бы к еще трем коммитам: два до P
, затем P
. Итак, мы запускаем git checkout master
, давая нам:
...--o--o <-- master (HEAD)
\
o--o--P--C--o--o <-- feature
Теперь мы запускаем git cherry-pick <hash-of-C>
. Git сравнивает снимок в коммите P
со снимком в коммите C
, чтобы увидеть, как мы исправили ошибку, затем вносит те же самые изменения в наш текущий индекс и рабочее дерево и фиксирует В результате мы получим копию сообщения фиксации от C
, что даст нам:
...--o--o--C' <-- master (HEAD)
\
o--o--P--C--o--o <-- feature
В результате мы скопировали коммит C
в новый коммит C'
. Он имеет то же сообщение , что и C
, и делает то же самое с кодом , что и C
с P
, но это другой коммит, в другом месте на графике.
Использование этого для исправления слияния
У вас есть:
C1-D1-E1
/ \
A--B F--G <-- somebranch (HEAD)
\ /
C2-D2-E2
, где F
- ошибочное слияние (возможно, сделано с git merge -s ours
) это отбрасывает часть или всю работу с C2-D2-E2
. При выборе этих трех коммитов Cherry выберет три новых коммита, каждый из которых выполняет то же, что и эти три коммита.
Чтобы выбрать C2
, D2
и E2
с помощью одной простой команды, при условии, что что F^2
равно E2
(т. е. E2
является вторым родителем F
), вы можете выполнить:
git cherry-pick F^2~3..F^2
(заменить F
на соответствующий идентификатор ha sh или используйте HEAD^
для присвоения имени через commit G
, например, git cherry-pick HEAD^^2~3..HEAD^^2
).
Cherry-pick против revert
Иногда мы Мне нравится не столько копировать коммит, сколько отменить его. То есть, предположим, что мы имеем:
...--o--P--C--o--o <-- somebranch (HEAD)
Просматривая историю, мы понимаем, что commit C
- это просто ошибка. Если бы мы могли просто отменить это, мы были бы в порядке. Вот где приходит git revert
. Мы говорим Git git revert <hash-of-C>
и Git сравнивает P
и C
, чтобы увидеть, что изменилось, затем отменяет это изменение. В результате получается новый коммит, противоположный C
(я нарисую его как 3
, который не так хорош, как Ↄ, но Ↄ может быть не во всех шрифтах, и он странным образом отображается в компьютерном тексте в моем браузере ):
...--o--P--C--o--o--3 <-- somebranch (HEAD)
Технически, и вишня, и реверс используют Git двигатель слияния . Они устанавливают базовую фиксацию слияния на P
для cherry-pick, и два входа - HEAD
и C
; или, для возврата, база слияния равна C
, а два входа - HEAD
и P
. Git затем выполняет полное трехстороннее слияние, и, если все идет хорошо, фиксирует результат как обычный коммит с коммитом HEAD
в качестве родителя нового коммита. Но эта техническая заметка нужна вам только в том случае, если вы должны разрешить конфликты слияния - эффект выбора или возврата вишни заключается в повторном применении или отмене изменений, найденных путем сравнения коммита с его родителем.
Выбор вишни или возврат слияния
* коммит слияния - один с по крайней мере двумя родителями (и большинство имеет только двух родителей). Так как сбор вишни работает путем сравнения коммита с его родителем, нет очевидного способа выбрать вишню слияния. У него два родителя и Git не могут сравниваться с обоими одновременно. 1 Но это также означает, что не существует очевидного способа вернуть слияние, и Git нужен способ отменить слияние. Так что и git cherry-pick
, и git revert
допускают опцию, -m
. Когда вы даете эту опцию, вы также указываете родительский номер . Git затем делает вид, что на время выполнения команды cherry-pick или merge у коммита слияния есть только один родительский: тот, которого вы выбрали.
Это означает, что если мы запустим :
git revert -m 2 <hash-of-F>
, где F
- ваше неудачное объединение, Git сравнит снимок в F
со снимком в E2
, чтобы увидеть, что изменилось. Если вы вручную запустите тот же diff, используя F
в качестве первого коммита и E2
в качестве второго:
git diff F F^2
, вы увидите изменения, которые Git попытается применить.
Если вы запустите этот git revert
, вы, вероятно, захотите добавить --edit
, чтобы вы могли редактировать сообщение фиксации, если Git может применить эти изменения самостоятельно. Это связано с тем, что стандартное сообщение для возврата особенно плохое для этого случая: Git будет просто утверждать, что новый коммит отменяет фиксацию F
, тогда как на самом деле вы восстанавливаете код, который был утерян по пути.