Git : quand un filesystem fait du versionning

Etrange tout de même. Quand on touche aux problématiques de gestion de version, on retrouve, parmi les grands noms -CVS, SVN, Mercurial, ...- un nom court, bizarre, et quand on remonte plus loin, attisé par la curiosité, on tombe sur un post de son concepteur, Linus Torvalds, qui clame haut et fort que c'est un embryon de filesystem. Alors Git, c'est quoi ? Pourquoi est-il aussi présent en tant que gestionnaire de version ?

Présentation

Comme je vous l'ai dit, Git n'est pas un SCM (Source Code Management system), ou du moins, il n'était pas destiné à l'être. Il regroupe en réalité toutes les fonctionnalités d'un filesystem, et dispose d'un trousseau d'outils de bas niveau directement développé pour des problématiques de systèmes de fichier. Ses caractéristiques sont les suivantes :
  • c'est un outil simple, de bas niveau, donc efficace.
  • c'est un outil communautaire, qui s'appuie sur le partage des données en peer-to-peer like. Il n'y a donc pas de centralisation des informations ni des rôles / accès. Chacun est libre de cloner un dépôt, d'y travailler dessus et de mettre le dépôt distant à jour.
  • il fonctionne sur la base d'une somme de contrôle SHA-1 pour évaluer quelles données ont été modifiées,
    • ainsi la donnée n'est stockée qu'une fois par état
    • l'identification et adressage de la donnée se basent sur la somme SHA-1 qui agit alors comme un index
    • cela permet une uniformisation des noms de fichiers quelque soit le dépôt
    • Git peut effectuer un contrôle du fichier en comparant son vrai hash à celui de son index
  • Git stocke les données telles quelles, et non sous forme de diff (contrairement à Mercurial, CVS, SVN,...)
  • Git est implémenté pour plusieurs OS

Fonctionnement

En interne, Git contient 2 types d'instances :
  • Une base de donnée d'objet, située sous .dircache sous le nom d'objects, et qui contient :
    • des blob - Binary Large OBject -, contenu binaire du fichier versionné, qui n'est pas directement lié au nom de fichier d'origine contenant la donnée et à son chemin. Il est facile d'accéder au contenu d'un blob via la commande
      git show
    • un tree, liste d'objet de type blob associés à des info complémentaires : nom du fichier, permissions. Il décrit l'état d'une hiérarchie de fichier à un état donné. On peut utiliser pour examiner un objet de type tree :
      git show
      git ls-tree
    • un commit -ou changeset-, historique d'une arborescence de fichiers, qui lie un tree à la description des nouveaux changements fournis lors du git commit. On peut le lire via :
      git show
      git log
      Un commit est défini par :
      • un objet de type log, mentionnant par exemple l'auteur du commit, le commiter et un commentaire.
      • un objet de type tree
      • des pointeurs sur les commits parents -le commit initial n'a pas de parent bien sûr-
    • des tags, sorte de qualificatif de commit. Il peut être exploité grâce à l'invocation des commandes
      git tag
      git cat-file
  • Un Directory cache
