web-dev-qa-db-fra.com

Branches nommées vs référentiels multiples

Nous utilisons actuellement Subversion sur une base de code relativement importante. Chaque version a sa propre branche, et des correctifs sont apportés au tronc et migrés dans les branches de la version à l'aide de svnmerge.py

Je pense que le temps est venu de passer à un meilleur contrôle de la source et je joue avec Mercurial depuis un moment.

Il semble y avoir deux écoles de bien sur la gestion d'une telle structure de publication en utilisant Mercurial. Chaque version reçoit son propre référentiel, et des correctifs sont apportés à la branche de version et transférés vers la branche principale (et toutes les autres branches de version plus récentes.) OR à l'aide de branches nommées dans un même référentiel (ou plusieurs copies correspondantes).

Dans les deux cas, il me semble que je pourrais utiliser quelque chose comme transplantation pour modifier les changements de cherrypick et l'inclure dans les branches de publication.

Je te demande; Quels sont les mérites relatifs de chaque approche?

130
James Emerton

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

Cela signifie que les clones sont parfaits pour les expériences rapides lorsque vous ne voulez pas enregistrer de nom de branche, et que les branches nommées conviennent aux 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. Ces branches dans le référentiel peuvent être marquées d'un signet afin que vous puissiez les retrouver facilement. Supposons que vous ayez cloné le référentiel de la société comme suit:

[a] --- [b]

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

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

Pendant que quelqu'un place [c] et [d] dans le référentiel, vous obtenez un graphe 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 jeu de modifications, appelé jeu de modifications parent de la copie de travail. Vérifiez ceci avec:

% hg parents

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

% hg heads

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

% hg update --clean [d]

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

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

Pour pousser les changeset [e] seulement, vous devez faire

% hg Push -r [e]

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

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

% hg update [y]
% hg merge

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

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

[z] est la fusion entre [y] et [e]. Vous auriez aussi pu choisir de jeter la branche:

% hg strip [x]

Mon objectif 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 aucune extension. L'extension bookmarks est une aide précieuse, cependant. Cela vous permettra d'attribuer des noms (signets) à des ensembles de modifications. 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 tirés 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 le corrigez. Ici, vous n'avez pas à hg update puisque le clone en amont est prêt à être utilisé. Vous validez et créez [e]:

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

Pour inclure le correctif dans votre clone de développement, vous y insérez:

 [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 un peu moins de comptabilité mentale.

Les branches nommées ne sont pas vraiment venues ici parce qu’elles sont plutôt 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 nous faisons nos communiqués basés sur la branche "stable". Voir la page standard branching du wiki pour une description du flux de travail recommandé.

129
Martin Geisler

Je pense que vous voulez l'histoire entière dans un repo. Le repeuplement d'un dépôt à court terme est destiné à des expériences à court terme, et non à des événements majeurs comme des rejets.

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 elle, de l’abandonner et de ramasser les ordures. Les branches sont pour toujours. Je regrette de ne jamais vouloir abandonner l’histoire, mais les branches jetables super bon marché sont une caractéristique git que j’aimerais vraiment voir dans hg.

29
Norman Ramsey

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 publication pour créer et tester.

Une remarque clé est que même si vous utilisez plusieurs référentiels, vous devriez éviter d'utiliser transplant pour déplacer les ensembles de modifications entre eux car 1) il modifie le hachage et 2) il peut générer des bogues très difficiles à détecter en cas de modifications conflictuelles entre les ensembles de modifications. vous greffez et la branche cible. Vous voulez faire la fusion habituelle à la place (et sans premerge: toujours inspecter 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.

De manière plus détaillée, si vous utilisez plusieurs référentiels, le référentiel "trunk" (ou par défaut, principal, développement, peu importe) contient ALL changesets dans les référentiels ALL. Chaque référentiel de version/branche est simplement une branche dans le coffre, tous fusionnés dans un sens ou dans l'autre, jusqu'à ce que vous souhaitiez laisser une ancienne version. Par conséquent, la seule différence réelle entre ce référentiel principal et le référentiel unique dans le schéma de branche nommée est simplement de savoir si les branches sont nommées ou non.

Cela devrait rendre évident pourquoi j'ai dit "commencez avec un repo". Ce référentiel unique est le seul endroit où vous aurez besoin de rechercher un ensemble de modifications dans une version. Vous pouvez en outre marquer les ensembles de modifications sur les branches des versions pour la gestion des versions. Il est clair et simple sur le plan conceptuel et simplifie l’administration du système, car c’est la seule chose qui doit absolument être disponible et récupérable à tout moment.

Cependant, vous devez toujours conserver un clone par branche/version que vous devez créer et tester. C'est trivial, car vous pouvez hg clone <main repo>#<branch> <branch repo>, puis hg pull dans le référentiel de branche extraira uniquement les nouveaux ensembles de modifications sur cette branche (plus les ensembles de modifications d'ancêtre sur des branches antérieures fusionnées).

