h
A queste obiezioni si può rispondere che dovrebbe esserci nel gruppo di lavoro qualcuno dedicato proprio a configurare e gestire i server, perché se tutti i membri di un gruppo di sviluppo sono oberati di lavoro, vuol dire che un gruppo opera in emergenza, senza paracadute e con un piede sull’orlo del disastro. Quando ci sarà un errore o un problema, sarà difficilissimo ricostruire una situazione stabile o annullare una modifica che si è rivelata perniciosa.
In secondo luogo, per fortuna, non è così complicato usare un sistema di controllo delle versioni. Nella normalità giornaliera, infatti, si tratta solo di perdere qualche minuto ogni volta che il progetto ha raggiunto un obiettivo in scaletta e celebrare l’evento con un bel check-in, con la lista delle modifiche e degli obiettivi di business che sono stati raggiunti.
Ci sono circostanze in cui si deve realizzare una versione ad hoc di un programma, per uno specifico cliente, o si deve ricostruire lo stato della release consegnata nel 2007 per risolvere in fretta un problema, o si vuole tentare una strada nuova di sviluppo. Tutte queste domande hanno una risposta da parte di un sistema di controllo delle versioni.
Gli sviluppatori allo stato brado hanno solo la possibilità di conservare varie copie (e copie delle copie) in diversi livelli di aggiornamento e stabilità del software. Di queste copie si perde rapidamente il controllo. Inoltre questo sistema spreca spazio e espone al rischio di perdere dati. La conseguenza è che può diventare talmente complicato fare un piccolo aggiornamento puntuale su una consegna del 2007 che si è costretti ad aggiornare il cliente all’ultima versione,
magari con effetti secondari non proprio marginali, costi di intervento e fermo macchine.
Git
Vediamo una soluzione open source, di notevole versatilità: l’ambiente git. La motivazione per insistere su un software open source sta nel fatto che la soluzione di minore attrito è certamente in un ambiente gratuito, facile da installare per tutti i sistemi operativi di uso comune, come Windows, Mac OS e Linux. Naturalmente, se si usa un sistema di sviluppo specifico, come Visual Studio, e si opta per la soluzione proprietaria corrispondente, come Visual SourceSafe, ci sono da aspettarsi semplificazioni nell’uso quotidiano e una maggiore integrazione con l’ambiente di lavoro. Comunque ai giorni magri una soluzione a costo nullo è sempre più eloquente al tavolo della direzione, soprattutto in tempi difficili come questi.
Una soluzione open source ha un vantaggio specifico anche quando si opera dal cliente, su macchine che sono responsabilità di terzi. L’installazione di un sistema open source non espone chi è responsabile della conduzione delle macchine a obblighi di licenza.
Un po’ di preistoria
Il moderno capostipite degli ambienti di sviluppo open source è Cvs, un software che nasce sulle ceneri di Rcs, un ambiente di controllo delle versioni del codice nato per lo Unix Bsd, il capostipite degli Unix a sorgente aperto.
Dall’insofferenza per le limitazioni del Cvs è nato il progetto Subversion, il cui nome fa omaggio non solo al controllo delle versioni secondarie ma anche al desiderio di sovversione che ha ispirato lo sviluppo.
Cvs, però, nonostante sia stato per anni il motore di archiviazione dei sorgenti della Apache Software Foundation e di parecchi altri archivi open source, ha sollevato parecchie critiche e, come accade normalmente nella comunità open source, le critiche hanno fatto nascere server alternativi, come Mercurial, un ambiente realizzato in linguaggio Python, e Git, un motore nato dalle esigenze particolari del gruppo di sviluppo kernel di Linux e di Linus Torvalds, persona che si occupa dell’integrazione delle modifiche presentate dai diversi collaboratori.
Un kernel si compone di decine di migliaia di sorgenti, che assommano un numero di righe di codice dell’ordine del milione. Facile convincersi che il compito non è dei più semplici, anche perché i partecipanti al progetto non operano fianco a fianco nello stesso laboratorio, ma sono sparsi per il mondo. Il modello centralizzato di Cvs non si presta facilmente a questa organizzazione, perché può funzionare solo se il repository dei sorgenti è ospitato su Internet e accessibile a tutti i membri, che integrano uno alla volta e aggiornano spesso la propria copia dell’albero.
Più spesso capita che gli sviluppatori non interagiscano così di frequente, ma scarichino una release stabile su cui compiono diversi aggiornamenti fino ad avere un insieme di aggiornamenti che completa una funzionalità applicativa ed è stabile abbastanza da qualificarsi per un’integrazione.
Il modello di Cvs, con un unico albero condiviso, fallisce in questo caso, soprattutto perché chi sviluppa una funzionalità complessa è costretto a decidere fra caricare con frequenza giornaliera modifiche non stabili, o lavorare senza controllo delle versioni su un albero locale, due strade egualmente problematiche. Il problema si presenta sia che il gruppo di lavoro sia distribuito, sia che gli sviluppatori lavorino tutti nello stesso ufficio. Se si stabilisce la regola di mantenere stabile il repository Cvs per avere sempre una release testabile, si costringono i gruppi che hanno modifiche in sospeso a avere una macchina o un albero di sviluppo separato per ogni fase incompleta di sviluppo di una funzionalità.
Git nasce per risolvere questo e molti altri problemi di Cvs, come anche Mercurial, per questo è sembrato logico metterlo alla prova in un progetto che ha impegnato la maggior parte del secondo semestre del 2010.
Installazione
Git si installa scaricando la distribuzione da git-scm.com, il sito dedicato al programma. Per Windows ci sono due versioni, ma consiglio di scaricare msysgit, la meno invasiva. Ci sono due versioni di Git su Windows perché Git è sviluppato con un approccio tradizionalmente Unix alla costruzione di applicazioni, riciclando cioè il set di comandi di base particolarmente ricco e versato nel trattamento di testi del sistema più amato dai programmatori. Per questa ragione, Git non è un ambiente totalmente entrocontenuto, che realizza tutte le funzioni richieste, ma ha numerose dipendenze su un ambiente Unix esterno, su cui costruisce.
Dato che su Windows manca questo ambiente operativo, la scelta più logica è ricrearlo con una qualche emulazione. Ci sono due approcci al problema, quello dell’ambiente Cygnus, che ricrea la funzionalità di basso livello di un kernel Unix appoggiandosi su Windows e l’ambiente Mingw, che offre sostituti nativi Windows per i comandi Unix più comuni, come sh, vi, awk, wc.
Si tratta di due approcci abbastanza ortogonali: Cygnus mira a permettere di ricompilare senza modifiche sorgenti nati su Unix, Mingw si basa su porting di un set di comandi che permettono agli shell script più comuni di girare senza modifiche. Va da sé che l’ambiente che richiede di meno al sistema operativo e lo appesantisce di meno è il secondo. In più, dato che i comandi di Mingw sono applicativi nativi Windows che eseguono in un comune ambiente Windows, la compatibilità con il sistema operativo è intrinsecamente più a portata di mano.
In diversi mesi di collaudo, l’ambiente Git Mingw non ha perso un colpo. I comandi di Git funzionano correttamente, comprese le interfacce grafiche scritte in Tcl/tk. Si aggiunga che un set di comandi Unix facilmente a disposizione permette anche di prendersi qualche facile soddisfazione, che con Windows non è a portata di mano. Per esempio il comando per calcolare il numero di righe di codice C# a cui siamo arrivati, interessante per valutare i progressi del progetto giorno per giorno:
Iniziare con Git
Per iniziare con il controllo delle versioni, si può aprire la shell di Git nella radice di un progetto Visual Studio e dare il comando:
Questo comando prenota un posto nell’indice locale di Git a tutti i file presenti nella directory corrente e nelle sue subdirectory. Si noti che questo aggiunge al repository tutti, ma proprio tutti, i file che incontra. Mentre noi siamo interessati ad archiviare e tracciare i sorgenti, non siamo, in generale, interessati a archiviare i prodotti di compilazione, come oggetti, eseguibili e file temporanei. Archiviare questi file appesantisce inutilmente il repository, aumentandone le dimensioni e allungando i tempi di lavoro in rete. Per evitare il problema, conviene compilare, prima del primo add, un file di nome .gitignore, che deve contenere su ogni riga le specifiche dei file da ignorare. Ecco un esempio di file .gitignore adatto per un progetto C#:
Dopo la add, non cambia nulla nel repository:
la richiesta di commit corrisponde alla memorizzazione definitiva, come in un database. Come in ogni sistema per il controllo dei sorgenti, una commit deve essere accompagnata da un elenco di motivazioni per le modifiche al codice e di risultati raggiunti, che è tanto più di aiuto quanto più è circostanziato e conciso. Un breve messaggio si può passare sulla riga di comando, con l’opzione –m. Se non si usa l’opzione –m si apre una finestra con l’editor configurato, tipicamente vi.
La descrizione di un commit non deve esser un componimento, ma una motivazione per le modifiche di una data giornata di lavoro, che servirà a ricostruire il percorso quando sarà riesaminata. Dopo la commit troveremo nella
cartella di lavoro una directory di nome .git che contiene l’archivio dei sorgenti in un formato codificato e compresso che si rivela piuttosto efficiente, tanto che un archivio di sorgenti con tutta la sua storia può avere la stessa occupazione di disco della versione di lavoro dei sorgenti appena estratti.
Avere un repository locale è utilissimo, perché uno sviluppatore ha sempre a disposizione il paracadute, cioè la possibilità di ritornare a una versione stabile eliminando con sicurezza tutte le modifiche alla base di codice che si fossero rilevate un errore di percorso. È anche molto utile poter ripercorrere la storia di un sorgente per ricostruire il cammino che lo ha portato in un certo stato, esaminando la lista delle modifiche e dei rispettivi artefici.
Una rete flessibile
Una delle particolarità di Git è che l’archivio locale non è un lusso che ci si concede fino a che non si decide di utilizzare un repository centrale condiviso, ma piuttosto un’abitudine che si conserva in qualsiasi configurazione. Ogni sviluppatore, infatti, ha un archivio individuale in cui lavorare fino al compimento di un elemento stabile di funzionalità da propagare nella release. L’archivio locale può essere sincronizzato con un archivio centralizzato quando lo sviluppatore lo ritiene opportuno.
Questa organizzazione ha numerosi effetti benefici sull’organizzazione di un gruppo ed è supportata con molta semplicità attraverso comandi che agiscono sull’archivio locale, come checkout e commit, e comandi che sincronizzano il repository locale con il master: push e pull.
In questi casi è molto semplice scambiarsi le modifiche: basta fare un archivio dei sorgenti e inviarlo su un server comune o spedirlo tramite email. Per esempio, si può fare fronte a una completa mancanza di infrastruttura da parte di un cliente utilizzando il sistema Dropbox per creare una cartella condivisa in rete e replicata su diversi sistemi. Ecco un semplice esempio di shell script, compatibile con il sistema Mingw installato insieme a Git, per archiviare il repository Git in una cartella condivisa con Dropbox
Osserviamo anche che non c’è solo la riga di comando per operare con Git, anche se l’uso della riga di comando è alla portata di qualsiasi sviluppatore. Git è corredato da due strumenti con interfaccia grafica: gitgui e gitk. Questi strumenti possono essere lanciati da riga di comando, o dal menu contestuale di Gestione Risorse, attraverso un’estensione della shell caricata dall’installazione.
Nonostante la grafica spartana, si può navigare la storia di un albero di sorgenti rispondendo a diverse domande come:
In conclusione, fatto salvo Mercurial, su cui non abbiamo ancora indagato, mi sento di raccomandare Git come un deciso passo avanti rispetto ad altre soluzioni open source, oramai limitate.
Uso
Git è un gestore di sorgenti, un oggetto simile a SourceSafe o Cvs, e che i suoi punti di forza sono:
repository.
In Windows la finestra di comando di Git si può aprire con un clic destro del mouse su Esplora risorse o lanciare dall’icona nel menu di avvio. Il primo metodo è migliore perché ci porta subito nella cartella di lavoro.
Si noti che Git, nella versione con MinGW quella che racomando di installare, è accompagnato da un ambiente di lavoro simile a Unix, anche se nativamente Windows. Tutti i comandi che mostro in questi esempi, quindi, possono essere eseguiti indifferentemente su Windows, Linux e Mac OS.
Come creare un repository vuoto
Questo è ovviamente il primo step, l’atto di nascita di un archivio locale. Questo archivio può diventare condiviso e migrare su un server in rete locale o Internet in un momento qualsiasi. Ecco come si crea un repository vuoto dalla riga di comando:
Possiamo usare anche una delle estensioni della shell installate con Git per eseguire il compito direttamente da Esplora risorse. Conviene creare fin da subito un file .gitignore in cui specificare i file che non devono essere archiviati,
perché sono prodotti di compilazione e non sorgenti, quindi archiviarli e tracciarne le modifiche serve solo a sprecare spazio su disco e banda in rete. Ecco un gile .gitignore adatto per Visual Studio 2010
Aggiungere file all’archivio
Se interroghiamo Git sullo stato delle cose, con il comando git status otterremo una lista dei file nella cartella in cui abbiamo creato l’archivio, che per Git saranno tutti file di nuova creazione, dato che non sono in archivio. Ecco un esempio:
La pagina di messaggi di stato è abbastanza verbosa e ricca di suggerimenti, osserviamo da vicino alcuni messaggi.
ed ecco il risultato dell’aggiunta di tutto il contenuto della cartella all’archivio
Git:
Dalla lista dei file si vede che abbiamo un solo sorgente java, un immagine, un file audio e un batch file.
Aggiungere davvero i file all’archivio
Il commento Changes to be committed ci dice che questi sono i file che sarebbero messi in archivio se facessimo un commit, ma i file non sono ancora in cassaforte.
Git ci dà anche un altro consiglio: possiamo usare il comando:
per togliere file dall’elenco dei file da aggiungere.
Proviamo a rimuovere .DS_Store, un file di sistema di MacOS che archivia le personalizzazioni della visualizzazione della cartella in cui si trova, per esempio colore e posizione delle icone. Togliamo questo file dallo stage, cioè dal palco in cui si trova in attesa di fare la sua parte. Verifichiamo di nuovo la situazione
con il comando git status:
Il file .DS_Store non è più nell’elenco dei cambiamenti in attesa, ma nella lista dei file non tracciati. Un inserimento nel .gitignore ci leverà il pensiero di questo file.
Il file .DS_Store è sparito e il file .gitignore risulta modificato rispetto allo stage. Se usiamo, come suggerito, il comando:
possiamo tornare alla situazione precedente.
Proviamo la gui
Lanciamo l’interfaccia visuale di Git dalla riga di comando, con
Oppure eseguiamola dal menu contestuale della cartella in cui abbiamo inizializzato l’archivio Git.
Notiamo che alcuni file sono preparati per la nuova revisione, sono elencati nella parte bassa della barra laterale sinistra e nella parte centrale della finestra. Osserviamo anche che il file .gitignore, che abbiamo modificato di recente, è segnalato fra le modifiche non preparate nella Gui, mentre appare nella lista Changed but not updated nell’output del comando status.
La ragione è che il file non corrisponde a quello che risulta in archivio, perché è stato modificato. Quindi occorre manifestare la volontà di sostituire la copia corrente con un comando:
oppure con un clic sul pulsante Prepara modificati della Gui. Adesso, il
nostro insieme di modifiche comprende tutti i file del progetto, nella sua versione iniziale. Possiamo inserire un messaggio nel campo di testo in basso e spedire la release nell’archivio.
Se non avessimo aggiunto anche il file .gitignore alla lista dei file preparati, avremmo conservato la versione precedente. Ci sono spesso buone ragioni per non consegnare tutto all’archivio. A volte abbiamo modifiche definitive e stabili da archiviare insieme a file in rapida evoluzione che preferiamo tenere fuori dall’archivio, almeno fino a sera. Con Git è semplice farlo. Naturalmente, conviene fare un checkin dei sorgenti modificati piuttosto spesso, in modo da non affastellare insieme una giornata di lavoro spesa in dieci compiti diversi in una sola transazione.
Esercizi di stile
Conviene anche fare un checkin separato per ogni singola funzionalità, senza affastellare insieme tre modifiche diverse. Bisogna pensare il log dell’archivio di sorgenti come un giustificativo delle spese per il tempo:
modificato questo e questo sorgente per questa funzionalità. Può darsi che in alcuni contesti l’archivio dei sorgenti e il suo log siano usati per giustificare il tempo speso dal gruppo o aiutare a compilare i cartellini, ma anche nel caso del singolo sviluppatore hobbista, il log è un modo di ricostruire i propri passi.
Se si vuole vedere un uso creativo del log dell’archivio dei sorgenti c’è chi si è preso la briga di creare animazioni fantastiche della crescita di un progetto, il risultato è il progetto gource su Google code. Il messaggio di checkin deve essere chiaro e sintetico:
la regola dovrebbe essere che ogni gradino compiuto di funzionalità dovrebbe essere un checkin a sé e avere un titolo esplicativo, per esempio “spostamento dei pulsanti nella toolbar”. Rispettando questa regola diventa semplice tracciare l’impatto di una singola modifica e, eventualmente, disfare una singola fase di lavoro in caso di pentimento. Naturalmente è inutile descrivere cosa è stato cambiato, dato gli strumenti permettono di vedere molto chiaramente tutte le righe di codice associate a un determinato checkin e lo stato prima e dopo di ogni sorgente.
Quello che è importante spiegare è perché si è fatto quel determinato intervento. Quindi non dovremo scrivere “spostato initdevice() da start() a init()” ma piuttosto qualcosa tipo “risolto il problema dell’inizializzazione del lettore di smartcard al
primo uso”.
La storia siamo noi
La storia di un file è molto utile e può essere usata in diversi modi, per esempio ci si può domandare in che situazione si trovava un dato file il venti settembre, il giorno di una demo di successo, cosa è cambiato da allora, chi ha modificato il file e in che occasione.
La funzione che permette di esaminare un file insieme alla storia delle modifiche è chiamata blame, che sta per biasimare o, meglio, dare la colpa a qualcuno. In effetti lo scopo primario è trovare chi ha scritto la riga di codice in cui il programma si è schiantato nell’ultimo test, ma naturalmente trovare il colpevole non è l’approccio giusto in un gruppo ben pilotato. Per entrare in questa funzione
si deve:
Esiste un altro modo di tracciare la storia di un progetto,
lanciando gitk dalla riga di comando, o scegliendo Visualizza la cronologia di master dal menu Archivio. Questo strumento mostra il log delle revisioni, insieme alla lista dei file modificati in ciascuna revisione, ma lo si può anche usare per osservare un insieme di file.
Bisogna schiacciare il radio button directory per abilitare la visualizzazione di tutti i file e quindi scegliere dal menu contestuale associato alle foglie dell’albero, cioè ai singoli file, la voce Evidenzia solo questo o Evidenzia anche questo. I tasti prec e succ permettono di navigare fra le revisioni che contengono modifiche ai file evidenziati mostrando il messaggio di checkin e le variazioni subite dai file evidenziati.
Conclusioni
L’elenco di comandi di Git è notevolmente lungo e le pagine di manuale sono parecchie, ma gli strumenti che abbiamo descritto in questa analisi sono sufficienti per analisi approfondite e il controllo fine di un piccolo progetto gestito su un singolo computer.
Naturalmente, si può andare molto oltre installando Git in rete, o aprendo un account su github.com, il sito che ospita archivi Git gratuitamente per i progetti open source e a pagamento per i progetti a sorgente chiuso. Chi vuol seguire questa strada, naturalmente, farà bene a leggere più documentazione di quella che possiamo presentare in queste pagine.
La qualità del controllo e le prestazioni di Git ci hanno dato parecchie soddisfazioni nell’uso quotidiano. Certamente l’interfaccia è spartana e non all’altezza delle applicazioni native più sofisticate, ma stiamo parlando di applicazioni a pagamento. Esiste anche un’interfaccia integrata con Esplora risorse di Windows, che non abbiamo descritto qui, che deriva da un progetto sviluppato per Cvs di nome Tortoise-SVN. La versione per Git di Tortoise è certamente più gradevole da usare, ma abbiamo preferito ometterla per due ragioni:
Sistemi di controllo versione
Non ci sono scuse per non utilizzare il controllo di versione dei propri sorgenti. Spesso si sente obiettare che nessuno nel gruppo di sviluppo ha il tempo di configurare i server e gestirli, inoltre gli sviluppatori non hanno il tempo di imparare qualcosa di nuovo perché sono oberati di lavoro.A queste obiezioni si può rispondere che dovrebbe esserci nel gruppo di lavoro qualcuno dedicato proprio a configurare e gestire i server, perché se tutti i membri di un gruppo di sviluppo sono oberati di lavoro, vuol dire che un gruppo opera in emergenza, senza paracadute e con un piede sull’orlo del disastro. Quando ci sarà un errore o un problema, sarà difficilissimo ricostruire una situazione stabile o annullare una modifica che si è rivelata perniciosa.
In secondo luogo, per fortuna, non è così complicato usare un sistema di controllo delle versioni. Nella normalità giornaliera, infatti, si tratta solo di perdere qualche minuto ogni volta che il progetto ha raggiunto un obiettivo in scaletta e celebrare l’evento con un bel check-in, con la lista delle modifiche e degli obiettivi di business che sono stati raggiunti.
Ci sono circostanze in cui si deve realizzare una versione ad hoc di un programma, per uno specifico cliente, o si deve ricostruire lo stato della release consegnata nel 2007 per risolvere in fretta un problema, o si vuole tentare una strada nuova di sviluppo. Tutte queste domande hanno una risposta da parte di un sistema di controllo delle versioni.
Gli sviluppatori allo stato brado hanno solo la possibilità di conservare varie copie (e copie delle copie) in diversi livelli di aggiornamento e stabilità del software. Di queste copie si perde rapidamente il controllo. Inoltre questo sistema spreca spazio e espone al rischio di perdere dati. La conseguenza è che può diventare talmente complicato fare un piccolo aggiornamento puntuale su una consegna del 2007 che si è costretti ad aggiornare il cliente all’ultima versione,
magari con effetti secondari non proprio marginali, costi di intervento e fermo macchine.
Git
Vediamo una soluzione open source, di notevole versatilità: l’ambiente git. La motivazione per insistere su un software open source sta nel fatto che la soluzione di minore attrito è certamente in un ambiente gratuito, facile da installare per tutti i sistemi operativi di uso comune, come Windows, Mac OS e Linux. Naturalmente, se si usa un sistema di sviluppo specifico, come Visual Studio, e si opta per la soluzione proprietaria corrispondente, come Visual SourceSafe, ci sono da aspettarsi semplificazioni nell’uso quotidiano e una maggiore integrazione con l’ambiente di lavoro. Comunque ai giorni magri una soluzione a costo nullo è sempre più eloquente al tavolo della direzione, soprattutto in tempi difficili come questi.
Una soluzione open source ha un vantaggio specifico anche quando si opera dal cliente, su macchine che sono responsabilità di terzi. L’installazione di un sistema open source non espone chi è responsabile della conduzione delle macchine a obblighi di licenza.
Un po’ di preistoria
Il moderno capostipite degli ambienti di sviluppo open source è Cvs, un software che nasce sulle ceneri di Rcs, un ambiente di controllo delle versioni del codice nato per lo Unix Bsd, il capostipite degli Unix a sorgente aperto.
Dall’insofferenza per le limitazioni del Cvs è nato il progetto Subversion, il cui nome fa omaggio non solo al controllo delle versioni secondarie ma anche al desiderio di sovversione che ha ispirato lo sviluppo.
Cvs, però, nonostante sia stato per anni il motore di archiviazione dei sorgenti della Apache Software Foundation e di parecchi altri archivi open source, ha sollevato parecchie critiche e, come accade normalmente nella comunità open source, le critiche hanno fatto nascere server alternativi, come Mercurial, un ambiente realizzato in linguaggio Python, e Git, un motore nato dalle esigenze particolari del gruppo di sviluppo kernel di Linux e di Linus Torvalds, persona che si occupa dell’integrazione delle modifiche presentate dai diversi collaboratori.
Un kernel si compone di decine di migliaia di sorgenti, che assommano un numero di righe di codice dell’ordine del milione. Facile convincersi che il compito non è dei più semplici, anche perché i partecipanti al progetto non operano fianco a fianco nello stesso laboratorio, ma sono sparsi per il mondo. Il modello centralizzato di Cvs non si presta facilmente a questa organizzazione, perché può funzionare solo se il repository dei sorgenti è ospitato su Internet e accessibile a tutti i membri, che integrano uno alla volta e aggiornano spesso la propria copia dell’albero.
Più spesso capita che gli sviluppatori non interagiscano così di frequente, ma scarichino una release stabile su cui compiono diversi aggiornamenti fino ad avere un insieme di aggiornamenti che completa una funzionalità applicativa ed è stabile abbastanza da qualificarsi per un’integrazione.
Il modello di Cvs, con un unico albero condiviso, fallisce in questo caso, soprattutto perché chi sviluppa una funzionalità complessa è costretto a decidere fra caricare con frequenza giornaliera modifiche non stabili, o lavorare senza controllo delle versioni su un albero locale, due strade egualmente problematiche. Il problema si presenta sia che il gruppo di lavoro sia distribuito, sia che gli sviluppatori lavorino tutti nello stesso ufficio. Se si stabilisce la regola di mantenere stabile il repository Cvs per avere sempre una release testabile, si costringono i gruppi che hanno modifiche in sospeso a avere una macchina o un albero di sviluppo separato per ogni fase incompleta di sviluppo di una funzionalità.
Git nasce per risolvere questo e molti altri problemi di Cvs, come anche Mercurial, per questo è sembrato logico metterlo alla prova in un progetto che ha impegnato la maggior parte del secondo semestre del 2010.
Installazione
Git si installa scaricando la distribuzione da git-scm.com, il sito dedicato al programma. Per Windows ci sono due versioni, ma consiglio di scaricare msysgit, la meno invasiva. Ci sono due versioni di Git su Windows perché Git è sviluppato con un approccio tradizionalmente Unix alla costruzione di applicazioni, riciclando cioè il set di comandi di base particolarmente ricco e versato nel trattamento di testi del sistema più amato dai programmatori. Per questa ragione, Git non è un ambiente totalmente entrocontenuto, che realizza tutte le funzioni richieste, ma ha numerose dipendenze su un ambiente Unix esterno, su cui costruisce.
Dato che su Windows manca questo ambiente operativo, la scelta più logica è ricrearlo con una qualche emulazione. Ci sono due approcci al problema, quello dell’ambiente Cygnus, che ricrea la funzionalità di basso livello di un kernel Unix appoggiandosi su Windows e l’ambiente Mingw, che offre sostituti nativi Windows per i comandi Unix più comuni, come sh, vi, awk, wc.
Si tratta di due approcci abbastanza ortogonali: Cygnus mira a permettere di ricompilare senza modifiche sorgenti nati su Unix, Mingw si basa su porting di un set di comandi che permettono agli shell script più comuni di girare senza modifiche. Va da sé che l’ambiente che richiede di meno al sistema operativo e lo appesantisce di meno è il secondo. In più, dato che i comandi di Mingw sono applicativi nativi Windows che eseguono in un comune ambiente Windows, la compatibilità con il sistema operativo è intrinsecamente più a portata di mano.
In diversi mesi di collaudo, l’ambiente Git Mingw non ha perso un colpo. I comandi di Git funzionano correttamente, comprese le interfacce grafiche scritte in Tcl/tk. Si aggiunga che un set di comandi Unix facilmente a disposizione permette anche di prendersi qualche facile soddisfazione, che con Windows non è a portata di mano. Per esempio il comando per calcolare il numero di righe di codice C# a cui siamo arrivati, interessante per valutare i progressi del progetto giorno per giorno:
$ find . –name *.cs | xargs wc -l
Iniziare con Git
Per iniziare con il controllo delle versioni, si può aprire la shell di Git nella radice di un progetto Visual Studio e dare il comando:
$ git add .
Questo comando prenota un posto nell’indice locale di Git a tutti i file presenti nella directory corrente e nelle sue subdirectory. Si noti che questo aggiunge al repository tutti, ma proprio tutti, i file che incontra. Mentre noi siamo interessati ad archiviare e tracciare i sorgenti, non siamo, in generale, interessati a archiviare i prodotti di compilazione, come oggetti, eseguibili e file temporanei. Archiviare questi file appesantisce inutilmente il repository, aumentandone le dimensioni e allungando i tempi di lavoro in rete. Per evitare il problema, conviene compilare, prima del primo add, un file di nome .gitignore, che deve contenere su ogni riga le specifiche dei file da ignorare. Ecco un esempio di file .gitignore adatto per un progetto C#:
*.exe *.pdb *.baml *.dll *.tlog *.cache *.g.cs *.suo *.g.i.cs *.lref *.FileListAbsolute.txt *.Cache *.resources *.lref *.exe.config *.trx *.dat bin obj .trx *.Designer.vb *.user obj Test References *.vs10x
Dopo la add, non cambia nulla nel repository:
c’è distinzione fra comunicare l’intenzione di aggiungere un file e aggiungerlo effettivamente.Per inserire in archivio i file sorgenti usiamo il comando
$ git commit –m "versione iniziale"
la richiesta di commit corrisponde alla memorizzazione definitiva, come in un database. Come in ogni sistema per il controllo dei sorgenti, una commit deve essere accompagnata da un elenco di motivazioni per le modifiche al codice e di risultati raggiunti, che è tanto più di aiuto quanto più è circostanziato e conciso. Un breve messaggio si può passare sulla riga di comando, con l’opzione –m. Se non si usa l’opzione –m si apre una finestra con l’editor configurato, tipicamente vi.
La descrizione di un commit non deve esser un componimento, ma una motivazione per le modifiche di una data giornata di lavoro, che servirà a ricostruire il percorso quando sarà riesaminata. Dopo la commit troveremo nella
cartella di lavoro una directory di nome .git che contiene l’archivio dei sorgenti in un formato codificato e compresso che si rivela piuttosto efficiente, tanto che un archivio di sorgenti con tutta la sua storia può avere la stessa occupazione di disco della versione di lavoro dei sorgenti appena estratti.
Avere un repository locale è utilissimo, perché uno sviluppatore ha sempre a disposizione il paracadute, cioè la possibilità di ritornare a una versione stabile eliminando con sicurezza tutte le modifiche alla base di codice che si fossero rilevate un errore di percorso. È anche molto utile poter ripercorrere la storia di un sorgente per ricostruire il cammino che lo ha portato in un certo stato, esaminando la lista delle modifiche e dei rispettivi artefici.
Una rete flessibile
Una delle particolarità di Git è che l’archivio locale non è un lusso che ci si concede fino a che non si decide di utilizzare un repository centrale condiviso, ma piuttosto un’abitudine che si conserva in qualsiasi configurazione. Ogni sviluppatore, infatti, ha un archivio individuale in cui lavorare fino al compimento di un elemento stabile di funzionalità da propagare nella release. L’archivio locale può essere sincronizzato con un archivio centralizzato quando lo sviluppatore lo ritiene opportuno.
Questa organizzazione ha numerosi effetti benefici sull’organizzazione di un gruppo ed è supportata con molta semplicità attraverso comandi che agiscono sull’archivio locale, come checkout e commit, e comandi che sincronizzano il repository locale con il master: push e pull.
Per esemplificare, partecipare a un progetto già avviato comporta una pull iniziale, un checkout, qualche commit e una push finale.Ci sono anche casi in cui un repository centralizzato non si può creare, per la resistenza di un cliente o problemi di rete, per esempio nel caso di un gruppo che lavora in parte dal cliente e in parte in laboratorio.
In questi casi è molto semplice scambiarsi le modifiche: basta fare un archivio dei sorgenti e inviarlo su un server comune o spedirlo tramite email. Per esempio, si può fare fronte a una completa mancanza di infrastruttura da parte di un cliente utilizzando il sistema Dropbox per creare una cartella condivisa in rete e replicata su diversi sistemi. Ecco un semplice esempio di shell script, compatibile con il sistema Mingw installato insieme a Git, per archiviare il repository Git in una cartella condivisa con Dropbox
$ tar cvf ../../../My\ Dropbox/archivio-git.tar .git
Osserviamo anche che non c’è solo la riga di comando per operare con Git, anche se l’uso della riga di comando è alla portata di qualsiasi sviluppatore. Git è corredato da due strumenti con interfaccia grafica: gitgui e gitk. Questi strumenti possono essere lanciati da riga di comando, o dal menu contestuale di Gestione Risorse, attraverso un’estensione della shell caricata dall’installazione.
Nonostante la grafica spartana, si può navigare la storia di un albero di sorgenti rispondendo a diverse domande come:
- quali differenze ci sono fra l’archivio di sorgenti e i sorgenti estratti
- quali file sono stati modificati a settembre
- che aspetto aveva un dato file nella release 1.3 del 15 ottobre
- chi ha inserito un if nella riga 300 e in quale occasione.
In conclusione, fatto salvo Mercurial, su cui non abbiamo ancora indagato, mi sento di raccomandare Git come un deciso passo avanti rispetto ad altre soluzioni open source, oramai limitate.
Uso
Git è un gestore di sorgenti, un oggetto simile a SourceSafe o Cvs, e che i suoi punti di forza sono:
- un repository per ogni sviluppatore
- un’operatività che si adatta a singoli, gruppi coesi e gruppi sparpagliati geograficamente
- la disponibilità del sistema su Windows, Linux e Mac OS
- una licenza open source, che toglie il peso dei costi di licenza dall’uso quotidiano
repository.
In Windows la finestra di comando di Git si può aprire con un clic destro del mouse su Esplora risorse o lanciare dall’icona nel menu di avvio. Il primo metodo è migliore perché ci porta subito nella cartella di lavoro.
Si noti che Git, nella versione con MinGW quella che racomando di installare, è accompagnato da un ambiente di lavoro simile a Unix, anche se nativamente Windows. Tutti i comandi che mostro in questi esempi, quindi, possono essere eseguiti indifferentemente su Windows, Linux e Mac OS.
Come creare un repository vuoto
Questo è ovviamente il primo step, l’atto di nascita di un archivio locale. Questo archivio può diventare condiviso e migrare su un server in rete locale o Internet in un momento qualsiasi. Ecco come si crea un repository vuoto dalla riga di comando:
$ git init
Possiamo usare anche una delle estensioni della shell installate con Git per eseguire il compito direttamente da Esplora risorse. Conviene creare fin da subito un file .gitignore in cui specificare i file che non devono essere archiviati,
perché sono prodotti di compilazione e non sorgenti, quindi archiviarli e tracciarne le modifiche serve solo a sprecare spazio su disco e banda in rete. Ecco un gile .gitignore adatto per Visual Studio 2010
# oggetti e eseguibili *.exe *.pdb *.baml *.dll *.tlog *.cache *.suo # sorgenti creati dinamicamente *.g.cs *.g.i.cs *.lref *.FileListAbsolute.txt *.Cache *.resources *.lref *.trx *.dat *.trx *.Designer.vb *.user *.vs10x # cartelle create dalla compilazione bin obj # cartelle create da nunit Test References TestResults
Aggiungere file all’archivio
Se interroghiamo Git sullo stato delle cose, con il comando git status otterremo una lista dei file nella cartella in cui abbiamo creato l’archivio, che per Git saranno tutti file di nuova creazione, dato che non sono in archivio. Ecco un esempio:
$ git status # On branch master # # Initial commit # # Untracked files: # (use “git add...” to include in what will be committed) ## .DS_Store # build/ # images/ # pt.bat Macintosh:PlayTune mico$ git status # On branch master # # Initial commit # # Untracked files: # (use “git add ...” to include in what will be committed) ## .DS_Store # build/ # images/ # pt.bat
La pagina di messaggi di stato è abbastanza verbosa e ricca di suggerimenti, osserviamo da vicino alcuni messaggi.
- On branch master sta a significare che l’archivio è posizionato sul ramo master, cioè il principale. Potremmo creare rami paralleli,per esempio per la versione lite del nostro prodotto o una edizione su misura per un cliente. In questo caso Git segnalerebbe quale ramo è quello su cui operiamo.
- La lista di Untracked files è la lista di file che non sono in archivio. A regime, questa lista potrebbe essere vuota, quando tutti i file presenti sono menzionati nel .gitignore o sono parte dell’archivio. Si può anche decidere di aggiungere manualmente i soli file da tracciare, ma non è il modo da raccomandare, anche perché si perde il piacere di fare git add di una directory con la sicurezza che Git ignorerà i file già tracciati e i prodotti di compilazione, per aggiungere tutti e soli i sorgenti nuovi.
- Nel caso di questo progetto, che è un’applicazione Java, c’è un unico sorgente, che si trova nella cartella src. La cartella build è una cartella dedicata alla compilazione, come è d’abitudine quando si usa ant come motore di compilazione.
$ cat .gitignore PlayTune.app PlayTune.icns PlayTune.xcodeproj build untitled.xcclassmodel
ed ecco il risultato dell’aggiunta di tutto il contenuto della cartella all’archivio
Git:
$ git add .Macintosh:PlayTune mico$ git status # On branch master # # Initial commit # # Changes to be committed: # (use “git rm —cached...” to unstage) ## new file: .DS_Store # new file: .gitignore # new file: images/Ship01.pct # new file: pt.bat # new file: sin440.aif # new file: src/PlayTune.java #
Dalla lista dei file si vede che abbiamo un solo sorgente java, un immagine, un file audio e un batch file.
Aggiungere davvero i file all’archivio
Il commento Changes to be committed ci dice che questi sono i file che sarebbero messi in archivio se facessimo un commit, ma i file non sono ancora in cassaforte.
Git ci dà anche un altro consiglio: possiamo usare il comando:
$ git rm --cached
per togliere file dall’elenco dei file da aggiungere.
Proviamo a rimuovere .DS_Store, un file di sistema di MacOS che archivia le personalizzazioni della visualizzazione della cartella in cui si trova, per esempio colore e posizione delle icone. Togliamo questo file dallo stage, cioè dal palco in cui si trova in attesa di fare la sua parte. Verifichiamo di nuovo la situazione
con il comando git status:
$ git status# On branch master # # Initial commit # # Changes to be committed: # (use “git rm —cached...” to unstage) ## new file: .gitignore # new file: images/Ship01.pct # new file: pt.bat # new file: sin440.aif # new file: src/PlayTune.java # # Untracked files: # (use “git add ...” to include in what will be committed) ## .DS_Store
Il file .DS_Store non è più nell’elenco dei cambiamenti in attesa, ma nella lista dei file non tracciati. Un inserimento nel .gitignore ci leverà il pensiero di questo file.
$ git status # On branch master # # Initial commit # # Changes to be committed: # (use “git rm —cached...” to unstage) ## new file: .gitignore # new file: images/Ship01.pct # new file: pt.bat # new file: sin440.aif # new file: src/PlayTune.java # # Changed but not updated: # (use “git add ...” to update what will be committed) # (use “git checkout — ...” to discard changes in working directory) ## modified: .gitignore
Il file .DS_Store è sparito e il file .gitignore risulta modificato rispetto allo stage. Se usiamo, come suggerito, il comando:
$ git checkout — .gitignore
possiamo tornare alla situazione precedente.
Proviamo la gui
Lanciamo l’interfaccia visuale di Git dalla riga di comando, con
$ git gui
Oppure eseguiamola dal menu contestuale della cartella in cui abbiamo inizializzato l’archivio Git.
Notiamo che alcuni file sono preparati per la nuova revisione, sono elencati nella parte bassa della barra laterale sinistra e nella parte centrale della finestra. Osserviamo anche che il file .gitignore, che abbiamo modificato di recente, è segnalato fra le modifiche non preparate nella Gui, mentre appare nella lista Changed but not updated nell’output del comando status.
La ragione è che il file non corrisponde a quello che risulta in archivio, perché è stato modificato. Quindi occorre manifestare la volontà di sostituire la copia corrente con un comando:
$ git add .gitignore
oppure con un clic sul pulsante Prepara modificati della Gui. Adesso, il
nostro insieme di modifiche comprende tutti i file del progetto, nella sua versione iniziale. Possiamo inserire un messaggio nel campo di testo in basso e spedire la release nell’archivio.
Se non avessimo aggiunto anche il file .gitignore alla lista dei file preparati, avremmo conservato la versione precedente. Ci sono spesso buone ragioni per non consegnare tutto all’archivio. A volte abbiamo modifiche definitive e stabili da archiviare insieme a file in rapida evoluzione che preferiamo tenere fuori dall’archivio, almeno fino a sera. Con Git è semplice farlo. Naturalmente, conviene fare un checkin dei sorgenti modificati piuttosto spesso, in modo da non affastellare insieme una giornata di lavoro spesa in dieci compiti diversi in una sola transazione.
Esercizi di stile
Conviene anche fare un checkin separato per ogni singola funzionalità, senza affastellare insieme tre modifiche diverse. Bisogna pensare il log dell’archivio di sorgenti come un giustificativo delle spese per il tempo:
modificato questo e questo sorgente per questa funzionalità. Può darsi che in alcuni contesti l’archivio dei sorgenti e il suo log siano usati per giustificare il tempo speso dal gruppo o aiutare a compilare i cartellini, ma anche nel caso del singolo sviluppatore hobbista, il log è un modo di ricostruire i propri passi.
Se si vuole vedere un uso creativo del log dell’archivio dei sorgenti c’è chi si è preso la briga di creare animazioni fantastiche della crescita di un progetto, il risultato è il progetto gource su Google code. Il messaggio di checkin deve essere chiaro e sintetico:
la regola dovrebbe essere che ogni gradino compiuto di funzionalità dovrebbe essere un checkin a sé e avere un titolo esplicativo, per esempio “spostamento dei pulsanti nella toolbar”. Rispettando questa regola diventa semplice tracciare l’impatto di una singola modifica e, eventualmente, disfare una singola fase di lavoro in caso di pentimento. Naturalmente è inutile descrivere cosa è stato cambiato, dato gli strumenti permettono di vedere molto chiaramente tutte le righe di codice associate a un determinato checkin e lo stato prima e dopo di ogni sorgente.
Quello che è importante spiegare è perché si è fatto quel determinato intervento. Quindi non dovremo scrivere “spostato initdevice() da start() a init()” ma piuttosto qualcosa tipo “risolto il problema dell’inizializzazione del lettore di smartcard al
primo uso”.
La storia siamo noi
La storia di un file è molto utile e può essere usata in diversi modi, per esempio ci si può domandare in che situazione si trovava un dato file il venti settembre, il giorno di una demo di successo, cosa è cambiato da allora, chi ha modificato il file e in che occasione.
La funzione che permette di esaminare un file insieme alla storia delle modifiche è chiamata blame, che sta per biasimare o, meglio, dare la colpa a qualcuno. In effetti lo scopo primario è trovare chi ha scritto la riga di codice in cui il programma si è schiantato nell’ultimo test, ma naturalmente trovare il colpevole non è l’approccio giusto in un gruppo ben pilotato. Per entrare in questa funzione
si deve:
- lanciare l’interfaccia visuale di git, con il comando git gui dalla shell di Git, oppure il menu contestuale associato a una cartella in Windows.
- Scegliendo Archivio e quindi Esplora i file di master, si apre il ramo principale dell’archivio di sorgenti, di cui ci viene mostrata la radice con le cartelle di livello più elevato. Navigando fra le cartelle possiamo aprire il file che ci interessa elaborare. Il sorgente sotto analisi appare con una lista di annotazioni che fanno riferimento alle diverse revisioni a cui è stato sottoposto.
- Passando col mouse sopra una determinata revisione, appare il messaggio di log, la data e l’autore. Facendo un clic sopra una revisione si naviga nel sorgente visualizzando lo stato del file prima delle modifiche.
- Nella barra superiore appare una freccia che permette di navigare all’ indietro, come in un browser. La navigazione può proseguire a diversi livelli, fino alla prima stesura del file.
Esiste un altro modo di tracciare la storia di un progetto,
lanciando gitk dalla riga di comando, o scegliendo Visualizza la cronologia di master dal menu Archivio. Questo strumento mostra il log delle revisioni, insieme alla lista dei file modificati in ciascuna revisione, ma lo si può anche usare per osservare un insieme di file.
Bisogna schiacciare il radio button directory per abilitare la visualizzazione di tutti i file e quindi scegliere dal menu contestuale associato alle foglie dell’albero, cioè ai singoli file, la voce Evidenzia solo questo o Evidenzia anche questo. I tasti prec e succ permettono di navigare fra le revisioni che contengono modifiche ai file evidenziati mostrando il messaggio di checkin e le variazioni subite dai file evidenziati.
Conclusioni
L’elenco di comandi di Git è notevolmente lungo e le pagine di manuale sono parecchie, ma gli strumenti che abbiamo descritto in questa analisi sono sufficienti per analisi approfondite e il controllo fine di un piccolo progetto gestito su un singolo computer.
Naturalmente, si può andare molto oltre installando Git in rete, o aprendo un account su github.com, il sito che ospita archivi Git gratuitamente per i progetti open source e a pagamento per i progetti a sorgente chiuso. Chi vuol seguire questa strada, naturalmente, farà bene a leggere più documentazione di quella che possiamo presentare in queste pagine.
La qualità del controllo e le prestazioni di Git ci hanno dato parecchie soddisfazioni nell’uso quotidiano. Certamente l’interfaccia è spartana e non all’altezza delle applicazioni native più sofisticate, ma stiamo parlando di applicazioni a pagamento. Esiste anche un’interfaccia integrata con Esplora risorse di Windows, che non abbiamo descritto qui, che deriva da un progetto sviluppato per Cvs di nome Tortoise-SVN. La versione per Git di Tortoise è certamente più gradevole da usare, ma abbiamo preferito ometterla per due ragioni:
- La prima è che è meglio avere un’idea dei concetti di base prima di usare un’interfaccia che sparpaglia i comandi in posti diversi con una logica coerente, ma che richiede di avere in testa il modello di come funziona il controllo delle revisioni.
- La seconda ragione è che il progetto non è ancora molto maturo, anche se la revisione disponibile è stabile. Git nudo e crudo si è dimostrato perfettamente usabile, sempre molto veloce e all’altezza delle aspettative.
No comments:
Post a Comment
Note: Only a member of this blog may post a comment.