Chapitre 614 minDébutant
Résoudre un conflit Git
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- expliquer pourquoi un conflit arrive ;
- lire les marqueurs de conflit et
git statuspendant une fusion ; - résoudre un conflit pas à pas, dans le terminal ou dans VS Code ;
- abandonner une fusion et limiter les conflits dans une équipe.
4 exercices corrigés
Un conflit Git survient quand deux branches modifient les mêmes lignes d'un même fichier, ou quand l'une modifie un fichier que l'autre supprime : Git ne peut pas choisir à ta place, il s'arrête et te demande de trancher. Résoudre un conflit, c'est éditer les fichiers marqués, garder le bon code, retirer les marqueurs, puis git add et terminer la fusion. Ce chapitre décode les marqueurs <<<<<<<, ======= et >>>>>>>, montre la résolution dans le terminal et dans VS Code, et explique comment éviter la plupart des conflits.
Pourquoi un conflit arrive
Git fusionne tout seul la grande majorité des modifications. Si Lina modifie le haut de config.js et Hugo le bas, Git combine les deux sans rien demander. Il ne s'arrête que dans quelques cas :
| Type de conflit | Ce que dit Git | Situation |
|---|---|---|
| Contenu | CONFLICT (content) | Les deux branches ont modifié les mêmes lignes, ou des lignes voisines |
| Modification et suppression | CONFLICT (modify/delete) | Une branche a modifié un fichier que l'autre a supprimé |
| Double ajout | CONFLICT (add/add) | Les deux branches ont créé un fichier de même nom avec des contenus différents |
| Renommage | CONFLICT (rename/rename) | Les deux branches ont renommé le même fichier différemment |
Un conflit n'a rien de grave : rien n'est perdu, rien n'est cassé. Les deux versions sont sous tes yeux, et Git attend ta décision.
Le scénario
Sur main, le plafond de remboursement d'un repas est fixé à 20 € dans config.js. Lina crée feature/plafond et le passe à 25 €, comme le demande la nouvelle politique de l'entreprise. Pendant ce temps, Hugo corrige une faute sur main et, sans le savoir, change la même ligne pour mettre 22 €, un ancien chiffre trouvé dans un courriel. Lina fusionne :
git switch main
git merge feature/plafond
Auto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.
Git a fusionné tout ce qu'il pouvait et s'est arrêté sur config.js. La fusion est en cours : tant qu'elle n'est pas terminée ou abandonnée, ne fais rien d'autre.
git status pendant un conflit
git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: config.js
no changes added to commit (use "git add" and/or "git commit -a")
git status liste les fichiers en conflit (both modified) et rappelle les deux issues possibles : résoudre puis commiter, ou abandonner avec git merge --abort. Les fichiers fusionnés sans problème sont déjà préparés.
Les marqueurs de conflit
Ouvre config.js :
module.exports = {
devise: 'EUR',
<<<<<<< HEAD
plafondRepas: 22,
=======
plafondRepas: 25,
>>>>>>> feature/plafond
plafondNuitee: 90,
};
| Marqueur | Signification |
|---|---|
<<<<<<< HEAD | Début de la version de la branche courante, celle sur laquelle tu fusionnes (ici main) |
======= | Séparation entre les deux versions |
>>>>>>> feature/plafond | Fin de la version de la branche que tu fais entrer |
Les lignes en dehors des marqueurs ont été fusionnées sans problème. Seule la zone entre <<<<<<< et >>>>>>> demande une décision.
Résoudre le conflit pas à pas
- Comprendre les deux intentions. Pourquoi
maindit 22 et la branche 25 ? Lis les messages des commits :git log --merge -p config.jsaffiche les commits des deux côtés qui ont touché le fichier. Si tu ne sais pas qui a raison, demande : ici, Lina vérifie la note de service, qui fixe 25 €. - Éditer le fichier. Garde la bonne version, ou combine les deux si les deux modifications sont utiles, et supprime les trois marqueurs :
module.exports = {
devise: 'EUR',
plafondRepas: 25,
plafondNuitee: 90,
};
- Vérifier. Cherche les marqueurs oubliés :
git diff --checksignale ceux qui restent. Lance l'application ou les tests : un fichier sans marqueur peut encore être faux. - Marquer le fichier comme résolu en le préparant :
git add config.js
- Terminer la fusion :
git commit
Git propose le message Merge branch 'feature/plafond'. Complète-le si la décision mérite une explication : « Plafond repas à 25 € conformément à la note de service de janvier ». Tu peux aussi utiliser git merge --continue, qui fait la même chose.
Garder une version entière
Si tu sais d'avance qu'une des deux versions est la bonne pour tout un fichier, pas besoin d'éditer à la main :
git checkout --ours config.js # garder la version de la branche courante (HEAD)
git checkout --theirs config.js # garder la version de la branche fusionnée
git add config.js
Attention au sens : pendant un merge, ours est la branche sur laquelle tu es. Pendant un rebase, c'est l'inverse, car Git rejoue tes commits sur l'autre branche : ours désigne la branche de base, theirs tes propres commits. C'est une source d'erreur fréquente : dans le doute, ouvre le fichier.
Pour un conflit modify/delete, décide si le fichier doit exister : git add fichier pour le garder, git rm fichier pour confirmer la suppression.
Voir aussi la version d'origine
Par défaut, le conflit montre tes deux versions mais pas celle d'avant, celle de l'ancêtre commun. Or c'est souvent elle qui permet de comprendre ce que chacun a voulu faire. Active le style zdiff3 :
git config --global merge.conflictStyle zdiff3
Le conflit affiche alors une troisième section, après |||||||, avec la version de l'ancêtre commun :
<<<<<<< HEAD
plafondRepas: 22,
||||||| 1b6c5d8
plafondRepas: 20,
=======
plafondRepas: 25,
>>>>>>> feature/plafond
On lit tout de suite que les deux côtés sont partis de 20 et ont chacun choisi une nouvelle valeur.
Résoudre dans VS Code
VS Code détecte les marqueurs et affiche au-dessus de chaque conflit quatre actions :
| Action | Effet |
|---|---|
| Accept Current Change | Garde la version de la branche courante (HEAD) |
| Accept Incoming Change | Garde la version de la branche fusionnée |
| Accept Both Changes | Garde les deux, l'une après l'autre |
| Compare Changes | Affiche les deux versions côte à côte |
Le bouton Resolve in Merge Editor ouvre un éditeur en trois volets : les deux versions en haut, le résultat en bas. Ces boutons retirent les marqueurs pour toi, mais ils ne comprennent pas ton code : Accept Both Changes sur deux définitions de la même variable donne un fichier qui ne compile pas. Relis toujours le résultat, puis prépare le fichier et termine la fusion dans le terminal ou avec le bouton Commit de l'onglet Contrôle de code source.
Abandonner une fusion
Le conflit est plus gros que prévu, ou tu as fusionné la mauvaise branche ? Tant que tu n'as pas commité la fusion :
git merge --abort
Le dépôt revient exactement à l'état d'avant le git merge. Tu peux relancer plus tard, après en avoir parlé à ton coéquipier. Les commandes équivalentes existent pour les autres opérations qui peuvent s'arrêter sur un conflit : git rebase --abort, git cherry-pick --abort, git revert --abort.
Les conflits ailleurs qu'au merge
| Commande | Conflit possible | Pour continuer après résolution | Pour abandonner |
|---|---|---|---|
git merge | Oui | git add puis git commit (ou git merge --continue) | git merge --abort |
git pull | Oui, c'est une fusion ou un rebase | Selon le mode : comme merge ou comme rebase | git merge --abort ou git rebase --abort |
git rebase | Oui, commit par commit | git add puis git rebase --continue | git rebase --abort |
git cherry-pick | Oui | git add puis git cherry-pick --continue | git cherry-pick --abort |
git stash pop | Oui | git add, puis git stash drop (l'entrée reste dans la pile) | git reset --merge |
git revert | Oui | git add puis git revert --continue | git revert --abort |
Le principe est toujours le même : éditer, git add, continuer. git status te rappelle à chaque fois l'opération en cours et les commandes possibles.
Moins de conflits
On n'élimine pas les conflits, mais on les rend petits et rares :
- Des branches courtes. Une branche qui vit deux jours accumule peu de différences ; une branche de trois semaines devient un cauchemar à fusionner.
- Mettre sa branche à jour souvent avec
main(pargit merge mainou un rebase) : tu règles les petits conflits au fil de l'eau, pas tous à la fin. - Se répartir le travail : deux personnes qui réécrivent le même fichier le même jour vont au conflit. Les tickets et le tableau de projet servent aussi à ça.
- Des commits ciblés : reformater tout un fichier (indentation, guillemets) dans le même commit qu'une correction crée des conflits sur chaque ligne. Fais le reformatage dans un commit à part, et mets-toi d'accord sur un formateur automatique (Prettier, PHP CS Fixer) avec un fichier de configuration versionné.
- Les fins de ligne : Windows termine les lignes par
CRLF, Linux et macOS parLF. Un fichier.gitattributesavec la ligne* text=autolaisse Git normaliser les fins de ligne et évite les conflits sur des fichiers « entièrement modifiés » sans raison visible.
Exercices
Exercice 1 sur 4 Lire un conflit Réussi À revoir
Pendant la fusion de feature/libelles dans main, Git affiche ce conflit dans messages.js :
<<<<<<< HEAD
const MSG_REFUS = "Note refusée : justificatif manquant";
=======
const MSG_REFUS = "Note de frais refusée";
const MSG_VALIDE = "Note de frais validée";
>>>>>>> feature/libelles
- Quelle version vient de
main? Laquelle defeature/libelles? - L'équipe veut le libellé de refus détaillé de
mainet le nouveau libellé de validation de la branche. Écris le contenu final. - Quelles commandes terminent la fusion ?
Voir la réponseMasquer la réponse de l'exercice 1
-
Entre
<<<<<<< HEADet=======, la version demain, la branche courante. Entre=======et>>>>>>> feature/libelles, celle de la branche fusionnée. -
Sans aucun marqueur :
const MSG_REFUS = "Note refusée : justificatif manquant"; const MSG_VALIDE = "Note de frais validée"; -
git add messages.jspuisgit commit(ougit merge --continue).
Tu avais trouvé ?
Exercice 2 sur 4 Remettre dans l'ordre Réussi À revoir
Remets ces actions dans l'ordre pour résoudre un conflit sur routes/notes.js pendant une fusion :
- a.
git commit - b. ouvrir
routes/notes.jset choisir le bon code - c.
git status - d.
git add routes/notes.js - e. supprimer les marqueurs restants et lancer les tests
- f.
git merge feature/pagination
Voir la réponseMasquer la réponse de l'exercice 2
f, c, b, e, d, a. On lance la fusion (f), on repère les fichiers en conflit (c), on édite (b), on nettoie et on vérifie (e), on marque le fichier comme résolu (d), on termine la fusion (a).
Tu avais trouvé ?
Exercice 3 sur 4 Ours ou theirs ? Réussi À revoir
- Tu es sur
mainet tu fusionnesfeature/theme. Le fichiercouleurs.cssest en conflit et tu veux garder entièrement la version defeature/theme. Quelle commande lances-tu avantgit add? - Tu es sur
feature/themeet tu lancesgit rebase main. Le même fichier est en conflit, et tu veux encore garder ta version, celle defeature/theme. Quelle commande ?
Voir la réponseMasquer la réponse de l'exercice 3
git checkout --theirs couleurs.css: pendant unmerge, theirs est la branche fusionnée,feature/theme.git checkout --theirs couleurs.cssaussi : pendant unrebase, Git rejoue tes commits defeature/themesurmain, donc ours désignemain(la base) et theirs les commits rejoués, les tiens. Les deux réponses sont identiques alors que tu es sur une branche différente : c'est tout le piège.
Tu avais trouvé ?
Exercice 4 sur 4 Diagnostic Réussi À revoir
Après un git pull, Hugo voit dans son navigateur une page blanche et, dans la console, SyntaxError: Unexpected token '<<' dans App.jsx. git status affiche nothing to commit, working tree clean. Que s'est-il passé, et comment réparer ?
Voir la réponseMasquer la réponse de l'exercice 4
Un coéquipier (ou Hugo lui-même) a commité un fichier qui contenait encore des marqueurs de conflit : le commit est propre pour Git, mais le code est invalide. Pour réparer : ouvrir App.jsx, chercher <<<<<<<, ======= et >>>>>>>, garder le bon code, vérifier que l'application démarre, puis commiter la correction (« Retire des marqueurs de conflit oubliés dans App.jsx ») et pousser. Pour retrouver le commit fautif : git log -S "<<<<<<<" --oneline.
Tu avais trouvé ?