Risposte:
Usando git rebase. È il generico comando "take commit (s) e plop / loro su un altro genitore (base)" in Git.
Alcune cose da sapere, tuttavia:
Poiché gli SHA di commit coinvolgono i loro genitori, quando cambi il genitore di un determinato commit, il suo SHA cambierà, così come gli SHA di tutti i commit che lo seguono (più recenti di quelli) nella linea di sviluppo.
Se lavori con altre persone e hai già spinto il commit in questione in pubblico nel punto in cui l'hanno portato, modificare il commit è probabilmente una Bad Idea ™. Ciò è dovuto al n. 1, e quindi alla conseguente confusione che incontreranno i repository degli altri utenti quando provano a capire cosa è successo a causa dei tuoi SHA che non corrispondono più ai loro per gli "stessi" commit. (Vedere la sezione "RECUPERO DALLA REVISIONE DI UPSTREAM" della pagina man collegata per i dettagli.)
Detto questo, se sei attualmente in una filiale con alcuni commit che vuoi spostare in un nuovo genitore, sarebbe simile a questo:
git rebase --onto <new-parent> <old-parent>
Ciò sposterà tutto ciò che segue <old-parent> nel ramo corrente per sedersi in cima <new-parent>invece.
--ontoserve! La documentazione è sempre stata completamente oscura per me, anche dopo aver letto questa risposta!
git rebase --onto <new-parent> <old-parent>mi ha detto tutto su come usare rebase --ontoogni altra domanda e documento che ho letto finora non è riuscito.
rebase this commit on that oneo git rebase --on <new-parent> <commit>. Usare il vecchio genitore qui non ha senso per me.
git rebase <new-parent>.
Se si scopre che è necessario evitare di riassegnare i commit successivi (ad es. Perché una riscrittura della cronologia sarebbe insostenibile), è possibile utilizzare il comando di sostituzione git (disponibile in Git 1.6.5 e versioni successive).
# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.
replace_first_parent() {
old_parent=$(git rev-parse --verify "${1}^1") || return 1
new_parent=$(git rev-parse --verify "${2}^0") || return 2
new_commit=$(
git cat-file commit "$1" |
sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
git hash-object -t commit -w --stdin
) || return 3
git replace "$1" "$new_commit"
}
replace_first_parent B A
# …---o---A---o---o---…
# \
# C---b---b---…
#
# C is the replacement for B.
Con la sostituzione sopra stabilita, qualsiasi richiesta per l'oggetto B restituirà effettivamente l'oggetto C. I contenuti di C sono esattamente gli stessi del contenuto di B ad eccezione del primo genitore (stessi genitori (tranne il primo), stesso albero, stesso messaggio di commit).
I sostituti sono attivi per impostazione predefinita, ma possono essere disattivati usando l' --no-replace-objectsopzione per git (prima del nome del comando) o impostando la GIT_NO_REPLACE_OBJECTSvariabile di ambiente. I ricambi possono essere condivisi premendo refs/replace/*(oltre al normalerefs/heads/* ).
Se non ti piace il commit-munging (fatto con sed sopra), puoi creare il tuo commit sostitutivo usando i comandi di livello superiore:
git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -
La grande differenza è che questa sequenza non propaga i genitori aggiuntivi se B è un commit di unione.
refs/replace/gerarchia dei riferimenti. Se hai solo bisogno di farlo una volta, puoi farlo git push yourremote 'refs/replace/*'nel repository di origine e git fetch yourremote 'refs/replace/*:refs/replace/*'nei repository di destinazione. Se è necessario farlo più volte, è possibile invece aggiungere tali refspec a una remote.yourremote.pushvariabile di configurazione (nel repository di origine) e una remote.yourremote.fetchvariabile di configurazione (nei repository di destinazione).
git replace --grafts. Non sono sicuro di quando è stato aggiunto, ma il suo scopo è quello di creare un sostituto uguale a commit B ma con i genitori specificati.
Nota che cambiare un commit in Git richiede che anche tutti i commit che lo seguono debbano essere modificati. Questo è scoraggiato se hai pubblicato questa parte della storia e qualcuno potrebbe aver costruito il proprio lavoro sulla storia che era prima del cambiamento.
La soluzione alternativa git rebasemenzionata nella risposta di Amber consiste nell'utilizzare il meccanismo degli innesti (vedere la definizione degli innesti Git nel Glossario Git e la documentazione del .git/info/graftsfile nella documentazione relativa al layout del repository Git ) per modificare il genitore di un commit, verificare che sia stato corretto con un visualizzatore di cronologia ( gitk, git log --graph, ecc.) e quindi utilizzare git filter-branch(come descritto nella sezione "Esempi" della sua pagina man) per renderlo permanente (e quindi rimuovere l'innesto e, facoltativamente, rimuovere i riferimenti originali di cui è stato eseguito il backup dagit filter-branch o riposizionare il repository):
echo "$ commit-id $ graft-id" >> .git / info / innesti git filter-branch $ graft-id..HEAD
NOTA !!! Questa soluzione è diversa dalla soluzione di rebase in quanto git rebaserifarebbe / trapianta i cambiamenti , mentre la soluzione basata su innesti semplicemente riassegnerebbe gli commit così come sono , senza tener conto delle differenze tra vecchio genitore e nuovo genitore!
git replaceha sostituito gli innesti git (supponendo che tu abbia git 1.6.5 o successivo).
git-replace+ git-filter-branch? Nel mio test, filter-branchsembrava rispettare la sostituzione, riequilibrando l'intero albero.
git filter-branchriscrive la storia, rispettando gli innesti e le sostituzioni (ma nella cronologia riscritta le discussioni saranno discutibili); la pressione comporterà un cambio non in avanti. git replacecrea sostituzioni trasferibili sul posto; push sarà un avanzamento rapido, ma è necessario premere refs/replaceper trasferire le sostituzioni per avere una cronologia corretta. HTH
Per chiarire le risposte sopra e collegare spudoratamente il mio script:
Dipende se si desidera "rebase" o "reparent". Un rebase , come suggerito da Amber , si sposta tra diff . Un parente , come suggerito da Jakub e Chris , si muove intorno a istantanee dell'intero albero. Se vuoi ripartire, ti suggerisco di usaregit reparent invece di fare il lavoro manualmente.
Supponiamo che tu abbia l'immagine a sinistra e desideri che assomigli all'immagine a destra:
C'
/
A---B---C A---B---C
Sia il rebasing che il reparenting produrranno la stessa immagine, ma la definizione di C'differisce. Con git rebase --onto A B, C'non conterrà alcuna modifica introdotta da B. Con git reparent -p A, C'sarà identico a C(tranne che Bnon sarà nella storia).
reparentsceneggiatura; funziona magnificamente; altamente raccomandato . Consiglierei anche di creare una nuova branchetichetta e a checkoutquel ramo prima di chiamare (poiché l'etichetta del ramo si sposterà).
Sicuramente la risposta di Jakub mi ha aiutato alcune ore fa quando stavo provando esattamente la stessa cosa dell'OP.
Tuttavia, git replace --graftora è la soluzione più semplice per quanto riguarda gli innesti. Inoltre, un grosso problema con quella soluzione era che il ramo del filtro mi faceva perdere tutti i rami che non venivano uniti nel ramo della HEAD. Quindi, ha git filter-repofatto il lavoro perfettamente e in modo impeccabile.
$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force
Per maggiori informazioni: consulta la sezione "Reinnesto della cronologia" nei documenti