web-dev-qa-db-fra.com

Mon bureau veut que la fusion de succursales à l'infini soit une politique; quelles autres options avons-nous?

Mon bureau essaie de comprendre comment nous gérons les divisions et les fusions de succursales, et nous avons rencontré un gros problème.

Notre problème est avec les branches latérales à long terme - le genre où vous avez quelques personnes travaillant une branche latérale qui se sépare du maître, nous développons pendant quelques mois, et lorsque nous atteignons un jalon, nous synchronisons les deux.

Maintenant, à mon humble avis, la façon naturelle de gérer cela est de réduire la branche latérale en un seul commit. master continue de progresser; comme il se doit - nous ne versons pas rétroactivement des mois de développement parallèle dans l'histoire de master. Et si quelqu'un a besoin d'une meilleure résolution pour l'histoire de la branche secondaire, eh bien, bien sûr, tout est toujours là - ce n'est tout simplement pas dans master, c'est dans la branche latérale.

Voici le problème: je travaille exclusivement avec la ligne de commande, mais le reste de mon équipe utilise GUIS. Et j'ai découvert que le GUIS n'a pas une option raisonnable pour afficher l'historique des autres branches. Donc, si vous atteignez un commit squash, en disant "ce développement écrasé de la branche XYZ", c'est énorme douleur d'aller voir ce qu'il y a dedans XYZ.

