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 ( --keepest 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 quiHEADfaisait référence à 2 opérations auparavant, c'est-à-dire avant que nous 1. vérifionsnewbranchet 2. utiliségit resetpour 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 rebaserejeter les 3 commits après le premier exemple? C'est parce git rebaseque sans argument active l' --fork-pointoption 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 rebasepouvez 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 newbranchcomme ayant bifurqué la branche amont lors d'une révision qui inclut les 3 validations, puis reset --hardréécrit l'historique de l'amont pour supprimer les validations, et donc la prochaine fois que vous l'exécutez, git rebaseil 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-pointdans les documents git rebase et git merge-base .