Lezione 6 di 35

Git 06

🔀 Git & GitHub · Modulo 1 · Lezione 06

Annullare modifiche ⏪

git restore · git reset (soft / mixed / hard) · git revert · Quando usare quale · Errori comuni

⏱️ Durata stimata: 75–90 min 📊 Livello: Base (Modulo 1) 🔧 Strumenti: Terminale · Repository con almeno 5 commit 📋 Prerequisiti: Lezioni 01–05

🛟 Sbagliare è umano. Annullare con Git è divino. Questa è la lezione che ti salverà la vita almeno una volta alla settimana. Imparerai a scartare modifiche nella working directory, togliere file dalla staging, annullare commit interi e riscrivere la storia recente. La regola d’oro: se non hai ancora fatto push, puoi annullare quasi tutto.

🎯 Obiettivi della lezione

  • Scartare modifiche nella working directory con git restore
  • Togliere file dalla staging area con git restore --staged
  • Annullare l’ultimo commit con git reset (soft, mixed, hard)
  • Creare un commit di “annullamento” con git revert
  • Capire la differenza tra reset (riscrive la storia) e revert (aggiunge alla storia)
  • Scegliere lo strumento giusto per ogni situazione
  • Modificare il messaggio dell’ultimo commit con --amend

📖 1. Teoria

1.1 — La mappa delle situazioni: “Dove ho sbagliato?”

Prima di imparare i comandi, devi capire dove si trova l’errore. La risposta determina quale strumento usare:

L’errore è… Situazione Strumento Distruttivo?
Nella working directory Hai modificato un file e vuoi tornare alla versione dell’ultimo commit git restore file ⚠️ Sì
Nella staging area Hai fatto git add per errore e vuoi togliere il file dalla staging git restore –staged file ✅ No
Nell’ultimo commit (locale) Hai committato qualcosa di sbagliato, ma non hai ancora fatto push git reset ⚠️ Dipende
In un commit già pushato Un commit pubblico contiene un errore — devi annullarlo senza riscrivere la storia git revert ✅ No
Nel messaggio dell’ultimo commit Il commit è giusto ma il messaggio ha un typo git commit –amend ⚠️ Riscrive
Mappa decisionale: “Cosa devo annullare?”
Working Dir
restore
Staging
restore –staged
Commit locale
reset / amend
Commit pushato
revert
Da sinistra a destra: più l’errore è “avanti” nella catena, più serve cautela.

1.2 — git restore: annullare nella working directory e nella staging

Hai già incontrato git restore nella Lezione 04. Ora lo approfondiamo come strumento di annullamento.

✅ git restore –staged file (sicuro)
Cosa fa: toglie il file dalla staging area. Le modifiche restano nella working directory.

Quando: hai fatto git add per errore — il file non deve andare nel prossimo commit.

Rischio: zero. Nessuna modifica viene persa.
⚠️ git restore file (distruttivo)
Cosa fa: cancella le modifiche nella working directory e ripristina il file alla versione in staging (o all’ultimo commit se non c’è nulla in staging).

Quando: hai fatto modifiche sbagliate e vuoi ricominciare da zero.

Rischio: le modifiche vengono cancellate permanentemente. Non c’è undo.
# Scenario: hai modificato un file e vuoi scartare le modifiche
$ echo “codice sbagliato” >> index.html
$ git diff ← mostra la riga aggiunta

# Scarta le modifiche — torna alla versione dell’ultimo commit
$ git restore index.html
$ git diff ← nessun output — il file è tornato com’era

# Per scartare TUTTE le modifiche nella working directory:
$ git restore . ← ATTENZIONE: cancella tutte le modifiche non staged!
# Ripristinare un file da un commit specifico
$ git restore –source HEAD~3 index.html
Questo ripristina index.html alla versione di 3 commit fa nella working directory. Le modifiche appaiono come “modified” — puoi poi fare add e commit per salvare questa versione “vecchia” come un nuovo commit.

1.3 — git commit –amend: correggere l’ultimo commit

Hai appena fatto un commit e ti accorgi che il messaggio ha un typo, oppure hai dimenticato di includere un file. Non serve fare un nuovo commit — puoi modificare l’ultimo.

