Fonctionnement de git⚓︎
Exploration initiale⚓︎
Pour comprendre comment fonctionne git, il peut être intéressant de jeter un coup d'oeil sous le capot. Tout se trouve dans le dossier .git
, il suffit donc d'explorer.
Commençons par créer un dépôt vide (répertoire test), et naviguer dedans.
# Création d'un dépôt vide, dans le répertoire test
git init test
# Navigation dans ce dossier
cd test
Nous pouvons voir que ce dépôt ne contient qu'un répertoire "caché" : .git
Listons le contenu de ce répertoire .git
$ ls -al .git/
total 7
drwxrwxr-x 7 jmbarbier jmbarbier 10 juin 30 18:27 ./
drwxrwxr-x 3 jmbarbier jmbarbier 3 juin 30 18:27 ../
drwxrwxr-x 2 jmbarbier jmbarbier 2 juin 30 18:27 branches/
-rw-rw-r-- 1 jmbarbier jmbarbier 92 juin 30 18:27 config
-rw-rw-r-- 1 jmbarbier jmbarbier 73 juin 30 18:27 description
-rw-rw-r-- 1 jmbarbier jmbarbier 21 juin 30 18:27 HEAD
drwxrwxr-x 2 jmbarbier jmbarbier 15 juin 30 18:27 hooks/
drwxrwxr-x 2 jmbarbier jmbarbier 3 juin 30 18:27 info/
drwxrwxr-x 4 jmbarbier jmbarbier 4 juin 30 18:27 objects/
drwxrwxr-x 4 jmbarbier jmbarbier 4 juin 30 18:27 refs/
Nous allons explorer chacun de ces éléments.
Le fichier HEAD
: c'est un simple fichier texte, qui contient le texte ref: refs/heads/main
$ cat .git/HEAD
ref: refs/heads/main
Le fichier config
est aussi un fichier texte, au format toml
. Il contient quelques lignes pouvant varier selon les systèmes.
# Sur un mac
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
ignorecase = true
precomposeunicode = true
# Sur un linux
$ cat .git/config
[core]
repositoryformatversion = 0
filemode = true
bare = false
logallrefupdates = true
Le fichier description
est aussi un fichier texte. Comme son nom l'indique, il est destiné à contenir le nom du projet, mais on peut tout à fait ne jamais le toucher.
$ cat .git/description
Unnamed repository; edit this file 'description' to name the repository.
Le dossier hooks
contient des exemples de scripts qui peuvent être appelés à chaque étape du cycle de vie d'un dépôt.
$ ls .git/hooks
applypatch-msg.sample* pre-applypatch.sample* pre-rebase.sample* sendemail-validate.sample*
commit-msg.sample* pre-commit.sample* pre-receive.sample* update.sample*
fsmonitor-watchman.sample* pre-merge-commit.sample* prepare-commit-msg.sample*
post-update.sample* pre-push.sample* push-to-checkout.sample*
Le dossier info
ne contient qu'un seul fichier, le fichier exclude
. Ce fichier permet d'exclure certains fichiers du suivi de version, comme les fichiers .gitignore
, mais sans être lui-même enregistré dans le suivi de version.
cat .git/info/exclude
# git ls-files --others --exclude-from=.git/info/exclude
# Lines that start with '#' are comments.
# For a project mostly in C, the following would be a good set of
# exclude patterns (uncomment them if you want to use them):
# *.[oa]
# *~
Le dossier objects
ne contient que deux répertoires vides : info
et pack
ls -l .git/objects
.git/objects/info:
total 0
.git/objects/pack:
total 0
De mĂŞme, le dossier refs
ne contient que deux répertoires vides : heads
et tags
ls -l .git/refs/*
.git/refs/heads:
total 0
.git/refs/tags:
total 0
La commande git status
affiche :
$ git status
Sur la branche main
Aucun commit
rien à valider (créez/copiez des fichiers et utilisez "git add" pour les suivre)
Nous allons voir comment évolue le contenu de ce répertoire .git
au fur et à mesure que l'on travaille avec ce dépôt.
Premier commit⚓︎
Nous allons rajouter un fichier dans le dépôt, un fichier test
qui contient le texte hello
.
echo "hello" > test
La commande git status
affiche alors
$ git status
Sur la branche main
Aucun commit
Fichiers non suivis:
(utilisez "git add <fichier>..." pour inclure dans ce qui sera validé)
test
aucune modification ajoutée à la validation mais des fichiers non suivis sont présents (utilisez "git add" pour les suivre)
Le contenu du dossier .git
n'a pas du tout changé. Si nous utilisons la commande suggérée : git add test
git add test
git status
nous confirme que quelque chose a changé :
$ git status
Sur la branche main
Aucun commit
Modifications qui seront validées :
(utilisez "git rm --cached <fichier>..." pour désindexer)
nouveau fichier : test
En listant les fichiers, find .git
, nous voyons au milieu des fichiers et dossiers déjà présents précédemment 2 nouveautés :
$ find .git
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
.git/index
Ces 2 fichiers sont des fichiers binaires. Des commandes git existent pour pouvoir lire leur contenu plus facilement :
git ls-files -s
: lit le fichierindex
(entre autres)git cat-file -p <hash>
: lit le contenu des fichiers dansobjects
. Le hash est constitué des 2 lettres du répertoire + le nom de fichier (ici:ce013625030ba8dba906f756967f9e9ca394464a
). Si il n'y a pas d'ambiguité, on peut ne donner que les premières lettres du hash (minimum 4). La même commande avec-s
nous donnera la taille et-t
le type.
RĂ©sultat de ces deux commandes :
$ git cat-file -p ce013625
hello
$ git cat-file -s ce013625
6
$ git cat-file -t ce013625
blob
$ git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 test
On voit que le contenu du fichier test
a été copié dans la base des objets (le nom de chaque objet est un hash associé au contenu),
et que le fichier index
référence le fichier test
comme correspondant Ă l'objet ce013625..
.
flowchart LR
ce01[blob:ce01:hello]
Nous allons maintenant faire le commit
pour enregistrer cet Ă©tat :
$ git commit -m "commit 1"
[main (commit racine) bdb7563] commit 1
1 file changed, 1 insertion(+)
create mode 100644 test
Git nous confirme que le commit a bien été effectué, et nous donne un résumé des modifications.
La commande git status
nous indique que tout est propre :
$ git status
Sur la branche main
rien Ă valider, la copie de travail est propre
Le contenu du dossier .git
a changé : dans le dossier objects
, de nouveaux fichiers sont apparus :
$ find .git/objects -type f
.git/objects/bd/b7563d98b04292eaaa047ac8c5d64998e6d18a # nouveau
.git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90 # nouveau
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
Nous pouvons inspecter le contenu de ces fichiers, en commencant par celui dont le hash correspond à celui indiqué dans le message de commit :
$ git cat-file -t bdb7
commit
$ git cat-file -p bdb7
tree 31d73eb4914a8ddb6cb0e4adf250777161118f90
author BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719766117 +0200
committer BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719766117 +0200
commit 1
Ce fichier contient toutes les informations nécessaires pour retrouver l'état du dépôt à ce moment précis, ainsi que des informations sur l'auteur et le commiteur du commit, la date, et le message associé.
Ce commit indique un tree
, qui est un autre objet git, permettant de représenter l'arborescence des fichiers à ce moment précis. C'est le 3ème objet qui est apparu dans le dossier objects
.
git cat-file -t 31d7
tree
$ git cat-file -p 31d7
100644 blob ce013625030ba8dba906f756967f9e9ca394464a test
On voit que ce tree
contient une seule entrée, permettant d'associer le fichier test
Ă l'objet ce013625..
.
flowchart LR
ce01[blob:ce01:hello]
31d7[tree:31d7]
bdb7[commit:bdb7:commit 1]
bdb7 --> 31d7
31d7 -- test --> ce01
Un autre fichier est apparu dans le dossier refs/heads
:
$ cat .git/refs/heads/main
bdb7563d98b04292eaaa047ac8c5d64998e6d18a
# Rappel
$ cat .git/HEAD
ref: refs/heads/main
Ce fichier nous indique le hash du dernier commit sur la branche main
, et le fichier HEAD
nous indique la branche sur laquelle nous sommes actuellement.
flowchart LR
ce01[blob:ce01:hello]
31d7[tree:31d7]
bdb7[commit:bdb7:commit 1]
bdb7 --> 31d7
31d7 -- test --> ce01
HEAD --> refs/heads/main
refs/heads/main --> bdb7
Petit coup d'oeil sur le fichier index
:
$ git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 test
Deuxième commit⚓︎
Nous allons :
- rajouter un fichier
test2
contenant le motworld
, dans le même répertoire. - rajouter un fichier
test3
contenant le mottest
, dans un sous-répertoiredossier
. - modifier le fichier
test
pour mettre une majuscule Ăhello
.
echo "world" > test2
mkdir dossier
echo "world" > dossier/test3
echo "Hello" > test
La commande git status
nous indique que test2
et le contenu de dossier
sont non suivis, et que test
a été modifié.
$ git status
Sur la branche main
Modifications qui ne seront pas validées :
(utilisez "git add <fichier>..." pour mettre à jour ce qui sera validé)
(utilisez "git restore <fichier>..." pour annuler les modifications dans le répertoire de travail)
modifié : test
Fichiers non suivis:
(utilisez "git add <fichier>..." pour inclure dans ce qui sera validé)
dossier/
test2
aucune modification n'a été ajoutée à la validation (utilisez "git add" ou "git commit -a")
Sur la branche main
Fichiers non suivis:
(utilisez "git add
Comme précédemment, rien n'a changé dans le dossier `.git` pour l'instant.
Nous allons ajouter ces modifications Ă l'index.
```bash
$ git add test test2 dossier/test3
La commande git status
nous indique que les fichiers sont prêts à être commités.
$ git status
Sur la branche main
Modifications qui seront validées :
(utilisez "git restore --staged <fichier>..." pour désindexer)
nouveau fichier : dossier/test3
modifié : test
nouveau fichier : test2
En listant les objets dans le dossier .git
, nous voyons que 2 nouveaux objets sont apparus :
$ find .git/objects -type f
.git/objects/bd/b7563d98b04292eaaa047ac8c5d64998e6d18a
.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78 # nouveau
.git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90
.git/objects/cc/628ccd10742baea8241c5924df992b5c019f71 # nouveau
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
Ces 2 nouveaux objets sont les objets correspondant aux contenus Hello
et world
.
$ git cat-file -t e965
blob
$ git cat-file -p e965
Hello
$ git cat-file -t cc62
blob
$ git cat-file -p cc62
Hello
Le fichier index
a aussi été mis à jour :
$ git ls-files -s
100644 ce013625030ba8dba906f756967f9e9ca394464a 0 dossier/test3
100644 e965047ad7c57865823c7d992b1d046ea66edf78 0 test
100644 cc628ccd10742baea8241c5924df992b5c019f71 0 test2
Nous allons maintenant faire le commit :
$ git commit -m "commit 2"
[main 166e718] commit 2
3 files changed, 3 insertions(+), 1 deletion(-)
create mode 100644 dossier/test3
create mode 100644 test2
Ă€ nouveau, des objets sont apparus dans le dossier objects
:
.git/objects/bd/b7563d98b04292eaaa047ac8c5d64998e6d18a
.git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78
.git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90
.git/objects/a5/cac10b4a8614c94e77bf34b2a887a879e6a9bd # nouveau
.git/objects/cc/628ccd10742baea8241c5924df992b5c019f71
.git/objects/16/6e71833a2cfb74d9d22807a597b0a1a2634364 # nouveau
.git/objects/0a/5033915bbf715a2b1f67b077ba195b12302b28 # nouveau
.git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
Ces 3 nouveaux objets sont les objets du commit, et des 2 tree
associés permettant d'enregistrer l'arborescence des fichiers à ce moment précis.
$ git cat-file -t 166e
commit
$ git cat-file -p 166e
tree a5cac10b4a8614c94e77bf34b2a887a879e6a9bd
parent bdb7563d98b04292eaaa047ac8c5d64998e6d18a
author BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719769536 +0200
committer BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719769536 +0200
commit 2
Le parent de ce commit est le commit précédent, et le tree
associé est le nouvel objet a5cac10b4a8614c94e77bf34b2a887a879e6a9bd
.
$ git cat-file -t a5ca
tree
$ git cat-file -p a5ca
040000 tree 0a5033915bbf715a2b1f67b077ba195b12302b28 dossier
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78 test
100644 blob cc628ccd10742baea8241c5924df992b5c019f71 test2
Ce tree
contient 3 entrées, permettant d'associer les fichiers test
, test2
et le dossier dossier
Ă leur objet respectif.
$ git cat-file -t 0a50
tree
$ git cat-file -p 0a50
100644 blob ce013625030ba8dba906f756967f9e9ca394464a test3
Le fichier refs/heads/main
a été mis à jour :
$ cat .git/refs/heads/main
166e71833a2cfb74d9d22807a597b0a1a2634364
On peut tracer le graphe des objets git :
flowchart LR
bdb7[commit:bdb7:commit 1]
e965[blob:e965:Hello]
31d7[tree:31d7]
a5ca[tree:a5ca]
cc62[blob:cc62:world]
0a50[tree:0a50]
ce01[blob:ce01:hello]
166e[commit:166e:commit 2]
bdb7 --> 31d7
31d7 -- test --> ce01
166e -- parent --> bdb7
166e -- tree --> a5ca
a5ca -- dossier --> 0a50
a5ca -- test --> e965
a5ca -- test2 --> cc62
0a50 -- test3 --> ce01
HEAD --> refs/heads/main
refs/heads/main --> 166e
On peut voir que les commits sont des objets qui contiennent des références vers des tree
, qui contiennent des références vers des objets blob
ou d'autres tree
. C'est ce mécanisme qui permet de retrouver l'état du dépôt à un moment donné, et de suivre l'évolution des fichiers.
Branche et nouveau commit⚓︎
Une branche est un pointeur vers un état du dépôt.
Nous allons créer une nouvelle branche issue-1
, et faire un commit sur cette branche.
$ git branch issue-1
Cette commande crée un nouveau fichier dans le dossier refs/heads
:
find .git/refs -type f
.git/refs/heads/main
.git/refs/heads/issue-1
Le contenu de ce fichier est le hash du commit sur lequel pointe la branche :
$ cat .git/refs/heads/issue-1
166e71833a2cfb74d9d22807a597b0a1a2634364
En revanche le fichier HEAD
n'a pas changé :
$ cat .git/HEAD
ref: refs/heads/main
Nous allons maintenant rendre la branche issue-1
active :
$ git checkout issue-1
Le fichier HEAD
a été mis à jour :
$ cat .git/HEAD
ref: refs/heads/issue-1
Nous allons maintenant rajouter un fichier test4
contenant le mot issue
, et faire un commit sur cette branche.
$ echo "issue" > test4
$ git add test4
$ git commit -m "commit 3"
[issue-1 932f923] commit 3
1 file changed, 1 insertion(+)
create mode 100644 test4
Des objets sont apparus dans le dossier objects
:
$ ls -altr .git/objects/*/*
-r--r--r-- 1 jmbarbier jmbarbier 139 juin 30 18:48 .git/objects/bd/b7563d98b04292eaaa047ac8c5d64998e6d18a
-r--r--r-- 1 jmbarbier jmbarbier 49 juin 30 18:48 .git/objects/31/d73eb4914a8ddb6cb0e4adf250777161118f90
-r--r--r-- 1 jmbarbier jmbarbier 21 juin 30 19:37 .git/objects/e9/65047ad7c57865823c7d992b1d046ea66edf78
-r--r--r-- 1 jmbarbier jmbarbier 21 juin 30 19:37 .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a
-r--r--r-- 1 jmbarbier jmbarbier 21 juin 30 19:37 .git/objects/cc/628ccd10742baea8241c5924df992b5c019f71
-r--r--r-- 1 jmbarbier jmbarbier 106 juin 30 19:45 .git/objects/a5/cac10b4a8614c94e77bf34b2a887a879e6a9bd
-r--r--r-- 1 jmbarbier jmbarbier 169 juin 30 19:45 .git/objects/16/6e71833a2cfb74d9d22807a597b0a1a2634364
-r--r--r-- 1 jmbarbier jmbarbier 50 juin 30 19:45 .git/objects/0a/5033915bbf715a2b1f67b077ba195b12302b28
-r--r--r-- 1 jmbarbier jmbarbier 21 juin 30 20:02 .git/objects/78/77337e0ffb58fda84644dea72ffae13e394d94 # nouveau
-r--r--r-- 1 jmbarbier jmbarbier 132 juin 30 20:02 .git/objects/e6/24812b89d4c38a5f59fb477665fc50eb477ee9 # nouveau
-r--r--r-- 1 jmbarbier jmbarbier 170 juin 30 20:02 .git/objects/93/2f9235b01b90343e78647d4f89e4879e6d6446 # nouveau
Ces 3 fichiers correpondents :
- au commit
commit 3
pour le fichier93/2f9235b01b90343e78647d4f89e4879e6d6446
- au
tree
associé pour le fichiera5/cac10b4a8614c94e77bf34b2a887a879e6a9bd
- au contenu du fichier
test4
pour le fichiere6/24812b89d4c38a5f59fb477665fc50eb477ee9
$ git cat-file -t 932f
commit
$ git cat-file -p 932f
tree e624812b89d4c38a5f59fb477665fc50eb477ee9
parent 166e71833a2cfb74d9d22807a597b0a1a2634364
author BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719770529 +0200
committer BARBIER Jean-Matthieu <jm.barbier@solidev.net> 1719770529 +0200
commit 3
$ git cat-file -t e624
tree
$ git cat-file -p e624
040000 tree 0a5033915bbf715a2b1f67b077ba195b12302b28 dossier
100644 blob e965047ad7c57865823c7d992b1d046ea66edf78 test
100644 blob cc628ccd10742baea8241c5924df992b5c019f71 test2
100644 blob 7877337e0ffb58fda84644dea72ffae13e394d94 test4
$ git cat-file -t 7877
blob
$ git cat-file -p 7877
issue
flowchart TB
bdb7[commit:bdb7:commit 1]
e965[blob:e965:Hello]
31d7[tree:31d7]
a5ca[tree:a5ca]
cc62[blob:cc62:world]
0a50[tree:0a50]
ce01[blob:ce01:hello]
166e[commit:166e:commit 2]
932f[commit:932f:commit 3]
e624[tree:e624]
7877[blob:7877:issue]
bdb7 --> 31d7
31d7 -- test --> ce01
166e -- parent --> bdb7
166e -- tree --> a5ca
a5ca -- dossier --> 0a50
a5ca -- test --> e965
a5ca -- test2 --> cc62
0a50 -- test3 --> ce01
932f -- parent --> 166e
932f -- tree --> e624
e624 -- test4 --> 7877
HEAD --> refs/heads/issue-1
refs/heads/issue-1 --> 932f
refs/heads/main --> 166e
On voit ici que la branche "main" existe toujours, mais que la branche "issue-1" pointe sur un commit différent. On peut basculer d'une branche à l'autre, et faire évoluer chaque branche indépendamment.
Si on veut fusionner les modifications de la branche "issue-1" dans la branche "main", on peut faire un merge
.
$ git checkout main
$ git merge issue-1
Mise Ă jour 166e718..932f923
Fast-forward
test4 | 1 +
1 file changed, 1 insertion(+)
create mode 100644 test4
git log --oneline --graph --all
A ce stade, les branches "main" et "issue-1" pointent sur le mĂŞme commit, et on peut supprimer la branche "issue-1".
$ git branch -d issue-1