Capitolo 6. Git multi-giocatore

Inizialmente usavo Git per progetti privati dove ero l’unico sviluppatore. Tra i comandi legati alla natura distribuita di Git, avevo bisogno solamente di pull e clone così da tenere lo stesso progetto in posti diversi.

Più tardi ho voluto pubblicare il mio codice tramite Git e includere modifiche di diversi contributori. Ho dovuto imparare a gestire progetti con multipli sviluppatori da tutto il mondo. Fortunatamente questo è il punto forte di Git, e probabilmente addirittura la sua ragion d’essere.

Chi sono?

Ogni commit ha il nome e l’indirizzo e-mail di un autore, i quali sono mostrati dal comando git log. Per default Git utilizza i valori di sistemamastery per definire questi campi. Per configurarli esplicitamente, digitate:

$ git config --global user.name "John Doe"
$ git config --global user.email johndoe@example.com

Omettete l’opzione --global per configurare questi valori solo per il deposito corrente.

Git via SSH e HTTP

Supponiamo che avete un accesso SSH a un server web sul quale Git non è però installato. Anche se meno efficiente rispetto al suo protocollo nativo, Git può comunicare via HTTP.

Scaricate, compilate e installate Git sul vostro conto, e create un deposito nella vostra cartella web:

$ GIT_DIR=proj.git git init
$ cd proj.git
$ git --bare update-server-info
$ cp hooks/post-update.sample hooks/post-update

Con versioni meno recenti di Git il comando di copia non funziona e dovete eseguire:

$ chmod a+x hooks/post-update

Ora potete trasmettere le vostre modifiche via SSH da qualsiasi clone:

$ git push web.server:/path/to/proj.git master

e chiunque può ottenere il vostro progetto con:

$ git clone http://web.server/proj.git

Git tramite qualsiasi canale

Volete sincronizzare dei depositi senza server o addirittura senza connessione di rete? Avete bisogno di improvvisare durante un’emergenza? abbiamo già visto chegit fast-export e git fast-import possono convertire depositi in un semplice file. Possiamo quindi inviare questo tipo di file avanti e indietro per trasportare depositi Git attraverso un qualsiasi canale. Ma uno strumento più efficace è il comando git bundle.

Il mittente crea un pacchetto, detto bundle:

$ git bundle create qualche_file HEAD

poi trasmette il bundle, qualche_file, al destinatario attraverso qualsiasi metodo: email, chiave USB, stampa e riconoscimento caratteri, lettura di bit via telefono, segnali di funo, ecc. Il destinatario può recuperare i commit dal bundle digitando:

$ git pull qualche_file

Il destinatario può effettuare ciò anche in deposito interamente vuoto. Malgrado la sua dimensione, qualche_file contiene l’intero deposito Git originario.

Nel caso di progetti grandi, riducete gli sprechi includendo nel bundle solo i cambiamenti che mancano nell’altro deposito. Per esempio, supponiamo che il commit “1b6d…” è il commit più recente che è condiviso dai due depositi. Possiamo ora eseguire:

$ git bundle create qualche_file HEAD ^1b6d

Se fatta di frequente, potremmo facilmente dimenticare quale commit è stato mandato per ultimo. La pagina d’aiuto suggerisce di utilizzare delle tag per risolvere questo problema. In pratica, appena dopo aver inviato il bundle, digitate:

$ git tag -f ultimo_bundle HEAD

e create un nuovo bundle con:

$ git bundle create nuovo_bundle HEAD ^ultimo_bundle

Le patch: la moneta di scambio globale

Le patch sono delle rappresentazioni testuali dei vostri cambiamenti che possono essere facilmente comprensibili sia per computer che umani. È quello che le rende interessanti. Potete mandare una patch per email ad altri sviluppatori indipendentemente dal sistema di controllo di versione che utilizzano. A partire dal momento che possono leggere le loro email, possono vedere le vostre modifiche. Similarmente, da parte vostra non avete bisogno che di un indirizzo email: non c'è neanche bisogno di avere un deposito Git online

Ricordatevi dal primo capitolo, il comando:

$ git diff 1b6d > my.patch

produce una patch che può essere incollata in un’email per discussioni. In un deposito Git, eseguite:

$ git apply < my.patch

per applicare la patch.

In un contesto più formale, quando è il nome e magari la firma dell’autore devono essere presenti, generate le patch a partire da un certo punto digitando:

$ git format-patch 1b6d

I file risultanti possono essere passati a git-send-email, o inviati a mano. Potete anche specificare un intervallo tra due commit:

$ git format-patch 1b6d..HEAD^^

Dalla parte del destinatario salvate l’email in un file (diciamo email.txt) e poi digitate:

$ git am < email.txt

Questo applica le patch ricevute e crea inoltre un commit, includendo informazioni come il nome dell’autore.

Se utilizzate un client email in un navigatore web potreste dover cercare il modo di vedere il messaggio nel suo formato "raw" originario prima di salvare la patch come file.

