DVCS… Chi era costui?

L’introduzione del paradigma distribuito nel campo dei VCS (Version
Control System) porta a un’evoluzione ben più ampia del semplice processo di
sviluppo individuale del software, il cambiamento è infatti radicale e
riguarda l’intero gruppo di lavoro.

In questo articolo ci concentreremo sullo spiegare le situazioni in cui i
sistemi distribuiti offrono i maggiori vantaggi rispetto alle soluzioni
centralizzate ben più diffuse e note e che quindi non necessitano degli stessi
chiarimenti. Cercheremo anche di separare le caratteristiche di livello
implementativo, come particolari comandi o un’eventuale interfaccia grafica,
da quelle dovute al cambio di workflow del nuovo sistema.

La storia dei sistemi centralizzati OpenSource inizia molti anni fa con
RCS e passando per CVS arriva all’ultima incarnazione chiamata
Subversion che può essere considerato il software di VCS più diffuso e
utilizzato al mondo.

I sistemi centralizzati si caratterizzano per il fatto che esiste un solo
repository centrale a cui tutti gli sviluppatori devono fare riferimento per
lo sviluppo. Proprio in questo consiste la principale differenza con i sistemi
distribuiti dove, sebbene possa esistere un unico repository a cui tutti fanno
riferimento, ciascuno sviluppatore nel momento del checkout crea localmente
una copia identica del repository principale e può quindi lavorare localmente
senza bisogno di continua sincronizzazione oppure può scegliere di
sincronizzarsi con repository diversi da quello principale.

Questa differenza ne introduce contemporaneamente un’altra riguardante l’atto
pratico dell’utilizzo: poiché la copia locale del repository è identica a
quella remota, il singolo passo di commit dei sistemi centralizzati viene
diviso in commit e push, ovvero nella conferma dei cambiamenti registrando il
changeset in locale e la copia dei nuovi changeset nel repository remoto di
riferimento (o di nostra scelta, non siamo più vincolati al nostro repository
principale).

Per procedere nell’analisi però diventa utile introdurre alcuni workflow che
si differenziano per essere i più frequentemente presenti nelle nostre lunghe
giornate di lavoro. Analizzeremo quindi il comportamento dei due concorrenti
alla prova dei fatti.

Sviluppo Centralizzato

Sviluppo centralizzato

Figura 1. La figura rappresenta lo schema logico di funzionamento di un sistema centralizzato

Il primo, e più scontato, flusso è quello tipico dei sistemi centralizzati,
si può dividere nei passi:

  1. Checkout del repository principale
  2. Modifica locale
  3. Commit dei cambiamenti
  4. Push dei changeset sull’albero principale (solo nei sistemi distribuiti)

Non esistono particolari problemi strutturali che possano impedire ai
sistemi distribuiti di funzionare in questo modo e ovviamente i sistemi
centralizzati sono stati creati per supportare principalmente questo ciclo
di sviluppo.

Sviluppo Branch-Based

Sviluppo branch-based

Figura 2. In figura è rappresentato lo schema logico dello sviluppo branch based. Le frecce indicano
la direzione dello scambio di dati ed è possibile notare che nei sistemi distribuiti questo può avvenire
anche tra i computer degli sviluppatori e non solo tra l’albero principale e i branch.

Negli ultimi anni si sta affermando un sistema di sviluppo che si fonda sul
funzionamento continuo del ramo principale del repository. Spesso negli ambienti
agili lo sviluppo branch-based viene affiancato da una tecnica chiamata
Continuous Integration.
I benefici principali ottenibili riguardano appunto l’integrazione tra diverse
componenti del software, che possono essere sviluppate indipendentemente da
più gruppi diversi, che utilizzano le ultime versioni disponibili senza
preoccuparsi di eventuali bachi nei componenti usati in quanto ciascun albero
è garantito essere funzionante in ogni momento. Questo permette di ridurre
drasticamente i tempi di integrazione mentre ci si avvicina al rilascio e
riduce notevolmente il numero di bachi grazie alla continua osservazione dello
stato funzionale dei componenti e del sistema nel suo complesso. È facile
comprendere come l’uso di ciascuna di queste tecniche permette di aumentare
la qualità del software che sviluppiamo e di ridurre i momenti di panico dovuti
a bachi estremamente difficili da trovare e quindi correggere.

