Quando usi git rebase invece di git merge?

Quando è consigliabile utilizzare git rebase vs. git merge ?

Devo ancora unirmi dopo una corretta esecuzione di rebase?

Versione breve

  • Unisci prende tutte le modifiche in un ramo e le unisce in un altro ramo in un unico commit.
  • Rebase dice che voglio il punto in cui mi sono ramificato per passare a un nuovo punto di partenza

Quindi quando usi uno dei due?

fondersi

  • Diciamo che hai creato una filiale allo scopo di sviluppare una singola funzione. Quando vuoi riportare queste modifiche sul master, probabilmente vuoi unire (non ti interessa mantenere tutti i commit intermedi).

rebase

  • Un secondo scenario sarebbe se avessi iniziato a fare qualche sviluppo e poi un altro sviluppatore avesse apportato una modifica non correlata. Probabilmente vuoi tirare e poi rebase per basare le tue modifiche dalla versione corrente dal repository.

È semplice, con rebase dici di usare un altro ramo come nuova base per il tuo lavoro.

Se si dispone ad esempio di un master succursale e si crea un ramo per implementare una nuova funzione, dì che la si chiama cool-feature , ovviamente il ramo master è la base della nuova funzione.

Ora a un certo punto si desidera aggiungere la nuova funzione implementata nel ramo master . Potresti semplicemente passare a master e unire il ramo delle cool-feature avanzate:

 $git checkout master $git merge cool-feature 

ma in questo modo viene aggiunto un nuovo commit fittizio, se vuoi evitare la storia degli spaghetti puoi rebase :

 $git checkout cool-feature $git rebase master 

e quindi unirlo in master :

 $git checkout master $git merge cool-feature 

Questa volta, poiché il ramo dell’argomento ha gli stessi commit del master più i commit con la nuova funzione, l’unione sarà solo un avanzamento rapido.

Per completare la mia risposta menzionata da TSamper ,

  • un rebase è spesso una buona idea da fare prima di unire, perché l’idea è di integrare nel tuo ramo Y il lavoro del ramo B sul quale ti unirai.
    Ma ancora, prima di unire, risolvi qualsiasi conflitto nel tuo ramo (es .: “rebase”, come in “ripeti il ​​mio lavoro nel mio ramo a partire da un punto recente dal ramo B )
    Se fatto correttamente, la fusione successiva dal ramo al ramo B può essere avanti veloce.

  • un effetto di fusione direttamente il ramo di destinazione B , il che significa che l’unione è meglio essere banali, altrimenti quel ramo B può essere lungo per tornare a uno stato stabile (tempo per te per risolvere tutti i conflitti)


il punto di fusione dopo un rebase?

Nel caso che descrivo, rebase B sul mio ramo, solo per avere l’opportunità di riprodurre il mio lavoro da un punto più recente da B , ma rimanendo nel mio ramo.
In questo caso, è ancora necessaria un’unione per portare il mio lavoro “riprodotto” su B

L’altro scenario ( descritto in Git Ready per esempio), è quello di portare il tuo lavoro direttamente in B attraverso un rebase (che conserva tutti i tuoi bei commit, o anche di darti l’opportunità di riordinarli attraverso un rebase interattivo).
In tal caso, (in caso di rebase mentre si è nel ramo B), hai ragione: non è necessaria un’ulteriore fusione:

Un albero di Git di default quando non ci siamo uniti né rebasati

rebase1

otteniamo ridiscutendo:

rebase3

Quel secondo scenario è tutto: come faccio a ottenere la nuova funzionalità in master.

Il mio punto, descrivendo il primo scenario di rebase, è ricordare a tutti che un rebase può anche essere usato come passo preliminare a quello (che è “ottenere una nuova funzionalità nel master”).
Puoi usare rebase per portare inizialmente il master “in” nel ramo della nuova funzione: il rebase riprodurrà i commit di nuove feature dal HEAD master , ma ancora nel ramo della nuova funzione, spostando in modo efficace il punto iniziale del ramo da un vecchio commit master a HEAD-master .
Ciò consente di risolvere eventuali conflitti nella propria diramazione (cioè, isolatamente, consentendo al master di continuare ad evolversi in parallelo se la fase di risoluzione dei conflitti richiede troppo tempo).
Quindi puoi passare a masterizzare e unire new-feature (o rebase new-feature su master se vuoi conservare i commit fatti nel tuo new-feature ramo di new-feature ).

