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.
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.
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
.
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…
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