Per fornire queste possibilità il modello tende a creare nuovi rami di
sviluppo, i cosidetti branch, per sviluppare nuove funzionalità o
correggere bachi esistenti, e una volta terminato il lavoro si prosegue a
riunire i due rami attraverso l’operazione di merging.

Nei sistemi centralizzati come Subversion si cerca di ottenere questa
funzionalità strutturando l’albero in almeno 2 sottodirectory e usando il
comando di copia come comando di branch. Come conseguenza di questo, si
ottiene una fisionomia dei repository che può essere spesso ricondotta
alle seguenti 3 directory:

trunk/
branches/
tags/

Nel momento in cui lo sviluppatore intende lavorare a una nuova
funzionalità, crea una copia dell’ultima versione del repository, presente
in trunk, ad esempio nella directory branches/new_cool_feature e prosegue
quindi il suo lavoro in questo nuovo branch.

Nei sistemi distribuiti come Mercurial si procederebbe
creando una copia dell’albero di riferimento, lavorando quindi su di essa
e procedendo nel push dei changeset interessati verso l’albero di
riferimento.

Vi sarete accorti della pochissima differenza presente tra il modello di
sviluppo centralizzato e questo modello per quanto riguarda i sistemi
distribuiti. In realtà non c’è nessun problema in questo poiché i moderni
DVCS sono stati creati proprio pensando allo sviluppo branch-based
rendendolo, di fatto, uguale allo sviluppo centralizzato.

Si potrebbe pensare che, comunque stiano le cose, non ci sia una grande
differenza neanche tra i sistemi distribuiti e quelli centralizzati. In
realtà però le differenze sono molto grosse e non subito evidenti.

Nei log dei sistemi distribuiti, fino al momento del merge, non vi è
traccia dei cambiamenti avvenuti nel branch. Si tratta di un cambiamento
positivo in quanto permette di tracciare una chiara linea di demarcazione
tra i cambiamenti che appartengono a un ben determinato branch e quelli
dell’albero principale. Sebbene inizialmente possa sembrare poco utile,
non si può non notare come in realtà lo diventi nonappena si procede alla
ricerca dei commit riguardanti una data funzionalità. Se questa è stata
sviluppata in un branch, in un sistema centralizzato il risultato che
otterremo sarà un’inversione temporale dei log, che quindi non saranno più
correttamente ordinati considerando il momento del merge come momento
iniziale dell’introduzione della nuova funzionalità, saranno invece
mischiati temporalmente agli altri log ottenuti durante lo sviluppo di
altri branch o del ramo principale. Questo aumenta la confusione e
diminuisce drasticamente l’utilità dei log.

Un’ulteriore differenziazione, e questa di origine molto pratica, è la
possibilità di eseguire con grande semplicità il cosiddetto merge forward.
Capita praticamente sempre infatti che dopo la creazione di un branch, un
altro venga unito nuovamente con il ramo principale e, nel caso di branch
dalla vita molto lunga, possono capitare anche più fusioni tra branch. Per
gestire questo tipo di necessità nei sistemi centralizzati bisogna
ricorrere a strumenti di terze parti e/o al lavoro manuale di
risoluzione dei conflitti tramite patch. Nei sistemi distribuiti invece
basta eseguire un nuovo comando di aggiornamento dell’albero locale con
l’albero remoto. Tutta la storia locale verrebbe fusa con quella
principale (anche se non necessariamente, posso fondere con la stessa
semplicità anche due branch distinti) sfruttando algoritmi estremamente
sofisticati o strumenti esterni installati sul sistema in uso. In questa
situazione, un ulteriore vantaggio dei sistemi distribuiti è, ancora una
volta, la chiara separazione tra albero principale e alberi secondari
relegando gli eventuali problemi al branch in sviluppo piuttosto che
all’albero principale che deve rimanere relativamente stabile.

Sviluppo di software customizzati

Sviluppo di software customizzati

Figura 3. È possibile creare branch isolati e customizzati per ciascuno dei nostri
clienti continuando a sviluppare il ramo principale e importanto solo i cambiamenti
interessanti negli alberi specializzati.

