Lezione 11 di 35

Git 11

🔀 Git & GitHub · Modulo 2 · Lezione 11

Rebase 🧬

Merge vs rebase · Riscrivere la base di un branch · Rebase interattivo · La regola d’oro

⏱️ Durata stimata: 75–90 min 📊 Livello: Intermedio (Modulo 2) 🔧 Strumenti: Terminale · Editor di testo 📋 Prerequisiti: Lezioni 01–10 (sai fare merge e risolvere conflitti)

🧬 Il rebase è l’alternativa al merge per integrare le modifiche tra branch. Dove il merge crea un “nodo” nella storia (il merge commit), il rebase riscriverebbe la storia come se i commit fossero sempre stati fatti sopra la versione più recente di main. Il risultato è una storia lineare e pulita — ma con un costo: stai riscrivendo la storia. In questa lezione impari quando è vantaggioso e quando è pericoloso.

🎯 Obiettivi della lezione

  • Capire cosa fa git rebase concettualmente: “riappoggiare” i commit su una nuova base
  • Confrontare merge e rebase: vantaggi, svantaggi, risultato nel grafo
  • Eseguire un rebase base: git rebase main
  • Risolvere conflitti durante un rebase
  • Usare il rebase interattivo per riordinare, combinare e modificare commit
  • Conoscere la regola d’oro: non fare rebase su branch pubblici

📖 1. Teoria

1.1 — Il problema: la storia diventa complicata

Con il merge, ogni integrazione di branch crea un merge commit. Su un progetto attivo con molti branch, il grafo diventa rapidamente un groviglio di linee che si incrociano — la cosiddetta “merge spaghetti”. Il rebase è un modo per evitarlo.

Storia con molti merge
* Merge feature/z
|\
| * commit z2
* | Merge feature/y
|\ \
| * | commit y1
* | | Merge fix/a
|\| |
| * | fix a
|/ /
* | commit x
|/
* base
Difficile da leggere
Storia con rebase
* commit z2
* commit z1
* commit y1
* fix a
* commit x
* base





Lineare — facile da leggere

1.2 — Cos’è il rebase: il concetto

Il nome dice tutto: re-base = cambiare la base. Quando fai git rebase main dal tuo branch, stai dicendo a Git: “prendi i miei commit e riapplicali come se li avessi fatti partendo dall’ultimo commit di main, non da quello vecchio”.

“`
Prima del rebase: feature parte da un commit vecchio di main
C1
→
C2
base originale
↗
C3
→
C4
main
↘
F1
→
F2
feature
Feature è partita da C2, ma main è avanzato a C4. I branch hanno diverguto.
Dopo git rebase main (eseguito dal branch feature):
C1
→
C2
→
C3
→
C4
main
→
F1′
→
F2′
feature
F1′ e F2′ sono le stesse modifiche di F1 e F2, ma “riappoggiati” sopra C4. La storia è lineare!
“`

Nota l’apostrofo: F1' e F2'. Non sono gli stessi commit di prima — sono nuovi commit con le stesse modifiche ma hash diversi, perché hanno un genitore diverso (C4 invece di C2). Git ha “ricreato” i tuoi commit su una nuova base.

📌 Il punto chiave: il rebase riscrive la storia. I commit originali (F1, F2) vengono sostituiti da nuovi commit (F1′, F2′) con hash diversi. Il contenuto è lo stesso, ma per Git sono commit completamente nuovi. Questa è sia la forza (storia pulita) sia il pericolo (se qualcun altro aveva i vecchi commit, nasce il caos).

1.3 — Merge vs Rebase: confronto completo

git merge git rebase
Cosa fa Crea un merge commit che unisce i due branch Ricrea i commit del branch sopra la nuova base
Storia risultante Ramificata (con nodo di merge) Lineare
Riscrive la storia? No — aggiunge solo Sì — ricrea i commit
Sicuro su branch pushati? Sì — sempre No — mai
Conflitti Si risolvono una volta sola Possono presentarsi per ogni commit riapplicato
Quando usarlo Sempre sicuro. Branch pubblici. Pull request. Branch locali personali. Pulizia prima di fare merge/PR.