Così:

  • “rebase vs. merge” può essere visto come due modi per importare un lavoro su, per esempio, master .
  • Ma “rebase then merge” può essere un stream di lavoro valido per risolvere prima il conflitto in isolamento, quindi riportare il lavoro.

Molte risposte dicono che la fusione trasforma tutti i tuoi commit in uno, e quindi suggerisce di usare rebase per preservare i tuoi commit. Questo non è corretto E una ctriggers idea se hai già spinto i tuoi impegni .

Unisci non cancella i tuoi impegni. Unisci conserva la cronologia! (guarda gitk) Rebase riscrive la storia, che è una brutta cosa dopo che l’hai spinta .

Usa unione – non rebase ogni volta che hai già premuto.

Ecco Linus ‘(autore di git) assumere su di esso . È davvero una buona lettura. Oppure puoi leggere la mia versione della stessa idea qui sotto.

Rialanciare un ramo sul master:

  • fornisce un’idea errata di come sono stati creati i commit
  • i polluti padroneggiano con una serie di commit intermedi che potrebbero non essere stati ben testati
  • potrebbe in realtà introdurre interruzioni di build su questi commit intermedi a causa delle modifiche che sono state apportate al master tra quando il ramo dell’argomento originale è stato creato e quando è stato ridefinito.
  • rende difficile trovare buoni posti nel master per il checkout.
  • Fa sì che i timestamp in commit non si allineano con il loro ordine cronologico nell’albero. Quindi vedresti che il commit A precede il commit B nel master, ma il commit B è stato creato per primo. (Che cosa?!)
  • Produce più conflitti perché i singoli commit nel ramo dell’argomento possono comportare conflitti di fusione che devono essere risolti individualmente (Ulteriori informazioni sulla storia di ciò che è accaduto in ogni commit).
  • è una riscrittura della storia. Se il ramo che viene ridefinito è stato spinto ovunque (condiviso con qualcuno diverso da te stesso), hai rovinato tutti quelli che hanno quel ramo da quando hai riscritto la cronologia.

Al contrario, unendo un ramo argomento in master:

  • conserva la cronologia di dove sono stati creati i rami dell’argomento, incluse eventuali fusioni dal master al ramo dell’argomento per mantenerlo aggiornato. Hai davvero un’idea precisa di quale codice stava lavorando lo sviluppatore quando stavano costruendo.
  • master è un ramo costituito per lo più da unioni e ognuna di queste unioni di commit sono in genere ‘punti utili’ nella cronologia che è sicuro da verificare perché è lì che il ramo dell’argomento era pronto per essere integrato.
  • tutti i singoli commit della branca dell’argomento vengono mantenuti, incluso il fatto che si trovavano in una branca di argomenti, quindi isolare quelle modifiche è naturale e puoi eseguire il drill in dove richiesto.
  • i conflitti di unione devono essere risolti una volta sola (al punto di unione) in modo che le modifiche di commit intermedie apportate nel ramo dell’argomento non debbano essere risolte in modo indipendente.
  • può essere fatto più volte senza intoppi. Se si integra periodicamente il ramo dell’argomento da padroneggiare, la gente può continuare a build sul ramo dell’argomento e può continuare a unirsi in modo indipendente.

TL; DR

In caso di dubbi, utilizzare l’unione.

Risposta breve

Le uniche differenze tra un rebase e un’unione sono:

  • La struttura ad albero risultante della cronologia (generalmente visibile solo quando si guarda un grafico di commit) è diversa (una avrà rami, l’altra no).
  • Unisci generalmente creerà un commit aggiuntivo (ad es. Il nodo nell’albero).
  • Unisci e rebase gestiranno i conflitti in modo diverso. Rebase presenterà i conflitti di un commit alla volta in cui l’unione li presenterà tutti in una volta.

