Da bravi appassionati di computer quali siamo, o più specificatamente da utenti linux, ci capita quasi tutti i giorni di avere a che fare con progetti che coinvolgono file vari. File che vengono modificati più volte mentre sperimentiamo qualcosa: una configurazione che dovrebbe migliorare la vita della batteria, un linguaggio di programmazione di cui abbiamo sentito parlare, quella modifica al nostro sito personale.

Mentre facciamo le nostre prove, in qualche maniera teniamo traccia delle varie modifiche, magari salvando diversi file con nomi tipo "prova1", "prova2", "finito", "funziona", etc..

Ma dopo due giorni trovare qual'era quello che funzionava diventa difficile: era "finito"? o "funziona"? Magari funziona funzionava ma non era finito.. o finito era finito ma non funzionava? Ouf!

Ci vorrebbe qualcosa che ci aiuti a tenere traccia dei nostri lavori!

GIT

Ed è qui che il protagonista di questo articolo entra in scena.

Dice la homepage del sito git-scm.com:

Git è un sistma di cotrollo delle versioni distribuito, libero e opensource studiato per gestire tutto, dai progetti piccoli a quelli molto grandi, velocemente e con efficienza

A noi interessa proprio gestire piccoli progetti! Guarda te il caso!

GIT funziona registrando in un "magazzino" ("repository") le modifiche fatte ai file su cui si sta lavorando.

E' possibile aggiungere un commento che descrive le modifiche, oltre che raggruppare più file in un'unica "registrazione" ("commit").

Ogni commit riporta la data, l'autore, la descrizione, l'elenco dei file registrati ed è collegato al precedente, costruendo così un log ordinato delle modifiche.

Iniziamo

Per prima cosa dobbiamo creare il repository per il nostro progetto. Ogni progetto avrà il suo repository.

Creiamo la cartella dove lavoreremo e ci entriamo:

~$ mkdir mioprogetto
~$ cd mioprogetto

Ora creiamo il repository*:

~/mioprogetto$ git init

Nota: non è necessario partire con una cartella vuota. Se abbiamo già una cartella in cui stiamo lavorando a un progetto, possiamo crearci un repository senza problemi.

Teniamo traccia delle modifiche

Ora che abbiamo il nostro repository è tempo di tenere traccia delle modifiche.

GIT non salva automaticamente le varie versioni, ma siamo noi a dovergli dire cosa e quando salvare.

Per gli esempi a seguire, diciamo che il nostro progetto consiste nella scrittura di un racconto. Nella cartella creeremo un nuovo file per ogni capitolo del nostro racconto.

Cominciamo dall'inizio. "Capitolo 1". Creiamo il file "capitolo_1.txt" con il seguente contenuto:

Capitolo 1

Era una notte buia e tempestosa.

Un buon inizio, d'effetto! Salviamo i nostri progressi fin qui.

Per salvare le modifiche i passi da eseguire sono due:

  1. Indicare quali file vogliamo che vengano presi in considerazione
  2. Creare un nuovo commit inserendo una descrizione delle modifiche

In questo caso vogliamo salvare le modifiche fatte fino a qui a "capitolo_1.txt"

~/mioprogetto$ git add capitolo_1.txt

Se non ci saranno errori, il comando non scriverà nessun messaggio di risposta.

Ora creiamo il commit

~/mioprogetto$ git commit -m "Inizio del capitolo uno"

git commit risponderà

[master (root-commit) a8a3977] Inizio del capitolo uno
 1 file changed, 4 insertions(+)
 create mode 100644 capitolo_1.txt

Riga per riga:

  • id del commit creato (a8a3977, e siccome è il primo commit aggiunge 'root-commit'), con il messaggio che abbiamo inserito
  • un riassunto delle modifiche: abbiamo modificato un file, e in totale abbiamo fatto quattro aggiunte (le quattro righe del file, in questo caso)
  • la lista dei file aggiunti o rimossi: abbiamo creato un file chiamato "capitolo_1.txt" con permessi 644

"creato" in questo caso si intende che è la prima volta che compare nel repository.

Abbiamo salvato la nostra prima versione del libro!

Ora sarà il caso di scrivere qualcosa in più...

Aggiungiamo un po' di storia al capitolo uno:

Capitolo 1

Era una notte buia e tempestosa. Possenti tuoni scuotevano la terra ed improvvisi lampi squarciavano le tenebre.

Evocativo! Passiamo al secondo capitolo. Creiamo il file "capitolo_2.txt" e scriviamo

Capitolo 2

Tempo stabile e brezza leggera per tutta la settimana. Possibili rovesci giovedì.