1.4 — Come eseguire un rebase base

Il workflow è: ti sposti sul branch che vuoi riappoggiare, poi esegui git rebase indicando la nuova base.

# 1. Spostati sul branch da riappoggiare
$ git switch feature/nav

# 2. Riappoggia sopra main
$ git rebase main
Successfully rebased and updated refs/heads/feature/nav.

# 3. Ora feature/nav è “sopra” main — puoi fare merge fast-forward
$ git switch main
$ git merge feature/nav
Fast-forward
Nota la direzione: merge vs rebase
Merge Ti sposti su main → git merge feature (le modifiche entrano in main)
Rebase Ti sposti su feature → git rebase main (feature viene riappoggiata sopra main)
La direzione è opposta: con merge vai sul branch “ricevente”. Con rebase vai sul branch che vuoi spostare.

1.5 — Conflitti durante il rebase

Anche durante un rebase possono nascere conflitti — con una differenza importante: il merge li risolve tutti in una volta, il rebase potrebbe chiederti di risolvere un conflitto per ogni commit che sta riapplicando.

$ git rebase main
CONFLICT (content): Merge conflict in style.css
error: could not apply f1a2b3c… Aggiunto stile header
Resolve all conflicts manually, mark them as resolved with
“git add <file>” and run “git rebase –continue”.
Or, to abort: “git rebase –abort”
Comando Quando Cosa fa
git rebase –continue Dopo aver risolto il conflitto e fatto git add Continua il rebase applicando il prossimo commit
git rebase –abort Quando vuoi annullare tutto Annulla il rebase e torna allo stato precedente
git rebase –skip Quando vuoi saltare un commit problematico Ignora il commit corrente e passa al successivo
# Flusso completo: rebase con conflitto
$ git rebase main ← conflitto su F1!
# …risolvi il conflitto nel file…
$ git add style.css
$ git rebase –continue ← Git applica F1′, poi prova F2
Successfully rebased and updated refs/heads/feature/nav.

1.6 — La regola d’oro del rebase

Non fare mai rebase su commit
che sono già stati pushati e condivisi.
Se qualcun altro ha già scaricato i tuoi commit (F1, F2), e tu fai rebase creando nuovi commit (F1′, F2′), le due storie divergono. Il tuo collaboratore ha F1 e F2, tu hai F1′ e F2′ — stessi contenuti, hash diversi. Quando provate a sincronizzarvi, Git vede commit duplicati e crea un pasticcio irrecuperabile.
✅ Rebase sicuro
Il tuo branch locale che non hai ancora pushato. Nessun altro lo conosce. Puoi riscrivere la storia quanto vuoi.
Esempio: git rebase main sul tuo branch di feature locale prima di fare push per la prima volta.
❌ Rebase pericoloso
Il branch main o qualsiasi branch che altri hanno già scaricato. Riscrivere la storia condivisa crea conflitti irrisolvibili per tutti.
Esempio: git rebase su main dopo che i compagni hanno già fatto pull. Mai.

1.7 — Rebase interattivo: il coltellino svizzero

Il rebase interattivo (git rebase -i) ti permette di modificare la storia recente del tuo branch: riordinare commit, combinarne più di uno, modificare messaggi, cancellare commit. È lo strumento perfetto per “pulire” i tuoi commit prima di condividerli.

# Riscrivi gli ultimi 3 commit in modo interattivo
$ git rebase -i HEAD~3

# Git apre l’editor con qualcosa come:
pick a1b2c3d Aggiunto header
pick d4e5f6a Fixato typo nel header
pick b7c8d9e Aggiunti stili header

