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 transplant
pour 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 pull
dans 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:
- Référentiel faisant autorité unique contenant l'ensemble des modifications et l'historique des versions.
- Gestion des versions claire et simplifiée.
- Flux de travail clair et simplifié pour les développeurs et l'intégrateur.
- 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.