La maggior parte delle risposte precedenti sono pericolosamente sbagliate!
Non farlo:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
La prossima volta che eseguirai git rebase
(o git pull --rebase
) quei 3 commit verrebbero scartati silenziosamente newbranch
! (vedi spiegazione sotto)
Invece fai questo:
git reset --keep HEAD~3
git checkout -t -b newbranch
git cherry-pick ..HEAD@{2}
- Per prima cosa elimina i 3 commit più recenti (
--keep
è come --hard
, ma più sicuro, poiché fallisce piuttosto che eliminare le modifiche non confermate).
- Quindi si stacca
newbranch
.
- Quindi raccoglie i 3 commit su Cherry
newbranch
. Dato che non sono più referenziati da un ramo, lo fa usando reflog di git : HEAD@{2}
è il commit che HEAD
faceva riferimento a 2 operazioni fa, vale a dire prima che noi 1. uscissimo newbranch
e 2. git reset
scartassimo i 3 commit.
Attenzione: il reflog è abilitato di default, ma se lo hai disabilitato manualmente (ad es. Utilizzando un repository git "nudo"), non sarai in grado di riavere i 3 commit dopo l'esecuzione git reset --keep HEAD~3
.
Un'alternativa che non si basa sul 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
(se preferisci puoi scrivere @{-1}
- il ramo precedentemente estratto - invece di oldbranch
).
Spiegazione tecnica
Perché git rebase
scartare i 3 commit dopo il primo esempio? È perché git rebase
senza argomenti abilita l' --fork-point
opzione per impostazione predefinita, che utilizza il reflog locale per cercare di essere robusto contro il ramo a monte che viene forzato.
Supponiamo che tu abbia ramificato origin / master quando conteneva commit di M1, M2, M3, quindi hai effettuato tre commit da solo:
M1--M2--M3 <-- origin/master
\
T1--T2--T3 <-- topic
ma poi qualcuno riscrive la storia spingendo forzatamente origin / master per rimuovere M2:
M1--M3' <-- origin/master
\
M2--M3--T1--T2--T3 <-- topic
Usando il tuo reflog locale, git rebase
puoi vedere che hai biforcato da una precedente incarnazione del ramo origine / master, e quindi che i commit M2 e M3 non fanno realmente parte del ramo topic. Quindi si presume ragionevolmente che da quando M2 è stato rimosso dal ramo a monte, non lo si desidera più nemmeno nel ramo argomento una volta che il ramo argomento è stato modificato:
M1--M3' <-- origin/master
\
T1'--T2'--T3' <-- topic (rebased)
Questo comportamento ha un senso ed è generalmente la cosa giusta da fare durante il rebasing.
Quindi il motivo per cui i seguenti comandi falliscono:
git branch -t newbranch
git reset --hard HEAD~3
git checkout newbranch
è perché lasciano il reflog nello stato sbagliato. Git vede newbranch
come aver biforcato il ramo upstream in una revisione che include i 3 commit, quindi reset --hard
riscrive la cronologia dell'upstream per rimuovere i commit, quindi la prossima volta che li esegui git rebase
li scarta come qualsiasi altro commit che è stato rimosso dall'upstream.
Ma in questo caso particolare vogliamo che quei 3 commit vengano considerati come parte del ramo dell'argomento. Per raggiungere questo obiettivo, dobbiamo sborsare l'upstream alla revisione precedente che non include i 3 commit. Questo è quello che fanno le mie soluzioni suggerite, quindi entrambi lasciano il reflog nello stato corretto.
Per maggiori dettagli, consultare la definizione di --fork-point
nei documenti git rebase e git merge-base .