Tutorial > Gestire le versioni con Git

Gestire le versioni con Git

Pubblicato il: 07 gennaio 2020

GIT Sviluppo Version Control

Git è uno dei software di controllo versione distribuito più utilizzato fra gli sviluppatori su Linux per facilitare la coordinazione su uno stesso progetto. 

Se non conosci Git ti consigliamo di leggere la nostra guida introduttiva sulla installazione e i primi passi con Git. Finora ne sono state mostrate solo le funzioni basilari, come la preparazione dell’area di lavoro e la creazione delle varie repository, tramite “stage” dei file e “commit” delle modifiche, evidenziando anche la possibilità di rimuovere o rinominare i file.

Il passo successivo consiste nel capire come muoversi tra le differenti versioni di progetto.
Come è facilmente intuibile dalla natura del programma, infatti, la possibilità di creare nuove e infinite repository, partendo da un lavoro preesistente, crea inevitabilmente una catena di percorsi diversi, tutti derivanti da uno stesso progetto.

Lo scopo di questa guida è quello di spiegare, quindi, come gestire in modo corretto il versioning di un progetto, spostandosi fra le varie ramificazioni (branching), evidenziando la possibilità di fondere i vari rami e ripristinare versioni precedenti. La guida risulta valida per le principali distribuzioni Linux ed è stata testata su Ubuntu 18.04.

Le Ramificazioni

Le ramificazioni o “branch” vengono utilizzate per implementate funzionalità differenti e indipendenti l’una dall’altra a partire dalla stessa radice. Il ramo principale di un progetto viene generato quando si crea una “repository” ed è etichettato come “master”.
La prima cosa da fare per imparare a muoversi tra i vari branch di una repository è utilizzare il comando “git checkout”, seguito dal ramo su cui vogliamo lavorare:

$ git checkout <branch>

Qualora, per esempio, volessi spostarti e lavorare sul ramo principale dovrai utilizzare il comando seguito da “master”, come riportato di seguito:

$ git checkout master

Una volta indicato il ramo dal quale partire per creare la tua repository, nella cartella di lavoro i file saranno portati all’ultimo stadio del ramo scelto. Tutte le future modifiche che andrai ad apportare, quindi, creeranno una nuova versione di progetto relativa a quel particolare branch.

Creare un Ramo

Per creare un ramo con Git non dovrai far altro che ricorrere al comando “git branch” seguito dal nome che vuoi attribuire al ramo creato:

$ git branch <nome_ramo>

Lo spostamento al ramo appena creato non è automatico, per cui per lavorare sulla nuova ramificazione appena creata dovrai ricorrere al comando “git checkout”:

$ git checkout <nome_ramo>

A questo punto ogni commit effettuata farà riferimento al ramo appena creato.
In figura 1 è riportato un esempio in cui sono eseguite le seguenti operazioni:

  • Spostamento sul ramo “master”;
  • Creazione di un nuovo ramo (Test0);
  • Spostamento sul ramo appena creato;
  • Aggiunta di un file (BranchTest);
  • Commit delle modifiche;
  • Ritorno sul ramo “master”.
Creazione Ramo e Commit

Fig. 1 – Esempio Creazione Ramo e Commit su Ubuntu 18.04

Trovare e cancellare Rami

Il comando “git branch” non seguito da nulla rilascerà semplicemente una lista di tutti i rami esistenti.

$ git branch

Per cancellare un ramo puoi ricorrere sempre al comando “git branch”, questa volta seguito dall’argomento “-d” e il nome del ramo da rimuovere:

$ git branch -d <nome_ramo>

In figura 2 è riportato un esempio di lista dei rami ed eliminazione del ramo appena creato (Test0).

Lista dei Rami ed Eliminazione

Fig. 2 – Esempio Lista dei Rami ed Eliminazione su Ubuntu 18.04

Come visibile in figura, se cercherai di eliminare un ramo sul quale hai svolto delle modifiche Git rilascerà un errore: dato che il ramo non è stato unito (branch merge), per evitare di farti perdere erroneamente tutto il lavoro svolto, ne eviterà l’eliminazione.
Eliminare il ramo sarà comunque possibile utilizzando l’argomento “-D”:

$ git branch -D <nome_ramo>

Fusione tra Rami

Lo scopo dei rami è quello di creare flussi di lavoro paralleli e indipendenti per poter sperimentare nuove funzioni senza intaccare il progetto principale (per esempio versioni “release”, “beta” o “fix”).
Per questo motivo i rami sono concepiti per avere vita breve, che culmina con la fusione ad un altro ramo.
La fusione tra rami è possibile impiegando il comando “git merge” seguito dal nome del ramo da fondere.

$ git merge <ramo_da_unire>

