Git
Chapters ▾ 2nd Edition

10.6 Les tripes de Git - Les protocoles de transfert

Les protocoles de transfert

Git peut transférer des données entre deux dépôts de deux façons principales : le protocole « stupide » et le protocole « intelligent ».

Cette section fait un tour d’horizon du fonctionnement de ces deux protocoles.

Le protocole stupide

Si vous mettez en place un dépôt à accéder en lecture seule sur HTTP, c’est vraisemblablement le protocole stupide qui sera utilisé.

Ce protocole est dit « stupide », car il ne nécessite aucun code spécifique à Git côté serveur durant le transfert ; le processus de récupération est une série de requêtes GET, où le client devine la structure du dépôt Git présent sur le serveur.

Note

Le protocole stupide est rarement utilisé ces derniers temps. Il est difficile de le rendre sécurisé ou privé, et donc la plupart des hébergeurs Git (sur le cloud ou sur serveur dédié) refusent de l’utiliser. On conseille généralement d’utiliser le protocole intelligent, qui est décrit plus loin.

Suivons le processus http-fetch pour la bibliothèque simplegit :

$ git clone http://server/simplegit-progit.git

La première chose que fait cette commande est de récupérer le fichier info/refs. Ce fichier est écrit par la commande update-server-info et c’est pour cela qu’il faut activer le crochet post-receive, sinon le transfert HTTP ne fonctionnera pas correctement :

=> GET info/refs
ca82a6dff817ec66f44342007202690a93763949     refs/heads/master

On possède maintenant une liste des références distantes et empreintes SHA-1. Ensuite, on regarde vers quoi pointe HEAD, pour savoir sur quelle branche se placer quand on aura fini :

=> GET HEAD
ref: refs/heads/master

On aura besoin de se placer sur la branche master, quand le processus sera terminé. On est maintenant prêt à démarrer le processus de parcours. Puisque votre point de départ est l’objet commit ca82a6 que vous avez vu dans le fichier info/refs, vous commencez par le récupérer :

=> GET objects/ca/82a6dff817ec66f44342007202690a93763949
(179 bytes of binary data)

Vous obtenez un objet, cet objet est dans le format brut sur le serveur et vous l’avez récupéré à travers une requête HTTP GET statique. Vous pouvez le décompresser avec zlib, ignorer l’en-tête et regarder le contenu du commit :

$ git cat-file -p ca82a6dff817ec66f44342007202690a93763949
tree cfda3bf379e4f8dba8717dee55aab78aef7f4daf
parent 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
author Scott Chacon <schacon@gmail.com> 1205815931 -0700
committer Scott Chacon <schacon@gmail.com> 1240030591 -0700

changed the version number

Puis, vous avez deux autres objets supplémentaires à récupérer : cfda3b qui est l’arbre du contenu sur lequel pointe le commit que nous venons de récupérer et 085bb3 qui est le commit parent :

=> GET objects/08/5bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
(179 bytes of data)

Cela vous donne le prochain objet commit. Récupérez l’objet arbre :

=> GET objects/cf/da3bf379e4f8dba8717dee55aab78aef7f4daf
(404 - Not Found)

Oups, on dirait que l’objet arbre n’est pas au format brut sur le serveur, vous obtenez donc une réponse 404. On peut en déduire certaines raisons : l’objet peut être dans un dépôt suppléant ou il peut être dans un fichier groupé de ce dépôt. Git vérifie la liste des dépôts suppléants d’abord :

=> GET objects/info/http-alternates
(empty file)

Si la réponse contenait une liste d’URL suppléantes, Git aurait cherché les fichiers bruts et les fichiers groupés à ces emplacements, c’est un mécanisme sympathique pour les projets qui ont dérivé d’un autre pour partager les objets sur le disque. Cependant, puisqu’il n’y a pas de suppléants listés dans ce cas, votre objet doit se trouver dans un fichier groupé. Pour voir quels fichiers groupés sont disponibles sur le serveur, vous avez besoin de récupérer le fichier objects/info/packs, qui en contient la liste (générée également par update-server-info) :

=> GET objects/info/packs
P pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack

Il n’existe qu’un seul fichier groupé sur le serveur, votre objet se trouve évidemment dedans, mais vous allez tout de même vérifier l’index pour être sûr. C’est également utile lorsque vous avez plusieurs fichiers groupés sur le serveur, vous pouvez donc voir quel fichier groupé contient l’objet dont vous avez besoin :

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.idx
(4k of binary data)

Maintenant que vous avez l’index du fichier groupé, vous pouvez vérifier si votre objet est bien dedans car l’index liste les empreintes SHA-1 des objets contenus dans ce fichier groupé et des emplacements de ces objets. Votre objet est là, allez donc récupérer le fichier groupé complet :

=> GET objects/pack/pack-816a9b2334da9953e530f27bcac22082a9f5b835.pack
(13k of binary data)

Vous avez votre objet arbre, vous continuez donc le chemin des commits. Ils sont également tous contenus dans votre fichier groupé que vous venez de télécharger, vous n’avez donc pas d’autres requêtes à faire au serveur. Git récupère une copie de travail de votre branche master qui été référencée par HEAD que vous avez téléchargé au début.

Le protocole intelligent

Le protocole stupide est simple mais un peu inefficace, et il ne permet pas l’écriture de données du client au serveur. Le protocole intelligent est une méthode plus habituelle pour transférer des données, mais elle nécessite l’exécution sur le serveur d’un processus qui connaît Git : il peut lire les données locales et déterminer ce que le client a ou ce dont il a besoin pour générer un fichier groupé personnalisé pour lui. Il y a deux ensembles d’exécutables pour transférer les données : une paire pour téléverser des données et une paire pour en télécharger.

Téléverser des données

Pour téléverser des données vers un exécutable distant, Git utilise les exécutables send-pack et receive-pack. L’exécutable send-pack tourne sur le client et se connecte à l’exécutable receive-pack du côté serveur.

SSH

Par exemple, disons que vous exécutez git push origin master dans votre projet et origin est défini comme une URL qui utilise le protocole SSH. Git appelle l’exécutable send-pack, qui initialise une connexion à travers SSH vers votre serveur. Il essaye d’exécuter une commande sur le serveur distant via un appel SSH qui ressemble à :

$ ssh -x git@server "git-receive-pack 'simplegit-progit.git'"
00a5ca82a6dff817ec66f4437202690a93763949 refs/heads/master report-status \
	delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1+github-607-gfba4028 delete-refs
0000

La commande git-receive-pack répond immédiatement avec une ligne pour chaque référence qu’elle connaît actuellement, dans ce cas, uniquement la branche master et son empreinte SHA-1. La première ligne contient également une liste des compétences du serveur (ici : report-status, delete-refs et quelques autres, dont l’identifiant du client).

Chaque ligne commence avec une valeur hexadécimale sur 4 caractères, spécifiant le reste de la longueur de la ligne. La première ligne, ici, commence avec 00a5, soit 165 en hexadécimal, ce qui signifie qu’il y a 165 octets restants sur cette ligne. La ligne d’après est 0000, signifiant que le serveur a fini de lister ses références.

Maintenant qu’il connait l’état du serveur, votre exécutable send-pack détermine quels commits il a de plus que le serveur. L’exécutable send-pack envoie alors à l’exécutable receive-pack les informations concernant chaque référence que cette commande push va mettre à jour. Par exemple, si vous mettez à jour la branche master et ajoutez la branche experiment, la réponse de send-pack ressemblera à quelque chose comme :

0076ca82a6dff817ec66f44342007202690a93763949 15027957951b64cf874c3557a0f3547bd83b3ff6 \
	refs/heads/master report-status
006c0000000000000000000000000000000000000000 cdfdb42577e2506715f8cfeacdbabc092bf63e8d \
	refs/heads/experiment
0000

Git envoie une ligne pour chaque référence que l’on met à jour avec l’ancien SHA-1, le nouveau SHA-1 et la référence en train d’être mise à jour. La première ligne contient également les compétences du client. La valeur SHA-1 remplie de 0 signifie qu’il n’y avait rien à cet endroit avant, car vous êtes en train d’ajouter la référence experiment. Si vous étiez en train de supprimer une référence, vous verriez l’opposé : que des 0 du côté droit.

