W3docs

git reset

Informazioni sul comando git reset, i tre alberi di Git e la loro relazione con git reset, con esempi pratici.

git reset

git reset è il comando principale per annullare le modifiche nel repository locale. A seconda della modalità scelta, può togliere un file dalla staging area, scartare le modifiche in staging o spostare un branch indietro a un commit precedente. Poiché può riscrivere la posizione del branch e scartare lavoro, è potente ma facile da usare in modo errato.

Questa pagina spiega i tre "alberi" su cui agisce git reset, illustra le tre modalità (--soft, --mixed, --hard) con esempi e mostra quando usare invece git revert.

Quando usare git reset

Usa git reset quando vuoi riscrivere la cronologia locale o la staging area che non è ancora stata condivisa:

  • Togliere un file dalla staging area aggiunto per errore: git reset <file>.
  • Svuotare l'intera staging area per ricostruire da zero il prossimo commit: git reset.
  • Scartare commit e modifiche locali completamente: git reset --hard <commit>.
  • Unire o riscrivere gli ultimi commit prima di fare il push.

Se i commit che vuoi annullare sono già stati pubblicati su un branch condiviso, usa invece git revert — registra un nuovo commit che inverte le modifiche senza riscrivere la cronologia da cui dipendono altri.

Git reset e i tre alberi

git reset ha tre forme di invocazione che corrispondono ai tre sistemi interni di gestione dello stato di Git, spesso chiamati i tre alberi di Git:

  • HEAD — la cronologia dei commit (lo snapshot a cui punta attualmente il branch).
  • Staging index — le modifiche in staging per il prossimo commit.
  • Working directory — i file su disco nel tuo editor.

Esamineremo ciascuno di questi sistemi a turno.

La working directory

Il primo albero è la working directory. Rappresenta i file nel file system del tuo computer che il tuo editor può modificare. La working directory è sincronizzata con un commit specifico del progetto estratto: quando un progetto viene estratto, Git decomprime le versioni dei file del repository su disco.

Se modifichiamo un file tracciato, git status lo segnala come una modifica non in staging nella working directory:

echo 'hello git reset' > edited_file
git status 
#On branch master 
#Changes not staged for commit: 
#(use "git add ..." to update what will be committed) 
#(use "git checkout -- ..." to discard changes in working directory) 
#modified: edited_file

Staging index

Il secondo albero è lo staging index, che traccia le modifiche in staging per il prossimo commit. Git di solito nasconde i dettagli interni della staging area. Lo troverai indicato anche con altri nomi: cache, directory cache, staged files o staging area.

Per ispezionare direttamente lo staging index possiamo usare git ls-files -s, uno strumento di debug che stampa le voci in staging insieme ai relativi hash degli oggetti:

git ls-files -s
#543644 a32de29bb3c1d643328b29ae775ad8c2e48c3256 0 edited_file

Cronologia dei commit

Il terzo albero è la cronologia dei commit. Il comando git commit prende ciò che si trova nello staging index e lo registra come snapshot permanente nella cronologia:

git commit -am "edit content of test_file"
#[master ab23324] edit the content of edited_file
#1 file changed, 1 insertion(+)
git status
#On branch master
#nothing to commit, working tree clean

Nell'esempio precedente si vede il nuovo commit con il messaggio "edit content of test_file". Le modifiche sono collegate alla cronologia dei commit. In questa fase, eseguendo git status non risultano modifiche in sospeso in nessuno dei tre alberi. Invocando git log si vede la cronologia dei commit. Una volta apportate modifiche attraverso i tre alberi, è possibile usare git reset.

Come funziona

A prima vista, il comando git reset ha alcune somiglianze con git checkout, poiché entrambi operano su HEAD. Il comando git checkout opera esclusivamente sul puntatore di riferimento HEAD, mentre il comando git reset sposta sia il puntatore HEAD che il puntatore del branch corrente. L'illustrazione seguente aiuta a capirne il comportamento:

git reset1

