Mi ricordo piuttosto bene il 10 Gennaio 2010: quello fu il giorno in cui perdemmo la cronologia completa di un progetto. Come version control system stavamo usando Subversion, che teneva la cronologia del progetto in un repository centrale su un server e noi facevamo regolarmente il backup di questo server, o perlomeno pensavamo fosse così. Poi, il server si ruppe e il backup non era andato a buon fine. Il nostro progetto non era completamente perso, ma tutte le versioni storiche erano andate.
Migrammo a Git poco dopo la rottura del server. Avevo sempre visto il version control come una tortura: era troppo complesso e non abbastanza utile perché potessi vederne il valore, ma lo usavo comunque per dovere. Però, dopo aver passato un po’ di tempo sul nuovo sistema, cominciai a capire quanto potesse essere utile Git. Da allora, mi ha salvato la pelle in molte situazioni.
Nel corso di questo articolo, vi mostrerò come Git possa aiutarvi ad evitare gli errori e come rimediarvi se sono già successi.
Ogni collega del team è un backup#section1
Dal momento che Git è un version control system distribuito, ogni membro del vostro team che ha clonato un progetto (o che ha fatto “check out” se venite da Subversion) ha automaticamente un backup sul proprio disco. Questo backup contiene la versione più recente del progetto così come la sua storia completa.
Questo significa che se la macchina locale di uno sviluppatore o addirittura il nostro server centrale dovessero mai rompersi ancora (e il backup non funzionare per una ragione qualsiasi), saremmo di nuovo “up and running” nel giro di pochi minuti: ogni repository locale dal disco di un collega del team è tutto quello di cui abbiamo bisogno per una sostituzione completamente funzionante.
I branch mantengono separate quel che deve rimanere separato#section2
Non sono subito impazzito di gioia quando i miei colleghi più tecnici mi avevano parlato di quanto fosse “cool” il branching in Git. Primo, devo ammettere che non avevo davvero compreso i vantaggi del branching e, secondo, venendo da Subversion, mi ricordavo fin troppo bene quanto la procedura fosse complessa e quanto facile commettere errori. Dati questi brutti ricordi, l’idea di lavorare con i branch mi metteva ansia e quindi avevo provato ad evitarli quando era possibile.
Mi ci è voluto un po’ di tempo per comprendere che il branching e il merging funzionano in maniera completamente differente in Git rispetto alla maggior parte degli altri sistemi, specialmente per la loro facilità di utilizzo! Quindi, se avete imparato il concetto di branching da un altro version control system (come Subversion), vi consiglio di dimenticarvi questa vostra conoscenza pregressa e di iniziare da zero. Cominciamo innanzitutto dal capire perché i branch sono così importanti.
Perché i branch sono essenziali#section3
Quando in passato non usavo i branch, lavorare su una nuova feature era un disastro. Praticamente, potevo scegliere tra due workflow, entrambe pessimi:
(a) Sapevo già che fare dei piccoli commit granulari con solo pochi cambiamenti era una buona abitudine di version control. Tuttavia, se lo facevo mentre stavo sviluppando una nuova feature, ogni commit mischiava la mia feature completa a metà con la code base principale fino a che non avevo finito. Per i miei colleghi, non era piacevole trovare dei bug nel progetto introdotti dalla mia feature non ancora completata.
(b) Per evitare di mischiare il mio “work in progress” con altri argomenti (sia dei miei colleghi sia miei), lavoravo su una feature in un mio spazio separato. Creavo una copia della cartella del progetto con cui avrei potuto lavorare tranquillamente e facevo poi il commit una volta completata la mia feature. Ma fare il commit dei miei cambi solo alla fine produceva un unico commit gigante, riempito di tutti i cambiamenti. Né io né i miei colleghi potevamo capire cosa fosse successo esattamente in questo commit quando ci saremmo tornati in un secondo momento.
Pian piano, capii che dovevo familiarizzare con i branch se volevo migliorare le mie capacità di programmatore.
Lavorare nei contesti#section4
Ogni progetto ha più contesti in cui avviene il lavoro: ogni feature, bug fix, esperimento o alternativa del prodotto è in effetti un contesto a sé stante. Può essere visto come un suo “topic” chiaramente separato dagli altri topic.
Se non separate questi topic tra loro con il branching, inevitabilmente, aumenterete il rischio di avere problemi. Mischiare diversi topic nello stesso contesto:
- rende difficile mantenere una visione globale e con molti topic, diventa quasi impossibile;
- rende difficile annullare qualcosa si è scoperto contenere un bug, perché è già mischiata a molte altre cose;
- non incoraggia a sperimentare e a provare cose nuove, perché è difficile togliere il codice sperimentare dal repository una volta che è mischiato con il codice stabile.
L’utilizzo dei branch mi ha dato la sicurezza che non avrei distrutto nulla. Nel caso le cose prendano una brutta piega, potrei sempre tornare indietro, annullare e ricominciare, oppure cambiare il contesto.
Branching: le basi#section5
Il branching in Git coinvolge solo qualche comando. Per cominciare, osserviamo il workflow di base.
Per creare un nuovo branch basandosi sullo stato corrente, tutto quello che dovete fare è scegliere un nome ed eseguire un unico comando da riga di comando. Supponiamo di voler cominciare a lavorare su una nuova versione della nostra form di contatto e quindi creiamo un branch chiamato “contact-form”:
$ git branch contact-form
Usando il comando git branch
senza specificare un nome farà elencare tutti i branch che abbiamo al momento (e la flag “-v” ci fornisce pochi dati in più rispetto al solito):
$ git branch -v

