Aller au contenu
Réussir mon BTS SIO

Tape une notion, une commande ou le nom d'une épreuve.

Par exemple

Chapitre 514 minDébutant

Les branches Git : créer, changer, fusionner

Par Josselyn C., prof de BTS SIO. Mis à jour le .

À la fin de ce chapitre, tu sauras

  • expliquer ce qu'est une branche et pourquoi l'utiliser ;
  • créer une branche, changer de branche et la nommer correctement ;
  • fusionner avec git merge, en avance rapide ou non ;
  • supprimer une branche et sortir d'un HEAD détaché.

4 exercices corrigés2 schémas

Une branche Git est une ligne de travail indépendante : tu y développes une fonctionnalité ou une correction sans toucher à la branche principale, puis tu fusionnes ton travail quand il est prêt. Techniquement, une branche n'est qu'une étiquette qui pointe vers un commit, ce qui la rend instantanée à créer. Ce chapitre montre comment créer une branche, changer de branche, fusionner avec git merge (en avance rapide ou avec un commit de fusion), puis renommer et supprimer des branches.

Pourquoi travailler sur des branches

Sans branche, tout le monde commite sur main. Lina commence la route d'export, Hugo corrige un bug d'affichage, et main contient en permanence du travail à moitié fini : impossible de livrer la correction de Hugo sans livrer aussi l'export inachevé de Lina.

Avec des branches, chaque sujet vit à part :

  • main contient toujours une version qui fonctionne, celle qu'on peut déployer ou montrer à la commission ;
  • chaque fonctionnalité ou correction se développe sur sa propre branche, où l'on peut commiter du travail en cours sans gêner personne ;
  • quand le travail est terminé et vérifié, on le fusionne dans main.

C'est aussi ce qui te permet d'essayer une idée sans risque : si elle ne mène nulle part, tu supprimes la branche, et main n'a jamais rien su.

Une branche est un pointeur

Une branche est un simple fichier qui contient l'empreinte d'un commit : le dernier commit de la branche. Quand tu commites sur une branche, Git crée le commit et avance l'étiquette sur lui. HEAD, lui, pointe vers la branche sur laquelle tu te trouves.

prochain commitABCDEFmainfeature/exportHEADUne branche = une étiquette sur un commit
Une branche est une étiquette posée sur un commit ; HEAD désigne la branche courante

Créer une branche ne copie aucun fichier : Git écrit une étiquette de 41 octets. C'est pour cela qu'on crée des branches sans hésiter, une par sujet.

Créer une branche et changer de branche

bash
git branch                       # liste les branches locales, * devant la branche courante
git switch -c feature/export     # crée la branche à partir de HEAD et y va
text
Switched to a new branch 'feature/export'
CommandeEffet
git branchListe les branches locales
git branch -vLes liste avec leur dernier commit
git branch -aAjoute les branches de suivi des dépôts distants (remotes/origin/…)
git branch feature/exportCrée la branche sans y aller
git switch feature/exportVa sur une branche existante
git switch -c feature/exportCrée la branche et y va
git switch -c hotfix/total mainCrée la branche à partir de main, quelle que soit la branche courante
git switch -Revient à la branche précédente

Quand tu changes de branche, Git remplace les fichiers de ton répertoire de travail par ceux du dernier commit de la branche visée. Tes modifications non commitées te suivent si elles ne gênent pas ; si un fichier modifié diffère entre les deux branches, Git refuse le changement pour ne pas l'écraser. Commite ou range-les avec git stash.

Nommer ses branches

Un nom de branche décrit le sujet, en minuscules, avec des tirets, et commence souvent par un préfixe qui donne la nature du travail :

PréfixeUsageExemple
feature/ ou feat/Nouvelle fonctionnalitéfeature/export-csv
fix/ ou bugfix/Correction de bugfix/total-ligne-vide
hotfix/Correction urgente en productionhotfix/connexion-impossible
docs/Documentationdocs/guide-installation
refactor/Restructuration sans changement de comportementrefactor/service-indemnites

Beaucoup d'équipes ajoutent le numéro du ticket : fix/12-filtre-date. Évite les espaces, les accents et les noms personnels (branche-hugo) : une branche porte un sujet, pas une personne.

Fusionner deux branches avec git merge

La fonctionnalité est terminée sur feature/export. Pour l'intégrer, place-toi sur la branche qui reçoit, puis fusionne la branche qui apporte :

bash
git switch main
git pull                  # main à jour avant de fusionner
git merge feature/export

Le nom qui suit merge est la branche qu'on fait entrer, pas la destination. Selon l'historique, Git fusionne de deux façons.

L'avance rapide (fast-forward). Si main n'a reçu aucun commit depuis la création de feature/export, il n'y a rien à combiner : Git avance simplement l'étiquette main jusqu'au dernier commit de la branche.