Quindi la risposta breve è scegliere rebase o fusione in base a ciò che si desidera che la cronologia assuma .

Risposta lunga

Ci sono alcuni fattori da considerare quando si sceglie quale operazione utilizzare.

Il ramo che stai ricevendo cambia da condiviso con altri sviluppatori esterni al tuo team (es. Open source, pubblico)?

In tal caso, non rebase. Rebase distrugge il ramo e quegli sviluppatori avranno repository rotti / incoerenti a meno che non usino git pull --rebase . Questo è un buon modo per turbare rapidamente altri sviluppatori.

Quanto è competente il tuo team di sviluppo?

Rebase è un’operazione distruttiva. Ciò significa che se non lo applichi correttamente, potresti perdere il lavoro impegnato e / o rompere la coerenza dei repository di altri sviluppatori.

Ho lavorato su team in cui gli sviluppatori provenivano tutti da un periodo in cui le aziende potevano permettersi personale dedicato per affrontare la ramificazione e la fusione. Questi sviluppatori non sanno molto di Git e non vogliono sapere molto. In queste squadre non rischierei di raccomandare la ribasatura per nessuna ragione.

Il ramo stesso rappresenta informazioni utili

Alcuni team usano il modello branch-per-feature in cui ogni branch rappresenta una feature (o bugfix, o sub-feature, ecc.) In questo modello la branch aiuta a identificare i set di commit relativi. Ad esempio, è ansible ripristinare rapidamente una funzione ripristinando l’unione di quel ramo (per essere giusti, questa è un’operazione rara). Oppure diff una caratteristica confrontando due rami (più comuni). Rebase distruggerebbe il ramo e questo non sarebbe semplice.

Ho anche lavorato su team che hanno utilizzato il modello branch-per-developer (siamo stati tutti lì). In questo caso il ramo stesso non fornisce alcuna informazione aggiuntiva (il commit ha già l’autore). Non ci sarebbe nulla di male nella ridefinizione.

Potrebbe voler annullare l’unione per qualsiasi motivo?

Ripristinare (come annullare) un rebase è considerevolmente difficile e / o imansible (se il rebase ha conflitti) rispetto al ripristino di un’unione. Se pensi che ci sia una possibilità che vorresti ripristinare, usa l’unione.

Lavori in una squadra? Se è così, sei disposto a fare un approccio tutto o niente su questo ramo?

Le operazioni di rebase devono essere tirate con un corrispondente git pull --rebase . Se lavori da solo potresti essere in grado di ricordare quale utilizzare al momento opportuno. Se lavori in una squadra, sarà molto difficile coordinare. Questo è il motivo per cui la maggior parte dei flussi di lavoro di rebase consiglia di utilizzare rebase per tutte le git pull --rebase (e git pull --rebase per tutti i pull).

Miti comuni

Unisci distrugge la cronologia (lo schiacciamento commette)

Supponendo che tu abbia la seguente fusione:

  B -- C / \ A--------D 

Alcune persone dichiareranno che l’unione “distrugge” la cronologia dei commit perché se si dovesse guardare il log solo del ramo master (A-D) si perderebbero i messaggi di commit importanti contenuti in B e C.

Se questo fosse vero non avremmo domande come questa . Fondamentalmente, vedrai B e C a meno che tu non chieda esplicitamente di non vederli (usando –first-parent). Questo è molto facile da provare per te stesso.

Rebase consente fusioni più sicure / più semplici

