Nouvelles Chroniques d'Amethyste

Penser au Sens, pas au Verbe

Git comme on vous en a sans doute jamais parlé – Partie 2/2

Poster un commentaire

Deuxième partie d’un article commencé ici:

https://amethyste16.wordpress.com/2016/04/02/git-comme-on-vous-en-a-sans-doute-jamais-parle-partie-12/

Cette fois on va parler de branches et de merge.

On réutilisera le projet présenté dans l’article qui précède qui pour info ressemble à ceci:

2016-03-13_23-43-34

2016-03-13_23-45-10

Où sont les branches?

On a toujours au moins une branche master:

git branch

2016-03-13_20-33-44

Où sont enregistrées les informations relatives aux branches dans Git? Dans le répertoire .git bien entendu et plus précisément dans refs:

2016-03-13_20-35-46

 

C’est quoi une branche pour de vrai?

Le fichier master est associé à la branche master, il contient une seule ligne:

d7a45a76ea8ca30d711287e53c442599dddcb204

Vous reconnaissez peut être le hash du dernier commit réalisé dans la branche:

2016-03-13_23-45-10

Essayons de créer une nouvelle branche pour voir:

git branch feature1

tree refs /f

2016-03-13_20-45-54

Comme on pouvait le soupçonner un fichier feature1 est créé à côté de master. Son contenu est:

d7a45a76ea8ca30d711287e53c442599dddcb204

Vous reconnaissez je pense le dernier commit réalisé.

 

Faisons une modification dans readme.txt suivit d’un commit. On récupère l’identifiant:

2016-03-14_00-08-54

Et on vérifie que l’on a le même dans master tandis que feature1 reste inchangé.

 

On a tout ce qu’il faut pour conclure:

Une branche est un simple pointeur vers un commit.

 

Je vous laisse imaginer les possibilités de hack que cette remarque ouvre, d’autant plus que c’est simple, master ou feature1 sont des fichiers comme les autres. On peut les supprimer, les renommer, les éditer.

Mais bon, c’est des hacks…

La branche courante

git branch

2016-03-13_20-51-10

On retrouve nos deux branches, mais avec un visuel qui indique que master est la branche courante.

Comment fait Git pour retrouver cette info?

Vous avez peut être remarqué la présence d’un fichier appelé HEAD. Son contenu est le suivant:

ref: refs/heads/master

Changeons de branche:

git checkout feature1

git branch

2016-03-13_22-09-56

Et ré-éditons le fichier HEAD:

ref: refs/heads/feature1

 

Jusqu’ici on peut conclure que la branche HEAD est la branche qui pointe sur la branche courante.

Au fur et à mesure des commits, nous avons vu que la branche courante se déplace de commit en commit, par contre la branche HEAD ne bouge pas, elle pointe toujours sur la branche courante.

En fait il est tout à fait possible de pointer HEAD non pas sur une branche, mais sur un commit quelconque pour entrer dans le mode detached HEAD. Il est donc plus correct d’écrire:

HEAD désigne le commit sur lequel est positionné le pointeur de l’état courant du repository Git.

 

Il s’agit en général d’une branche, mais pas toujours.

Comment fonctionne les fusions?

On a deux cas de figures à considérer:

  • fusion avec conflit
  • fusion sans conflit

 

Le comportement de Git sera sensiblement différent.

Fusion sur conflit

On va travailler sur le fichier readme.txt avec les contenus suivants (ne pas oublier de faire commit) qui vont déclencher un conflit lors du merge.

  • Branche master:

Fleur
Fraise

  • Branche feature1:

Pierre
Papier

 

Vérifiez que vous êtes sur la branche master:

git checkout master

La situation ressemble donc à celle-ci. En bleu les différents commits. Les flèches pointent vers le commit parent.

2016-03-14_13-29-50

Puis on lance merge:

git merge feature1

Comme prévu un conflit se produit:

2016-03-14_00-33-58

Si on regarde le statut:

2016-03-14_00-37-22

Un conflit a été détecté et aucun commit ne pourra être fait avant qu’il ne soit résolu.

 

