Git workflow: due approcci efficaci

Uno dei principali punti di forza di Git è, senza dubbio, la flessibilità.

Con Git siamo liberi di lavorare con il nostro team di sviluppo, in modo decentralizzato, senza particolari vincoli riguardo al flusso di lavoro da seguire. Ogni gruppo è libero di strutturare il proprio workflow nel modo che ritiene più opportuno ed efficace.

Negli anni sono però emersi diversi pattern che col tempo hanno acquisito popolarità e sono lentamente divenuti degli standard de facto per chi lavora con Git.

In questo post andremo ad esaminare due approcci molto diffusi e che probabilmente ci capiterà di adottare all’interno di progetti complessi.

Nota Parlo di progetti complessi perché, probabilmente, quando si lavora a progetti piuttosto semplici è più conveniente utilizzare il solo master branch senza complicarsi troppo la vita… Ovviamente nulla vieta di applicare questi approcci anche ai nostri micro-progetti personali.

Feature branching workflow

Questo flusso di lavoro è piuttosto semplice da applicare: innanzitutto abbiamo il classico branch master che costituisce il filone principale di sviluppo; parallelamente ad esso si diramano n feature branch, ovvero rami di sviluppo focalizzati su funzionalità specifiche da aggiungere al progetto.

In pratica, nel momento in cui si comincia a lavorare su una nuova funzionalità, si crea un nuovo branch partendo da master:

git checkout master
git pull
git checkout -b nuova-funzionalita

I commit relativi alla nuova funzionalità avverranno sul branch corrispondente, che potrebbe risiedere solamente sul nostro repository locale, o che possiamo pushare nel repository centrale così da permettere ad altri componenti del team di collaborare con noi:

git push origin nuova-funzionalita

Nel momento in cui la nuova funzionalità è pronta per essere rilasciata in produzione, il feature branch può essere mergiato in master:

git checkout master
git pull
git merge --no-ff nuova-funzionalita
git push origin master

Solitamente viene utilizzata l’opzione --no-ff per il merge così da creare esplicitamente un merge commit che rappresenti un “punto di giunzione” tra il feature branch e master che identifichi (anche visivamente) un nuovo rilascio in produzione (concetto che può essere rafforzato applicando anche un apposito tag).

Dopo il merge è buona norma eliminare il ramo di sviluppo relativo alla funzionalità completata (eventualmente, anche dal repository centrale):

git branch -d nuova-funzionalita
git push origin --delete nuova-funzionalita

Gitflow

Esiste un altro famoso workflow, molto diffuso anche in ambito open source, che può essere considerato un’evoluzione del feature branching, ma che definisce regole molto più precise e, per certi versi, stringenti sul ciclo di vita dei branch.

Questo flusso di lavoro è stato ideato da Vincent Driessen e descritto per la prima volta nel 2010, in un ormai celebre articolo del suo blog.

Gitflow è particolarmente adatto ad essere impiegato in team di sviluppo Agile (Scrum) con rapidi cicli di sviluppo e rilascio del software; inoltre si adatta perfettamente a sistemi di deployment automatizzato tipici della Continuous Delivery.

Lo sviluppo si muove lungo due “binari” fondamentali: il classico branch master e un secondo ramo definito development o per brevità dev. Il primo includerà i soli commit relativi ai rilasci in produzione, il secondo branch sarà invece quello da utilizzare per l’effettivo sviluppo del progetto.

In fase di setup del nostro repository procederemo quindi in questo modo:

git checkout master
git pull
bit checkout -b dev

Diversamente da quanto visto nel feature branching workflow, in questo caso i branch relativi a nuove funzionalità andranno creati a partire da dev:

git checkout -b nuova-funzionalita dev

… e in esso andranno mergiati una volta completato lo sviluppo della feature:

git checkout dev
git pull
git merge --no-ff nuova-funzionalita
git push origin dev

Quando il progetto ha raggiunto uno stato papabile per il rilascio in produzione, è il momento di creare un nuovo release branch.

Anche i branch di rilascio si diramano da dev e comunemente seguono la naming convention release-<versione>:

git checkout -b release-1.0 dev

Nel branch di rilascio si andranno a completare tutte le attività finali (e, possibilmente, di minore entità) necessarie alla distribuzione di una nuova versione del progetto. Al termine di queste attività, il branch potrà essere mergiato in master ed eventualmente taggato con un corrispondente codice di versione:

git checkout master
git merge --no-ff release-1.0
git tag 1.0

Le modifiche introdotte nel release branch andranno poi riportate anche in dev:

git checkout dev
git merge --no-ff release-1.0

Infine, Gitflow prevede anche un’ulteriore tipologia di branch: gli hotfix-branch. Questi rami si originano direttamente da master e vengono utilizzati per correggere problemi riscontrati nella versione rilasciata in produzione.

Nel caso, ad esempio, fosse stato riscontrato un bug nella versione 1.0 appena rilasciata, dovremmo agire in questo modo:

git checkout -b hotfix-1.0.1 master

Terminate le attività di fixing, il ramo andrà nuovamente a confluire in master generando una nuova versione “stabile”:

git checkout master
git merge --no-ff hotfix-1.0.1
git tag 1.0.1

Come i release branch, anche gli hotfix branch vanno mergiati anche in dev:

git checkout dev
git merge --no-ff hotfix-1.0.1

Nota Se un nuovo release branch è già stato creato prima della chiusura dell’hotfix branch, quest’ultimo potrà essere mergiato direttamente nella nuova release.

Tutti i rami derivati da master e dev vanno eliminati dai repository locali e da quello centrale una volta mergiati nei branch principali.

In conclusione

In questo post abbiamo esaminato due flussi di lavoro comunemente applicati quando si lavora con Git.

Scrivete nei commenti se ne conoscete altri, magari migliori… così li provo anch’io! :-)

A presto,

David