Perché Git?

Negli ultimi anni si è diffuso a macchia d’olio Git, un sistema di controllo di versione (VCS) distribuito, creato da Linus Torvalds nel 2005.

A differenza di altri VCS, Git permette di eseguire la maggior parte del lavoro sulla propria macchina, senza quindi avere bisogno di contattare continuamente il server.

Questo comporta alcuni vantaggi: il server non deve essere continuamente raggiungibile; Git è quasi sempre più veloce di SVN in quanto non lavora per file ma bensì per i contenuti degli stessi ed in questo modo non è necessario scaricare tutto il file se parte di esso è già nella copia locale.

L’unico “problema” è che per comprendere a fondo Git ci vuole un tempo piuttosto lungo, perché comporta un workflow diverso dal solito, se si è abituati a SVN.

Per diventare produttivi però bastano 15 minuti.

Perché non usare Git-SVN?

Git-SVN è un tool che permette di avere repo SVN e di usare Git lato client per il lavoro di tutti i giorni. Pur facendo bene il suo lavoro, Git-SVN possiede alcuni limiti, il più importante dei quali è il fatto che non supporti gli svn-externals.

Questi sono un modo comodo per importare altri repo o parti di essi all’interno di un progetto SVN.

Inoltre, Git-SVN non permette di usare alcune estensioni di Git come ad esempio git-lfs che consente di immagazzinare file di grandi dimensioni in un repo Git senza però degradare troppo le performance.

Questo limite è dovuto al fatto che il server è ancora SVN puro.

Migrazione

Girovagando un po’ sul web si trovano molti tutorial su come migrare una serie di repo SVN a Git.

Il workflow tipico è:

Migrazione SVN GIT

Pur essendo facilmente automatizzabile, questa procedura presenta qualche problema:

Queste problematiche ci hanno spinto alla creazione di un tool ad hoc per gestire la migrazione; è nato quindi Git-Externals.

Git-Externals

Il tool era inizialmente dedicato solo alla gestione degli externals, ma poi è diventato uno strumento utile per migrazioni che magari non coinvolgono affatto gli externals.

In realtà sotto questo nome cadono una serie di script Python per gestire in maniera modulare la migrazione:

Git-Externals è stato pensato fin dall’inizio per lavorare in 3 step:

$ gittify clone --authors-file authors-file.txt file:///var/lib/svn foo
$ gittify fetch --authors-file authors-file.txt file:///var/lib/svn foo
$ gittify cleanup foo $ gittify finalize foo
$ cd foo.git $ git remote add origin https://gitlab.com/bar/foo.git $ git push origin --all $ git push origin --tags

In questo modo si limitano al minimo i tempi di attesa.

Per quanto riguarda gli externals si è deciso di non usare nessuna soluzione già integrata con Git come i submodules, perché presentavano tutti delle limitazioni nel mappare gli externals. Nel caso specifico dei submodules il problema è che permettono di specificare solo un intero repo Git come submodule, quando una delle poche cose comode di SVN è il fatto che permette di effettuare il checkout (/clonare) solo di una parte del repo. Abbiamo quindi deciso che era giunto il momento di creare qualcosa di simile ai submodules ma più flessibile: git-externals.

È interessante notare come in realtà git-externals possa venire usato in un repo Git mai migrato da SVN, perché esso di per sé non ha alcuna nozione di SVN. Ad esempio, può essere un’alternativa più comoda dei submodules quando
occorre una maggiore flessibilità nella gestione degli externals. I submodules funzionano bene quando c’è una dipendenza fra progetti davvero indipendenti come ciclo di sviluppo. Quando invece si sviluppano contemporaneamente il progetto principale e il sottomodulo, allora è “legnoso” da usare perché bisogna indicare dal repository principale il commit del sottomodulo che si vuole usare e, quindi, ogni volta che si aggiorna il sottomodulo, bisogna aggiornare anche il riferimento.

Tutto quello di cui ha bisogno è un file JSON git_externals.json in .git/externals. Questo file viene creato automaticamente se si usano gli script per la migrazione, ma è anche possibile modificarlo con lo script git-externals stesso.

Ad esempio, i seguenti comandi andranno ad aggiungere 2 externals al repo corrente:

$ git externals add --branch=master https://gitlab.com/gitlab-org/gitlab-ce.git shared/ foo

$ git externals add --branch=master https://gitlab.com/gitlab-org/gitlab-ce.git shared/ bar

$ git externals add --branch=master https://gitlab.com/gitlab-org/gitlab-ce.git README.md baz/README.md

$ git externals add --tag=v4.4 https://github.com/torvalds/linux.git Makefile Makefile

$ git add git_externals.json

$ git commit -m "DO NOT FORGET TO COMMIT git_externals.json!!!"

Si noti come per rendere effettiva l’aggiunta di questi externals si debba committare il file di configurazione git_externals.json; questo per rendere tracciabile anche l’aggiornamento delle versioni degli externals.

Vantaggi

Git-Externals cerca di mappare il più fedelmente possibile alcune delle feature di SVN in Git. Ad esempio, la capacità di SVN di scaricare solo parti di repo viene mappata in Git attraverso lo sparse-checkout.

Tuttavia questo risulta un po’ noioso da usare soprattutto se vogliamo eseguire lo sparse-checkout su un submodule. Git-Externals nasconde tutto questo all’utilizzatore finale, perché il processo di configurazione per lo sparse-checkout è integrato direttamente dentro Git-Externals.

Inoltre git-externals presenta un buon numero di comandi accessori per gestire gli externals. Per esempio, è possibile guardare il diff o lo stato su tutti gli externals(o una parte di essi) con semplici comandi.

$ git externals status

$ git externals status ext1 ext1

$ git externals diff

Inoltre è possibile aggiornare tutti gli externals alle versioni specificate in git_externals.json con git externals update.

In ogni caso git externals --help è tuo amico.

Limitazioni

Sotto il cofano git-externals utilizza i symlinks per mappare la posizione reale di un external alla posizione desiderata nel repo principale. Questo perché se si copiasse un file dall’external nel progetto e lo si modificasse ci sarebbe un disallineamento tra la versione usata dal repo principale e quella nell’external.

Il problema nasce dal fatto che Windows permette di usarli solo agli utenti con Admin Privileges. Abbiamo deciso di convivere con questo, perché crediamo che non rappresenti un grande ostacolo per la maggior parte degli sviluppatori Windows.

Inoltre, dal momento che possiamo usare come external una sottodirectory di un repository, alcuni path relativi usati all’interno della sottodirectory potrebbero semplicemente non essere presenti. Ad esempio, se abbiamo un external che assume di trovare nella parent directory un file foo.baz, molto probabilmente quel file non sarà lì perché la parent directory dell’external non corrisponde a quella del progetto. Crediamo però che questi casi non debbano esistere, perché semanticamente questo vorrebbe dire che una dipendenza fa assunzioni sulla configurazione del progetto, rendendo la dipendenza non facilmente riutilizzabile in altri progetti.

Happy gittifying!