Git 🌙
Chapters â–Ÿ 2nd Edition

3.6 Les branches avec Git - Rebaser (Rebasing)

Rebaser (Rebasing)

Dans Git, il y a deux façons d’intĂ©grer les modifications d’une branche dans une autre : en fusionnant (merge) et en rebasant (rebase). Dans ce chapitre, vous apprendrez la signification de rebaser, comment le faire, pourquoi c’est un outil incroyable et dans quels cas il est dĂ©conseillĂ© de l’utiliser.

Les bases

Si vous revenez à un exemple précédent du chapitre Fusions (Merges), vous remarquerez que votre travail a divergé et que vous avez ajouté des commits sur deux branches différentes.

Historique divergeant simple
Figure 35. Historique divergeant simple

Comme nous l’avons dĂ©jĂ  expliquĂ©, le moyen le plus simple pour intĂ©grer ces branches est la fusion via la commande merge. Cette commande rĂ©alise une fusion Ă  trois branches entre les deux derniers instantanĂ©s (snapshots) de chaque branche (C3 et C4) et l’ancĂȘtre commun le plus rĂ©cent (C2), crĂ©ant un nouvel instantanĂ© (et un commit).

Fusion pour intégrer des travaux aux historiques divergeants
Figure 36. Fusion pour intégrer des travaux aux historiques divergeants

Cependant, il existe un autre moyen : vous pouvez prendre le patch de la modification introduite en C4 et le réappliquer sur C3. Dans Git, cette action est appelée "rebaser" (rebasing). Avec la commande rebase, vous pouvez prendre toutes les modifications qui ont été validées sur une branche et les rejouer sur une autre.

Dans cet exemple, vous lanceriez les commandes suivantes :

$ git checkout experiment
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: added staged command

Cela fonctionne en cherchant l’ancĂȘtre commun le plus rĂ©cent des deux branches (celle sur laquelle vous vous trouvez et celle sur laquelle vous rebasez), en rĂ©cupĂ©rant toutes les diffĂ©rences introduites par chaque commit de la branche courante, en les sauvant dans des fichiers temporaires, en rĂ©initialisant la branche courante sur le mĂȘme commit que la branche de destination et en appliquant finalement chaque modification dans le mĂȘme ordre.

Rebasage des modifications introduites par `C4` sur `C3`
Figure 37. Rebasage des modifications introduites par C4 sur C3

À ce moment, vous pouvez retourner sur la branche master et rĂ©aliser une fusion en avance rapide (fast-forward merge).

$ git checkout master
$ git merge experiment
Avance rapide de la branche `master`
Figure 38. Avance rapide de la branche master

À prĂ©sent, l’instantanĂ© pointĂ© par C4' est exactement le mĂȘme que celui pointĂ© par C5 dans l’exemple de fusion. Il n’y a pas de diffĂ©rence entre les rĂ©sultats des deux types d’intĂ©gration, mais rebaser rend l’historique plus clair. Si vous examinez le journal de la branche rebasĂ©e, elle est devenue linĂ©aire : toutes les modifications apparaissent en sĂ©rie mĂȘme si elles ont eu lieu en parallĂšle.

Vous aurez souvent Ă  faire cela pour vous assurer que vos commits s’appliquent proprement sur une branche distante — par exemple, sur un projet oĂč vous souhaitez contribuer mais que vous ne maintenez pas. Dans ce cas, vous rĂ©aliseriez votre travail dans une branche puis vous rebaseriez votre travail sur origin/master quand vous ĂȘtes prĂȘt Ă  soumettre vos patchs au projet principal. De cette maniĂšre, le mainteneur n’a pas Ă  rĂ©aliser de travail d’intĂ©gration — juste une avance rapide ou simplement une application propre.

Il faut noter que l’instantanĂ© pointĂ© par le commit final, qu’il soit le dernier des commits d’une opĂ©ration de rebasage ou le commit final issu d’une fusion, sont en fait le mĂȘme instantanĂ© — c’est juste que l’historique est diffĂ©rent. Rebaser rejoue les modifications d’une ligne de commits sur une autre dans l’ordre d’apparition, alors que la fusion joint et fusionne les deux tĂȘtes.

Rebases plus intéressants

