Большинство предыдущих ответов опасно неправильны!
Не делайте этого:
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 reflog : HEAD@{2}коммит, который HEADиспользовался для ссылки на 2 операции назад, то есть до того, как мы 1. извлекли newbranchи 2. использовали git resetдля отмены 3 коммитов.
Предупреждение: reflog включен по умолчанию, но если вы отключили его вручную (например, с помощью «чистого» хранилища git), вы не сможете вернуть 3 коммитов после запуска git reset --keep HEAD~3.
Альтернатива, которая не зависит от reflog:
# 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по умолчанию эта опция включена, которая использует локальный журнал reflog, чтобы попытаться быть устойчивым к принудительной ветке восходящей ветки.
Предположим, что вы разветвились от 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
Используя ваш локальный reflog, 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
потому что они оставляют reflog в неправильном состоянии. Git считает, newbranchчто он раздвоил ветку upstream в ревизии, которая включает в себя 3 коммита, затем reset --hardпереписывает историю апстрима для удаления коммитов, и поэтому при следующем запуске git rebaseон отбрасывает их, как и любой другой коммит, который был удален из апстрима.
Но в данном конкретном случае мы хотим, чтобы эти 3 коммита рассматривались как часть ветки темы. Чтобы достичь этого, нам нужно отключить апстрим в более ранней ревизии, которая не включает 3 коммита. Это то, что делают мои предложенные решения, поэтому они оба оставляют reflog в правильном состоянии.
Для получения дополнительной информации см. Определения --fork-pointв документах git rebase и git merge-base .