Git è il sistema di controllo versione del codice (“version control system”, VCS) più famoso ed utilizzato al mondo, creato a metà anni 2000 dalla comunità Linux (e in particolare da Linus Torvalds) per poter gestire più facilmente lo sviluppo di Linux, e da allora è stato adottato da innumerevoli sviluppatori e compagnie. Qualche nome? Google, Facebook, Microsoft, Netflix, LinkedIn. Git è indirizzato agli sviluppatori web e software -ma non solo, visto che l’uso non è ristretto a determinati tipi di file e quindi può essere utilizzato in diversi campi- e l’idea di base è quella di avere un sistema che permetta di avere a disposizione varie versioni dei file, ognuna corrispondente ad un salvataggio particolare. In questo articolo spiegheremo i vantaggi, i concetti chiave e la struttura di Git, cosa che ci condurrà inevitabilmente ad un’unica conclusione possibile: affidarsi ad Hosting Provider qualificati, che utilizzano tecnologie di alto livello, è il solo metodo vincente per lo sviluppo di siti web. I vantaggi dell’uso di Git per gestire il codice sonoevidenti: se si commette un errore è facile “tornare indietro” ad una versione precedente e funzionante del file, è presente uno storico delle modifiche, aiuta nella gestione e documentazione in termini di sviluppo codice incrementale, permette di avere diverse versioni del codice e di avere più persone che lavorano sullo stesso file senza creare conflitti, si possono sperimentare nuove opzioni o feature senza toccare il codice originale ed altro ancora che verrà scoperto con l’esperienza. Git è disponibile per Windows, Linux o Mac; normalmente si usa da riga di comando, ma sono disponibili delle interfacce grafiche.
FTP: un metodo svantaggioso
Prima dei software di controllo del codice, e per la verità tutt’ora nonostante non sia conveniente, i siti venivano gestiti interamente da FTP. Intendiamoci: l’FTP rimane un valido supporto alle attività di gestione, ma in alcuni casi mostra tutti i suoi limiti. File cancellati per errore o messi nella cartella sbagliata perché non ci si è accorti, duplicati non necessari, file caricati inavvertitamente, lentezza in upload e download di tanti file, difficoltà nel lavorare in più persone sullo stesso sito, nessun sistema di versioning dei file, nessun registro delle modifiche… certo, se il sito consiste in un file HTML e in un file CSS, allora FTP può essere una soluzione papabile, eppure manca un sistema per poter ripristinare una versione precedente se il codice si rompe. Insomma, FTP va bene ma Git è un’alternativa molto più vantaggiosa che corregge la gran parte dei problemi che FTP porta naturalmente con sé.
Concetti chiave e struttura
Il repository: L’unità fondamentale di Git è il repository, abbreviato in repo. Un repository, che in inglese significa ripostiglio, deposito, è una struttura di dati relativa al progetto in considerazione e contiene sia i file della cartella di lavoro (working directory) sia gli oggetti dei commit e sia i file di riferimento ai commit, chiamati heads. Gli oggetti sono costituiti da 3 elementi, cioè i file che rappresentano lo stato del progetto al momento del commit, i riferimenti agli oggetti alla commit madre (parent commit) e una stringa SHA-1 che identifica univocamente l’oggetto; le heads invece, sono dei semplici riferimenti alle commit che le identificano velocemente. Ogni repo può avere diverse commit e diverse head.
Il repository può essere remoto, cioè presente su server di terzi (è il caso di BitBucket e GitHub, entrambi supportati da Plesk, ma anche Plesk stesso permette di essere usato come repo) o presenti sulla rete locale, oppure locale alla macchina con cui si lavora. RocketWeb Hosting supporta sia i repo locali sia i repo remoti, al fine di garantire la massima flessibilità d’uso: essendo Git un sistema VCS distribuito, non fa differenza a livello funzionale quale tipo si andrà ad utilizzare, dato che sono disponibili le stesse funzioni, ma è importante avere questa possibilità di scelta. L’uso di Git si può riassumere nelle operazioni eseguibili sui repo e sui file di cui tiene traccia.
I commit I commit consistono nel salvataggio delle modifiche dei file nel repo. In pratica è come premere il tasto salva, e oltre al salvataggio del file vengono aggiunte delle meta-informazioni circa lo stato come nome ed indirizzo email di chi esegue l’operazione, data, hash identificativi (del commit, del commit genitore e dell’albero genealogico) ed un messaggio che si può utilizzare per descrivere velocemente il contenuto del commit, molto utile quando si elencano i vari commit e bisogna distinguerli. Git non salva le differenze tra diverse versioni dei file o codifiche delta, bensì esegue uno snapshot (istantanea) di quel preciso istante di tutti i file modificati. Per capire meglio i commit, è utile comprendere il ciclo vitale dei file che è ben esemplificato da questa immagine. I file presenti nella cartella di lavoro possono essere tracked (tracciati) o untracked (non tracciati); i primi sono presenti nell’ultimo commit e possono essere unmodified (non modificati), modified (modificati) o staged, mentre i secondi sono quelli non presenti nell’ultimo commit o nell’area di stage. L’area di stage comprende tutti i file da includere nel commit.
I file untracked si tracciano con il comando add; lo stato dei file nell’area di lavoro si verifica con il comando status: un messaggio come “nothing to commit, working directory clean” indica che non ci file nuovi o modificati e che quelli presenti coincidono con quelli del commit, mentre se sono presenti file untracked o tracked ma da “committare”, questi sono segnalati. Git segna i file editati come, appunto, modificati perché cambiati rispetto all’ultimo commit: questi vanno inseriti nell’area di stage e poi si esegue il commit di ciò che è presente in quest’area, quindi il ciclo si ripete.
Anche lo stato dei file si controlla con il comando status, che segnala la presenza di file non tracciati e che quindi non verranno inclusi nel commit. Per aggiungerli si usa il comando add. Attenzione a comprendere un meccanismo che talvolta crea confusione: se si aggiunge un file all’area di stage (versione-0 del file), lo si modifica (versione-1 del file) e si esegue il comando status, il file verrà segnato sia come staged che unstaged. L’ambiguità è risolta ricordando che la versione-0 è quella staged, la versione-1 è quella unstaged, e quindi quando si esegue il commit verrà salvata la versione-0; per includere nel commit la versione-1 occorre eseguire di nuovo il comando add sul file. In pratica, l’aggiunta dei file all’area di stage è esplicita. Le head sono dei riferimenti agli oggetti dei commit. In ogni momento è selezionata una head specifica, la head corrente/attuale che ha un nome ben preciso: HEAD (tutto maiuscolo).
Solitamente in un’area di lavoro sono presenti dei file temporanei, log e simili che non si vuole includere nei commit: si devono indicare nell’apposito file .gitignore e Git non li prenderà in considerazione. Ad esempio se si gestisce un sito WordPress con Git è bene non considerare i file di cache presenti nel percorso /wp-content/cache/
Cronologia commit e annullamento operazioni Durante le operazioni di lavoro capita di chiedersi a che punto si è e quali sono le versioni disponibili, in altre parole serve consultare la cronologia dei commit: la funzione dedicata di Git è log, che elenca i commit dal più recente al più vecchia includendo il codice SHA-1 identificativo, l’autore con il suo indirizzo email e il messaggio del commit. Sono presenti dei filtri per filtrare i risultati e, naturalmente, è possibile vedere le differenze tra differenti versioni dello stesso file. Sono disponibili delle interfacce grafiche come gitk per una consultazione più comoda. Prima di eseguire un commit occorre accertarsi dello stato dei file e relative modifiche. Il comando status mostra lo stato dell’area di lavoro attuale, e se è tutto a posto -ossia non ci sono file unstaged, file da tracciare, file tracciati ma non aggiunti o altre situazioni strane- si può procedere ad eseguire il commit. In qualsiasi momento si può annullare qualsiasi cosa; ad esempio se ci si dimentica di aggiungere un file ad un commit, o si salva un file sbagliato, si può annullare il commit. In particolare si possono annullare i commit, annullare le modifiche ad un file e rimuovere file dall’area di stage. In ogni caso qualsiasi cosa che è oggetto di commit può essere recuperata, mentre le modifiche che non sono parte di commit solitamente non sono recuperabili, quindi occorre usare la giusta attenzione quando si eseguono commit.
Branch e merge Uno strumento veramente potente di Git consiste nella ramificazione (branch) di un progetto. Sostanzialmente una branch consiste in una linea di lavoro che diverge dal progetto originale (come analogia si può usare una strada principale con, ad un certo punto, una diramazione secondaria). Si può lavorare temporaneamente sui rami e poi fondere le modifiche con il ramo principale oppure tenere diversi rami per diversi scopi di sviluppo. Git incoraggia l’uso delle ramificazioni, che diventano quindi degli strumenti utili qualsiasi volta si voglia divergere dal corso principale dello sviluppo. Ad esempio si sta lavorando su un progetto e si vuole testare una nuova idea ma non sul codice principale: si crea una branch e si lavora su essa. Se poi l’idea si rivela vincente, le modifiche possono essere incorporate nel ramo principale. Il pattern suggerito è di avere un ramo principale, master, affiancato da varie diramazioni in cui sono presenti nuove funzioni, sperimentazioni, etc.. Quando un ramo raggiunge uno stato completo, può essere fuso con quello principale ed essere messo in produzione. Inoltre lavorare su un ramo permette di avere varie libertà dal momento che ciò che viene salvato con un commit non ha influenza sul resto del repo, a meno di non eseguire una fusione.
Con RocketWeb Hosting inoltre si possono assegnare le branch a diverse cartelle del sito e assegnare differenti permessi di visualizzazione (ad esempio si può proteggere la cartella con autenticazione via password) in modo da limitare l’accesso a parti di sviluppo solo agli addetti ai lavori. Per comprendere meglio cosa è un ramo, è bene ricordare che ogni commit contiene un puntatore dello snapshot creato oltre ad altre informazioni, e dei puntatori ai commit precedenti (parent commits, commit genitori): zero puntatori per il primo commit, un puntatore per un commit normale (=un genitore) e più puntatori per ogni commit frutto della fusione di rami.
Ogni volta che si lavora e si esegue un commit, il commit successivo conterrà un puntatore a quello precedente. In Git, un ramo è semplicemente un puntatore ad uno di questi commit; il ramo principale si chiama master. Quando si lavora su questo ramo e si eseguono dei commit, questi saranno assegnati a questo ramo che punterò all’ultimo creato, e ad ogni commit inviato il puntatore si sposta automaticamente in avanti. Una situazione tipo è riassunta da questa immagine, con ciascun commit identificato dalle prime 8 cifre dell’hash identificativo e contenente una specifica istantanea (come abbiamo visto, sono presente anche altre meta-informazioni sullo stato). Quando si crea un nuovo ramo con il comando branch non si sta facendo altro che creare un altro puntatore, diverso da master, da poter muovere. Git assegna al ramo in cui ci si è posizionati un puntatore particolare che abbiamo introdotto precedentemente: HEAD. Per spostarsi tra head (notare il carattere minuscolo) si usa il comando checkout. Nell’immagine che segue ci si è spostati dal ramo master al ramo testing e si è eseguito un commit: la HEAD punta al commit corretto. Riassumendo: si crea un ramo con il comando branch, ci si sposta in esso con il comando checkout. Quest’ultimo comando è utilizzato per spostarsi anche tra rami oltre al principale (se ci sono dei conflitti Git non permette di spostarsi previa risoluzione dei problemi): quando ci si sposta tra rami, l’area di lavoro viene attualizzata allo snapshot corrispondente a quel ramo. La merge (fusione) consiste nell’unione del lavoro eseguito sulla ramificazione con quello presente sul ramo principale (o su altro ramo): per continuare la nostra analogia, è come quando la strada secondaria si ricongiunge a quella principale. La fusione si esegue con il comando merge dopo essersi spostati nella head dove far convergere il ramo da unire con il comando checkout; Git determina automaticamente il miglior ramo genitore da utilizzare per l’unione di base. Se sorgono dei problemi Git li segnala e non esegue la fusione dei file in questione segnandoli come unmerged (verificabile con il comando status): in questo caso bisogna procedere a risolvere manualmente i conflitti scegliendo quale versione tenere eseguendo un commit adeguato. Lo strumento mergetool permette di gestire le fusioni con un’interfaccia grafica. Dopodiché si esegue un commit per salvare quanto fatto e si può eliminare la branch fusa. Rebase Il rebase, o rifondazione, è un metodo alternativo al merge di integrare le modifiche di una branch nel ramo principale. In sostanza consiste nel prendere i cambiamenti applicati in un ramo secondario e applicarli ad un altro ramo (ad es. quello principale). La differenza principale rispetto ad una fusione è che i due rami non tornano a coincidere (modo parallelo), bensì si calcolano con diff le modifiche presenti nel nuovo ramo rispetto a principale che vengono aggiunte a questo (modo seriale), in pratica rappresentandone una continuazione seriale. La figura sottostante esemplifica bene questa situazione: è stato fatto il rebase del commit C4 (appartenente ad un altro ramo) sul commit C3 del ramo principale: il risultato è un commit C4’ che ha come genitore C3 e non C3 e C4 come accadrebbe con una fusione.
Lavorare con server remoto Quando si vuole lavorare con un ambiente condiviso tra più sviluppatori, oppure quando si vuole un repo a cui accedere indipendentemente da dove ci si trova, serve un repo remoto. Si viene a creare un vero e proprio sistema di controllo versione distribuito. Il repository da creare è del tipo bare repo: contiene solo i file modificati che vengono man mano caricati dei collaboratori; non è presente un’area di lavoro. Non si deve lavorare direttamente sui file qui contenuti: il contenuto locale va sincronizzato con quello remoto; l’operazione che scarica il contenuto nuovo si chiama fetch (con comando omonimo), ma una volta eseguito non è ancora possibile lavorare su questi file: bisogna eseguire una fusione con merge. Il comando pull riassume i due precedenti ed è usato al loro posto per comodità. C’è anche però una sottigliezza che fa preferire uno dei due a seconda dei casi: fetch aggiorna la copia locale in modo da avere anche il contenuto del repo remoto in questione e non tocca i rami locali e la cartella di lavoro. pull invece modifica la versione della branch e della cartella di lavoro attuali in modo da includere le modifiche presenti sul ramo remoto specificato. La prima volta che si accede al repo remoto, questo va copiato interamente con il comando clone.
L’operazione di caricamento del lavoro eseguito si chiama push, anch’essa con omonimo comando. Se si è gli unici a lavorare sul repo, non ci sono problemi di conflitti, che però possono sorgere se si è in più persone a lavorare sullo stesso repository. Git comunque avvisa prima di ogni eventuale problema; in particolare se si esegue un pull, si lavora e nel mentre un collega esegue un push sul repo remoto, Git non permette di eseguire il push: prima vanno scaricati i nuovi file caricati dagli altri (pull o fetch+merge), poi vanno incorporati nel proprio lavoro attraverso un merge e infine si può procedere al push. Una situazione ideale per lo sviluppo web è rappresentata da un bare repository su server (GitHub, o anche RocketWeb Hosting) che agisce da master: a questo repo condiviso si connettono gli sviluppatori del progetto e caricano/scaricano le versioni dei file. Ciascun collaboratore esegue un pull dei dati, lavora, esegue un commit e infine esegue il push delle modifiche al repo condiviso. Gli hooks Gli hooks (letteralmente agganci) sono script Perl, Python, Ruby, etc.. che permettono di personalizzare Git con degli script automatici che vengono eseguiti al verificarsi di determinati eventi e si applicano sia lato client che lato server. Un hook importante e degno di menzione, noto come post-receive hook, si applica lato server e consiste in uno script nel linguaggio preferito che esegue il pull dei dati dal repo ogni volta che viene eseguito un push dei dati (da locale): in questo modo il server in produzione è sempre sincronizzato con la copia più recente del repo.
Con RocketWeb Hosting non è necessario creare questo tipo di hook: si può creare in partenza un repo con l’opzione Automatic deployment che esegue il deployment del sito non appena viene fatto il push da locale.
Flusso di lavoro Ora che abbiamo mostrato in breve ma con chiarezza i concetti base di Git possiamo applicarli allo sviluppo web con un work flow adeguato. Non c’è un vero e proprio flusso di lavoro codificato nello sviluppo di siti Web, sia questo un semplice sito vetrina o un sito complesso e articolato. In generale, dopo aver creato il repository, si procede così:
Si lavora sul sito con le modifiche in locale.
Si aggiungono manualmente con il comando corretto i file su cui eseguire il commit all’area di stage.
Si controlla lo stato dei file per trovare cosa è stato cambiato, quando, come e perché (e da chi, se l’ambiente di sviluppo coinvolge più persone) e si verifica che sia tutto come ci si aspetta.
Si esegue il commit dei file al repo locale. Le modifiche sono salvate solo sul computer locale.
Si esegue il push del repo locale sul repo remoto: le modifiche saranno presenti anche sul repo remoto, cioè il sito web, grazie ad un apposito hook (da impostare in precedenza).
Se sorge un problema introdotto dai nuovi file (ad esempio un errore di connessione al database, o una chiamata PHP sbagliata), nessun problema: si ripristina la versione precedente del repo remoto, si lavora sul repo locale correggendo gli errori, e infine si esegue il push dei file corretti sul repo remoto. Relativamente allo sviluppo Web, Git trova altre due interessanti situazioni d’uso.
Git per un ambiente di staging Quando si sviluppa un sito in genere c’è la necessità di avere un ambiente di prova in cui testare le nuove parti, separato dall’ambiente di produzione, ossia un’area di staging (qui il concetto di staging non è correlato a quello precedentemente visto a proposito dei repo). Viene creata una branch (diramazione) del repo principale, ad esempio master, che crea un secondo ramo, ad esempio chiamato dev, che contiene il sito e sul quale si può lavorare in tranquillità apportando le dovute modifiche senza interferire con il sito in produzione. Quando il contenuto di dev sarà corretto e funzionante, si può decidere di fare una merge con il repo master, cioè mettere in produzione il nuovo sito. Con RocketWeb Hosting è possibile assegnare i repo a determinati percorsi dello spazio hosting (ad esempio, master si assegna alla root del sito, dunque inserendo il contenuto del sito nella cartella principale come si fa di solito), e si può determinare la visibilità o meno dall’esterno del contenuto delle cartelle: in altre parole è possibile nascondere ai visitatori (e ai bot di indicizzazione dei motori di ricerca) l’area di staging.
Git come risorsa contro le infezioni hacker Git offre un comando molto utile, diff, che mostra le differenze tra commit, evidenziando eventuali file mancanti/aggiunti e file modificati tra una commit e la successiva; risulta utile quando si esegue una merge che crea conflitti. Ma può essere usato anche per un altro scopo: se il sito è oggetto di attacco hacker, si possono vedere le differenze tra la versione attuale (che contiene i file modificati dall’hacker) e una versione passata di cui si è certi che non contenga file alterati da hacker: in questo modo si può risalire precisamente agli elementi da rimuovere per bonificare il sito. Ragionando in locale, anziché eseguire il pull del contenuto remoto, si esegue fetch, dopodichè si trovano le differenze con diff. Naturalmente l’operazione di bonifica non si ferma ad una diff tra commit ma deve proseguire identificando il punto di accesso dell’hacker e proteggerlo con uno strumento adeguato. In alternativa anche il comando status, che mostra lo stato della commit attuale, è adatto allo scopo: infatti se la versione del sito è quella caricata da noi, il comando mostrerà un output pulito. Infatti la commit attuale è pari a quella da noi eseguita. Ma se un hacker carica uno script malevolo, o inserisce del codice in alcuni file, allora status mostrerà un output diverso da quello aspettato e indicherà la presenza di modifiche non introdotte da noi: un chiaro campanello di allarme. È possibile creare uno script aggiunto a crontab (che quindi viene eseguito automaticamente ad intervalli di tempo predefiniti) che esegue il comando status e se l’output è diverso da ‘working directory clean’ (che è il messaggio che compare quando non ci sono file untracked o unstaged, cioè non ci sono file caricati o modificati non da noi) avvisa con una email della presenza di file sospetti. Se si usa un sistema tale, è bene fare un commit ogni volta che si fanno delle modifiche al sito, come cambiare una pagina HTML o installare un plugin WordPress. Può essere una noia ricordarsi di farlo ogni volta ma in cambio si ha un ottimo sistema di risposta immediata in caso di attacco hacker. Questa procedura è sufficiente se si usa un sito che non fa uso di database, in quanto il sito intero è qui gestito con Git; se però si usa un CMS o altro sistema che da uso di database, come WordPress, ciò non è sufficiente: occorre anche controllare il database. In ogni caso diff e status sono due potenti risorse da usare in caso di infezione.
Conclusioni
In questo articolo abbiamo grattato la superficie di Git esponendo i concetti base e mostrando quanto ben si presta allo sviluppo di siti Web; naturalmente può essere utilizzato in svariati contesti e, tenendo a mente che può gestire qualsiasi file in formato testuale (ma anche binario, con le dovute precauzioni), il lettore può senza dubbio trovare altri casi dove l’uso di Git snellisce il carico di lavoro. Una buona padronanza dei concetti base di Git è alla base di un’esperienza d’uso soddisfacente ed efficace: è inutile ricorrere a tutorial che indicano sommariamente quali comandi bisogna eseguire da riga di comando se non si conoscono l’architettura e i concetti sui quali si fonda Git.
Questo articolo deve essere solo un punto di partenza per l’esplorazione di Git; ecco tre risorse importanti da tenere sempre sottomano: il libro “Pro Git”, scritto da Scott Chacon e Ben Straub, la cui la versione Web è disponibile gratuitamente a questo indirizzo, e le guide online di GitHub.
">Questo video su YouTube è un talk di Randal Schwartz piuttosto datato (2007) ma che spiega chiaramente il funzionamento di Git. Al fine di garantire una migliore capacità di gestione del proprio sito per sviluppatori, RocketWeb Hosting offre pieno supporto a Git e GitHub nei suoi piani hosting: in un articolo successivo tratteremo l’argomento in dettaglio.
Se questo articolo ti è piaciuto e vuoi rimanere sempre informato con le notizie di
ChannelCity.it iscriviti alla nostra
Newsletter gratuita.