La plupart des systèmes de gestion de version proposent une forme de gestion des branches. Cela permet de dévier du développement principal sans l'impacter. Dans de nombreux systèmes, ce processus est coûteux, nécessitant souvent une copie complète du répertoire source, ce qui peut prendre du temps pour les gros projets.
Le modèle de branches de Git est souvent qualifié de "fonctionnalité tueuse". Il distingue véritablement Git des autres systèmes. La spécificité ? Les branches Git sont incroyablement légères. Leur création est quasi instantanée, tout comme le passage de l'une à l'autre. Contrairement à d'autres systèmes, Git encourage l'utilisation fréquente de branches et de fusions, même plusieurs fois par jour. Une fois le concept maîtrisé, on comprend pourquoi Git change fondamentalement les méthodes de développement.
Principe d'une branche
Pour comprendre l'implémentation, rappelons que Git ne stocke pas des différences, mais des instentanés complets des fichiers.
À chaque commit, Git crée un objet commit contenant : un pointeur vers l'instantané du contenu indexé, des métadonnnées (auteur, message, etc.), et zéro ou plusieurs pointeurs vers les commits parents (aucun pour le premier commit, un pour un commit standard, plusieurs pour un commit issu d'une fusion).
Considérons un dépôt initial avec trois fichiers. L'opération git add calcule une somme de contrôle (SHA-1) pour chaque fichier, sauvegarde leur version comme objets blob et met à jour la zone de staging.
$ git add README test.rb LICENSE
$ git commit -m 'Initialisation du projet'
Git calcule ensuite la somme de contrôle de chaque sous-répertoire, les stocke comme objets tree. L'objet commit créé pointe vers l'objet tree racine. Le dépôt contient alors cinq objets : trois blobs, un tree et un commit.
Après modification et nouveau commit, l'objet commit suivant inclura un pointeur vers le commit précédent.
Une branche dans Git est essentiellement un pointeur mutable vers un objet commit. La branche par défaut s'appelle master. Après plusieurs commits, ce pointeur avance automatiquement.
Créer une branche consiste simplement à créer un nouveau pointeur vers le commit actuel. L'utilitaire git branch effectue cette opération sans changer la branche courante.
$ git branch development
Git maintient un pointeur spécial nommé
, qui indique la branche locale active. Ce concept diffère de celui d'autres systèmes. agit comme un alias pour la branche courante. Pour changer de branche, on utilise git checkout. Ce déplace le pointeur
et met à jour les fichiers du répertoire de travail pour correspondre à l'instantané de la branche sélectionnée. ``` $ git checkout development
Les modifications et commits effectués sur la branche `development` feront avancer son pointeur, laissant `master` sur son dernier commit. Revenir à `master` avec un `checkout` restaure les fichiers de cette branche.
Cette approche est extrêmement efficace car une branche n'est qu'un fichier contenant les 41 caractères d'une somme SHA-1 (plus un saut de ligne). Créer et supprimer une branche est donc une opération triviale. Cela contraste fortement avec d'autres systèmes qui copient souvent l'intégralité des fichiers du projet.
### Création, fusion et gestion des branches
Un workflow typique pourrait être :
1. Développer un site.
2. Créer une branche pour une nouvelle fonctionnalité.
3. Travailler sur cette branche.
4. Recevoir un signalement de bug urgent sur la version de production.
5. Revenir à la branche de production, créer une branche de correction (`hotfix`), appliquer et tester le correctif.
6. Fusionner la correction dans la production et déployer.
7. Reprendre le travail sur la branche de fonctionnalité.
Pour créer et basculer vers une nouvelle branche en une seule commande, on utilise l'option `-b` avec `git checkout`.
$ git checkout -b correction-serveur
... corrections et commits ...
$ git checkout master $ git merge correction-serveur
Si la branche `master` est un ancêtre direct de `correction-serveur`, Git effectue une "fast-forward", avançant simplement le pointeur `master`. Sinon, Git réalise une fusion à trois points (les deux extrémités et leur ancêtre commun), créant un nouveau commit de fusion.
Des conflits surviennent lorsque le même segment de fichier est modifié différemment dans les branches à fusionner. Git marque les fichiers en conflit avec des balises. Le développeur doit éditer manuellement ces fichiers, choisir la version souhaitée (ou les combiner), puis marquer le conflit comme résolu avec `git add` avant de terminer le commit de fusion.
La commande `git branch` liste toutes les branches. L'astérisque (\*) indique la branche courante. L'option `-v` affiche le dernier commit de chaque branche. Les options `--merged` et `--no-merged` filtrent les branches déjà fusionnées ou non dans la branche courante.
### Branches à long terme et branches de fonctionnalité
Git encourage des branches à long terme pour différents niveaux de stabilité (ex: `main` pour la production, `develop` pour l'intégration continue). Les branches de fonctionnalité (ou branches thématiques) sont de courte durée, dédiées à un travail spécifique. Elles peuvent être créées, utilisées et supprimées fréquemment sans surcoût.
### Branches à distance
Une branche distante (`remote branch`) est un index local de l'état d'une branche sur un dépôt distant. Elle se note sous la forme `nom_distant/nom_branche` (ex: `origin/main`). Ces pointeurs sont mis à jour lors des opérations réseau (`git fetch`, `git pull`).
Pour partager une branche locale, on utilise `git push`.
$ git push distant nom-branche-locale
Cette commande peut être étendue pour spécifier un nom distant différent : `git push distant locale:distante`.
Une branche locale suivant une branche distante (`tracking branch`) permet d'utiliser `git push` et `git pull` sans spécifier explicitement la destination. Cette configuration est automatique lors du clonage pour la branche principale.
Créer une branche locale qui suit une branche distante existante
$ git checkout --track origin/nouvelle-fonctionnalite
Supprimer une branche distante se fait avec une syntaxe spécifique :
$ git push distant :branche-a-supprimer
### Rebasing (Réalignement)
Outre la fusion (`merge`), Git offre le `rebase` pour intégrer des changements. Le réalignement rejoue les commits d'une branche sur une autre base, créant un historique linéaire.
Sur la branche 'feature', la réaligner sur 'main'
$ git checkout feature $ git rebase main
Git retourne à l'ancêtre commun, crée des patchs pour chaque commmit de la branche courante, puis les applique séquentiellement sur la nouvelle base (`main`). Un `git merge` rapide (`fast-forward`) peut ensuite être effectué.
Le réalignement génère un historique plus propre et linéaire. Il est particulièrement utile pour préparer des patchs à soumettre à un projet tiers, en se basant sur la dernière version de la branche principale distante.
Cependant, il existe une règle d'or : **ne jamais réaligner des commits déjà publiés sur un dépôt partagé.** Cela réécrirait l'historique, forçant les collaborateurs à résoudre à nouveau des conflits et créant une divergence et une confusion dans l'historique partagé.
L'option `--onto` permet des réalignements plus complexes, par exemple pour appliquer une série de commits d'une branche dérivée sur une base totalement différente.
Appliquer les commits de 'feature2' (venant de 'feature1') sur 'main'
$ git rebase --onto main feature1 feature2