Sull’onda del secondo modello analizzato arriva lo sviluppo di un software
che viene poi adattato per ciascuno dei nostri clienti, anche solo perché
vengono cambiati leggermente alcuni degli algoritmi implementati o i temi
e i layout grafici. È immediata l’osservazione che riguarda la possibilità
di isolare totalmente i singoli clienti (senza mantenerli nello stesso
albero, magari dentro la directory branches/) mantenendo però un singolo
albero principale, uguale per tutti, su cui avverrebbe lo sviluppo comune.

La semplicità di gestione del merge forward e la totale separazione dei
branch rende gli strumenti distribuiti quelli più adatti a questo genere
di situazioni lavorative.

Backport di funzionalità

Ancora una volta la semplicità di esecuzione del merge forward ci
permette di avere alcune revision dei nostri software, anche chiamate tag,
che vengono congelate in quanto rappresentano punti di riferimento nella
storia di quel software. Capita spesso però di trovare problemi di
sicurezza durante lo sviluppo e sarebbe estremamente utile avere la
possibilità di aggiungere un tale update anche alle vecchie versioni
rilasciate dei nostri prodotti. Nei sistemi distribuiti il problema viene
semplicemente ridotto alla copia dei changeset interessanti per il fix
all’interno dell’albero contenente il tag. Così facendo abbiamo aggiornato
i nostri tag con le ultime patch di sicurezza e possiamo quindi aggiornare
i software vecchi con il minimo sforzo. Nei sistemi centralizzati invece è
richiesto un lavoro più complesso di estrazione del cambiamento,
specialmente se consistente in più commit, e nell’integrazione dello
stesso nel tag.

Esternamente a tutti e 4 i modelli vanno fatte notare ulteriori caratteristiche.

Soltanto gli utenti autorizzati possono eseguire un’operazione di
push (spostamento dei changeset da locale a remoto), significa quindi
che nel caso in cui uno sviluppatore non avesse tali permessi non sarebbe
possibile proseguire in questa operazione ma sarebbe possibile procedere solo
con l’aggiornamento del server con il repository remoto, eseguita da chi
ha sufficienti permessi e che quindi può decidere se e come proseguire nel
merge avendo pieno controllo sull’albero principale. Al limite
estremo l’albero principale potrebbe essere accessibile solo allo
sviluppatore principale o al responsabile del progetto che deciderebbe
autonomamente quando e come unire le funzionalità sviluppate nei branch dei
singoli sviluppatori.

Ciascun branch creato può quindi essere messo a disposizione del resto del
mondo attraverso uno dei protocolli supportati dal software che decidiamo di
usare, permettendo ai manutentori del progetto, su cui non abbiamo sufficienti
permessi, di aggiornare i loro repository con i nostri cambiamenti, o
addirittura ad aggiornare l’albero principale con i bugfix e le nuove
funzionalità presenti nei repository dei membri della community.