I due approcci si fondono in modo diverso, ma non è chiaro se uno è sempre migliore dell’altro e può dipendere dal stream di lavoro dello sviluppatore. Ad esempio, se uno sviluppatore tende a impegnarsi regolarmente (ad es. Magari si impegnano due volte al giorno mentre passano da un lavoro all’altro), potrebbero esserci molti commit per un determinato ramo. Molti di questi commit potrebbero non assomigliare al prodotto finale (io tendo a ridefinire il mio approccio una o due volte per caratteristica). Se qualcun altro stava lavorando su un’area di codice correlata e hanno cercato di ribattere le mie modifiche, potrebbe essere un’operazione abbastanza noiosa.

Rebase è più cool / più sexy / più professionale

Se ti piace l’alias rm in rm -rf in “save time” allora forse rebase è per te.

I miei due centesimi

Penso sempre che un giorno mi imbatterò in uno scenario in cui git rebase è lo strumento fantastico che risolve il problema. Molto come penso che incontrerò uno scenario in cui git reflog è uno strumento fantastico che risolve il mio problema. Ho lavorato con git per oltre cinque anni. Non è successo.

Le storie disordinate non sono mai state un problema per me. Non leggo mai la mia storia di commit come un romanzo eccitante. La maggior parte delle volte ho bisogno di una storia che userò git blame o git bisect comunque. In tal caso, avere l’unione di commit è effettivamente utile per me, perché se l’unione ha introdotto il problema che è un’informazione significativa per me.

Aggiornamento (4/2017)

Mi sento obbligato a dire che mi sono personalmente addolcito nell’uso di rebase sebbene il mio consiglio generale sia ancora valido. Recentemente ho interagito molto con il progetto Angular 2 Material . Hanno usato rebase per mantenere una cronologia dei commit molto pulita. Questo mi ha permesso di vedere molto facilmente quale commit ha risolto un determinato difetto e se il commit è stato incluso in una release. Rappresenta un ottimo esempio dell’uso corretto di rebase.

Unisci significa: crea un singolo nuovo commit che unisce le mie modifiche alla destinazione.

Rebase significa: crea una nuova serie di commit, usando il mio attuale set di commit come suggerimenti. In altre parole, calcola quali sarebbero state le mie modifiche se avessi iniziato a realizzarle dal punto in cui mi sto basando. Dopo il rebase, quindi, potrebbe essere necessario ri-testare le modifiche e durante il rebase, si potrebbero avere alcuni conflitti.

Dato questo, perché dovresti rebase? Solo per mantenere chiara la cronologia dello sviluppo. Supponiamo che tu stia lavorando sulla funzione X e quando hai finito, unisci le tue modifiche. La destinazione ora avrà un singolo commit che direbbe qualcosa sulla falsariga di “Funzione aggiunta X”. Ora, invece di unire, se si è ridefinito e quindi unito, la cronologia di sviluppo della destinazione conterrà tutti i singoli commit in una singola progressione logica. Ciò rende più semplice la revisione delle modifiche in seguito. Immagina quanto sia difficile trovarlo per rivedere la cronologia dello sviluppo se 50 sviluppatori stessero unendo varie funzionalità per tutto il tempo.

Detto questo, se hai già spinto il ramo su cui stai lavorando in upstream, non dovresti rebase, ma unisci invece. Per le filiali che non sono state spinte a monte, rebase, test e unione.

Un’altra volta che potresti voler ribattere è quando vuoi eliminare i commit dalla tua branch prima di spingere verso monte. Ad esempio: commit che introducono un po ‘di codice di debug in anticipo e altri si impegnano ulteriormente a pulire quel codice. L’unico modo per farlo è eseguire un rebase interattivo: git rebase -i

AGGIORNAMENTO: Si desidera utilizzare anche rebase quando si utilizza Git per interfacciarsi con un sistema di controllo versione che non supporta la cronologia non lineare (sovversione ad esempio). Quando si utilizza il bridge git-svn, è molto importante che le modifiche che si uniscono nuovamente in subversion siano un elenco sequenziale di modifiche in aggiunta alle modifiche più recenti nel trunk. Ci sono solo due modi per farlo: (1) Ricreare manualmente le modifiche e (2) Usare il comando rebase, che è molto più veloce.

