Workflows d'équipe Git : Merge ou rebase ?

Nicola Paolucci
Nicola Paolucci
Retour à la liste

La question est simple : pour une équipe de développement qui utilise Git et la création de branche de fonctionnalité, quel est le meilleur moyen d'intégrer le travail fini dans la ligne de développement principale ? Ce débat est sulfureux. Les opinions sont bien tranchées et les conversations sont rarement constructives (pour d'autres exemples de débats sulfureux, voir : Internet).

Devez-vous adopter une stratégie de rebase où l'historique de dépôt reste plat et propre ? Ou une stratégie de merge, qui garantit plus de traçabilité, au détriment de la lisibilité et de la clarté (en allant jusqu'à interdire les fast-forward merges) ?

La question fait débat

Le sujet est un peu controversé, mais peut-être pas autant que les anciennes guerres saintes entre vim et Emacs ou entre Linux et BSD. Quoi qu'il en soit, les deux camps font clairement entendre leur voix.

Mon pouls empirique sur all-things-git – scientifique, je sais ! – est la suivante : l'approche de merge permanent a une plus grande notoriété. Mais le rebase permanent est également assez populaire en ligne. Vous trouverez quelques exemples ici :

Pour être honnête, la répartition en deux camps (rebase permanent et merge permanent) peut être perturbante, car le rebase en tant que nettoyage local est différent d'un rebase en tant que stratégie d'équipe.

De plus : le rebase en tant que nettoyage est génial dans le cycle de vie du code

Le rebase en tant que stratégie d'équipe est différent du rebase en tant que nettoyage. Le rebase en tant que nettoyage est un élément sain du cycle de vie de la programmation Git. Permettez-moi de détailler quelques exemples de scénarios qui montrent quand il est raisonnable et efficace d'utiliser le rebasage (et quand ce ne l'est pas) :

  • Vous développez en local. Vous n'avez partagé votre travail avec personne. À ce stade, le rebase est préférable au merge pour garder votre historique propre. Si vous disposez de votre fork personnel du dépôt et que vous ne le partagez pas avec d'autres développeurs, vous pouvez sans problème faire un rebase, même après un push vers votre fork.
  • Votre code est prêt pour la revue. Vous créez une pull request, d'autres personnes révisent votre travail et peuvent le fetcher dans leur fork pour une revue locale. À ce stade, vous ne devez pas rebaser votre travail. Vous devez créer des commits de « reprise » et mettre à jour votre branche de fonctionnalité. Cela favorise la traçabilité de la pull request et empêche tout blocage accidentel de l'historique.
  • La revue a été effectuée et est prête à être intégrée à la branche cible. Félicitations ! Vous êtes sur le point de supprimer votre branche de fonctionnalité. Étant donné que d'autres développeurs ne vont pas fetcher et merger ces changements à partir de ce point, vous avez une opportunité de nettoyer l'historique. À ce stade, vous pouvez réécrire l'historique et faire un fold des commits originaux et de ces maudits commits « pr rework » et « merge » dans un petit ensemble de commits très ciblés. La création d'un merge explicite pour ces commits est facultative, mais intéressante. Cela enregistre le moment où la fonctionnalité devient master.

Cette question éclaircie, nous pouvons désormais parler des stratégies. J'essaierai de garder une approche équilibrée à ce sujet et j'expliquerai comment ce problème est abordé dans Atlassian.

Stratégie d'équipe Rebase : Définition, avantages et inconvénients

De toute évidence, une généralisation est difficile, étant donné que chaque équipe est différente, mais nous devons bien commencer quelque part. Envisagez cette stratégie comme exemple possible : Lorsque le développement d'une branche de fonctionnalité est terminé, effectuez un rebase/squash de tout le travail jusqu'au nombre minimal de commits cohérents et évitez de créer un commit de merge, soit en vous assurant que les changements font l'objet d'un fast-forward merge ou simplement en exécutant la commande cherry-pick sur les commits pour les placer dans la branche cible.

Lorsque le travail est toujours en cours et qu'une branche de fonctionnalité doit être mise à jour avec la branche upstream cible, utilisez rebase (au lieu de pull ou merge) afin d'éviter de polluer l'historique avec des merges parasites.

Avantages :

  • L'historique du code reste plat et lisible. Des messages de commit propres et clairs font autant partie de la documentation de votre base de code que les commentaires du code, les commentaires de votre outil de suivi de tickets, etc. Pour cette raison, il est important de ne pas polluer l'historique avec 31 commits d'une seule ligne qui s'annulent partiellement les uns les autres pour une seule correction de fonctionnalité ou de bug. Parcourir l'historique pour déterminer quand un bug ou une fonctionnalité a été introduit(e), et pourquoi, sera difficile dans une situation pareille.
  • La gestion d'un commit unique est facile (par exemple, son revert).

Inconvénients :

  • Compresser la fonctionnalité en une poignée de commits peut masquer le contexte, sauf si vous conservez la branche historique avec tout l'historique de développement.
  • Le rebase ne fait pas bon ménage avec les pull requests, parce que vous ne pouvez pas voir les changements mineurs apportés s'ils sont rebasés (entre parenthèses, le consensus au sein de l'équipe de développement Bitbucket Server est de ne jamais effectuer de rebase durant une pull request).
  • Le rebase peut être dangereux ! La réécriture de l'historique des branches partagées peut bloquer le travail d'équipe. Ce problème peut être atténué en effectuant le rebase/squash sur une copie d'une branche de fonctionnalité, mais le rebase nécessite des compétences et de l'attention.
  • La tâche se complique : l'utilisation du rebase pour garantir que votre branche de fonctionnalité est à jour nécessite que vous résolviez encore et toujours les mêmes conflits. Oui, vous pouvez parfois réutiliser les résolutions enregistrées (rerere), mais les merges l'emportent : résolvez simplement les conflits, et le tour est joué.
  • Un autre effet secondaire du rebase avec des branches distantes est que vous devez faire un force-push à un moment donné. Le principal problème rencontré chez Atlassian est que les gens font un force-push (ce qui est bien), mais ne définissent pas git push.default. Cela entraîne des mises à jour de toutes les branches présentant le même nom, qu'elles soient locales ou distantes. Cette situation est difficile à gérer.

