[GIT] Il versionamento distribuito con GIT ed un efficiente modello di branching

Vi scrivo oggi di un interessante modello di versionamento che si basa sul famosissimo GIT, repo usata ai primordi per la distribuzione del kernel Linux e negli ultimi tempi come maturo e completo sistema di versioning distribuito. GIT ha attirato gran parte degli sviluppatori di vari linguaggi, tanto da diventare più utilizzato di SVN.

Per la configurazione di GIT e alcune nozioni di base, vi rimando all’ottimo articolo “CVS, SVN e GIT!“.
Per approfondire, il miglior riferimento è il freebook online: http://git-scm.com/book
Carina è anche la semplice guida “git – the simple guide
Personalmente uso il servizio online di GITHUB per versionare i miei progetti. Anche se mi sto trovando molto bene con Assembla, invece, per versionare piccoli progetti su repo SVN. Ho utilizzato Mercurial, ma ho avuto non pochi problemi di compatibilità/bug con vari plugin HG. Se volete versionare su una repo GIT, vi consiglio di installare i client SmartGIT, SourceTree o Tortoise (su Mac, ma alcuni anche per Windows). Su XCode in Mac, il plugin di GIT è già configurato e potete sceglierlo come versionatore del vostro progetto.
Ora, qual è il punto di forza di GIT? Sicuramente la decentralizzazione del codice sorgente. Nell’articolo che vi linko, si enumerano i punti a favore di GIT rispetto a sistemi centralizzati di controllo versione come SubversionWhy you should switch from SubVersione to GIT
In sintesi, come già detto, GIT ha il vantaggio di essere una repo distribuita. Invece di lanciare il comando “svn checkout (url)“, che preleva l’ultima versione della repository di progetto, su GIT si lancia il comando “git clone (url)”  che copia-clona l’intera history di progetto. Ciò significa che il clone non intacca il progetto originale presente sulla repo e uno sviluppatore può autonomamente lavorare su una sua copia, in modalità offline.
Con questo nuovo paradigma è possibile committare in una repository locale anche in assenza di connessione alla rete, cosa che invece non era possibile con SVN, visto che il committ viene fatto interamente sulla repo remota.
Altro vantaggio implicito di questo modello è che il workflow non ha un “single point of failure“. In GIT ciascun membro può pushare su qualsiasi altro server se quello centrale viene corrotto, così che altri membri possono accedere in SSH e ripristinare la working copy in pochi minuti.
Grazie al meccanismo di pull e push, la sincronizzazione tra le repo GIT di ciascun membro è alquanto efficiente. Il meccanismo distribuito permette di creare tante repo private (locali) e sincronizzarle con quelle pubbliche. Un integration manager può pullare tutte le copie pubbliche su un unico “gold repository”, dove ci sarà la versione stabile da deployare in produzione.

 

Una feature ben fatta di GIT è quella che permette il merging tra i branch. Ogni membro del team può creare la propria “feature” su un branch separato e committare/pullare il tutto sul branch condiviso (modello che approfondiremo in seguito).
Quando viene creato un branch in GIT, si può facilmente switchare a quello nuovo per iniziare a svilupparci sopra. I branch si possono riaprire per fixare bug, inserire nuovo codice e poi mergiare il tutto sulla “mainline work“. L’operazione di merge genera pochissimi conflitti sui file.
Esiste una vera e propria “area di sosta” dei file, detta staging area, che non è altro che una cache locale in cui si trovano le modifiche del codice prima del commit. Questa è molto utile per avere una peer-review del lavoro fatto. E’ possibile committare i singoli changeset in seguito. GIT mantiene traccia di “parti di file” (stage files) come delta delle modifiche apportate al codice, che si possono committare selettivamente (patch staging).
Queste modifiche, logicamente separate, possono essere committate separatamente per una facile review del progetto e tale feature è detta cherry-pickable.
Passiamo ora alla descrizione di un efficiente modello di branch di GIT, prendendo come riferimento l’articolo “A successful GIT branching model“.

 

In questo modello esiste una repo denominata “origin” che è quella centrale in cui tutti i membri del team pullano e pushano una versione stabile o condivisa delle modifiche.

 

 

Main branches. La repo centrale contiene due main branches che vivono per tutta la durata del progetto e che si chiamano “master” e “develop“.

 