UPDATE2: Un altro modo per pensare a un rebase è che abilita una sorta di mapping dal tuo stile di sviluppo allo stile accettato nel repository a cui ti stai impegnando. Diciamo che ti piace impegnarti in piccoli, piccoli pezzi. Hai un commit per correggere un errore di battitura, un commit per eliminare il codice inutilizzato e così via. Quando hai finito ciò che devi fare, hai una lunga serie di commit. Ora diciamo che il repository che stai impegnando per incoraggiare grandi commit, quindi per il lavoro che stai facendo, ci si aspetterebbe uno o forse due commit. Come prendi la tua stringa di commit e comprimili in ciò che è previsto? Dovresti utilizzare un rebase interattivo e schiacciare i tuoi piccoli commit in un numero inferiore di blocchi più grandi. Lo stesso è vero se era necessario il contrario – se il tuo stile era costituito da qualche grosso commit, ma il repo richiedeva lunghe stringhe di piccoli commit. Dovresti usare anche un rebase per farlo. Se invece si è fusa, ora hai innestato lo stile di commit sul repository principale. Se ci sono molti sviluppatori, puoi immaginare quanto sarebbe difficile seguire una cronologia con diversi stili di commit dopo un po ‘di tempo.

UPDATE3: Does one still need to merge after a successful rebase? Si. Il motivo è che un rebase implica essenzialmente uno “spostamento” dei commit. Come ho detto sopra, questi commit sono calcolati, ma se tu avessi 14 commit dal punto di branching, quindi supponendo che nulla sia andato storto con il tuo rebase, avrai 14 commit in anticipo (del punto su cui ti stai basando) dopo il rebase è finito. Hai avuto un ramo prima di un rebase. Avrai un ramo della stessa lunghezza dopo. Devi ancora unire prima di pubblicare le modifiche. In altre parole, rebase tutte le volte che vuoi (di nuovo, solo se non hai spinto le tue modifiche a monte). Unisci solo dopo il rebase.

prima di unire / rebase:

 A <- B <- C [master] ^ \ D <- E [branch] 

dopo git merge master :

 A <- B <- C ^ ^ \ \ D <- E <- F 

dopo git rebase master :

 A <- B <- C <- D' <- E' 

(A, B, C, D, E e F sono commessi)

questo esempio e molte altre informazioni ben illustrate su git possono essere trovate qui: http://excess.org/article/2008/07/ogre-git-tutorial/

Questa frase lo ottiene:

In generale, il modo migliore per ottenere il meglio da entrambi i mondi è quello di rebase alle modifiche locali che hai apportato ma che non hai ancora condiviso prima di spingerle per ripulire la tua storia, ma non rebase mai nulla che hai spinto da qualche parte.

Fonte: http://www.git-scm.com/book/en/v2/Git-Branching-Rebasing#Rebase-vs.-Merge

Mentre la fusione è sicuramente il modo più semplice e più comune per integrare i cambiamenti, non è l’unico: Rebase è un mezzo di integrazione alternativo.

Comprendere un po ‘meglio

Quando Git esegue un’unione, cerca tre commit:

  • (1) commit degli antenati comuni Se si segue la cronologia di due rami in un progetto, hanno sempre almeno un commit in comune: in quel momento, entrambi i rami avevano lo stesso contenuto e quindi si evolvevano in modo diverso.
  • (2) + (3) Endpoint di ogni ramo L’objective di un’integrazione è quello di combinare gli attuali stati di due rami. Pertanto, le rispettive ultime revisioni sono di particolare interesse. La combinazione di questi tre commit comporterà l’integrazione a cui miriamo.

Avanzamento veloce o Unisci commit

In casi molto semplici, uno dei due rami non ha nuovi commit da quando è avvenuta la ramificazione – il suo ultimo commit è ancora l’antenato comune.

inserisci la descrizione dell'immagine qui

In questo caso, eseguire l’integrazione è semplicissimo: Git può semplicemente aggiungere tutti i commit dell’altro ramo sopra il commit degli antenati comuni. In Git, questa forma di integrazione più semplice è chiamata unione “veloce”. Entrambe le filiali condividono quindi la stessa storia.

