Aller au contenu

Git avancé : fusion et conflits

Fenêtre de terminal
git switch main
git merge fonctionnalite
Situation Résultat
main n’a pas avancé depuis la création de la branche Fast-forward : main avance simplement, aucun commit de fusion
Les deux branches ont avancé Commit de fusion (deux parents)
Fast-forward : Fusion à trois voies :
A---B main A---B---C main A---B---C---M main
\ \ \ /
D---E fonctionnalite D---E fonctionnalite D-----E
Fenêtre de terminal
git merge --ff-only fonctionnalite # refuse si un commit de fusion serait nécessaire
git merge --no-ff fonctionnalite # force un commit de fusion (garde la trace de la branche)
git merge --squash fonctionnalite # applique tout en UN seul commit (à valider vous-même)
git merge --no-commit fonctionnalite # fusionne mais n'enregistre pas (pour vérifier)
git merge --abort # annule une fusion en cours
Option Quand
--ff-only Garder un historique linéaire ; échoue si un rebase est nécessaire
--no-ff Conserver la notion de branche (une fonctionnalité = un commit de fusion)
--squash Réduire une branche de travail à un commit propre sur main

Réglage par défaut : git config --global merge.ff only (ou pull.ff only) pour éviter les fusions accidentelles lors d’un git pull.

Stratégie Historique de main Pour qui
Fusion --no-ff Commits de fusion, branches visibles Branches de fonctionnalité nombreuses, historique fidèle
Squash Un commit par fonctionnalité Commits intermédiaires peu soignés
Rebase + fast-forward Linéaire, tous les commits Commits atomiques et propres (Rebase et historique)

Les plateformes (Gitea, GitHub, GitLab) proposent ces trois modes à la fusion d’une demande : choisir un mode pour le projet et le documenter (voir Gitea : dépôts, accès et API).

Un conflit survient quand deux branches modifient les mêmes lignes (ou qu’une branche supprime ce que l’autre modifie). Git ne sait pas arbitrer et s’arrête :

Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.

Dans le fichier :

const port = 3000;
<<<<<<< HEAD
const hote = '127.0.0.1';
=======
const hote = '0.0.0.0';
>>>>>>> fonctionnalite
Marqueur Contenu
<<<<<<< HEAD à ======= Version de la branche courante (main)
======= à >>>>>>> Version de la branche fusionnée
  1. Ouvrir le fichier, choisir (ou combiner) le contenu voulu et supprimer les marqueurs.
  2. git add config.js
  3. git commit (le message de fusion est pré-rempli) — ou git rebase --continue pendant un rebase.
Fenêtre de terminal
git status # liste « both modified »
git diff --name-only --diff-filter=U # fichiers non résolus
git checkout --ours config.js # garder la version de la branche courante (pendant un merge)
git checkout --theirs config.js # garder la version de l'autre branche
git add config.js
Fenêtre de terminal
git config --global merge.conflictStyle zdiff3 # (ou diff3 sur une version plus ancienne)
<<<<<<< HEAD
const hote = '127.0.0.1';
||||||| ancêtre commun
const hote = 'localhost';
=======
const hote = '0.0.0.0';
>>>>>>> fonctionnalite

Le bloc ||||||| montre la version de l’ancêtre commun : on voit ce que chaque côté a changé, ce qui rend l’arbitrage bien plus facile. zdiff3 réduit en plus les lignes communes en début et fin de bloc. Recommandé.

Fenêtre de terminal
git mergetool # lance l'outil configuré pour chaque fichier en conflit
git config --global merge.tool vimdiff # ou meld, kdiff3, code (VS Code)
git config --global mergetool.keepBackup false
Outil Remarque
vimdiff Dans le terminal, déjà présent avec Vim
meld Graphique (sudo apt install meld)
VS Code Éditeur de fusion à trois colonnes intégré
git diff --diff-filter=U Lire les conflits en ligne de commande
Pratique Effet
Branches courtes Moins de divergence
Rebaser régulièrement sur main Les conflits apparaissent tôt, par petits morceaux
Commits petits et ciblés Conflits plus lisibles
Ne pas reformater un fichier entier dans un commit mêlé à d’autres changements Évite des conflits sur tout le fichier
Formateur automatique commun (Prettier) Plus de conflits de style
Séparer les fichiers à fort trafic Moins de modifications concurrentes
Se coordonner sur les fichiers partagés (verrouillage de dépendances) Voir plus bas