Puoi cambiare la parola pick all’inizio di ogni riga con una di queste azioni:

Azione Abbreviazione Cosa fa
pick p Tieni il commit così com’è (default)
reword r Tieni il commit ma cambia il messaggio
squash s Unisci questo commit con il precedente (combina i messaggi)
fixup f Unisci con il precedente ma butta via il messaggio di questo commit
edit e Fermati su questo commit — puoi modificare i file prima di continuare
drop d Cancella questo commit completamente
Esempio: combinare 3 commit in uno (squash)
Nell’editor, cambi così:
pick a1b2c3d Aggiunto header
fixup d4e5f6a Fixato typo nel header
fixup b7c8d9e Aggiunti stili header
Risultato: un solo commit pulito
* a9b8c7d Aggiunto header

3 commit → 1 commit
Stesse modifiche, storia più pulita.

📌 Quando usare squash/fixup: il caso classico è quando hai fatto commit come “WIP”, “fix typo”, “oops dimenticato un file”, “ora funziona”. Prima di fare push o creare una Pull Request, usi il rebase interattivo per combinarli in un unico commit pulito con un messaggio significativo. I tuoi compagni non hanno bisogno di vedere la tua cronologia dei tentativi — solo il risultato finale.

1.8 — Quando usare rebase nella pratica

Situazione Usa Perché
Aggiornare il tuo branch locale con le ultime modifiche di main rebase Mantieni la storia lineare, eviti merge commit inutili
Pulire i commit prima di una Pull Request rebase -i Combini commit WIP, correggi messaggi, storia professionale
Integrare un branch in main merge Il merge commit documenta l’integrazione. Più sicuro in team.
Riscrivere la storia di main o di un branch condiviso MAI rebase Creerebbe problemi a tutti i collaboratori

💻 2. Esempi guidati

Esempio A — Rebase base: riappoggiare un branch su main

# === SETUP ===
$ mkdir lab-rebase && cd lab-rebase && git init
$ echo “v1” > file.txt && git add . && git commit -m “C1: base”
$ echo “v2” >> file.txt && git add . && git commit -m “C2: aggiunto v2”

# Crea branch e fai 2 commit
$ git switch -c feature/nav
$ echo “nav” > nav.html && git add . && git commit -m “F1: aggiunta nav”
$ echo “nav style” > nav.css && git add . && git commit -m “F2: stili nav”

# Intanto main avanza
$ git switch main
$ echo “v3” >> file.txt && git add . && git commit -m “C3: aggiunto v3”

# Vediamo la divergenza
$ git log –oneline –graph –all
* b7c8d9e (HEAD -> main) C3: aggiunto v3
| * d4e5f6a (feature/nav) F2: stili nav
| * a1b2c3d F1: aggiunta nav
|/
* e4f5a6b C2: aggiunto v2
* c8d3f4a C1: base

# === REBASE ===
$ git switch feature/nav
$ git rebase main
Successfully rebased and updated refs/heads/feature/nav.

# Ora la storia è lineare!
$ git log –oneline –graph –all
* f9a8b7c (HEAD -> feature/nav) F2: stili nav
* e1d2c3b F1: aggiunta nav
* b7c8d9e (main) C3: aggiunto v3
* e4f5a6b C2: aggiunto v2
* c8d3f4a C1: base
Gli hash di F1 e F2 sono cambiati (ora sono F1′ e F2′): stesse modifiche, base diversa. La storia è completamente lineare. Ora il merge sarà fast-forward.
# Merge finale: fast-forward garantito
$ git switch main
$ git merge feature/nav
Fast-forward
$ git branch -d feature/nav

Esempio B — Rebase interattivo: pulire i commit

# Creiamo un branch con commit “sporchi”
$ git switch -c feature/form
$ echo “<form>” > form.html && git add . && git commit -m “WIP form”
$ echo “<input>” >> form.html && git add . && git commit -m “aggiunto input”
$ echo “</form>” >> form.html && git add . && git commit -m “fix: chiuso tag form”
$ echo “form { padding: 1rem; }” > form.css && git add . && git commit -m “stili”

