Come Annullare in Modo Sicuro i Commit Locali e Remoti in Git

Annulla in modo sicuro i commit Git con reset, revert, reflog e force-with-lease senza perdere lavoro o rompere i branch condivisi.

Come Annullare in Modo Sicuro i Commit Locali e Remoti in Git

Annullare un commit Git è facile. Annullare la cosa giusta, senza perdere lavoro o sorprendere tutti gli altri sul branch, è la parte che richiede giudizio.

La prima domanda non è "quale comando eseguo?" È "chi ha visto questo commit?" Se il commit esiste solo sul tuo laptop, di solito puoi riscriverlo con git reset o git commit --amend. Se il commit è già stato inviato a un branch che altre persone usano, preferisci git revert. Questo mantiene intatta la cronologia e crea un nuovo commit che annulla la modifica errata.

Prima di toccare la cronologia, fai un'istantanea di dove ti trovi:

git status
git branch backup-before-undo
git log --oneline --decorate -5

Quel branch temporaneo è un'assicurazione economica. Se resetti troppo indietro o cambi idea, il vecchio commit ha ancora un nome.

Se il commit non è stato inviato

Per un commit locale che non hai inviato, git reset è di solito lo strumento più pulito. Sposta il puntatore del branch all'indietro. La modalità che scegli decide cosa succede ai file.

Usa --soft quando il messaggio del commit era sbagliato o hai dimenticato un piccolo file:

git reset --soft HEAD~1

Il tuo ultimo commit scompare, ma le modifiche rimangono in staging. Puoi aggiungere il file mancante e fare di nuovo il commit:

git add missing-file.yml
git commit -m "Aggiorna configurazione di deployment"

Usa il reset misto predefinito quando vuoi che le modifiche tornino nel tuo albero di lavoro, non in staging:

git reset HEAD~1

Questo è il comando quotidiano "annulla questo commit, ma mantieni le mie modifiche". È utile quando un commit dovrebbe diventare due commit più piccoli, o quando hai committato un'istruzione di debug insieme al codice reale.

Usa --hard solo quando vuoi davvero buttare via le modifiche locali:

git reset --hard HEAD~1

Questo resetta i file tracciati al commit precedente. Non chiede gentilmente se intendevi farlo. Se hai lavoro non committato in file tracciati, può scomparire dall'albero di lavoro. Controlla prima git status e fai stash o branch di tutto ciò che potresti volere indietro.

Per una piccola correzione all'ultimo commit locale, git commit --amend è spesso meglio di reset:

git add corrected-file.js
git commit --amend

Questo sostituisce l'ultimo commit con uno nuovo. Vale la stessa regola: modifica liberamente prima di inviare; fai attenzione dopo aver inviato.

Se il commit è già stato inviato

Su un branch condiviso, usa git revert a meno che tu non abbia una forte ragione per riscrivere la cronologia. Revert crea un nuovo commit che applica la patch opposta.

git revert a1b2c3d

Questo comando apre il tuo editor con un messaggio generato. Salvalo e Git crea un nuovo commit. Il commit originale rimane nella cronologia, che è esattamente ciò che vuoi su main, master, develop, branch di rilascio e qualsiasi branch che i compagni di squadra potrebbero aver scaricato.

Se devi annullare diversi commit consecutivi, puoi farlo in un unico batch in staging:

git revert --no-commit HEAD~3..HEAD
git status
git commit -m "Annulla modifiche recenti di deployment"

Leggi attentamente l'intervallo. HEAD~3..HEAD significa gli ultimi tre commit, escluso HEAD~3 stesso. In caso di dubbio, elenca prima i commit:

git log --oneline HEAD~3..HEAD

I commit di merge necessitano di una decisione extra. Un merge ha più di un genitore, quindi Git deve sapere quale lato deve essere trattato come linea principale:

git revert -m 1 <sha-commit-merge>

