Git cherry pick vs rebase


120

Recentemente ho iniziato a lavorare con Git.

Esaminando il libro Git online ho trovato quanto segue nella sezione "Git Rebase":

Con il comando rebase, puoi prendere tutte le modifiche che sono state salvate su un ramo e riprodurle su un altro.

(Citato da: http://git-scm.com/book/en/Git-Branching-Rebasing )

Ho pensato che questa fosse la definizione esatta di git cherry-pick (riapplicare un commit o un insieme di oggetti di commit sul ramo attualmente estratto).

Qual è la differenza tra i due?

Risposte:


166

Dato che il tempo ha git cherry-pickimparato ad essere in grado di applicare più commit, la distinzione in effetti è diventata alquanto discutibile, ma questa è qualcosa che può essere chiamata evoluzione convergente ;-)

La vera distinzione sta nell'intento originale di creare entrambi gli strumenti:

  • git rebaseIl compito è di inoltrare una serie di modifiche che uno sviluppatore ha nel proprio repository privato, create contro la versione X di qualche branch upstream, alla versione Y di quello stesso branch (Y> X). Questo cambia effettivamente la base di quella serie di commit, quindi "rebasing".

    (Consente inoltre allo sviluppatore di trapiantare una serie di commit su qualsiasi commit arbitrario, ma questo è di uso meno ovvio.)

  • git cherry-pickserve per portare un impegno interessante da una linea di sviluppo a un'altra. Un esempio classico è il backport di una correzione di sicurezza effettuata su un ramo di sviluppo instabile su un ramo stabile (di manutenzione), dove a mergenon ha senso, poiché porterebbe un sacco di modifiche indesiderate.

    Fin dalla sua prima apparizione, git cherry-pickè stato in grado di selezionare più commit contemporaneamente, uno per uno.

Quindi, forse la differenza più evidente tra questi due comandi è il modo in cui trattano il ramo su cui lavorano: di git cherry-picksolito porta un commit da qualche altra parte e lo applica sopra il tuo ramo corrente, registrando un nuovo commit, mentre git rebaseprende il tuo ramo corrente e riscrive una serie del proprio suggerimento si impegna in un modo o nell'altro. Sì, questa è una descrizione molto stupida di ciò che git rebasepuò fare, ma è intenzionale, per cercare di far penetrare l'idea generale.

Aggiorna per spiegare ulteriormente un esempio di utilizzo git rebasein discussione.

Data questa situazione,
uno stato del repo prima del ribasamento
The Book afferma:

Tuttavia, c'è un altro modo: puoi prendere la patch della modifica introdotta in C3 e riapplicarla sopra C4. In Git, questo si chiama ribasamento. Con il comando rebase, puoi prendere tutte le modifiche che sono state salvate su un ramo e applicarle su un altro.

In questo esempio, dovresti eseguire quanto segue:

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

