Non riesco a capire il comportamento di git rebase --onto


169

Ho notato che i due blocchi di seguenti comandi git hanno comportamenti diversi e non capisco perché.

Ho un Ae un Bramo che divergono con unocommit

---COMMIT--- (A)
\
 --- (B)

Voglio rifare l'ultimo Bramo A(e avere il commit sul Bramo)

---COMMIT--- (A)
         \
          --- (B)

Nessun problema se lo faccio:

checkout B
rebase A

Ma se lo faccio:

checkout B
rebase --onto B A

Non funziona affatto, non succede nulla. Non capisco perché i due comportamenti siano diversi.

Phpstorm git client usa la seconda sintassi, e quindi mi sembra completamente rotto, ecco perché chiedo questo problema di sintassi.


Risposte:


412

tl; dr

La sintassi corretta da rifare Boltre Aall'uso git rebase --ontonel tuo caso è:

git checkout B
git rebase --onto A B^

o rifare Bin cima a Apartire dal commit che è il padre diB referenziato con B^o B~1.

Se sei interessato alla differenza tra git rebase <branch>e continua a git rebase --onto <branch>leggere.

The Quick: git rebase

git rebase <branch>sta per rebase al ramo attualmente avete verificato, a cui fa riferimento HEAD, in cima alla ultima commit che è raggiungibile dalla <branch>, ma non da HEAD.
Questo è il caso più comune di rebasing e probabilmente quello che richiede meno pianificazione in anticipo.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                         \
              D---E (HEAD)                              D---E (HEAD)

In questo esempio, Fe Gsono commit che sono raggiungibili da branchma non da HEAD. Dicendo git rebase branchprenderà D, questo è il primo commit dopo il punto di diramazione, e lo riposizionerà (cioè cambierà il suo genitore ) sopra l'ultimo commit raggiungibile branchma non da HEAD, cioè G.

Il preciso: git rebase --onto con 2 argomenti

git rebase --ontoti consente di effettuare il rebase a partire da un commit specifico . Ti dà il controllo esatto su ciò che viene ripassato e dove. Questo è per gli scenari in cui è necessario essere precisi.

Ad esempio, immaginiamo che dobbiamo rifare HEADesattamente oltre a Fpartire da E. Siamo interessati solo ad entrare Fnel nostro ramo di lavoro mentre, allo stesso tempo, non vogliamo mantenerlo Dperché contiene alcune modifiche incompatibili.

          Before                           After
    A---B---C---F---G (branch)        A---B---C---F---G (branch)
             \                                     \
              D---E---H---I (HEAD)                  E---H---I (HEAD)

In questo caso, diremmo git rebase --onto F D. Questo significa:

Sostituisci il commit raggiungibile dal HEADcui genitore è Din cima F.

In altre parole, cambia il genitore di Eda Da F. La sintassi di git rebase --ontoè quindi git rebase --onto <newparent> <oldparent>.

Un altro scenario in cui questo è utile è quando si desidera rimuovere rapidamente alcuni commit dal ramo corrente senza dover effettuare un rebase interattivo :

          Before                       After
    A---B---C---E---F (HEAD)        A---B---F (HEAD)

In questo esempio, al fine di rimuovere Ce Edalla sequenza si direbbe git rebase --onto B E, o rifare HEADil punto in Bcui si trovava il vecchio genitore E.

The Surgeon: git rebase --onto con 3 argomenti

git rebase --ontopuò fare un passo avanti in termini di precisione. In effetti, ti consente di rifare un intervallo arbitrario di commit sopra un altro.

Ecco un esempio:

          Before                                     After
    A---B---C---F---G (branch)                A---B---C---F---G (branch)
             \                                             \
              D---E---H---I (HEAD)                          E---H (HEAD)

In questo caso, vogliamo ridefinire l'intervallo esatto E---Hsopra F, ignorando dove HEADsta puntando attualmente. Possiamo farlo dicendo git rebase --onto F D H, il che significa:

Ricambia l'intervallo di commit il cui genitore è D fino a Hin cima F.

La sintassi di git rebase --ontocon una serie di commit diventa quindi git rebase --onto <newparent> <oldparent> <until>. Il trucco qui è ricordare che il commit a cui fa riferimento <until>è incluso nell'intervallo e diventerà il nuovo HEADdopo il completamento del rebase.


