Informatica per script kiddies 15 – Versionamento e git

Eccoci con il quindicesimo Informatica per script kiddies, in cui parleremo di uno degli strumenti più utili nella programmazione di software (ma anche nella vita in generale, ne sogno un’applicazione ai codici legislativi), che per qualche motivo è anche uno dei meno conosciuti ai programmatori in erba: il controllo versione.

Parlerò poi nello specifico delle basi (ma proprio basi-basi) di git, il sistema di controllo versione ideato e usato per il kernel Linux ed estremamente popolare. Ne parlo perché è davvero molto diffuso e perché è quello che conosco meglio (assieme a svn ormai obsoleto, grazie al cielo). Sono ben consapevole che ne esistono altri, spesso migliori sotto alcuni aspetti, e invito i loro sostenitori a parlarne nei commenti (anche perché poi me li curioso).

Cercherò di essere un po’ più breve e semplice del solito, che a questo giro il tempo è limitato (grazie, OVH, avevo bisogno di una bella botta di disaster recovery), ma alla fine è una cosa così facile che lo permette anche.

Cosa è il versionamento

Quando si scrive un programma – e qualsiasi altra cosa – ci si accorge ben presto che è di grandissima importanza mantenere diverse versioni di quello che si sta facendo.

Si scrive una funzionalità, va alla grandissima, le si aggiunge un pezzo, non funziona più niente. A questo punto, ricordarsi cosa si è cambiato da quando funzionava e tornare al punto in cui funzionava è praticamente impossibile.

Questa dura esperienza di vita è la madre della cosa più orrenda che si possa trovare su un PC: le cartelle contenenti cartelle chiamate programma_funzionante, programma_funzionante_definitivo, programma_funzionante_definitivo_nuova_versione e via dicendo.

Il versionamento è l’automazione, fatta con criterio, di questa cosa.

Come funziona

L’idea alla base del versionamento è estremamente semplice. Chi usa un sistema di questo tipo, si cura di salvare come “nuova versione” il suo lavoro ogni volta che fa un set completo, ma anche non completissimo (sul lavoro dipende tanto dai gusti del PM o del tecnico che ne fa le veci), di modifiche.

Il sistema si curerà, mentre tu lavori sempre sugli stessi file senza farne copie, di salvarsi una traccia di cosa è cambiato nei file dall’ultima versione a quella corrente, e permetterà in qualsiasi momento di tornare a una versione precedente del lavoro (e di tornare poi alla corrente, o a qualsiasi altra).

L’atto di salvare una nuova versione del lavoro si chiama commit (e per estensione viene chiamata così anche la versione, che tecnicamente si chiama revisione). Ogni commit va accompagnato da una descrizione di cosa si è fatto, breve quanto basta a renderla utile e facile da consultare.

I sistemi di versionamento in genere dispongono di una serie di strumenti, a volte parte del sistema e a volte esterni, per consultare comodamente la lista di tutte le revisioni e le differenze tra l’una e l’altra, rendendo comoda l’analisi delle cose fatte e il debug in caso di problemi.

Cos’altro può fare

Dato il loro grande utilizzo nell’ambito della programmazione software, i sistemi di versionamento devono supportare una caratteristica importante di questo ambiente: i software vengono quasi sempre scritti in più persone.

Questi sistemi, quindi, di solito e in maniera più o meno efficiente, permettono a più persone di creare versioni parallele degli stessi file, occupandosi poi di ricongiungerle in maniera semiautomatica con la creazione di un nuovo commit che descriva le differenze tra le versioni parallele.

Questo li rende al momento uno degli strumenti più potenti per permettere la scrittura a più mani di programmi, e il principale motore dell’innovazione del modo in cui si scrive software. Cose oggi diffusissime, come le tecniche di agile development, sarebbero ai fatti impossibili senza dei robusti sistemi che permettano di avere traccia delle revisioni e di collaborare senza pestarsi eccessivamente i piedi.

Come funziona Git

Git, come detto, è uno dei sistemi di versionamento più diffusi oggi. Coprirne ogni aspetto è praticamente impossibile in un articolo come questo, e coprirne le tecniche di utilizzo è poco utile (ne esistono di standard, ma ai fatti ogni PM ha le sue e le adatta alla struttura organizzativa dei singoli progetti), ma cercherò di spiegare le basi a chi ci si approcciasse ora.