La maggior parte dei team usa -m 1 quando annulla un merge di un branch di funzionalità dal branch di destinazione, ma non eseguire questo comando alla cieca. Guarda il commit di merge con git show --summary <sha> e conferma l'ordine dei genitori.

Se hai inviato la cosa sbagliata e devi rimuoverla

A volte revert non è sufficiente. Se hai inviato un segreto, un binario enorme o dati privati dei clienti, un revert lo rimuove solo dall'albero più recente. Il contenuto sensibile esiste ancora nella cronologia. Questo diventa un problema di risposta agli incidenti, non solo di pulizia Git.

Per i segreti, ruota prima le credenziali. Poi rimuovi i dati dalla cronologia con uno strumento di riscrittura della cronologia appropriato come git filter-repo, BFG Repo-Cleaner o un processo di rimozione dei segreti specifico dell'host. Coordinati con il proprietario del repository e presumi che chiunque abbia accesso potrebbe aver scaricato il commit errato prima che tu lo rimuovessi.

Per un normale commit errato sul tuo branch di funzionalità personale, riscrivere il branch remoto può essere accettabile. Resetta localmente, poi forza il push con un lease:

git reset --hard HEAD~1
git push --force-with-lease origin my-feature-branch

--force-with-lease è la forma più sicura perché rifiuta di aggiornare il remoto se qualcun altro ha inviato nuovo lavoro dopo il tuo ultimo fetch. È comunque un force push. Riscrive comunque il branch. Usalo su branch di funzionalità personali o coordinati, non casualmente su branch condivisi.

Una buona abitudine prima di forzare il push è:

git fetch origin
git log --oneline --left-right --graph origin/my-feature-branch...my-feature-branch

Questo mostra cosa esiste solo sul remoto e cosa esiste solo localmente. Se il lato sinistro contiene commit di qualcun altro, fermati e parla con loro.

Recupero con reflog

git reflog è il comando che salva molti pomeriggi brutti. Registra dove il tuo HEAD locale e i riferimenti del branch sono stati puntati di recente. Se resetti al posto sbagliato, hai cancellato un branch o hai modificato il commit sbagliato, reflog spesso conosce il vecchio SHA del commit.

git reflog

Potresti vedere qualcosa come:

7cc8a91 HEAD@{0}: reset: moving to HEAD~1
2b41f0d HEAD@{1}: commit: add retry around deploy step

Per recuperare il vecchio commit, crea un branch su di esso:

git branch recovered-deploy-work 2b41f0d

Preferisco creare un branch invece di eseguire immediatamente un altro hard reset. Ti dà un handle stabile e puoi ispezionare il lavoro recuperato con calma:

git show recovered-deploy-work
git switch recovered-deploy-work

Reflog è locale. Il reflog del tuo compagno di squadra non conterrà i tuoi commit locali persi e gli host remoti potrebbero non esporre lo stesso percorso di recupero. Viene anche potato nel tempo secondo le impostazioni di garbage collection di Git, quindi trattalo come una rete di sicurezza recente, non come archiviazione permanente.

Una guida pratica alle decisioni

Se hai committato troppo presto e non hai inviato, usa git reset --soft HEAD~1 o git reset HEAD~1.

Se hai committato il file sbagliato e vuoi che le modifiche scompaiano localmente, usa git reset --hard HEAD~1 solo dopo aver controllato git status.

Se il commit errato è già su un branch condiviso, usa git revert <sha>.

Se il tuo branch di funzionalità è remoto ma lo usi solo tu, git reset più git push --force-with-lease è di solito accettabile.

Se il commit contiene un segreto o dati sensibili, non affidarti a revert. Ruota il segreto, riscrivi la cronologia con lo strumento giusto e coordina la pulizia.

Il flusso di lavoro più sicuro per annullare Git è noioso: ispeziona prima, crea un branch di backup, scegli il comando in base a se il commit è condiviso e usa reflog quando devi recuperare dal tuo stesso tentativo di recupero.