1
Bella risposta. Solo una piccola aggiunta per il caso generale: il <oldparent>nome scompare se le due parti dell'intervallo si trovano su rami diversi. Generalmente è: "Includi tutti i commit raggiungibili da <until>ma escludi tutti i commit da cui è possibile raggiungere <oldparent>".
musiKk,

50
git rebase --onto <newparent> <oldparent>è la migliore spiegazione del comportamento --onto che ho visto!
Ronkot,

4
Grazie! Ero un po 'alle prese con l' --ontoopzione ma questo lo ha reso cristallino! Non capisco nemmeno come non avrei potuto
capirlo

3
Sebbene questa risposta sia eccellente, ritengo che non copra tutti i casi possibili. L'ultima forma di sintassi può anche essere usata per esprimere un tipo più sottile di rebase. Andando fuori dall'esempio in Pro Git (2a edizione), D non deve necessariamente essere un antenato di H. Invece, D e H potrebbero anche essere commessi con un antenato comune - in questo caso, Git scoprirà il loro antenato comune e replica da quell'antenato a H su F.
Pastafarian,

1
Questo è stato utile. La pagina man non spiega affatto gli argomenti.
a544jh

61

Questo è tutto ciò che devi sapere per capire --onto:

git rebase --onto <newparent> <oldparent>

Stai commutando un genitore su un commit, ma non stai fornendo lo sha del commit, ma solo lo sha del suo genitore (vecchio) attuale.


4
Breve e facile In realtà, rendermi conto che devo fornire il commit dei genitori a quello che voglio ribattere invece di quel commit mi ha richiesto il tempo più lungo.
Antoniossss,

1
Un dettaglio importante è che si sceglie un figlio di un parente dal ramo corrente perché un commit può essere padre di molti commit ma quando si limita te stesso al ramo corrente, il commit può essere padre solo di un commit. In altre parole, la nave di relazione principale è unica sul ramo ma non deve essere se non si specifica il ramo.
Trismegisto,

2
Nota: è necessario essere nel ramo o aggiungere il nome del ramo come terzo parametro git rebase --onto <nuovo> <oldparent> <feature-branch>
Jason Portnoy

1
Questa risposta è sorprendente, dritta al punto necessario in questa discussione
John Culviner il

13

In breve, dato:

      Before rebase                             After rebase
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                         \   \
          D---E---H---I (HEAD)                      \   E'---H' (HEAD)
                                                     \
                                                      D---E---H---I

git rebase --onto F D H

Che è lo stesso di (perché --ontoaccetta un argomento):

git rebase D H --onto F

Significa rebase commette nell'intervallo (D, H] sopra F. Nota che l'intervallo è esclusivo per la mano sinistra. È esclusivo perché è più facile specificare il 1o commit digitando ad es. branchPer gittrovare il 1o commit divergente da branchie Dche porta aH .

Caso OP

    o---o (A)
     \
      o (B)(HEAD)

git checkout B
git rebase --onto B A

Può essere modificato in comando singolo:

git rebase --onto B A B

Quello che sembra un errore qui è il posizionamento Bche significa "sposta alcuni commit che portano al ramo Bsopra B". La domanda è cosa siano "alcuni commit". Se aggiungi -iflag vedrai che è un singolo commit puntato da HEAD. Il commit viene ignorato perché è già applicato al --ontotarget Be quindi non succede nulla.

Il comando è senza senso in ogni caso in cui il nome del ramo viene ripetuto in questo modo. Questo perché la gamma di commit sarà composta da alcuni commit già presenti in quel ramo e durante il rebase tutti verranno saltati.

Ulteriore spiegazione e uso applicabile di git rebase <upstream> <branch> --onto <newbase>.

git rebase impostazioni predefinite.

git rebase master

Si espande in entrambi:

git rebase --onto master master HEAD
git rebase --onto master master current_branch

Checkout automatico dopo rebase.

Se utilizzato in modo standard, come:

git checkout branch
git rebase master

Non ti accorgerai che dopo che il rebase si gitsposta branchsull'ultimo commit reimpostato e lo fa git checkout branch(vedi la git reflogcronologia). Ciò che è interessante quando il secondo argomento è commit hash invece il nome del ramo rebase funziona ancora ma non c'è alcun ramo da spostare, quindi si finisce in "HEAD distaccato" invece di essere estratto nel ramo spostato.

