Skip to article frontmatterSkip to article content

on contextualise

on a vu tout à l’heure un exemple de fusion (merge) qui s’est passé sans douleur ni surprise - il faut dire qu’on avait tout bien choisi pour :)

mais toutes les fusions ne se comportent pas de la même manière, et dans ce notebook très court on va essayer de couvrir un peu tous les cas de figure :

rappels

on rappelle que la forme générale de la fusion c’est

git merge <to-merge>

et que l’effet escompté, c’est qu’on se retrouve à la fin sur
un commit qui contienne à la fois le code courant et celui du commit to-merge

(que comme d’habitude on peut exprimer comme un nom de branche, un SHA1, le résultat d’une navigation avec les opérateurs ~ et ^, etc…)

le fast-forward

une fusion fast-forward, c’est le cas où le commit courant est déjà un parent (transitivement) de to-merge; comme par exemple ici:

et du coup dans ce cas de figure, la fusion n’a même pas besoin de créer un commit
il suffit de faire avancer la branche courante (d’où le terme de fast-forward)

(bien sûr ça signifie aussi de modifier l’index et les fichiers;
il faut toujours lancer la fusion sur un dépôt propre)

comment savoir si on est dans le cas du fast-forward ?

la règle qui permet de savoir si on est, ou pas, dans le cas d’un fast-forward, est simple :
si (et seulement si) il existe une chaine de parenté entre les deux commits (celui ou on est, et celui qu’on veut fusionner),
alors on est dans un fast-forward, il n’est pas besoin de créer un commit, il en existe déjà un qui matérialise la fusion

ici dans le premier exemple on avait to-merge=C → B → HEAD=A, donc pas besoin de créer un commit, simplement besoin d’avancer la branche courante

une “vraie fusion”

dans tous les autres cas - c’est-à-dire quand il n’existe pas de lien de parenté entre les deux commits - on ne peut pas trouver un commit existant qui convienne, on doit donc créer un nouveau commit

pour réaliser ça, git a recours à des outils tiers (diff et diff3) qui reposent sur le fait que les contenus sont textuels

Mais reprenons : la fusion fonctionne plutôt très bien avec le texte, tant que les deux modifications impactent des endroits différents dans les fichiers;

mais à l’impossible nul n’est tenu ! et si les deux branches touchent le même endroit du code, on atteint les limites de l’outil : la fusion échoue avec un conflit

mon premier conflit

allons-y, on va provoquer cette situation en pratique :

préparation

mkdir my-first-conflict
cd my-first-conflict
git init
prof #1 Nom  :
prof #1 Note :
---
prof #2 Nom  :
prof #2 Note :
---
total        :
git add form.txt
git commit -m"le formulaire vierge"

les deux branches

ce qu’on veut faire, c’est simuler deux changements faits en même temps par deux personnes différentes; disons qu’on a deux profs, Minerva McGonagall et Albus Dumbledore, qui remplissent chacun leur partie

pour simplifier on va dire que

Ils partent donc tous les deux du formulaire vide, ils remplissent chacun leur partie au mieux, et leurs versions respectives du fichier sont

cat form-mcgonagall.txt
prof #1 Nom  : McGonagall
prof #1 Note : 12
---
prof #2 Nom  :
prof #2 Note :
---
total        : 12

cat form-dumbledore.txt
prof #1 Nom  :
prof #1 Note :
---
prof #2 Nom  : Dumbledore
prof #2 Note : 15
---
total        : 15

sauriez-vous simuler ce scénario ?

pour McGonagall, c’est simple, comme on est sur main il suffit de modifier form.txt (1ère version, note 12) et de commiter

cp form-mcdonagall.txt form.txt
git add form.txt
git commit -m"notes mcgonagall"

pour la deuxième version, on a besoin de créer la branche dumbledore et de revenir en arrière; on peut faire en un seul coup avec le raccourci git switch -c

git switch -c dumbledore HEAD~

à ce stade, le formulaire est à nouveau vide (car on est revenu en arrière)

on remplit alors la fiche dans la version dumbledore, et on commite exactement comme au dessus;

cp form-dumbledore.txt form.txt
git add form.txt
git commit -m"notes dumbledore"

et à ce stade le repo ressemble à ceci

$ git log --all --graph --oneline

* 34fa617 (HEAD -> dumbledore) notes dumbledore
| * 65b135a (main) notes mcgonagall
|/

* 6f201cc le formulaire vierge

le merge

à ce stade on a le choix :

  1. de se mettre sur main et de fusionner dumbledore
  2. ou l’inverse, rester sur dumbledore, et fusionner main

c’est relativement équivalent, mais en général on préfère se mettre sur main, et c’est ce qu’on va faire ici

$ git switch main
Switched to branch 'main'

on peut maintenant essayer de fusionner

$ git merge dumbledore
Auto-merging form.txt
CONFLICT (content): Merge conflict in form.txt
Automatic merge failed; fix conflicts and then commit the result.

ouh là, il n’a pas l’air content !

ce qui se passe, c’est ceci

voilà, on a créé un conflit, et du coup tout s’arrête

conflit: état du repo

dans quel état est notre dépôt à ce stade ?

nettoyer - option 1: revenir en arriére

pour cela, on a principalement deux choix :

  1. soit on décide que tout bien réfléchi, c’est beaucoup trop compliqué, on ne va pas s’en sortir, on veut juste tout défaire et revenir à l’état avant le merge, et pour ça on fait juste
$ git merge --abort
$ git status
On branch main
nothing to commit, working tree clean

ouf, on a tout effacé, on est exactement comme avant le merge

nettoyer - option 2: résoudre le conflit

  1. soit on décide de gérer, c’est-à-dire de passer sur les conflits (nous on n’en a qu’un) et de décider quoi faire;

faisons-le; nous ici on veut dire que le total, ça n’est ni 12 ni 15, mais 27

pour résoudre le conflit, on doit utiliser un éditeur de code; dans vs-code par exemple on verrait ceci

la bonne nouvelle, c’est qu’il a détecté la présence d’un conflit; il nous propose même des options toutes faites (Accept current change…) pour choisir l’une ou l’autre des deux versions

la mauvaise nouvelle, c’est qu’aucune des deux ne convient, et ce qu’on va faire simplement c’est d’écrire la ligne comme elle doit être, ça donne ceci:

à ce stade :

résumé

à retenir à propos de ce notebook :