# Caso 1: solo il messaggio è sbagliato
$ git commit –amend -m “Messaggio corretto”
Sostituisce il messaggio dell’ultimo commit. L’hash del commit cambia (è un nuovo commit che rimpiazza il vecchio).
# Caso 2: hai dimenticato un file nell’ultimo commit
$ git add file-dimenticato.txt ← aggiungi il file alla staging
$ git commit –amend –no-edit ← aggiungi al commit senza cambiare il messaggio
Il flag --no-edit mantiene il messaggio originale. Il file dimenticato viene incluso nell’ultimo commit come se fosse sempre stato lì.

⚠️ Non usare --amend su commit già pushati! Poiché --amend crea un nuovo commit con un hash diverso, se il commit originale è già stato condiviso con altri (push), creerai un conflitto nella storia. Usa --amend solo sull’ultimo commit locale, prima del push.

1.4 — git reset: riportare indietro il branch

git reset sposta il puntatore del branch all’indietro — come se gli ultimi commit non fossero mai successi. Ha tre modalità che determinano cosa succede ai file:

Modalità Commit Staging Area Working Dir Le modifiche…
–soft ↩️ Annullato ✅ Mantenute ✅ Mantenute Restano in staging, pronte per un nuovo commit
–mixed ↩️ Annullato ❌ Svuotata ✅ Mantenute Restano nella working dir (non staged). È il default.
–hard ↩️ Annullato ❌ Svuotata ❌ Cancellate Perse per sempre. Come se non avessi mai fatto quelle modifiche.
Visualizzazione: cosa succede con reset HEAD~1
–soft
Commit annullato
Modifiche in staging
Working dir intatta

Puoi rifare commit subito
–mixed (default)
Commit annullato
Staging svuotata
Modifiche in working dir

Devi rifare add + commit
–hard
Commit annullato
Staging svuotata
Working dir ripristinata

Tutto cancellato per sempre
# Annullare l’ultimo commit mantenendo le modifiche in staging
$ git reset –soft HEAD~1

# Annullare l’ultimo commit mantenendo le modifiche nella working dir
$ git reset HEAD~1 ← equivale a –mixed (default)

# Annullare l’ultimo commit e CANCELLARE tutto
$ git reset –hard HEAD~1 ← ATTENZIONE: irreversibile!

# Annullare gli ultimi 3 commit
$ git reset –soft HEAD~3

📌 Quando usare –soft vs –mixed: usa --soft quando l’errore è nel commit ma le modifiche sono corrette — vuoi solo rifare il commit (magari con un messaggio diverso o con file diversi). Usa --mixed quando vuoi ripensare anche la staging — quali file includere nel prossimo commit. Usa --hard solo quando sei sicuro di voler cancellare tutto e ripartire dallo stato pulito.

1.5 — git revert: annullare senza riscrivere la storia

git revert è fondamentalmente diverso da git reset: non cancella il commit sbagliato, ma crea un nuovo commit che annulla le modifiche di quello vecchio. La storia rimane intatta — si aggiunge un “commit di annullamento”.

reset = riscrive la storia
A → B → C → D
Il commit D sparisce. Il branch torna a C. Chi ha già scaricato D avrà problemi.

Da usare: solo su commit locali (non pushati).
revert = aggiunge alla storia
A → B → C → D → D’
D’ è un nuovo commit che annulla le modifiche di D. D resta nella storia. Nessun conflitto per nessuno.

Da usare: su commit già pushati e condivisi.
# Annullare l’ultimo commit creando un commit di annullamento
$ git revert HEAD
[main f1a2b3c] Revert “Aggiunto foglio di stile e collegato alla home”
2 files changed, 0 insertions(+), 6 deletions(-)

# Annullare un commit specifico (non necessariamente l’ultimo)
$ git revert c8d3f4a

# Revert senza creare subito il commit (per modificare prima)
$ git revert –no-commit HEAD ← le modifiche vanno in staging, tu decidi quando committare