text
Updating 0f0c3b3..f7e293d
Fast-forward
 export.js | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 export.js

La fusion à trois points. Si main a avancé de son côté, les deux historiques ont divergé. Git compare trois versions : le dernier commit de chaque branche et leur ancêtre commun. Il combine les deux séries de modifications et crée un commit de fusion, qui a deux parents.

text
Merge made by the 'ort' strategy.
 filtre.js | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 filtre.js

ort est le nom de l'algorithme de fusion par défaut de Git depuis 2021. Git ouvre ton éditeur avec le message Merge branch 'feature/filtre' : tu peux le garder tel quel.

Avance rapide (fast-forward)ABCDmainavantmainaprèsfeature/exportaucun nouveau commit :l'étiquette main avanceFusion à trois points (commit de fusion)ABCDEMmainfeature/filtreM a deux parents :C et E
Avance rapide quand main n'a pas bougé ; commit de fusion quand les deux branches ont avancé

Pour visualiser le résultat :

bash
git log --oneline --graph --all
text
*   4398d2d (HEAD -> main) Merge branch 'feature/filtre'
|\
| * 2eb48ca (feature/filtre) Ajoute le filtre par date
* | b14293e Documente l'installation
|/
* f7e293d (feature/export) Ajoute l'export CSV
* 0f0c3b3 Ajoute le point d'entrée de l'API
* 24b02c7 Initialise le projet

Chaque * est un commit, les traits dessinent les branches. On lit de bas en haut : feature/filtre est partie de f7e293d, main a reçu b14293e entre-temps, et le commit de fusion 4398d2d réunit les deux lignes.

Forcer ou interdire l'avance rapide

Certaines équipes veulent garder la trace de chaque fonctionnalité dans l'historique, même quand l'avance rapide était possible. L'option --no-ff force un commit de fusion :

bash
git merge --no-ff feature/export

À l'inverse, git merge --ff-only feature/export refuse de fusionner si l'avance rapide est impossible : pratique pour vérifier que ta branche est bien à jour. Et git merge --squash feature/export prépare toutes les modifications de la branche en un seul bloc, sans commit de fusion : tu les commites ensuite en un seul commit. C'est ce que fait le bouton Squash and merge de GitHub (chapitre 9).

Si deux branches modifient les mêmes lignes d'un même fichier, Git ne sait pas laquelle garder et s'arrête sur un conflit. Le chapitre 6 le traite en détail.

Supprimer et renommer des branches

Une branche fusionnée ne sert plus. Supprime-la pour garder une liste lisible :

bash
git branch -d feature/export
text
Deleted branch feature/export (was f7e293d).

Les commits restent dans l'historique de main : seule l'étiquette disparaît. -d refuse de supprimer une branche dont le travail n'est fusionné nulle part, pour te protéger :

text
error: The branch 'feature/plafond' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature/plafond'.

-D force la suppression ; les commits de la branche ne sont alors plus accessibles que par le reflog, pendant 30 jours.

CommandeEffet
git branch -d nomSupprime une branche fusionnée
git branch -D nomSupprime une branche, fusionnée ou non
git push origin --delete nomSupprime la branche sur le dépôt distant
git branch -m nouveau-nomRenomme la branche courante
git branch -m ancien nouveauRenomme une autre branche
git branch --merged mainListe les branches déjà fusionnées dans main, donc supprimables

Pour renommer une branche déjà poussée, renomme-la en local, pousse le nouveau nom, puis supprime l'ancien sur le serveur :

bash
git branch -m fix/filtre fix/12-filtre-date
git push -u origin fix/12-filtre-date
git push origin --delete fix/filtre

HEAD détaché

Tu veux voir le projet tel qu'il était il y a dix commits, pour vérifier quand un bug est apparu :

bash
git switch --detach a1b2c3d
text
HEAD is now at a1b2c3d Ajoute le calcul des indemnités

HEAD pointe maintenant directement sur un commit, et non plus sur une branche : c'est l'état HEAD détaché (detached HEAD). Tu peux lire, compiler, tester. Si tu commites dans cet état, tes commits ne sont rattachés à aucune branche : dès que tu changes de branche, ils ne sont plus accessibles que par le reflog. Pour les garder, crée une branche avant de partir : git switch -c essai-ancien-calcul. Pour revenir, git switch main.

Tu tombes aussi en HEAD détaché quand tu fais git switch --detach v1.0 pour tester une version étiquetée, ou au milieu d'un rebase.

Exercices

Exercice 1 sur 4 Avance rapide ou commit de fusion ? Réussi À revoir