Questa illustrazione mostra la sequenza di commit sul branch master. Come si può vedere, il riferimento HEAD e il riferimento al branch master puntano attualmente al commit d. Vedremo come cambia l'immagine nel caso di git checkout b e git reset b.

git checkout b

Eseguendo il comando git checkout, il riferimento master punta ancora al commit d. Per quanto riguarda il riferimento HEAD, è stato spostato e ora punta al commit b. Di conseguenza, il repository si trova ora in uno stato di 'detached HEAD'.

git reset2

git reset b

Il comando git reset sposta sia HEAD che il riferimento al branch corrente al commit specificato. Può anche modificare lo stato dello staging index e della working directory. Tre opzioni della riga di comando — --soft, --mixed e --hard — controllano fino a dove arriva il reset:

git reset3

Opzioni principali

Per impostazione predefinita, git reset viene eseguito con gli argomenti --mixed e HEAD. Quindi invocare git reset equivale a git reset --mixed HEAD. Il HEAD qui è il commit di destinazione — puoi sostituirlo con qualsiasi riferimento a un commit, come un hash SHA-1 (git reset 0a1b2c3) o un puntatore relativo (git reset HEAD~2).

La tabella seguente riassume quali alberi vengono toccati da ciascuna modalità:

ModalitàCronologia dei commit (HEAD)Staging indexWorking directory
--softspostatoinvariatoinvariata
--mixed (predefinito)spostatoresettato al targetinvariata
--hardspostatoresettato al targetresettata al target (modifiche perse)

Un modo utile per ricordarlo: --soft mantiene le modifiche in staging, --mixed le mantiene come modifiche non in staging, e --hard le elimina completamente.

git reset4

--hard

--hard è l'opzione più potente e più pericolosa. Sposta il riferimento della cronologia dei commit al commit di destinazione, quindi forza lo staging index e la working directory ad allinearsi con quel commit. Qualsiasi modifica in sospeso nello staging index e nella working directory viene scartata — e questa perdita non può essere annullata con git reset.

Attenzione: --hard elimina definitivamente il lavoro non committato. Esegui prima git status per confermare cosa stai per perdere.

Per dimostrarlo, creiamo prima alcune modifiche in sospeso:

echo 'test content' > test_file
git add test_file
echo 'modified content' >> edited_file

È stato creato e aggiunto alla staging area un nuovo file chiamato test_file, e il contenuto di edited_file è stato modificato nella working directory. Verifichiamo lo stato del repository con il comando git status:

git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file

Ora ci sono modifiche in sospeso in due alberi: lo staging index contiene il nuovo test_file, e la working directory contiene le modifiche a edited_file. Vediamo lo stato dello staging index:

git ls-files -s
#123126 7a32454a5477b1bf4765946147c49509a431f963 0 test_file
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

Il test_file è stato aggiunto all'index. Il edited_file è stato aggiornato, ma lo SHA dello staging index (d7d77c1b04b5edd5acfc85de0b592449e5303770) rimane lo stesso. Queste modifiche si trovano nella working directory. Non sono state promosse allo staging index perché non abbiamo eseguito il comando git add. Ora eseguiamo git reset --hard e ispezioniamo il nuovo stato del repository:

git reset --hard
#HEAD is now at ab23324 update content of edited_file
git status
#On branch master
#nothing to commit, working tree clean
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

L'opzione --hard ha eseguito un "hard reset". Git indica che HEAD punta al commit recente ab23324. Poi lo stato del repository viene verificato con git status. Git indica che non ci sono modifiche in sospeso. Per quanto riguarda lo stato dello staging index, è stato resettato a un punto precedente all'aggiunta di test_file. Le modifiche a edited_file e l'aggiunta di test_file sono state eliminate. Questa perdita non può essere annullata.

--mixed

--mixed è la modalità predefinita. Sposta i puntatori di riferimento e resetta lo staging index al commit di destinazione, ma lascia invariata la working directory. Le modifiche rimosse dall'index riappaiono come modifiche nella working directory, quindi nulla va perso — hai semplicemente l'opportunità di aggiungerle nuovamente alla staging area.

