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.
--onto
serve! 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 --onto
ogni altra domanda e documento che ho letto finora non è riuscito.
rebase this commit on that one
o 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-objects
opzione per git (prima del nome del comando) o impostando la GIT_NO_REPLACE_OBJECTS
variabile 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.push
variabile di configurazione (nel repository di origine) e una remote.yourremote.fetch
variabile 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 rebase
menzionata 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/grafts
file 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 rebase
rifarebbe / 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 replace
ha sostituito gli innesti git (supponendo che tu abbia git 1.6.5 o successivo).
git-replace
+ git-filter-branch
? Nel mio test, filter-branch
sembrava rispettare la sostituzione, riequilibrando l'intero albero.
git filter-branch
riscrive 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 replace
crea sostituzioni trasferibili sul posto; push sarà un avanzamento rapido, ma è necessario premere refs/replace
per 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 B
non sarà nella storia).
reparent
sceneggiatura; funziona magnificamente; altamente raccomandato . Consiglierei anche di creare una nuova branch
etichetta e a checkout
quel 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 --graft
ora è 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-repo
fatto 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