Close

Merging vs. rebasing

git rebase est souvent perçue comme une commande magique que les débutants doivent éviter à tout prix. Cependant, si elle est utilisée à bon escient, elle peut véritablement faciliter la vie des développeurs. Dans cet article, nous comparerons la commande git rebase à la commande git merge qui lui est associée, et nous identifierons toutes les possibilités d'intégration du rebase à un workflow Git classique.


Conceptual overview


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 principale (main) 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 (main) 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é (feature) : le merge ou le rebase.

Bases de données
Ressource connexe

Comment déplacer un dépôt Git complet

Logo Bitbucket
DÉCOUVRIR LA SOLUTION

Découvrir Git avec Bitbucket Cloud

The merge option

La solution la plus simple consiste à faire un merge de la branche principale (main) dans la branche de fonctionnalité en procédant de la manière suivante :

git checkout feature
git merge main

Vous pouvez également condenser cette commande en une ligne :

git merge feature main

Un « commit de merge » (merge commit) est créé dans la branche feature. Il relie les historiques des deux branches. La structure de branche est alors redéfinie comme suit :

Merging main into feature branch

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é (feature) dès que vous intégrez des changements en amont. Une branche principale (main) 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.

The rebase option

Plutôt que de faire un merge, vous pouvez faire un rebase de la branche de fonctionnalité (feature) sur la branche (main) en exécutant les commandes suivantes :

git checkout feature
git rebase main

Toute la branche de fonctionnalité (feature) sera ainsi déplacée sur la pointe de la branche (main), et tous les nouveaux commits seront intégrés à main. 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.

Rebasing feature branch into main

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é.

Interactive rebasing

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 main.

Pour lancer une session de rebase interactif, ajoutez l'option i à la commande git rebase :

git checkout feature
git rebase -i main

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

pick 33d5b7a Message for commit #1
pick 9480b3d Message for commit #2
pick 5c67e61 Message for commit #3

Cette liste détermine précisément quelle sera la structure de la branche après le rebase. En modifiant la commande pick et/ou en réorganisant les entrées, vous pouvez agencer l'historique de la branche comme bon vous semble. Par exemple, si le deuxième commit résout un petit problème dans le premier, vous pouvez les merger en un seul grâce à la commande fixup :

pick 33d5b7a Message for commit #1
fixup 9480b3d Message for commit #2
pick 5c67e61 Message for commit #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.

The golden rule of rebasing


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 main sur votre branche de fonctionnalité (feature) :

Rebasing the main branch

Le rebase déplace tous les commits de main sur la pointe de feature. Le problème, c'est que le changement n'a lieu que dans votre dépôt. Tous les autres développeurs travaillent toujours avec la branche principale (main) d'origine. Comme le rebase génère de nouveaux commits, Git va penser que l'historique de votre branche principale (main) diffère des autres.

La seule manière de synchroniser les deux branches (main) 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 ?

Ainsi, avant d'exécuter la commande git rebase, pensez à toujours vérifier si quelqu'un d'autre travaille sur la branche concernée. Si oui, ôtez tout de suite vos mains du clavier et réfléchissez à une manière moins radicale d'effectuer vos changements (p. ex., en utilisant la commande git revert). Dans le cas contraire, vous pouvez réécrire l'historique à votre guise.

Force-pushing

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

# Be very careful with this command! git push --force

La branche (main) 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é.

Workflow walkthrough


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é.

Dans tout workflow qui utilise la commande git rebase, la première chose à faire est de créer une branche pour chaque fonctionnalité. Ainsi, vous obtiendrez la structure adéquate pour une utilisation optimale du rebase :

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

Local cleanup

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 (main)) 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 main

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.

Incorporating upstream changes into a feature

Dans la section Présentation des concepts, nous avons vu comment une branche de fonctionnalité peut intégrer des changements en amont issus de main grâce à la commande git merge ou git rebase. Le merge est une solution sûre, qui préserve tout l'historique de votre dépôt, tandis que le rebase génère un historique linéaire en déplaçant votre branche de fonctionnalité sur la pointe de la branche principale (main).

Cette utilisation de la commande git rebase s'apparente à un nettoyage en local (qui peut être réalisé simultanément), à cela près que les commits en amont sont intégrés à partir de main.

Gardez à l'esprit que rien ne vous interdit d'effectuer un rebase sur une branche distante plutôt que sur main. 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.

Par exemple, si votre collègue Jean et vous avez ajouté des commits dans la branche feature, votre dépôt présentera la structure suivante une fois que vous aurez fait un fetch de la branche feature dans le dépôt de Jean :

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 main : soit vous mergez votre branche de fonctionnalité (feature) locale avec jean/feature, soit vous rebasez cette branche de fonctionnalité (feature) locale sur la pointe de jean/feature.

Comparaison du merge et du rebase sur une branche distante

Remarque : cette opération ne viole en rien la règle d'or du rebase, puisque seuls vos commits locaux sur feature sont déplacés. Rien d'autre ne change. C'est un peu comme si vous souhaitiez intégrer vos changements à ce que Jean a déjà réalisé. Dans la plupart des cas, cette opération sera plus intuitive qu'une synchronisation avec la branche distante au moyen d'un commit de merge.

Par défaut, la commande git pull fait un merge, mais vous pouvez la forcer à intégrer la branche distante à un rebase en y ajoutant l'option --rebase.

Reviewing a feature with a 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.

Integrating an approved feature

Une fois qu'une fonctionnalité a été approuvée par votre équipe, vous pouvez la rebaser sur la pointe de la branche principale (main) 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 (main), 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.

Integrating a feature into main with and without a 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 main
# [Clean up the history]
git checkout main
git merge temporary-branch

Résumé


À 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.

Par ailleurs, si vous souhaitez conserver l'historique complet de votre projet sans devoir réécrire vos commits publics, optez pour git merge. Les deux solutions se valent, mais, au moins, vous pouvez maintenant profiter des avantages de git rebase.


Partager cet article

Lectures recommandées

Ajoutez ces ressources à vos favoris pour en savoir plus sur les types d'équipes DevOps, ou pour les mises à jour continues de DevOps chez Atlassian.

Des personnes qui collaborent à l'aide d'un mur rempli d'outils

Le blog Bitbucket

Illustration DevOps

Parcours de formation DevOps

Démos Des démos avec des partenaires d'Atlassian

Fonctionnement de Bitbucket Cloud avec Atlassian Open DevOps

Inscrivez-vous à notre newsletter DevOps

Thank you for signing up