Potreste ora aver notato il piccolo asterisco sul branch di nome “master”: esso indica il branch attualmente attivo. Quindi, prima di cominciare a lavorare sulla nostra contact form, dobbiamo rendere quest’ultimo il contesto attivo:
$ git checkout contact-form
Adesso Git ha reso questo branch il contesto di lavoro attuale. (Nel gergo di Git, questo viene chiamato “HEAD branch”). Tutti i cambiamenti e qualunque commit faremo d’ora in poi influenzerà solo quest’unico contesto, gli altri contesti non verranno toccati. Se vogliamo cambiare il contesto ad un altro branch, dobbiamo semplicemente usare di nuovo il comando git checkout
.
Nel caso vogliamo integrare i cambiamenti da un branch in un altro, possiamo farne il “merge” nel contesto di lavoro attuale. Immaginate che abbiamo lavorato sulla nostra feature “contact-form” per un po’ e adesso vogliamo integrare questi cambiamenti nel nostro branch “master”. Tutto quello che dobbiamo fare è tornare a questo branch e chiamare git merge:
$ git checkout master
$ git merge contact-form
Usare i branch#section6
Vi consiglio caldamente di usare i branch il più possibile nel vostro workflow quotidiano. I branch sono uno dei concetti chiave su cui è costruito Git. Sono estremamente economici e facili da creare, nonché semplici da gestire e ci sono moltissime risorse se siete pronti per imparare di più sul loro utilizzo.
Annullare azioni#section7
C’è una cosa che ho imparato come programmatore nel corso degli anni: gli errori si fanno, indipendentemente da quanta esperienza si abbia. Non si possono evitare ma si possono tenere a portata di mano degli strumenti che ci aiutino a recuperare.
Una delle feature migliori di Git è che si può fare l’undo di quasi tutto. Questo mi ha dato la fiducia necessaria per provare cose senza paura, perché finora non sono riuscito a distruggere davvero qualcosa oltre il recovery.
Correggere l’ultimo commit#section8
Anche se fate i vostri commit molto attentamente, è fin troppo facile dimenticarsi di aggiungere un cambio o scrivere male un messaggio. Con la flag —amend
del comando git commit
, Git ci permette di cambiare l’ultimissimo commit ed è una fix molto semplice da eseguire. Per esempio, se vi siete dimenticati di aggiungere una certa modifica e avete anche fatto un errore di battitura nell’oggetto del commit, potete facilmente correggerli:
$ git add some/changed/files
$ git commit --amend -m "The message, this time without typos"
C’è solo una cosa che dovreste tenere a mente: non dovreste mai fare l’amend di un commit che è già stato inviato a un repository remoto. Rispettando questa regola, l’opzione “amend” è un grande piccolo aiutante per sistemare l’ultimo commit.
(Per ulteriori dettagli sull’opzione amend
, vi raccomando l’eccellente guida di Nick Quaranto).
Fare l’undo dei cambiamenti locali#section9
I cambiamenti di cui non si è ancora fatto il commit sono detti “locali”. Tutte le modifiche che sono attualmente presenti nella vostra directory di lavoro sono cambiamenti “local” non “committed”.
Scartare questi cambiamenti può anche aver senso quando il vostro lavoro attuale è… beh… peggiore di quello che avevate prima. Con Git, potete facilmente fare l’undo dei cambiamenti locali e cominciare da capo con l’ultima versione del vostro progetto di cui avete fatto il commit.
Se volete ripristinare un singolo file, potete usare il comando git checkout
:
$ git checkout -- file/to/restore
Non confondete questo utilizzo del comando checkout
con il cambio dei branch (vedi sopra). Se lo usate con due trattini (separati con uno spazio!) e il percorso del file, eliminerà i cambiamenti di cui non si è fatto il commit in un dato file.
Tuttavia, in una giornata cattiva, potreste addirittura voler eliminare tutti i cambiamenti locali e ripristinare l’intero progetto:
$ git reset --hard HEAD
Questo rimpiazzerà tutti i files nella vostra directory di lavoro con l’ultima revisione di cui avete fatto il commit. Proprio come l’utilizzo del comando checkout sopra, questo eliminerà i cambiamenti locali.
State attenti con queste operazioni: dal momento che non avete fatto il check in un repository dei cambiamenti locali, non c’è modo di riaverli una volta eliminati!
Fare l’undo dei cambiamenti di cui si è fatto il commit#section10
Ovviamente, l’annullamento delle cose non è limitato solo ai cambiamenti locali. Potete anche fare l’undo di alcuni commit quando è necessario, per esempio, se avete introdotto un bug.
Praticamente, ci sono due comandi principali per fare l’undo di un commit:
(a) git reset#section11

