Unire o fondere — è l’azione di combinare due rami in Git, unendo le modifiche da un ramo all’altro. Nello sviluppo moderno, il merge è il metodo standard per integrare un ramo di funzionalità nel ramo principale del progetto. Secondo GitHub Octoverse 2024, ogni giorno vengono eseguiti oltre 15 milioni di merge. Merge è un meccanismo chiave del lavoro collaborativo, che consente di combinare il lavoro di più sviluppatori in un unico prodotto.
Punti Chiave
Il merge in Git è l’operazione di combinare due o più storie di sviluppo in una sola. Quando uno sviluppatore unisce un ramo, Git trova automaticamente l’antenato comune (base commit) e crea un nuovo commit di merge che include le modifiche di entrambi i rami. Three-way merge è l’algoritmo standard che confronta tre stati: l’antenato comune, il primo ramo e il secondo ramo.
Il processo di merge inizia con il comando git merge. Git determina il punto di divergenza dei rami e applica sequenzialmente le modifiche dal ramo sorgente al ramo di destinazione. Se le modifiche non sono in conflitto, Git esegue un fast-forward o crea un merge commit a seconda delle impostazioni. Fast-forward è uno scenario in cui il ramo di destinazione si sposta semplicemente sui commit del ramo sorgente.
# Passare al ramo di destinazione e unire
git checkout main
git merge feature/payment-module
# Unire con no-fast-forward esplicito
git merge --no-ff feature/payment-module
# Interrompere il merge se i conflitti sono troppo complessi
git merge --abort
Il flag --no-ff (no fast-forward) forza la creazione di un merge commit anche quando è possibile un fast-forward. Questo preserva l’informazione che le modifiche sono state effettuate in un ramo separato. Molti team preferiscono questo approccio per mantenere esplicitamente la cronologia di ramificazione.
Git ha tre strategie principali di fusione dei rami, ciascuna adatta a uno scenario specifico. La scelta della strategia dipende dalla cultura del team e dai requisiti di pulizia della cronologia del progetto.
| Strategia | Risultato | Quando usarla |
|---|---|---|
| Standard merge | merge commit + cronologia completa | team che apprezzano la cronologia completa |
| Squash merge | un commit, cronologia compressa | rami di funzionalità con molti commit piccoli |
| Rebase merge | cronologia lineare, senza merge commit | rami di funzionalità personali, prima di creare un PR |
Standard merge crea un merge commit con due genitori. La cronologia completa viene preservata, ma il grafo di ramificazione diventa più complesso. Squash merge combina tutti i commit di un ramo di funzionalità in uno solo e lo applica sul ramo di destinazione — la cronologia diventa lineare e pulita, ma le informazioni sulle fasi intermedie vengono perse.
Rebase, sebbene non sia un merge completo, ottiene lo stesso risultato — le modifiche di un ramo vengono spostate sopra un altro. La differenza è che la cronologia viene riscritta: i commit del ramo di funzionalità vengono ricreati sopra l’ultimo commit del ramo di destinazione. Questo dà una cronologia perfettamente lineare, ma richiede force push durante l’invio.
Un conflitto di merge si verifica quando le stesse righe di un file vengono modificate in due rami. Git non può determinare automaticamente quale versione mantenere e richiede l’intervento dello sviluppatore. I conflitti vengono visualizzati nei file utilizzando marcatori speciali: <<<<<<<, =======, >>>>>>>.
Il processo di risoluzione dei conflitti comprende diversi passaggi. Prima, lo sviluppatore apre il file in conflitto e seleziona manualmente le modifiche necessarie. È importante non solo scegliere una versione, ma comprendere la logica di entrambe le modifiche e prendere una decisione corretta. Dopo aver modificato il file, i marcatori di conflitto vengono rimossi e le modifiche vengono aggiunte all’area di staging tramite git add.
# Visualizzare l'elenco dei file in conflitto
git status
# Avviare mergetool (es. VS Code, IntelliJ)
git mergetool
# Dopo aver risolto tutti i conflitti
git add .
git merge --continue
# O annullare completamente il merge
git merge --abort
L’uso di strumenti visivi di merge accelera significativamente la risoluzione dei conflitti. VS Code, IntelliJ IDEA e GitKraken forniscono interfacce con tre pannelli: ramo corrente, ramo entrante e risultato. Lo strumento git mergetool apre automaticamente l’editor configurato per ogni file in conflitto.
Il modo migliore per evitare conflitti complessi è la sincronizzazione regolare del ramo di funzionalità con il ramo principale. Se uno sviluppatore unisce main nel proprio ramo una volta al giorno, i conflitti saranno piccoli e facilmente risolvibili. Accumulare modifiche per una settimana garantisce conflitti complessi con un alto rischio di errori.
Rebase e merge sono due modi per combinare le modifiche, e la scelta tra di loro spesso genera dibattiti nei team. Rebase sposta i commit da un ramo sopra un altro, riscrivendo la cronologia. Merge crea un nuovo commit di merge, preservando la cronologia di ramificazione. Ciascun approccio ha i suoi vantaggi e limiti.
Rebase è appropriato quando uno sviluppatore lavora sul proprio ramo di funzionalità locale e vuole una cronologia lineare pulita prima di creare una Pull Request. Dopo il rebase, tutti i commit vengono organizzati sequenzialmente senza commit di merge non necessari. Tuttavia, rebase richiede force push e non è applicabile ai rami su cui lavorano più persone contemporaneamente.
La regola d’oro di Git: non usare rebase su commit che sono già stati inviati al repository condiviso. Questo garantisce che la cronologia nel ramo condiviso rimanga invariata e che gli altri sviluppatori non incontrino commit duplicati o persi. Per integrare un ramo di funzionalità nel ramo principale, usa merge tramite Pull Request.
Un corretto processo di merge è il fondamento dello sviluppo stabile. Nel lavoro di squadra moderno, la fusione non viene eseguita tramite console, ma tramite Pull Request su GitHub o Merge Request in GitLab. Un PR passa attraverso la revisione del codice, i controlli automatici CI e solo dopo viene unito al ramo principale.
La prima pratica — unire solo dopo che tutti i controlli sono stati superati. La pipeline CI deve compilare il progetto, eseguire i test e verificare la qualità del codice. Se almeno un controllo fallisce, il merge viene bloccato. Le piattaforme moderne (GitHub, GitLab) hanno protezione integrata: le branch protection rules bloccano automaticamente il merge in caso di fallimento del CI.
La seconda pratica — non unire mai codice rotto. Prima del merge, lo sviluppatore deve assicurarsi che le sue modifiche non rompano la build e non regrediscano le funzionalità esistenti. A questo scopo esistono test automatici e revisione del codice.
La terza pratica — pulire i rami di funzionalità dopo il merge. Un ramo che è già stato unito deve essere eliminato. Questo previene confusione e disordine nel repository. GitHub offre automaticamente di eliminare il ramo dopo l’unione di un PR e le impostazioni del repository possono essere configurate per l’eliminazione automatica.
Domande Frequenti
Un merge è la combinazione di due rami Git in uno. Le modifiche da un ramo vengono trasferite a un altro tramite una fusione a tre vie (three-way merge). Il risultato viene registrato in un nuovo commit di merge che ha due commit genitori. Merge commit preserva le informazioni su quali rami sono stati uniti.
Merge crea un nuovo commit di merge, preservando la cronologia di ramificazione. Rebase riscrive la cronologia spostando i commit su un altro ramo senza creare un commit di merge. Rebase dà una cronologia lineare ma richiede force push. Merge è più sicuro per i rami condivisi, rebase è migliore per i rami personali.
Apri il file in conflitto, trova i marcatori <<<<<<<, ======= e >>>>>>>, seleziona le modifiche necessarie e rimuovi i marcatori. Aggiungi il file tramite git add e completa il merge con git merge --continue. Usa git mergetool per la risoluzione visiva in VS Code o IntelliJ IDEA.
Pull Request (o Merge Request) è obbligatorio quando si unisce un ramo di funzionalità nel ramo principale del progetto. Un PR passa attraverso la revisione del codice dei colleghi e i controlli automatici CI. Questo è lo standard dello sviluppo moderno. Il push diretto nel ramo principale è vietato nella maggior parte dei progetti.
Squash merge combina tutti i commit di un ramo di funzionalità in uno solo prima di unire. Questo dà una cronologia pulita del ramo principale senza commit di bozza intermedi. Usa squash merge quando un ramo di funzionalità contiene molti commit di servizio (wip, fixes) e non è necessario preservare tutti i passaggi intermedi nella cronologia.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche