Introduzione
Scopri come Git riscrive la cronologia con git commit --amend, git reflog e git rebase — cosa fa ogni comando, quando usarlo e come operare in sicurezza.
In Git, la cronologia è la sequenza di commit che registra come il tuo progetto è cambiato nel tempo. Ogni commit è identificato da un hash univoco e punta al commit (o ai commit) che lo ha preceduto. La maggior parte delle volte ti limiti ad aggiungere elementi a quella catena. Ma a volte hai bisogno di modificare ciò che è già presente — correggere un errore di battitura in un messaggio di commit, unire diversi commit disordinati in un unico commit pulito, o recuperare un lavoro che pensavi di aver perso.
Ecco a cosa servono gli strumenti di riscrittura della cronologia di Git. I tre che utilizzerai più spesso sono:
git commit --amend— sostituisce il commit più recente.git reflog— mostra ogni posizione a cui ha puntatoHEAD, così puoi recuperare i commit "persi".git rebase— riproduce una serie di commit su una nuova base, modificandoli facoltativamente lungo il percorso.
Questa pagina ti offre una panoramica pratica di ciascuno. I capitoli più approfonditi collegati sopra illustrano l'insieme completo di opzioni.
La regola d'oro della riscrittura della cronologia: non riscrivere mai i commit che hai già inviato a un branch condiviso. La riscrittura modifica gli hash dei commit, quindi chiunque abbia scaricato i vecchi commit riceverà conflitti. Riscrivi liberamente sui tuoi branch locali; lascia intatta la cronologia condivisa.
Modificare l'ultimo commit con git commit --amend
È facile dimenticare di mettere in stage un file, lasciare un errore di battitura in un messaggio di commit, o fare il commit troppo presto. Invece di aggiungere un commit "fix" separato, git commit --amend ti permette di sostituire l'ultimo commit con uno corretto.
Per correggere solo il messaggio del commit più recente:
git commit --amend -m "Add login validation"Per aggiungere un file dimenticato all'ultimo commit, mettilo in stage prima, poi esegui l'amend senza cambiare il messaggio:
git add forgotten-file.js
git commit --amend --no-editIl flag --no-edit mantiene il messaggio esistente e lo riutilizza. Dietro le quinte, amend non modifica il vecchio commit sul posto — crea un commit completamente nuovo (con un nuovo hash) e sposta il branch in modo che punti ad esso. Il commit originale rimane indietro, senza riferimenti.
Poiché l'amend produce un nuovo hash, si applica la stessa regola d'oro: esegui l'amend solo su un commit che non hai inviato a un branch condiviso con altri.
Per una guida completa, vedi git commit --amend.
Recuperare i commit persi con git reflog
Il reflog (reference log) registra ogni modifica alla punta dei tuoi branch e a HEAD — ogni commit, checkout, reset, merge e rebase. Questo è il tuo salvagente: anche dopo una riscrittura o un hard reset, i vecchi commit sono ancora nel repository e il reflog ricorda dove si trovavano.
Visualizza il log con:
git reflogUn output tipico si presenta così:
a1b2c3d HEAD@{0}: commit: Add login validation
9f8e7d6 HEAD@{1}: reset: moving to HEAD~1
4c3b2a1 HEAD@{2}: commit: Work in progressOgni voce mostra un hash del commit, un riferimento HEAD@{n} (quanti spostamenti fa) e cosa è accaduto. Se hai accidentalmente rimosso un commit con un reset, trovalo nel reflog e ripristinalo:
git reset --hard HEAD@{2}Il comando git reflog dispone anche di sottocomandi come git reflog show, git reflog expire e git reflog delete. Da notare che le voci del reflog sono locali al tuo repository e scadono nel tempo (90 giorni per impostazione predefinita per le voci raggiungibili), quindi si tratta di uno strumento di recupero, non di un backup permanente. Vedi git reflog e il capitolo correlato git reset per i dettagli.
Eseguire il rebase su una nuova base con git rebase
git rebase prende una serie di commit e li riproduce sopra un commit base diverso. Il suo principale vantaggio è una cronologia lineare e leggibile: invece di un merge commit che collega due branch, il tuo lavoro appare come una linea pulita di commit.
Esistono due modalità:
- La modalità standard applica i commit del tuo branch corrente sopra la punta di un altro branch. È l'alternativa al merge quando si integrano aggiornamenti da
main:
git rebase main- La modalità interattiva ti permette di riordinare, modificare, combinare o eliminare i commit man mano che vengono riprodotti. Aggiungi l'opzione
-ie passa il commit su cui vuoi fare il rebase:
git rebase -i <base-branch>Un obiettivo interattivo comune è "gli ultimi N commit", ad esempio gli ultimi tre:
git rebase -i HEAD~3Per una trattazione più approfondita, vedi git rebase e git rebase interactive.
Modificare i commit durante un rebase interattivo
Quando avvii un rebase interattivo, Git apre un editor che elenca i commit selezionati, ciascuno preceduto da un'azione. Cambi la parola chiave dell'azione per controllare cosa succede:
pick— mantieni il commit così com'è (il default).reword(r) — metti in pausa per riscrivere il messaggio di quel commit.squash(s) — unisci questo commit nel precedente e combina i loro messaggi in un editor.fixup(f) — comesquash, ma scarta il messaggio di questo commit e mantieni solo quello del precedente (nessun prompt dell'editor).edit(e) — metti in pausa in modo da poter modificare il contenuto del commit.drop(d) — rimuovi il commit completamente.
Un tipico elenco di operazioni di rebase si presenta così:
pick a1b2c3d Add login form
squash 9f8e7d6 Fix typo in label
fixup 4c3b2a1 Adjust spacingQui il secondo e il terzo commit vengono incorporati nel primo, lasciando un unico commit ordinato.
Perché i commit riscritti ottengono nuovi ID
Il rebase non sposta i commit originali — crea commit nuovi con le stesse modifiche ma genitori diversi, e quindi hash diversi. I vecchi commit diventano privi di riferimenti (ancora recuperabili tramite il reflog per un certo periodo). Anche i commit contrassegnati con pick ottengono nuovi ID se un commit precedente nella sequenza è stato modificato, perché il loro genitore è cambiato.

Questo è esattamente il motivo per cui la regola d'oro è importante: nuovi hash significano una cronologia divergente per chiunque avesse i vecchi commit.