Quando esegui git revert HEAD, Git apre l’editor per farti scrivere il messaggio del commit di annullamento. Il messaggio predefinito è Revert "messaggio originale" — nella maggior parte dei casi va bene così. Salva e chiudi l’editor per completare il revert.

1.6 — La regola d’oro: reset vs revert

La Regola
Commit NON pushato
Solo tu lo hai
→ git reset
|
Commit GIÀ pushato
Altri lo hanno scaricato
→ git revert
Non fare reset su commit pushati — crea caos per tutti i collaboratori.

1.7 — Tabella riepilogativa completa

Situazione Comando Effetto Sicuro?
Scartare modifiche in un file git restore file File torna all’ultimo commit ⚠️
Togliere file dalla staging git restore –staged file File torna a “not staged” ✅
Correggere messaggio ultimo commit git commit –amend -m “…” Sostituisce l’ultimo commit ⚠️ locale
Aggiungere file dimenticato all’ultimo commit git add file + git commit –amend –no-edit Include il file nell’ultimo commit ⚠️ locale
Annullare ultimo commit, tenere modifiche in staging git reset –soft HEAD~1 Commit sparisce, staging intatta ✅
Annullare ultimo commit, tenere file in working dir git reset HEAD~1 Commit sparisce, staging svuotata ✅
Annullare ultimo commit e cancellare tutto git reset –hard HEAD~1 Tutto cancellato ❌
Annullare un commit pubblico git revert HASH Crea nuovo commit che annulla ✅

💻 2. Esempi guidati

Esempio A — Preparare il repository di test

$ mkdir lab-undo && cd lab-undo && git init
$ echo “v1” > file.txt && git add . && git commit -m “Commit 1: creato file.txt”
$ echo “v2” >> file.txt && git add . && git commit -m “Commit 2: aggiunta riga v2”
$ echo “v3” >> file.txt && git add . && git commit -m “Commit 3: aggiunta riga v3”
$ echo “v4-errore” >> file.txt && git add . && git commit -m “Commit 4: riga sbagliata”
$ git log –oneline
d4e5f6a Commit 4: riga sbagliata
b7c8d9e Commit 3: aggiunta riga v3
a1b2c3d Commit 2: aggiunta riga v2
e4f5a6b Commit 1: creato file.txt

Esempio B — reset –soft: annullare il commit, rifare meglio

# Il Commit 4 è sbagliato. Annulliamolo mantenendo le modifiche in staging
$ git reset –soft HEAD~1
$ git log –oneline ← il Commit 4 è sparito!
b7c8d9e Commit 3: aggiunta riga v3
a1b2c3d Commit 2: aggiunta riga v2
e4f5a6b Commit 1: creato file.txt

$ git status -s
M file.txt ← le modifiche sono ancora in staging!

# Correggiamo il file e rifacciamo il commit
$ echo “v1” > file.txt && echo “v2” >> file.txt && echo “v3” >> file.txt && echo “v4-corretta” >> file.txt
$ git add file.txt
$ git commit -m “Commit 4: aggiunta riga v4 corretta”

Esempio C — revert: annullare un commit conservando la storia

# Immaginiamo che il Commit 4 sia già stato pushato. Non possiamo fare reset.
# Usiamo revert per creare un commit che annulla le modifiche del Commit 4.
$ git revert HEAD
# Si apre l’editor — accetta il messaggio predefinito e salva
[main f9a8b7c] Revert “Commit 4: aggiunta riga v4 corretta”

$ git log –oneline
f9a8b7c Revert “Commit 4: aggiunta riga v4 corretta”
d4e5f6a Commit 4: aggiunta riga v4 corretta
b7c8d9e Commit 3: aggiunta riga v3
a1b2c3d Commit 2: aggiunta riga v2
e4f5a6b Commit 1: creato file.txt
Nota: 5 commit, non 4. Il Commit 4 è ancora nella storia, ma il suo effetto è stato annullato dal Commit “Revert”. La storia è intatta e leggibile.

Esempio D — amend: correggere l’ultimo commit

# Oops, il messaggio dell’ultimo commit ha un typo
$ git commit –amend -m “Revert del Commit 4 (riga errata)”
$ git log –oneline -n 1
a2b3c4d Revert del Commit 4 (riga errata) ← hash cambiato! è un nuovo commit