Comments

  1. Devo dare un +1 a tutto l’articolo di Valentino. Uso svn il 99% delle volte perché seguo sempre il “path of least resistance”, ma ci sono decine di volte in cui la facilità di branching mi renderebbe la vita molto più semplice.

    Ho recentemente cominciato ad utilizzare git e mi ci trovo così bene che prima o poi faccio il salto nel buio e mollo svn.

  2. Giovanni, questo sembra essere un trend crescente nel mondo open source. Sempre più progetti lo adottano al posto di SVN.

  3. Prima di fare il salto mi serve ancora che Mercurial, GIT, Bzr o altri facciano un filo più di strada nello stabilizzarsi degli ecosistemi che girano loro intorno. 🙂

    Peraltro, SVN ha anche il vantaggio di essere universale: ce l’ho come “one click install” dove ho l’hosting, ce l’ha Google e ce l’ha SourceForge. Scegliere fra Mercurial, GIT o Bzr è di per sé un ostacolo in più nell’adozione di un sistema che sembra oggettivamente migliore di SVN… 😛

    Credo che non ci sia alcun “problema” se Hg implementasse “meglio” la connessione ad un repository SVN in modo trasparente: per SVN sarebbe un semplice nuovo “commit”, mentre per l’Hg repository locale sarebbe un subtree. C’è un tool presente ma non mi sembra adeguatamente “trasparente”. 🙂

    Vedo cmq che il rant si è sviluppato in uno splendido articolo, good work Valentino. 🙂

  4. Grazie Davide! L’integrazione con SVN e` indubbiamente un problema che andrebbe sistemato per bene. Pero` se le comunita` sono interessate a questa funzionalita` presto sara` supportata estremamente bene, non temere :).

  5. Walter Franzini says:

    Valentino,

    il tuo e` un articolo interessante, ma mi sembra che nello svolgere le tue argomentazioni hai interpretato alcune delle cose che si possono fare _anche_ con un sistema distribuito come una caratteristica _esclusiva_ dei sistemi distribuiti.

    Nel seguito cerco di analizzare il tuo articolo mettendo in luce quelli che sono, secondo me, i punti deboli della tua argomentazione.
    Conduco la mia analisi facendo riferimento ad un sistema centralizzato
    reale, se avrai la pazienza di arrivare fino alla fine scoprirai di cosa si tratta.

    Quello che e` importante per lo “Sviluppo centralizzato” non e` tanto
    l’essere centralizzato o distribuito, bensi supportate la multiutenza.

    A tal riguardo sostieni sostieni che “non esistono particolari problemi strutturali che possano impedire ai sistemi distribuiti di funzionare in questo modo”.

    Penso che concorderai con me nel classificare come distribuito un
    (ipotetico) sistema che supporta un (1) solo utente locale e che implementa come unica operazione tra repository la pull. Io sostengo che un tale sistema distribuito non e` adeguato ad essere usato nel modello “Sviluppo Centralizzato”.

    Parlando del modello “Branch Based” la tua osservazione rispetto ai log “fino al momento del merge, non c’e` traccia dei cambiamenti …” e` certamente vera, ma l’uso di un sistema distribuito non e` l’unico modo di ottenere questo effetto.

    In un sistema centralizzato (non ipotetico) i branch costituiscono un
    albero e finche il branch figlio
    non e` completo (committato) le modifiche introdotte non sono registrate nel branch padre.

    In un sistema centralizzato (non ipotetico) i branch costituiscono un
    albero e finche il branch figlio non e` completo (committato) le modifiche introdotte non sono registrate nel branch padre. Proseguendo con il nostro esempio, finche il branch figlio “1” non e` completo le sue modifiche non sono visibili agli altri figli “2”, “3”.
    Quando il branch “1” e` completo i file che ha modificato e non sono
    modificati nel branch “2” sono a questo immediatamente visibili. Nel caso ci siano file comuni tra il branch “1” e “2” questi sono marcati come da “riconciliare” con selezione automatica delle corrette version di file da utilizzare nell’operazione di merge. Prima del
    completamento del branch “2” tutti i file che ne hanno bisogno _devono_
    essere riconciliati. Penso che quello che ho descritto sia piu` o meno quello che descrivi come “merge forward”.

    Anche il caso della gestione di “Software customizzati” puo` essere
    gestito come indichi tu utilizzando lo stesso software centralizzato che ho menzionato.

    Per quello che riguarda le attivita` di backporting la vera differenza e` fatta dalla possibilita` replicare un changeset e non dalla possibilita` di avere piu` repository. Il concetto di changeset e` un grosso passo avanti rispetto ai sistemi in cui il commit puo` essere
    fatto a livello di singolo file in maniera indipendente ed e` essenziale per i sistemi distribuiti, ma non e` a loro esclusivo appannaggio. Ad esempio, dal 1998, prima di avere qualunque
    funzionalita` di distribuzione dei changeset, Aegis dispone del comando aeclone per copiare un changeset da un branch all’altro.

    Quello che ho scritto non e` una critica verso i sistemi distribuiti o verso il tuo articolo, ma vuole essere solo una precisazione al tuo articolo che ho trovato comunque stimolante. 🙂

Policy per i commenti: Apprezzo moltissimo i vostri commenti, critiche incluse. Per evitare spam e troll, e far rimanere il discorso civile, i commenti sono moderati e prontamente approvati poco dopo il loro invio.