SHA

Toutes les informations nécessaires pour décrire l’historique d’un projet sont stockées dans des fichiers référencés par un « nom d’objet » de 40 caractères qui ressemble à quelque chose comme ça :

6ff87c4664981e4397625791c8ea3bbb5f2279a3

Partout dans Git, vous trouverez ces chaînes de 40 caractères. Dans chaque situation, le nom est calculé en prenant le hash SHA1 représentant le contenu de l’objet. Le hash SHA1 est une fonction de hash cryptographique. Ce que cela signifie pour nous, c’est qu’il est virtuellement impossible de trouver deux objets différents avec le même nom. Cela à de nombreux avantages, parmi lesquels :

  • Git peut rapidement savoir si 2 objets sont les mêmes ou non, juste en comparant les noms ;
  • puisque le nom des objets sont calculés de la même façon dans chaque dépôt, le même contenu stocké dans des dépôts différent sera toujours stocké avec le même nom ;
  • Git peut détecter les erreurs quand il lit un objet, en vérifiant que le nom de l’objet est toujours le hash SHA1 de son contenu.

Les objets

Chaque objet se compose de 3 choses : un type, une taille et le contenu. La taille est simplement la taille du contenu, le contenu dépend du type de l’objet et il y a 4 types d’objets différents : « blob », « tree », « commit » et « tag ».

  • Un « blob » est utilisé pour stocker les données d’un fichier — il s’agit en général d'un fichier.
  • Un « tree » est comme un répertoire — il référence une liste d’autres « tree » et/ou d’autres « blobs » (i.e. fichiers et sous-répertoires).
  • Un « commit » pointe vers un unique "tree" et le marque afin de représenter le projet à un certain point dans le temps. Il contient des méta-informations à propos de ce point dans le temps, comme le timestamp, l’auteur du contenu depuis le dernier commit, un pointeur vers le (ou les) dernier(s) commit(s), etc.
  • Un « tag » est une manière de représenter un commit spécifique un peu spécial. Il est normalement utilisé pour tagger certains commits en tant que version spécifique ou quelque chose comme ça.

La quasi-totalité de Git est construit autour de la manipulation de cette simple structure de 4 types d’objets différents. C’est comme un mini-système de fichier qui se situe au-dessus du système de fichier de votre ordinateur.

Différences avec SVN

Il est important de noter que ce système est très différent des autres outils de contrôle de version (SCM) dont vous êtes familier. Subversion, CVS, Perforce, Mercurial et les autres utilisent tous un système de stockage de Delta — ils stockent les différences entre un commit et le suivant. Git ne fait pas ça — il stocke une vue instantanée de la représentation de tous les fichiers de votre projet dans une structure hiérarchisée chaque fois que vous faites un commit. C’est un concept très important pour comprendre comment utiliser Git.

L’objet blob

Un « blob » stocke généralement le contenu d’un fichier.

Vous pouvez utiliser git show pour examiner le contenu de n’importe quel blob. Si nous avons le SHA1 d’un blob, nous pouvons l’examiner comme ceci :

$ git show 6ff87c4664

 Note that the only valid version of the GPL as far as this project
 is concerned is _this_ particular version of the license (ie v2, not
 v2.2 or v3.x or whatever), unless explicitly otherwise stated.
...

Un « blob » n’est rien de plus qu’un morceau de données binaires. Il ne fait référence à rien et n’a aucun attribut, même pas un nom de fichier.

Puisque le « blob » est entièrement défini par ses données, si deux fichiers dans un répertoire (ou dans différentes versions du dépôt) ont le même contenu, ils partageront alors le même objet blob. Cet objet est totalement indépendant de l’endroit où il se trouve dans la hiérarchie des dossiers et renommer un fichier ne change pas l’objet auquel ce fichier est associé.

L’objet tree

Un « tree » est un simple objet qui contient une liste de pointeurs vers des « blobs » et d’autres « trees » — il représente généralement le contenu d’un répertoire ou d’un sous-répertoire.

La commande polyvalente git show peut aussi être utilisée pour examiner un objet « tree », mais git ls-tree vous donnera plus de détails. Si nous avons le SHA1 d’un « tree », nous pouvons le détailler comme ceci :

$ git ls-tree fb3a8bdd0ce
100644 blob 63c918c667fa005ff12ad89437f2fdc80926e21c    .gitignore
100644 blob 5529b198e8d14decbe4ad99db3f7fb632de0439d    .mailmap
100644 blob 6ff87c4664981e4397625791c8ea3bbb5f2279a3    COPYING
040000 tree 2fb783e477100ce076f6bf57e4a6f026013dc745    Documentation
100755 blob 3c0032cec592a765692234f1cba47dfdcc3a9200    GIT-VERSION-GEN
100644 blob 289b046a443c0647624607d471289b2c7dcd470b    INSTALL
100644 blob 4eb463797adc693dc168b926b6932ff53f17d0b1    Makefile
100644 blob 548142c327a6790ff8821d67c2ee1eff7a656b52    README
...

Comme vous pouvez le voir, un objet « tree » contient une liste d’entrées, chacune avec un mode, un type d’objet, un nom SHA1, un nom, le tout trié à partir des noms. L’objet « tree » représente le contenu d’un unique dossier.

