Branches#

Les branches sont la fonctionnalité la plus puissante de Git. Elles permettent de développer plusieurs lignes de travail en parallèle, d’expérimenter sans risque et de collaborer efficacement. Contrairement à d’autres systèmes de gestion de versions où créer une branche est une opération coûteuse, Git rend cette operation quasi instantanée et pratiquement gratuite en espace disque.

Ce chapitre couvre la gestion des branches en profondeur : leur nature interne, leur création, la navigation entre elles, leur visualisation sous forme de graphe orienté, leur suppression, et le concept crucial de HEAD détaché. Maitriser les branches est un préalable indispensable avant d’aborder la fusion et le rebase dans les chapitres suivants.

Qu’est-ce qu’une branche ?#

Définition 18 (Branche)

Une branche Git est un pointeur léger et mobile vers un commit. Concrètement, créer une branche revient à écrire un fichier de 41 octets (le hash SHA-1 du commit cible, suivi d’un saut de ligne) dans le répertoire .git/refs/heads/. La branche avance automatiquement à chaque nouveau commit effectué dessus.

Définition 19 (HEAD)

HEAD est un pointeur spécial qui indique la branche courante. Il est stocké dans le fichier .git/HEAD et contient généralement une référence symbolique de la forme ref: refs/heads/main. C’est HEAD qui détermine quelle branche avancera lors du prochain commit.

Remarque 12

Dans les systèmes comme SVN, créer une branche copie l’intégralité de l’arborescence du projet, ce qui est une opération en \(O(n)\) ou \(n\) est la taille du dépôt. Dans Git, créer une branche est une opération en \(O(1)\) : il suffit d’écrire 41 octets dans un fichier. C’est cette légèreté qui encourage à créer des branches pour la moindre fonctionnalité, le moindre correctif, la moindre expérimentation.

Créer et naviguer entre les branches#

Commençons par créer un dépôt de démonstration avec quelques commits.

%%bash
cd /tmp && rm -rf demo-branches && mkdir demo-branches && cd demo-branches && git init
git config user.email "demo@example.com" && git config user.name "Demo"
echo "v1" > fichier.txt && git add . && git commit -m "Commit A"
echo "v2" >> fichier.txt && git add . && git commit -m "Commit B"
Dépôt Git vide initialisé dans /tmp/demo-branches/.git/
[main (commit racine) ee4b129] Commit A
 1 file changed, 1 insertion(+)
 create mode 100644 fichier.
txt
[main 113695d] Commit B
 1 file changed, 1 insertion(+)

Créer une branche#

La commande git branch <nom> crée une nouvelle branche qui pointe sur le commit courant, sans basculer dessus.

%%bash
cd /tmp/demo-branches
git branch feature

Lister les branches#

%%bash
cd /tmp/demo-branches
git branch
  feature
* main

L’asterisque * indique la branche active, c’est-à-dire celle sur laquelle HEAD pointe.

Basculer sur une branche#

%%bash
cd /tmp/demo-branches
git switch feature
git branch
Basculement sur la branche 'feature'
* feature
  main

Remarque 13

La commande git switch a été introduite dans Git 2.23 (aout 2019) pour remplacer git checkout dans son role de bascule entre branches. La commande checkout était surchargée : elle servait à la fois à changer de branche et à restaurer des fichiers. Désormais, git switch gère les branches et git restore gère les fichiers. L’ancienne syntaxe git checkout feature fonctionne toujours, mais git switch est plus claire et moins sujette aux erreurs.

Créer et basculer en une seule commande#

%%bash
cd /tmp/demo-branches
git switch -c nouvelle-branche
git branch
Basculement sur la nouvelle branche 'nouvelle-branche'
  feature
  main
* nouvelle-branche

L’option -c (pour create) combine la création et la bascule. L’équivalent avec l’ancienne syntaxe est git checkout -b nouvelle-branche.

Faire diverger les branches#

Revenons sur la branche feature et effectuons des commits différents sur chaque branche pour créer une divergence.

%%bash
cd /tmp/demo-branches
git switch feature
echo "feature line 1" >> feature.txt && git add . && git commit -m "Commit C (feature)"
echo "feature line 2" >> feature.txt && git add . && git commit -m "Commit D (feature)"
Basculement sur la branche 'feature'
[feature 81cf4b5] Commit C (feature)
 1 file changed, 1 insertion(+)
 create mode 100644 feature.txt

[feature aa97cf4] Commit D (feature)
 1 file changed, 1 insertion(+)