inserisci la descrizione dell'immagine qui

In molti casi, tuttavia, entrambi i rami sono andati avanti individualmente. inserisci la descrizione dell'immagine qui

Per realizzare un’integrazione, Git dovrà creare un nuovo commit che contenga le differenze tra di essi: il merge commit.

inserisci la descrizione dell'immagine qui

Commits umani e commit di unione

Normalmente, un commit viene accuratamente creato da un essere umano. È un’unità significativa che include solo le modifiche correlate e le annota con un commento.

Un merge commit è un po ‘diverso: invece di essere creato da uno sviluppatore, viene creato automaticamente da Git. E invece di avvolgere una serie di modifiche correlate, il suo scopo è quello di colbind due rami, proprio come un nodo. Se si desidera comprendere un’operazione di unione in un secondo momento, è necessario dare un’occhiata alla cronologia di entrambi i rami e al grafico di commit corrispondente.

Integrazione con Rebase

Alcune persone preferiscono rinunciare a tali commit di unione automatica. Invece, vogliono che la storia del progetto appaia come se si fosse evoluta in una singola linea retta. Nessuna indicazione rimane che fosse stata divisa in più rami ad un certo punto.

inserisci la descrizione dell'immagine qui

Passiamo attraverso l’operazione di rebase passo dopo passo. Lo scenario è lo stesso degli esempi precedenti: vogliamo integrare i cambiamenti da branch-B a branch-A, ma ora usando rebase.

inserisci la descrizione dell'immagine qui

Lo faremo in tre passaggi

  1. git rebase branch-A // syncs the history with branch-A
  2. git checkout branch-A // change the current branch to branch-A
  3. git merge branch-B // merge/take the changes from branch-B to branch-A

In primo luogo, Git “annulla” tutti i commit sul ramo-A che è accaduto dopo che le linee hanno iniziato a diramarsi (dopo il commit degli antenati comuni). Tuttavia, ovviamente, non li scarterà: invece si può pensare a quei commit come “salvati temporaneamente”.

inserisci la descrizione dell'immagine qui

Quindi applica i commit dal ramo B che vogliamo integrare. A questo punto, entrambi i rami hanno lo stesso aspetto.

inserisci la descrizione dell'immagine qui

Nella fase finale, i nuovi commit sul ramo A vengono ora riapplicati, ma su una nuova posizione, in cima al commit integrato del ramo B (sono ri-basati). Il risultato sembra che lo sviluppo sia avvenuto in linea retta. Invece di un commit di unione che contiene tutte le modifiche combinate, la struttura di commit originale è stata conservata.

inserisci la descrizione dell'immagine qui

Finalmente si ottiene un ramo filiale pulito -A senza commit indesiderati e generati automaticamente.

Nota: tratto dal fantastico post di git-tower . Anche gli svantaggi di rebase sono una buona lettura nello stesso post.

Il libro dei professionisti come una spiegazione davvero buona sulla pagina di rebasing .

Fondamentalmente una fusione richiederà 2 commit e li combinerà.

A rebase will go to the common ancestor on the 2 and incrementally apply the changes on top of each other. This makes for a ‘cleaner’ and more linear history.

But when you rebase you abandon previous commits and create new ones. So you should never rebase a repo that is public. The other people working on the repo will hate you.

For that reason alone I almost exclusively merge. 99% of the time my branches don’t differ that much, so if there are conflicts it’s only in one or two places.

This answer is widely oriented around Git Flow . The tables have been generated with the nice ASCII Table Generator , and the history trees with this wonderful command ( aliased as git lg ):

 git log --graph --abbrev-commit --decorate --date=format:'%Y-%m-%d %H:%M:%S' --format=format:'%C(bold blue)%h%C(reset) - %C(bold cyan)%ad%C(reset) %C(bold green)(%ar)%C(reset)%C(bold yellow)%d%C(reset)%n'' %C(white)%s%C(reset) %C(dim white)- %an%C(reset)' 