Ometti commit divergenti primari.

L' masteringresso --ontoè preso dal 1 ° git rebaseargomento.

                   git rebase master
                              /    \
         git rebase --onto master master

In pratica può essere qualsiasi altro commit o ramo. In questo modo è possibile limitare il numero di commit rebase prendendo gli ultimi e lasciando commit divergenti primari.

git rebase --onto master HEAD~
git rebase --onto master HEAD~ HEAD  # Expanded.

Sarà rebase singolo impegnano a punta da HEADad mastere finiscono in "testa staccata".

Evita i checkout espliciti.

L'impostazione predefinita HEADo l' current_branchargomento sono contestualmente presi dal posto in cui ti trovi. Questo è il motivo per cui la maggior parte delle persone fa il check-out al ramo che desidera riformulare. Ma quando l'argomento 2nd rebase viene dato esplicitamente non è necessario effettuare il checkout prima di rebase per passare in modo implicito.

(branch) $ git rebase master
(branch) $ git rebase master branch  # Expanded.
(branch) $ git rebase master $(git rev-parse --abbrev-ref HEAD)  # Kind of what git does.

Ciò significa che è possibile modificare le commit e le filiali da qualsiasi luogo. Quindi, insieme al checkout automatico dopo rebase. non è necessario effettuare il checkout separatamente del ramo rebased prima o dopo il rebase.

(master) $ git rebase master branch
(branch) $ # Rebased. Notice checkout.

8

In poche parole, git rebase --ontoseleziona un intervallo di commit e li ripristina sul commit indicato come parametro.

Leggi le pagine man di git rebase, cerca "onto". Gli esempi sono molto buoni:

example of --onto option is to rebase part of a branch. If we have the following situation:

                                   H---I---J topicB
                                  /
                         E---F---G  topicA
                        /
           A---B---C---D  master

   then the command

       git rebase --onto master topicA topicB

   would result in:

                        H'--I'--J'  topicB
                       /
                       | E---F---G  topicA
                       |/
           A---B---C---D  master

In questo caso dici a git di reimpostare i commit da topicAa topicBsopra master.


8

Per capire meglio la differenza tra git rebaseed git rebase --ontoè bene sapere quali sono i possibili comportamenti per entrambi i comandi. git rebaseci permettono di spostare i nostri commit in cima al ramo selezionato. Come qui:

git rebase master

e il risultato è:

Before                              After
A---B---C---F---G (master)          A---B---C---F---G (master)
         \                                           \
          D---E (HEAD next-feature)                   D'---E' (HEAD next-feature)

git rebase --ontoè più precisi. Ci consente di scegliere un commit specifico da dove vogliamo iniziare e anche da dove vogliamo finire. Come qui:

git rebase --onto F D

e il risultato è:

Before                                    After
A---B---C---F---G (branch)                A---B---C---F---G (branch)
         \                                             \
          D---E---H---I (HEAD my-branch)                E'---H'---I' (HEAD my-branch)

Per avere maggiori dettagli ti consiglio di consultare il mio articolo su git rebase - sulla panoramica


@Makyen Certo, lo terrò a mente in futuro :)
womanonrails il

Quindi, possiamo leggere git rebase --onto F Dcome figlio secondario del genitore D come F , no?
Prihex

2

Perché ontohai bisogno di due rami aggiuntivi. Con quel comando puoi applicare i commit branchBche si basano branchAsu un altro ramo ad es master. Nell'esempio seguente branchBsi basa su branchAe si desidera applicare le modifiche di branchBon mastersenza applicare le modifiche di branchA.

o---o (master)
     \
      o---o---o---o (branchA)
                   \
                    o---o (branchB)

usando i comandi:

checkout branchB
rebase --onto master branchA 

otterrai la seguente gerarchia di commit.

      o'---o' (branchB)
     /
o---o (master)
     \
      o---o---o---o (branchA)

1
Puoi per favore spiegare un po 'di più, se vogliamo tornare al master, come mai diventa l'attuale ramo? Se lo facessi rebase --onto branchA branchB, metterebbe l'intero ramo principale sulla testa del ramo A?
Polimerasi,

8
non dovrebbe essere checkout branchB: rebase --onto master branchA?
goldenratio,