# Abbiamo anche dimenticato di aggiungere un file di note
$ echo “Motivo del revert: riga sbagliata” > note-revert.txt
$ git add note-revert.txt
$ git commit –amend –no-edit ← il file viene incluso nell’ultimo commit
$ git show –stat HEAD ← ora il commit include sia file.txt che note-revert.txt

🏋️ 3. Esercizi

🟢 Esercizio 1 — restore in pratica (Facile)

a) Crea un repo con un file pagina.html, committalo.
b) Aggiungi una riga sbagliata a pagina.html. Verifica con git diff.
c) Scarta la modifica con git restore pagina.html. Verifica con git diff (nessun output).
d) Aggiungi di nuovo una riga. Fai git add pagina.html.
e) Togli dalla staging con git restore --staged pagina.html. Verifica con git status: il file è in rosso, non in verde.
f) La riga è ancora nel file? (Sì — restore --staged non tocca la working directory.)

🔵 Esercizio 2 — Le tre modalità di reset (Medio)

Questo esercizio dimostra la differenza tra –soft, –mixed e –hard. Fallo 3 volte, una per modalità:

a) Crea un repo con 3 commit (A, B, C). Verifica con git log --oneline.

Prova 1 (–soft):
b) Esegui git reset --soft HEAD~1. Controlla git log --oneline (2 commit). Controlla git status -s (file in verde). Dove sono le modifiche del commit C? Risposta: in staging.

Prova 2 (ricrea il commit C prima, oppure rifai il repo):
c) Esegui git reset HEAD~1 (mixed). Controlla log (2 commit) e status (file in rosso). Dove sono? In working dir.

Prova 3:
d) Esegui git reset --hard HEAD~1. Controlla log (2 commit) e status (clean). Dove sono le modifiche? Sparite.

🟠 Esercizio 3 — revert vs reset (Medio)

a) Crea un repo con 4 commit. Il commit 4 introduce un errore.
b) Annulla il commit 4 con git revert HEAD. Quanti commit ci sono ora? (5)
c) Verifica con git log --oneline: il commit 4 è ancora nella storia?
d) Ora fai git reset --hard HEAD~2 per tornare al commit 3. Quanti commit? (3)
e) Il commit 4 è nella storia? E il revert? (No — sono stati cancellati dal reset.)
f) Spiega: perché il reset ha cancellato la storia mentre il revert no?

🟣 Esercizio 4 — Scenario realistico (Difficile)

Simula una giornata di lavoro con errori e correzioni:

a) Crea un repo progetto-web con 3 file: index.html, style.css, README.md. Commit iniziale.
b) Modifica style.css aggiungendo stili. Committa: “Aggiunti stili base”.
c) Oops — ti accorgi di aver scritto backgruond invece di background nel CSS. Usa git reset --soft HEAD~1, correggi il typo, rifai add e commit con lo stesso messaggio.
d) Modifica index.html e README.md. Fai git add . — ma ti accorgi che README.md non doveva essere in questo commit. Usa git restore --staged README.md per toglierlo. Committa solo index.html.
e) Committa README.md separatamente.
f) Crea un file password.txt con dentro “admin123”. Fai add e commit. Oops! Usa git reset HEAD~1 per annullare il commit. Cancella il file. Verifica che non sia nella storia con git log --stat.
g) Verifica la storia finale con git log --oneline --stat: deve essere pulita, senza trace del file password.

❓ 4. Quiz di verifica

1. Qual è la differenza tra git restore file e git restore --staged file?

Mostra risposta

git restore file (senza flag) cancella le modifiche nella working directory ripristinando il file alla versione in staging o dell’ultimo commit. È distruttivo e irreversibile. git restore --staged file toglie il file dalla staging area riportandolo allo stato “modified” o “untracked” nella working directory — le modifiche restano intatte. Il primo è come dire “butta via il lavoro”, il secondo è come dire “toglilo dal carrello ma tienilo in mano”.


2. Hai fatto un commit con il messaggio sbagliato “Aggiunti stli” invece di “Aggiunti stili”. Come correggi senza creare un nuovo commit?

