Les branches : de simples étiquettes
Par Maxime Bréhin • Publié le 3 juin 2022

Mise à jour le 8 août 2022, 11:37

Tu viens de l’univers SVN ou équivalent et tu as peur de faire des branches ? Ou tu découvres seulement la gestion de versions et tu souhaites apprendre comment gérer tes branches avec Git ? Cet article est fait pour toi !

Je t’explique ici ce que sont les branches, la facilité avec laquelle on peut les manipuler, et pourquoi on peut les créer et les supprimer sans crainte.

Cet article fait partie de notre série sur le glossaire Git.

Tu préfères une vidéo ?

Si tu es du genre à préférer regarder que lire pour apprendre, on a pensé à toi :

Une simple étiquette

On a vu dans l’article sur les objets Git qu’un historique est constitué de commits que se référencent à la chaîne.

Ici, c2 pointe sur son commit parent c1 qui pointe sur son commit parent c0 :

c0 <- c1 <- c2

La notion de branche n’est qu’un complément à cela pour permettre de désigner un emplacement en lui attribuant un nom. On appose alors une étiquette sur le dernier commit (le plus récent) de l'historique censé représenter la branche. En d'autres termes, une branche n’est qu’un pointeur sur un commit.

Ici la branche dev pointe sur le commit c2 et désigne donc implicitement la "chaîne" jusqu’à c0 :

               dev
              /
c0 <- c1 <- c2

Ce qu’on appelle alors la branche est la cascade des références de commits (jusqu'au commit racine, qui n'a plus de commit parent).

Ces étiquettes ont la particularité d’évoluer lorsqu’on produit de nouveaux commits alors qu'elles constituent la branche active, ou lorsqu’on souhaite les déplacer ailleurs (annulation de commits etc.). Elles servent également à indiquer au garbage collector les chaînes de commits à préserver (ça fonctionne aussi avec les tags). Enfin, elles peuvent aussi servir au partage via les dépôts distants.

Dans le diagramme ci-après, le commit c3 a été produit depuis l’état de c2. Il pointe donc sur son parent c2 et l’étiquette de branche dev a évolué pour le référencer :

                     dev
                    /
c0 <- c1 <- c2 <- c3

Performance et tranquillité

Ce fonctionnement par référence est extrêmement pratique à bien des égards :

  • la performance est accrue : on ne manipule que des pointeurs ;
  • on peut garder l’esprit tranquille quand on effectue nos opérations : jamais un commit ou objet Git ne sera supprimé, au pire il sera déréférencé.

La création d’une branche ne représente rien de plus que la création d’un fichier unique sur le disque. La branche est disponible localement et pourra être partagée plus tard (ou pas, si on veut réaliser un travail « privé »).

Le renommage d’une branche signifie uniquement le renommage de son fichier descripteur.

La suppression d’une branche consiste en la suppression de ce fichier, donc du pointeur, mais en aucun cas des commits qu’elle désignait. Là aussi, c’est du coup très rapide. On a en prime la capacité de récupérer un historique potentiellement perdu (déréférencé / invisible dans le log classique).

Pour donner un contre-exemple, lorsqu’on demande à SVN de créer une branche, il va (sur le serveur SVN) dupliquer l'intégralité de l’historique et des fichiers désignés et les affecter à cette branche. On est donc soumis à la latence réseau en plus d’une duplication encombrante. Il en va de même pour la suppression et les autres manipulations.

Cycle de vie d’une branche : les cernes des branches

Comme dans nos articles sur les objets Git et HEAD je trouve intéressant de regarder sous le capot la manière dont sont traitées nos branches pour mieux les comprendre.

(En botanique, les cernes sont les cercles / anneaux de croisance d’un arbre 🌳.)

Initialisation d’un projet

À l’initialisation d’un projet, aucun commit ni branche n’existe. Une branche par défaut sera créée au premier commit. Par convention cette branche s’appelle main (auparavant master, ce renommage ayant eu lieu suite au mouvement Black Lives Matter).

On trouve cette référence de branche active / courante via notre HEAD :

# On regarde le contenu du fichier HEAD avec la commande `cat`
> cat .git/HEAD
ref: refs/heads/main

Les fichiers des étiquettes de branches sont stockés dans le répertoire .git/refs/heads/ et portent le nom des branches. Pour l’instant ce répertoire est vide car je n’ai rien dans le projet.

Si je crée un commit, j’observe l’apparition du fichier .git/refs/heads/main. Et si je regarde son contenu je m’aperçois qu’il ne contient qu’une ligne de 40 caractères : un identifiant de commit (SHA-1).

> cat .git/refs/heads/main
377758e1b6a31850f0307abb1ac5f039da53e17f

Évolution d’une branche

Si je continue et réalise d’autres commits, je constate que ce fichier enregistre la référence du dernier commit produit :