$ git log –oneline
d1e2f3a stili
b4c5d6e fix: chiuso tag form
a7b8c9d aggiunto input
e1f2a3b WIP form
← 4 commit disordinati, nessuno capirebbe cosa è successo

# Puliamo con rebase interattivo: combiniamo i primi 3 in uno
$ git rebase -i HEAD~4

# Nell’editor cambiamo:
pick e1f2a3b WIP form
fixup a7b8c9d aggiunto input
fixup b4c5d6e fix: chiuso tag form
reword d1e2f3a stili

# Salva e chiudi. Git chiede il nuovo messaggio per “reword”:
# Scriviamo “Aggiunti stili per il form” → salva e chiudi

$ git log –oneline
f4e3d2c Aggiunti stili per il form
a9b8c7d WIP form
4 commit → 2 commit puliti. Il “WIP form” contiene ora tutto (form + input + chiusura tag). Poi gli stili come commit separato con messaggio corretto.

🏋️ 3. Esercizi

🟢 Esercizio 1 — Primo rebase (Facile)

a) Segui l’Esempio A: crea un repo con divergenza tra main e un branch feature.
b) Prima del rebase, copia gli hash dei commit feature con git log --oneline.
c) Fai il rebase: git rebase main.
d) Confronta i nuovi hash con quelli copiati. Sono gli stessi? (No — sono cambiati!)
e) La storia è lineare? Verifica con git log --oneline --graph --all.
f) Torna su main e fai merge. È fast-forward?

🔵 Esercizio 2 — Rebase interattivo: squash (Medio)

a) Crea un branch con 5 commit “sporchi”: “inizio”, “WIP”, “altro WIP”, “fix typo”, “quasi finito”.
b) Usa git rebase -i HEAD~5 per combinarli tutti in un unico commit con il messaggio “Implementata feature X completa”.
c) Verifica con git log --oneline: un solo commit?
d) Verifica con git diff HEAD~1 che tutte le modifiche siano presenti.

🟠 Esercizio 3 — Rebase con conflitto (Medio)

a) Crea un repo dove main e feature modificano la stessa riga di un file (come nella Lezione 10).
b) Invece di fare merge, fai git rebase main dal branch feature.
c) Osserva il messaggio di conflitto. È simile a quello del merge?
d) Risolvi il conflitto (modifica il file, rimuovi i marker).
e) Fai git add file e poi git rebase --continue (non git commit!).
f) Verifica la storia: è lineare? C’è un merge commit? (No — è il vantaggio del rebase.)

🟣 Esercizio 4 — Merge vs Rebase: confronto visuale (Difficile)

Questo esercizio ti fa confrontare direttamente i due approcci sullo stesso scenario:

a) Crea un repo confronto-merge: commit iniziale, poi un branch feature/a con 2 commit, e main che avanza di 1 commit. Fai merge. Salva l’output di git log --oneline --graph --all.

b) Crea un repo confronto-rebase: stessa identica struttura. Ma questa volta fai rebase + fast-forward merge. Salva l’output di git log --oneline --graph --all.

c) Confronta i due grafi. Quale è più leggibile? Quale conserva più informazioni su “come” è avvenuto lo sviluppo?

d) Scrivi un paragrafo che spiega quando preferiresti l’uno e quando l’altro.

❓ 4. Quiz di verifica

1. Cosa fa concettualmente git rebase main quando sei sul branch feature?

Mostra risposta

Prende tutti i commit che sono su feature ma non su main, li “stacca” temporaneamente, sposta la base di feature al commit più recente di main (C4), poi riapplica i commit uno alla volta sopra la nuova base. Il risultato sono nuovi commit (F1′, F2′) con le stesse modifiche ma hash diversi — perché hanno un genitore diverso. La storia diventa lineare: sembra che tu abbia iniziato a lavorare dalla versione più recente di main, anche se in realtà avevi iniziato prima.


