Mon bureau essaie de comprendre comment nous gérons les scissions et les fusions de succursales, et nous avons un gros problème.
Notre problème concerne les branches secondaires à long terme - du genre où vous avez quelques personnes qui travaillent dans une branche qui se sépare de master, 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 d’écraser la branche latérale en un seul commit. master
continue d'avancer; comme il se doit - nous ne renversons pas de manière rétroactive des mois de développement parallèle dans l master
'histoire de. Et si quelqu'un a besoin d'une meilleure résolution de l'histoire de la branche secondaire, eh bien, tout est toujours là: il n'est tout simplement pas master
inclus, 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 les GUIS n’ont pas l’option raisonnable d’ afficher l’ historique des autres branches. Donc, si vous parvenez à un engagement au squash en disant "ce développement est écrasé XYZ
", c'est très pénible d'aller voir ce qu'il y a dedans XYZ
.
Pour SourceTree, dans la mesure où je suis capable de le trouver, c’est un mal de tête énorme: si vous êtes sur master
et que vous voulez voir l’historique master+devFeature
, vous devez soit vérifier master+devFeature
(en touchant chaque fichier qui est différent), soit faites défiler un journal affichant TOUTES les branches de votre référentiel en parallèle jusqu'à ce que vous trouviez le bon emplacement. Et bonne chance pour savoir où vous êtes.
Mes coéquipiers, à juste titre, ne veulent pas avoir une histoire de développement aussi inaccessible. Ils veulent donc que ces grandes et longues branches axées sur le développement soient fusionnées, toujours avec un engagement de fusion. Ils ne veulent pas d’historique qui ne soit pas immédiatement accessible depuis la branche principale.
Je déteste cette idée; cela signifie un enchevêtrement sans fin et non navigable dans l’histoire du développement parallèle. Mais je ne vois pas quelle alternative nous avons. Et je suis assez dérouté; cela semble bloquer presque tout ce que je sais sur la bonne gestion des succursales, et ma frustration sera constante si je ne trouve pas de solution.
Avons-nous une option ici en dehors de la fusion constante de branches secondaires en maître avec des commits de fusion? Ou bien, y a-t-il une raison pour laquelle l'utilisation constante de fusions par fusion n'est pas aussi mauvaise que je le crains?
la source
git log master+bugfixDec10th
à être le point de rupture: - /merge --no-ff
ce que tu veux? Surmaster
lui-même, vous avez un commit qui décrit ce qui a changé dans la branche, mais tous les commits existent toujours et sont apparentés à HEAD.Réponses:
Même si j'utilise Git sur la ligne de commande, je suis d'accord avec vos collègues. Il n'est pas judicieux d'écraser de gros changements dans un seul commit. Vous perdez ainsi l’histoire et ne la rendez pas moins visible.
Le contrôle du code source consiste à suivre l'historique de toutes les modifications. Quand a changé quoi pourquoi? À cette fin, chaque commit contient des pointeurs sur les commits 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 ayant conduit à cet état. Le ramasse-miettes peut supprimer des validations inaccessibles.
Des actions telles que le rebasement, le tri sélectif ou l'écrasement suppriment ou réécrivent l'historique. En particulier, les commits résultants ne font plus référence aux commits d'origine. Considère ceci:
[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. Ce qui est
master+devFeature
dans votre caisse locale pourrait êtredoodlediduh
dans la mienne. Les branches ne font que déplacer des étiquettes qui pointent sur un objet de validation.Parmi toutes les techniques de réécriture d’historique, le rebasement est le plus inoffensif, car il duplique les validations complètes avec tout leur historique et ne fait que remplacer une validation parent.
Que l'
master
histoire comprenne l'histoire complète de toutes les branches qui 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 commits de fusion explicites sur l’historique linéarisé, mais finalement faux, résultant d’un changement de base.
Sur la ligne de commande,
git log
essayez de simplifier l'historique affiché et de garder 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 journal git qui parcourt le graphique de validation, mais il est généralement impossible de répondre «ce commit a-t-il été commis à l'origine sur telle ou telle branche?». Le premier parent d'une validation de fusion est la précédenteHEAD
, c'est-à-dire la validation de la branche dans laquelle vous fusionnez. Mais cela suppose que vous n'avez pas effectué de fusion inverse de maître dans la branche de fonctionnalité, puis de transfert rapide du maître vers la fusion.La meilleure solution que j'ai rencontrée pour les branches à long terme consiste à empêcher les branches fusionnées de fusionner 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, et non pas dans «configurons un serveur Jenkins»), suggère même plusieurs fusions par jour, c’est-à-dire non pour conserver des branches distinctes, mais pour partager une branche de développement en équipe. Pour effectuer une fusion avant une fonctionnalité, QA exige que celle-ci soit masquée derrière un indicateur de fonctionnalité.
En contrepartie, une intégration fréquente permet de détecter les problèmes potentiels beaucoup plus tôt et contribue à maintenir une architecture cohérente: des changements de grande portée sont possibles, car ces changements sont rapidement inclus dans toutes les branches. Si un changement casse un code, il ne durera que quelques jours de travail, pas quelques mois.
La réécriture de l'historique peut avoir un sens pour des projets vraiment énormes lorsqu'il existe plusieurs millions de lignes de code et des centaines ou des milliers de développeurs actifs. On peut se demander pourquoi un projet d'une telle envergure devrait être un référentiel Git unique au lieu d'être divisé en bibliothèques séparées, mais à cette échelle, il est plus pratique que le référentiel central ne contienne 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 courrier électronique, au lieu d’une fusion au niveau git.
la source
git bisect
, mais seulement de le faire arriver à un uber-commit de 10 000 lignes à partir d’une branche latérale.J'aime la réponse d’Amon , mais j’ai pensé qu’une petite partie méritait plus d’attention: vous pouvez facilement simplifier l’historique tout en consultant des journaux pour répondre à vos besoins, mais d’autres ne peuvent pas ajouter d’historique tout en consultant des journaux pour répondre à leurs besoins. C'est pourquoi il est préférable de conserver l'historique tel qu'il s'est passé.
Voici un exemple tiré de l'un de nos référentiels. Nous utilisons un modèle à demande tirée, de sorte que chaque fonctionnalité ressemble à vos branches longues de l'histoire, même si elles n'exécutent généralement qu'une semaine ou moins. Les développeurs individuels choisissent parfois d'écraser leur historique avant de fusionner, mais nous nous associons souvent à des fonctionnalités, ce qui est relativement inhabituel. Voici le top des derniers commits
gitk
, l'interface graphique fournie avec git:Oui, un peu enchevêtrement, mais nous aimons aussi parce que nous pouvons voir précisément qui a changé quoi à quelle heure. Cela reflète fidèlement notre histoire de développement. Si nous voulons voir une vue de niveau supérieur, fusion d'une demande d'extraction à la fois, nous pouvons examiner la vue suivante, qui équivaut à la
git log --first-parent
commande:git log
a beaucoup plus d'options conçues pour vous donner précisément les vues que vous voulez.gitk
peut prendre n'importe quelgit log
argument arbitraire 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 votregit log
vision préférée à tout le monde au moment de la fusion.la source
merge --no-ff
- n'utilisez pas de fusions à avance rapide, mais créez toujours un commit de fusion afin de--first-parent
pouvoir travailler avec quelque choseMa première pensée est la suivante: ne faites même pas cela à moins que cela ne soit absolument nécessaire. Vos fusions doivent parfois être difficiles. Gardez les branches indépendantes et aussi courtes que possible. C'est un signe que vous devez diviser vos histoires en plus petits morceaux de mise en œuvre.
Si vous devez le faire, il est possible de fusionner dans l'option git avec l'option --no-ff afin que les historiques restent distincts sur leur propre branche. Les commits apparaîtront toujours dans l'historique fusionné, mais pourront également être vus séparément dans la branche de la fonctionnalité afin qu'il soit au moins possible de déterminer la ligne de développement à laquelle ils appartenaient.
Je dois admettre que lorsque j'ai commencé à utiliser git, j'ai trouvé un peu étrange que les commits de branche apparaissent 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 vraiment pénible, si l’on considère que la branche d’intégration n’est que cela, c’est tout son objectif est de combiner les différentes branches. Dans notre équipe, nous n'écrasons pas et nous effectuons des commits de fusion fréquents. Nous utilisons --no-ff à tout moment pour s’assurer qu’il est facile de consulter l’historique exact de toute fonctionnalité si nous souhaitons l’examiner.
la source
git merge --no-ff
git merge --no-ff
. Si vous utilisez gitflow, cela devient la valeur par défaut.Permettez-moi de répondre à vos points directement et clairement:
Vous ne voulez généralement pas laisser vos branches 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
pour des raisons de simplicité. Maintenant, chaque fois que vous vous engagez à maîtriser, vous pouvez et devriezgit checkout long_running_feature ; git rebase master
. Cela signifie que vos branches sont, de par leur conception, toujours synchronisées.git rebase
C'est aussi la bonne chose à faire ici. Ce n'est pas un hack ou quelque chose d'étrange ou de dangereux, mais complètement naturel. Vous perdez une information, qui correspond à "l'anniversaire" de la branche de fonctionnalité, mais c'est tout. Si quelqu'un trouve cela important, vous pouvez le faire en le conservant ailleurs (dans votre système de billetterie ou, si le besoin se fait sentir, dans ungit tag
...).Non, vous ne voulez absolument pas cela, vous voulez un commit de fusion. Une validation de fusion est également une "validation unique". En quelque sorte, il n’insère pas tous les commits de branche individuels "dans" le maître. Il s’agit d’un simple commit avec deux parents - le
master
chef et le chef de succursale au moment de la fusion.Assurez-vous de spécifier l'
--no-ff
option, bien sûr; la fusion sans--no-ff
devrait, dans votre scénario, être strictement interdite. Malheureusement, ce--no-ff
n'est pas la valeur par défaut; mais je crois qu’il existe une option que vous pouvez définir pour le rendre tel. Voirgit help merge
pour quoi--no-ff
fait (en bref: il active le comportement que j'ai décrit dans le paragraphe précédent), il est crucial.Absolument pas - vous ne dormez jamais quelque chose "dans l'historique" d'une branche, surtout pas avec un commit de fusion.
Avec un commit de fusion, il est toujours là. Pas en maître, mais dans la branche latérale, clairement visible en tant que l’un des parents du groupe fusionné 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 exactement là avec le
--no-ff
commit de fusion .(Remarque: 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 individuelle emacs, je préfère la commande mais faites-vous plaisir et essayez au moins
git gui
une fois. C’est tellement plus efficace pour choisir des lignes, des mecs, etc. pour ajouter / annuler des ajouts.)C'est parce que ce que vous essayez de faire est totalement contraire à l'esprit de
git
.git
construit à partir du noyau d’un "graphe acyclique dirigé", ce qui signifie que beaucoup d’informations se trouvent dans la relation parent-enfant des commits. Et, pour les fusions, cela signifie de véritables commits de fusion avec deux parents et un enfant. Les interfaces graphiques de vos collègues iront très bien dès que vous utiliserez desno-ff
commits de fusion.Oui, mais ce n’est pas un problème de l’interface graphique, mais du commit squash. L'utilisation d'un squash signifie que vous laissez la tête de la branche fonctionnelle pendante et que vous créez un nouveau commit dans
master
. Cela casse la structure à deux niveaux, créant un gros désordre.Et ils ont absolument raison. Mais ils ne sont pas « fusionnés dans », ils sont tout simplement fusionnés. Une fusion est une chose vraiment équilibrée, elle n’a aucun côté préféré qui soit fusionné "dans" l’autre (
git checkout A ; git merge B
exactement la même chose quegit checkout B ; git merge A
pour les différences visuelles mineures telles que les branches échangées,git log
etc.).Ce qui est complètement correct. À une époque où il n’existait aucune fonctionnalité non fusionnée, une seule branche
master
avec une histoire riche encapsulait toutes les lignes de validation des fonctionnalités qui existaient, remontant à lagit init
validation depuis le début des temps (notez que j’ai évité précisément d’utiliser le terme " branches "dans la dernière partie de ce paragraphe car l'historique à ce moment-là n'est plus" des branches ", bien que le graphe de commit soit assez ramifié).Ensuite, vous souffrez un peu, car vous travaillez contre l’outil que vous utilisez. L'
git
approche est très élégante et puissante, en particulier dans le domaine des branches / fusions; si vous le faites correctement (comme mentionné ci-dessus, en particulier avec--no-ff
), c'est par sauts et limites supérieur à d'autres approches (par exemple, le désordre de subversion d'avoir des structures de répertoires parallèles pour les branches).Sans fin, parallèle - oui.
Inavigable, enchevêtrement - non.
Pourquoi ne pas travailler comme l'inventeur de
git
, vos collègues et le reste du monde, tous les jours?Pas d'autres options pas aussi mauvais.
la source
Réduire une branche latérale à long terme vous ferait perdre beaucoup d’informations.
Ce que je ferais, c’est d’ essayer de rebaser maître dans la branche secondaire à long terme avant de fusionner celle-ci en maître . De cette façon, vous gardez chaque commit en maître, tout en rendant l’historique des commit linéaire et plus facile à comprendre.
Si je ne pouvais pas le faire facilement à chaque engagement, je le laisserais non linéaire , afin de garder le contexte de développement clair. À mon avis, si j’ai un problème de fusion lors de la reconstitution de master en sidebranche, cela signifie que la non-linéarité a 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 fouiller dans l’histoire. J'ai aussi l'avantage immédiat de ne pas avoir à refaire.
la source
rebase
. Que ce soit ou non la meilleure approche ici (personnellement, je n’ai pas eu de grandes expériences avec cela, bien que je ne l’aie pas beaucoup utilisé), cela semble définitivement être ce qui se rapproche le plus de ce que OP semble vraiment vouloir - un compromis entre une décharge d’historique désordonnée et la dissimulation complète de cette histoire.Personnellement, je préfère faire mon développement dans une fourchette, puis extraire les demandes pour les fusionner dans le référentiel principal.
Cela signifie que si je veux baser mes modifications sur le maître ou écraser certaines commissions WIP, je peux tout à fait le faire. Ou je peux simplement demander que toute mon histoire soit également fusionnée.
Ce que j'aime faire, c'est faire mon développement sur une branche mais me rebase souvent contre master / dev. De cette façon, je reçois les modifications les plus récentes du maître sans avoir beaucoup de commits de fusion dans ma branche, ni de gérer de nombreux conflits de fusion lorsqu'il est temps de fusionner.
Pour répondre explicitement à votre question:
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 en avant sur le maître après une refonte finale.
la source
Le contrôle de révision est garbage-in.
Le problème est que le travail en cours sur la branche de fonctionnalité peut contenir beaucoup de "essayons ceci ... non cela n'a pas fonctionné, remplaçons-le par cela" et tous les commits sauf le "final" final en polluant inutilement l’histoire.
En fin de compte, l'historique doit être conservé (une partie peut être utile dans le futur), mais seule une "copie vierge" doit être fusionnée.
Avec Git, cela peut être fait en commençant par brancher la branche de fonctionnalités (pour conserver tout l'historique), puis en rebasonnant (de manière interactive) la branche de la branche de caractéristiques depuis le maître, puis en fusionnant la branche rebasée.
la source
git
, elles sont locales. Ce qui est nomméfoo
dans un dépôt peut être nommébar
dans un autre. Et elles sont censées être temporaires: vous ne voulez pas encombrer votre repo avec des milliers de branches de fonctionnalités qui doivent être maintenues en vie afin d'éviter de perdre l'historique. Et vous auriez besoin de conserver ces branches pour que l’historique reste la référence, cargit
il supprimera éventuellement tout commit qui n’est plus accessible par une branche.