The git rebase command has a reputation for being magical Git voodoo that beginners should stay away from, but it can actually make life much easier for a development team when used with care. In this article, we’ll compare git rebase with the related git merge command and identify all of the potential opportunities to incorporate rebasing into the typical Git workflow.

Présentation des concepts

La première chose à savoir sur la commande git rebase est qu'elle poursuit le même objectif que git merge. Ces deux commandes permettent de transférer des changements d'une branche à une autre. Seule la manière de procéder diffère.

Songez à ce qu'il se produit si vous commencez à travailler sur une nouvelle fonctionnalité dans une branche dédiée, puis que l'un de vos collègues met à jour la branche master avec de nouveaux commits. Résultat ? Vous obtenez un historique forké, un élément bien connu de tout développeur ayant déjà utilisé l'outil de collaboration Git.

L'historique de commit forké

À présent, imaginez que les nouveaux commits de la branche master soient pertinents pour la fonctionnalité sur laquelle vous travaillez. Deux méthodes s'offrent à vous pour ajouter les nouveaux commits dans votre branche de fonctionnalité : le merge ou le rebase.

L'option Merge

The easiest option is to merge the master branch into the feature branch using something like the following:

git checkout feature
git merge master

Vous pouvez également condenser cette commande en une ligne :

git merge master feature

This creates a new “merge commit” in the feature branch that ties together the histories of both branches, giving you a branch structure that looks like this:

Faire un merge de la branche master dans la branche de fonctionnalité

Le merge est une opération intéressante, car elle est non destructive. Les branches existantes ne sont aucunement altérées. Cela permet d'éviter les pièges potentiels du rebase (abordés ci-dessous).

Par ailleurs, cela signifie également qu'un commit de merge extérieur sera généré dans la branche de fonctionnalité dès que vous intégrez des changements en amont. Une branche master très active peut grandement contribuer à polluer l'historique de votre branche de fonctionnalité. Si vous tentez d'atténuer ce problème à l'aide des options git log avancées, les autres développeurs auront des difficultés pour suivre l'historique du projet.

L'option Rebase

As an alternative to merging, you can rebase the feature branch onto master branch using the following commands:

git checkout feature
git rebase master

Toute la branche de fonctionnalité sera ainsi déplacée sur la pointe de la branche master, et tous les nouveaux commits seront intégrés à master. Cependant, au lieu d'utiliser un commit de merge, le rebase consiste à réécrire l'historique du projet en créant de nouveaux commits pour chaque commit de la branche d'origine.

Rebase de la branche de fonctionnalité sur la branche master

Le principal avantage du rebase est que l'historique de votre projet sera nettement plus propre. Premièrement, cette opération permet de supprimer les commits de merge superflus requis par la commande git merge. Deuxièmement, comme l'illustre le schéma ci-dessus, vous obtenez un historique de projet parfaitement linéaire, qui vous permettra de suivre la pointe de la branche de fonctionnalité à toutes les étapes du projet à partir de son commencement, sans fork. Ainsi, vous pourrez naviguer dans votre projet plus librement grâce à des commandes comme git log, git bisect et gitk.

Mais deux caractéristiques sont essentielles pour cet historique de commit épuré : la sécurité et la traçabilité. Si vous ne suivez pas la règle d'or du rebase, la réécriture d'historique de projet peut potentiellement se révéler catastrophique pour votre workflow de collaboration. En outre et dans une moindre mesure, le rebase perd le contexte fourni par un commit de merge : vous ne pouvez pas voir à quel moment des changements apportés au dépôt upstream ont été intégrés à la fonctionnalité.

Rebase interactif

Le rebase interactif vous donne la possibilité de modifier des commits lorsqu'ils sont déplacés vers la nouvelle branche. Cette opération est plus efficace qu'un rebase automatique, puisqu'elle permet de contrôler l'intégralité de l'historique des commits de la branche. Le plus souvent, le rebase interactif est utilisé pour nettoyer un historique désordonné avant de faire un merge d'une branche de fonctionnalité dans master.

To begin an interactive rebasing session, pass the i option to the git rebase command:

git checkout feature
git rebase -i master

Cette commande ouvre un éditeur de texte répertoriant tous les commits qui seront déplacés.

pick 33d5b7a Message de commit n° 1
pick 9480b3d Message de commit n° 2
pick 5c67e61 Message de commit n° 3

This listing defines exactly what the branch will look like after the rebase is performed. By changing the pick command and/or re-ordering the entries, you can make the branch’s history look like whatever you want. For example, if the 2nd commit fixes a small problem in the 1st commit, you can condense them into a single commit with the fixup command:

pick 33d5b7a Message pour le commit nº 1
fixup 9480b3d Message pour le commit nº 2
pick 5c67e61 Message pour le commit nº 3

Lorsque vous enregistrez et fermez le fichier, Git effectue le rebase conformément à vos instructions et vous obtenez un historique de projet semblable au suivant :

Écraser un commit avec un rebase interactif

En supprimant les commits superflus de cette manière, l'historique de votre fonctionnalité sera nettement plus lisible. La commande git merge ne vous permet pas d'obtenir ce résultat.

Règle d'or du rebase

Une fois que vous avez compris en quoi consiste le rebase, vous devez avant tout savoir dans quels cas ne pas l'utiliser. N'utilisez jamais la commande git rebase sur les branches publiques. C'est la règle d'or !

Par exemple, imaginez ce qu'il se produirait si vous effectuiez un rebase de master sur votre branche de fonctionnalité :

Rebase de la branche master

The rebase moves all of the commits in master onto the tip of feature. The problem is that this only happened in your repository. All of the other developers are still working with the original master. Since rebasing results in brand new commits, Git will think that your master branch’s history has diverged from everybody else’s.

La seule manière de synchroniser les deux branches master est de les merger à nouveau, ce qui générera un commit de merge supplémentaire et deux ensembles de commits contenant les mêmes changements (les commits d'origine et ceux issus de votre branche rebasée). De quoi compliquer les choses, non ?

So, before you run git rebase, always ask yourself, “Is anyone else looking at this branch?” If the answer is yes, take your hands off the keyboard and start thinking about a non-destructive way to make your changes (e.g., the git revert command). Otherwise, you’re safe to re-write history as much as you like.

Force-push

Si vous tentez de pusher à nouveau la branche master rebasée vers un dépôt distant, Git vous en empêchera, car elle entre en conflit avec la branche master distante. Vous pouvez toutefois forcer ce push en lançant le flag --force comme suit :

# Soyez très prudent avec cette commande !
git push --force

La branche master distante sera remplacée de manière à correspondre à la branche rebasée issue de votre dépôt. Résultat : vos collègues auront peine à s'y retrouver ! Veillez donc à utiliser cette commande uniquement quand vous êtes sûr de ce que vous faites.

Vous devriez uniquement forcer un push lorsque vous effectuez un nettoyage local après avoir pushé une branche de fonctionnalité privée vers un dépôt distant (par ex., à des fins de sauvegarde). C'est comme dire : « Oups, je ne voulais pas vraiment pusher cette version d'origine de la branche de fonctionnalité. Prenez plutôt la branche courante. » À nouveau, il est important que personne ne travaille sur les commits de la version d'origine de la branche de fonctionnalité.

Description du workflow

Le rebase peut être intégré à votre workflow Git existant dans la mesure qui convient à votre équipe. Dans cette section, nous examinerons les avantages du rebase aux différentes étapes du développement d'une fonctionnalité.

The first step in any workflow that leverages git rebase is to create a dedicated branch for each feature. This gives you the necessary branch structure to safely utilize rebasing:

Développement d'une fonctionnalité dans une branche dédiée

Nettoyage local

L'un des meilleurs moyens d'intégrer le rebase à votre workflow est de nettoyer les fonctionnalités locales en cours. En réalisant périodiquement un rebase interactif, vous pouvez vous assurer que chaque commit de votre fonctionnalité est ciblé et sensé. Cela vous permet d'écrire votre code sans avoir à vous préoccuper de le séparer en commits isolés : vous pouvez par la suite le réparer.

Lorsque vous appelez la commande git rebase, vous avez le choix entre deux bases : la branche parente de la fonctionnalité (par ex., la branche master) ou un commit précédent de votre fonctionnalité. Nous avons examiné un exemple de la première option dans la section Rebase interactif. Privilégiez cette solution si vous devez seulement réparer les quelques commits les plus récents. Par exemple, la commande suivante lance un rebase interactif uniquement pour les trois derniers commits.

git checkout feature
git rebase -i HEAD~3

En définissant HEAD~3 comme la nouvelle base, vous ne déplacez pas la branche : vous réécrivez les trois commits qui la suivent de manière interactive. Remarque : cette solution ne vous permet pas d'intégrer les changements en amont à la branche de fonctionnalité.

Rebase sur Head~3

Si vous souhaitez réécrire toute la fonctionnalité dans son ensemble en utilisant cette méthode, la commande git merge-base vous sera utile pour retrouver la base d'origine de la branche de fonctionnalité. La commande suivante vous permet de récupérer la référence du commit de la base d'origine, puis de la transmettre à git rebase :

git merge-base feature master

Cette utilisation du rebase interactif est une excellente manière d'intégrer git rebase à votre workflow, puisque cette opération concerne uniquement les branches locales. La seule chose que les autres développeurs verront sera votre résultat final, c'est-à-dire un historique de branche de fonctionnalité propre et clair.

Mais à nouveau, cela ne fonctionne que pour les branches de fonctionnalité privées. Si vous collaborez avec d'autres développeurs dans la même branche de fonctionnalité, cette branche est publique, et vous n'êtes pas autorisé à réécrire son historique.

En cas de rebase interactif, git merge est la seule commande qui vous permettra de nettoyer les commits locaux.

Incorporer les changements en amont dans une fonctionnalité

In the Conceptual Overview section, we saw how a feature branch can incorporate upstream changes from master using either git merge or git rebase. Merging is a safe option that preserves the entire history of your repository, while rebasing creates a linear history by moving your feature branch onto the tip of master.

This use of git rebase is similar to a local cleanup (and can be performed simultaneously), but in the process it incorporates those upstream commits from master.

Gardez à l'esprit que rien ne vous interdit d'effectuer un rebase sur une branche distante plutôt que sur master. Cette méthode vous sera utile si vous collaborez sur une même fonctionnalité avec un autre développeur et que vous souhaitez intégrer ses changements à votre dépôt.

For example, if you and another developer named John added commits to the feature branch, your repository might look like the following after fetching the remote feature branch from John’s repository:

Collaborer sur la même branche de fonctionnalité

Vous pouvez résoudre ce fork exactement de la même manière que lorsque vous intégrez des changements en amont à partir de master : soit vous mergez votre branche de fonctionnalité locale avec john/feature, soit vous rebasez cette branche de fonctionnalité locale sur la pointe de john/feature.

Comparaison du merge et du rebase sur une branche distante

Note that this rebase doesn’t violate the Golden Rule of Rebasing because only your local feature commits are being moved—everything before that is untouched. This is like saying, “add my changes to what John has already done.” In most circumstances, this is more intuitive than synchronizing with the remote branch via a merge commit.

By default, the git pull command performs a merge, but you can force it to integrate the remote branch with a rebase by passing it the --rebase option.

Revoir une fonctionnalité avec une pull request

Si vous faites des pull requests dans le cadre de la revue du code, évitez d'utiliser git rebase après avoir créé une pull request. Dès que vous aurez fait la pull request, les autres développeurs pourront examiner vos commits. En d'autres termes, cette branche sera publique. Si vous réécrivez son historique, Git et vos collègues n'auront plus la possibilité d'examiner les commits de suivi ajoutés à la fonctionnalité.

Tout changement apporté par les autres développeurs devra être intégré avec git merge et non git rebase.

Pour cette raison, il est généralement judicieux de nettoyer votre code avec un rebase interactif avant de soumettre votre pull request.

Intégration d'une fonctionnalité approuvée

Une fois qu'une fonctionnalité a été approuvée par votre équipe, vous pouvez la rebaser sur la pointe de la branche master avant d'exécuter git merge pour intégrer la fonctionnalité à la base de code principale.

La situation est la même que lorsque vous intégrez des changements en amont à une branche de fonctionnalité. Cependant, comme vous n'êtes pas autorisé à réécrire des commits dans la branche master, vous devrez utiliser git merge pour intégrer la fonctionnalité. Cependant, en réalisant un rebase avant un merge, vous effectuerez à coup sûr un fast-forward merge, et votre historique sera parfaitement linéaire. Par ailleurs, cette opération vous permet d'écraser tout commit de suivi ajouté lors d'une pull request.

Intégration d'une fonctionnalité dans la branche master avec et sans rebase

Si vous n'êtes pas tout à fait à l'aise avec la commande git rebase, vous pouvez toujours réaliser le rebase dans une branche temporaire. Ainsi, si vous semez le chaos sans le vouloir dans l'historique de votre fonctionnalité, il vous suffira de récupérer la branche d'origine pour réessayer. Par exemple :

git checkout feature
git checkout -b temporary-branch
git rebase -i master
# [Nettoyage de l'historique]
git checkout master
git merge temporary-branch

Summary

À présent, vous savez tout ce qu'il faut savoir sur le rebase des branches. Si vous souhaitez obtenir un historique propre, linéaire et sans commits de merge superflus, utilisez git rebase plutôt que git merge au moment d'intégrer vos changements à partir d'une autre branche.

On the other hand, if you want to preserve the complete history of your project and avoid the risk of re-writing public commits, you can stick with git merge. Either option is perfectly valid, but at least now you have the option of leveraging the benefits of git rebase.