Mostra risposta

Usa git commit --amend -m "Aggiunti stili". Questo sostituisce l’ultimo commit con uno nuovo che ha il messaggio corretto. L’hash del commit cambia perché tecnicamente è un nuovo commit. Funziona solo sull’ultimo commit e solo se non è stato ancora pushato — altrimenti creerebbe conflitti per chi ha già scaricato il commit originale.


3. Dopo git reset --soft HEAD~1, dove si trovano le modifiche del commit annullato?

Mostra risposta

Nella staging area. Il commit è stato annullato (non appare più in git log), ma le modifiche sono ancora pronte per un nuovo commit — basta fare git commit -m "..." per creare un nuovo commit con le stesse modifiche (o modificarle prima). Questo rende --soft perfetto per situazioni in cui il lavoro è giusto ma il commit era sbagliato (messaggio errato, file sbagliato incluso, ecc.).


4. Qual è la differenza fondamentale tra git reset e git revert?

Mostra risposta

git reset riscrive la storia: sposta il puntatore del branch all’indietro e i commit “spariscono” dalla storia. git revert aggiunge alla storia: crea un nuovo commit che annulla le modifiche di un commit precedente, ma il commit originale resta nella storia. La regola pratica: usa reset per commit locali (non pushati), revert per commit già condivisi. Fare reset su commit pushati crea problemi a tutti i collaboratori perché la loro storia diverge dalla tua.


5. Hai accidentalmente committato un file con password. Il commit NON è stato pushato. Qual è il modo più sicuro per eliminare ogni traccia?

Mostra risposta

Usa git reset HEAD~1 per annullare il commit mantenendo i file nella working directory. Poi cancella il file con la password (rm password.txt) e rifai il commit senza quel file. Verifica con git log --stat che il file non appaia in nessun commit. Questo funziona perché il commit non è mai stato pushato — nessuno l’ha mai visto. Se il commit fosse già stato pushato, la situazione sarebbe molto più complessa e richiederebbe strumenti come git filter-branch o BFG Repo-Cleaner — e la password dovrebbe essere considerata compromessa e cambiata immediatamente.


6. git reset --hard HEAD~3: cosa succede ai 3 commit e ai file?

Mostra risposta

I 3 commit più recenti vengono rimossi dalla storia (non appaiono più in git log). La staging area viene svuotata. La working directory viene ripristinata allo stato del commit su cui ora punta HEAD (quello che prima era HEAD~3). Tutte le modifiche introdotte da quei 3 commit vengono cancellate permanentemente — non c’è undo (a meno di usare git reflog, un comando avanzato che tiene traccia dei movimenti di HEAD per un periodo limitato). Ecco perché --hard va usato con estrema cautela e solo quando sei sicuro di voler eliminare tutto.


📚 Riepilogo della lezione

Cosa hai imparato oggi:
  • git restore file — scarta modifiche nella working dir (distruttivo!)
  • git restore --staged file — toglie dalla staging (sicuro)
  • git restore --source HASH file — ripristina un file da un commit specifico
  • git commit --amend — corregge l’ultimo commit (messaggio o file dimenticati)
  • git reset --soft HEAD~n — annulla commit, modifiche in staging
  • git reset HEAD~n (mixed) — annulla commit, modifiche in working dir
  • git reset --hard HEAD~n — annulla commit, cancella tutto
  • git revert HASH — crea un commit che annulla, senza riscrivere la storia
  • La regola d’oro: reset per commit locali · revert per commit pushati

🎉 Congratulazioni! Hai completato il Modulo 1 — Fondamenti di Git. Sai creare repository, fare commit, leggere la storia e annullare errori. Queste sono le basi su cui si costruisce tutto il resto. Dal prossimo modulo imparerai a lavorare in parallelo con i branch — il vero superpotere di Git.

Prossima lezione →

Lezione 07 — Cos’è un branch
Il modello a puntatori di Git. HEAD, branch come etichette mobili.
Perché i branch Git sono leggeri — e perché cambiano tutto.

Corso Git & GitHub · Per studenti e insegnanti · Lezione 06 di 45