Skywalker13

Diary of an ex-GeeXboX developer…

Mercurial, depuis fin 2006 ~ đŸ‡«đŸ‡·

Posted at — Jan 10, 2010

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