La domanda “come faccio a disfare questo?” ricorre continuamente, e la risposta dipende da una variabile critica: qualcun altro ha già scaricato il commit che vuoi annullare? Se sì, riscrivere la storia causa problemi ai tuoi colleghi. Se no, hai più opzioni. La mappa qui sotto ti porterà al comando giusto rapidamente, ma capire perché ogni comando funziona nel modo in cui funziona è ciò che fa la differenza in un incidente alle 2 di notte.
git restore: Scartare Modifiche nel Working Tree#
git restore è il sostituto moderno del vecchio pattern git checkout -- file. Scarta le modifiche nel working tree o nell’area di staging senza toccare i commit.
# Scarta le modifiche non staged in un file (solo working tree)
git restore path/to/file.go
# Rimuovi un file dallo staging (spostalo dall'area di staging al working tree)
git restore --staged path/to/file.go
# Rimuovi dallo staging E scarta le modifiche nel working tree
git restore --staged --worktree path/to/file.gogit restore sul working tree è distruttivo e irreversibile. Non esiste un reflog per le modifiche non staged. Usalo solo quando sei sicuro di non aver bisogno di quelle modifiche.
git reset: Spostare HEAD#
git reset sposta il puntatore del branch (HEAD) a un commit diverso. Cosa succede alle modifiche dei commit “saltati” dipende dal flag.
| Flag | HEAD si sposta | Area di staging | Working tree |
|---|---|---|---|
--soft | Sì | Invariata (modifiche staged) | Invariato |
--mixed (default) | Sì | Svuotata (modifiche non staged) | Invariato |
--hard | Sì | Svuotata | Svuotato (modifiche perse) |
# Annulla l'ultimo commit, mantieni le modifiche staged
git reset --soft HEAD~1
# Annulla l'ultimo commit, mantieni le modifiche nel working tree (non staged)
git reset --mixed HEAD~1
# Annulla l'ultimo commit, scarta tutte le modifiche
git reset --hard HEAD~1HEAD~1 significa “un commit prima di HEAD”. Puoi usare anche un SHA specifico: git reset --soft abc1234.
Non usare mai git reset su commit che sono già stati pushati su un branch condiviso. Riscrive la storia. Se un collega ha già scaricato quei commit, il suo repo divergerà dal tuo e il merge risultante sarà confuso e problematico. Usa invece git revert (vedi sotto).
git revert: Creare un Commit di Annullamento#
git revert non sposta HEAD all’indietro. Crea invece un nuovo commit le cui modifiche sono l’inverso del commit specificato. Tutta la storia viene preservata.
# Crea un commit di revert per il commit più recente
git revert HEAD
# Revert di un commit specifico tramite SHA
git revert abc1234
# Revert senza fare il commit immediatamente (fai prima lo stage delle modifiche, poi committa manualmente)
git revert --no-commit abc1234Questo è l’approccio corretto per annullare modifiche su main, release o qualsiasi altro branch che altri ingegneri hanno già scaricato. La storia mostra esattamente cosa è successo: il commit originale seguito dal commit di revert.
Quando si fa il revert di un merge commit, è necessario specificare a quale parent tornare con il flag -m: git revert -m 1 <sha-del-merge-commit>. Il parent 1 è il branch in cui si è fatto il merge; il parent 2 è il branch mergiato.
Diagramma Decisionale#
flowchart TD
A[Devo annullare qualcosa] --> B{È già stato pushato\nsu un branch condiviso?}
B -->|Sì| C[git revert\nCrea un nuovo commit di annullamento\nLa storia viene preservata]
B -->|No| D{Dove si trovano\nle modifiche?}
D -->|Working tree non staged| E[git restore file\nScarta le modifiche al file]
D -->|Staged ma non committato| F[git restore --staged file\nRimuovi dallo staging]
D -->|Committato localmente| G{Cosa voglio fare\ncon le modifiche?}
G -->|Tenerle staged| H[git reset --soft HEAD~1]
G -->|Tenerle non staged| I[git reset --mixed HEAD~1]
G -->|Scartarle del tutto| J[git reset --hard HEAD~1]
git reflog: La Rete di Sicurezza#
Il reflog è un log locale di tutti i posti dove HEAD ha puntato negli ultimi 30 giorni. Anche se esegui git reset --hard e “perdi” commit, sono ancora nell’object store e raggiungibili tramite il reflog.
# Mostra il reflog
git reflog
# L'output appare così:
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: add login feature
# ...
# Recupera il commit "perso"
git checkout def5678
# Oppure esegui un reset a quel punto
git reset --hard def5678Il reflog è puramente locale. Non viene pushato al remote. Se cloni un repo da zero e perdi commit con --hard, sono andati. Ma nel normale workflow quotidiano sulla tua macchina, il reflog è una rete di sicurezza quasi affidabile.
Le voci del reflog scadono dopo 90 giorni per default (30 giorni per gli oggetti non raggiungibili). Puoi regolare questo con gc.reflogExpire e gc.reflogExpireUnreachable nella configurazione git, ma i default sono generosi abbastanza per la maggior parte degli scenari di recupero.
Se vuoi approfondire uno qualsiasi di questi argomenti, offro sessioni di coaching 1:1 per ingegneri che lavorano su integrazione AI, architettura cloud e platform engineering. Prenota una sessione (50 EUR / 60 min) o scrivimi a manuel.fedele+website@gmail.com.