Il branch origin/master conterrà il codice che verrà messo in produzione. Il branch origin/develop è quello dove vengono pushate tutte le modifiche di sviluppo di ciascun membro del team di sviluppo e che viene continuamente aggiornato fino a quando non si è prodotta una versione stabile da mettere in produzione. È anche detto “branch di integrazione” delle repo distribuite tra i vari sviluppatori.
Quando si reputa stabile, il codice su origin/develop viene mergiato su origin/master.
Supporting branches.
Oltre ai branch “principali”, esistono altre tipologie di branch di vita limitata (detti branch di supporto) che si classificano come segue:
  • Feature branches
  • Release branches
  • Hotfix branches

Feature branches. Si stacca (branch off) da develop e si mergia sempre in esso. Si può chiamare in qualsiasi modo tranne che master, develop, release-* o hotfix-*.
Si chiama anche “topic branches” e contiene gli sviluppi di una features o funzionalità logica del progetto.
Per creare un nuovo feature branch facendo il branch off da develop:
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
 Per mergiare la feature su develop quando è stata ultimata:
$ git checkout develop
#Switched to branch 'develop'
$ git merge --no-ff myfeature
#Updating ea1b82a..05e9557
#(Summary of changes)
$ git branch -d myfeature
#Deleted branch myfeature (was 05e9557).
$ git push origin develop
Il flag –no-ff (dove “ff” sta per “fast-forward”) evita di “confondere” il codice della features nella mainline di progetto. Molto utile nel caso si voglia ricostruire la storia dei commit.

 

Release branches. Il branch off qui avviene da develop e si può mergiare sia su master che su develop. La naming convention è release-*. Questo tipo di branch supporta la preparazione per un nuovo rilascio in produzione. Infatti, mette a disposizione un insieme di metatag utili per etichettare la build version, la data di rilascio, ecc.
Per creare un release branch da develop, supponendo di stare alla versione 1.1.5 in produzione e di voler passare alla 1.2:
$ git checkout -b release-1.2 develop
#Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
#Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
#[release-1.2 74d9424] Bumped version number to 1.2
#1 files changed, 1 insertions(+), 1 deletions(-)
 A questo punto, possiamo mergiare il release branch sul master:
$ git checkout master
#Switched to branch 'master'
$ git merge --no-ff release-1.2
#Merge made by recursive.
#(Summary of changes)
$ git tag -a 1.2
Per prendere le modifiche fatte nel release branch e portarle in develop:
$ git checkout develop
#Switched to branch 'develop'
$ git merge --no-ff release-1.2
#Merge made by recursive.
#(Summary of changes)
Infine, per rimuovere il release branch:
$ git branch -d release-1.2
#Deleted branch release-1.2 (was ff452fe).

 

Hotfix branches. Il branch off si fa da master e si mergia sia in master che in develop. Il naming convention è hotfix-*.
È come il release branch ma viene creato dopo che si ha uno stato indesiderato in produzione, come un bug critico da risolvere immediatamente. A tal scopo di fa un branch off dal corrispondente tag del master che marca la versione in produzione.

Supponendo di avere in produzione la versione 1.2 su cui è avvenuto un severe bug, un hotfix branch si crea così:

 

$ git checkout -b hotfix-1.2.1 master
#Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
#Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
#[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
#1 files changed, 1 insertions(+), 1 deletions(-)
 I commit delle modifiche per la correzione del bug si effettuano su tale hotfix:
$ git commit -m "Fixed severe production problem"
#[hotfix-1.2.1 abbe5d6] Fixed severe production problem
#5 files changed, 32 insertions(+), 17 deletions(-)
A fine correzione, il merge sul master si fa come un release branch:
$ git checkout master
#Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
#Merge made by recursive.
#(Summary of changes)
$ git tag -a 1.2.1
E per allineare il develop con il bugfix:
$ git checkout develop
#Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
#Merge made by recursive.
#(Summary of changes)
Unica eccezione si ha quando la fix riguarda un release branch esistente. In tal caso, l’hotfix va mergiato su tale release, invece che su develop.
Per rimuovere l’ hotfix branch:
$ git branch -d hotfix-1.2.1
#Deleted branch hotfix-1.2.1 (was abbe5d6).

[Subversion] Spostamento di una repository da un server SVN ad un altro