Tables are in reverse chronological order to be more consistent with the history trees. See also the difference between git merge and git merge --no-ff first (you usually want to use git merge --no-ff as it makes your history look closer to the reality):

git merge

Commands:

 Time Branch "develop" Branch "features/foo" ------- ------------------------------ ------------------------------- 15:04 git merge features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

Risultato:

 * 142a74a - YYYY-MM-DD 15:03:00 (XX minutes ago) (HEAD -> develop, features/foo) | Third commit - Christophe * 00d848c - YYYY-MM-DD 15:02:00 (XX minutes ago) | Second commit - Christophe * 298e9c5 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git merge --no-ff

Commands:

 Time Branch "develop" Branch "features/foo" ------- -------------------------------- ------------------------------- 15:04 git merge --no-ff features/foo 15:03 git commit -m "Third commit" 15:02 git commit -m "Second commit" 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

Risultato:

 * 1140d8c - YYYY-MM-DD 15:04:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/foo' - Christophe | * 69f4a7a - YYYY-MM-DD 15:03:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 2973183 - YYYY-MM-DD 15:02:00 (XX minutes ago) |/ Second commit - Christophe * c173472 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git merge vs git rebase

First point: always merge features into develop, never rebase develop from features . This is a consequence of the Golden Rule of Rebasing :

The golden rule of git rebase is to never use it on public branches.

In other words :

Never rebase anything you’ve pushed somewhere.

I would personally add: unless it’s a feature branch AND you and your team are aware of the consequences .

So the question of git merge vs git rebase applies almost only to the feature branches (in the following examples, --no-ff has always been used when merging). Note that since I’m not sure there’s one better solution ( a debate exists ), I’ll only provide how both commands behave. In my case, I prefer using git rebase as it produces a nicer history tree 🙂

Between feature branches

git merge

Commands:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- -------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git merge --no-ff features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

Risultato:

 * c0a3b89 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 37e933e - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * eb5e657 - YYYY-MM-DD 15:07:00 (XX minutes ago) | |\ Merge branch 'features/foo' into features/bar - Christophe | * | 2e4086f - YYYY-MM-DD 15:06:00 (XX minutes ago) | | | Fifth commit - Christophe | * | 31e3a60 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | | Fourth commit - Christophe * | | 98b439f - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ \ Merge branch 'features/foo' - Christophe | |/ / |/| / | |/ | * 6579c9c - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 3f41d96 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 14edc68 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git rebase

Commands:

 Time Branch "develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m "Sixth commit" 15:07 git rebase features/foo 15:06 git commit -m "Fifth commit" 15:05 git commit -m "Fourth commit" 15:04 git commit -m "Third commit" 15:03 git commit -m "Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m "First commit" 

Risultato:

 * 7a99663 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 708347a - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 949ae73 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 108b4c7 - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 189de99 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ | * 26835a0 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * a61dd08 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * ae6f5fc - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

From develop to a feature branch

git merge

Commands:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m “Sixth commit" 15:08 git merge --no-ff development 15:07 git merge --no-ff features/foo 15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

Risultato:

 * 9e6311a - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 3ce9128 - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * d0cd244 - YYYY-MM-DD 15:08:00 (XX minutes ago) | |\ Merge branch 'develop' into features/bar - Christophe | |/ |/| * | 5bd5f70 - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | * | 4ef3853 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | | Third commit - Christophe | * | 3227253 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ / Second commit - Christophe | * b5543a2 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * 5e84b79 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 2da6d8d - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git rebase

Commands:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ------------------------------- 15:10 git merge --no-ff features/bar 15:09 git commit -m “Sixth commit" 15:08 git rebase development 15:07 git merge --no-ff features/foo 15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

Risultato:

 * b0f6752 - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 621ad5b - YYYY-MM-DD 15:09:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * 9cb1a16 - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * b8ddd19 - YYYY-MM-DD 15:05:00 (XX minutes ago) |/ Fourth commit - Christophe * 856433e - YYYY-MM-DD 15:07:00 (XX minutes ago) |\ Merge branch 'features/foo' - Christophe | * 694ac81 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * 5fd94d3 - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * d01d589 - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