Puis, le client téléverse un fichier groupé de tous les objets que le serveur n’a pas encore.

Finalement, le serveur répond avec une indication de succès (ou d’échec) :

000eunpack ok
HTTP(S)

Le processus est quasiment le même avec HTTP, à une différence près lors de l’établissement de la liaison (handshaking). La connection est amorcée avec cette requête :

=> GET http://server/simplegit-progit.git/info/refs?service=git-receive-pack
001f# service=git-receive-pack
00ab6c5f0e45abd7832bf23074a333f739977c9e8188 refs/heads/master \
	report-status delete-refs side-band-64k quiet ofs-delta \
	agent=git/2:2.1.1~vmg-bitmaps-bugaloo-608-g116744e
0000

Ceci est la fin du premier échange client-serveur. Le client fait alors une nouvelle requête, qui est cette fois un POST, avec les données fournies par git-upload-pack.

=> POST http://server/simplegit-progit.git/git-receive/pack

La requête POST contient la sortie de send-pack et le fichier groupé. Enfin, le serveur indique le succès ou l’échec dans sa réponse HTTP.

Téléchargement des données

Lorsque vous téléchargez des données, les exécutables fetch-pack et upload-pack entrent en jeu. Le client démarre un processus fetch-pack qui se connecte à un processus upload-pack du côté serveur pour négocier les données qui seront téléchargées.

SSH

Si vous téléchargez par SSH, fetch-pack fait quelque chose comme ceci :

$ ssh -x git@server "git-upload-pack 'simplegit-progit.git'"

Une fois fetch-pack connecté, upload-pack lui répond quelque chose du style :

00dfca82a6dff817ec66f44342007202690a93763949 HEAD multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Ceci est très proche de la réponse de receive-pack mais les compétences sont différentes. En plus, il envoie ce qui est pointé par HEAD (symref=HEAD:refs/heads/master), afin que le client sache ce qu’il doit récupérer dans le cas d’un clone.

À ce moment, fetch-pack regarde les objets qu’il a et répond avec la liste des objets dont il a besoin en envoyant « want » (vouloir) suivi du SHA-1 qu’il veut. Il envoie tous les objets qu’il a déjà avec « have » suivi du SHA-1. À la fin de la liste, il écrit « done » (fait) pour inciter l’exécutable upload-pack à commencer à envoyer le fichier groupé des données demandées :

003cwant ca82a6dff817ec66f44342007202690a93763949 ofs-delta
0032have 085bb3bcb608e1e8451d4b2432f8ecbe6306e7e7
0009done
0000
HTTP(S)

L’établissement de la liaison pour une opération de téléchargement nécessite deux requêtes HTTP. La première est un GET vers le même point que dans le protocole stupide :

=> GET $GIT_URL/info/refs?service=git-upload-pack
001e# service=git-upload-pack
00e7ca82a6dff817ec66f44342007202690a93763949 HEAD multi_ack thin-pack \
	side-band side-band-64k ofs-delta shallow no-progress include-tag \
	multi_ack_detailed no-done symref=HEAD:refs/heads/master \
	agent=git/2:2.1.1+github-607-gfba4028
003fca82a6dff817ec66f44342007202690a93763949 refs/heads/master
0000

Ceci ressemble beaucoup à un appel à git-upload-pack par une connection SSH, mais le deuxième échange est fait dans une requête séparée :

=> POST $GIT_URL/git-upload-pack HTTP/1.0
0032want 0a53e9ddeaddad63ad106860237bbf53411d11a7
0032have 441b40d833fdfa93eb2908e52742248faf0ee993
0000

Une fois de plus, ce format est le même que plus haut. La réponse à cette requête indique le succès ou l’échec, et contient le fichier groupé.

Résumé sur les protocoles

Cette section contient un survol basique des protocoles de transfert. Les protocoles contiennent de nombreuses autres fonctionalités, comme les compétences multi_ack ou side-band, mais leur étude est hors du sujet de ce livre. Nous avons essayé de vous donner une idée générale des échanges entre client et serveur. Si vous souhaitez en connaître davantage, vous devrez probablement jeter un œil sur le code source de Git.