TL;DR: git revert aggiunge un nuovo commit che annulla uno vecchio — la cronologia resta intatta, sicuro sui branch che altri hanno già scaricato. git reset sposta all’indietro il puntatore del branch — la cronologia viene riscritta, sicuro solo sui commit che non hai ancora fatto in push. Usa revert sui branch condivisi, reset per la pulizia locale. reset --hard cancella anche le modifiche non committate nel working tree; quello è il comando che si mangia lavoro vero.
Qual è la differenza tra git revert e git reset?
Entrambi fanno sembrare che un commit del passato non sia mai esistito. Il disaccordo è sul come:
git revert <sha>calcola la patch inversa di un commit e la committa. La tua cronologia cresce di un commit che dice «annulla quello». Il commit cattivo resta nel log, seguito dalla sua cancellazione. Gli SHA di tutto il resto restano intatti.git reset <sha>sposta il puntatore del branch corrente su<sha>. I commit successivi si staccano — restano nell’object database fino alla garbage collection, ma non sono più raggiungibili da nessun branch ogit log.
Uno non riscrive nulla, l’altro finge che non sia successo. Questa singola proprietà decide quale dei due serve in ogni situazione:
# Demo: un repo usa-e-getta eseguibile
git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"
# Revert: la cronologia tiene entrambi i commit, più un terzo che annulla "two"
git revert --no-edit HEAD
git log --oneline # 3 commit: revert, two, one
# Reset: il puntatore del branch torna indietro, "two" sparisce dal log
git reset --hard HEAD~1
git log --oneline # 1 commit: one Lancia entrambe le demo e guarda git log — l’asimmetria è tutta la risposta.
Cosa fanno davvero git reset —soft, —mixed e —hard?
Le tre modalità controllano dove sopravvivono le tue modifiche dopo lo spostamento del puntatore:
| Modalità | Puntatore di branch | Area di staging | Working tree | Le tue modifiche |
|---|---|---|---|---|
--soft | torna indietro | resta in staging | intatto | conservate del tutto |
--mixed (default) | torna indietro | fuori staging | intatto | conservate, non in staging |
--hard | torna indietro | azzerata | azzerato | eliminate |
Letture concrete di quella tabella:
git reset --soft HEAD~1— «ho committato troppo presto». Tutto torna in staging, pronto per il recommit, magari insieme ad altro lavoro.git reset HEAD~1(mixed) — «ho messo in staging i file sbagliati». Le modifiche sopravvivono nel working tree, nulla resta in staging. Abbinalo agit remove untracked filesquando vuoi far sparire anche i file vaganti.git reset --hard HEAD~1— «questo commit e le mie modifiche non committate sono spazzatura». Git non te lo chiede due volte; per la parte di working tree non esiste undo, a meno che tu non l’abbia prima messa in stash o committata.
revert non ha modalità perché non distrugge mai nulla — si limita ad aggiungere un commit compensativo.
Quando usare git revert invece di git reset?
Una sola domanda: qualcun altro ha già fatto pull di questo commit? Se sì, reset è fuori discussione.
Resettare un branch su cui altri hanno costruito riscrive la cronologia condivisa. Il loro prossimo pull incontra branch divergenti, e la «sistemazione» di solito è un force-push che trasforma il tuo errore nel problema di tutti. Revert aggiunge un commit normale, quindi un semplice git pull mergia pulito per tutti — ecco perché revert è la risposta di default su main e sulle pull request GitHub, dove il bottone “Revert” esiste proprio perché riscrivere la cronologia di una PR mergiata non è un’opzione.
Reset è per la finestra prima della condivisione: il commit fatto trenta secondi fa, lo staging sbagliato, il branch di esperimento locale. Se l’unica copia del lavoro sta sulla tua macchina, sei libero di riarrangiare la cronologia — è proprio questo il senso della finestra pre-push.
C’è un caso intermedio: hai fatto push sul tuo feature branch e nessun altro ci costruisce sopra. Un force-push dopo il reset è socialmente accettabile lì, ma revert costa comunque meno coordinamento. Riserva i force-push ai branch che possiedi da solo.
git revert è più sicuro di git reset sui branch condivisi?
Sì, strutturalmente — non solo per convenzione:
- Revert produce un commit ordinario: la CI ci gira sopra, il diff è revisionabile, e
git logspiega al te-del-futuro perché la modifica è sparita. - Reset scarta in silenzio lo stato intermedio. Nessuno che revisiona dopo può vedere cosa è stato annullato e quando, perché il log mostra una linea retta che non l’ha mai contenuto.
Due spigoli da conoscere:
- Annullare un merge commit richiede
git revert -m 1 <sha>(tieni la linea del primo parent). Senza-m, git rifiuta e ti lascia leggere l’errore. - Revert non è una macchina del tempo: annulla il diff di un solo commit. Se commit successivi hanno toccato le stesse righe, potresti dover risolvere conflitti — è git che ti dice che l’annullamento è annodato, un’informazione utile, non un malfunzionamento.
Per annullare nello specifico il tuo ultimo commit — tenendo o buttando le sue modifiche — le opzioni sono tutte in git undo last commit: tenere le modifiche.
E git restore, dove sta?
git restore (e git switch, il suo gemello) sono arrivati in git 2.23 per prendere i lavori che reset svolgeva male per sovraccarico di nomi:
| Compito | Modo vecchio | Modo chiaro |
|---|---|---|
| Scartare le modifiche al working tree di un file | git checkout -- file / git reset --hard | git restore file |
| Togliere un file dallo staging | git reset HEAD file | git restore --staged file |
| Spostare il branch su un altro commit | git reset <sha> | git reset <sha> (nessun sostituto — questo resta) |
Quindi la divisione moderna è: restore sistema i file, reset sposta i branch, revert annulla i commit pubblicati. L’autocomplete mostra che la gente cerca “git revert vs reset vs restore” insieme, e per una buona ragione — sono un unico modello mentale spezzato su tre comandi. Le vecchie forme di git checkout funzionano ancora ovunque; le nuove ti evitano solo di mandare un branch intero nel distruttore quando intendevi togliere un file dallo staging.
Se l’obiettivo è copiare in avanti un commit buono anziché cancellarne uno cattivo, quello è il lavoro di cherry-pick — vedi git cherry-pick: commit multipli, branch e conflitti.
Riepilogo rapido delle decisioni
- Commit pubblico (fatto il push, altri hanno fatto pull) →
git revert <sha>. - Commit solo locale, vuoi rifarlo →
git reset --softo--mixede ricommit. - Solo locale, vuoi eliminarlo insieme al disordine non committato →
git reset --hard, dopo un’occhiata onesta a cos’altro contiene il working tree. - Un file è stato malmenato →
git restore <file>e lascia stare il branch.
L’unica voce davvero pericolosa della lista è --hard — tutto il resto si può riportare indietro con git reflog. Le perdite permanenti in git sono poche e in gran parte richiedono che tu lo scriva esplicitamente; l’abitudine che vale la pena costruire è la mezza pausa prima di --hard, non l’evitare del tutto gli attrezzi affilati di git.
— mrsaynothing
Get the next one by email
One email per post. No spam, no algorithms.
what is this?Il servizio systemd non parte? Come risolverlo
Ti piacciono gli articoli? È così che costruisco per lavoro. assumimi