Ovviamente la fusione deve avvenire tra due rami. Il comando “git merge” fonde il ramo indicato nella sintassi con quello sul quale ti trovi al momento. Qualora quindi volessi svolgere il “merge” assicurati, tramite “git checkout” di posizionarti sul ramo con il quale vuoi effettuare l’unione.
Il comando “git merge” così indicato non lascerà nella cronologia della “repository” nessuna traccia dell’avvenuta unione. Molte volte può essere necessario tener traccia della fusione pertanto sarà sufficiente utilizzare l’argomento “--no-ff”. In questo modo Git effettuerà automaticamente un nuovo commit in modo da poter identificare il “merge” in futuro:

$ git merge --no-ff <ramo_da_unire>

La fusione di due rami non implica l’eliminazione del ramo che è stato unito a quello indicato, bensì esso continuerà ad esistere. Per rimuoverlo definitivamente si dovrà procedere con l’eliminazione utilizzando il comando “git branch -d”.
In figura 3 è riportato un esempio di fusione tra rami. In particolare le operazioni eseguite sono:

  • Spostamento sul ramo “master”;
  • Lista dei rami;
  • Merge del ramo “Br01”;
  • Cancellazione del ramo “Br01”.
Merge dei rami

Fig. 3 – Esempio Merge dei rami su Ubuntu 18.04 

Qualora volessi una lista che mostri solo i rami non uniti a quello attuale potrai utilizzare sempre il comando “git branch” seguito dall’argomento “--no-merged”.

$ git branch --no-merged

Risoluzione di un Confitto

Quando viene effettuato il merge di due rami, Git determina automaticamente quale dei due commit genitori sia il migliore ed effettua la fusione.
Potrebbe accadere che Git non sia in grado di decidere automaticamente quale versione dei file preservare durante quest’operazione, pertanto notificherà un conflitto.
Qualora ti dovessi trovare in questa situazione dovrai procedere manualmente alla risoluzione del conflitto ed effettuare il commit delle modifiche apportate. Alternativamente la fusione dei rami non avverrà.
La situazione può essere illustrata meglio tramite il seguente esempio. Prendendo in considerazioni due rami diversi in cui sono salvate due versioni differenti dello stesso file, una volta effettuato il merge, Git rilascerà il seguente avviso:

CONFLICT (content): Merge conflict in <filename>

Automatic merge failed; fix conflicts and then commit the result.

Per ovviare al problema dovrai recarti nella cartella in cui è posizionato il file in conflitto, modificarlo e fare un successivo “add” con “commit”. Nel caso si trattasse di un file di testo, Git segnalerà nel file stesso le sezioni incriminate inserendo tra “<<<<<<<” e “=======”  la sezione presente solo nel primo ramo e tra “=======” e “>>>>>>>” quella presente solo nel secondo.

Modificando il file manualmente, eseguendo il comando “git add” seguito dal comando “git commit” verrà finalmente effettuata la fusione tra i rami, come mostrato in figura 4.

Nell'esempio presente in figura 4, è stata inoltre svolta anche una verifica dei rami non uniti, in modo da essere sicuri della corretta riuscita del “merge”.

Risoluzione Conflitto e Merge

Fig. 4 – Esempio Risoluzione Conflitto e Merge su Ubuntu 18.04

Effettivamente, dopo il “commit”, il ramo “Br03” non compare più nella lista dei rami scollegati, a prova del fatto che la fusione al ramo “Br04” è avvenuta con successo.

Gestione dei Tag

I “Tag” non sono altro che etichette da allegare ai vari “commit” allo scopo di riportare informazioni utili sulla versione e sul progetto su cui si sta lavorando.
Per visualizzare i tag presenti in un “repository” potrai ricorrere al comando “git tag”:

$ git tag

In Git esistono due tipi diversi di tag: “annotated” e “lightweight”. I primi servono ad indicare informazioni complete sulla versione, quali “checksum”, nome dello sviluppatore, data/ora e un commento. Gli ultimi sono semplici puntatori a un “commit”.
Il comando “git tag” seguito dal testo identificativo della versione creerà un tag “lightweight”. La sintassi è riportata di seguito:

$ git tag <Nome_Versione>

Ricorrendo all’argomento “-a” si procederà, invece, alla versione di un tag “annotated”:

$ git tag -a <Nome_Versione> -m <Commento>

Infine per visionare un tag potrai utilizzare il comando “git show” come riportato di seguito:

$ git show <Nome_Versione>

I comandi appena esaminati permettono di aggiungere “tag” solo all’ultimo commit. Qualora volessi aggiungere un tag a un commit più vecchio dovrai indicare dopo il comando “git tag” anche l’identificativo (Checksume) del commit a cui vuoi far riferimento. La sintassi completa è riportata di seguito:

$ git tag -a <Nome_Versione> <Checksum_Commit>

Per risalire al “checksum” dei vari commit effettuati puoi utilizzare il comando “git log”.

$ git log --pretty=oneline

Una volta lanciato il comando verrà visualizzata sul terminale una lista in ordine cronologico dei vari “checksum” dei commit seguiti dal commento inserito in fase di “commit” delle modifiche.