Git 09
🔀 Git & GitHub · Modulo 2 · Lezione 09
Merge: fast-forward e three-way 🔀
Ricombinare i branch · Fast-forward vs three-way · Merge commit · git merge in pratica
🔀 Creare branch è solo metà del lavoro. L’altra metà è rimetterli insieme. Il merge è l’operazione che prende il lavoro fatto su un branch e lo integra in un altro. Git è straordinariamente bravo a farlo automaticamente — nella maggior parte dei casi, un merge richiede un solo comando e zero intervento manuale. In questa lezione impari i due tipi di merge e quando Git usa ciascuno.
🎯 Obiettivi della lezione
- Capire cosa significa “fare merge” di un branch
- Distinguere tra merge fast-forward e merge three-way (ricorsivo)
- Eseguire un merge con
git merge - Leggere un merge commit nel log
- Capire quando Git sceglie fast-forward e quando three-way
- Forzare un merge commit con
--no-ff - Pulire il branch dopo il merge
📖 1. Teoria
1.1 — Cos’è un merge
Un merge è l’operazione che integra le modifiche di un branch in un altro. Il workflow tipico è: (1) crei un branch, (2) ci lavori sopra, (3) quando il lavoro è finito, fai merge del branch dentro main. Dopo il merge, main contiene tutto il lavoro che hai fatto sul branch.
Il comando è semplice: ti sposti sul branch che deve ricevere le modifiche (di solito main), poi esegui git merge nome-branch:
$ git switch main
# 2. Integra il branch sorgente
$ git merge feature/navbar
📌 Regola di direzione: il merge porta le modifiche nel branch su cui sei. Se sei su main e fai git merge feature/navbar, le modifiche di navbar entrano in main. Il branch navbar non viene toccato — dopo il merge esiste ancora e punta allo stesso commit di prima.
1.2 — Fast-forward merge: il caso semplice
Il fast-forward succede quando il branch di destinazione (main) non ha ricevuto nuovi commit da quando il branch sorgente è stato creato. In altre parole, main è rimasto fermo — tutto il lavoro nuovo è sul branch.
git merge feature: fast-forward — main si sposta$ git merge feature/navbar
Updating a6b7c8d..b1a2c3d
Fast-forward
navbar.html | 1 +
style.css | 1 +
2 files changed, 2 insertions(+)
1.3 — Three-way merge: il caso con divergenza
Il three-way merge succede quando entrambi i branch hanno ricevuto commit dopo la separazione. Main non è più fermo — qualcuno ci ha lavorato sopra. Git deve combinare le modifiche di entrambi i branch.
git merge feature: Git crea un merge commit$ git merge feature/contatti
Merge made by the ‘ort’ strategy.
contatti.html | 8 ++++++++
1 file changed, 8 insertions(+)
create mode 100644 contatti.html
Merge branch 'feature/contatti' va benissimo).1.4 — Confronto: fast-forward vs three-way
| Fast-forward | Three-way | |
|---|---|---|
| Quando | Main non ha nuovi commit dopo la separazione | Entrambi i branch hanno nuovi commit |
| Cosa fa Git | Sposta il puntatore di main in avanti | Crea un nuovo commit (merge commit) con 2 genitori |
| Merge commit? | No — storia lineare | Sì — storia ramificata |
| Output nel terminale | Fast-forward | Merge made by the ‘ort’ strategy |
| Apre l’editor? | No | Sì (per il messaggio del merge commit) |
| Conflitti possibili? | Mai | Possibili (li gestirai nella Lezione 10) |
1.5 — Il merge commit: un commit speciale
Un merge commit è un commit con due genitori invece di uno solo. Ogni commit normale ha un solo genitore (il commit precedente). Un merge commit ha due genitori: uno per ciascun branch che è stato combinato.
* f8a9b0c (HEAD -> main) Merge branch ‘feature/contatti’
|\
| * d7e8f9a (feature/contatti) Aggiunto form di contatto
| * b5c6d7e Creata pagina contatti base
* | a1b2c3d Corretto typo nella home
|/
* e4f5a6b Aggiunto foglio di stile
f8a9b0c ha la forma |\ nel grafo: due linee che si ricongiungono. Puoi vedere i due rami — main (sinistra) e feature/contatti (destra) — che convergono nel merge commit.$ git show –stat f8a9b0c
commit f8a9b0c…
Merge: a1b2c3d d7e8f9a ← due genitori! (main e feature)
Author: Mario Rossi <mario.rossi@email.it>
Date: …
Merge branch ‘feature/contatti’
contatti.html | 8 ++++++++
1 file changed, 8 insertions(+)
1.6 — Forzare un merge commit: –no-ff
A volte vuoi un merge commit anche quando Git potrebbe fare fast-forward. Perché? Perché il merge commit crea un “nodo” visibile nella storia che documenta quando e perché un branch è stato integrato. Con il fast-forward, quella informazione si perde — la storia sembra lineare come se il branch non fosse mai esistito.
* C3
* C2
* C1
Lineare — nessuna traccia del branch
|\
| * C4 (feature)
| * C3
|/
* C2
* C1
Si vede che C3-C4 erano su un branch
📌 Quale scegliere? Per progetti scolastici e personali, il fast-forward (default) va benissimo. Per team professionali, molti preferiscono --no-ff perché il merge commit documenta chiaramente che una feature è stata sviluppata su un branch separato. GitHub stesso usa sempre merge commit (non fast-forward) quando integri una Pull Request.
1.7 — Dopo il merge: pulizia
Dopo un merge, il branch sorgente esiste ancora — punta allo stesso commit di prima. Se il lavoro è finito, puoi (e dovresti) cancellarlo per tenere il repository pulito:
$ git branch -d feature/navbar
Deleted branch feature/navbar (was b1a2c3d).
# -d funziona perché il branch è stato merged!
# Se non fosse stato merged, Git avrebbe rifiutato.
$ git branch
* main
1.8 — Il workflow completo: branch → lavora → merge → pulisci
feature/x
add, commit
…
git merge
feature/x
feature/x
💻 2. Esempi guidati
Esempio A — Fast-forward merge
$ mkdir lab-merge && cd lab-merge && git init
$ echo “<h1>Home</h1>” > index.html
$ git add . && git commit -m “Setup: pagina home”
# === BRANCH E LAVORO ===
$ git switch -c feature/footer
$ echo “” > footer.html
$ git add . && git commit -m “Aggiunto footer”
$ echo “footer { background: #333; }” > footer.css
$ git add . && git commit -m “Aggiunti stili footer”
# === MERGE (main non ha nuovi commit = fast-forward!) ===
$ git switch main
$ git merge feature/footer
Fast-forward
footer.css | 1 +
footer.html | 1 +
2 files changed, 2 insertions(+)
# === VERIFICA ===
$ git log –oneline –graph –all
* c3d4e5f (HEAD -> main, feature/footer) Aggiunti stili footer
* a1b2c3d Aggiunto footer
* e4f5a6b Setup: pagina home
$ git branch -d feature/footer
Deleted branch feature/footer (was c3d4e5f).
Esempio B — Three-way merge
# === CREIAMO DIVERGENZA ===
$ git switch -c feature/contatti
$ echo “<form>Contattaci</form>” > contatti.html
$ git add . && git commit -m “Aggiunta pagina contatti”
# Torna a main e fai un commit (questo crea la divergenza!)
$ git switch main
$ echo “
Benvenuto sul sito
” >> index.html$ git add . && git commit -m “Aggiunto paragrafo di benvenuto”
# Ora main e feature/contatti hanno entrambi commit nuovi
$ git log –oneline –graph –all
* b7c8d9e (HEAD -> main) Aggiunto paragrafo di benvenuto
| * d4e5f6a (feature/contatti) Aggiunta pagina contatti
|/
* c3d4e5f Aggiunti stili footer
* …
# === MERGE (three-way perché c’è divergenza) ===
$ git merge feature/contatti
# Si apre l’editor con il messaggio: Merge branch ‘feature/contatti’
# Salva e chiudi l’editor
Merge made by the ‘ort’ strategy.
contatti.html | 1 +
1 file changed, 1 insertion(+)
# === VERIFICA: ora c’è un merge commit! ===
$ git log –oneline –graph –all
* f8a9b0c (HEAD -> main) Merge branch ‘feature/contatti’
|
| * d4e5f6a (feature/contatti) Aggiunta pagina contatti
* | b7c8d9e Aggiunto paragrafo di benvenuto
|/
* c3d4e5f Aggiunti stili footer
f8a9b0c ha due genitori (le due linee che convergono). Main ora contiene sia il paragrafo di benvenuto che la pagina contatti.Esempio C — Merge con –no-ff
$ git branch -d feature/contatti
# Creiamo un nuovo branch (senza divergenza su main)
$ git switch -c feature/logo
$ echo “<img src=’logo.png’>” > logo.html
$ git add . && git commit -m “Aggiunto logo”
# Merge forzando il merge commit
$ git switch main
$ git merge –no-ff feature/logo
# Si apre l’editor — salva e chiudi
Merge made by the ‘ort’ strategy.
$ git log –oneline –graph -n 4
* a2b3c4d (HEAD -> main) Merge branch ‘feature/logo’
|
| * e5f6a7b (feature/logo) Aggiunto logo
|/
* f8a9b0c Merge branch ‘feature/contatti’
--no-ff ha forzato un merge commit. Nel grafo si vede chiaramente il “palloncino” del branch logo.🏋️ 3. Esercizi
🟢 Esercizio 1 — Il tuo primo merge (Facile)
b) Crea un branch
feature/about, crea about.html e fai 1 commit.c) Torna a main. Fai il merge:
git merge feature/about.d) Che tipo di merge è stato? (Guarda l’output: “Fast-forward” o “Merge made by”?)
e) Il file
about.html è visibile su main?f) Cancella il branch con
git branch -d feature/about.g) Il file
about.html è ancora su main? (Sì — il merge lo ha integrato.)
🔵 Esercizio 2 — Three-way merge (Medio)
feature/gallery e fai 2 commit (crea gallery.html, poi aggiungi contenuto).b) Torna a main e fai 1 commit (modifica README.md aggiungendo una riga).
c) Verifica con
git log --oneline --graph --all che i branch abbiano diverguto.d) Fai il merge:
git merge feature/gallery.e) Che tipo di merge è stato? C’è un merge commit?
f) Quanti genitori ha il merge commit? (Verificalo con
git show --stat HEAD — cerca “Merge:”)g) Cancella il branch e verifica la storia.
🟠 Esercizio 3 — ff vs –no-ff (Medio)
b) Crea
feature/a, fai 1 commit. Torna a main. Merge con fast-forward (default).c) Guarda il grafo con
git log --oneline --graph. È lineare?d) Crea
feature/b, fai 1 commit. Torna a main. Merge con --no-ff.e) Guarda il grafo. Si vede il “palloncino” del branch?
f) Confronta i due grafi. Quale preferisci? Spiega perché.
🟣 Esercizio 4 — Simulazione progetto completo (Difficile)
Simula un ciclo di sviluppo completo con 3 feature:
progetto-merge con commit iniziale: README.md, index.html, style.css.b) Feature 1 (navbar): crea branch, 2 commit, torna a main, merge, cancella branch.
c) Feature 2 (gallery): crea branch e fai 1 commit. Ma prima di fare merge, torna a main e fai un commit di fix sul README. Poi merge la gallery. Che tipo di merge è stato?
d) Feature 3 (contatti): crea branch, 2 commit, torna a main, merge con
--no-ff, cancella branch.e) Guarda il grafo finale:
git log --oneline --graph --all. Disegnalo su carta. Identifica: quanti merge commit ci sono? Quali sono fast-forward e quali three-way?f) Quanti file ha main alla fine? Elencali con
ls.
❓ 4. Quiz di verifica
1. Quando fai git merge feature/x, le modifiche entrano nel branch feature/x o nel branch su cui ti trovi?
Mostra risposta
Le modifiche entrano nel branch su cui ti trovi (quello indicato da HEAD). Se sei su main e fai git merge feature/x, le modifiche di feature/x vengono integrate in main. Il branch feature/x non viene toccato — punta ancora allo stesso commit di prima. Per questo il primo passo è sempre git switch main (o il branch che deve ricevere le modifiche) prima di eseguire il merge.
2. Qual è la condizione per un fast-forward merge?
Mostra risposta
Il fast-forward è possibile solo quando il branch di destinazione (main) non ha ricevuto nuovi commit dopo la creazione del branch sorgente. In altre parole, main è un antenato diretto del branch — c’è un percorso lineare senza biforcazioni. In questo caso, Git non ha bisogno di “combinare” nulla — basta spostare il puntatore di main in avanti fino al commit del branch sorgente. Se main ha nuovi commit (i branch hanno diverguto), il fast-forward non è possibile e Git deve fare un three-way merge.
3. Cos’è un merge commit e cosa lo rende diverso da un commit normale?
Mostra risposta
Un merge commit è un commit speciale che ha due genitori invece di uno. Ogni commit normale ha un solo genitore (il commit precedente). Un merge commit ha due genitori: uno dal branch di destinazione e uno dal branch sorgente. Nel grafo di git log --graph, appare come il punto dove due linee convergono. Il contenuto del merge commit è il risultato della combinazione delle modifiche di entrambi i branch. Puoi vedere i due genitori con git show --stat HASH (la riga “Merge: hash1 hash2”).
4. Dopo il merge, il branch sorgente esiste ancora. Perché, e cosa dovresti fare?
Mostra risposta
Il merge non cancella il branch sorgente — ne integra solo le modifiche nel branch di destinazione. Il branch sorgente continua a esistere e punta allo stesso commit di prima. Se il lavoro su quel branch è finito, dovresti cancellarlo con git branch -d nome per tenere il repository pulito. I commit non vengono cancellati perché sono raggiungibili da main attraverso il merge. Se prevedi di continuare a lavorare sullo stesso branch, puoi tenerlo — ma in pratica è meglio creare branch nuovi per ogni nuova unità di lavoro.
5. A cosa serve il flag --no-ff in git merge --no-ff?
Mostra risposta
Forza la creazione di un merge commit anche quando Git potrebbe fare fast-forward. Senza --no-ff, se i branch non hanno diverguto, Git fa fast-forward e la storia resta lineare — non c’è traccia visiva nel log che il lavoro è stato fatto su un branch separato. Con --no-ff, il merge commit crea un “nodo” nel grafo che documenta chiaramente quando e da dove le modifiche sono state integrate. Molti team lo preferiscono perché rende la storia più leggibile. GitHub usa questo comportamento quando integri una Pull Request.
6. Nell’output di git merge leggi “Fast-forward”. Cosa è successo e quanti commit sono stati creati?
Mostra risposta
Zero commit sono stati creati. Git ha semplicemente spostato il puntatore del branch di destinazione (main) in avanti fino al commit a cui puntava il branch sorgente. Non c’è stato bisogno di combinare modifiche perché main non aveva commit propri — era un antenato diretto del branch sorgente. La storia resta lineare, come se il lavoro fosse stato fatto direttamente su main. È l’operazione di merge più semplice e veloce possibile.
📚 Riepilogo della lezione
git merge nome-branch— integra le modifiche nel branch corrente- Prima di merge: spostati sul branch che deve ricevere (
git switch main) - Fast-forward: main non ha nuovi commit → Git sposta il puntatore → storia lineare, nessun merge commit
- Three-way: entrambi i branch hanno diverguto → Git crea un merge commit con 2 genitori
--no-ff— forza il merge commit anche quando il fast-forward è possibile- Dopo il merge:
git branch -d nomeper cancellare il branch sorgente - Il workflow: branch → lavora → switch main → merge → cancella branch
- Merge commit nel grafo:
|\e|/— due linee che convergono
Prossima lezione →
Lezione 10 — Gestire i conflitti di merge
Come nascono i conflitti, come si presentano nel file, come risolverli.
Marker <<< === >>>. Esercitazione pratica con conflitto simulato.
Corso Git & GitHub · Per studenti e insegnanti · Lezione 09 di 45