Вы можете получить это:
C--D <-- new-branch
/
A--B <-- master
, но для этого необходимо принудительно все Git репозитории, которые имеют ветвь с именем master
, указывающая на существующий коммит D
, чтобы убрать их master
для указания на существующий коммит B
. Это может быть сложно, поскольку Git «любит» добавить коммитов, но не «удалять» их.
Чтобы получить это локально, при условии, что у вас есть:
A--B--C--D <-- master (HEAD)
пробег:
git branch new-branch master
git reset --hard HEAD~2
Теперь у вас есть:
C--D <-- new-branch
/
A--B <-- master (HEAD)
локально. К сожалению, в другом Git хранилище в origin
все еще есть master
, указывающее на коммит D
. Любые клоны, сделанные из этого другого хранилища Git, также имеют их master
, указывающие на фиксацию D
. Вы должны заставить все эти репозитории делать одно и то же извлечение. Если есть только один репозиторий, и , у вас есть разрешение на использование git push --force
для его отвода, теперь вы можете:
git push origin new-branch # to create new-branch on origin
git push -f origin master # to force them to retract their master
(Вы можете добавить -u
к первый git push
.)
Чтобы получить более безопасную вещь, которую вы ищете - добавление нового коммита к master
, который сбрасывает его обратно в прежнее состояние B
- Вы можете использовать:
git branch temp master
, который устанавливает следующее:
A--B--C--D <-- master (HEAD), temp
Затем вы можете запустить git revert -n HEAD~2..HEAD; git commit
или git read-tree -u HEAD~2; git commit
, чтобы сделать новый коммит E
, чей моментальный снимок соответствует B
:
A--B--C--D <-- temp
\
E <-- master (HEAD)
Существующие коммиты C-D
застряли там, где они находятся. Если вы хотите, чтобы new-branch
был потомком master
, вы должны создать его. Обратите внимание, что если вы в порядке с new-branch
, указывающим на существующий коммит D
, вы можете просто использовать имя new-branch
вместо temp
, и вы получите то, что хотели. Но если вам нужен новый коммит (назовем его F
), который идет после E
, который соответствует содержимому коммита D
, вы можете создать новую ветку сейчас:
git checkout -b new-branch master
, давая:
A--B--C--D <-- temp
\
E <-- master, new-branch (HEAD)
Теперь вы можете:
- отменить фиксацию возврата
E
или git read-tree
совершить D
и зафиксировать, или git cherry-pick
фиксирует C
и D
.
Чтобы выполнить первое, запустите git revert master
или git revert HEAD
(достаточно), и вы получите:
A--B--C--D <-- temp
\
E <-- master
\
F <-- new-branch (HEAD)
Чтобы выполнить второе, запустите git read-tree -u temp; git commit
, чтобы сделать новый коммит F
(вам нужно будет ввести новое сообщение о коммите).
Чтобы сделать третий, запустите git cherry-pick temp~2..temp
; это даст:
A--B--C--D <-- temp
\
E <-- master
\
C'-D' <-- new-branch (HEAD)
, где имена C'
и D'
указывают, что это выбранные вишней копии C
и D
соответственно. (Когда мы вернулись, чтобы сделать E
, мы объединили оба этапа отмены, используя git revert -n
, поэтому E
- это не просто отмена D
или C
, а скорее отмена оба сразу.)
Теперь можно безопасно удалить имя temp
, так как оно нам больше не нужно, чтобы более удобно находить C
и D
. (Нам не нужно было все это время, так как мы могли бы их найти в любом случае, но было удобно иметь имя для коммита D
, чтобы коммиты C
и B
были temp~2
и temp~1
соответственно. )
Теперь безопасно (и не требует git push --force
) до git push origin master new-branch
.
В качестве альтернативы, вместо создания temp
, вы можете сделать имя new-branch
указывает на коммит D
все время. То есть, начиная с:
A--B--C--D <-- master (HEAD)
вы можете сделать:
git branch new-branch master
давая:
A--B--C--D <-- master (HEAD), new-branch
Затем вы можете "отменить" коммиты C-D
на master
как и раньше, например, производя:
A--B--C--D <-- new-branch
\
E <-- master (HEAD)
В качестве следующего шага вы можете выполнить:
git checkout new-branch; git merge -s ours master
, чтобы Git создать новое слияние коммит F
, чей снимок совпадает со снимком существующего коммита D
:
A--B--C--D---F <-- new-branch
\ /
E <-- master (HEAD)
Фиксация F
теперь опережает master
, как и раньше, но при чтении в обратном направлении по прямой линии, через ссылки первого родителя, он никогда не посещает коммит E
: Git возвращается от F
к D
до C
и так далее. Опять же, это при условии , который вы используете --first-parent
, когда git log
или git rev-list
перемещаетесь назад от F
. По умолчанию обратный путь от F
посещений обоих коммитов E
и D
(а так как по умолчанию go по порядку даты коммиттера, вы ' увидим E
, который мы сделали позже D
, перед тем как увидеть D
).