Dans le système de contrôle de version Git, il existe deux façons de combiner une branche avec une autre, représentées par différentes commandes :
git merge. Les commits d’une branche sont transférés dans une autre en créant un commit de fusion (merge commit).
git rebase. Les commits d’une branche sont transférés dans une autre tout en conservant l’ordre original des modifications.
En termes simples : avec git merge, les commits d’une branche sont « compressés » en un seul, tandis qu’avec git rebase, ils restent intacts, mais les branches sont tout de même combinées.
La commande git rebase permet donc de combiner les commits des deux branches pour former un historique commun des modifications.
Ce guide explique la commande git rebase, qui sert à réappliquer (rebaser) des commits (modifications) d’une branche sur une autre.
Tous les exemples présentés utilisent Git version 2.34.1, fonctionnant sur un serveur Hostman avec le système d’exploitation Ubuntu 22.04.
Vous pouvez utiliser ces guides pour installer Git sur votre machine :
Git Rebase est une commande puissante principalement utilisée pour intégrer les modifications d’une branche sur une autre en réécrivant l’historique des commits. Contrairement à git merge, qui crée un nouveau commit de fusion et préserve l’historique des deux branches, git rebase déplace ou « rejoue » une série de commits d’une branche sur une autre. Ce processus produit un historique linéaire, donnant l’impression que la branche de fonctionnalité a été développée directement à partir du dernier commit de la branche principale (par exemple, main ou master). Cela permet d’obtenir un historique plus propre et plus facile à comprendre.
La meilleure façon de comprendre le fonctionnement du rebase dans Git est d’examiner un dépôt abstrait composé de plusieurs branches, en suivant le processus étape par étape.
Supposons que nous ayons créé un dépôt avec une seule branche master, contenant un seul commit. La branche master ressemble à ceci :
master
commit_1
Ensuite, à partir de master, nous avons créé une nouvelle branche appelée hypothesis, où nous testons certaines fonctionnalités. Dans cette branche, nous avons effectué plusieurs commits pour améliorer le code. La branche ressemble maintenant à ceci :
hypothesis
commit_4
commit_3
commit_2
commit_1
Plus tard, nous avons ajouté un autre commit à la branche master pour corriger rapidement une vulnérabilité. La branche master ressemble maintenant à ceci :
master
commit_5
commit_1
Nous avons donc maintenant deux branches :
master
commit_5
commit_1
hypothesis
commit_4
commit_3
commit_2
commit_1
La branche master est la principale, tandis que hypothesis est secondaire (dérivée). Les commits récents sont listés avant les anciens, comme dans la sortie de la commande git log.
Supposons que nous souhaitions continuer à travailler sur la fonctionnalité que nous avions déplacée dans la branche hypothesis. Cependant, cette branche ne contient pas la correction critique effectuée dans master.
Nous voulons donc « synchroniser » l’état de hypothesis avec master, afin que le commit de correction apparaisse également dans la branche de fonctionnalité. Autrement dit, nous voulons que la structure du dépôt ressemble à ceci :
master
commit_5
commit_1
hypothesis
commit_4
commit_3
commit_2
commit_5
commit_1
Comme vous pouvez le voir, la branche hypothesis répète maintenant exactement l’historique de master, bien qu’elle ait été créée avant commit_5. En d’autres termes, hypothesis contient désormais l’historique des deux branches : la sienne et celle de master.
Pour obtenir ce résultat, nous devons utiliser git rebase.
Ensuite, les modifications apportées dans hypothesis peuvent être fusionnées dans master en utilisant la commande classique git merge, qui crée un commit de fusion.
La structure du dépôt ressemblera alors à ceci :
master
commit_merge
commit_5
commit_1
hypothesis
commit_4
commit_3
commit_2
commit_5
commit_1
Exécuter git merge après git rebase peut également réduire la probabilité de conflits.
Après avoir couvert la théorie, passons à la pratique en testant git rebase dans un dépôt réel. La structure du dépôt sera identique à celle de l’exemple théorique.
Créez d’abord un répertoire pour le dépôt :
mkdir rebase
Accédez-y :
cd rebase
Initialisez ensuite le dépôt :
git init
Un message d’information standard apparaîtra dans la console :
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
...
Dans le répertoire actuel, un dossier caché .git apparaîtra, que vous pouvez afficher avec :
ls -a
Le paramètre -a signifie all et permet d’afficher le système de fichiers en mode étendu. Son contenu :
. .. .git
Avant de créer des commits, nous devons définir quelques informations utilisateur de base.
D’abord, le nom :
git config --global user.name "NAME"
Puis l’e-mail :
git config --global user.email "NAME@HOST.COM"
À l’aide de fichiers texte simples, nous allons simuler l’ajout de différentes fonctions au projet. Chaque nouvelle fonction sera représentée par un commit distinct.
Créez un fichier pour la première fonction :
nano function_1
Ajoutez le contenu suivant :
Function 1
Indexez les modifications :
git add .
Vérifiez l’état de l’indexation :
git status
La console devrait afficher les modifications indexées :
On branch master
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: function_1
Validez maintenant :
git commit -m "commit_1"
La console confirmera le commit avec succès dans master :
[master (root-commit) 4eb7cc3] commit_1
1 file changed, 1 insertion(+)
create mode 100644 function_1
Créez une nouvelle branche nommée hypothesis :
git checkout -b hypothesis
L’option -b permet de passer directement à la nouvelle branche.
La console affiche :
Switched to a new branch 'hypothesis'
Effectuez trois commits successifs :
commit_2 avec le fichier function_2 et le contenu : Function 2commit_3 avec le fichier function_3 et le contenu : Function 3commit_4 avec le fichier function_4 et le contenu : Function 4Vérifiez ensuite la liste des commits :
git log --oneline
Résultat :
d3efb82 (HEAD -> hypothesis) commit_4
c9f57b7 commit_3
c977f16 commit_2
4eb7cc3 (master) commit_1
L’option --oneline affiche l’historique sous forme condensée.
Ajoutez maintenant un autre commit à master :
git checkout master
Message de confirmation :
Switched to branch 'master'
Créez un nouveau fichier :
nano function_5
Avec le contenu suivant :
Function 5
Indexez les modifications :
git add .
Validez le commit :
git commit -m "commit_5"
Vérifiez :
git log --oneline
Résultat :
3df7a00 (HEAD -> master) commit_5
4eb7cc3 commit_1
Passez à la branche hypothesis :
git checkout hypothesis
Exécutez le rebase :
git rebase master
Un message confirme le succès :
Successfully rebased and updated refs/heads/hypothesis.
Vérifiez la liste des commits :
git log --oneline
Résultat :
8ecfd58 (HEAD -> hypothesis) commit_4
f715aba commit_3
ee47470 commit_2
3df7a00 (master) commit_5
4eb7cc3 commit_1
La branche hypothesis contient maintenant l’historique complet du dépôt.
Comme avec git merge, des conflits peuvent survenir lors d’un git rebase et nécessitent une résolution manuelle.
Créez un fichier de test :
nano conflict
Contenu :
There must be a conflict here!
Indexez :
git add .
Validez :
git commit -m "conflict_1"
Passez à master :
git checkout master
Créez un fichier similaire :
nano conflict
Contenu :
There must NOT be a conflict here!
Indexez et validez :
git add .
git commit -m "conflict_2"
Modifiez le fichier à nouveau :
nano conflict
Contenu :
There definitely must NOT be a conflict here!
Indexez et validez encore :
git add .
git commit -m "conflict_3"
Revenez sur hypothesis et exécutez :
git checkout hypothesis
git rebase master
Git signale un conflit :
Auto-merging conflict
CONFLICT (add/add): Merge conflict in conflict
error: could not apply 6003ed7... conflict_1
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: run "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".
Could not apply 6003ed7... conflict_1
Git suggère d’éditer le fichier, d’indexer les modifications avec git add, puis de continuer avec git rebase --continue.
Ouvrez le fichier :
nano conflict
Le fichier contient deux versions entre balises :
<<<<<<< HEAD
There definitely must NOT be a conflict here!
=======
There must be a conflict here!
>>>>>>> 6003ed7 (conflict_1)
Supprimez les lignes inutiles et gardez :
There must absolutely definitely unanimously NOT be any conflict here!
Indexez :
git add .
Continuez :
git rebase --continue
Après cela, la console ouvrira un éditeur de texte suggérant de modifier le message de commit original du commit où le conflit s’est produit :
conflict_1
# Please enter the commit message for your changes. Lines starting
# with '#' will be ignored, and an empty message aborts the commit.
#
# interactive rebase in progress; onto bd7aefc
# Last commands done (4 commands done):
# pick 8ecfd58 commit_4
# pick 6003ed7 conflict_1
# No commands remaining.
# You are currently rebasing branch 'hypothesis' on 'bd7aefc'.
#
# Changes to be committed:
# modified: conflict
#
La console affiche ensuite un message confirmant la réussite du processus de rebase :
[detached HEAD 482db49] conflict_1
1 file changed, 1 insertion(+), 1 deletion(-)
Successfully rebased and updated refs/heads/hypothesis.
Si vous vérifiez maintenant la liste des commits dans la branche hypothesis :
git log --oneline
Vous verrez la séquence originale de toutes les modifications effectuées :
482db49 (HEAD -> hypothesis) conflict_1
bd5d036 commit_4
407e245 commit_3
948b41c commit_2
bd7aefc (master) conflict_3
d98648d conflict_2
3df7a00 commit_5
4eb7cc3 commit_1
Notez que les commits conflict_2 et conflict_3, réalisés dans la branche master, apparaissent dans l’historique avant le commit conflict_1. Cela s’applique à tout commit effectué dans la branche master.
En plus du travail local, il est possible d’effectuer un rebase lors de la récupération de modifications depuis un dépôt distant. Utilisez l’option --rebase avec pull :
git pull --rebase remote branch
Où :
remote est le dépôt distant
branch est la branche distante
Cette configuration est équivalente à la commande git rebase, à la différence que les modifications (commits) appliquées à la branche actuelle proviennent du dépôt distant.
La commande git rebase permet de créer un historique linéaire, composé de commits successifs, facilitant la lecture et la compréhension.
Exécuter git rebase avant git merge réduit considérablement la probabilité de conflits. Les conflits sont plus faciles à résoudre dans des commits séquentiels que dans un commit de fusion unique.
Contrairement à merge, rebase réécrit partiellement l’historique de la branche, supprimant des éléments inutiles.
La réécriture de l’historique peut entraîner des erreurs irréversibles et la perte de données si elle est mal utilisée.
Git rebase est particulièrement utile lorsqu’on travaille sur de petites branches ou sur des branches de fonctionnalités individuelles qui seront ensuite fusionnées dans une branche principale partagée. Il est également très pratique pour garder un historique propre et linéaire, ce qui est particulièrement bénéfique dans les projets open source ou lorsqu’il faut maintenir un historique de commits clair pour faciliter le débogage et la compréhension du projet.
Cependant, dans les environnements d’équipe comportant plusieurs contributeurs, rebase doit être utilisé avec prudence afin d’éviter les problèmes liés à la réécriture de l’historique public. Il est important de communiquer avec votre équipe pour déterminer quand il est approprié d’utiliser rebase et s’assurer que tous soient conscients des risques de conflits potentiels. Dans de nombreux cas, utiliser git merge pour intégrer les branches peut être plus sûr et plus simple, en particulier lorsqu’on travaille sur des branches partagées ou lorsqu’un historique non linéaire est acceptable.
L’un des aspects essentiels de git rebase est qu’il réécrit l’historique des commits, ce qui signifie que les identifiants SHA‑1 des commits rebasés changent. Cela peut poser des problèmes dans les environnements collaboratifs, notamment lorsque vous rebasez une branche déjà poussée vers un dépôt distant partagé. Le rebase modifie l’historique des commits, ce qui peut entraîner des conflits avec d’autres développeurs ayant basé leur travail sur l’ancien historique. Il peut également provoquer des erreurs lors du push, car le dépôt distant détectera que l’historique local ne correspond plus à celui du dépôt distant.
Une bonne pratique consiste à éviter de rebaser des branches publiques qui ont déjà été partagées avec d’autres. Comme rebase modifie l’historique des commits, rebaser des branches utilisées par plusieurs développeurs peut entraîner des divergences d’historique, de la confusion et des conflits de fusion difficiles à résoudre. En général, git rebase est plus adapté aux branches locales ou à la préparation d’une branche de fonctionnalité avant sa fusion finale dans la branche principale. Les branches publiques, surtout celles sur lesquelles plusieurs personnes travaillent, devraient généralement être fusionnées (merge) plutôt que rebasées.
Pendant un rebase, si des modifications existent à la fois dans la branche de fonctionnalité et dans la branche cible, Git s’arrête et demande de résoudre le conflit manuellement. Bien que la résolution de conflits lors d’un rebase soit similaire à celle d’un merge, le rebase exige de résoudre les conflits pour chaque commit à réappliquer, ce qui peut être plus fastidieux, surtout sur de longues branches de fonctionnalités. Une fois les conflits résolus, vous pouvez poursuivre le rebase avec la commande git rebase --continue.
La fusion de deux branches avec git rebase diffère fondamentalement de la fusion classique avec git merge.
git merge combine les commits d’une branche en un seul commit dans une autre.
git rebase déplace les commits d’une branche à la fin d’une autre, tout en conservant l’ordre original.
Un effet similaire de rebase peut également être obtenu en utilisant la commande git pull avec l’option supplémentaire --rebase.
D’un côté, la commande git rebase permet d’obtenir un historique de commits plus propre et plus facile à comprendre dans la branche principale, ce qui améliore la maintenabilité du dépôt.
De l’autre, git rebase réduit le niveau de détail des modifications dans la branche, simplifiant l’historique et supprimant certains enregistrements.
Pour cette raison, le rebase est une fonctionnalité destinée aux utilisateurs expérimentés qui comprennent bien le fonctionnement interne de Git.
Le plus souvent, la commande git rebase est utilisée conjointement avec git merge afin d’obtenir une structure de dépôt et de branches aussi optimale que possible.