Sur SourceTree, pour autant que je puisse trouver, c'est un énorme casse-tête: si vous êtes sur master et que vous voulez voir l'historique de master+devFeature, vous devez soit vérifier master+devFeature out (en touchant chaque fichier différent), ou faites défiler un journal affichant [~ # ~] tout [~ # ~] votre référentiel branches en parallèle jusqu'à ce que vous trouviez le bon endroit. Et bonne chance pour savoir où vous en êtes.

Mes coéquipiers, à juste titre, ne veulent pas que l'historique de développement soit aussi inaccessible. Ils veulent donc que ces grandes et longues branches de développement soient fusionnées, toujours avec un commit de fusion. Ils ne veulent pas d'historique qui n'est pas immédiatement accessible depuis la branche principale.

Je déteste cette idée; cela signifie un enchevêtrement sans fin et non navigable de l'histoire du développement parallèle. Mais je ne vois pas quelle alternative nous avons. Et je suis assez déconcerté; cela semble bloquer presque tout ce que je sais sur la bonne gestion des succursales, et ça va être une frustration constante si je ne trouve pas de solution.

Avons-nous une option ici en plus de fusionner constamment les branches latérales en master avec les merge-commits? Ou, y a-t-il une raison pour laquelle utiliser constamment les merge-commits n'est pas aussi mauvais que je le crains?

120
Standback

Même si j'utilise Git sur la ligne de commande - je dois être d'accord avec vos collègues. Il n'est pas judicieux d'écraser les modifications importantes en un seul commit. Vous perdez l'histoire de cette façon, pas seulement en la rendant moins visible.

Le point de contrôle de source est de suivre l'historique de tous les changements. Quand est-ce que ça a changé pourquoi? À cette fin, chaque commit contient des pointeurs vers les commit parents, un diff et des métadonnées comme un message de commit. Chaque commit décrit l'état du code source et l'historique complet de toutes les modifications qui ont conduit à cet état. Le garbage collector peut supprimer les validations qui ne sont pas accessibles.

Des actions telles que le rebasage, la sélection des cerises ou l'écrasement de l'historique de suppression ou de réécriture En particulier, les validations résultantes ne font plus référence aux validations d'origine. Considère ceci:

  • Vous écrasez certaines validations et notez dans le message de validation que l'historique écrasé est disponible dans la validation d'origine abcd123.
  • Vous supprimez[1] toutes les branches ou balises qui incluent abcd123 car elles sont fusionnées.
  • Vous laissez le garbage collector fonctionner.

[1]: Certains serveurs Git permettent de protéger les branches contre toute suppression accidentelle, mais je doute que vous souhaitiez conserver toutes vos branches de fonctionnalités pour l'éternité.

Maintenant, vous ne pouvez plus rechercher ce commit - il n'existe tout simplement pas.

Référencer un nom de branche dans un message de validation est encore pire, car les noms de branche sont locaux dans un référentiel. Quel est master+devFeature dans votre caisse locale pourrait être doodlediduh dans la mienne. Les branches déplacent simplement des étiquettes qui pointent vers un objet de validation.

De toutes les techniques de réécriture d'historique, le rebasage est le plus bénin car il duplique les validations complètes avec tout leur historique et remplace simplement une validation parent.

Le fait que l'historique master inclut l'historique complet de toutes les branches qui y ont été fusionnées est une bonne chose, car cela représente la réalité.[2] S'il y avait un développement parallèle, cela devrait être visible dans le journal.

[2]: Pour cette raison, je préfère également les validations de fusion explicites à l'histoire linéarisée mais finalement fausse résultant du rebasage.

Sur la ligne de commande, git log s'efforce de simplifier l'historique affiché et de conserver tous les commits affichés pertinents. Vous pouvez modifier la simplification de l'historique en fonction de vos besoins. Vous pourriez être tenté d'écrire votre propre outil de journalisation git qui parcourt le graphique de validation, mais il est généralement impossible de répondre "cette validation a-t-elle été initialement commise sur telle ou telle branche?". Le premier parent d'un commit de fusion est le précédent HEAD, c'est-à-dire le commit dans la branche dans laquelle vous fusionnez. Mais cela suppose que vous n'avez pas effectué de fusion inverse du maître dans la branche de fonctionnalité, puis que le maître a avancé rapidement vers la fusion.

La meilleure solution aux branches à long terme que j'ai rencontrées est d'empêcher les branches qui ne sont fusionnées qu'après quelques mois. La fusion est plus facile lorsque les modifications sont récentes et minimes. Idéalement, vous fusionnerez au moins une fois par semaine. L'intégration continue (comme dans Extreme Programming, pas comme dans "configurons un serveur Jenkins"), suggère même plusieurs fusions par jour, c'est-à-dire pour ne pas maintenir des branches de fonctionnalités distinctes mais partager une branche de développement en équipe. La fusion avant qu'une fonctionnalité ne soit QA nécessite que la fonctionnalité soit cachée derrière un indicateur de fonctionnalité.

En contrepartie, une intégration fréquente permet de repérer les problèmes potentiels beaucoup plus tôt et aide à conserver une architecture cohérente: des changements de grande envergure sont possibles car ces changements sont rapidement inclus dans toutes les branches. Si un changement casse un code, il ne cassera que quelques jours de travail, pas quelques mois.

La réécriture de l'historique peut avoir du sens pour des projets vraiment énormes lorsqu'il y a plusieurs millions de lignes de code et des centaines ou des milliers de développeurs actifs. On peut se demander pourquoi un projet aussi important devrait être un seul dépôt git au lieu d'être divisé en bibliothèques distinctes, mais à cette échelle, il est plus pratique si le dépôt central ne contient que des "versions" des composants individuels. Par exemple. le noyau Linux utilise l'écrasement pour garder l'historique principal gérable. Certains projets open source nécessitent l'envoi de correctifs par e-mail, au lieu d'une fusion au niveau git.

245
amon

J'aime réponse d'Amon , mais je sentais qu'une petite partie avait besoin de beaucoup plus d'accentuation: Vous pouvez facilement simplifier l'historique tout en consultant les journaux pour répondre à vos besoins, mais d'autres ne peuvent pas ajouter historique tout en consultant les journaux pour répondre à leurs besoins. C'est pourquoi il est préférable de conserver l'historique tel qu'il s'est produit.

Voici un exemple tiré de l'un de nos référentiels. Nous utilisons un modèle de pull-request, donc la fonction every ressemble à vos branches de longue durée dans l'histoire, même si elles ne fonctionnent généralement qu'une semaine ou moins. Les développeurs individuels choisissent parfois d'écraser leur histoire avant de fusionner, mais nous nous associons souvent à des fonctionnalités, c'est donc relativement inhabituel. Voici les premiers commits dans gitk, le gui fourni avec git:

standard gitk view

Oui, un peu enchevêtré, mais nous l'aimons aussi parce que nous pouvons voir précisément qui a eu quels changements à quel moment. Il reflète fidèlement notre historique de développement. Si nous voulons voir une vue de niveau supérieur, une fusion de demande de tirage à la fois, nous pouvons regarder la vue suivante, qui est équivalente à git log --first-parent commande:

gitk view with --first-parent

git log propose de nombreuses autres options conçues pour vous donner précisément les vues souhaitées. gitk peut prendre n'importe quel arbitraire git log argument pour construire une vue graphique. Je suis sûr que d'autres interfaces graphiques ont des capacités similaires. Lisez la documentation et apprenez à l'utiliser correctement, plutôt que d'appliquer vos préférences git log vue sur tout le monde au moment de la fusion.

110
Karl Bielefeldt

Notre problème est avec les branches latérales à long terme - le genre où vous avez quelques personnes travaillant une branche latérale qui se sépare du maître, nous développons pendant quelques mois, et lorsque nous atteignons un jalon, nous synchronisons les deux.

Ma première pensée est - ne faites même pas cela à moins que cela ne soit absolument nécessaire. Vos fusions doivent parfois être difficiles. Gardez les succursales indépendantes et de courte durée possible. C'est un signe que vous devez diviser vos histoires en morceaux d'implémentation plus petits.

Dans le cas où vous devriez le faire, il est possible de fusionner dans git avec l'option --no-ff afin que les historiques soient maintenus distincts sur leur propre branche. Les validations apparaîtront toujours dans l'historique fusionné, mais peuvent également être vues séparément sur la branche de fonctionnalité afin qu'au moins il soit possible de déterminer de quelle ligne de développement elles faisaient partie.

Je dois admettre que lorsque j'ai commencé à utiliser git, j'ai trouvé un peu étrange que la validation de la branche apparaisse dans la même histoire que la branche principale après la fusion. C'était un peu déconcertant car il ne semblait pas que ces commits appartenaient à cette histoire. Mais dans la pratique, ce n'est pas quelque chose de vraiment pénible du tout, si l'on considère que la branche d'intégration n'est que cela - tout son but est de combiner les branches de fonctionnalité. Dans notre équipe, nous n'effectuons pas de squash et nous effectuons fréquemment des commits de fusion. Nous utilisons --no-ff tout le temps pour nous assurer qu'il est facile de voir l'historique exact de n'importe quelle fonctionnalité si nous voulons l'étudier.

34
Brad Thomas

Écraser une branche latérale à long terme vous ferait perdre beaucoup d'informations.

Ce que je ferais est essayez de rebaser le master en sidebranch à long terme avant de fusionner le sidebranch en master. De cette façon, vous conservez chaque commit dans master, tout en rendant l'historique des commit linéaire et plus facile à comprendre.

Si je ne pouvais pas le faire facilement à chaque commit, je le laisserais non linéaire, afin de garder le contexte de développement clair. À mon avis, si j'ai une fusion problématique lors du rebase du maître dans la branche latérale, cela signifie que la non-linéarité avait une signification réelle. Cela signifie qu'il sera plus facile de comprendre ce qui s'est passé au cas où j'aurais besoin de creuser dans l'histoire. J'ai également l'avantage immédiat de ne pas avoir à refaire.

10
Pierre.Sassoulas

Permettez-moi de répondre à vos points directement et clairement:

Notre problème est avec les branches latérales à long terme - le genre où vous avez quelques personnes travaillant une branche latérale qui se sépare du maître, nous développons pendant quelques mois, et lorsque nous atteignons un jalon, nous synchronisons les deux.

Vous ne voulez généralement pas laisser vos succursales non synchronisées pendant des mois.

Votre branche de fonctionnalité a dérivé de quelque chose en fonction de votre flux de travail; appelons-le simplement master par souci de simplicité. Maintenant, chaque fois que vous vous engagez à maîtriser, vous pouvez et devez git checkout long_running_feature ; git rebase master. Cela signifie que vos branches sont, par conception, toujours synchronisées.

git rebase est également la bonne chose à faire ici. Ce n'est pas un hack ou quelque chose de bizarre ou dangereux, mais complètement naturel. Vous perdez un bit d'information, qui est "l'anniversaire" de la branche de fonctionnalité, mais c'est tout. Si quelqu'un trouve cela important, cela pourrait être fourni en l'enregistrant ailleurs (dans votre système de ticket, ou, si le besoin est grand, dans un git tag...).

Maintenant, à mon humble avis, la façon naturelle de gérer cela est de réduire la branche latérale en un seul commit.

Non, vous ne voulez absolument pas cela, vous voulez un commit de fusion. Un commit de fusion est également un "single commit". Il n'insère pas, d'une manière ou d'une autre, toutes les validations de branche individuelles "dans" master. Il s'agit d'un commit unique avec deux parents - la tête master et la tête de branche au moment de la fusion.

Assurez-vous de spécifier le --no-ff option, bien sûr; fusion sans --no-ff devrait, dans votre scénario, être strictement interdit. Malheureusement, --no-ff n'est pas la valeur par défaut; mais je crois qu'il y a une option que vous pouvez définir qui le fait. Voir git help merge pour quoi --no-ff le fait (en bref: il active le comportement que j'ai décrit dans le paragraphe précédent), c'est crucial.

nous ne jetons pas rétroactivement des mois de développement parallèle dans l'histoire du maître.

Absolument pas - vous ne jetez jamais quelque chose "dans l'histoire" d'une branche, surtout pas avec un commit de fusion.

Et si quelqu'un a besoin d'une meilleure résolution pour l'histoire de la branche secondaire, eh bien, bien sûr, tout est toujours là - ce n'est tout simplement pas en maître, c'est dans la branche latérale.

Avec un commit de fusion, il est toujours là. Pas en maître, mais dans la branche latérale, clairement visible comme l'un des parents du commit de fusion, et conservé pour l'éternité, comme il se doit.

Tu vois ce que j'ai fait? Toutes les choses que vous décrivez pour votre commit squash sont juste là avec la fusion --no-ff commit.

Voici le problème: je travaille exclusivement avec la ligne de commande, mais le reste de mon équipe utilise GUIS.

(Remarque latérale: je travaille presque exclusivement avec la ligne de commande également (eh bien, c'est un mensonge, j'utilise habituellement emacs magit, mais c'est une autre histoire - si je ne suis pas dans un endroit pratique avec ma configuration emacs individuelle, je préfère la commande Mais faites-vous une faveur et essayez au moins git gui une fois que. C'est donc beaucoup plus efficace pour choisir des lignes, des mecs, etc. pour ajouter/annuler des ajouts.)

Et j'ai découvert que le GUIS n'a pas une option raisonnable pour afficher l'historique des autres branches.

C'est parce que ce que vous essayez de faire est totalement contraire à l'esprit de git. git se construit à partir du noyau sur un "graphique acyclique dirigé", ce qui signifie que beaucoup d'informations se trouvent dans la relation parent-enfant des commits. Et, pour les fusions, cela signifie que la vraie fusion s'engage avec deux parents et un enfant. Les interfaces graphiques de vos collègues seront très bien dès que vous utilisez no-ff merge valide.

Donc, si vous atteignez un commit squash, en disant "ce développement écrasé de la branche XYZ", c'est une énorme douleur d'aller voir ce qu'il y a dans XYZ.

Oui, mais ce n'est pas un problème de GUI, mais de commit squash. L'utilisation d'un squash signifie que vous quittez la tête de la branche de fonction pendant et créez un tout nouveau commit dans master. Cela brise la structure à deux niveaux, créant un gros gâchis.

Ils veulent donc que ces grandes et longues branches de développement soient fusionnées, toujours avec un commit de fusion.

Et ils ont absolument raison. Mais ils ne sont pas "fusionnés in", ils sont juste fusionnés. Une fusion est une chose vraiment équilibrée, elle n'a pas de côté préféré qui est fusionné "dans" l'autre (git checkout A ; git merge B est exactement le même que git checkout B ; git merge A sauf pour des différences visuelles mineures comme les branches échangées dans git log etc.).

Ils ne veulent pas d'historique qui n'est pas immédiatement accessible depuis la branche principale.

Ce qui est tout à fait correct. À une époque où il n'y a pas de fonctionnalités non fusionnées, vous auriez une seule branche master avec un historique riche encapsulant toutes les lignes de validation des fonctionnalités qui existent, en revenant à la git init commit depuis le début des temps (notez que j'ai spécifiquement évité d'utiliser le terme "branches" dans la dernière partie de ce paragraphe parce que l'historique à ce moment-là n'est plus "branches", bien que le graphe de commit soit assez ramifié) .

Je déteste cette idée;

Ensuite, vous ressentez un peu de douleur, car vous travaillez contre l'outil que vous utilisez. L'approche git est très élégante et puissante, en particulier dans le domaine des branchements/fusions; si vous le faites correctement (comme mentionné ci-dessus, en particulier avec --no-ff) c'est par bonds supérieur à d'autres approches (par exemple, le désordre de Subversion d'avoir des structures de répertoires parallèles pour les branches).

cela signifie un enchevêtrement sans fin et non navigable de l'histoire du développement parallèle.

Sans fin, parallèle - oui.

Non navigable, enchevêtrement - non.

Mais je ne vois pas quelle alternative nous avons.

Pourquoi ne pas travailler comme l'inventeur de git, vos collègues et le reste du monde, tous les jours?

Avons-nous une option ici en plus de fusionner constamment les branches latérales en master avec les merge-commits? Ou, y a-t-il une raison pour laquelle l'utilisation constante de fusion-commits n'est pas aussi mauvaise que je le crains?

Aucune autre option; pas aussi mauvais.

10
AnoE

Personnellement, je préfère faire mon développement dans un fork, puis extraire les demandes de fusion dans le référentiel principal.

Cela signifie que si je veux rebaser mes modifications par-dessus Master ou écraser certains commits WIP, je peux totalement le faire. Ou je peux simplement demander que toute mon histoire soit également fusionnée.

Ce que je comme faire, c'est faire mon développement sur une branche mais souvent rebaser contre master/dev. De cette façon, j'obtiens les modifications les plus récentes de master sans avoir un tas de validations de fusion dans la branche my, ou sans avoir à gérer un tas de conflits de fusion quand il est temps de fusionner avec master.

Pour répondre explicitement à votre question:

Avons-nous une option ici en plus de fusionner constamment les branches latérales en master avec les merge-commits?

Oui - vous pouvez les fusionner une fois par branche (lorsque la fonctionnalité ou le correctif est "terminé") ou si vous n'aimez pas que la fusion soit validée dans votre historique, vous pouvez simplement effectuer une fusion rapide sur master après avoir effectué un rebase final.

1
Wayne Werner