Introduzione a Git e al versioning

Introduzione a Git e al versioning

Se non usi ancora un sistema di versioning, direi di usare i buoni propositi per il nuovo anno e fare del 2021 l'anno in cui inizierai a farlo, anche se non sviluppi in team. Vediamo cos’è, a cosa si applica e come iniziare ad usarlo subito.

Questo articolo è stato scritto molto tempo fa. Potrebbe essere stato superato dalla tecnologia.

Cosa significa fare versioning o meglio, gestire un sistema di controllo versione?

Quando si lavora a progetti digitali, capita spesso, per non dire sempre, di dover apportare modifiche, cancellazioni, aggiunte o voler testare funzionalità salvo poi voler tornare alla versione precedente. Per tenere traccia di queste modifiche, puoi fare come stai facendo adesso (commentare codice e replicarlo, creare un nuovo file rinominato o peggio perdendole per sempre sovrascrivendo lo stesso file) oppure puoi usare un sistema creato ad hoc per gestire le varie “release” di progetto: Git.

In questa piccola guida introduttiva lo useremo da riga di comando ma sappi che esistono anche diverse interfacce grafiche (GUI).

1. Installare Git

Per prima cosa vai sul sito di Git e scarica la versione per il tuo sistema operativo. Installalo e apri il terminale che partiamo con il nostro primo repository.

2. Creare un repository Git locale

Quando si crea un nuovo progetto sulla macchina locale utilizzando Git, si crea prima un nuovo repository (chiamato spesso, 'repo', in breve). Per semplificare, immagina un repository come una copia della cartella contenente il tuo progetto, salvata in quel preciso momento e con un commento assegnato.

Per iniziare, spostiamoci nella directory del nostro progetto. Per esempio, se hai una cartella Code sul tuo utente, farai qualcosa del tipo:

rslanzi> cd ~/Code
rslanzi> mkdir welcomegit
rslanzi> cd welcomegit

Per inizializzare un repository Git nella cartella, eseguire il comando git init:

rslanzi> git init
Inizializzato repository Git vuoto in /Users/rslanzi/Code/welcomegit/.git/

In questo caso la directory è vuota ma potresti anche prima creare la prima versione dei file e poi inizializzare la repo.

3. Aggiungere un nuovo file al repository

Procedere all'aggiunta di un nuovo file al progetto, nel nostro caso index.php:

rslanzi> touch index.php

Una volta aggiunti o modificati i file in una cartella contenente un repo, Git noterà che il file esiste all'interno del repo ma non ne terrà traccia a meno che non andiamo a dirglielo esplicitamente. Per vedere di quali file Git conosce l’esistenza e in che stato sono, si può usare il comando git status:

rslanzi> git status
Sul branch master

Non ci sono ancora commit