In un precedente articolo si è parlato della installazione e configurazione di un server SVN su ambiente Linux (distribuzione Debian). Una  volta configurato il server, però, mi si è posto il problema dell’importazione di una repository salvata su un server SVN installato su un’altra macchina (Windows), eseguendo una procedura alquanto semplice e che ha “travasato” tutta la history, senza perdere nessun dato.

Ecco i passi da eseguire:

1. Backup della vecchia repository: Occorre effettuare un dump della repository che si occorre importare eseguendo il comando da terminale (DOS su Windows; shell/bash su Unix)

svnadmin dump /path/to/repository > repo_name.svn_dump
									

Tale comando invoca il servizio svnadmin (occorre ovviamente essere amministratori della macchina) che esegue il comando dump, seguito dal path assoluto della repository (il path è quello del file system e non la URL della repository). Il dump copia tutta la repository in un file che ha estensione svn_dump che potete chiamare come preferite (potete specificare anche un path assoluto in cui salvare tale file).

2. Creazione della nuova repository: se non avete già creato una repository sul nuovo server SVN, occorre crearla con il comando svnadmin create

svnadmin create /path/to/repository
									

3. Importazione del dump sulla nuova repository: a questo punto, copiare il dump che avete esportato sulla nuova macchina, posizionatevi da terminale nella directory dove avete copiato il file di dump e lanciate il comando svnadmin load

svnadmin load /path/to/repository < repo_name.svn_dump
									

In alcuni casi, occorre forzare che l’UUID della nuova repository sia uguale a quella della vecchia. Per cui, se vi viene visualizzato un errore del genere dopo aver lanciato il comando load, potete rilanciare la precedente riga di codice specificando l’opzione –force-uuid (l’UUID della vecchia repository la potete vedere con un client SVN visionando le proprietà della working copy).

Ecco dei comandi utili per poter visualizzare e settare la UUID di una repository:

$ svnlook uuid /var/svn/repos cf2b9d22-acb5-11dc-bc8c-05e83ce5dbec 
$ svnadmin setuuid /var/svn/repos  #NOTA. generate a new UUID 
$ svnlook uuid /var/svn/repos 3c3c38fe-acc0-11dc-acbc-1b37ff1c8e7c 
$ svnadmin setuuid /var/svn/repos  cf2b9d22-acb5-11dc-bc8c-05e83ce5dbec #NOTA. restore the old UUID 
$ svnlook uuid /var/svn/repos cf2b9d22-acb5-11dc-bc8c-05e83ce5dbec
									

Importazione di una repository a partire da una specifica revisione

E’ possibile importare una vecchia repository anche a partire da una specifica revisione. Occorre effettuare il dump specificando quale revisione importare e importando poi in modo incrementale. Ecco i comandi:

svnadmin dump --incremental -r 1234 /path/to/repository > rev1234.svn_dump

L'importazione nella nuova repository si importa allo stesso modo:
svnadmin load /path/to/repository < rev1234.svn_dump
									

 

[SubVersion] Installazione e configurazione di un server SVN su Linux Debian

In questo articolo si descrivono i passaggi per installare e configurare un server SVN (http://subversion.tigris.org/) su ambiente Linux (Debian) per il versioning del codice sorgente e della documentazione di un progetto.

Per installare l’ultima versione di Subversion presente sulla repository della distribuzione Debian, occorre lanciare da terminale il seguente comando:

$ apt-get install subversion
									

Tale comando scarica l’ultima release di Subversion e risolve tutte le dipendenze. Se si riscontrano problemi, occorre scaricare e installare tutti i pacchetti e procedere con una installazione manuale. Se si usa una distribuzione Linux che non sia Debian o il comando apt-get non è disponibile, leggete l’articolo How to install subversion on Linux and make it working.

Per verificare che l’installazione del server SVN sia andata a buon fine, basta digitare sulla shell il comando svn. Se è tutto ok, potete passare alla creazione della repository. Ecco il comando:

$ svnadmin create /srv/svn/repos
									

Tale comando crea la repository nel percorso /srv/svn/repos/ del vostro server (potete sceglierne anche uno a vostro piacimento). Se tutto è andato a buon fine, ritroverete in tale directory i seguenti file e sottodirectory:

conf/   dav/    db/    format/    hooks/    locks/     README.txt

Di seguito, ulteriori dettagli sulla configurazione e l’utilizzo del server SVN installato.

Continua la lettura