GNU/Linux: git stash, revert e reset spiegati facili

Oggi riprendiamo il discorso su Git. Se siete nuovi sul canale, recuperate il primo video della serie su Git: serve per capire quello che diciamo qui.

Nella puntata precedente vi ho spiegato che Git è una macchina del tempo che consente di creare linee temporali per tenere sotto controllo le versioni del codice. Ma non vi ho spiegato come muovervi lungo queste linee: solo come andare avanti, aggiungendo nuovi commit. Come ogni buona macchina del tempo, Git permette anche di tornare indietro. Oggi vediamo le tecniche per muoversi fra i commit: prima nel presente con lo stash, poi nel passato con revert e reset.

Lo stash: accantonare le modifiche

Una caratteristica interessante di Git è la possibilità di accantonare le modifiche a cui state lavorando, il momento presente, per fare altre cose e poi riprenderle. Introduciamo una nuova area: conoscevamo l’area di lavoro, l’area di staging e il repository; ora c’è l’area di stash, dove vengono accantonati gruppi di modifiche.

Un esempio. Nella mia cartella ci sono animali.txt, frutta.txt e un nuovo file, verdure.txt, su cui sto lavorando:

echo carote > verdure.txt
git status

Git ci dice che c’è un file non tracciato, lo aggiungiamo come sempre:

git add verdure.txt

Ora il file è nell’area di staging, pronto per il commit. Supponiamo che intervenga un’emergenza: dobbiamo interrompere il lavoro su verdure.txt e fare subito dei cambiamenti al file degli animali. Siccome è una cosa urgente e veloce, la faccio sulla branch dove mi trovo, senza creare una branch a parte (delle branch parleremo più avanti). Voglio solo mettere da parte verdure e poter lavorare a qualcos’altro, partendo da una situazione pulita. Ci viene in soccorso git stash:

git stash

Se rifaccio git status l’area di lavoro è perfettamente pulita. Per sapere quali modifiche ho accantonato (si possono accantonare più gruppi di modifiche, con un nome parlante) uso:

git stash list

L’output è:

stash@{0}: WIP on master: 4b2c1d3 aggiunta frutta

Un solo gruppo di modifiche. Ora lavoro sul file animali.txt come mi è stato chiesto, aggiungo l’animale e faccio il commit:

echo gatto >> animali.txt
git add animali.txt
git commit -m "aggiunto un animale"

Siamo di nuovo in area pulita. Passata l’emergenza, riprendo con calma il lavoro sulle verdure e prelevo le modifiche dall’area di stash:

git stash pop

Pop preleva il gruppo di modifiche in cima alla lista e Git ci informa che lo stash è stato svuotato di quel gruppo. Se invece avessi usato:

git stash apply

le modifiche sarebbero state applicate all’area di lavoro ma sarebbero rimaste nell’elenco degli stash: utile se dovete applicare le stesse modifiche a più branch.

Siamo tornati dove eravamo, con verdure.txt da completare. Finito il lavoro, faccio il commit.

Stash con un messaggio

Si possono creare stash con un nome esplicativo. Faccio un’altra modifica a verdure.txt e la accantono con un messaggio:

git stash push -m "modifiche alle verdure"

Se ora faccio git stash list, accanto allo stash compare il messaggio. Facciamo anche una modifica al file degli animali e accantoniamo anche quella:

git stash push -m "modifiche agli animali"

Ora abbiamo due stash, ognuno col suo nome, e l’area di lavoro pulita. Funziona allo stesso modo anche con le modifiche già nell’area di staging. Dopo aver fatto altre cose, voglio riprendere le modifiche sulle verdure: indico l’identificativo dello stash che voglio, che in questo caso è stash@{1}:

git stash pop stash@{1}

Finisco il lavoro sulle verdure. Poi prendo anche l’altro stash, rimasto da solo:

git stash pop

e siamo di nuovo in area di lavoro pulita.

Revert: annullare senza toccare la storia

Ora che ci siamo mossi nel presente, vediamo la prima tecnica per muoverci nel passato e annullare delle modifiche. Introduco volutamente un errore nel file degli animali: aggiungo un minerale (il quarzo) e faccio il commit senza accorgermene. Guardo la cronologia nella versione corta:

git log --oneline

L’output è:

bd4e7a1 aggiunto quarzo
4b2c1d3 aggiunto un animale

Il commit con l’errore è il primo in alto. Mi viene segnalato che il quarzo non deve stare nel file degli animali. Uso git revert:

git revert bd4e7a1

Git revert crea una sorta di contro-commit: non modifica la cronologia, aggiunge un nuovo commit che fa la modifica contraria a quello che voglio annullare. Si apre l’editor per il messaggio, come quando si fa un commit senza -m; salviamo e Git ci dice che ha fatto il revert. Con git log –oneline vediamo un nuovo commit sopra quello sbagliato, che fa esattamente la modifica che serve:

git log --oneline

L’output è:

c7f31e2 Revert "aggiunto quarzo"
bd4e7a1 aggiunto quarzo
4b2c1d3 aggiunto un animale

Questa tecnica lascia nella storia il commit sbagliato.

Reset: riscrivere la storia

Ci sono casi in cui questo non va bene. Per esempio, per errore ho fatto il commit di una password o di un dato che non doveva stare nel repository. Con un revert il dato sparisce dai commit futuri, ma resta nel commit che lo conteneva: se guardo la cronologia di animali.txt trovo ancora, a quell’altezza, la riga con il quarzo. Per tornare davvero indietro, modificando la cronologia, si usa non revert ma git reset, un comando estremamente potente e altrettanto delicato: è importante capire come funziona.

Introduco stavolta un errore nel file delle verdure, e faccio il commit unendo le opzioni -a e -m, cioè git add e git commit -m in un colpo solo (per i file già tracciati). È più veloce, ma la fretta porta ad errori:

git commit -am "modificato verdure"

Guardiamo la cronologia: c’è un commit “modificato verdure” che contiene l’errore. Voglio tornare alla situazione precedente, eliminandolo dalla cronologia. Faccio un reset indicando il commit al quale voglio tornare (qui il padre di HEAD):

git reset HEAD~1

Le modifiche tornano nell’area di lavoro (abbiamo ancora le banane fra le verdure), ma la cronologia è cambiata: il commit con l’errore è scomparso e l’ultimo commit è quello corretto. Ora correggo verdure.txt, salvo e faccio il commit.

Poi ci ripenso: il sedano proprio non mi va giù, voglio eliminare completamente anche questo dalla cronologia. Prima abbiamo visto che git reset col riferimento del commit rimette le modifiche nell’area di lavoro; se voglio eliminarle completamente uso –hard:

git reset --hard HEAD~1

Qui l’area di lavoro è pulita: nulla è stato rimesso nell’area di lavoro, il commit è scomparso, e con lui anche i dannati sedani.

Git reset ha diverse modalità. Abbiamo visto quella di default (mixed), con la soft fa cose simili e le approfondiremo più avanti, che riporta le modifiche nell’area di lavoro; e la modalità –hard, che scollega completamente il commit sbagliato e pulisce anche la cronologia, alterandola. Potrebbe essere quello che volete, ma potrebbe non esserlo: con –hard le modifiche non committate sono perse davvero.

Conclusione

In sintesi, per muoversi fra i commit: git stash accantona le modifiche del presente (git stash list le elenca, pop le riprende e le toglie dall’elenco, apply le riprende lasciandole, push -m dà loro un nome); git revert annulla un commit aggiungendone un altro, senza toccare la storia; git reset torna indietro riscrivendola, con l’opzione –hard che butta via anche le modifiche. Aprite la vostra Linux box, riprendete il vostro repository e sperimentate questi comandi per capire bene come funzionano.

Link e risorse

Sostieni il canale

PER SOSTENERE IL CANALE BASTANO UN LIKE E UN’ISCRIZIONE!
ma se vuoi offrirmi un caffè, mi aiuterà a rimanere sveglio mentre registro i video 😁☕
clicca qui per scoprire come https://buymeacoffee.com/thelinuxnerd

questi link sono affiliati Amazon, acquistando i libri che ti consiglio sostieni il canale!