4
Perché questo è stato votato? questo non fa quello che dice di fare.
De Novo,

Ho modificato e risolto la risposta, quindi le persone non devono prima rompere i rami dei repository e subito dopo vengono a leggere i commenti ... 🙄
Kamafeather,

0

C'è un altro caso in cui git rebase --ontoè difficile da capire: quando si rinnova su un commit risultante da un selettore di differenza simmetrica (i tre punti "... ')

Git 2.24 (Q4 2019) fa un lavoro migliore nella gestione di quel caso:

Vedi commit 414d924 , commit 4effc5b , commit c0efb4c , commit 2b318aa (27 agosto 2019) e commit 793ac7e , commit 359eceb (25 agosto 2019) di Denton Liu ( Denton-L) .
Aiutato da: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) e Johannes Schindelin ( dscho) .
Vedi commit 6330209 , commit c9efc21 (27 ago 2019) e commit 4336d36 (25 ago 2019) di Ævar Arnfjörð Bjarmason ( avar).
Aiutato da: Eric Sunshine ( sunshineco) , Junio ​​C Hamano ( gitster) , Ævar Arnfjörð Bjarmason ( avar) e Johannes Schindelin ( dscho) .
(Unita da Junio ​​C Hamano - gitster- in commit 640f9cd , 30 set 2019)

rebase: avanzamento rapido --ontoin più casi

Prima, quando avevamo il seguente grafico,

A---B---C (master)
     \
      D (side)

correre ' git rebase --onto master... master side' si tradurrebbe in Dessere sempre ridisegnato, non importa quale.

A questo punto, leggi " Quali sono le differenze tra punto doppio ' ..' e punto triplo" ..."negli intervalli di commit diff Git? "

https://sphinx.mythic-beasts.com/~mark/git-diff-help.png

Qui: " master..." si riferisce a master...HEAD, ovvero B: HEAD è side HEAD (attualmente estratto): stai eseguendo il rebasing B.
Cosa stai ribellando? Qualsiasi commit non nel master, e raggiungibile dal sideramo: esiste un solo commit che si adatta a quella descrizione: D... che è già in cima B!

Ancora una volta, prima di Git 2.24, un simile rebase --ontorisultato risulterebbe Dessere sempre rinnovato, non importa quale.

Tuttavia, il comportamento desiderato è che rebase dovrebbe notare che questo è avanzamento rapido e farlo invece.

È simile a rebase --onto B Aquello dell'OP, che non ha fatto nulla.

Aggiungi il rilevamento in can_fast_forwardmodo che questo caso possa essere rilevato e verrà eseguito un avanzamento rapido.
Prima di tutto, riscrivi la funzione per usare goto che semplifica la logica.
Successivamente, poiché il

options.upstream &&
!oidcmp(&options.upstream->object.oid, &options.onto->object.oid)

le condizioni sono state rimosse cmd_rebase, reintroduciamo un sostituto in can_fast_forward.
In particolare, controllando le basi di unione upstreame headrisolvendo un caso non riuscito in t3416.

Il grafico abbreviato per t3416 è il seguente:

        F---G topic
       /
  A---B---C---D---E master

e il comando fallito era

git rebase --onto master...topic F topic

Prima, Git avrebbe visto che c'era una base di unione ( C, risultato di master...topic), e l'unione e su erano le stesse, quindi avrebbe restituito erroneamente 1, indicando che avremmo potuto avanzare rapidamente. Ciò farebbe sì che il grafico riformulato fosse " ABCFG" quando ci aspettavamo " ABCG".

A rebase --onto C F topicsignifica qualsiasi commit dopo F , raggiungibile da topicHEAD: questo è Gsolo, non Fse stesso.
L' Favanzamento veloce in questo caso includerebbe nel ramo riformulato, il che è sbagliato.

Con la logica aggiuntiva, rileviamo che è la base di fusione upstream e head F. Dal momento che onto non lo è F, significa che non stiamo riequilibrando l'intero set di commit master..topic.
Poiché escludiamo alcuni commit, non è possibile eseguire un avanzamento rapido e quindi restituiamo correttamente 0.

Aggiungi ' -f' ai casi di test non riusciti a seguito di questa modifica perché non si aspettavano un avanzamento rapido in modo da forzare un rebase.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.