Git non offre funzioni di “undo” simili a quello di programmi consumer, ma offre comunque diversi comandi che permettono di tornare sui propri passi e gestire la sequenza di commit nella history come se fosse una macchina del tempo.
Il comando Git checkout in Git
Sappiamo che Git memorizza una serie di snapshot di un progetto chiamati commit, ogni commit ha un suo identificativo e punta al commit precedente, in modo da ricostruire l’intera cronologia. Sappiamo anche che Git permette di avere una o più “linee temporali”, chiamate branch, che si diramano eventualmente a partire da un determinato commit comune.
Prendiamo il caso di un repository con un singolo branch, chiamato main, su cui abbiamo eseguito alcuni commit.
[17452bb93]<---[88adbcdf6]<---[fe78029f1]<---[6d9c22a3b]::{main} | | HEAD
In questa situazione Git mantiene un riferimento simbolico chiamato HEAD che dice cosa sta mostrando la nostra working copy. Tipicamente, HEAD punta a un branch (ovvero alla sua “tip”).
Il comando git checkout in Git permette di decidere cosa estrarre nella working copy, sia esso un branch, un tag, o un altro riferimento valido, come per esempio un commit.
In questo modo è possibile recuperare nella propria working copy uno specifico snapshot del progetto, tornando quindi “indietro nel tempo” al momento in cui quello snapshot è stato salvato.
Nel momento in cui si effettua il git checkout di uno specifico commit o tag, Git mostra un avviso che potrebbe inizialmente spaventare:
$ git checkout 88adbcdf6 Note: switching to '88adbcdf6'. You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by switching back to a branch. If you want to create a new branch to retain commits you create, you may do so (now or later) by using -c with the switch command. Example: git switch -c <new-branch-name> Or undo this operation with: git switch - Turn off this advice by setting config variable advice.detachedHead to false HEAD is now at 88adbcdf6 Change behavior In pratica, rispetto al grafico precedente, la situazione è cambiata così: [17452bb93]<---[88adbcdf6]<---[fe78029f1]<---[6d9c22a3b]::{main} | | HEAD
Poiché la HEAD punta direttamente a un commit, Git avvisa che la working copy è in uno stato detached. In pratica, quindi, ogni eventuale commit successivo non verrà aggiunto al branch main. Il messaggio indica come, eventualmente, creare un nuovo branch o tornare al branch di partenza tramite il comando git switch.
È comunque possibile utilizzare nuovamente il comando git checkout per tornare al branch iniziale:
$ git checkout main Previous HEAD position was 88adbcdf6 Change behavior Switched to branch 'master' Your branch is up to date with 'origin/master'. Nel caso in cui avessimo creato dei commit mentre ci si trovava nello stato detached e fossimo poi tornati al branch main, avremmo [800e2aa6]<---[fae892ce] / [17452bb93]<---[88adbcdf6]<---[fe78029f1]<---[6d9c22a3b]::{main} | | HEAD
In questa situazione, non è stato creato alcun riferimento al commit fae892ce (cioè non è stato creato un nuovo branch) e Git provvederà a rimuovere tale commit (e per estensione anche il commit 800e2aa6) tramite suoi processi di routine.
Riassumendo, possiamo quindi dire che git checkout:
- serve principalmente per indicare cosa vogliamo “caricare” nella nostra working copy
- tipicamente, viene usato per indicare il branch che vogliamo “caricare” nella working copy (in questo caso i commit successivi sono agganciati al branch attuale): git checkout <NOME_BRANCH>
- può essere utilizzato per indicare uno specifico commit, tramite il suo id o un tag, da “caricare” nella working copy (in questo caso si entra nella modalità sganciata da ogni branch): git checkout <COMMIT_O_TAG>
Vedremo, in un capitolo successivo, come lavorare creando branch per separare gli sviluppi futuri, in particolare come “spostare” commit da un branch a un altro.