REMARQUE : lorsque l'historique est réécrit dans une branche partagée par plusieurs développeurs, des blocages peuvent survenir.

Stratégie d'équipe Merge : définitions, avantages et inconvénients

Les stratégies Toujours faire un merge fonctionnent plutôt comme ceci : lorsqu'une branche de fonctionnalité est terminée, mergez-la dans votre branche cible (master ou develop ou next).

Assurez-vous que le merge est explicite avec --no-ff, qui force Git à enregistrer un commit de merge dans tous les cas, même si les changements pouvaient être rejoués en haut de la branche cible.

Avantages :

  • Traçabilité : les informations sur l'existence historique d'une branche de fonctionnalité peuvent être conservées, et tous les commits de la fonctionnalité sont regroupés.

Inconvénients :

  • L'historique peut être extrêmement pollué par de nombreux commits de merge, et les diagrammes visuels de votre dépôt peuvent présenter des lignes de branche très disparates qui n'ajoutent pas trop d'informations, voire déroutent les développeurs. (Maintenant et en toute honnêteté, les doutes sont facilement dissipés en sachant comment parcourir votre historique. Le truc : utiliser par exemple git log --first-parent pour clarifier la situation.)
Arborescences de merge
  • Déboguer à l'aide de la commande git bisect peut s'avérer plus difficile, étant donné les commits de merge.

Décisions, décisions, décisions : qu'est-ce que vous importe le plus ?

Quelle est la meilleure solution ? Que recommandent les experts ?

Si votre équipe et vous-même méconnaissez ou ne comprenez pas les tenants et les aboutissants du rebase, vous ne devriez pas l'utiliser. Dans ce contexte, l'option toujours faire un merge est la plus sûre.

Si votre équipe et vous-même êtes habitués aux deux options, la décision repose essentiellement autour de cette question : préférez-vous un historique propre et linéaire ou la traçabilité de vos branches ? Dans le premier cas, utilisez une stratégie de rebase, dans le deuxième, utilisez une stratégie de merge.

Notez qu'une stratégie de rebase est assortie de petites contre-indications et qu'elle nécessite plus d'efforts.

Chez Atlassian

La stratégie au sein de l'équipe Bitbucket Server d'Atlassian implique de toujours merger les branches de fonctionnalité et nécessite que les branches soient mergées via une pull request pour la revue de la qualité et du code. Mais l'équipe n'est pas trop stricte en matière de fast-forward.

Conclusions et remerciements

Cet article est le résultat de la confluence d'échanges intéressants (jeu de mots voulu !) sur le sujet avec l'équipe Bitbucket Server.

Nous espérons que ce billet dissipera les doutes à ce sujet et vous permettra d'adopter une approche adaptée à votre équipe. Suivez-moi (@durdn) et l'incroyable équipe @AtlDevtools pour plus d'infos passionnantes sur Git.

Prêt à découvrir Git ?

Essayez ce tutoriel interactif.

Démarrez maintenant