Prima o poi capita: sul branch principale finisce del codice che non doveva arrivarci e serve riportare tutto a un commit precedente. Git offre due strade molto diverse, e sceglierla in base a chi altro lavora su quel branch evita un bel po’ di guai.

Serve in entrambi i casi l’identificativo del commit a cui vuoi tornare. Per trovarlo:

git log --oneline

Strada sicura: git revert

Se il branch è condiviso — cioè se qualcun altro l’ha già scaricato — questa è la strada da preferire. revert non cancella nulla: crea un nuovo commit che annulla le modifiche di quelli indesiderati, lasciando intatta la storia.

Per annullare un singolo commit:

git checkout master
git revert <hash_del_commit>
git push origin master

Per annullare tutti i commit successivi a un certo punto, indica l’intervallo (^.. include l’estremo iniziale):

git revert --no-commit <hash_del_commit_buono>..HEAD
git commit -m "Ripristino dello stato al commit <hash_del_commit_buono>"
git push origin master

Il vantaggio pratico è che non serve nessun push forzato: chi ha già il branch in locale riceve la correzione con un normale git pull, senza dover fare niente di speciale. Resta traccia sia dell’errore sia della correzione, che in un repository di lavoro è spesso un pregio, non un difetto.

Strada distruttiva: reset e push forzato

Se il branch è tuo e non l’ha ancora scaricato nessuno, puoi riscrivere la storia e far finta che quei commit non siano mai esistiti:

git checkout master
git reset --hard <hash_del_commit>
git push --force-with-lease origin master

Due precisazioni importanti su questi comandi.

git reset --hard scarta le modifiche non committate presenti nella working directory, senza chiedere conferma e senza possibilità di recupero. Se hai lavoro in corso che vuoi conservare, mettilo da parte prima con git stash.

--force-with-lease è preferibile al classico --force: rifiuta il push se sul remoto è arrivato qualcosa che tu non hai ancora scaricato. Con --force secco, i commit che un collega ha pushato nel frattempo vengono sovrascritti e vanno perduti; con --force-with-lease il push si interrompe e ti accorgi del problema. Costa un carattere in più e salva una giornata.

Molti repository proteggono il branch principale lato server (GitHub, GitLab, Azure DevOps): in quel caso il push forzato viene rifiutato a prescindere. È una protezione sensata — se la incontri, la risposta giusta è quasi sempre passare a git revert.

Se hai già fatto reset e te ne penti

Finché il commit non è stato rimosso dal garbage collector, è recuperabile. git reflog elenca tutte le posizioni in cui HEAD è passato:

git reflog
git reset --hard HEAD@{1}

HEAD@{1} è la posizione immediatamente precedente all’ultima operazione; nell’elenco del reflog trovi l’hash esatto a cui tornare.

Una nota su master e main

Negli esempi il branch si chiama master, ma dal 2020 i repository creati su GitHub usano main come nome predefinito. Se i comandi restituiscono un errore del tipo pathspec 'master' did not match any file(s) known to git, sostituisci master con main. Per sapere come si chiama il branch corrente:

git branch --show-current

Leggi anche: