Branches nommées vs référentiels multiples

130

Nous utilisons actuellement la subversion sur une base de code relativement importante. Chaque version a sa propre branche, et des correctifs sont effectués sur le tronc et migrés vers les branches de publication à l'aidesvnmerge.py

Je pense que le moment est venu de passer à un meilleur contrôle des sources, et je joue avec Mercurial depuis un moment.

Il semble y avoir deux écoles de pensée sur la gestion d'une telle structure de publication à l'aide de Mercurial. Soit chaque version obtient son propre référentiel, et des correctifs sont apportés à la branche de publication et envoyés à la branche principale (et à toute autre branche de version plus récente). OU en utilisant des branches nommées dans un seul référentiel (ou plusieurs copies correspondantes).

Dans les deux cas, il semble que j'utilise quelque chose comme la transplantation pour chercher les changements à inclure dans les branches de publication.

Je vous demande; quels sont les mérites relatifs de chaque approche?

James Emerton
la source

Réponses:

129

La plus grande différence réside dans la manière dont les noms des branches sont enregistrés dans l'historique. Avec les branches nommées, le nom de la branche est intégré dans chaque changeset et deviendra ainsi une partie immuable de l'historique. Avec les clones, il n'y aura pas d' enregistrement permanent de l'origine d'un ensemble de modifications particulier.

Cela signifie que les clones sont parfaits pour les expériences rapides où vous ne voulez pas enregistrer un nom de branche, et les branches nommées sont bonnes pour les branches à long terme ("1.x", "2.x" et similaires).

Notez également qu'un seul référentiel peut facilement accueillir plusieurs branches légères dans Mercurial. De telles branches dans le référentiel peuvent être mises en signet afin que vous puissiez facilement les retrouver. Disons que vous avez cloné le référentiel de l'entreprise quand il ressemblait à ceci:

[a] --- [b]

Vous piratez et faites [x]et [y]:

[a] --- [b] --- [x] --- [y]

Cela signifie que quelqu'un met [c]et [d]dans le référentiel, donc lorsque vous tirez, vous obtenez un graphique d'historique comme celui-ci:

            [x] --- [y]
           /
[a B c d]

Ici, il y a deux têtes dans un seul référentiel. Votre copie de travail reflétera toujours un seul changeset, le jeu de modifications parent de la copie de travail. Vérifiez ceci avec:

% hg parents

Disons qu'il rapporte [y]. Vous pouvez voir les têtes avec

% hg heads

et cela rapportera [y]et [d]. Si vous souhaitez mettre à jour votre référentiel pour une extraction propre de [d], alors faites simplement (remplacez-le [d]par le numéro de révision pour [d]):

% hg update --clean [d]

Vous verrez alors ce hg parentsrapport [d]. Cela signifie que votre prochain commit aura [d]pour parent. Vous pouvez ainsi corriger un bug que vous avez remarqué dans la branche principale et créer un changeset [e]:

            [x] --- [y]
           /
[a] --- [b] --- [c] --- [d] --- [e]

Pour pousser le changeset [e]uniquement, vous devez faire

% hg push -r [e]

[e]est le hachage du changeset. Par défaut hg push, vous comparerez simplement les référentiels et verrez que [x], [y]et [e]sont manquants, mais vous ne voudrez peut-être pas partager [x]et [y]encore.

Si le correctif vous affecte également, vous souhaitez le fusionner avec votre branche de fonctionnalités:

% hg update [y]
% hg merge

Cela laissera votre graphique de référentiel ressemblant à ceci:

            [x] --- [y] ----------- [z]
           / /
[a] --- [b] --- [c] --- [d] --- [e]

[z]est la fusion entre [y]et [e]. Vous pourriez également avoir choisi de jeter la branche:

% hg strip [x]

Mon point principal de cette histoire est le suivant: un seul clone peut facilement représenter plusieurs pistes de développement. Cela a toujours été vrai pour "plain hg" sans utiliser d'extensions. L' extension de signets est cependant d'une grande aide. Il vous permettra d'attribuer des noms (signets) aux changesets. Dans le cas ci-dessus, vous voudrez un signet sur votre tête de développement et un sur la tête en amont. Les signets peuvent être poussés et extraits avec Mercurial 1.6 et sont devenus une fonctionnalité intégrée à Mercurial 1.8.

Si vous aviez choisi de créer deux clones, votre clone de développement aurait ressemblé à ceci après avoir créé [x]et [y]:

[a] --- [b] --- [x] --- [y]

Et votre clone en amont contiendra:

[a] --- [b] --- [c] --- [d]

Vous remarquez maintenant le bogue et corrigez-le. Ici, vous n'avez pas à le faire hg updatecar le clone en amont est prêt à être utilisé. Vous vous engagez et créez [e]:

[a] --- [b] --- [c] --- [d] --- [e]

Pour inclure le correctif dans votre clone de développement, insérez-le ici:

[a] --- [b] --- [x] --- [y]
           \
            [c] --- [d] --- [e]

et fusionner:

[a] --- [b] --- [x] --- [y] --- [z]
           \ /
            [c] --- [d] --- [e]

Le graphique peut sembler différent, mais il a la même structure et le résultat final est le même. En utilisant les clones, vous deviez faire une comptabilité un peu moins mentale.

Les branches nommées ne sont pas vraiment entrées dans l'image ici car elles sont assez optionnelles. Mercurial lui-même a été développé en utilisant deux clones pendant des années avant de passer à l'utilisation de branches nommées. Nous maintenons une branche appelée 'stable' en plus de la branche 'default' et créons nos versions basées sur la branche 'stable'. Voir la page de branchement standard dans le wiki pour une description du flux de travail recommandé.

Martin Geisler
la source
1
si l'ensemble de modifications provenait d'un utilisateur différent, cela aurait été enregistré, donc utiliser des clones n'a rien de mal. Lorsque vous lancez une nouvelle fonctionnalité, il est souvent inintéressant de savoir que vous l'avez fait à partir d'un dépôt séparé. Il existe également une extension localbranch, qui vous donne une branche uniquement locale. Utile lorsque le clonage du dépôt est associé à des coûts élevés (temps / espace).
Johannes Rudolph
2
faisant référence à: «les clones sont parfaits pour des expériences rapides» - Non, ils ne le sont pas! Que faire si vous avez quelques tonnes de fichiers dans le dépôt? Le clonage prendra des années (à tout moment au-dessus de 1 minute) tandis que le changement de branche juste un instant (<1 seconde). Toujours utiliser des branches nommées polluera le journal des modifications. N'est-ce pas une impasse? Ou je manque quelque chose?
seler
Ok seler; Sonne comme une modification de son argument original; Les clones sont utiles lorsque la surcharge de plusieurs copies complètes n'est pas importante pour vous, ou lorsque vous pouvez utiliser les liens symboliques / hardlinks de hg pour atténuer le coût de copies de travail locales séparées par branche.
Warren P
@seler: vous avez tout à fait raison de dire que les clones ne sont pas pratiques si le code est gros. Les signets sont alors la solution.
Martin Geisler
29

Je pense que vous voulez l'intégralité de l'historique en un seul dépôt. La création d'un repo à court terme est destinée à des expériences à court terme, pas à des événements majeurs tels que les versions.

L'une des déceptions de Mercurial est qu'il ne semble pas y avoir de moyen facile de créer une branche éphémère, de jouer avec, de l'abandonner et de ramasser les ordures. Les branches sont éternelles. Je sympathise avec le fait de ne jamais vouloir abandonner l'histoire, mais les branches jetables très bon marché sont une gitfonctionnalité que j'aimerais vraiment voir hg.

Norman Ramsey
la source
20
Vous pouvez très facilement créer une telle branche de fonctionnalité: "hg update" à votre point de branche, modifier et "hg commit". Vous venez de créer une ligne de développement divergente - de nouveaux commits étendront cette branche. Utilisez "hg clone -r" pour vous en débarrasser, ou supprimez-le en ligne par "hg strip". Alors, ne soyez pas déçu ou venez sur les listes de diffusion Mercurial avec vos demandes de fonctionnalités.
Martin Geisler
8
On dirait que hg stripc'est ce que je veux. Pourquoi la documentation en ligne prétend-elle que les branches ne peuvent pas être supprimées?
Norman Ramsey
11
Voir aussi ce billet de blog pour une explication sur la façon dont Mercurial a, d'une certaine manière, des branches moins chères que git: stevelosh.com/blog/entry/2009/8/30
Martin Geisler
9
Vous pouvez fermer une branche nommée avec hg ci --close-branch.
Andrey Vlasovskikh
3
@Norman Ramsey: quand les gens disent que les branches ne peuvent pas être supprimées, cela signifie que vous ne pouvez pas changer le nom de branche intégré dans les changesets. Un changeset nous non sur une branche, il définit une branche. Vous devrez supprimer l'ensemble de modifications et le recréer avec un nom de branche différent si vous souhaitez le «déplacer» vers une autre branche.
Martin Geisler
14

Vous devriez faire les deux .

Commencez par la réponse acceptée de @Norman: utilisez un référentiel avec une branche nommée par version.

Ensuite, ayez un clone par branche de version pour la construction et les tests.

Une note clé est que même si vous utilisez plusieurs référentiels, vous devez éviter d'utiliser transplantpour déplacer des ensembles de modifications entre eux car 1) cela change le hachage et 2) cela peut introduire des bogues très difficiles à détecter lorsqu'il y a des changements conflictuels entre l'ensemble de modifications que vous greffe et la branche cible. Vous voulez faire la fusion habituelle à la place (et sans prémerge: inspectez toujours visuellement la fusion), ce qui entraînera ce que @mg a dit à la fin de sa réponse:

Le graphique peut sembler différent, mais il a la même structure et le résultat final est le même.

Plus en détail, si vous utilisez plusieurs référentiels, le référentiel "trunk" (ou par défaut, main, développement, peu importe) contient TOUS les changesets dans TOUS les référentiels. Chaque référentiel de version / branche est simplement une branche dans le tronc, tous fusionnés dans un sens ou dans l'autre vers le tronc, jusqu'à ce que vous vouliez laisser une ancienne version derrière. Par conséquent, la seule vraie différence entre ce repo principal et le repo unique dans le schéma de succursales nommées est simplement de savoir si les succursales sont nommées ou non.

Cela devrait rendre évident la raison pour laquelle j'ai dit "commencer par un dépôt". Ce dépôt unique est le seul endroit où vous aurez besoin de rechercher un ensemble de modifications dans n'importe quelle version . Vous pouvez baliser d'autres ensembles de modifications sur les branches de version pour la gestion des versions. C'est conceptuellement clair et simple, et simplifie l'administration système, car c'est la seule chose qui doit absolument être disponible et récupérable à tout moment.

Mais alors, vous devez toujours maintenir un clone par branche / version que vous devez construire et tester. C'est trivial comme vous pouvez hg clone <main repo>#<branch> <branch repo>, puis hg pulldans le repo de branche, vous ne tirerez que de nouveaux ensembles de modifications sur cette branche (plus les ensembles de modifications ancêtres sur les branches précédentes qui ont été fusionnées).

Cette configuration correspond le mieux au modèle de commit du noyau Linux de l' extracteur unique (n'est-il pas bon d'agir comme Lord Linus. Dans notre entreprise, nous appelons l' intégrateur de rôle ), car le dépôt principal est la seule chose que les développeurs doivent cloner et le l'extracteur doit entrer. La maintenance des dépôts de succursale est purement pour la gestion des versions et peut être complètement automatisée. Les développeurs n'ont jamais besoin de tirer / pousser vers les dépôts de branche.


Voici l'exemple de @ mg refondu pour cette configuration. Point de départ:

[a] - [b]

Créez une branche nommée pour une version finale, dites "1.0", lorsque vous arrivez à la version alpha. Commit des corrections de bogues dessus:

[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

(1.0)n'est pas un vrai changeset car la branche nommée n'existe pas tant que vous ne vous engagez pas. (Vous pouvez faire un commit trivial, comme ajouter une balise, pour vous assurer que les branches nommées sont correctement créées.)

La fusion [m1]est la clé de cette configuration. Contrairement à un référentiel de développeurs où il peut y avoir un nombre illimité de têtes, vous ne voulez PAS avoir plusieurs têtes dans votre référentiel principal (sauf pour l'ancienne branche de version morte comme mentionné précédemment). Ainsi, chaque fois que vous avez de nouveaux ensembles de modifications sur des branches de version, vous devez les fusionner immédiatement avec la branche par défaut (ou une branche de version ultérieure). Cela garantit que tout correctif de bogue dans une version est également inclus dans toutes les versions ultérieures.

En attendant, le développement sur la branche par défaut se poursuit vers la prochaine version:

          ------- [c] - [d]
         /
[a] - [b] ------------------ [m1]
         \                 /
          (1.0) - [x] - [y]

Et comme d'habitude, vous devez fusionner les deux têtes sur la branche par défaut:

          ------- [c] - [d] -------
         /                         \
[a] - [b] ------------------ [m1] - [m2]
         \                 /
          (1.0) - [x] - [y]

Et voici le clone de branche 1.0:

[a] - [b] - (1.0) - [x] - [y]

C'est maintenant un exercice pour ajouter la branche de version suivante. Si c'est 2.0, alors il se ramifiera certainement par défaut. Si c'est 1.1, vous pouvez choisir de bifurquer 1.0 ou par défaut. Quoi qu'il en soit, tout nouvel ensemble de modifications sur 1.0 doit d'abord être fusionné avec la branche suivante, puis par défaut. Cela peut être fait automatiquement s'il n'y a pas de conflit, résultant simplement en une fusion vide.


J'espère que l'exemple clarifie mes points précédents. En résumé, les avantages de cette approche sont:

  1. Référentiel faisant autorité unique contenant l'ensemble des modifications et l'historique des versions.
  2. Gestion des versions claire et simplifiée.
  3. Flux de travail clair et simplifié pour les développeurs et l'intégrateur.
  4. Facilitez les itérations de flux de travail (revues de code) et l'automatisation (fusion vide automatique).

UPDATE hg lui - même le fait : le référentiel principal contient les branches par défaut et stables, et le référentiel stable est le clone de branche stable. Cependant, il n'utilise pas de branche versionnée, car les balises de version le long de la branche stable sont suffisantes pour ses besoins de gestion des versions.

Geoffrey Zheng
la source
5

La différence majeure, pour autant que je sache, est quelque chose que vous avez déjà déclaré: les nommés branchés sont dans un seul référentiel. Les branches nommées ont tout à portée de main en un seul endroit. Les dépôts séparés sont plus petits et faciles à déplacer. La raison pour laquelle il y a deux écoles de pensée à ce sujet est qu'il n'y a pas de gagnant clair. Quel que soit l'argumentation de votre camp qui a le plus de sens pour vous, c'est probablement celle avec laquelle vous devriez aller, car il est probable que leur environnement soit le plus similaire au vôtre.

dwc
la source
2

Je pense que c'est clairement une décision pragmatique en fonction de la situation actuelle, par exemple la taille d'une fonctionnalité / refonte. Je pense que les forks sont vraiment bons pour les contributeurs qui n'ont pas encore de rôle de commettant pour rejoindre l'équipe de développeurs en prouvant leur aptitude avec une surcharge technique négligeable.

thSoft
la source
0

Je déconseille vraiment d'utiliser des branches nommées pour les versions. C'est vraiment à cela que servent les balises. Les branches nommées sont destinées à des détournements de longue durée, comme une stablebranche.

Alors pourquoi ne pas simplement utiliser des balises? Un exemple basique:

  • Le développement se fait sur une seule branche
  • Chaque fois qu'une version est créée, vous la marquez en conséquence
  • Le développement continue à partir de là
  • Si vous avez des bogues à corriger (ou autre chose) dans une certaine version, il vous suffit de mettre à jour sa balise, d'apporter vos modifications et de valider

Cela créera une nouvelle tête sans nom sur la defaultbranche, aka. une branche anonyme, ce qui est parfaitement bien en hg. Vous pouvez alors à tout moment fusionner les commits de correction de bogue dans la piste de développement principale. Pas besoin de branches nommées.

DanMan
la source
Cela dépend beaucoup de votre processus. Une application Web, par exemple, fonctionne bien avec une hiérarchie de branches stable / testing / devel. Lors de la création d'un logiciel de bureau, nous avons généralement une branche de développement (par défaut) ainsi qu'une à trois (!) Branches différentes en maintenance. Il est difficile de prédire quand nous pourrions avoir besoin de revoir une branche, et il y a une certaine élégance à avoir une branche pour suivre une version major.minor.
James Emerton