Ci sono delle leggere differenze nel caso di client email che si basano sul formato mbox, ma se utilizzate uno di questi, siete probabilmente il tipo di persona che riesce a risolverle senza bisogno di leggere questo tutorial!

Ci dispiace, abbiamo cambiato indirizzo

Dopo aver conato un deposito, l’esecuzione di git push o git pull farà automaticamente riferimento all’URL del deposito d’origine. Come fa Git? Il segreto risiede nelle opzioni di configurazione create durante la clonazione. Diamoci un’occhiata:

$ git config --list

L’opzione remote.origin.url determina l’URL della sorgente; “origin” è l’alias del deposito d’origina. Come per la convenzione di nominare “master” la branch principale, possiamo cambiare o cancellare questo alias ma non c'è normalmente nessuna ragione per farlo.

Se l’indirizzo del deposito originario cambia, potete modificare il suo URL con:

$ git config remote.origin.url git://new.url/proj.git

L’opzione branch.master.merge specifica la branch di default utilizzata dal comando git pull. Al momento della clonazione iniziale il nome scelto è quello della branch corrente del deposito originario. Anche se l’HEAD del deposito d’origine è spostato verso un’altra branch, il comando pull continuerà a seguire fedelmente la branch iniziale.

Quest’opzione si applicherà unicamente al deposito usato nel clonazione iniziale, cioè quello salvato nell’opzione branch.master.remote. Se effettuiamo un pull da un altro deposito dobbiamo indicare esplicitamente quale branch vogliamo:

$ git pull git://example.com/other.git master

Questo spiega tra l’altro come mai alcuni dei precedenti esempi di push e pull non avevano nessun argomento.

Branch remote

Quando cloniamo un deposito, cloniamo anche tutte le sue branch. Magari non ve ne siete accorti perché Git le nascondei: dovete chiedere esplicitamente di vederle. Questo impedisce alle branch del deposito remoto d’interferire con le vostre branch, e rende l’uso di Git più facile per i novizi.

Per ottenere una lista delle branch remote eseguite:

$ git branch -r

Dovreste ottenere qualcosa come:

origin/HEAD
origin/master
origin/experimental

Questi sono le branch e l’HEAD del deposito remoto, e possono essere usati in normali comandi Git. Supponiamo per esempio di aver fatto molti commit e che ora volete paragonare le differenze con l’ultima versione ottenibile con fetch. Potreste cercare nel log il codice SHA1 appropriato, ma è molto più semplice scrivere:

$ git diff origin/HEAD

Oppure potete anche vedere che cosa sta succedendo nella branch ‘`experimental’:'

$ git log origin/experimental

Depositi remoti multipli

Supponiamo che due altri sviluppatori stanno lavorando sul vostro progetto, e che vogliate tenerli d’occhio entrambi. Possiamo seguire più depositi allo stesso tempo con:

$ git remote add altro git://example.com/un_deposito.git
$ git pull altro una_branch

Ora abbiamo fatto un merge con una branch di un secondo deposito e possiamo avere facile accesso a tutte le branch di tutti i depositi:

$ git diff origin/experimental^ altro/una_branch~5

Ma come fare se vogliamo solo paragonare i loro cambiamenti senza modificare il nostro lavoro? I altre parole, vogliamo esaminare le loro branch senza che le loro modifiche invadano la nostra cartella di lavoro. In questo caso, invece di fare un pull, eseguite:

$ git fetch        # Fetch dal deposito d'origine, il default
$ git fetch altro  # Fetch dal secondo programmatore.

Questo fa un fetch solamente delle storie. Nonostante la cartella di lavoro rimane intatta, possiamo riferirci a qualsiasi branch in qualsiasi deposito con i comandi Git, perché ora abbiamo una copia locale.

Ricordatevi che dietro le quinte, un pull è semplicemente un fetch seguito da un merge. Normalmente facciamo un pull perché vogliamo ottenere un merge delle ultime modifiche dopo aver fatto un fetch. La situazione precedente è una notevole eccezione.

Guardate git help remote per sapere come eliminare depositi remoti, ignorare delle branch, e ancora di più.

Le mie preferenze

Per i miei progetti mi piace che i contributori preparino depositi dai quali posso fare in pull. Alcuni servizi di host Git permettono di creare i vostri cloni di un progetto con il click di un bottone.

Dopo aver fatto il fetch di una serie di modifiche, utilizzo i comandi Git per navigare e esaminare queste modifiche che, idealmente, saranno ben organizzate e descritte. Faccio il merge dei miei cambiamenti, e forse qualche modifica in più. Una volta soddisfatto, faccio un push verso il deposito principale.

Nonostante non riceva molto spesso dei contributi, credo che questo approccio scali bene. In proposito, vi consiglio di guardare questo post di Linus Torvalds.

Restare nel mondo di Git è un po' più pratiche che usare file di patch, visto che mi risparmia di doverli convertire in commit Git. Inoltre, Git gestisce direttamente dettagli come salvare il nome e l’indirizzo email dell’autore, così come la data e l’ora, e chiede anche all’autore di descrivere i cambiamenti fatti.