"Il problema" qui è che in questo esempio, il ramo "esperimento" (l'argomento per ribasare) è stato originariamente biforcato dal ramo "principale", e quindi condivide i commit da C0 a C2 con esso - effettivamente, "esperimento" è " master "fino a, e incluso, C2 più commit C3 su di esso. (Questo è il caso più semplice possibile; ovviamente, "esperimento" potrebbe contenere diverse dozzine di commit in cima alla sua base originale.)

Ora git rebaseviene detto di riassumere "esperimento" sull'attuale punta di "maestro", e funziona git rebasecosì:

  1. Corre git merge-baseper vedere qual è l'ultimo commit condiviso sia da "experiment" che da "master" (qual è il punto di diversione, in altre parole). Questo è C2.
  2. Salva via tutti i commit effettuati dal punto di deviazione; nel nostro esempio di giocattoli, è solo C3.
  3. Riavvolge la TESTA (che punta al commit di punta di "esperimento" prima che l'operazione inizi) per puntare alla punta di "maestro" - ci stiamo ribasando su di essa.
  4. Tenta di applicare ciascuno dei commit salvati (come se fosse con git apply) in ordine. Nel nostro esempio di giocattolo è solo un commit, C3. Diciamo che la sua applicazione produrrà un commit C3 '.
  5. Se tutto è andato bene, il riferimento "esperimento" viene aggiornato per puntare al commit derivante dall'applicazione dell'ultimo commit salvato (C3 'nel nostro caso).

Ora torna alla tua domanda. Come puoi vedere, qui tecnicamente git rebase infatti trapianta una serie di commit da "esperimento" alla punta di "maestro", quindi puoi giustamente dire che c'è davvero "un altro ramo" nel processo. Ma il succo è che il tip commit da "experiment" è finito per essere il nuovo tip commit in "experiment", ha semplicemente cambiato la sua base:
stato dopo l'unione

Di nuovo, tecnicamente puoi dire che git rebasequi sono incorporati alcuni commit da "master", e questo è assolutamente corretto.


2
Grazie. Ancora non ho capito bene cosa intendi qui. Nel libro, viene fornito l'esempio che rebase applica una serie di commit di punta da un altro ramo, mentre tu dici che proviene dallo "stesso ramo". O forse ci sono alcuni casi di come funziona?
acido lisergico

1
Ho provato a spiegare la questione aggiornando la mia risposta.
kostix

98

Con cherry-pick, i commit / branch originali rimangono in giro e vengono creati nuovi commit. Con rebase, l'intero ramo viene spostato con il ramo che punta ai commit riprodotti.

Supponiamo che tu abbia iniziato con:

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

rebase:

$ git rebase master topic

Ottieni:

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

Scegliere selettivamente:

$ git checkout master -b topic_new
$ git cherry-pick A^..C

Ottieni:

      A---B---C topic
     /
D---E---F---G master
             \
              A'--B'--C' topic_new

per maggiori informazioni su git questo libro ne contiene la maggior parte (http://git-scm.com/book)


3
Ben risposto. È anche comune che potresti voler selezionare solo i commit A e B, ma lasciare C sospeso in quei casi potresti voler mantenere il ramo e selezionare solo le modifiche che i colleghi potrebbero aver bisogno di vedere. Git è fatto per lavorare con le persone, quindi se non vedi i vantaggi di qualcosa quando lavori da solo, è spesso più comunemente usato quando lavori in gruppi più grandi.
Pablo Jomer

Se invece fosse fatto un rebase interattivo, tralasciando uno o più commit, quali branch avresti alla fine? se fosse topicribasato solo sopra master, non conterrebbe i commit lasciati fuori, quindi di quale ramo faranno parte?
Anthony

Ancora una cosa che voglio aggiungere: se tu git checkout topice poi git reset --hard C'dopo la raccolta delle ciliegie, hai lo stesso risultato della ribasatura. Mi sono salvato da molti conflitti di unione usando la selezione di ciliegie sul ribasamento, perché l'antenato comune era molto indietro.
sorrymissjackson

@anthony - stackoverflow.com/questions/11835948/... : per quanto ho capito sono persi. Io non sono un git-guru ma questo rebase/ cherry-pickè su tutti i dettagli con gitcui ho avuto un problema di comprensione.
thoni56

1
I tuoi grafici fanno più male che bene, perché sono funzionalmente identici. L'unica differenza è il ramo creato da git checkout -b, che non ha nulla a che fare con git cherry-pick. Un modo migliore per spiegare quello che stai cercando di dire sarebbe “corri git rebasesul topicramo e lo passi master; corri git cherry-picksul masterramo e lo passi (si impegna da) topic. "
Rory O'Kane

14

La selezione delle ciliegie funziona per i singoli commit .

Quando esegui il rebasing, vengono applicati tutti i commit nella cronologia all'HEAD del ramo che mancano.


Grazie. Sai se questi funzionano allo stesso modo sotto le coperte? (memorizza i loro output intermedi in file "patch", ecc.).
acido lisergico

Afaik sì. Applica tutte le patch una per una. Questo è il motivo per cui a volte è necessario risolvere i conflitti di unione durante un rebase prima di continuare.
iltempo

6
@iltempo, ha funzionato per i singoli commit solo nelle versioni precedenti di Git; al momento puoi fare qualcosa come git cherry-pick foo~3..fooe ottenere i commit della cima dell'albero da "foo" scelti uno per uno.
kostix

1
git-rebase utilizza la stessa API del cherry-picking nel codebase, iirc
alternativa

Non credo che funzionino allo stesso modo sotto le coperte. Ho provato a ribasare migliaia di commit e penso che git crei un enorme file di casella di posta e poi git amfunzioni su di esso. Considerando che un cherry pick applica commit per commit (possibilmente creando una casella di posta a messaggio singolo per ogni patch). Il mio rebase ha avuto esito negativo perché il file della casella di posta che stava creando ha esaurito lo spazio sull'unità, ma la scelta rapida con lo stesso intervallo di revisione è riuscita (e sembra funzionare più velocemente).
nessuno

11

Una breve risposta:

  • git cherry-pick è più "di basso livello"
  • In quanto tale, può emulare git rebase

Le risposte fornite sopra sono buone, volevo solo fare un esempio nel tentativo di dimostrare la loro interrelazione.

Non è consigliabile sostituire "git rebase" con questa sequenza di azioni, è solo "una prova di concetto" che, spero, aiuti a capire come funzionano le cose.

Dato il seguente archivio di giocattoli:

$ git log --graph --decorate --all --oneline
* 558be99 (test_branch_1) Test commit #7
* 21883bb Test commit #6
| * 7254931 (HEAD -> master) Test commit #5
| * 79fd6cb Test commit #4
| * 48c9b78 Test commit #3
| * da8a50f Test commit #2
|/
* f2fa606 Test commit #1

Diciamo, abbiamo alcune modifiche molto importanti (commit da # 2 a # 5) nel master che vogliamo includere nel nostro test_branch_1. Di solito passiamo a un ramo e facciamo "git rebase master". Ma poiché stiamo fingendo di essere equipaggiati solo con "git cherry-pick", facciamo:

$ git checkout 7254931                # Switch to master (7254931 <-- master <-- HEAD)
$ git cherry-pick 21883bb^..558be99   # Apply a range of commits (first commit is included, hence "^")    

Dopo tutte queste operazioni, il nostro grafico di commit sarà simile a questo:

* dd0d3b4 (HEAD) Test commit #7
* 8ccc132 Test commit #6
* 7254931 (master) Test commit #5
* 79fd6cb Test commit #4
* 48c9b78 Test commit #3
* da8a50f Test commit #2
| * 558be99 (test_branch_1) Test commit #7
| * 21883bb Test commit #6
|/
* f2fa606 Test commit #1

Come possiamo vedere, i commit # 6 e # 7 sono stati applicati a 7254931 (un tip commit del master). HEAD è stato spostato e indica un commit che è, essenzialmente, la punta di un ramo ribasato. Ora tutto ciò che dobbiamo fare è eliminare un vecchio puntatore di ramo e crearne uno nuovo:

$ git branch -D test_branch_1
$ git checkout -b test_branch_1 dd0d3b4

test_branch_1 è ora radicato dall'ultima posizione master. Fatto!


Ma rebase può simulare anche git cherry-pick?
Numero 945

Dal momento che cherry-pickè in grado di applicare una serie di commit, penso di sì. Sebbene questo sia un modo un po 'strano di fare le cose, nulla ti impedisce di selezionare tutti i commit nel tuo ramo di funzionalità sopra master, quindi eliminare il ramo di funzionalità e ricrearlo in modo che punti alla punta di master. Puoi pensare git rebasea una sequenza di git cherry-pick feature_branch, git branch -d feature_branche git branch feature_branch master.
raiks

7

Sono entrambi comandi per riscrivere i commit di un ramo sopra un altro: la differenza sta in quale ramo - "tuo" (quello attualmente estratto HEAD) o "loro" (il ramo passato come argomento al comando) - è la base per questa riscrittura.

git rebaseprende un commit iniziale e ripete i tuoi commit come successivi al loro (il commit iniziale).

git cherry-pickprende una serie di commit e riproduce i loro commit come se venissero dopo il tuo (tuo HEAD).

In altre parole, i due comandi sono, nel loro comportamento principale (ignorando le loro caratteristiche prestazionali divergenti, convenzioni di chiamata e le opzioni di miglioramento), simmetrico : controllando ramo bare funzionante git rebase fooimposta il barramo alla stessa storia come controllando ramo fooe funzionante git cherry-pick ..barfisserebbe fooa (le modifiche da foo, seguite dalle modifiche da bar).

Dal punto di vista del nome, la differenza tra i due comandi può essere ricordata in quanto ognuno descrive ciò che fa al ramo corrente : rebasefa dell'altra testa la nuova base per le tue modifiche, mentre cherry-pickprende le modifiche dall'altro ramo e le mette sopra il tuoHEAD (come le ciliegie sopra un gelato).


1
Non riuscivo a capire nessuna delle risposte tranne la tua! È conciso e ha perfettamente senso senza una formulazione superflua.
neoxic

4

Entrambi fanno cose molto simili; la principale differenza concettuale è (in termini semplificati) che:

  • rebase sposta i commit dal ramo corrente a un altro ramo .

  • Cherry-pick copia i commit da un altro ramo al ramo corrente .

Utilizzando diagrammi simili alla risposta di @Kenny Ho :

Dato questo stato iniziale:

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

... e supponendo che tu voglia far riprodurre i commit dal topicramo sopra il masterramo corrente , hai due opzioni:

  1. Utilizzando rebase: dovresti prima andare a topicfacendo git checkout topic, quindi spostare il ramo eseguendo git rebase master, producendo:

    A---B---C---D master
                 \
                  E'---F'---G' topic
    

    Risultato: il tuo ramo attuale è topicstato ribasato (spostato) su master.
    Il topicramo è stato aggiornato, mentre il masterramo è rimasto al suo posto.

  2. Utilizzando cherry-pick : dovresti prima andare a masterfacendo git checkout master, quindi copiare il ramo eseguendo git cherry-pick topic~3..topic(o, equivalentemente, git cherry-pick B..G), producendo:

    A---B---C---D---E'---F'---G' master
         \
          E---F---G topic
    

    Risultato: i commit da topicsono stati copiati in master.
    Il masterramo è stato aggiornato, mentre il topicramo è rimasto al suo posto.


Ovviamente, qui dovevi dire esplicitamente a cherry-pick di scegliere una sequenza di commit , usando la notazione range foo..bar . Se avessi semplicemente passato il nome del ramo, come in git cherry-pick topic, avrebbe raccolto solo il commit sulla punta del ramo, risultando in:

A---B---C---D---G' master
     \
      E---F---G topic
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.