Vous pouvez aussi faire rejouer votre rebasage sur autre chose qu’une branche. Prenez un historique tel que Un historique avec deux branches thĂ©matiques qui sortent l’une de l’autre par exemple. Vous avez crĂ©Ă© une branche thĂ©matique (server) pour ajouter des fonctionnalitĂ©s cĂŽtĂ© serveur Ă  votre projet et avez rĂ©alisĂ© un commit. Ensuite, vous avez crĂ©Ă© une branche pour ajouter des modifications cĂŽtĂ© client (client) et avez validĂ© plusieurs fois. Finalement, vous avez rebasculĂ© sur la branche server et avez rĂ©alisĂ© quelques commits supplĂ©mentaires.

Un historique avec deux branches thĂ©matiques qui sortent l’une de l’autre
Figure 39. Un historique avec deux branches thĂ©matiques qui sortent l’une de l’autre

Supposons que vous dĂ©cidez que vous souhaitez fusionner vos modifications du cĂŽtĂ© client dans votre ligne principale pour une publication (release) mais vous souhaitez retenir les modifications de la partie serveur jusqu’à ce qu’elles soient un peu mieux testĂ©es. Vous pouvez rĂ©cupĂ©rer les modifications du cĂŽtĂ© client qui ne sont pas sur le serveur (C8 et C9) et les rejouer sur la branche master en utilisant l’option --onto de git rebase :

$ git rebase --onto master server client

Cela signifie en substance "Extraire la branche client, dĂ©terminer les patchs depuis l’ancĂȘtre commun des branches client et server puis les rejouer sur master ". C’est assez complexe, mais le rĂ©sultat est assez impressionnant.

Rebaser deux branches thĂ©matiques l’une sur l’autre
Figure 40. Rebaser deux branches thĂ©matiques l’une sur l’autre

Maintenant, vous pouvez faire une avance rapide sur votre branche master (cf. Avance rapide sur votre branche master pour inclure les modifications de la branche client):

$ git checkout master
$ git merge client
Avance rapide sur votre branche `master` pour inclure les modifications de la branche client
Figure 41. Avance rapide sur votre branche master pour inclure les modifications de la branche client

Supposons que vous dĂ©cidiez de tirer (pull) votre branche server aussi. Vous pouvez rebaser la branche server sur la branche master sans avoir Ă  l’extraire avant en utilisant git rebase [branchedebase] [branchethematique] — qui extrait la branche thĂ©matique (dans notre cas, server) pour vous et la rejoue sur la branche de base (master) :

$ git rebase master server

Cette commande rejoue les modifications de server sur le sommet de la branche master, comme indiqué dans Rebasage de la branche server sur le sommet de la branche master.

Rebasage de la branche server sur le sommet de la branche `master`
Figure 42. Rebasage de la branche server sur le sommet de la branche master

Vous pouvez ensuite faire une avance rapide sur la branche de base (master) :

$ git checkout master
$ git merge server

Vous pouvez effacer les branches client et server une fois que tout le travail est intĂ©grĂ© et que vous n’en avez plus besoin, Ă©liminant tout l’historique de ce processus, comme visible sur Historique final des commits :

$ git branch -d client
$ git branch -d server
Historique final des _commits_
Figure 43. Historique final des commits

Les dangers du rebasage

Ah
 mais les joies de rebaser ne viennent pas sans leurs contreparties, qui peuvent ĂȘtre rĂ©sumĂ©es en une ligne :

Ne rebasez jamais des commits qui ont déjà été poussés sur un dépÎt public.

Si vous suivez ce conseil, tout ira bien. Sinon, de nombreuses personnes vont vous haïr et vous serez méprisé par vos amis et votre famille.

Quand vous rebasez des donnĂ©es, vous abandonnez les commits existants et vous en crĂ©ez de nouveaux qui sont similaires mais diffĂ©rents. Si vous poussez des commits quelque part, que d’autres les tirent et se basent dessus pour travailler, et qu’aprĂšs coup, vous rĂ©Ă©crivez ces commits Ă  l’aide de git rebase et les poussez Ă  nouveau, vos collaborateurs devront re-fusionner leur travail et les choses peuvent rapidement devenir trĂšs dĂ©sordonnĂ©es quand vous essaierez de tirer leur travail dans votre dĂ©pĂŽt.

Examinons un exemple expliquant comment rebaser un travail déjà publié sur un dépÎt public peut générer des gros problÚmes. Supposons que vous clonez un dépÎt depuis un serveur central et réalisez quelques travaux dessus. Votre historique de commits ressemble à ceci :

