Большинство предыдущих ответов опасно неверны!
Не делайте этого:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
Поскольку в следующий раз, когда вы запустите git rebase
(или git pull --rebase
), эти 3 коммита будут автоматически удалены из newbranch
! (см. объяснение ниже)
Вместо этого сделайте следующее:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- Сначала он отбрасывает 3 самых последних коммита (
--keep
похож на --hard
, но безопаснее, так как не выполняется, а не отбрасывает незафиксированные изменения).
- Потом отваливается
newbranch
.
- Затем он отбирает эти 3 коммита обратно на
newbranch
. Поскольку на них больше не ссылается ветка, это делается с помощью журнала ссылок git.: HEAD@{2}
- это фиксация, которая HEAD
использовалась для ссылки на 2 операции назад, то есть до того, как мы 1. проверили newbranch
и 2. использовали git reset
для отмены 3-х фиксаций.
Предупреждение: журнал ссылок включен по умолчанию, но если вы отключили его вручную (например, с помощью "чистого" репозитория git), вы не сможете вернуть 3 фиксации после выполнения _14 _. em>
Альтернатива, которая не зависит от рефлога:
# newbranch will omit the 3 most recent commits.
git checkout -b newbranch HEAD~3
git branch --set-upstream-to=oldbranch
# Cherry-picks the extra commits from oldbranch.
git cherry-pick ..oldbranch
# Discards the 3 most recent commits from oldbranch.
git branch --force oldbranch oldbranch~3
(при желании вы можете написать @{-1}
- ранее проверенная ветка - вместо oldbranch
).
Техническое объяснение
Зачем git rebase
отбрасывать 3 коммита после первого примера? Это связано с тем, что git rebase
без аргументов по умолчанию включает параметр --fork-point
, который использует локальный журнал ссылок, чтобы попытаться быть устойчивым к принудительному выталкиванию восходящей ветки.
Предположим, вы разветвились от origin / master, когда он содержал коммиты M1, M2, M3, а затем сами сделали три коммита:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
но затем кто-то переписывает историю, принудительно нажимая origin / master для удаления M2:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
Используя ваш локальный журнал ссылок, git rebase
может увидеть, что вы разветвились из более раннего воплощения ветки origin / master, и, следовательно, коммиты M2 и M3 на самом деле не являются частью вашей тематической ветки. Следовательно, это разумно предполагает, что, поскольку M2 был удален из ветки восходящего потока, он больше не нужен вам в ветке темы после того, как ветка темы будет перебазирована:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
Такое поведение имеет смысл и обычно является правильным при перебазировании.
Итак, причина того, что следующие команды не работают:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
потому что они оставляют рефлог в неправильном состоянии. Git видит newbranch
как ответвление ветки восходящего потока в ревизии, включающей 3 коммита, затем reset --hard
переписывает историю восходящего потока, чтобы удалить коммиты, и поэтому в следующий раз, когда вы запустите git rebase
, он отбрасывает их, как любой другой коммит, который был удален из восходящий поток.
Но в данном конкретном случае мы хотим, чтобы эти 3 коммита рассматривались как часть тематической ветки. Для этого нам нужно разветвить апстрим в более ранней версии, которая не включает 3 коммита. Это то, что делают мои предлагаемые решения, поэтому они оба оставляют рефлог в правильном состоянии.
Подробнее см. Определение --fork-point
в git rebase и git merge-base docs.
person
John Mellor
schedule
06.04.2016