echo 'new file content' > test_file
git add test_file
echo 'append content' >> edited_file
git add edited_file
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#new file: test_file
#modified: edited_file
git ls-files -s
#123126 6a32154a5477b1bf4765946147c49509a4323d32 0 test_file
#123126 3c3262db063f9e9426901092c00a3394b4bd3445 0 edited_file

Nell'esempio precedente, test_file è stato aggiunto e il contenuto di edited_file è stato modificato, e entrambe le modifiche sono state applicate allo staging index con git add. Con questo stato del repository, è il momento di invocare git reset:

git reset --mixed
git status
#On branch master
#Changes not staged for commit:
#(use "git add ..." to update what will be committed)
#(use "git checkout -- ..." to discard changes in working directory)
#modified: edited_file
#Untracked files:
#(use "git add ..." to include in what will be committed)
#test_file
#no changes added to commit (use "git add" and/or "git commit -a")
git ls-files -s
#123126 6c423c1b04b5edd5acfc85de0b592449e5303773 0 edited_file

--mixed è la modalità predefinita. Ha lo stesso effetto di git reset. Il git status mostra che ci sono modifiche a edited_file e che test_file è un file non tracciato. Questo è esattamente il comportamento di --mixed. Lo staging index è stato resettato e le modifiche in sospeso sono state spostate nella working directory.

--soft

L'argomento --soft sposta i puntatori di riferimento e si ferma lì. Non tocca lo staging index né la working directory, quindi tutto ciò che era stato committato rimane in staging e pronto per essere committato di nuovo. È la modalità da usare quando si vogliono unire più commit in uno solo.

git reset --soft
git status
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
git ls-files -s
#123126 32a252710639e5da6b515416fd779d0741e4561a 0 edited_file

Un soft reset sposta solo la cronologia dei commit. Per impostazione predefinita punta a HEAD. Creiamo un nuovo commit e poi proviamo un reset --soft con un commit di destinazione diverso da HEAD:

git commit -m "add changes to edited_file"

Ora il repository ha tre commit. Per trovare il primo, controlliamo il suo ID nell'output di git log:

git log
#commit 62e793f6941c7e0d4ad9a1345a175fe8f45cb9df
#Author: w3docs
#Date: Fri Nov 1 14:02:07 2019 -0800
#add changes to edited_file
#commit ab23324a6da9f0dec51ed16d3d8823f28e1a72a
#Author: w3docs
#Date: Fri Nov 1 11:31:58 2019 -0800
#change content of edited_file
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

L'ultima voce è il commit iniziale; useremo il suo ID come destinazione per il soft reset. Prima verifichiamo lo stato attuale del repository:

git status && git ls-files -s
#On branch master
#nothing to commit, working tree clean
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

Ora possiamo eseguire il soft reset tornando al primo commit:

git reset --soft 780411da3b47117270c0e3a8d5dcfd11d28d04a4
git status && git ls-files -s
#On branch master
#Changes to be committed:
#(use "git reset HEAD ..." to unstage)
#modified: edited_file
#123126 32a252710639e5da6b515416fd779d0741e4561a  0 edited_file

Nell'esempio precedente abbiamo eseguito un soft reset e invocato il comando combinato git status e git ls-files, che mostra lo stato del repository. Il comando git status indica che ci sono alcune modifiche a edited_file, evidenziate come modifiche in staging per il prossimo commit. L'output di git ls-files mostra che lo staging index è rimasto invariato e conserva lo SHA 32a252710639e5da6b515416fd779d0741e4561a. Esaminiamo la cronologia dei commit dopo il soft reset con git log:

git log
#commit 780411da3b47117270c0e3a8d5dcfd11d28d04a4
#Author: w3docs
#Date: Thu Sep 31 18:40:29 2019 -0800
#initial commit