(E' un racconto meteorologico...)

Ora salviamo i nostri progressi letterari. Ricordiamoci che abbiamo modificato il capitolo uno e creato il capitolo due:

~/mioprogetto$ git add capitolo_1.txt capitolo_2.txt
~/mioprogetto$ git commit -m "Finito capitolo uno, iniziato capitolo due"

ed ecco cosa GIT stampa alla creazione del commit:

[master bf73bf7] Finito capitolo uno, iniziato capitolo due
 2 files changed, 5 insertions(+), 2 deletions(-)
 create mode 100644 capitolo_2.txt

Abbiamo modificato due file, aggiunto 5 rige, rimosse 2 righe (vedremo più avanti perchè)

Conoscere la storia

Ora abbiamo il nostro progetto con alcune modifiche memorizzate da GIT.

Vediamo un elenco delle modiche fatte fin qui. Chiediamo il log:

~/mioprogetto$ git log

ed ecco che GIT stampa l'elenco:

commit bf73bf706df350de5941e2e46ad01fe6896eac1b (HEAD -> master)
Author: Fabrix <fabrix@piccettino>
Date:   Wed Sep 12 21:19:48 2018 +0200

    Finito capitolo uno, iniziato capitolo due

commit a8a3977490885cb80d29429c4d8ed68e3307cb88
Author: Fabrix <fabrix@piccettino>
Date:   Wed Sep 12 21:02:46 2018 +0200

    Inizio del capitolo uno

La lista comincia dall'ultimo andando indietro. Riporta l'id del commit, l'autore, la data in cui è stato registrato e il messaggio.

L'id del commit è un hash e quando dobbiamo scriverlo, magari un un comando, possiamo accorciarlo.

Abbiamo visto prima un esempio di id del commit abbreviato nell'output risultato dalla creazione di un nuovo commit.

Possiamo vedere le differenze tra due commit usando git diff [id commit vecchio] [id commit nuovo]:

~/mioprogetto$ git diff a8a3977 bf73bf7

questo è l'output che otteremo:

diff --git a/capitolo_1.txt b/capitolo_1.txt
index f54fb9f..867dbdf 100644
--- a/capitolo_1.txt
+++ b/capitolo_1.txt
@@ -1,4 +1,4 @@
 Capitolo 1

-Era una notte buia e tempestosa.
-
+Era una notte buia e tempestosa. Possenti tuoni scuotevano la terra
+ed improvvisi lampi squarciavano le tenebre.

diff --git a/capitolo_2.txt b/capitolo_2.txt
new file mode 100644
index 0000000..ed299d1
--- /dev/null
+++ b/capitolo_2.txt
@@ -0,0 +1,3 @@
+Capitolo 2
+
+Tempo stabile e brezza leggera per tutta la settimana. Possibili rovesci giovedì.

Per ogni file abbiamo una intestazione che riporta il nome e altre informazioni sul file che si sta visualizzando, seguita dalle effettive modifiche.

Le righe del file che iniziano con - sono le righe della vecchia versione, quelle con + sono della nuova.

Possiamo quindi vedere come il capitolo uno inizialmente conteneva, dopo il titolo, una riga di testo e una vuota, che sono state sostituite dalla versione nuova.

Il capitolo due non esisteva nella versione precedente, si vede che nell'intestazione dice "new file".

Contiamo le righe con - e le righe con + ed ecco le due righe rimosse e le cinque aggiunte che abbiamo notato prima.

Chi fa, sbaglia. GIT lo aiuta.

In un momento di ispirazione abbiamo continuato il capitolo due, ma una volta riletto il nostro racconto ci accorgiamo che non va bene: è tutto sbagliato! Era molto meglio prima!

In questo caso non abbiamo ancora registrato le modifiche nel repository.

Per prima cosa, vediamo cosa abbiamo modificato:

~/mioprogetto$ git status

Il comando stampa la situazione del nostro progetto rispetto all'ultima versione registrata nel repository:

On branch master
Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git checkout -- <file>..." to discard changes in working directory)

    modified:   capitolo_2.txt

vediamo chiaramente che abbiamo modificato il capitolo due.

Ma quali modifiche abbiamo fatto al capitolo due? git diff senza parametri ci mostra le modifiche tra i file che abbiamo su disco e l'utima versione memorizzata.

 ~/mioprogetto$ git diff

ci dice:

diff --git a/capitolo_2.txt b/capitolo_2.txt
index ed299d1..e349b77 100644
--- a/capitolo_2.txt
+++ b/capitolo_2.txt
@@ -1,3 +1,4 @@
 Capitolo 2

 Tempo stabile e brezza leggera per tutta la settimana. Possibili rovesci giovedì.
+Notti fresche, con bassa umidità.

No, proprio non ci siamo. Quella frase aggiunta non va bene.

Potremmo semplicemente aprire il nostro file nell'editor e cancellare la riga. Immaginiamo pero' di aver modificato molti più capitoli, magari con piccole modifiche. Diventa un lavoro tedioso ed incline ad errori.

Non sarebbe meglio dire a GIT "ripristina i file modificati allo stato in cui erano nell'ultima versione"? Oh, si puo'! (e git status prima ci ha dato un idea di come fare)

Usiamo git checkout -- [nome del/dei file da ripristinare]:

~/mioprogetto$ git checkout -- capitolo_2.txt

anche in questo caso, nessun messaggio significa "tutto bene!"

Ricontrolliamo lo stato del nostro progetto:

~/mioprogetto$ git status
On branch master
nothing to commit, working tree clean

Ottimo! Le modifiche sono state annullate e siamo tornati all'ultimo stato salvato.

La macchina del tempo

... dissolvenza a nero, titolo "qualche giorno dopo"

E' passato del tempo e siamo andati avanti con il nostro progetto. Abbiamo creato un paio di nuovi capitoli, e aggiunto qualche commit al nostro repository:

~/mioprogetto$ git log
commit 5b029ffc3d26d560480390a3ec4be47bb64c6273 (HEAD -> master)
Author: Fabrix <fabrix@piccettino>
Date:   Tue Sep 18 22:44:58 2018 +0200

    Aggiunto il quarto capitolo

commit 1efa91e7cd6bc71ed83cff5bb45b44847719918d
Author: Fabrix <fabrix@piccettino>
Date:   Sat Sep 15 18:42:35 2018 +0200

    modificato capitolo 2 perchè si

commit e0621cf58ad57e54c541edc6abdae014a4f240c5
Author: Fabrix <fabrix@piccettino>
Date:   Fri Sep 14 21:42:09 2018 +0200

    Modificato capitolo 1 dietro consiglio di un faggio

commit b1fa88c6790fa172cb28be4d1d5111059fdabe24
Author: Fabrix <fabrix@piccettino>
Date:   Fri Sep 14 20:41:37 2018 +0200

    aggiuto il capitolo 3

commit bf73bf706df350de5941e2e46ad01fe6896eac1b
Author: Fabrix <fabrix@piccettino>
Date:   Wed Sep 12 21:19:48 2018 +0200

    Finito capitolo uno, iniziato capitolo due

commit a8a3977490885cb80d29429c4d8ed68e3307cb88
Author: Fabrix <fabrix@piccettino>
Date:   Wed Sep 12 21:02:46 2018 +0200

    Inizio del capitolo uno

Vediamo cosa è cambiato rispetto al commit bf73bf7 "Finito capitolo uno, iniziato capitolo due". Invece dell'id del commit più nuovo, scriviamo HEAD, che è un nome che si riferisce all'ultimo commit salvato nel repository. L'abbiamo visto sopra nell'output di git log.

~/mioprogetto$ git diff bf73bf7 HEAD
diff --git a/capitolo_1.txt b/capitolo_1.txt
index 867dbdf..fb21a74 100644
--- a/capitolo_1.txt
+++ b/capitolo_1.txt
@@ -1,4 +1,4 @@
 Capitolo 1

-Era una notte buia e tempestosa. Possenti tuoni scuotevano la terra
-ed improvvisi lampi squarciavano le tenebre.
+Era una serata buia e silenziosa. Possenti suoni scuotevano la terra
+ed improvvisi campi squarciavano le tenebrose oscurità.
diff --git a/capitolo_2.txt b/capitolo_2.txt
index ed299d1..292a39f 100644
--- a/capitolo_2.txt
+++ b/capitolo_2.txt
@@ -1,3 +1,4 @@
 Capitolo 2

 Tempo stabile e brezza leggera per tutta la settimana. Possibili rovesci giovedì.
+Le uscite di sicurezza sono posizionate tutte alla nostra destra.
diff --git a/capitolo_3.txt b/capitolo_3.txt
new file mode 100644
index 0000000..728b0b0
--- /dev/null
+++ b/capitolo_3.txt
@@ -0,0 +1,5 @@
+Capitolo 3
+
+Il telefono squillò che era da poco passato mezzogiorno.
+Il cuoco tolse i guanti, ripose la mazza, aggiustò il farfallino e uscì sul ponte.
+
diff --git a/capitolo_4.txt b/capitolo_4.txt
new file mode 100644
index 0000000..1a8beeb
--- /dev/null
+++ b/capitolo_4.txt
@@ -0,0 +1,5 @@
+Capitolo 4
+
+- Corri! - disse il pilota - Ruota il controllo di fase del campo di quadratura del cerchio verso sinistra, o è la fine!
+
+Il Conte Mascetti osservava incredulo la scena.

Guardando le modiche (e rileggendo il racconto) ci rendiamo conto che le modifiche inserite nel capitolo uno sono assolutamente sbagliate!

Anche in questo caso vogliamo riportare il testo alla situazione precedente.

Non possiamo usare git checkout come prima, perchè le modifiche sono già state inserite nel repository. Dobbiamo creare un nuovo commit che riporti le cose allo stato corretto.

Fortunatamente GIT ci aiuta anche in questo.

Prima di tutto dobbiamo sapere qual'è il commit che registra le modifiche che vogliamo annullare. E' chiaro leggendo il log che si tratta del commit la cui descrizione dice "Modificato il capitolo 1...", id e0621cf.

Con questa informazione possiamo usare il comando git revert [id commit]. Questo comando crea un nuovo commit che annulla le modifiche apportate dal commit specificato nella riga di comando.

Lanciamo

~/mioprogetto$ git revert e0621cf

GIT aprirà l'editor di testo a console (nano, vim, o altro a seconda del vostro sistema) dove sarà possibile rivedere il messaggio che verrà inserito nel commit che sta per essere creato.

Il testo predefinito è abbastanza esplicativo:

Revert "Modificato capitolo 1 dietro consiglio di un faggio"

This reverts commit e0621cf58ad57e54c541edc6abdae014a4f240c5.

# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# On branch master
# Changes to be committed:
#       modified:   capitolo_1.txt
#

Tutte le righe che iniziano con # verranno ignorate. Salviamo e usciamo dall'editor per confermare il messaggio di commit.

ed ecco il risultato:

[master b8c27d4] Revert "Modificato capitolo 1 dietro consiglio di un faggio"
 1 file changed, 2 insertions(+), 2 deletions(-)

Vediamo il contenuto di capitolo_1.txt:

Capitolo 1

Era una notte buia e tempestosa. Possenti tuoni scuotevano la terra ed improvvisi lampi squarciavano le tenebre.

Ottimo!

Vediamo il log con git log:

commit b8c27d4fcbaed8656313297224cdebd5a4f4a943 (HEAD -> master)
Author: Fabrix <fabrix@piccettino>
Date:   Wed  18 18:02:41 2018 +0200

    Revert "Modificato capitolo 1 dietro consiglio di un faggio"

    This reverts commit e0621cf58ad57e54c541edc6abdae014a4f240c5.

commit 5b029ffc3d26d560480390a3ec4be47bb64c6273
Author: Fabrix <fabrix@piccettino>
Date:   Tue Sep 18 22:44:58 2018 +0200

    Aggiunto il quarto capitolo

commit 1efa91e7cd6bc71ed83cff5bb45b44847719918d
Author: Fabrix <fabrix@piccettino>
Date:   Sat Sep 15 18:42:35 2018 +0200

    modificato capitolo 2 perchè si

commit e0621cf58ad57e54c541edc6abdae014a4f240c5
Author: Fabrix <fabrix@piccettino>
Date:   Fri Sep 14 21:42:09 2018 +0200

    Modificato capitolo 1 dietro consiglio di un faggio

[...]

(ho tagliato l'output, per brevità)

Ecco il nuovo commit che annulla le modifiche del commit e0621cf!

Conlusioni

Gli esempi fatti in questo articolo sono ovviamente poco seri e costruiti. Si tratta comunque di situazioni reali in cui capita di trovarsi mentre si porta avanti un progetto: si fanno delle prove, si modificano dei file per poi accorgersi che ci si è sbagliati, e putroppo la funzione di "annulla" del nostro editor è limitata.

Inserendo man mano le modifiche in GIT è possibile non solo avere uno storico di quello che si è fatto, ma anche recuperare cose cancellate o modificate.

Oltre alla modifica dei testi, GIT tiene traccia anche dei file cancellati, e se serve sarà possibile recuperarli.

E' possibile anche tenere traccia di diverse versioni del nostro progetto "in parallelo", magari per testare diversi approcci, e passare da una all'altra con facilità.

Vi invito a leggere la documentazione e seguire qualche tutorial, partendo dal sito ufficiale https://git-scm.com/ .