Editons le fichier:

<<<<<<< HEAD
Pierre
Papier
=======
Fleur
Fraise
>>>>>>> feature1

 

Disons que nous résolvons le conflit en ne conservant que ceci dans le fichier readme.txt:

Papier
Poire
Fleur
Fraise

Nous pouvons maintenant continuer.

Tout d’abord add:

git add readme.txt

git status

2016-03-14_13-39-27

Un blob a été créé (b2ca) avec le contenu résultant de la fusion. La situation est maintenant la suivante:

2016-03-14_13-43-03

On fait commit:

git commit -m « Commit après merge »

git status

2016-03-14_13-41-56

Cette fois il a été possible de créer un nouveau commit (6e88).

git log

 

2016-03-14_13-49-44

Il est intéressant de regarder le contenu du commit 6e88:

git cat-file -p 6e88

2016-03-14_15-21-22

Ce commit ressemble à tous ceux que nous avons vu, à une seule différence: il a deux parents.

Ce que l’on représente graphiquement par:

2016-03-14_13-45-22

Dans le même temps, Git redéfinit la branche master qui est un pointeur vers 6e88. Le HEAD continue de pointer sur master qui est la branche courante:

2016-03-14_13-46-27

Le point à retenir:

un merge est juste un commit avec deux parents

Voyons tout de suite une exception.

Merge sans conflit

On voudrait maintenant synchroniser master sur feature1. Déplaçons le HEAD sur feature1:

git checkout feature1

2016-03-14_15-46-19

git merge master

Cette fois il n’y a pas de conflits puisque l’on a déjà fait la résolution. readme.txt passe donc de :

Fleur
Fraise

A:

Papier
Poire
Fleur
Fraise

La sortie de commande est celui-ci:

2016-03-14_15-51-53

Merge réussi, mais on voit aussi apparaître fast-forward. C’est quoi?

Pour éclaircir le mystère essayons de retrouver le dernier commit soit avec git log, soit en éditant le fichier feature1. dans tous les cas on trouve:

6e88e0328739c6060e89731db81119f12a2ae275

Ce commit est la branche master. On n’est pas sur feature1?

 

C’est exactement cela. Git a remarqué qu’il est inutile de créer un nouveau commit puisqu’il existe déjà, c’est celui de master. Il procède donc à une optimisation simplement en déplaçant la branche feature1 sur le commit master. Ce n’est pas un problème puisqu’une branche n’est rien d’autre qu’une pointeur sur un commit.

Graphiquement la situation est celle-ci:

2016-03-14_16-01-55

Et on peut compléter la démonstration avec:

git branch

2016-03-14_16-04-52

Ce mécanisme d’optimisation est appelé fast-forward. Il permet de faire merge… sans fusionner des fichiers.

Il aurait été envisageable qu’avant que l’on fasse merge on commit une modification sur la branche master. Dans ce cas la situation finale serait:

2016-03-14_16-07-44

Et observe un merge avec un seul parent… si tant est que l’on puisse parler de merge!

Conclusions

Comme on vient de le voir, tout dans Git est un commit.

Une branche est un pointeur vers un commit, une sorte de tag. La différence avec un tag est que ce dernier ne change pas de cible et reste toujours attaché au même commit.

Un merge c’est le résultat du commit de la fusion de deux fichiers.

 

Bibliographie

http://alblue.bandlem.com/2011/08/git-tip-of-week-detached-heads.html

 

Publicités

Laisser un commentaire

Entrez vos coordonnées ci-dessous ou cliquez sur une icône pour vous connecter:

Logo WordPress.com

Vous commentez à l'aide de votre compte WordPress.com. Déconnexion / Changer )

Image Twitter

Vous commentez à l'aide de votre compte Twitter. Déconnexion / Changer )

Photo Facebook

Vous commentez à l'aide de votre compte Facebook. Déconnexion / Changer )

Photo Google+

Vous commentez à l'aide de votre compte Google+. Déconnexion / Changer )

Connexion à %s