Обновление - я написал оригинальный ответ в два этапа, и это немного сбивает с толку. Информация остается прежней, но я ее немного чищу ...
В зависимости от того, как вы squash
редактировали коммиты, у вас есть одна из двух очень похожих ситуаций. Грубо говоря:
1) Если вы раздавились от одной ветви к другой (например, git merge --aquash
), у вас есть график, подобный
x -- AB!MCD -- E <--(branch_for_pr)
A -- B -- !M -- C -- D <--(work_branch)
, где !M
отменил некоторое предыдущее слияние, а AB!MCD
- это "сквош" из A
, B
, !M
, C
и D
.
2) Если вы раздавили коммиты «на месте» на ветке, например, git rebase -i
, график больше похож на
x -- AB!MCD -- E <--(branch_for_pr)
A -- B -- !M -- C -- D <--(branch_for_pr@{2})
Здесь branch_for_pr@{2}
- запись reflog. Инструменты не отображают записи reflog так же явно, как текущие ссылки, поэтому может показаться, что A
.. D
«пропали», но (по крайней мере, в локальном клоне, где произошел сквош), они, надеюсь, не вернутся. т. (Это правда, что срок действия записей reflog истекает - по умолчанию примерно через 3 месяца или когда вы явно их истекаете. По истечении срока действия записи reflog gc
может удалить старые коммиты, если за это время вы что-то не сделали что делает их "доступными". Кроме того, reflogs не делятся на push
или fetch
.)
Вы можете найти запись reflog с помощью команды, подобной
git reflog branch_for_pr
и затем может поместить новую ветку или тег на него, пока вы не узнаете, что он вам больше не нужен, чтобы устранить риск того, что истечение срока действия рефлога скрыло бы его от вас. Если вы сделаете это, результат будет выглядеть как первая диаграмма (из (1) выше).
В остальной части этой записи предполагается, что фактическая ветвь указывает на D
. Итак ... как поступить?
Самое простое, что нужно сделать - вернуть !M
на branch_for_pr
. Это может показаться нелогичным, но вы можете изменить практически все; это не должно быть предком HEAD
. Поэтому вам нужно выражение, которое разрешается в !M
- это может быть его идентификатор фиксации или в этом примере что-то вроде work_branch~2
.
git checkout branch_for_pr
git revert work_branch~2
Несколько точек фона, которые следует знать здесь:
Фиксация - это просто снимок проекта с небольшим количеством метаданных. Метаданные включают в себя «родительский» список, который помещает снимок относительно других снимков в истории. Многие команды git работают с патчем коммита - то есть разницей между коммитом и его родителем (или одним из его родителей, в случае слияний). Метаданные коммита не включают какие-либо особые отношения для «коммит, возвращенный этим коммитом», или «коммит (и), заключенный в этот коммит»; эта информация по существу потеряна.
Как подразумевается выше, «возврат» - это просто краткий способ собрать коммит, который определенным образом относится к его родителю, управляемый отношениями другого коммита с его родителем. git
не увековечивает, что этот конкретный коммит был сгенерирован путем возврата другого коммита (намного меньше, чем тот, который был другим коммитом).
Аналогично, "сквош" - это просто сокращенный способ указания группы изменений, которые вы хотите применить к HEAD
для создания нового коммита. Полученный коммит не «знает», что это сквош.
Так что эти вещи могут изменить ваш взгляд на то, что вы пытаетесь сделать, и повлиять на то, как вы подходите к этому вопросу. И все же более неотложная проблема для предлагаемого решения «повторного слияния» заключается в том, что git немного странно относится к возвратам слияний. После того, как вы отменили слияние, его не так просто восстановить. Вы должны либо заново сгенерировать ветку из новых коммитов (чтобы сделать возможным повторное слияние), либо просто отменить возврат - это то, что я рекомендую сделать выше, прямо в вашей последней ветке.
Есть много вариантов этой идеи, которые могут привести к "более чистым" историям; но это самое простое и позволяет избежать еще более грязных вещей.