Git merge vs rebase: quale scegliere per il proprio team
Sia merge sia rebase integrano le modifiche di due branch, ma trattano la cronologia in modo diverso: merge la conserva così com'è e aggiunge un commit di merge, rebase riscrive i vostri commit sopra la nuova base e rende la cronologia lineare. Regola pratica: rebase per mettere ordine nei propri commit non ancora pubblicati, merge per integrare il lavoro finito nei branch condivisi.
In breve: la differenza tra merge e rebase
Il dibattito «git merge vs rebase» in realtà non riguarda quale comando sia migliore, ma quale cronologia del progetto volete vedere. Entrambi i comandi portano allo stesso codice finale. Cambia il percorso che resta registrato nel log.
- Merge unisce due linee di cronologia e registra onestamente che il lavoro è proceduto in parallelo. I commit esistenti non cambiano: si aggiunge un merge commit con due genitori.
- Rebase riscrive i vostri commit come se aveste iniziato a lavorare dalla versione più recente del branch di base. La cronologia appare come una linea retta, ma i commit ottengono nuovi hash.
La meccanica di ciascun comando (flag, risoluzione dei conflitti, modalità interattiva) è trattata in articoli dedicati. Qui parliamo solo della scelta della strategia.
Come appare la cronologia nei due casi
Situazione di partenza: avete creato feature, fatto due commit, e nel frattempo su main sono arrivati due commit di altri.
D---E feature
/
A---B---C---F---G main
Dopo git merge feature su main:
D---E
/ \
A---B---C---F---G---M main
Dopo git rebase main su feature e il fast-forward di main:
A---B---C---F---G---D'---E' main
Nel primo caso la cronologia mostra che la feature è stata sviluppata in parallelo e quando è stata integrata. Nel secondo la cronologia si legge come un racconto sequenziale, ma il fatto che il lavoro sia stato parallelo sparisce: D' ed E' sono commit nuovi, creati al momento del rebase.
Tabella di confronto git merge vs rebase
| Criterio | git merge | git rebase |
|---|---|---|
| Commit esistenti | Non cambiano | Vengono ricreati con nuovi hash |
| Forma della cronologia | Grafo ramificato con merge commit | Lineare |
| Sicurezza sui branch condivisi | Sempre sicuro | Pericoloso per i commit già pubblicati |
| Serve un force push | No | Sì, se il branch è già sul server |
| Conflitti | Si risolvono una volta, nel merge commit | Possono comparire su ogni commit spostato |
| Contesto «da dove arriva» | Conservato | Perso |
| Annullare un'intera feature | Un solo git revert -m 1 del merge commit | Revert di ogni commit o di un intervallo |
Comodità di git bisect e git log | Grafo più difficile da leggere | Più semplice: una sola linea retta |
| Curva di apprendimento | Bassa | Più alta, bisogna capire la riscrittura della cronologia |
| Pulizia dei commit (squash, reword) | Solo --squash in blocco | Flessibile con rebase -i |
Pro e contro di merge
Pro
- Non distrugge la cronologia. Tutto ciò che è stato pubblicato resta al suo posto. I colleghi non dovranno sistemare nulla dopo la vostra operazione.
- Il conflitto si risolve una volta sola. Anche se il branch ha 30 commit, lavorate sullo stato finale e non su ogni singolo passo.
- Il contesto resta. Dal merge commit si vede quale attività è entrata nel branch principale e quando, ed è facile annullarla per intero.
Contro
- Grafo rumoroso. Con sincronizzazioni frequenti la cronologia si riempie di commit «Merge branch 'main' into feature» e
git log --graphdiventa una mappa ferroviaria. - I commit di bozza finiscono nella cronologia. «wip», «fix», «altro fix» restano per sempre nel branch principale, se non li comprimete.
Pro e contro di rebase
Pro
- Cronologia lineare e leggibile. Ogni commit è al suo posto,
git logsi legge come un elenco di modifiche egit bisecttrova più facilmente il commit con il bug. - Possibilità di riordinare i commit prima della review: unirli, rinominarli, cambiarne l'ordine.
- Niente merge commit tecnici dovuti alla sincronizzazione con il branch principale.
Contro
- Riscrive la cronologia. Il rebase di un branch condiviso già pubblicato rompe il lavoro di chiunque lo abbia scaricato.
- Richiede un force push per i branch sul server, quindi disciplina e
--force-with-lease. - Conflitti ripetuti. Lo stesso blocco di codice può andare in conflitto su più commit di fila.
- I commit intermedi potrebbero non compilare. Dopo lo spostamento sulla nuova base, nessuno ha testato i vecchi commit nel nuovo contesto.
Quando usare l'uno o l'altro
Rebase per il proprio lavoro locale
Aggiornate il vostro branch prima di inviarlo: git pull --rebase. Preparate il branch per la review e volete eliminare i commit di bozza: git rebase -i. Il vostro feature branch personale è rimasto indietro rispetto a main: git rebase origin/main. In tutti questi casi riscrivete commit che avete visto solo voi (o che appartengono solo a voi).
Merge per i branch condivisi e l'integrazione
Una feature finita viene integrata in main o develop. Sul branch lavorano più persone. Bisogna riportare un branch di release in quello principale. Qui è importante non toccare i commit già pubblicati, e merge è l'unica opzione corretta.
Una regola semplice
Se i commit li avete solo voi, potete fare rebase. Se qualcuno li ha già scaricati, solo merge.
Flussi di lavoro tipici per il team
| Schema | Come funziona | Per chi è adatto |
|---|---|---|
| Solo merge | Le feature si integrano con --no-ff, la sincronizzazione con git merge main | Chi è alle prime armi con Git, team per cui contano una cronologia completa e rollback semplici |
| Rebase + merge | Prima dell'integrazione il branch viene ribasato su main, poi un merge commit con --no-ff | Team che vogliono sia una cronologia pulita sia i confini delle feature |
| Rebase + fast-forward | Il branch viene ribasato e integrato in fast-forward, la cronologia è del tutto lineare | Team piccoli con commit ordinati e atomici |
| Squash merge | Ogni pull request diventa un solo commit su main | Team con task brevi, dove la cronologia dettagliata del branch non serve |
Su GitHub e GitLab questi schemi corrispondono ai pulsanti «Create a merge commit», «Rebase and merge» e «Squash and merge». Conviene abilitare nelle impostazioni del repository solo l'opzione scelta, così la cronologia non diventa un miscuglio di stili.
Impostazioni che fissano la scelta
# pull senza merge commit superflui
git config --global pull.rebase true
# oppure: pull solo in fast-forward, altrimenti errore e decidete voi
git config --global pull.ff only
# sempre un merge commit quando si integrano le feature
git config --global merge.ff false
Attenzione a merge.ff false: vale anche per git pull in modalità merge. Molti preferiscono non impostarlo a livello globale e scrivere esplicitamente --no-ff quando integrano una feature.
Esempio: lo stesso task in due stili
Vediamo uno scenario reale. Da tre giorni sviluppate la feature dei pagamenti nel branch feature/payment, e nel frattempo su main sono stati integrati cinque task di altri. Prima della pull request dovete portarvi dentro il main aggiornato e verificare che tutto funzioni.
Variante con merge
git switch feature/payment
git fetch origin
git merge origin/main
# conflitti risolti una volta sola, git add, git commit
git push
Il branch ha ricevuto il commit «Merge remote-tracking branch 'origin/main' into feature/payment». Il push è normale e i colleghi che seguivano il branch non notano nulla. Se sincronizzate così ogni giorno, nel branch ci saranno tre commit del genere, e finiranno su main insieme alla feature se integrate senza squash.
Variante con rebase
git switch feature/payment
git fetch origin
git rebase origin/main
# i conflitti si risolvono strada facendo, commit per commit: git add, git rebase --continue
git rebase -i origin/main # facoltativo: unire i commit di bozza
git push --force-with-lease
Il branch sembra creato stamattina dal main aggiornato, non ci sono commit superflui e chi fa la review legge le modifiche passo per passo più facilmente. Il prezzo è il push forzato e la necessità di assicurarsi che nessun altro faccia commit su quel branch.
Cosa vedrà alla fine main
Con l'integrazione tramite pull request la differenza rimane. Lo stile merge produce un grafo con diramazioni e merge di sincronizzazione, lo stile rebase una catena ordinata di commit della feature, che si può integrare in fast-forward o con un unico merge commit. Il codice sarà identico, a patto che i conflitti siano stati risolti allo stesso modo.
Come decidere in team
Rispondete ad alcune domande. Più persone lavorano sullo stesso branch? Se sì, il rebase per quel branch è escluso. Quanto è esperto il team con Git? Se ci sono spesso nuovi arrivati, partite da merge e squash merge: perdonano gli errori. Serve una cronologia completa per audit e analisi degli incidenti? Allora i merge commit delle feature sono utili. Contano un git log leggibile e un uso frequente di git bisect? Allora vince il rebase con la cronologia lineare.
Scrivete la decisione nel CONTRIBUTING o nel README del repository e attivate la protezione del branch principale: niente force push, pull request obbligatoria, metodo di merge consentito. Una regola fissata nelle impostazioni funziona meglio di qualsiasi accordo verbale.
Errori di valutazione comuni
- «Rebase è roba da esperti, merge è per principianti». No: sono strumenti diversi per compiti diversi. Gli sviluppatori esperti li usano entrambi.
- «I merge commit sono spazzatura». Quelli di sincronizzazione spesso sì, sono rumore. Ma il merge commit che integra una feature è un punto utile: mostra e permette di annullare un intero task.
- «Con rebase ci sono meno conflitti». I conflitti sono gli stessi, perché cambiano le stesse righe. Rebase li distribuisce sui singoli commit, merge li risolve tutti insieme.
- «Rebase perde i commit». I vecchi commit restano in
git refloge si possono recuperare finché il garbage collector non li elimina.
Conclusioni
Scegliere tra git merge vs rebase significa scegliere tra una cronologia fedele e una cronologia lineare e leggibile. Merge è sicuro e conserva il contesto, rebase offre pulizia ma riscrive i commit. La pratica più affidabile è combinarli: ribasare e ripulire il proprio branch finché è solo vostro, e integrare il lavoro finito nei branch condivisi con merge o squash secondo le regole del team. L'essenziale è accordarsi su uno schema e fissarlo nelle impostazioni del repository. Entrambe le strategie e il lavoro in team con i branch sono trattati in pratica nel corso su Git.
Domande frequenti
Per git pull è meglio merge o rebase?
Per aggiornare il branch locale è più comodo rebase: i vostri commit non pubblicati si posizionano sopra quelli remoti aggiornati, senza merge commit superflui. Si attiva con git config --global pull.rebase true.
Si possono mescolare merge e rebase nello stesso progetto?
Sì, ed è l'approccio più diffuso: rebase per i propri branch, merge per quelli condivisi. L'importante è che la regola sia nota a tutto il team.
Cosa conviene scegliere a un principiante?
Iniziate con merge: non riscrive la cronologia ed è più difficile rompere qualcosa. Imparate rebase sui vostri branch locali, una volta presa confidenza con reflog e force push.
La strategia scelta influisce sul codice finale?
No, se i conflitti sono risolti allo stesso modo lo stato finale dei file coincide. Cambia solo la cronologia dei commit registrata.
Che differenza c'è tra squash merge e rebase?
Lo squash merge comprime l'intero branch in un unico nuovo commit sul branch principale, mentre rebase sposta ogni commit singolarmente. Entrambi producono una cronologia lineare, ma lo squash perde il dettaglio dei singoli passi.
Questo argomento è trattato in modo approfondito, con pratica, nel corso «Git da zero a sviluppatore sicuro».
- Processi Git nei team reali: quale workflow scegliere
- Strumenti avanzati: rebase, stash, cherry-pick e tag