Un objet référencé par un « tree » peut être un « blob » représentant le contenu d’un fichier ou un autre « tree » représentant le contenu d’un sous-répertoire. Puisque les « trees » et les « blobs », comme les autres objets, sont nommés par le hash SHA1 de leur contenu, deux « trees » ont le même nom SHA1 si, et seulement si, leur contenu (en incluant récursivement le contenu de tous les sous-répertoires) est identique. Cela permet à git de déterminer rapidement les différences entre deux objets « trees » associés puisqu'il peut ignorer les entrées avec le même nom d’objet.

Note : en présence de sous-modules, les « trees » peuvent aussi contenir des commits comme entrées. Voir la section Sous-Modules.

Notez que tous les fichiers ont le mode 644 ou 755 : git ne tient compte que du bit exécutable.

L’objet commit

L’objet « commit » lie l’état physique d’un « tree » avec une description de la manière et de la raison de l’arrivée à cet état.

Vous pouvez utiliser git show ou git log avec l’option --pretty=raw pour examiner vos « commits » favoris :

$ git show -s --pretty=raw 2be7fcb476
commit 2be7fcb4764f2dbcee52635b91fedb1b3dcf7ab4
tree fb3a8bdd0ceddd019615af4d57a53f43d8cee2bf
parent 257a84d9d02e90447b149af58b271c19405edb6a
author Dave Watson <dwatson@mimvista.com> 1187576872 -0400
committer Junio C Hamano <gitster@pobox.com> 1187591163 -0700

    Fix misspelling of 'suppress' in docs

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

Comme vous pouvez le voir, un commit est défini par :

  • Un « tree » : Le nom SHA1 de l’objet « tree » (comme défini précédemment) représentant le contenu d’un répertoire à un certain moment.
  • parent(s) : Le nom SHA1 du (ou des) numéro de « commits » qui représente(nt) l’étape antérieure dans l’historique du projet. L’exemple ci-dessus a un parent; les commits mergés peuvent en avoir plus d’un. Un « commit » sans parent est nommé le « commit racine » et représente la révision initiale d’un projet. Chaque projet doit contenir au moins une racine. Un projet peut avoir plusieurs racine, bien que ça ne soit pas très commun (ou que ça ne soit pas une bonne idée).
  • Un author : Le nom de la personne responsable de ce changement, avec sa date
  • Un committer : Le nom de la personne qui a créé le « commit », avec la date de création. Cet attribut peut être différent de l’auteur, par exemple, si l’auteur écrit un patch et l’envoi à une autre personne par mail, cet personne peut utilisé le patch pour créer le « commit ».
  • Un comment qui décrit ce « commit ».

Notez qu’un « commit » ne contient pas d’information à propos de ce qui a été modifié ; tous les changements sont calculés en comparant les contenus du « tree » référencé dans ce « commit » avec le « tree » associé au(x) parent(s) du « commit ». En particulier, git n’essaye pas d’enregistrer le rennomage de fichier explicitement, bien qu’il puisse identifier des cas où la persistance des données d’un fichier avec un chemin modifié suggère un rennomage (voir par exemple, la commande git diff avec l’option -M).

Un « commit » est normalement créé avec la commande git commit, qui crée un « commit » dont le parent est le HEAD courant et avec le « tree » pris depuis le contenu actuellement stocké dans l’index.

Le modèle objet

Donc, maintenant que nous avons vu les 3 types d’objets principaux (blob, tree et commit), regardons rapidement comment ils travaillent ensemble.

Si nous avons un simple projet avec la structure de dossiers suivante :

$>tree
.
|-- README
`-- lib
    |-- inc
    |   `-- tricks.rb
    `-- mylib.rb

2 directories, 3 files

Et si nous committons ce projet sur un dépôt Git, il sera représenté comme ça :

Vous pouvez voir que nous avons créé un objet tree pour chaque répertoire (pour la racine aussi) et un objet blob pour chaque fichier. Ensuite nous avons un objet commit qui pointe vers la racine, afin que nous puissions récupérer l’apparence du projet quand il a été committé.

L’objet tag

Un objet « tag » contient un nom d’objet (simplement nommé « object »), un type d’objet, un nom de tag, le nom de la personne (« taggeur ») qui a créé le tag et un message, qui peut contenir une signature comme on peut le voir en utilisant git cat-file :

$ git cat-file tag v1.5.0
object 437b1b20df4b356c9342dac8d38849f24ef44f27
type commit
tag v1.5.0
tagger Junio C Hamano <junkio@cox.net> 1171411200 +0000

GIT 1.5.0
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)

iD8DBQBF0lGqwMbZpPMRm5oRAuRiAJ9ohBLd7s2kqjkKlq1qqC57SbnmzQCdG4ui
nLE/L9aUXdWeTFPron96DLA=
=2E+0
-----END PGP SIGNATURE-----

Voyez la commande git tag pour apprendre comment créer et vérifier les objets « tags » (Notez que git tag peut aussi être utilisé pour créer des « tags légers », qui ne sont pas du tout des objets « tags » mais juste de simples références dont le nom commence par « refs/tags/ »).