En fait, c'est beaucoup plus simple. C'est même presque « trivial » une fois que l'on comprend le schéma de principe mais, en effet, tant que l'on ne sait pas quel modèle Git adopte, on se visualise son fonctionnement en s'appuyant sur celui d'autres systèmes, qui sans être foncièrement compliqués restent malgré tout assez différents. Et de fait, cela rend les choses assez absconses.
- L'historique de Git est un « graphe », formé par les différents commits qui se référencent entre eux. Chaque sommet de ce graphe est un commit ;
- Les « commits » en particulier sont un des quatre types d'objets que Git définit : commit, tree, blob ou annotated tag ;
- Chaque objet est un fichier (dans .git/objects) et le nom de chaque objet est la somme SHA-1 de son contenu, ce qui les rend immuables (impossible de modifier leur contenu sans modifier leur somme, qui ne correspondrait plus à leur nom) ;
- Les commits font référence aux autres objets par leur nom, donc par leur somme SHA-1. Comme toutes ces références arrières font partie des informations d'un commit et que celui-ci est signé de la même manière, cela forme de fait une blockchain (et ce depuis 2005, c'est-à-dire bien avant que Bitcoin ne popularise le principe).
De là, la structure du graphe étant intrinsèque, les « branches » en elles-mêmes ne sont en fait que des références vers le commit du haut de la branche, depuis lequel on peut ensuite la remonter. Git les gère sous la forme de simples fichiers là-aussi (cette fois dans .git/refs) qui contiennent exactement le nom du commit référencé, comme un lien symbolique Unix contient exactement le fichier vers le fichier pointé, et uniquement ce chemin.
Ce qui est important, c'est que ces objets étant immuables, ce sont les mêmes du côté local et du côté distant, ainsi que sur tous les dépôts sur lesquels ils se trouvent sur la planète. De fait, lorsque tes dépôts sont synchrones et à jour, les branches homologues pointent le même objet (car elles contiennent la même somme SHA-1).
Exemple :
master → O ← origin/master (7fd5a) dernier commit en date
/ \
/ \
↙ ↘
Commit principal 5 (44567) O O (56842) Commit secondaire « c »
| |
↓ ↓
Commit principal 4 (1023f) O |
| |
↓ ↓
Commit principal 3 (99fac) O O (af675) Commit secondaire « b »
| |
↓ ↓
Commit principal 2 (78abc) O |
| |
↓ ↓
Commit principal 1 (56457) O O (8bc42) Commit secondaire « a »
\ /
\ /
↘ ↙
O (123fe) Premier commit commun
|
↓
O (bbcda) Commit initial
Si l'on provoque l'affichage de l'état des branches connues du dépôt local (branches locales et distantes) avec git branch -vva, on obtient :
* master 7fd5a [origin/master] v2release : prise en compte de la migration du framework
remotes/origin/HEAD -> origin/master
remotes/origin/master 7fd5a v2release : prise en compte de la migration du framework
On voit bien que les branches master et origin/master référencent le même objet, donc se trouvent au même endroit dans le graphe (et par conséquent, dans l'historique). On peut même aller explorer directement les fichiers concernés (avec cat, ou type sous Windows) :
$ cat .git/refs/heads/master
7fd5af02096ee0c6dd00c07f939d51a5b0d7da47
$ cat .git/refs/remotes/origin/master
7fd5af02096ee0c6dd00c07f939d51a5b0d7da47
Autre chose importante : les objets eux-mêmes sont tous individuels (même s'ils font référence les uns aux autres). Soit Git les produit lui-même, soit il les télécharge depuis l'extérieur mais, in fine, ils finissent tous « en vrac » au même endroit sous .git/objects.
Lorsque tu mets un dépôt à jour, même partiellement, que soit via fetch, push, pull ou remote update, Git récupère d'abord l'état de ces branches pour les mettre à jour. Il examine ensuite le nom de l'objet référencé par chaque branche et là, soit il possède déjà l'objet en question et il peut directement passer à la suite, soit il ne l'a pas encore et il va demander au dépôt distant de lui envoyer. Il va alors examiner le contenu de cet objet à la recherche d'autres références et s'il en trouve, vérifier s'il possède aussi les objets concernés et donc, demander à se les faire envoyer également et appliquer la même procédure, récursivement. C'est de cette manière que Git peut « tirer le fil » et reconstituer l'intégralité de l'historique qui lui manque, fût-ce même à partir de rien.
Ce qui nous amène à la situation qui t'intéresse : chaque dépot (local ou distant) possède donc sa propre copie de chaque objet. Par contre : ton dépôt local ne tient pas une « image » du dépôt distant. Il « sait » simplement que les branches distantes pointent les objets d'un même graphe, sur lequel il travaille déjà.
Lorsque tu lances git status, Git compare simplement le dernier état connu de tes branches locales et de celles des serveurs distants pour savoir combien de commits les sépare, état récupéré la dernière fois qu'il s'est connecté à ces serveurs et qu'il conserve en local dans les fameuses branches « remotes/… ».
Ces branches « remotes/… » sont ensuite mises à jour chaque fois que l'on y fait référence avec fetch, pull ou push, ou lorsque l'on fait la gestion des dépôts distants en particulier avec git remote. Cette dernière commande permet d'effectuer beaucoup d'opérations de maintenance dessus et, parmi elles, update te permet de mettre à jour un dépôt distant en particulier, ou tous les dépôts distants déclarés si aucun d'eux n'est explicitement spécifié.
Partager