Cloner un dépÎt et baser du travail dessus.
Figure 44. Cloner un dépÎt et baser du travail dessus

À prĂ©sent, une autre personne travaille et inclut une fusion, puis elle pousse ce travail sur le serveur central. Vous le rĂ©cupĂ©rez et vous fusionnez la nouvelle branche distante dans votre copie, ce qui donne l’historique suivant :

Récupération de _commits_ et fusion dans votre copie.
Figure 45. Récupération de commits et fusion dans votre copie

Ensuite, la personne qui a poussĂ© le travail que vous venez de fusionner dĂ©cide de faire marche arriĂšre et de rebaser son travail. Elle lance un git push --force pour forcer l’écrasement de l’historique sur le serveur. Vous rĂ©cupĂ©rez alors les donnĂ©es du serveur, qui vous amĂšnent les nouveaux commits.

Quelqu’un pousse des _commits_ rebasĂ©s, en abandonnant les _commits_ sur lesquels vous avez fondĂ© votre travail.
Figure 46. Quelqu’un pousse des commits rebasĂ©s, en abandonnant les commits sur lesquels vous avez fondĂ© votre travail

Vous ĂȘtes dĂ©sormais tous les deux dans le pĂ©trin. Si vous faites un git pull, vous allez crĂ©er un commit de fusion incluant les deux historiques et votre dĂ©pĂŽt ressemblera Ă  ça :

Vous fusionnez le mĂȘme travail une nouvelle fois dans un nouveau _commit_ de fusion
Figure 47. Vous fusionnez le mĂȘme travail une nouvelle fois dans un nouveau commit de fusion

Si vous lancez git log lorsque votre historique ressemble Ă  ceci, vous verrez deux commits qui ont la mĂȘme date d’auteur et les mĂȘmes messages, ce qui est dĂ©routant. De plus, si vous poussez cet historique sur le serveur, vous rĂ©introduirez tous ces commits rebasĂ©s sur le serveur central, ce qui va encore plus dĂ©router les autres dĂ©veloppeurs. C’est plutĂŽt logique de prĂ©sumer que l’autre dĂ©veloppeur ne souhaite pas voir apparaĂźtre C4 et C6 dans l’historique. C’est la raison pour laquelle il avait effectuĂ© un rebasage initialement.

Rebaser quand vous rebasez

Si vous vous retrouvez effectivement dans une situation telle que celle-ci, Git dispose d’autres fonctions magiques qui peuvent vous aider. Si quelqu’un de votre Ă©quipe pousse de force des changements qui Ă©crasent des travaux sur lesquels vous vous ĂȘtes basĂ©s, votre dĂ©fi est de dĂ©terminer ce qui est Ă  vous et ce qui a Ă©tĂ© rĂ©Ă©crit.

Il se trouve qu’en plus de l’empreinte SHA du commit, Git calcule aussi une empreinte qui est uniquement basĂ©e sur le patch introduit avec le commit. Ceci est appelĂ© un "identifiant de patch" (patch-id).

Si vous tirez des travaux qui ont été réécrits et les rebasez au-dessus des nouveaux commits de votre collÚgue, Git peut souvent déterminer ceux qui sont uniquement les vÎtres et les réappliquer au sommet de votre nouvelle branche.