> git commit …
> cat .git/refs/heads/main
9c118f78bc49f1be85085e708e61503a9d787c51

> git commit …
> cat .git/refs/heads/main
dd4d06db3420782a6b63fa5a067d82d42af9ec7d

Créer une autre branche

Puisque les branches ne sont que de simples étiquettes, rien ne m’empêche d'en créer autant que je veux, où je veux. Généralement je crée une branche depuis mon emplacement actuel pour démarrer un travail spécifique :

# Soit en 2 temps, création, "activation"
git branch feat/specific-stuff
git switch feat/specific-stuff
# Soit les 2 en un
git switch --create=feat/specific-stuff

J’ai délibérément choisi ce nom avec un séparateur slash / pour te montrer que dans le répertoire .git/refs/heads/ on obtient un sous-répertoire feat/ qui contient un fichier specific-stuff. Ce type de nommage me permet de regrouper mes branches par sous-ensembles thématiques mais me contraint par la même occasion en m’empêchant de créer une branche feat. Je trouve cette approche avantageuse car elle nous évite de partir dans les méandres des schémas de branches trop profonds. Si le sujet du nommage des branches t’intéresse, je t’invite à lire cet autre article.

Revenons à nos moutons : notre branche créée possède la même référence que notre branche main, à savoir le dernier commit produit dd4d06db3420782a6b63fa5a067d82d42af9ec7d. J’ai précisé au-dessus l’avoir activée. Ça signifie que HEAD pointe dessus et que si je réalise de nouveaux commits, cette nouvelle branche évoluera, mais pas main.

Renommer une branche

Admettons que ma branche feat/specific-stuff aurait dû s’appeler feat/stuff. Je peux changer son nom comme suit :

git branch -m feat/specific-stuff feat/stuff

Supprimer une branche

Supprimer une branche signifie supprimer une étiquette, pas les commits. Ceux-ci seront déréférencés, sauf si une autre branche (ou étiquette) désigne un historique contenant ma branche.

L’opération de suppression consiste à supprimer le fichier de pointeur localement, c’est donc très rapide :

git branch -d branch-name

Git possède un garde-fou très utile puisqu’il nous empêche normalement de supprimer une étiquette de branche si les commits qu’elle désigne risquent d’etre déréférencés 😮‍💨. Il nous affichera alors un message nous expliquant que la branche n’est pas fusionnée et qu’on va « perdre » des commits, nous proposant de forcer éventuellement cette opération (git branch -D branch-name ou git branch -d -f branch-name). Et tant qu’à faire, il nous interdira toujours de supprimer la branche sur laquelle on se trouve (bûcheron suicidaire, passe ton chemin 🪚🪵).

Fusionner des branches

La notion de fusion consiste généralement à marquer la fin d’un travail. Pour ce qui est des branches, la fusion implique de faire évoluer une branche en intégrant le travail d’une autre branche. On fait donc évoluer une des branches quand l’autre reste à sa place.

Prenons une branche main et une branche dev dont on vient de terminer le travail et qu’on souhaite intégrer à main :

        main <- HEAD
       /
*--*--*
    \
     *--*
         \
          dev

Une fois la fusion réalisée (avec la branche main active, on git merge dev), seule l’étiquette main a changé :

            main <- HEAD
           /
*--*--*---*
    \    /
     *--*
         \
          dev

La fusion est un vaste sujet qui demande un article dédié. Ce qu’il faut retenir, c’est qu’au niveau des branches ça représente une fois de plus un simple déplacement d’étiquette.

Résumons tout ça

Tu l’as compris, les branches sont faciles à utiliser. Les opérations se faisant à 99% localement (toutes sauf les interactions de partage, en fait), la performance est optimale. Cerise sur le gâteau : on a la garantie, même en cas de mauvaise manipulation, de ne rien perdre réellement. Bon, après, il va falloir travailler sur ta capacité à retrouver ce que tu as perdu, principalement avec le reflog.

Et pour ne rien rater de la suite…

Si tu veux être sûr·e de ne rater aucun de nos tutos et articles, le mieux est encore de t’abonner à notre newsletter et à notre chaîne YouTube. Tu peux aussi nous suivre sur Twitter. Si tu aimes ce que nous faisons, n’oublie pas de liker, de commenter et d’en parler autour de toi 🙂.

Et bien entendu, n'hésite pas à jeter un œil à nos formations ! Si comprendre Git sur le bout des doigts t’intéresse, la Git Total est faite pour toi 😎 !

Découvrez nos cours vidéo ! 🖥

Nos cours vidéo sont un complément idéal et bon marché à nos articles techniques et formations présentielles. Autour de Git, de JavaScript et d’autres sujets, retrouvez des contenus de très grande qualité à des prix abordables, spécialement conçus pour lever vos plus gros points de blocage.