%%bash
cd /tmp/demo-branches
git switch main
echo "main line 1" >> main.txt && git add . && git commit -m "Commit E (main)"
Basculement sur la branche 'main'
[main d8bf376] Commit E (main)
 1 file changed, 1 insertion(+)
 create mode 100644 main.txt

Visualisons maintenant l’état du graphe de commits.

%%bash
cd /tmp/demo-branches
git log --oneline --graph --all --decorate
* aa97cf4 (feature) Commit D (feature)
* 81cf4b5 Commit C (feature)
| * d8bf376 (HEAD -> main) Commit E (main)
|/  
* 113695d (nouvelle-branche) Commit B
* ee4b129 Commit A

Le graphe montre clairement la divergence : main et feature partagent les commits A et B, puis chacune suit son propre chemin.

Visualiser les branches#

La sortie textuelle de git log --graph est utile en ligne de commande, mais une représentation graphique est plus parlante. Construisons un DAG (graphe orienté acyclique) avec networkx et matplotlib pour visualiser la structure de nos branches.

Hide code cell source

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import networkx as nx
import seaborn as sns

sns.set_theme(style="whitegrid", palette="muted", font_scale=1.1)

G = nx.DiGraph()

# Commits et arêtes (parent -> enfant)
commits = ["A", "B", "C", "D", "E"]
G.add_nodes_from(commits)
G.add_edges_from([("A", "B"), ("B", "C"), ("C", "D"), ("B", "E")])

# Disposition horizontale (gauche à droite)
pos = {
    "A": (0, 0),
    "B": (1, 0),
    "C": (2, 0.8),
    "D": (3, 0.8),
    "E": (2, -0.8),
}

# Couleurs par branche
colors_map = {
    "A": "#5B9BD5",  # main (bleu)
    "B": "#5B9BD5",
    "C": "#70AD47",  # feature (vert)
    "D": "#70AD47",
    "E": "#5B9BD5",
}
node_colors = [colors_map[n] for n in G.nodes()]

fig, ax = plt.subplots(figsize=(10, 4))

nx.draw_networkx_nodes(G, pos, ax=ax, node_size=900, node_color=node_colors, edgecolors="white", linewidths=2)
nx.draw_networkx_labels(G, pos, ax=ax, font_size=14, font_weight="bold", font_color="white")
nx.draw_networkx_edges(G, pos, ax=ax, edge_color="#888888", arrows=True,
                       arrowstyle="-|>", arrowsize=15, connectionstyle="arc3,rad=0.1",
                       min_source_margin=18, min_target_margin=18)

# Etiquettes des branches
labels = {
    "D": ("feature", "#70AD47"),
    "E": ("main", "#5B9BD5"),
}
for node, (label, color) in labels.items():
    x, y = pos[node]
    ax.annotate(
        label,
        xy=(x, y), xytext=(x + 0.45, y + 0.35),
        fontsize=11, fontweight="bold", color="white",
        bbox=dict(boxstyle="round,pad=0.3", facecolor=color, edgecolor="none"),
        arrowprops=dict(arrowstyle="-", color=color, lw=1.5),
    )

# Etiquette HEAD
x_e, y_e = pos["E"]
ax.annotate(
    "HEAD",
    xy=(x_e, y_e), xytext=(x_e + 0.45, y_e - 0.45),
    fontsize=11, fontweight="bold", color="white",
    bbox=dict(boxstyle="round,pad=0.3", facecolor="#C00000", edgecolor="none"),
    arrowprops=dict(arrowstyle="-", color="#C00000", lw=1.5),
)

ax.set_title("Graphe de commits avec branches divergentes", fontsize=14, fontweight="bold", pad=15)
ax.set_xlim(-0.7, 4.2)
ax.set_ylim(-1.8, 1.8)
ax.axis("off")
plt.show()
_images/f3846d222653381ed3920fad9c0c652c33182642888ac6dd988a0b5218776980.png

Sur ce graphe, chaque noeud represente un commit. Les flèches pointent du parent vers l’enfant (sens chronologique). Les commits A et B appartiennent à l’historique commun. A partir de B, deux lignes divergent : la branche feature (en vert) avec les commits C et D, et la branche main (en bleu) avec le commit E. L’etiquette rouge HEAD indique que nous sommes actuellement sur main.

Supprimer une branche#

Lorsqu’une branche a été fusionnee ou n’est plus utile, on peut la supprimer.

Suppression securisée#

La commande git branch -d ne supprime la branche que si elle a déjà été fusionnée dans la branche courante.

%%bash
cd /tmp/demo-branches
git branch -d nouvelle-branche
Branche nouvelle-branche supprimée (précédemment 113695d).