Par exemple, dans le scĂ©nario prĂ©cĂ©dent, si au lieu de fusionner quand nous Ă©tions Ă  l’étape Quelqu’un pousse des commits rebasĂ©s, en abandonnant les commits sur lesquels vous avez fondĂ© votre travail nous exĂ©cutons la commande git rebase teamone/master, Git va :

  • DĂ©terminer quels travaux sont uniques Ă  notre branche (C2, C3, C4, C6, C7)

  • DĂ©terminer ceux qui ne sont pas des commits de fusion (C2, C3, C4)

  • DĂ©terminer ceux qui n’ont pas Ă©tĂ© rĂ©Ă©crits dans la branche de destination (uniquement C2 et C3 puisque C4 est le mĂȘme patch que C4')

  • Appliquer ces commits au sommet de teamone/master

Ainsi, au lieu du rĂ©sultat que nous avons observĂ© au chapitre Vous fusionnez le mĂȘme travail une nouvelle fois dans un nouveau commit de fusion, nous aurions pu finir avec quelque chose qui ressemblerait davantage Ă  Rebaser au-dessus de travaux rebasĂ©s puis que l’on a poussĂ© en forçant.

Rebaser au-dessus de travaux rebasĂ©s puis que l’on a poussĂ© en forçant
Figure 48. Rebaser au-dessus de travaux rebasĂ©s puis que l’on a poussĂ© en forçant

Cela fonctionne seulement si les commits C4 et C4' de votre collĂšgue correspondent presque exactement aux mĂȘmes modifications. Autrement, le rebasage ne sera pas capable de dĂ©terminer qu’il s’agit d’un doublon et va ajouter un autre patch similaire Ă  C4 (ce qui Ă©chouera probablement puisque les changements sont au moins partiellement dĂ©jĂ  prĂ©sents).

Vous pouvez Ă©galement simplifier tout cela en lançant un git pull --rebase au lieu d’un git pull normal. Vous pouvez encore le faire manuellement Ă  l’aide d’un git fetch suivi d’un git rebase team1/master dans le cas prĂ©sent.

Si vous utilisez git pull et voulez faire de --rebase le traitement par défaut, vous pouvez changer la valeur du paramÚtre de configuration pull.rebase par git config --global pull.rebase true.

Si vous considĂ©rez le fait de rebaser comme un moyen de nettoyer et rĂ©arranger des commits avant de les pousser et si vous vous en tenez Ă  ne rebaser que des commits qui n’ont jamais Ă©tĂ© publiĂ©s, tout ira bien. Si vous tentez de rebaser des commits dĂ©jĂ  publiĂ©s sur lesquels les gens ont dĂ©jĂ  basĂ© leur travail, vous allez au devant de gros problĂšmes et votre Ă©quipe vous en tiendra rigueur.

Si vous ou l’un de vos collĂšgues y trouve cependant une quelconque nĂ©cessitĂ©, assurez-vous que tout le monde sache lancer un git pull --rebase pour essayer de rendre les choses un peu plus faciles.

Rebaser ou Fusionner

Maintenant que vous avez vu concrĂštement ce que signifient rebaser et fusionner, vous devez vous demander ce qu’il est prĂ©fĂ©rable d’utiliser. Avant de pouvoir rĂ©pondre Ă  cela, revenons quelque peu en arriĂšre et parlons un peu de ce que signifie un historique.

On peut voir l’historique des commits de votre dĂ©pĂŽt comme un enregistrement de ce qu’il s’est rĂ©ellement passĂ©. Il s’agit d’un document historique qui a une valeur en tant que tel et ne doit pas ĂȘtre altĂ©rĂ©. Sous cet angle, modifier l’historique des commits est presque blasphĂ©matoire puisque vous mentez sur ce qu’il s’est rĂ©ellement passĂ©. Dans ce cas, que faire dans le cas d’une sĂ©rie de commits de fusions dĂ©sordonnĂ©s ? Cela reflĂšte ce qu’il s’est passĂ© et le dĂ©pĂŽt devrait le conserver pour la postĂ©ritĂ©.

Le point de vue inverse consiste Ă  considĂ©rer que l’historique des commits est le reflet de la façon dont votre projet a Ă©tĂ© construit. Vous ne publieriez jamais le premier brouillon d’un livre et le manuel de maintenance de votre projet mĂ©rite une rĂ©vision attentive. Ceci constitue le camp de ceux qui utilisent des outils tels que le rebasage et les branches filtrĂ©es pour raconter une histoire de la meilleure des maniĂšres pour les futurs lecteurs.

DĂ©sormais, nous espĂ©rons que vous comprenez qu’il n’est pas si simple de rĂ©pondre Ă  la question portant sur le meilleur outil entre fusion et rebasage. Git est un outil puissant et vous permet beaucoup de manipulations sur et avec votre historique mais chaque Ă©quipe et chaque projet sont diffĂ©rents. Maintenant que vous savez comment fonctionnent ces deux outils, c’est Ă  vous de dĂ©cider lequel correspond le mieux Ă  votre situation en particulier.

De maniĂšre gĂ©nĂ©rale, la maniĂšre de profiter au mieux des deux mondes consiste Ă  rebaser des modifications locales que vous avez effectuĂ©es mais qui n’ont pas encore Ă©tĂ© partagĂ©es avant de les pousser de maniĂšre Ă  obtenir un historique propre mais sans jamais rebaser quoi que ce soit que vous ayez dĂ©jĂ  poussĂ© quelque part.

scroll-to-top