La plupart des réponses précédentes sont dangereusement erronées!
Ne faites pas cela:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
Comme la prochaine fois que vous exécuterez git rebase
(ou git pull --rebase
) ces 3 commits seront éliminés en silence newbranch
! (voir explication ci-dessous)
Au lieu de cela, procédez comme suit:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- Tout d'abord, il supprime les 3 commits les plus récents (
--keep
est similaire --hard
, mais plus sûr, car échoue plutôt que de supprimer les modifications non validées).
- Ensuite, il bifurque
newbranch
.
- Ensuite, il sélectionne ces 3 commits
newbranch
. Puisqu'ils ne sont plus référencés par une branche, il le fait en utilisant le reflog de git : HEAD@{2}
est le commit qui HEAD
faisait référence à 2 opérations auparavant, c'est-à-dire avant que nous 1. vérifions newbranch
et 2. utilisé git reset
pour rejeter les 3 commits.
Attention: le reflog est activé par défaut, mais si vous l'avez désactivé manuellement (par exemple en utilisant un dépôt git "nu"), vous ne pourrez pas récupérer les 3 commits après avoir exécuté git reset --keep HEAD~3
.
Une alternative qui ne repose pas sur le reflog est:
# 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
(si vous préférez, vous pouvez écrire @{-1}
- la branche précédemment vérifiée - au lieu de oldbranch
).
Explication technique
Pourquoi git rebase
rejeter les 3 commits après le premier exemple? C'est parce git rebase
que sans argument active l' --fork-point
option par défaut, qui utilise le reflog local pour essayer d'être robuste contre la branche en amont poussée de force.
Supposons que vous ayez dérivé origine / master lorsqu'il contenait des validations M1, M2, M3, puis que vous ayez effectué trois validations vous-même:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
mais ensuite quelqu'un réécrit l'histoire en poussant force / origin pour supprimer M2:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
En utilisant votre reflog local, vous git rebase
pouvez voir que vous êtes issu d'une incarnation antérieure de la branche origine / maître, et donc que les validations M2 et M3 ne font pas vraiment partie de votre branche de sujet. Par conséquent, il suppose raisonnablement que puisque M2 a été supprimé de la branche en amont, vous ne le souhaitez plus dans votre branche de sujet une fois que la branche de thème est rebasée:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
Ce comportement est logique et est généralement la bonne chose à faire lors du rebasage.
Donc, la raison pour laquelle les commandes suivantes échouent:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
est parce qu'ils laissent le reflog dans le mauvais état. Git voit newbranch
comme ayant bifurqué la branche amont lors d'une révision qui inclut les 3 validations, puis reset --hard
réécrit l'historique de l'amont pour supprimer les validations, et donc la prochaine fois que vous l'exécutez, git rebase
il les supprime comme toute autre validation qui a été supprimée de l'amont.
Mais dans ce cas particulier, nous voulons que ces 3 commits soient considérés comme faisant partie de la branche du sujet. Pour y parvenir, nous devons bifurquer en amont lors de la révision précédente qui n'inclut pas les 3 commits. C'est ce que mes solutions suggérées font, donc elles laissent toutes deux le reflog dans l'état correct.
Pour plus de détails, voir la définition de --fork-point
dans les documents git rebase et git merge-base .