Вы можете переписать историю веток или отменить слияние. У каждого есть свои плюсы и минусы.
Сначала давайте начнем с немного измененной копии вашей диаграммы текущего состояния, которая отражает немного больше того, что происходит.
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master)
Вы не показывали никаких ссылок, поэтому я предполагаю, что master
указывает на Q
. Если у вас нет ветки на branch
, вам, вероятно, следует создать ее (если только вы не отменяете постоянно изменения A
, B
и C
). Кроме того, небольшая нотация, но я переключил все коммиты на буквы (так как иногда это может быть понятнее).
Переписать историю
Все обычные предупреждения о переписывании истории применимы к этому подходу. В основном это означает, что если master
был выдвинут так, что кто-то еще «видел» коммит O
как часть истории master
, то вам придется согласовать с ними успешную перезапись истории. Переписанный переводит их копию master
в плохое состояние, из которого они должны будут восстановиться, и если они сделают это неправильно - как они могли бы, если бы вы не сообщили о том, что происходит - тогда ваша работа может быть отменена , Для получения дополнительной информации см. «Восстановление из исходной перебазировки» в git rebase
документах, поскольку это применимо к условию, действительно ли вы используете команду rebase
для выполнения перезаписи.
Если вы хотите переписать, самый простой способ - перебазировать. Вам понадобятся либо идентификаторы коммитов N
и O
, либо выражения, разрешающие коммиты N
и O
. В этом примере мы можем использовать master~3
для N
и master~2
для O
.
git rebase --onto master~3 master~2 master
Это возьмет изменения, достижимые с master
, но не достижимые с O
, и воспроизведет их на N
; а затем переместите master
к переписанным коммитам.
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q <--(master@{1})
\
P' -- Q` <--(master)
Старые коммиты все еще существуют (и, как я показал здесь, reflog все еще может достигать их - на данный момент локально). Поскольку большинство инструментов не следуют журналу, вы, вероятно, увидите что-то более похожее на
A -- B -- C <--(branch>
M -- N -- P' -- Q` <--(master)
И действительно, после истечения срока действия reflog это именно то, что останется (если вы не сделаете что-то, чтобы сохранить старые коммиты за это время). На этом этапе, чтобы нажать master
, вам нужно сделать принудительный толчок. Самый безопасный способ сделать это -
git push --force-with-lease
Обычно люди рекомендуют просто параметр -f
, но это менее безопасно, так как это может привести к прерыванию коммитов, о которых вы не знаете, на пульте master
. В любом случае, после принудительного толчка наступает момент, когда кому-либо еще с копиями master
необходимо будет восстановиться после условия «восходящего восстановления».
Другие способы перезаписи (например, путем сброса и последующего выбора вишни) функционально эквивалентны (за исключением нескольких странных крайних случаев), но они более ручные и, следовательно, более подвержены ошибкам. Стоит повторить, что, хотя такие альтернативы могут не использовать команду rebase
, ситуация с «восходящей перебазировкой» все равно будет действовать точно так же.
без перезаписи
Если перезапись истории неосуществима - как это часто бывает в широко распространенных репозиториях - альтернатива - отменить коммит слияния. Это создает новый коммит, который «отменяет» изменения, внесенные слиянием. Чтобы использовать revert
в коммите слияния, вы должны задать опцию -m
(которая сообщает revert
, какой родитель должен вернуть в ; если вы пытаетесь отменить эффект слияния, это обычно -m 1
).
Снова вам нужен идентификатор или выражение, которое разрешается в O
; мы будем использовать master~2
в примере.
git checkout master
git revert -m 1 master~2
Теперь у вас есть
A -- B -- C <--(branch>
\
M -- N -- O -- P -- Q -- !O <--(master)
, где !O
отменяет изменения, которые O
применены к N
.
Как отмечалось в другом месте, git видит branch
как "уже учтенный" - он не отслеживает, что изменения !O
были предназначены для возврата / отката O
или чего-то подобного. Поэтому, если позже вы захотите сказать git merge branch
, он пропустит коммиты A
, B
и C
.
Один из способов исправить это с помощью rebase -f
. Например, после возврата вы можете сказать
git rebase -f master~3 branch
и все коммиты, доступные с branch
, но недоступные с master
до слияния на O
, будут переписаны. Конечно, это переписать branch
. Поскольку вы, возможно, использовали подход revert
, чтобы избежать перезаписи master
, вы также можете не захотеть переписывать branch
. (Если вы переписываете branch
и если branch
используется совместно с другими репозиториями, вам нужно будет push --force-with-lease
, а другим пользователям придется восстанавливаться после исходной перебазировки.)
Другой вариант, когда вы хотите объединить branch
обратно в master
, - это «отменить возврат». Предположим, прошло некоторое время с тех пор, как вы отменили слияние, и у вас есть
A -- B -- C -- D -- E <--(branch>
\
M -- N -- O -- P -- Q -- !O -- R -- S <--(master)
Теперь, чтобы объединить branch
с master
, можно сказать
git checkout master
git revert master~2
git merge branch