Suppression forcée#

Si la branche contient des commits non fusionnés et que l’on souhaite tout de même la supprimer, on utilise -D (majuscule).

%%bash
cd /tmp/demo-branches
git branch -D feature
git branch
Branche feature supprimée (précédemment aa97cf4).
* main

Remarque 14

Supprimer une branche ne supprime pas les commits auxquels elle pointait. Ces commits deviennent simplement inatteignables (aucune référence ne pointe vers eux). Ils restent dans la base d’objets de Git et sont accessibles via leur hash pendant un certain temps. Le ramasse-miettes de Git (git gc) finit par les supprimer, généralement après 30 jours pour les commits références dans le reflog, et 14 jours pour les objets orphelins. Si vous supprimez une branche par erreur, git reflog permet souvent de retrouver le hash du dernier commit et de recréer la branche.

HEAD detaché#

Définition 20 (HEAD detaché (detached HEAD))

On parle de HEAD détaché lorsque HEAD pointe directement vers un commit au lieu de pointer vers une branche. Dans cet état, HEAD contient un hash de commit plutôt qu’une référence symbolique (ref: refs/heads/...).

Quand cela se produit-il ?#

Le HEAD detaché survient lorsqu’on bascule sur un commit précis plutôt que sur une branche :

%%bash
cd /tmp/demo-branches
# Récupérer le hash du premier commit
HASH=$(git log --oneline | tail -1 | cut -d' ' -f1)
git checkout $HASH
Note : basculement sur 'ee4b129'.

Vous êtes dans l'état « HEAD détachée ». Vous pouvez vis
iter, faire des modifications
expérimentales et les valider. Il vous suffit de faire un autre bascu
lement pour
abandonner les commits que vous faites dans cet état sans impacter les autres branches
Si vous voulez créer une nouvelle branche pour conserver les commits que vous créez,
il vous suff
it d'utiliser l'option -c de la commande switch comme ceci :

  git switch -c <nom-de-la-nouvelle-b
ranche>

Ou annuler cette opération avec :

  git switch -

Désactivez ce conseil en renseignant 
la variable de configuration advice.detachedHead à false

HEAD est maintenant sur ee4b129 Commit A
%%bash
cd /tmp/demo-branches
cat .git/HEAD
ee4b1294c4b32d280df45570e17b3b1ead19d4dd

On constate que .git/HEAD contient un hash brut, et non une référence symbolique. Les situations courantes qui provoquent un HEAD détaché sont :

  • git checkout <hash-du-commit> – explorer un ancien commit

  • git checkout v1.0 – explorer un tag

  • git rebase en cours – chaque commit rejoué passe par un état détaché temporaire

Remarque 15

Les commits effectués en état de HEAD détaché ne sont rattachés à aucune branche. Si l’on bascule ensuite sur une branche existante, ces commits deviennent inatteignables et seront à terme supprimés par le ramasse-miettes. Pour conserver le travail réalisé en HEAD détaché, il faut toujours créer une branche avant de quitter cet état : git switch -c rescue-branch. C’est un réflexe essentiel à acquérir.

Revenons sur main pour quitter l’état détaché.

%%bash
cd /tmp/demo-branches
git switch main
cat .git/HEAD
La position précédente de HEAD était sur ee4b129 Commit A
Basculement sur la branche 'main'
ref: refs/heads/main

Exemple 6 (Sauver un travail en HEAD détaché)

Supposons que l’on ait fait des modifications en HEAD détaché et que l’on souhaite les conserver. Voici la demarche :

  1. On se trouve en HEAD détaché après git checkout <hash>.

  2. On effectue des modifications et des commits.

  3. Avant de quitter cet état, on crée une branche : git switch -c ma-branche-de-sauvetage.

  4. Les commits sont désormais ancrés sur une branche nommée et ne risquent plus d’être perdus.

Résumé#

Le tableau suivant recapitule les commandes de gestion des branches vues dans ce chapitre.

Commande

Description

git branch <nom>

Créer une branche (sans basculer dessus)

git branch

Lister les branches locales

git branch -v

Lister les branches avec le dernier commit

git switch <nom>

Basculer sur une branche existante

git switch -c <nom>

Créer une branche et basculer dessus

git checkout <nom>

Basculer sur une branche (ancienne syntaxe)

git checkout -b <nom>

Créer et basculer (ancienne syntaxe)

git branch -d <nom>

Supprimer une branche (si fusionnee)

git branch -D <nom>

Supprimer une branche (force)

git log --oneline --graph --all

Visualiser le graphe de commits