Un conflit dans package-lock.json ou pnpm-lock.yaml ne se résout pas à la main : accepter la version de main (ou de l’une des branches), puis régénérer :

Fenêtre de terminal
git checkout --theirs package-lock.json # ou --ours, selon le sens
npm install # recalcule le fichier de verrouillage
git add package-lock.json

Pour qu’un fichier généré ne produise jamais de marqueurs de conflit illisibles, .gitattributes peut indiquer une stratégie de fusion :

package-lock.json merge=ours
CHANGELOG.md merge=union
*.png binary

merge=union conserve les lignes des deux côtés (utile pour un journal de modifications) ; merge=ours exige de définir le pilote : git config merge.ours.driver true. À utiliser avec discernement.

Fenêtre de terminal
git config --global rerere.enabled true

Reuse recorded resolution : Git enregistre la façon dont on a résolu un conflit et la réapplique automatiquement si le même conflit se reproduit (rebase répété, fusion annulée puis refaite). Très utile quand on rebase souvent une longue branche.

Fenêtre de terminal
git rerere status
git rerere diff
git rerere forget fichier # oublier une résolution mauvaise
Situation Commande
Fusion en cours (conflits) git merge --abort
Fusion faite mais non poussée git reset --hard ORIG_HEAD (ou HEAD~1)
Fusion déjà poussée git revert -m 1 <commit-de-fusion>
Fenêtre de terminal
git revert -m 1 a1b2c3d # -m 1 : garder le parent 1 (la branche principale) comme référence
Fenêtre de terminal
git remote add collegue https://git.exemple.com/collegue/projet.git
git fetch collegue
git merge collegue/fonctionnalite
git merge --allow-unrelated-histories collegue/main # historiques sans ancêtre commun (fusion de deux dépôts)
Fenêtre de terminal
git log --oneline --graph --all --decorate -20
git log main..fonctionnalite --oneline # commits de la branche absents de main
git diff main...fonctionnalite # changements de la branche depuis l'ancêtre commun
git merge-base main fonctionnalite # ancêtre commun
git range-diff main@{1} main fonctionnalite # comparer deux versions d'une branche réécrite
git merge --no-commit --no-ff fonctionnalite # simuler : inspecter avant d'enregistrer
git merge --abort

git diff A...B (trois points) compare B à l’ancêtre commun : exactement ce qu’apporterait la fusion.

Symptôme Piste
error: Your local changes … would be overwritten by merge Valider ou mettre de côté (git stash) avant de fusionner
fatal: refusing to merge unrelated histories Dépôts sans ancêtre commun : --allow-unrelated-histories (si c’est voulu)
Not possible to fast-forward, aborting --ff-only : rebaser d’abord ou accepter une fusion
Marqueurs <<<<<<< restés dans le code Résolution oubliée : grep -rn '<<<<<<<' . ; hook de commit pour les refuser
Conflit sur un fichier binaire Choisir une version : git checkout --ours fichier ou --theirs
Le même conflit revient sans cesse Activer rerere, rebaser plus souvent
Fusion qui « perd » des modifications Résolution incorrecte ; git log -p --follow fichier, git diff ORIG_HEAD
git merge reporte « Already up to date » Les commits sont déjà inclus dans la branche courante

Ubuntu 24.04 et 26.04 : différences à connaître

Section intitulée « Ubuntu 24.04 et 26.04 : différences à connaître »

Aucune propre à Ubuntu. zdiff3 demande Git 2.35 ou plus : disponible sur les deux versions d’Ubuntu (git --version).