2. Perché gli hash dei commit cambiano dopo un rebase?

Mostra risposta

Perché l’hash di un commit in Git dipende dal contenuto, dall’autore, dalla data e dal genitore. Anche se il contenuto (le modifiche) è identico, il genitore è cambiato (da C2 a C4). Genitore diverso = hash diverso. Per Git, sono commit completamente nuovi — F1′ non è F1, anche se contiene le stesse modifiche. Questa è la ragione per cui il rebase “riscrive la storia”: i vecchi commit vengono sostituiti da nuovi commit equivalenti.


3. Qual è la regola d’oro del rebase?

Mostra risposta

Non fare mai rebase su commit che sono già stati pushati e condivisi con altri. Se qualcun altro ha già scaricato i tuoi commit originali (F1, F2), e tu li sostituisci con nuovi commit (F1′, F2′) tramite rebase, le due storie divergono. Il collaboratore ha i vecchi commit, tu hai i nuovi — stessi contenuti, hash diversi. Quando provate a sincronizzarvi, Git vede commit “duplicati” e crea un pasticcio. Usa il rebase solo su branch locali che non hai ancora condiviso.


4. Durante un rebase Git trova un conflitto. Cosa fai per completare il rebase?

Mostra risposta

Risolvi il conflitto nel file (rimuovi i marker, scegli la versione corretta), fai git add file-risolto, poi esegui git rebase --continue. Attenzione: non fare git commit — il rebase gestisce i commit da solo. Se vuoi annullare tutto: git rebase --abort. Se il conflitto si presenta su più commit, dovrai risolverlo per ogni commit problematico, facendo --continue dopo ciascuno.


5. Cosa fa fixup nel rebase interattivo?

Mostra risposta

fixup combina il commit con quello precedente nella lista, scartando il messaggio del commit corrente. Il risultato è un unico commit con le modifiche di entrambi e il messaggio del primo. È come squash, ma senza l’opportunità di modificare il messaggio combinato. Perfetto per commit di tipo “fix typo”, “oops”, “WIP” che non aggiungono informazione utile alla storia.


6. Dopo un rebase, il merge verso main sarà sempre fast-forward?

Mostra risposta

Sì, a condizione che nessuno abbia fatto nuovi commit su main tra il rebase e il merge. Dopo il rebase, il tuo branch “parte” dall’ultimo commit di main — main è un antenato diretto del tuo branch, che è la condizione esatta per il fast-forward. Se qualcuno fa un nuovo commit su main nel frattempo, dovrai rifare il rebase. In pratica: rebase → merge immediatamente → fast-forward garantito.


📚 Riepilogo della lezione

Cosa hai imparato oggi:
  • Rebase = riappoggiare i commit del tuo branch sopra una nuova base (l’ultimo commit di main)
  • Risultato: storia lineare, nessun merge commit, come se avessi iniziato dalla versione più recente
  • I commit vengono ricreati con hash diversi — stesse modifiche, nuovi commit
  • Direzione: git switch feature && git rebase main (opposto al merge)
  • Conflitti: si risolvono con git add + git rebase --continue (non git commit!)
  • Annullare: git rebase --abort
  • git rebase -i HEAD~n — rebase interattivo: pick, reword, squash, fixup, drop
  • La regola d’oro: mai rebase su commit già pushati e condivisi
  • Workflow tipico: rebase per pulire il branch locale → merge (fast-forward) in main

Prossima lezione →

Lezione 12 — Strategie di branching
Git Flow, GitHub Flow, Trunk-based development.
Pro e contro di ciascuna. Quale scegliere per un progetto scolastico.

Corso Git & GitHub · Per studenti e insegnanti · Lezione 11 di 45