Le directory cache est un unique objet binaire contenant un objet tree. Il est représenté par le fichier index situé sous .dircache. Il permet de stocker l'état d'une arborescence à un instant t. Cet état ne sera pas forcément mis à jour, notamment après un pull d'un dépôt extérieur ou de modifications local (c'est un cache, quoi). Il est lié à un dépôt, et est crée lors de sa création from scratch (init-db), l'ajout et la mise à jour du cache passant par update-cache. Toutes les données relatives à un projet se situent dans un répertoire unique -par défaut nommé .git- et situé à la racine du projet. Il contient toute l'arborescence nécessaire pour stocker les informations :
|-- HEAD         # pointeur vers la branche courante
|-- config       # configuration des préférences
|-- description  # description du projet
|-- hooks/       # pre/post actions hooks
|-- index        # fichier d'index
|-- logs/        # un historique de la branche
|-- objects/     # les objets (commits, trees, blobs, tags)
`-- refs/        # pointeurs vers les branches
Une distinction est à opérer concernant le répertoire de Git et le répertoire de travail. Le répertoire de travail est en fait la localisation du projet en cours, et sa composition est automatiquement accordée selon la branche sur laquelle vous travaillez. Cette mise à jour du contenu du répertoire de travail est entièrement opérée grâce au répertoire Git qui contient toutes les informations nécessaires. Il s'agit en quelque sorte d'un cache temporaire qui permet d'offrir le support avant de committer les modifications. Tout Git repose sur les hash SHA. Quand un nouvel objet est ajouté à la base de données, il subit une compression (zlib), puis un hash et la checksum résultante (tout du moins les 2 premiers bits) va permettre d'identifier l'objet dans la base de données. Enfin l'objet est stocké. Comme aucun élément mis à part le suivi du commit peut lier une donnée à une version, Git a la mauvaise manie de stocker la donnée intégrale plutôt que de procéder par des diff par rapport aux versions précédentes, ce qui peut amener une forte consommation des ressources.

Mais cette différence implique également que Git soit plus rapide, puisqu'il n'y a pas à reconsidérer tout un historique et de devoir reconstruire la donnée lors de l'accès à une version spécifique.

En externe, Git exploite plusieurs notions qu'il faut connaitre pour pouvoir manipuler l'outil sans problème.

Le dépôt

Le dépôt est un espace bien particulier qui contient un projet. Cet espace est accessible par toute la communauté, c'est-à dire partagée via HTTP, SSH, ou Git, tout simplement.

Le projet

Le projet est constitué de l'arborescence dans lequel se trouvent vos données. Ses moindres changements d'état sont consignés par Git, mais il n'est généralement accessible qu'en local. Il est destiné à être le support de toute modification éprouvées et testées, avant de pouvoir mettre à jour le dépôt relatif à ce projet. En clair, cela commence lorsque vous rapatriez un projet depuis un dépôt. Vous avez alors votre propre projet à vous.

Les branches

Les branches sont un peu comme un projet dans un projet. C'est un environnement distinct de la branche principale et dont le contenu est à intégrer à terme au projet. Cela permet notamment de développer des fonctionnalités de manière indépendante et puis de pouvoir les intégrer à un instant donné, sans avoir à mettre tout le projet en quarantaine le temps du développement. Maintenant que nous en savons un peu plus sur ce qui se trame dans la mécanique infernale de Git, nous sommes fins prêts pour...

Les travaux pratiques

Installation

Git offre un support d'installation pour chacune des 3 grandes distributions :
  • Linux, la plupart du temps, via les dépôts officiels de la distribution, sinon via les sources,
  • Mac OS X, grâce à MacPort (MacPort sudo port install git-core) ou via un  installer,
  • Windows, via un exécutable.

Configuration

La configuration de Git est très sommaire et consiste à vous identifier pour consigner chacune de vos modifications dans les projets : un nom, un mail, et le tour est joué.
git config --global user.name "K-Tux"
git config --global user.email "K-Tux@bux.com"
Vos paramètres seront sagement consignés dans ~/.gitconfig si vous avez précisé l'option --global (donc pour tous vos projets), sinon dans chacun des .git/.config de vos projets.

Dépot

Afin de pouvoir utiliser Git, il faut alors disposer d'un dépôt. Pour cela, vous avez le choix. Soit vous intégrez une équipe sur un projet, et donc vous récupérez le dépôt de ce projet, soit vous êtes à l'origine du projet, et il vous faut alors en créer un.

Création

La création d'un dépôt est très simple. Il suffit de se placer au niveau des sources, à la racine du projet à gitter, et de lancer un :
git init

Récupération

Pour récupérer un dépôt -on parle alors de cloner-, il suffit de lancer la commande :
git clone git://git.kernel.org/pub/scm/git/git.git
Mais vous pouvez spécifier votre propre URL de dépôt. Pas de panique, cela ne signifie pas que vous devez monter un web server en urgence, Git s'accommode très bien d'URL tapant sur du SSH, sur du HTTP, ou sur... bah du git. Vous récupérerez donc un répertoire copie conforme de celui du serveur et qui aura le nom de celui stipulé dans l'URL, le .git en moins.

Merge de projet

git pull ...
La commande sert à rapatrier un projet depuis un dépôt distant ou bien depuis une branche différente, mais effectue également un merge des 2 projets. Le merge sera alors disponible en local. Vu qu'il s'agit de merge, il peut en résulter des conflits qu'il faudra résoudre.

Mise à disposition d'un dépôt

La mise à disposition d'un dépôt peut utiliser un serveur HTTP, ou bien être accessible via SSH, comme on a pu le voir, mais il se trouve que Git propose également un moyen de partage beaucoup plus efficace. Pour pouvoir mettre à disposition un projet via un dépôt Git, il faut d'abord correctement le nommer. Pour cela, tapez juste :
git clone prj prj.git
Il faut aussi indiquer à Git si oui ou non il doit exporter le projet :
touch prj.git/git-daemon-export-ok
Et puis, bah oui, replacer le projet là où ça fait bien, sur votre petit serveur de dépôt.
En deux minutes, pour la lecture
git-daemon est un utilitaire qui permet de mettre à disposition en lecture un projet via un dépôt Git. L'outil est multifonction et se lance avec la ligne de commande :
git-daemon --base-path=/var/depots --listen=ipserver --user=git_user --group=git_group --detach --verbose
Et c'est opérationnel ! Le daemon tournera alors sur le port 9148, écoutera sur l'IP spécifiée par le --listen en utilisant l'utilisateur git_user et le groupe git_group. L'option --detach permet de lancer le daemon en arrière-plan, tout en utilisant les facilités de syslog. Plusieurs autres options sont disponibles, certaines concernent d'ailleurs le virtual hosting, à l'image de celui d'Apache. Pour cela, tombez le --base-path et jouez avec —interpolated-path.
Et pour l'écriture
C'est le souci de git-daemon. Il ne prend pas en charge l'écriture, c'est-à-dire la mise à jour par la communauté des données du dépôt. Il leur faudra donc passer par un traditionnel :
git push ...

Gestion d'un projet

Ajout de données au projet

L'ajout de données au sein d'un projet est fait par :
git add file1 file2 ...
Cet ajout correspond à la mise à jour de l'index sur les fichiers à monitorer. Avant de committer, vous pouvez toujours vérifier les différences entre ce qui existe déjà et vos modifications via git diff, et le récapitulatif via un :
git status

Commit des données modifiées

git commit
vous permet de valider vos données modifiées. A noter que git commit -a permet non seulement d'ajouter vos données mais également de les valider. Vous avez la possibilité d'avoir un aperçu de l'état actuel du projet grâce à git status. Si vous avez fait une boulette et que vous souhaitez revenir à un état donné,
git reset ...
prendra en compte le hash de l'état sur lequel vous voudriez vous remettre.

Les branches

La notion de branche est bien familière aux développeurs. Il s'agit en fait d'un environnement dédié, séparé du tronc principal du projet, qui fait l'objet de labo, afin de tester et d'implémenter de nouvelles fonctionnalités sans impacter la prod. D'ailleurs, cette différenciation implique qu'une branche prod soit effective. Dans Git, la branche principale est appelée master. Par défaut, elle est la seule branche présente, donc celle sur laquelle vous travaillez, mais il est facile d'en créer d'autres. Le nombre de branche n'est pas limité, en revanche, ce qui pose souvent souci, c'est justement de les merger tout en gardant la stabilité et la richesse des données de chacune d'entre elle. Git, comme tout bon SCM qui se respecte, permet de gérer efficacement les branches.

Création d'une branche

La création et le listing des branches passent par la commande
git branch
Pour créer une nouvelle branche, il faut fournir son petit nom en entrée, par exemple :
git branch dev
Le listing nous montre alors les différentes branches, la petite * permet de nous situer la branche sur laquelle on travaille.
git branch
dev
* master
Lorsque l'on veut travailler sur la branche dev, mais que l'on se trouve sur la master, il faut appeler
git checkout dev

Merge d'une branche

Lorsque l'on veut merger deux branches, il suffit de se mettre sur la branche destination et de lancer un
git merge branch-à-merger
Ce geste anodin semble sympatique, mais peut se révéler moins compréhensif lors de la détection de conflit.
CONFLICT (content): Merge conflict in bux.pl
Automatic merge failed; fix conflicts and then commit the result.
Le souci, c'est que la détection de conflit n'est pas évaluée avant le merge, ce qui amène Git à mettre un projet dans un état incohérent, ayant mergé tout ce qu'il pouvait jusqu'au conflit. Vous devez donc tout recoudre avec vos petites mains. Bon, vous n'êtes pas perdu, car dans les éléments impliqués (ici notre bux.pl), Git indique l'état des 2 branches avant le merge. A vous donc de décider laquelle est la plus appropriée dans ses traitements. Bien sûr, Git vous met à dispo tout un trousseau pour remonter jusqu'au problème
git status
git log
git bisect

Dé-merger une branche

Pour défaire un merge qui s'est -vraiment- très mal passé, il vous suffit de taper
git reset --hard HEAD
Cela restaurera l'état d'avant la merge.

Et caetera...

Cette petite introduction nous montre juste une partie des capacités de l'outil. D'ailleurs, on regrettera l'aspect un peu "fouilli" de Git, mais je reconnais facilement que ce pêle-mêle d'outils est très riche et trouve facilement son intérêt dans le versionning. En tout cas, la communauté est active car la dernière version en date est du 24/04/2010. Pas de doute que l'outil continuera à prendre de l'ampleur, quitte à finir par avoir sa place en tant que filesystem. D'ailleurs, pour les plus curieux d'entre vous -et les pires-, ce mois-ci, le numéro 129 de Linux Mag propose une exploitation assez surprenante de Git avec... SVN. Je n'ai pas encore eu l'occasion de mettre la main dessus, ce qui ne m'empêche pas de vous encourager à le lire !
Vus : 673
Publié par K-Tux : 59