modulo 04 / lezione 17

Riscrivere la storia: rebase, amend, cherry-pick

git rebase: spostare commit su un'altra base

Capire cosa fa il rebase davvero e perché può produrre conflitti uno per commit.

60 minGit e controllo versione

git rebase: spostare commit su un’altra base

Rebase significa prendere una serie di commit e riprodurli sopra un’altra base. Non integra due storie come un merge: crea nuovi commit equivalenti su un punto diverso del grafo.

È uno strumento potente per mantenere una storia lineare, ma va usato con disciplina perché riscrive hash.

Perché questa lezione conta

Molti sviluppatori usano rebase come incantesimo prima della PR. Funziona finché non compare un conflitto, un force push o un collega che aveva basato lavoro sul branch vecchio.

Capire il replay dei commit permette di usare rebase senza paura e senza danneggiare branch condivisi.

Obiettivo operativo

Alla fine della lezione devi saper fare rebase di un branch personale su main, risolvere conflitti per commit, abortire se necessario e capire quando preferire merge.

Prima e dopo

Prima:

      C -- D  feat
     /
A -- B -- E -- F  main

feat è partito da B, ma main è andato avanti.

git switch feat
git fetch origin
git rebase origin/main

Dopo:

A -- B -- E -- F -- C' -- D'  feat

C' e D' sono nuovi commit. Hanno contenuto simile a C e D, ma hash diverso perché hanno parent diverso.

Rebase non è merge

Merge conserva la divergenza:

      C -- D
     /      \
A -- B -- E -- M

Rebase riscrive la linea:

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

La storia diventa più lineare, ma perdi la forma originale della divergenza.

Conflitti durante rebase

Se un commit non si applica:

git status

Risolvi i file, poi:

git add file-risolto
git rebase --continue

Per saltare un commit:

git rebase --skip

Usalo con molta cautela: stai decidendo che quel commit non serve più.

Per annullare tutto:

git rebase --abort

Torna allo stato prima del rebase.

Quando usare rebase

Usalo per:

  1. aggiornare branch personale su main;
  2. preparare una PR lineare;
  3. evitare merge commit locali inutili;
  4. ripulire storia prima della condivisione.

Evitalo su:

  1. branch condivisi da più persone;
  2. branch di release;
  3. storia già usata come base da altri;
  4. situazioni in cui preservare il merge è informazione importante.

Rebase e push

Dopo rebase, il push normale può fallire:

git push

Perché il remoto ha la storia vecchia. Se il branch è tuo e il force push è previsto:

git push --force-with-lease

Mai usare --force per abitudine. --force-with-lease protegge dal sovrascrivere lavoro remoto che non hai visto.

Laboratorio guidato

In un repo demo:

  1. crea main con commit A e B;
  2. crea branch feat con commit C e D;
  3. torna a main e aggiungi E;
  4. fai rebase di feat su main;
  5. osserva hash prima/dopo;
  6. crea un conflitto e risolvilo con --continue;
  7. ripeti e usa --abort.

Output atteso

Un diagramma prima/dopo e una regola:

Rebase branch personale per linearità.
Merge branch condiviso per preservare storia.
Force push solo con --force-with-lease e solo se previsto.

Errori comuni

Pensare che rebase sposti gli stessi commit. Crea commit nuovi.

Rebasare branch condivisi. Obbliga altri a risincronizzare manualmente.

Usare --skip senza capire. Può eliminare modifiche necessarie.

Risoluzione conflitti frettolosa. Durante rebase il conflitto può ripresentarsi per commit successivi.

Checklist di verifica

  • so disegnare rebase prima/dopo;
  • so usare --continue, --abort, --skip;
  • so perché cambiano gli hash;
  • so quando serve --force-with-lease;
  • so quando preferire merge.

Esercizi

Base · facile

Riporta il tuo branch sopra l’ultima versione di main con un rebase.

Soluzione
git switch feature
git rebase main   # riapplica i commit di feature sopra l'attuale main

Il rebase “ricuce” i tuoi commit sopra la punta aggiornata di main, come se li avessi appena scritti. Risultato: una storia lineare, senza commit di merge.

Intermedio · medio

Spiega la differenza pratica tra git merge main e git rebase main su un branch feature, in termini di storia risultante.

Soluzione
merge main → crea un commit di merge; la storia mostra il punto di unione (non lineare)
rebase main → riscrive i commit di feature sopra main; storia lineare, niente commit di merge

merge preserva la storia reale (quando i rami si sono incontrati) ma la rende più intricata. rebase produce una sequenza lineare, più facile da leggere, al prezzo di riscrivere i commit (nuovi hash). Scelta tipica: rebase per pulire un branch personale prima della PR, merge per integrare preservando la traccia storica.

Sfida · difficile

Un rebase entra in conflitto a metà: mostra come risolverlo, proseguire o annullare tutto. Spiega perché il rebase può chiedere di risolvere lo stesso tipo di conflitto più volte.

Soluzione
# durante il rebase, su un conflitto:
# 1. risolvi i file, poi:
git add file-risolto.txt
git rebase --continue
# per saltare un commit problematico:
git rebase --skip
# per annullare tutto e tornare allo stato iniziale:
git rebase --abort

Il rebase riapplica i commit uno alla volta: se più commit toccano la stessa zona di codice, lo stesso conflitto può ripresentarsi a ogni commit riapplicato. Risolvi, git add, git rebase --continue per ciascuno; --abort riporta tutto al punto di partenza in sicurezza. (git rerere può ricordare risoluzioni ripetute, ma va abilitato.)

Collegamenti

  • Modulo: Riscrivere la storia: rebase, amend, cherry-pick — lezione 17 del corso.
  • Il rebase interattivo della prossima lezione usa lo stesso principio di riscrittura.