Отменить слияние без отмены коммитов, выполненных после

У меня маленькая проблема. Мне нужно отменить слияние в моем удаленном хранилище без отмены коммитов, сделанных после. Смотрите ниже мою модель и мои ожидания.

В настоящее время у меня есть эта модель:

введите описание изображения здесь

Я хотел бы это:

введите описание изображения здесь

Я проверил документы и поиск в Интернете, но я не нашел никакого умного решения. У тебя есть идея для меня? Я бы позаботился о моем слиянии на будущее!

Большое вам спасибо!

3 ответа

Решение

Вы можете либо переписать историю веток, либо отменить слияние. У каждого есть свои плюсы и минусы.

Сначала давайте начнем с немного измененной копии вашей диаграммы текущего состояния, которая отражает немного больше того, что происходит.

A -- B -- C <--(branch>
           \
  M -- N -- O -- P -- Q <--(master)

Вы не показывали никаких ссылок, поэтому я предполагаю master указывает на Q, Если у вас нет филиала в branch вы, вероятно, должны создать его (если вы не отменяете изменения A, B, а также C). Кроме того, незначительная нотация, но я переключил все коммиты на буквы (так как иногда это может быть понятнее).


Переписать историю

Все обычные предупреждения о переписывании истории применимы к этому подходу. В основном это означает, что если master толкнул так, что кто-то еще уже "видел" совершить O как часть masterс историей, то вам придется согласовать с ними, чтобы успешно сделать переписывание истории. Переписать поставлю свою копию master в плохом состоянии, из которого они должны будут оправиться, и если они сделают это неправильно - как они могли бы, если бы вы не сообщили о том, что происходит - тогда ваша работа может быть отменена. См. "Восстановление после восходящей версии" в git rebase документы для получения дополнительной информации, так как это является применимым условием независимо от того, используете ли вы на самом деле 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)

Старые коммиты все еще существуют (и, как я показал здесь, рефлог все еще может достигать их - в данный момент локально). Поскольку большинство инструментов не следуют журналу, вы, вероятно, увидите что-то более похожее

A -- B -- C <--(branch>

M -- N -- P' -- Q` <--(master)

И действительно, после истечения срока действия reflog это именно то, что останется (если вы не сделаете что-то, чтобы сохранить старые коммиты тем временем). На данный момент, чтобы подтолкнуть master вам придется сделать силовой толчок. Самый безопасный способ сделать это

git push --force-with-lease

Обычно люди просто рекомендуют -f вариант, но это менее безопасно, так как он может совершать коммиты, о которых вы не знаете, на удаленном master, В любом случае после принудительного толчка наступает момент, когда кто-либо еще с копиями master должен будет восстановиться после состояния "upstream rebase".

Другие способы перезаписи (например, путем сброса и последующего выбора вишни) функционально эквивалентны (за исключением нескольких странных крайних случаев), но они более ручные и, следовательно, более подвержены ошибкам. Стоит повторить, хотя такие альтернативы могут не использовать 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

Один из способов - сбросить настройки последнего коммита до O который не принадлежит ветви 5 - 6 - 7 т.е. N в этом случае. Тогда вишня выберет все необходимые коммиты т.е. P & Q, Пример ниже:

git reset --hard N         #This resets the branch to N & removes all commits of merged branch `5 - 6 - 7`
git cherry-pick P          #Manually add the 2 commits you want back.
git cherry-pick Q

Другой способ - отменить коммит слияния с помощью следующей команды:

git revert -m 1 O .     #this is alphabet O - merge commit id. Not Numeric zero.

Это добавит новый коммит поверх Q - Давайте назовем это как O', где

  • O' обратный коммит O

предостережение: если вы попытаетесь внести некоторые изменения в 5 - 6 - 7 переходите в будущее и объединяйте его снова - он не объединит коммиты 5, 6, 7 потому что эти идентификаторы коммитов уже находятся в этой ветке, а также существует обратный коммит этих коммитов поверх них.

Это означает, что вы никогда не сможете объединять коммиты 5, 6, 7,

Хотя существуют механизмы, с помощью которых вы можете изменить идентификаторы коммитов, выполнив перебазировку или сделав тривиальное изменение просто ради изменения идентификаторов, это приведет к конфликту слияния при идентичных изменениях. Лично я бы не рекомендовал такой подход.

Поскольку вы ничего не упоминаете о ветках, которые у вас есть, я предлагаю начать с создания веток (b7 а также bkp) в существующих коммитах, чтобы они оставались видимыми после изменения:

        +----- b7
        v
5 - 6 - 7       +-------- bkp
        |       v
M - N - O - P - Q
                ^
                +-------- bQ

Так как у вас, вероятно, есть ветка, которая указывает на Q Вы можете использовать его вместо bQ в командах ниже.

затем git rebase коммиты P а также Q на вершине коммита N чтобы получить структуру, которую вы желаете.

Команды:

# Preparations
# Create the branch `b7` to keep the commit `7` reachable after the change
git branch b7 7
# Create the branch `bkp` for backup (the commit `Q` will be discarded)
git branch bkp Q

# The operation
# Checkout `bQ` to start the change
git checkout bQ
# Move the `P` and `Q` commits on top of `N`
# Change "2" and "3" in the command below with the actual number of commits
# 2 == two commits: P and Q, 3 == 2 + 1
# Or you can use commit hashes (git rebase --onto N O)
git rebase --onto HEAD~3 HEAD~2

Теперь хранилище выглядит так:

        +----- b7
        v
5 - 6 - 7       +-------- bkp
        |       v
M - N - O - P - Q
    |
    P'- Q'
        ^
        +-------- bQ

Старый O, P а также Q коммиты все еще там, и они все еще доступны, пока ветвь bkp или любые другие ветви, которые указывают на Q все еще существует. Если вы довольны изменением, вы можете удалить bkp и любые другие ветви, которые указывают на Q у вас есть.

Команда:

git branch -D bkp

Вы, вероятно, не хотите удалять b7 если у вас уже нет другой ветки, которая указывает на фиксацию 7 уже. В этом случае вам даже не нужно создавать b7,

После этой операции репо выглядит так:

        +----- b7
        v
5 - 6 - 7

M - N - P'- Q'
            ^
            +-------- bQ

Обратите внимание, что коммиты P' а также Q' отличаются от оригинальных коммитов P а также Q,

Предупреждение

Если совершает O, P а также Q были уже переданы в удаленный репозиторий (через bQ ветка), толкая bQ опять не получится. Толкание может быть принудительным (git push --force origin bQ) но это действие запутает ваших коллег, которые уже получили текущую позицию bQ ветвь (она содержит O, P а также Q совершает).

Если вам действительно нужно выполнить этот трюк, обязательно сообщите всем об этом изменении.

Лучшим подходом в этой ситуации является git revert -m совершить O, Это создает новый коммит поверх Q вносит изменения, отменяющие изменения, внесенные коммитом O, Это некрасиво, но это самое безопасное решение.

Другие вопросы по тегам