Hello,
Cela fait plus de 3 ans qu’on utilise Mercurial dans les projets GeeXboX (avant on travaillait avec TLA Arch). Néanmoins on a de la peine à se conformer aux nouvelles fonctionnalités. Je souhaite en fait parler des branches. Avec Subversion (par exemple), les branches se présentent sous la forme de répertoires. Ça a le mérite d’être clair mais c’est un peu comme collectionner des copies du dépôt principal. Je n’ai jamais trop aimé cette approche, car personnellement je trouve ça un peu brouillon (ça n’engage que moi).
En parlant de brouillon, je vous invite à consulter cette page http://hg.geexbox.org qui liste tous les dépôts existants sous GeeXboX. Si vous regardez la colonne « Last change », vous verrez des « 3 years ago » par exemple. Autant dire que certains dépôts sont complètement obsolètes aujourd’hui. Et c’est même confus, prenez par exemple « geexbox-v2« . Son nom est devenu absurde avec le temps. Car la dedans vous trouverez nos tentatives foireuses de supporter Freevo ;-). Je vous épargne la description des autres.
Bref, là où je veux en venir c’est que pour faire des branches, on s’y prend à l’ancienne et on clone les dépôts. Je pense donc spécialement à « geexbox-1.2 » qui est de loin pas obsolète, car c’est lui qui contient tous les backports (spécialement les corrections de bugs) qui ont permis les versions 1.2.1 à 1.2.4 de GeeXboX. Personnellement il ne me plait pas beaucoup. Toutes les versions 1.2.x devraient être dans le dépôt principal « geexbox« .
Ainsi depuis quelques temps je me suis un peu intéressé aux branches Mercurial. Et c’est sur ce point que je vais m’attarder dans ce billet. Il faut aussi savoir qu’en 2006-2007, une fonctionnalité que je vais présenter ici n’existait pas encore (en tout cas pas sous la même forme). Il n’y a donc pas vraiment de mal d’avoir trainé les bonnes vieilles copies de dépôts pour faire des branches…
Cet intérêt soudain pour les branches m’est venu spécialement depuis les releases de libnfo, libplayer et libvalhalla. Je pense par exemple à avoir des branches pour des éventuels corrections sur les versions 1.0.0. Et surtout, sans faire des copies à l’ancienne.
Les branches nommées (NamedBranches)
Mon but n’est pas d’écrire un tutoriel sur Mercurial. Je vais ainsi essayer d’aller à l’essentiel. Un dépôt peut contenir des « heads » et des branches. Plusieurs « heads » apparaissent quand on commit des changements par rapport à une révision antérieure au « tip« . Ce qui demande donc de faire un « merge » et de le « comitter« .
Par défaut il y a toujours une branche dans chaque dépôt et elle se nomme « default ». Passons directement à la pratique. J’aimerais gérer les corrections de bugs pour libplayer-1.0.0. Admettons que je sois à la racine du dépôt, je créer alors une branche « v1.0″ à partir du tag « v1.0.0″.
$ hg update v1.0.0 33 files updated, 0 files merged, 7 files removed, 0 files unresolved $ hg branch v1.0 marked working directory as branch v1.0
A priori j’ai bien une nouvelle branche. Mais voyons ce que dit `hg branch` et `hg branches`.
$ hg branch v1.0 $ hg branches default 1326:cac53e7d727f
Du fait que je n’ai pas « commité » la nouvelle branche, on ne la voit pas dans la liste des branches. Mais la première commande confirme que je suis bien dans la « v1.0″ fraichement créée. Je vais donc « commiter » ce changement localement.
$ hg commit -m "new branch v1.0 for bugfix" created new head $ hg branches v1.0 1327:1c0c025f8a73 default 1326:cac53e7d727f (inactive)
Si on désire passer d’une branche à l’autre, c’est très simple.
$ hg update default 40 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg branch default $ hg update v1.0 33 files updated, 0 files merged, 7 files removed, 0 files unresolved $ hg branch v1.0
A noter que s’il y avait des changements non « commités » dans le dépôt, il ne serait pas possible de passer d’une branche à l’autre sans qu’ils soient tous annulés ou « commités ». Mais regardons un peu ce que ça donne avec `hg view`.
On voit deux colonnes bien distinctes qui sont rattachées depuis le « tag » v1.0.0. Un petit tour dans `hg heads` nous montre bien qu’il y en a deux. Et chacun est sur une branche différente.
$ hg heads changeset: 1327:1c0c025f8a73 branch: v1.0 tag: tip parent: 1230:6b3e2fed5f7a user: Mathieu Schroeter <mathieu.schroeter@mycable.ch> date: Sun Jan 10 10:26:50 2010 +0100 summary: new branch v1.0 for bugfix changeset: 1326:cac53e7d727f user: Mathieu Schroeter <mathieu.schroeter@mycable.ch> date: Sat Jan 09 20:34:51 2010 +0100 summary: set winid to 0 by default (fix warning if USE_X11 is not defined)
La branche « default » n’est pas montrée explicitement.
Ce qui m’intéresse maintenant c’est de gérer les corrections de bugs. Cette branche « v1.0″ est à considérer pour exister aussi longtemps que tout le dépôt.
Le « cherry picking »
C’est ce qu’on appel le « cherry picking » car seulement quelques « changesets » spécifiques m’intéressent. Les corrections de bugs sont toutes dans la branche « default ». Pour importer ces changements dans le dépôt « v1.0″ il y a plusieurs manières de faire. Mais personnellement la majorité des solutions que j’ai trouvé ne me plaisent pas. Et il y a peu de temps, je suis tombé sur une extension officielle (mais non activée par défaut) qui permet de faire exactement ce genre de traitements (et même plus). Elle s’appelle « transplant » et c’est elle que je vais utiliser pour mon exemple.
Il faut commencer par l’activer dans le fichier ~/.hgrc:
[extensions] transplant=
Je vais prendre une partie des corrections intéressantes et les transplanter dans la nouvelle branche.
$ hg transplant 1242 application de 95461fb8613f patching file Makefile Hunk #1 succeeded at 18 with fuzz 1 (offset -3 lines).
Je répète pour chaque « changeset ». On peut spécifier plusieurs « changesets » en les séparant par ‘:’ pour une plage REV1:REV4 par exemple. Mais avec le Mercurial d’Ubuntu j’évite de le faire, simplement parce que l’extension me retourne une exception et un joli traceback Python. En prenant un après l’autre, aucun problème.
Le « screenshot » ci-dessus permet de voir les différents « changesets » appliqués sur la branche « v1.0″.
Pour continuer à travailler sur la devel (branche « default »), rien de plus simple.
$ hg update default 40 files updated, 0 files merged, 0 files removed, 0 files unresolved $ vi Makefile $ hg commit -m "dummy commit" $ hg heads changeset: 1335:55589e41d0eb tag: tip parent: 1326:cac53e7d727f user: Mathieu Schroeter <mathieu.schroeter@mycable.ch> date: Sun Jan 10 13:07:34 2010 +0100 summary: dummy commit changeset: 1334:81c124600c6b branch: v1.0 user: Mathieu Schroeter <mathieu.schroeter@mycable.ch> date: Sat Jan 09 20:29:01 2010 +0100 summary: fix 'make dist' in src
On voit clairement évoluer le « tip » de la branche « default » en parallèle à la branche « v1.0″.
Un « push » avec plusieurs branches
Tout ceci n’est qu’un exemple, ainsi pour montrer le « push » je vais utiliser un répertoire local. Admettons que je clone depuis http://hg.geexbox.org/libplayer.
$ cd .. $ hg clone http://hg.geexbox.org/libplayer libplayer-local requesting all changes adding changesets adding manifests adding file changes added 1327 changesets with 2268 changes to 111 files updating working directory 104 files updated, 0 files merged, 0 files removed, 0 files unresolved
C’est donc le dépôt original où j’ai qu’une seule branche. Je retourne dans le dépôt qui contient la branche « v1.0″ et je vais faire un « push » sur libplayer-local.
$ cd libplayer $ hg push ../libplayer-local pushing to ../libplayer-local searching for changes abort: push creates new remote branch 'v1.0'! (did you forget to merge? use push -f to force)
Le « push » est automatiquement annulé. Ce qui est très bien, ainsi Mercurial nous préviens qu’on a créé une nouvelle branche. Il est alors nécessaire de le forcer (la première fois). Mercurial nous demande aussi si on a pas oublié de faire un « merge ». C’est légitime dans le cas où on ne veut pas avoir deux branches séparées sur le dépôt distant.
$ hg push -f ../libplayer-local pushing to ../libplayer-local searching for changes adding changesets adding manifests adding file changes added 9 changesets with 6 changes to 2 files (+1 heads)
Mercurial nous confirme qu’on a bien ajouté un « head ». A partir de ce point je me suis posé la question de ce qui se passe si quelqu’un clone ce dépôt avec ces deux « heads ». Est-ce qu’il va avoir un avertissement s’il tente de faire un « push », du genre qu’il devrait faire un « merge » ou forcer le « push »?! Ce serait ennuyeux si c’était le cas, car les branches ne devraient pas intervenir. Le « head » n’étant pas sur la même branche. J’ai donc simplement testé pour m’en assurer:
$ cd .. $ hg clone libplayer-local libplayer-foobar updating working directory 104 files updated, 0 files merged, 0 files removed, 0 files unresolved
Cette manipulation revient au même que de cloner le dépôt sur hg.geexbox.org si j’avais « pushé » les modifications. Sans me soucier des branches, je vais faire une modification dans libplayer-foobar, la « commiter », puis faire un « push » dans libplayer-local comme si c’était hg.geexbox.org/libplayer.
$ cd libplayer-foobar $ vi Makefile $ hg commit -m "still a dummy commit" $ hg push ../libplayer-local pushing to ../libplayer-local searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files
Il n’y a aucune remarque de Mercurial par rapport aux branches. Cela prouve que tout fonctionne correctement. Honnêtement j’avais toujours eu des doutes sur ces fonctionnalités. Au moins maintenant c’est clair. Je vais alors retourner dans le dépôt libplayer-local pour vérifier le résultat avec `hg view`.
Tout a fonctionné parfaitement..
Conclusion
Maintenant que j’en sais suffisamment sur les branches, je pense passer par ce moyen à l’avenir. Il pourrait être même judicieux (peut être) de repasser tous les backports de geexbox-1.2 dans le dépôt geexbox. L’idée serait de ne jamais écrire de nouveaux patchs dans les dépôts faisant références à des versions antérieures. Mais de toujours faire les modifications dans le « default » et uniquement des « transplant » dans les autres branches. Je pense par exemple au dossier debian/ dans les dépôts. Afin de garder l’historique, une nouvelle release tel que libplayer-1.0.1 par exemple, serait enregistrée dans les changelogs du « default », puis un « transplant » mettrait à jour la branche « v1.0″.
Il reste une utilisation des branches qui me pose encore un problème. Les cas où l’écriture d’une fonctionnalité ne devrait pas se faire directement dans le « default » car elle introduirait des régressions. Une branche supplémentaire tel que « experimental » pourrait faire l’affaire. On devrait ensuite faire un « merge » de cette branche dans le « default ». Ce qui me dérange c’est qu’on finit par collectionner les branches comme par exemple ici: http://hg.geexbox.org/enna/. Il y a « new_vfs » et « new_vfs_system ». Ces branches n’ont plus vraiment de raison d’être car elles ont été « mergées ». Dans le cas de l’exemple présenté au-dessus avec la branche « v1.0″, c’est parfaitement normal de la garder pour toujours. Je n’ai donc pas encore trouvé de solution propre (c’est une question de point de vue) pour travailler avec des branches de courtes durées de vie.
Si quelqu’un à une idée…
A bientôt,
Mathieu SCHROETER





















