Retour au cours

infra / git-github

Merge

Leçon 51 exercice

Explication

Ce que vous allez apprendre

  • Distinguer un merge fast-forward d'un merge à trois points (avec commit de merge)
  • Choisir entre --no-ff (trace explicite) et --squash (historique compacté)
  • Fusionner une branche dans une autre avec git merge depuis la branche cible
  • Annuler un merge proprement, avant ou après qu'il soit validé
  • Comprendre pourquoi git revert est plus sûr que reset sur un historique partagé

Dans quel contexte ?

Sur le dépôt boutique-api, la branche feature/user-auth est enfin prête à rejoindre main. Pendant son développement, deux autres correctifs sont déjà passés sur main. Le merge doit donc combiner ces deux lignes d'historique qui ont évolué en parallèle, ce qui crée un commit de merge visible — contrairement à une fusion simple où main n'aurait pas bougé entre-temps.

Le rôle d'une branche : finir par rejoindre le reste

D'abord, une branche n'a de sens que si elle finit par rejoindre le reste du projet. Le merge est l'opération qui intègre les commits d'une branche dans une autre.

Une étape moins effrayante qu'on ne le croit

C'est une étape que l'on redoute parfois à tort : dans la grande majorité des cas, elle se passe sans aucune intervention manuelle, Git étant capable de combiner automatiquement des changements qui ne se recoupent pas.

Premier scénario : le fast-forward

Il faut bien distinguer deux cas. Si la branche cible, souvent main, n'a reçu aucun nouveau commit depuis la création de ta branche, Git peut simplement avancer son pointeur jusqu'au dernier commit de ta branche.

Ce que ça donne concrètement

C'est un "fast-forward" : aucun commit supplémentaire n'est créé, l'historique reste parfaitement linéaire.

Deuxième scénario : les deux branches ont évolué

Si en revanche la cible a évolué en parallèle, ce qui est la norme en équipe, Git doit créer un commit spécial à deux parents, le "commit de merge", qui matérialise la réunion des deux lignes d'historique.

SituationType de mergeCommit de merge créé ?
main n'a pas bougé depuis la création de la brancheFast-forwardNon
main a reçu d'autres commits en parallèleMerge à 3 pointsOui

Pourquoi comprendre cette différence est utile

Comprendre cette différence explique pourquoi certains merges "ne laissent pas de trace" alors que d'autres ajoutent un commit visible.

Choisir la forme de son historique

L'option --no-ff force la création d'un commit de merge même quand un fast-forward serait possible : certaines équipes préfèrent cela car cela garde une trace explicite de "quand telle fonctionnalité a été intégrée".

Prérequis

Cette leçon suppose que tu es à l'aise avec la création et la navigation entre branches (git switch, git branch), vue à la leçon précédente.

Une autre option pour un historique plus propre

À l'inverse, --squash compresse toute une branche, même longue et bricolée, en un seul commit propre sur la cible : idéal quand seul le résultat final compte.

Revenir en arrière proprement

Enfin, contrairement à un simple retour arrière qui réécrirait l'historique déjà partagé, git revert crée un nouveau commit qui annule les effets d'un merge précédent, sans effacer ce qui s'est passé.

Bonne pratique

Sur une branche déjà partagée et poussée sur GitHub, préfère toujours git revert à un git reset --hard suivi d'un push forcé. revert ajoute un commit d'annulation visible par tous, sans jamais réécrire l'historique que tes collègues ont peut-être déjà récupéré.

La leçon suivante propose une autre façon d'intégrer des changements, avec une philosophie très différente : le rebase.

Commandes & code

Merge

bash
# Fusionner "feature/user-auth" DANS "main" : se placer sur la branche cible d'abord
git switch main
git merge feature/user-auth
bash
# Fast-forward : main n'a reçu aucun commit depuis la création de la branche
# -> Git avance juste le pointeur de "main", pas de commit de merge
main:            A---B
feature:              \---C---D
après merge:     A---B---C---D   (main pointe maintenant sur D)
bash
# Merge à 3 points : main a évolué en parallèle -> un commit de merge est créé
main:            A---B-------E
feature:              \---C---D
après merge:     A---B---C---D---E---M   (M = merge commit, 2 parents)
bash
# Forcer un merge commit même en cas de fast-forward possible (garde une trace explicite)
git merge --no-ff feature/user-auth -m "Merge feature/user-auth into main"

# Annuler un merge en cours (avant de résoudre les conflits)
git merge --abort

# Annuler un merge déjà commité (crée un nouveau commit inverse, sûr sur une branche partagée)
git revert -m 1 <sha-du-merge-commit>
bash
# Squash merge : intègre tous les commits de la branche comme UN SEUL commit sur main
git merge --squash feature/user-auth
git commit -m "Add user authentication (squash)"
# -> feature/user-auth n'apparaît plus comme parent dans l'historique de main
StratégieHistorique résultantCas d'usage
Fast-forwardLinéaire, aucun commit de mergePetites branches, pas d'évolution parallèle
--no-ffCommit de merge expliciteGarder une trace claire des features intégrées
--squashUn seul commit propreNettoyer l'historique d'une branche "brouillon"

Résumé

  • Fast-forward : pas de commit de merge, juste un déplacement de pointeur.
  • --no-ff garde une trace explicite de chaque fusion dans l'historique.
  • --squash compacte toute une branche en un seul commit propre sur la cible.
  • merge --abort annule un merge en cours de résolution de conflits.

Exercices pratiques

1 disponible
1

Mission : décoder un historique de merge suspect

Objectif : Distinguer un fast-forward d'un merge à trois points à partir d'un graphe git log, et choisir la bonne stratégie d'annulation.

Contexte

Sur boutique-api, git log --oneline --graph --all affiche un commit avec deux parents juste après l'intégration de feature/user-auth dans main, alors qu'un merge plus ancien de fix/typo n'a laissé aucune trace de ce type dans l'historique.

Résoudre l’exercice →