L'output mostra ora un singolo commit nella cronologia. Come per tutte le invocazioni di git reset, --soft prima resetta l'albero dei commit. A differenza degli esempi precedenti con --hard e --mixed che puntavano a HEAD, questo soft reset ha spostato l'albero dei commit indietro nel tempo a un commit più vecchio — mentre il lavoro è rimasto al sicuro nello staging index.

La differenza tra i comandi reset e revert

git revert è generalmente un modo più sicuro per annullare le modifiche rispetto a git reset, perché git reset può perdere lavoro. Un git reset non elimina immediatamente un commit, ma può lasciarlo orfano — nessun branch o tag vi punta più, quindi non c'è un modo diretto per raggiungerlo. Git alla fine rimuove gli oggetti orfani quando esegue il garbage collector (git gc), che per impostazione predefinita elimina gli oggetti irraggiungibili più vecchi di circa 90 giorni (le voci del reflog scadono dopo 90 giorni, o 30 giorni per quelle irraggiungibili). Nel frattempo, di solito è possibile recuperare un commit orfano con il comando git reflog.

L'altra differenza fondamentale: git revert è progettato per annullare commit pubblici, già condivisi aggiungendo un nuovo commit inverso, mentre git reset è progettato per annullare modifiche locali alla working directory e allo staging index.

Non resettare mai la cronologia pubblicata

Non eseguire git reset <commit> quando ci sono snapshot successivi a <commit> che sono già stati inviati a un repository condiviso. Una volta pubblicato un commit, altri sviluppatori vi fanno affidamento. Riscrivere o eliminare commit che i colleghi hanno già scaricato causerà cronologie divergenti e conflitti di merge dolorosi. Usa git reset solo su commit che esistono esclusivamente nel tuo repository locale. Per annullare modifiche pubbliche, usa invece il comando git revert.

Esempi

Rimuove un file specifico dalla staging area senza modificare la working directory — toglie il file dalla staging mantenendo le modifiche:

git reset <file>

Resetta l'intera staging area per corrispondere all'ultimo commit, lasciando invariata la working directory. Toglie dalla staging ogni file senza sovrascrivere le modifiche, così puoi ricostruire lo snapshot da zero:

git reset

Resetta sia la staging area che la working directory per corrispondere all'ultimo commit. Questo scarta tutte le modifiche non committate nella working directory:

git reset --hard

Sposta il puntatore del branch a un commit specificato e resetta la staging area per corrispondere, lasciando invariata la working directory:

git reset <commit>

Sposta il puntatore del branch corrente a un commit specificato e resetta sia la staging area che la working directory per corrispondere:

git reset --hard <commit>

Rimozione dei commit locali

Come mostrato in precedenza, puoi usare git reset per eliminare commit nel tuo repository locale. Nell'esempio seguente, git reset --hard HEAD~2 sposta il branch corrente indietro di due commit, rimuovendo i due snapshot più recenti dalla cronologia del progetto:

# Create a new file called `yourname.txt` and add some code to it
# Commit it to the project history
git add yourname.txt
git commit -m "Start to develop a project"
# Edit `yourname.txt` again and change some other tracked files, too
# Commit another snapshot
git commit -a -m "Continue developing"
# Scrap the project and remove the related commits
git reset --hard HEAD~2

Rimozione dei file dalla staging area

Un utilizzo molto comune di git reset è perfezionare cosa entra nel prossimo commit. Nell'esempio seguente abbiamo due file, task.txt e index.txt, entrambi in staging. git reset ci permette di togliere dalla staging le modifiche che non appartengono al prossimo commit, così ogni file può essere committato separatamente:

# Edit task.txt and index.txt
# Stage everything in the current directory
git add .
# Realize that the changes in task.txt and index.txt
# should be committed in different snapshots
# Unstage index.txt
git reset index.txt
# Commit only task.txt
git commit -m "Edit task.txt"
# Commit index.txt in a separate snapshot
git add index.txt
git commit -m "Edit index.txt"

Esercitazione

Pratica
Quali sono le funzionalità e le opzioni del comando 'git reset' in Git?
Quali sono le funzionalità e le opzioni del comando 'git reset' in Git?
Was this page helpful?