Cette configuration s’adapte le mieux au modèle de validation du noyau linux de single puller (il n’est pas agréable d’agir comme Lord Linus. Dans notre société, nous appelons le rôle intégrateur), en tant que principal. Le repo est la seule chose que les développeurs doivent cloner et l'extracteur doit s'y insérer. La maintenance des dépôts de succursale est purement destinée à la gestion des versions et peut être entièrement automatisée. Les développeurs n'ont jamais besoin d'extraire/de 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, dites "1.0", lorsque vous parvenez à la version alpha. Commit des corrections de bugs sur ce problème:

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

(1.0) n'est pas un réel jeu de modifications car la branche nommée n'existe pas tant que vous n'avez pas validé. (Vous pouvez effectuer une validation triviale, telle que l'ajout d'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 (à l'exception de l'ancienne branche à publication immédiate, comme mentionné précédemment). Ainsi, chaque fois que vous avez de nouveaux jeux 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 toute correction de bogue dans une version est également incluse 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 la branche 1.0:

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

C'est maintenant un exercice d'ajouter la prochaine branche de publication. Si c'est 2.0 alors ça va définitivement dériver par défaut. Si c'est 1.1, vous pouvez choisir de ne pas utiliser 1.0 ou par défaut. Quoi qu'il en soit, tout nouveau jeu de modifications sur la version 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 les suivants:

  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 du flux de travail (revues de code) et l'automatisation (fusion automatique à vide).

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

14
Geoffrey Zheng

La principale différence, à ma connaissance, est quelque chose que vous avez déjà dit: les branches nommées sont regroupées dans un seul référentiel. Les branches nommées ont tout à portée de main au même endroit. Les pensions séparées sont plus petites 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. Quelle que soit l'argumentation du parti qui vous convient le mieux, c'est probablement celle avec laquelle vous devriez vous appuyer, car il est probable que leur environnement ressemble beaucoup au vôtre.

5
dwc

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 fourchettes sont vraiment bonnes pour les contributeurs dont les rôles ne sont pas encore déterminés à rejoindre l’équipe de développeurs en prouvant leur aptitude avec des frais techniques négligeables.

2
thSoft

Je déconseillerais vraiment d'utiliser des branches nommées pour les versions. C'est vraiment à quoi servent les tags. Les branches nommées sont conçues pour des détournements de longue durée, comme une branche stable.

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

  • 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 juste à partir de là
  • Si vous avez des bugs à corriger (ou autre) dans une version donnée, il vous suffit de mettre à jour son tag, de faire vos modifications et de le valider.

Cela créera une nouvelle tête non nommée sur la branche default, aka. une branche anonyme qui convient parfaitement au hg. Vous pouvez ensuite à tout moment fusionner les corrections de bogues dans la piste de développement principale. Pas besoin de branches nommées.

0
DanMan