Présentation
Notes prises à partir de la
présentation de Tim Berglund
(@tlberglund/tlberglund@github.com /
Github).
Les thèmes abordés lors de la présentation
- commit à la main (repo internals)
- Rebase interactif
- Pull rebase
- External diff tools
- Reflog
- Reset
Ceux qui ne l'ont pas été par manque de temps
Un petit tour dans les entrailles de Git
3 Types d'objets gérés par Git
Un dépôt Git est un ensemble de fichiers de type blob, tree et Commit.
Si on se représente un arbre, les blob sont les feuilles et les tree sont les
nœuds. Les commits sont alors des pointeurs vers un nœud, ce qui est alors
un point d'entrée sur une branche.
Chaque fichier est stocké dans un fichier ayant pour nom, l'empreinte sha256 du
contenu de l'objet. Cela permet l'unicité des fichiers et des références
associées. On peut comme cela stocké plusieurs versions d'un même fichier et
référencer chaque version sans risque d'écrasement.
- le blob est le contenu d'un fichier.
- le tree est un élément qui contient une liste de blob et des empreintes
associées. Cela correspond au principe de répertoire
- le commit est un pointeur sur un objet de type 'tree' (empreinte du
tree). Il contient aussi l'auteur, le commiter et le message du commit
ainsi qu'un pointeur sur le _commit_parent
Chaque commit_donne lieu a de nouveaux _blob et tree correspondant à
l'ensemble des éléments qui composent le commit. Un fichier qui n'a pas été
modifié est référencé dans plusieurs tree au travers de plusieurs commits.
Il y va de même pour les tree qui n'ont pas changé dans un commit.
Bonne idée d'illustration en live
Pour faire la démonstration, Tim crée un dépôt vide
$ git init bits && cd bits
Il ouvre deux terminaux
Commandes Git |
while true; do _tree_ .git; sleep 1; done; |
$ git status
Sur la branche master
Aucun commit
rien à valider (créez/copiez des fichiers et utilisez "git add" pour les suivre)
|
.git
├── config
├── HEAD
├── objects
│ ├── info
│ └── pack
└── refs
├── heads
└── tags
|
Suppression des fichiers inutiles pour la démonstration
État initial du référentiel Git
Création d'un objet de type blob
$ echo "on cree un _blob_ qui contient ce texte" | git empreinte-object -w --stdin
128e7392089cd1fc22e3905214325da95a7d96e7
Information sur l'objet référencé par le empreinte
* Son type
$ git cat-file -t 128e7392089cd1fc22e3905214325da95a7d96e7
_tree_
- Son contenu
$ git cat-file -p 128e7392089cd1fc22e3905214325da95a7d96e7
on cree un _blob_ qui contient ce texte
État du référentiel Git est toujours l'état initial
$ git status
# FIXME: contrairemment à la démo, lors de ce git status, un objet _tree_
vide est créé
On va déclaré et nommé l'objet créé dans le cache.Cela impacte le contenu
du fichier .git/index
$ git update-index --add --cacheinfo 100644 \
825dc642cb6eb9a060e54bf8d69288fbee4904 README.txt
On le vérifie de la façon suivante
$ git status
...
new file: README.txt
...
deleted: README.txt
...
On créé maintenant l'objet tree qui correspond à l'état du répertoire
contenant README.txt
$ git write-_tree_
0055d7f6ee8c48e021848b27a13cd493449333b0
$ git cat-file -t 0055d7f
_tree_
$ git cat-file -p 0055d7f
100644 _blob_ 128e7392089cd1fc22e3905214325da95a7d96e7 README.txt
On commit_l'objet _tree
$ git commit-_tree_ 0055d7f6ee8c48e021848b27a13cd493449333b0 \
-m "Crazy crazy commit"
b610c2b4fc8b98a7a58e1657a18087c2d27ba6ff
L'état du référentiel est toujours sale git status
car la référence
pointée par HEAD n'existe pas
$ cat .git/HEAD
ref: refs/heads/master
On met à jour cette référence
$ git update-ref refs/heads/master b610c2b4fc8b98a7a58e1657a18087c2d27ba6ff
$ cat .git/refs/heads/master
b610c2b4fc8b98a7a58e1657a18087c2d27ba6ff
On est peut enfin avoir des choses connues
$ git log -p
_commit_b610c2b4fc8b98a7a58e1657a18087c2d27ba6ff (HEAD -> master)
Author: fccagou <fccagou@gmail.com>
Date: Sun May 19 11:29:00 2019 +0200
Crazy crazy commit
diff --git a/README.txt b/README.txt
new file mode 100644
index 0000000..128e739
--- /dev/null
+++ b/README.txt
@@ -0,0 +1 @@
+on cree un _blob_ qui contient ce texte
Mais l'état du répertoire courant n'est pas en phase avec l'index puisqu'on
n'a jamais créé de fichier README.txt.
$ git status
On branch master
Changes not staged for commit:
(use "git add/rm <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
deleted: README.txt
no changes added to _commit_(use "git add" and/or "git _commit_-a")
Il ne nous reste plus qu'à faire un checkout
$ git checkout HEAD -- README.txt
$ $ cat README.txt
on cree un _blob_ qui contient ce texte
$ git status
On branch master
nothing to commit, working _tree_ clean
Jouons avec les branches, les rebase et les logs
Nouvelle présentation pour l'illustration
Commandes Git |
while true; do clear; git log --color --decorate --graph
--pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr)'
--abbrev-commit --all; done
|
$ git ...
|
* 61f6e0a - (HEAD -> master) add samples files (il y a 4 heures)
* e96b81c - Un changement dans master (il y a 5 heures)
* db3aefb - une modif (il y a 5 heures)
* b610c2b - Crazy crazy commit (il y a 5 heures)
|
Dans le second terminal, changer de commande pour l'illustration. Elle permet de
voir évoluer les références de HEAD et des branches.
Création d'un ensemble de commits et branches
Dans le terminal des commandes :
$ git branch
* master
$ git branch feature
$ echo "modif pour illustrer le rebase" >> README.txt
$ git _commit_-a -m "une modif"
$ git checkout master
On créé un ensemble de fichiers
$ mkdir files && ce files
$ for i in {1..5}; do echo $RANDOM > file$i.txt; git add file$i.txt; git _commit_-m "add file$i.txt" file$i.txt; done
[feature 948fdba] add file1.txt
1 file changed, 1 insertion(+)
create mode 100644 files/file1.txt
[feature 36cf263] add file2.txt
1 file changed, 1 insertion(+)
create mode 100644 files/file2.txt
[feature 74e45f8] add file3.txt
1 file changed, 1 insertion(+)
create mode 100644 files/file3.txt
[feature 217e179] add file4.txt
1 file changed, 1 insertion(+)
create mode 100644 files/file4.txt
[feature 96edd19] add file5.txt
1 file changed, 1 insertion(+)
create mode 100644 files/file5.txt
On voit le graphe sur le terminal de démonstration.
Un merge est un commit_avec 2 parents mais ce n'est pas ce qu'on veut. On veut
déplacer l'ensemble des _commit_d'une branche sur la branche _master. C'est le
rebase en fast forward
$ git checkout master
$ echo "nouveau changement dans la version master" >> README.txt
$ git _commit_-a -m "Un changement dans master"
[master e96b81c] Un changement dans master
1 file changed, 1 insertion(+)
$ git merge featue
suggestion : en attente de la fermeture duMerge made by the 'recursive' strategy.
files/file1.txt | 1 +
files/file2.txt | 1 +
files/file3.txt | 1 +
files/file4.txt | 1 +
files/file5.txt | 1 +
5 files changed, 5 insertions(+)
create mode 100644 files/file1.txt
create mode 100644 files/file2.txt
create mode 100644 files/file3.txt
create mode 100644 files/file4.txt
create mode 100644 files/file5.txt
Oups, je ne voulais pas faire un merge ... Mais temps que rien n'est poussé,
je suis serein :)
$ git branch
feature
* master
# On garde le _commit_merge pour l'exerice
$ git rev-parse master
b2a894705506648be602f1a927da0d1faea224de
$ git reset --hard e96b81c
HEAD est maintenant à e96b81c Un changement dans master
Reflog pour se sauver des presque tout et trouver ce qui a été fait
$ git reflog
e96b81c (HEAD -> master) HEAD@{0}: reset: moving to e96b81c
b2a8947 HEAD@{1}: merge feature: Merge made by the 'recursive' strategy.
e96b81c (HEAD -> master) HEAD@{2}: commit: Un changement dans master
db3aefb HEAD@{3}: checkout: moving from feature to master
96edd19 (feature) HEAD@{4}: commit: add file5.txt
217e179 HEAD@{5}: commit: add file4.txt
74e45f8 HEAD@{6}: commit: add file3.txt
36cf263 HEAD@{7}: commit: add file2.txt
948fdba HEAD@{8}: commit: add file1.txt
b610c2b HEAD@{9}: checkout: moving from master to feature
db3aefb HEAD@{10}: checkout: moving from feature to master
b610c2b HEAD@{11}: checkout: moving from master to feature
db3aefb HEAD@{12}: commit: une modif
b610c2b HEAD@{13}:
On retrouve bien le merge indésirable et sa valeur d'empreinte
$ git rev-parse HEAD@{1}
b2a894705506648be602f1a927da0d1faea224de
Et on peux y revenir finalement
$ git reset --hard HEAD@{1}
HEAD est maintenant à b2a8947 Merge branch 'feature'
Mais ce n'est pas ce qu'on veut, on veut bien ne pas faire ce merge
$ git reflog
b2a8947 (HEAD -> master) HEAD@{0}: reset: moving to HEAD@{1}
e96b81c HEAD@{1}: reset: moving to e96b81c
b2a8947 (HEAD -> master) HEAD@{2}: merge feature: Merge made by the 'recursive' strategy.
e96b81c HEAD@{3}: commit: Un changement dans master
db3aefb HEAD@{4}: checkout: moving from feature to master
96edd19 (feature) HEAD@{5}: commit: add file5.txt
217e179 HEAD@{6}: commit: add file4.txt
74e45f8 HEAD@{7}: commit: add file3.txt
36cf263 HEAD@{8}: commit: add file2.txt
948fdba HEAD@{9}: commit: add file1.txt
b610c2b HEAD@{10}: checkout: moving from master to feature
db3aefb HEAD@{11}: checkout: moving from feature to master
b610c2b HEAD@{12}: checkout: moving from master to feature
db3aefb HEAD@{13}: commit: une modif
b610c2b HEAD@{14}:
$ git reset --hard HEAD@{3}
HEAD est maintenant à e96b81c Un changement dans master
C'est parti pour un rebase
Revenons en au fait pour mettre nos modification de la branche feature dans la
branche master.
$ git checkout feature
Basculement sur la branche 'feature'
$ git rebase master
Rembobinage préalable de head pour pouvoir rejouer votre travail par-dessus...
Application de add file1.txt
Application de add file2.txt
Application de add file3.txt
Application de add file4.txt
Application de add file5.txt
$ git checkour master
Basculement sur la branche 'master'
$ git merge feature
Mise à jour e96b81c..9c7c907
Fast-forward
files/file1.txt | 1 +
files/file2.txt | 1 +
files/file3.txt | 1 +
files/file4.txt | 1 +
files/file5.txt | 1 +
5 files changed, 5 insertions(+)
create mode 100644 files/file1.txt
create mode 100644 files/file2.txt
create mode 100644 files/file3.txt
create mode 100644 files/file4.txt
create mode 100644 files/file5.txt
Rebase interactif
Petit rappel sue la syntaxe des références
$ git rev-parse HEAD~5
$ git rev-parse "HEAD^^^^^"
On squash tous les commits de création des fichiers dans files pour ne
faire qu'un seul commit.
$ git rebase -i HEAD~5
...
=> on remplace les 4 derniers _pick_ par squash, on sauvegarde et on quitte
...
=> on fait un joli message de commit, on sauvegarde, on quitte et hop
...
suggestion : en attente de la fermeture du[HEAD détachée 61f6e0a] add samples files
Date: Sun May 19 12:06:37 2019 +0200
5 files changed, 5 insertions(+)
create mode 100644 files/file1.txt
create mode 100644 files/file2.txt
create mode 100644 files/file3.txt
create mode 100644 files/file4.txt
create mode 100644 files/file5.txt
Successfully rebased and updated refs/heads/master.
On obtient un nouveau commit sur la branche master et la branche feature
référence toujours les 5 commits faits pour créer les 5 fichiers.
On n'a plus besoin de la branche feature donc on a supprime
$ git branch -D feature
Branche feature supprimée (précédemment 9c7c907).
A ce stade, il n'y a plus de référence au commit pointé par le branche
feature MAIS le commit existe toujours. Pour s'en persuader, il suffit
d'interroger Git.
$ git cat -t 9c7c907
commit
External diff tools
From scratch
L'équivalent de git init scratch
$ mkdir scratch && cd scratch
$ mkdir -p .git/objects
$ mkdir -p .git/refs/heads
$ echo 'ref: refs/heads/master' > .git/HEAD
Création d'un premier fichier
$ git status
$ echo "first file" | git hash-object --stdin -w
$ git update-index --add --cacheinfo 100644 303ff981c488b812b6215f7db7920dedb3b59d9a README.txt
$ git write-tree
$ git commit-tree f9f8cb0e34417c41edafe889c303d1acc9a858b7 -m "Crazy crazy commit"
$ git update-ref refs/heads/master be8c65c58aa5ab7ef8737079da858486fd102bf8
$ git checkout master -- README.txt