Il comando git reset
fa davvero tornare indietro nel tempo. Gli dite a che versione volete tornare ed esso ripristina esattamente questo stato, annullando tutti i cambiamenti che sono avvenuti dopo questo punto nel corso del tempo. Semplicemente, dategli lo “hash ID” del commit a cui volete tornare:
$ git reset -- hard 2be18d9
L’opzione —hard
è l’approccio più semplice e pulito, ma elimina anche tutti i cambiamenti locali che potreste ancora avere nella vostra directory di lavoro. Quindi, prima di fare così, assicuratevi che non ci sia alcun cambiamento locale che desiderate ardentemente.
(b) git revert#section12

Il comando git revert
è usato in uno scenario differente. Immaginatevi di avere un commit che non volete più, ma i commit che sono venuti dopo sono ancora importanti per voi. In questo caso, non dovrete usare il comando git reset
perché annullerebbe anche tutti i commit successivi!
Tuttavia, il comando revert
fa il revert solo degli effetti di un certo commit. Non rimuove alcun commit, come invece fa git reset
. Al contrario, crea addirittura un nuovo commit, il quale introduce i cambiamenti che sono solo l’opposto del commit che deve essere ripristinato. Per esempio, se avete cancellato una certa riga di codice, revert
creerà un nuovo commit che introduce di nuovo proprio questa riga.
Per usarlo, passategli semplicemente lo “hash ID” del commit di cui volete fare il ripristino:
$ git revert 2be18d9
Trovare i bug#section13
Quando si tratta di scovare i bug, devo ammettere di aver passato molto tempo brancolando nel buio. Spesso sapevo che un paio di giorni prima funzionava, ma non avevo idea in che punto esattamente le cose fossero andate storte. Fu solo quando scoprii git bisect
che il processo cominciò a velocizzarsi un po’. Con il comando bisect
, Git fornisce uno strumento che ci aiuta a trovare il commit che ha introdotto un problema.
Immaginatevi questa situazione: sappiamo che la nostra versione attuale (taggata con “2.0”) ha dei problemi. Sappiamo anche che un paio di commit fa (la nostra versione “1.9”) tutto andava bene. Il problema deve essere successo da qualche parte fra questi due punti.

Questa informazione è già sufficiente per cominciare la nostra caccia al bug con git bisect
:
$ git bisect start
$ git bisect bad
$ git bisect good v1.9
Dopo aver iniziato il processo, avevamo detto a Git che il nostro commit attuale conteneva il bug e pertanto è “bad”. Poi, abbiamo anche detto a Git quale commit precedente funziona effettivamente (come parametro per git bisect good
).
Quindi, Git ripristina il nostro progetto a metà tra le condizioni note di bad e good:

Adesso, testiamo questa versione (per esempio, facendo degli unit test, creando l’app, facendone il deploy su un sistema di testing, etc.) per trovare se questo stato funziona, o se contiene già il bug. Non appena lo scopriamo, lo diciamo di nuovo a Git, o con git bisect bad
o git bisect good
.
Supponiamo di aver detto che questo commit era ancora “bad”. Questo significa effettivamente che il bug deve essere stato introdotto ancora prima e Git restringerà i commit in questione:

In questo modo, troverete rapidamente il punto esatto in cui è successo il problema. Una volta che lo saprete, dovrete chiamare git bisect reset
per terminare la caccia al bug e ripristinare lo stato originale del progetto.
Uno strumento per salvarvi la pelle#section14
Devo confessare che il mio primo incontro con Git non è stato amore a prima vista. All’inizio, sembrava proprio come tutte le altre esperienze con il version control: tedioso e inutile. Ma col tempo, la pratica è diventata intuitiva e si è guadagnato la mia fiducia.
Dopo tutto, gli errori capitano, non importa quanta esperienza abbiate o quanto duramente cerchiamo di evitarli. Quello che separa i professionisti dai principianti è la preparazione: aver configurato un sistema su cui fare affidamento in caso di problemi. Vi aiuta a stare aggiornati sulle cose, specialmente in progetti complessi. E, in ultima analisi, vi aiuta a diventare professionisti migliori.
Riferimenti#section15
- Sentitevi liberi di saperne di più su amend, revert e reset dei commit.
- Familiarizzate con “git bisect” con questo esempio dettagliato.
- Una dettagliata introduzione al branching.
Illustrazioni: {carlok}
Nessun commento
Altro da ALA
Webwaste
Uno strumento essenziale per catturare i vostri progressi lavorativi
Andiamo al cuore dell’accessibilità digitale
JavaScript Responsabile, Parte II
JavaScript Responsabile: parte prima