Il suo enorme successo è legato a doppio filo sia al fatto che è utilizzato per Linux (ed è ideato dalla stessa persona, Linus Torvalds), all’effettiva grande efficienza nel gestire codebase di grandi dimensioni, e al successo di GitHub, un server Git diventato sostanzialmente il principale social network di sviluppatori di software libero.

Git è un sistema di versionamento distribuito, che permette di mantenere sulla macchina di ogni collaboratore a un progetto una sua versione dello storico delle modifiche. Per semplicità, però, inizialmente, fingiamo che sia utilizzabile solo da un singolo utente.

Le basi

Una volta installato Git, per iniziare ad usarlo sui file presenti in una directory basta dare, in quella directory, il comando git init. A questo punto, Git considererà quella directory (e tutto il suo albero) un repository, ovvero un set di file di cui manterrà la versione.

Un file, agli occhi di Git, può essere in diversi stati. Quelli che ci interessano al momento sono:

  • Untracked, ovvero non sotto controllo versione
  • New ovvero sotto controllo versione e non presente nell’ultima revisione
  • Modified ovvero sotto controllo versione e diverso dall’ultima revisione
  • Tracked ovvero sotto controllo versione e identico all’ultima revisione

I file, se non sono Untracked, possono essere Staged e Not staged.

L’operazione di commit, che si fa con il comando commit -m "Commento", provvede a salvare nella nuova revisione i file Staged, ignorando quelli Unstaged.

Far diventare un file Staged è molto semplice: git add nomefile. Tirarlo fuori dallo stage è invece una cosa che dipende un po’ da dove vuoi metterlo, e git status spiega come fare per ogni gruppo di file.

I branch

Git prevede che sia possibile ramificare lo sviluppo su più linee, dette branch.

Questa funzionalità è molto comoda: si inizia a lavorare su un ramo principale (il ramo iniziale di default si chiama master). Se per qualche motivo si deve interrompere il lavoro per sviluppare una parte collaterale del software, come ad esempio una specifica funzionalità, si può creare un branch che sarà il clone del principale e lavorare su quello.

In qualsiasi momento si può poi saltare da un ramo all’altro, tornando ad avere il codice come lo si aveva prima di iniziare il lavoro collaterale. Quando si è finito di lavorare sul branch, lo si può riunire al principale.

Un utilizzo molto basilare e molto classico è quello di tenere sul ramo solo i rilasci veri e propri del software, per poi svilupparlo su uno o più branch secondari. A ogni rilascio, si riuniscono tutti i branch secondari sul principale. In qualsiasi momento, è possibile così saltare dalla versione “di sviluppo” del software all’ultima rilasciata.

Per creare un branch, si dà il comando git branch nome_del_branch, mentre per passare a un branch il comando è git checkout nome_del_branch. Il comando git checkout si può utilizzare anche per saltare a una qualsiasi revisione (i branch non sono altro che etichette sull’ultima revisione di un ramo), dando come nome l’id della revisione ottenuto con git log.

Per riunire due branch, invece, si può, essendo in quello di destinazione, dare il comando git merge sorgente. Git tenterà di riunire automaticamente i due branch, richiedendo di fare operazioni a mano sui file, che marca come In conflitto, quando non riesce.

L’utilizzo da parte di più utenti

Dicevamo che Git è un sistema distribuito. Ogni utente, infatti, può possedere il suo repository dello stesso progetto e fare i suoi branch e i suoi commit indipendentemente da quello che fanno gli altri.

A livello logico, ogni copia di ogni utente funziona come un branch a sé. Gli utenti possono fare una coppia di operazioni per tenersi sincronizzati con un server centrale.

Il pull (git pull) è un’operazione che permette di scaricare da un server (detto remote e gestibile con i comandi git remote) lo stato più aggiornato che ha per quel repository, e il push (git push) serve a caricare su un server la propria versione.

La procedura per aggiornarsi è quindi fare pull per ottenere le ultime modifiche. Git a questo punto tenterà un merge automatico, richiedendo di fare le operazioni manuali se fallisce. Aggiornato il proprio repository con le modifiche correnti, si può fare push per caricare sul server comune le proprie revisioni.

Queste sono più o meno le basi. Mi aspetto domande sul post Reddit, ché stavolta di cose per scontate ne ho date un po’ più del solito.