Side notes

git cherry-pick

When you just need one specific commit, git cherry-pick is a nice solution (the -x option appends a line that says ” (cherry picked from commit…) ” to the original commit message body, so it’s usually a good idea to use it – git log to see it):

Commands:

 Time Branch “develop" Branch "features/foo" Branch "features/bar" ------- -------------------------------- ------------------------------- ----------------------------------------- 15:10 git merge --no-ff features/bar 15:09 git merge --no-ff features/foo 15:08 git commit -m “Sixth commit" 15:07 git cherry-pick -x  15:06 git commit -m “Fifth commit" 15:05 git commit -m “Fourth commit" 15:04 git commit -m “Third commit" 15:03 git commit -m “Second commit" 15:02 git checkout -b features/bar 15:01 git checkout -b features/foo 15:00 git commit -m “First commit" 

Risultato:

 * 50839cd - YYYY-MM-DD 15:10:00 (XX minutes ago) (HEAD -> develop) |\ Merge branch 'features/bar' - Christophe | * 0cda99f - YYYY-MM-DD 15:08:00 (XX minutes ago) (features/bar) | | Sixth commit - Christophe | * f7d6c47 - YYYY-MM-DD 15:03:00 (XX minutes ago) | | Second commit - Christophe | * dd7d05a - YYYY-MM-DD 15:06:00 (XX minutes ago) | | Fifth commit - Christophe | * d0d759b - YYYY-MM-DD 15:05:00 (XX minutes ago) | | Fourth commit - Christophe * | 1a397c5 - YYYY-MM-DD 15:09:00 (XX minutes ago) |\ \ Merge branch 'features/foo' - Christophe | |/ |/| | * 0600a72 - YYYY-MM-DD 15:04:00 (XX minutes ago) (features/foo) | | Third commit - Christophe | * f4c127a - YYYY-MM-DD 15:03:00 (XX minutes ago) |/ Second commit - Christophe * 0cf894c - YYYY-MM-DD 15:00:00 (XX minutes ago) First commit - Christophe 

git pull --rebase

Not sure I can explain it better than Derek Gourlay … Basically, use git pull --rebase instead of git pull 🙂 What’s missing in the article though, is that you can enable it by default :

 git config --global pull.rebase true 

git rerere

Again, nicely explained here . But put simple, if you enable it, you won’t have to resolve the same conflict multiple times anymore.

Git rebase is used to make the branching paths in history cleaner and repository structure linear.

It is also used to keep the branches created by you private, as after rebasing and pushing the changes to server, if you delete your branch, there will be no evidence of branch you have worked upon. So your branch is now your local concern.

After doing rebase we also get rid of an extra commit which we used to see if we do normal merge.

And yes one still needs to do merge after a successful rebase as rebase command just puts your work on top of the branch you mentioned during rebase say master and makes the first commit of your branch as a direct descendant of the master branch. This means we can now do a fast forward merge to bring changes from this branch to master branch.

Some practical examples, somewhat connected to large scale development where gerrit is used for review and delivery integration.

I merge when i uplift my feature branch to a fresh remote master. This gives minimal uplift work and it’s easy to follow the history of the feature development in for example gitk.

 git fetch git checkout origin/my_feature git merge origin/master git commit git push origin HEAD:refs/for/my_feature 

I merge when I prepare a delivery commit.

 git fetch git checkout origin/master git merge --squash origin/my_feature git commit git push origin HEAD:refs/for/master 

I rebase when my delivery commit fails integration for whatever reason and I need to update it towards a fresh remote master.

 git fetch git fetch  git checkout FETCH_HEAD git rebase origin/master git push origin HEAD:refs/for/master 

When do I use git rebase ? Almost never, because it rewrites history. git merge is almost always the preferable choice, because it respects what actually happened in your project.