File non tracciati:
  (usa "git add file..." per includere l'elemento fra quelli di cui verrà eseguito il commit)
    index.php

non è stato aggiunto nulla al commit ma sono presenti file non tracciati (usa "git add" per tracciarli)

Conoscendo la terminologia, il messaggio di risposta è abbastanza chiaro:

  • Sul branch master - Cos’è un branch lo vedremo fra poco. In questo caso Git ci informa che ci troviamo su quello denominato master (nonché unico presente al momento)
  • Non ci sono ancora commit - Anche i commit li vedremo a breve. Introducendoli possiamo paragonarli ai singoli salvataggi delle modifiche.
  • File non tracciati - Sono i file presenti nella directory ma che non sono gestiti da Git.
  • Non è stato aggiunto nulla al commit: lo stage di lavoro non presenta file tracciati da Git ma rileva i file non tracciati appena visti.

Una delle parti più confuse quando si studia Git è il concetto dell'ambiente di stage e come si relaziona con un commit.

Un commit è una registrazione dei cambiamenti apportati dal commit precedente. Essenzialmente, tu fai dei cambiamenti al tuo repo (per esempio, aggiungendo un file o modificandone uno) e poi dici a Git di mettere quei cambiamenti in un commit.

I commit costituiscono l'essenza del tuo progetto e ti permettono di saltare dallo stato attuale di un progetto ad qualsiasi altro commit precedente.

Quindi, come si fa a dire a Git quali file inserire in un commit?

Qui è dove entra in gioco l'ambiente di staging. Come hai appena, quando si apportano modifiche al repository, Git si accorge che un file è cambiato ma non fa nulla. È necessario prima aggiungerlo all'ambiente di staging.

4. Aggiungere un file all'ambiente di staging

Aggiungere un file all'ambiente di staging usando il comando git add nome-file oppure git add . per aggiungere tutti i file non presenti.

rslanzi> git add index.php

Se si esegue nuovamente il comando git status, si vedrà che Git ha aggiunto il file all'ambiente di staging (notare la riga "Modifiche di cui verrà eseguito il commit").

rslanzi> git status
Sul branch master

Non ci sono ancora commit

Modifiche di cui verrà eseguito il commit:
  (usa "git rm --cached file..." per rimuovere gli elementi dall'area di staging)
    nuovo file:             index.php

Come capite dalla risposta, il file non è ancora stato aggiunto ad un commit, ma è pronto per esserlo.

5. Creare un commit

È il momento di creare il tuo primo commit!
Esegui il comando git commit -m "Il tuo messaggio sul commit".

rslanzi> git commit -m "Initial commit"
[master (commit radice) cab74a5] Initial commit
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 index.php

Il messaggio da aggiungere al commit dovrebbe essere identificativo delle modifiche che sono state apportate tipo se è stata introdotta una nuova funzionalità, se è stato corretto un bug o un typo. Non mettere un messaggio senza senso. I commit risiedono per sempre in un repository (tecnicamente puoi cancellarli se ne hai davvero, davvero bisogno, ma è sconsigliato), quindi se lasci una spiegazione chiara dei tuoi cambiamenti può essere estremamente utile per chi ci dovrà mettere le mani in seguito (incluso il te futuro) che magari cercheranno di capire perché qualche cambiamento è stato fatto.

6. Creare un nuovo branch

Ora che hai creato un nuovo commit, proviamo qualcosa di un po' più avanzato.

Diciamo che vuoi sviluppare una nuova funzionalità, ma sei preoccupato di apportare modifiche al progetto principale durante lo sviluppo della funzionalità oppure lo sviluppo sarà particolarmente lungo e magari dovrai contemporaneamente tenere aggiornato il progetto iniziale. Qui è dove entrano in gioco i branch (ramo, in italiano) di Git.

I branch permettono di muoversi avanti e indietro tra gli "stati" di un progetto. Per esempio, se vuoi aggiungere una nuova funzionalità alla tua web app, puoi creare un nuovo branch solo per quella, senza influenzare la parte già funzionante, e magari in uso, del progetto. Una volta che hai finito e testato che tutto funziona correttamente, puoi unire (merge) le tue modifiche del branch della funzionalità nel branch primario. Quando crei un nuovo branch, Git tiene traccia del commit di origine del nuovo branch, in modo da conoscere la storia dietro tutti i file.

Vediamo il codice. Diciamo che sei sul branch primario (master) e vuoi creare un nuovo branch per sviluppare la tua nuova funzionalità. Ecco cosa farai:

rslanzi> git branch new-feature
rslanzi> git checkout new-feature
Si è passati al branch 'new-feature'

Oppure, in un comando unico:

rslanzi> git checkout -b new-feature
Si è passati a un nuovo branch 'new-feature'

Questo comando, il parametro -b in realtà, creerà automaticamente un nuovo branch chiamato new-feature e poi, grazie al comando checkout, passerà su di esso spostandosi quindi dal branch master.

Dopo aver eseguito uno dei comando di cui sopra, puoi utilizzare il comando git branch per vedere tutti i branch esistenti e capire in quale stai lavorando (è contrassegnato da un asterisco):

rslanzi> git branch
  master
* new-feature

Creiamo un nuovo file nel nuovo branch, aggiungiamolo allo stage e committiamolo. Fatto questo, torniamo sul branch master e vediamo la situazione con git status e i contenuti presenti nella directory con un bel ls.

rslanzi> touch new-file.php
rslanzi> git add new-file.php
rslanzi> git commit -m "New feature released"
[new-feature deb42d2] New feature released
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 new-file.php
rslanzi> git checkout master
rslanzi> git status
Sul branch master
non c'è nulla di cui eseguire il commit, l'albero di lavoro è pulito
rslanzi> ls
index.php

Come puoi vedere, lo stage è pulito e, nella directory, non c’è traccia del nostro nuovo file. Puoi modificare il branch principale con altri commit e il vostro nuovo branch non vedrà nessuno di questi cambiamenti fino a quando non unirete questi cambiamenti nel vostro nuovo branch.

Una nota sui nomi dei branch

Per impostazione predefinita, il primo branch di ogni repository Git è chiamato `master`. Dal 2020 alcune associazioni antirazziste hanno promosso di cambiare nomenclatura per non ricordare il periodo degli schiavi (master-slave, sarebbe padrone e schiavo in italiano). In diversi progetti potreste trovare quindi il branch principale con altri nomi, quali main (il nuovo nome scelto dalla comunità Git) primary, default etc. Se vuoi saperne di più, sul sito di GitHub c’è una sezione apposita: https://github.com/github/renaming

7. Unire due branch

Una volta che riterrai sicuro integrare la tua nuova funzionalità nel branch principale, ti basterà eseguire il merge. Come? Spostati sul branch principale ed esegui il seguente comando:

rslanzi> git merge new-feature
Aggiornamento di cab74a5..deb42d2
Fast-forward
 new-file.php | 0
 1 file changed, 0 insertions(+), 0 deletions(-)

A questo punto, se vai a vedere i file presenti nella directory, vedrai che c’è anche il nuovo file.

Ovviamente questo è un caso semplice dove tutto è filato liscio. Sappiamo tutti che nella vita vera qualcosa invece può andare storto… tipo che due o più file siano in conflitto, ovvero che su entrambi i branch siano avvenute modifiche. Ma lo vedremo più avanti.

8. Eliminare un branch

Visto che il merge è andato a buon fine e che la nuova funzionalità è conclusa, procediamo ad eliminare il branch new-feature per tenere ordinato il nostro repository con il seguente comando:

rslanzi> git branch -d new-feature
Branch new-feature eliminato (era deb42d2).

9. Annullare un commit

A volte può sfuggire un errore oppure dover cambiare rotta nel progetto e dover tornare indietro. Git ci permette di farlo in due modi a seconda di cosa vogliamo ottenere e il comando da lanciare è sempre git reset ma con o senza il flag --hard. Qual è la differenza? Git reset annulla i commit successivi a quello specificato ma conserva i cambiamenti locali, mentre con il flag hard elimina anche i cambiamenti locali fino al commit specificato.

rslanzi> git reset cab74a57f604caaf93296dc79c04186693bc3b92
rslanzi> git reset --hard cab74a57f604caaf93296dc79c04186693bc3b92

10. Esploriamo l'evoluzione del progetto

Quando vuoi capire cosa è successo ad un repository nel tempo, puoi andare ad eseguire dei comandi ad hoc, ad esempio, con il comando log ci verrà visualizzato lo storico dei commit per il branch in cui siamo:

rslanzi> git log

Mentre se vogliamo vedere le differenze fra due branch, andremo ad eseguire il comando diff seguito dai nomi dei due branch:

rslanzi> git diff master new-feature

Infine, se vogliamo vedere il dettaglio dei cambiamenti di uno specifico commit, useremo il comando show seguito dall’id del commit che ci interessa:

rslanzi> git show cab74a57f604caaf93296dc79c04186693bc3b92

Collaborare tramite Git

Finora abbiamo visto alcuni comandi per lavorare su di un repository locale ma uno dei vantaggi indiscussi di usare il versioning è quello di collaborare contemporaneamente con i colleghi. Per fare questo occorre copiare il repository su di un server accessibile a tutto il team. Fortunatamente esistono diversi siti che possono ospitare, anche gratuitamente, il nostro codice. I più famosi sono GitHub (gratuito per i progetti pubblici) e BitBucket (gratuito per i progetti privati) ma esistono anche GitLab e dei software da piazzare sul vostro server tipo Gogs e Gitea.

In questa guida cercherò di essere il più neutrale possibile, vi rimando alla documentazione specifica del servizio per le configurazioni di fino.

Una volta creato un account sul servizio scelto per ospitare il nostro repository, create un nuovo repository specificandone il nome e compilando i dati che ti verranno richiesti. Se ti viene chiesto se lo vuoi crearlo da zero o se si vuole aggiungere un repo locale, seleziona questa seconda strada, visto che abbiamo già il nostro bel progetto di test in locale. Fatto questo passaggio, dovresti avere un percorso per il tuo repository remoto, nel mio caso è [email protected]:rslanzi/welcomegit.git

Facciamolo sapere al nostro repository locale con il comando git remote add

rslanzi> git remote add origin [email protected]:rslanzi/welcomegit.git

Carichiamo le modifiche sul server remoto con git push:

rslanzi> git push -u origin master
Enumerazione degli oggetti in corso: 5, fatto.
Conteggio degli oggetti in corso: 100% (5/5), fatto.
Compressione delta in corso, uso fino a 8 thread
Compressione oggetti in corso: 100% (3/3), fatto.
Scrittura degli oggetti in corso: 100% (5/5), 441 byte | 220.00 KiB/s, fatto.
5 oggetti totali (0 delta), 0 riutilizzati (0 delta), 0 riutilizzati nel file pack
To bitbucket.org:rslanzi/welcome.git
 * [new branch]      master -> master
Branch 'master' impostato per tracciare il branch remoto 'master' da 'origin'.

Solo per il primo push di ogni branch occorre specificare l’upstream (l’opzione -u). Con questo flag andiamo ad istruire il server remoto di quale branch remoto corrisponde al branch locale attivo. Dal secondo push in poi, potremo usare semplicemente git push.

Finito il push, collegandoti al tuo account sul server, vedrai l’elenco dei file caricati che in questo modo saranno a disposizione dei tuoi colleghi.

PS: la prima volta che accederai al server remoto, Git dovrebbe chiederti di effettuare il login con il tuo nome utente e la tua password o tramite la tua chiave ssh.

Scarichiamo le modifiche dal server remoto con git pull:

Quando qualche collega carica sul server delle modifiche sarà necessario scaricarle in locale per avere la codebase aggiornata. Per farlo, esegui il comando git pull.

rslanzi> git pull

Cloniamo un nuovo progetto in locale.

Se un tuo collega ha creato un nuovo repository e vuoi iniziare a lavorarci in locale, dovrai prima clonarlo con il comando git clone. Per farlo devi avere ovviamente i permessi per accedervi.

rslanzi> git clone [email protected]:rslanzi/new-repository.git

Con questo comando creerai una nuova directory new-repository nella cartella dalla quale hai lanciato il comando. Se vuoi specificare un altro nome o salvare i file direttamente nella cartella in cui sei, usa il . (punto) come nome del percorso di destinazione:

rslanzi> git clone [email protected]:rslanzi/new-repository.git welcome2
rslanzi> git clone [email protected]:rslanzi/new-repository.git .

Alcuni concetti un filo più avanzati

Questa guida non vuole essere esaustiva ma fornire le informazioni necessarie per destreggiarsi nel lavoro quotidiano con Git. Quindi almeno alcuni concetti un filo più complessi vanno spiegati.

Ignorare alcuni file dal versioning e soprattutto dalla condivisione

Abbiamo visto come aggiungere file ai commit ma non abbiamo visto come farlo selettivamente. Nel nostro progetto ci saranno sicuramente file contenenti dati riservati quali password e dati di accesso che non dobbiamo condividere con i nostri colleghi o dipendenze che non ha senso versionare in quanto facenti riferimento ad altri repository come la cartella vendor se usi composer. Come fare quindi? Creando un file .gitignore nella root del progetto.

Dentro a questo file andremo ad elencare i file o le directory da ignorare, mettendo un percorso per riga. Vediamo un esempio plausibile:

/node_modules
/public/storage
/storage/*.key
/vendor
/.idea
.env
.phpunit.result.cache
.php_cs.cache
npm-debug.log

È buona norma crearlo all’inizio del progetto e aggiornarlo prima di fare un commit ma, si sa, la perfezione non esiste, quindi, cosa fare nel caso si voglia (git)ignorare un file già committato? Innanzitutto occorre prestare molta attenzione e committare i file in staging per non perderli e poi procedere con i seguenti comandi:

rslanzi> git rm -r --cached .

Questo comando rimuove ricorsivamente (-r) tutti i file (.) dallo storico ma li mantiene in locale (--cached). Rimossi i file, andiamo a rimetterli nello stage di lavoro e a committarli nuovamente.

rslanzi> git add .
rslanzi> git commit -m ".gitignore updated"

Merge con conflitti

Abbiamo visto come fare un merge semplice di due branch ma quando si va a farlo su un progetto complesso gestito a più mani difficilmente non si troveranno dei conflitti. Cos’è un conflitto? Una modifica allo stesso spezzone di codice apportata in due branch diversi. Proviamo a creare un esempio con un conflitto in modo che sia tutto più chiaro.

Nel nostro repo welcomegit andiamo a creare un nuovo branch, aggiorniamo il file index.php aggiungendo una riga di testo, committiamo e torniamo nel master e, anche qui, aggiungiamo del testo diverso al file index.php e committiamo. Fatto questo procediamo al merge e vediamo cosa succede. In codice:

rslanzi> git checkout -b test-merge
rslanzi> echo "<?php function test() { echo 'test in test-merge branch'; }" >> index.php
rslanzi> git add .
rslanzi> git commit -m “Index updated”
rslanzi> git checkout master
rslanzi> echo "<?php function test() { echo 'test in master branch'; }" >> index.php
rslanzi> git commit -m “Index updated”
rslanzi> git merge test-merge
Merge automatico di index.php in corso
CONFLITTO (contenuto): conflitto di merge in index.php
Merge automatico fallito; risolvi i conflitti ed esegui il commit
del risultato.

Ed eccoci qui, abbiamo un conflitto. Che fare? Git ci viene in aiuto. Apri il file index.php con il tuo editor, ci troverai questo:

<<<<<<< HEAD
<?php function test() { echo 'test in master branch'; }
=======
<?php function test() { echo 'test in test-merge branch'; }
>>>>>>> test-merge

Fra <<<<<<< HEAD e ======= c’è il codice del branch attuale (master) mentre fra ======= e >>>>>>> c’è il codice contenuto nel branch indicato alla fine della riga (test-merge).

Cosa devi fare ora? Decidere quale versione tenere, risalvare il file, aggiungerlo allo stage e committare. Conflitto risolto!

Bene, ora che sai aggiungere file allo stage, committarli, creare branch, fare merge e lavorare con repository remoti, non hai proprio più scuse per non usare Git in ogni tuo progetto e se hai dubbi, scrivili nei commenti. Buon lavoro!

Ultima modifica: mercoledì 19 gennaio 2022

Cosa ne pensate:

Cerbaster

Cerbaster 3 anni fa

Dopo tre giorni che leggo su come utilizzare git ho finalmente trovato la tua guida esaustiva e che mi ha aiutato a comprendere meglio questo utilissimo strumento di versioning.

Rispondi
Riccardo Slanzi

Riccardo Slanzi 3 anni fa

Grazie del feedback @Cerbaster. Mi fa molto piacere che ti sia stata utile. E se da domani usandolo ti imbatti in qualcosa di poco chiaro, chiedi pure!

Rispondi
fava sergio

fava sergio 2 anni fa

grazie per il tuo lavoro. volevo chiedere, mi ritrovo 10000 modifiche riguardo ad una cartella sulla quale non ho nemmeno avviato il controllo di versione, come mai ? mi puoi aiutare ?

Rispondi
Riccardo Slanzi

Riccardo Slanzi 2 anni fa

Ciao Sergio, grazie del commento. Cosa intendi che hai 10 mila modifiche senza aver avviato il versionamento? Se vedi le modifiche, mi sa che il versionamento c'è, no? Forse l'hai attivato su una cartella di livello superiore?

Rispondi

Aggiungi il tuo commento

Iscriviti alla mia newsletter

Resterai informato sugli ultimi post, appena verranno pubblicati