Pour chaque situation, dis si git merge fait une avance rapide ou crée un commit de fusion.

  1. Lina crée feature/export depuis main, y fait trois commits, puis revient sur main, où personne n'a commité, et fusionne.
  2. Même départ, mais Hugo a poussé un commit sur main entre-temps, et Lina a fait git pull sur main avant de fusionner.
  3. Même départ qu'en 1, mais Lina lance git merge --no-ff feature/export.
  4. Situation 2, avec git merge --ff-only feature/export.
Voir la réponseMasquer la réponse de l'exercice 1
  1. Avance rapide : main n'a pas bougé, son dernier commit est l'ancêtre du dernier commit de feature/export. L'étiquette avance de trois commits.
  2. Commit de fusion : les deux historiques ont divergé (le commit de Hugo d'un côté, les trois commits de Lina de l'autre).
  3. Commit de fusion, forcé par --no-ff, alors que l'avance rapide était possible.
  4. Aucune fusion : Git refuse avec fatal: Not possible to fast-forward, aborting. Il faudrait d'abord mettre feature/export à jour avec main (par un rebase, chapitre 7).

Tu avais trouvé ?

Exercice 2 sur 4 Lire un graphe Réussi À revoir

Voici la sortie de git log --oneline --graph --all :

text
*   c81d2e0 (HEAD -> main) Merge branch 'fix/arrondi'
|\
| * 77ab310 (fix/arrondi) Arrondit le total au centime
* | 4e0f9a2 (feature/pdf) Ajoute l'export PDF
|/
* 1b6c5d8 Ajoute la liste des notes
* 0a2e4f7 Initialise le projet
  1. Sur quelle branche est-on ?
  2. De quel commit les deux branches sont-elles parties ?
  3. Comment feature/pdf a-t-elle été intégrée à main ?
  4. Combien de parents a le commit c81d2e0, et lesquels ?
  5. Quelles branches peut-on supprimer avec git branch -d ?
Voir la réponseMasquer la réponse de l'exercice 2
  1. Sur main : HEAD -> main.
  2. De 1b6c5d8 (« Ajoute la liste des notes »), là où les deux lignes se séparent.
  3. Par avance rapide : 4e0f9a2 est sur la ligne de main, sans commit de fusion propre à feature/pdf. main était sur 1b6c5d8, la fusion de feature/pdf a avancé l'étiquette sur 4e0f9a2.
  4. Deux : 4e0f9a2 (le dernier commit de main avant la fusion) et 77ab310 (le dernier commit de fix/arrondi).
  5. Les deux : feature/pdf et fix/arrondi sont entièrement contenues dans main.

Tu avais trouvé ?

Exercice 3 sur 4 Enchaîner les commandes Réussi À revoir

Tu es sur main, à jour. Écris les commandes pour :

  1. créer la branche fix/12-filtre-date et y aller ;
  2. commiter le fichier routes/notes.js corrigé ;
  3. revenir sur main et y intégrer la correction ;
  4. supprimer la branche devenue inutile.

Puis réponds : à l'étape 3, quel message affiche git merge si personne n'a touché à main ?

Voir la réponseMasquer la réponse de l'exercice 3
bash
git switch -c fix/12-filtre-date
git add routes/notes.js
git commit -m "Corrige le filtre par date des notes (ticket 12)"
git switch main
git merge fix/12-filtre-date
git branch -d fix/12-filtre-date

main n'a pas bougé : la fusion est une avance rapide, et Git affiche Updating <ancien>..<nouveau> (par exemple Updating 1b6c5d8..9e1c4a7), puis Fast-forward avec le récapitulatif des fichiers modifiés.

Tu avais trouvé ?

Exercice 4 sur 4 Le commit fantôme Réussi À revoir

Hugo a fait git switch --detach 1b6c5d8 pour tester une ancienne version. Il a corrigé une faute dans README.md et commité, puis il est revenu sur main. Son commit n'apparaît plus dans git log.

  1. Pourquoi ?
  2. Comment le retrouver et le rattacher à une branche ?
Voir la réponseMasquer la réponse de l'exercice 4
  1. En HEAD détaché, le commit n'était rattaché à aucune branche. En revenant sur main, plus aucune étiquette ne pointe dessus : git log ne le montre plus. Git l'a d'ailleurs signalé au moment du git switch main (Warning: you are leaving 1 commit behind, not connected to any of your branches).
  2. git reflog affiche l'empreinte du commit (ligne commit: … juste avant le retour sur main). Puis git branch fix/readme <empreinte> crée une branche dessus. Il peut ensuite la fusionner ou en récupérer le commit avec git cherry-pick (chapitre 7).

Tu avais trouvé ?

Chapitre terminé !

Fais les 4 exercices avant de passer à la suite : c'est en t'entraînant que tout se fixe.

chapitres
–
de série
–
du cours
–
Chapitre suivant : Résoudre un conflit GitRevoir le sommaire du cours