Pour installer Mercurial :
$ apt-get install mercurial
Demander de l'aide sur une commande (init, par exemple) :
$ hg help init
ou, pour une version plus détaillée :
$ hg help -v init
Pour toute information : http://hgbook.red-bean.com/read/
Avec Mercurial, tout se passe dans des dépôts : des répertoires possédant un répertoire caché .hg, qui contient tout le projet, soit
Ce dépôt peut être renommé, ou déplacé, sans problème (en ligne de commande, etc.)
Chaque dépôt est donc complet, et indépendant : si l'on fait un peu n'importe quoi avec notre dépôt, cela n'affectera pas les autres.
On conviendra de distinguer les deux éléments suivants :
Pour copier un dépôt, on préférera la commande clone à un simple cp (qui marcherait), comme par exemple :
$ hg clone http://hg.serpentine.com/tutorial/hello
Les raisons de préférer cette commande sont multiples :
Bien sûr, on peut copier ainsi localement un dépôt : on n'est pas obligé de passer par le web. De plus, on peut préciser le nom du dépôt cible :
$ hg clone depot_source depot_cible
La copie d'un dépôt local est légère, ne coûte rien. On peut donc y avoir recours sans compter.
Par exemple, si l'on a un projet, et que l'on souhaite développer en profondeur deux ou trois points nouveaux. On pourra alors créer, pour chacun, un nouveau dépôt, que l'on réintégrera au projet central quand les travaux le concernant auront aboutis.
On peut souhaiter sauvegarder son dépôt régulièrement, pour se prémunir des crash systèmes.
Cette solution peut se faire facilement, en plaçant dans le crontab une ligne de la forme
hg clone -U mon_depot depot_backup
Dans ce qui précède, l'option -U sert à indiquer que l'on ne souhaite pas cloner le répertoire de travail : on gagnera du temps dans le backup, et l'état des travaux au moment du crash n'est pas forcément pertinent.
Pour visualiser un résumé de l'historique des changements du dépôt :
$ hg log
On obtient une liste de paragraphes de la forme :
changeset: 0:0a04b987be5a user: mpm@selenic.com date: Fri Aug 26 01:20:50 2005 -0700 summary: Create a standard "hello, world" program
Le changeset contient deux nombres, séparés par :. Chacun permet d'identifier la révision considérée.
Les autres champs se comprennent aisément.
On peut demander de ne visualiser que certaines révisions, comme suit (les retours ne sont pas donnés) :
$ hg log -r 1 $ hg log -r 1 -r 4 $ hg log -r 2:4 $ hg log -r 4:2
On peut toujours remplacer les numéros de révision locale par les valeurs hachées. Cela n'a d'intérêt qu'à partir du moment où on effectue un échange entre deux dépôts.
Pour avoir un log plus détaillé, on peut ajouter l'option verbose :
$ hg log -v -r 3
On peut encore demander à voir les différences avec l'option patch :
$ hg log -vpr 2
changeset: 2:fef857204a0c
user: Bryan O'Sullivan <bos@serpentine.com>
date: Sat Aug 16 22:05:04 2008 +0200
files: hello.c
description:
Introduce a typo into hello.c.
diff -r 82e55d328c8c -r fef857204a0c hello.c
--- a/hello.c Fri Aug 26 01:21:28 2005 -0700
+++ b/hello.c Sat Aug 16 22:05:04 2008 +0200
-11,6 +11,6
int main(int argc, char **argv)
{
- printf("hello, world!\n");
+ printf("hello, world!\");
return 0;
}
Pour connaître le statut des fichiers du répertoire de travail, par rapport à la révision parente du dépôt réel (savoir s'ils sont nouveaux, modifiés ou supprimés), on utilise la commande status :
$ ls Makefile hello.c $ hg status M hello.c
On voit que le fichier hello.c a été modifié.
On peut avoir le détail des modifications, avec la commande diff :
$ hg diff
diff -r 2278160e78d4 hello.c
--- a/hello.c Sat Aug 16 22:16:53 2008 +0200
+++ b/hello.c Tue May 05 06:55:53 2009 +0000
-8,5 +8,6
int main(int argc, char **argv)
{
printf("hello, world!\");
+ printf("hello again!\n");
return 0;
}
Quand un travail a été réalisé, que l'on a fini une tâche, on peut faire du répertoire de travail une nouvelle version du projet. Cet enregistrement de notre travail dans un nouveau changeset se fait à l'aide de la commande commit.
Pour pouvoir «commiter», Mercurial a besoin de connaître l'utilisateur, et son mail : ainsi, chaque utilisateur d'un dépôt donné sera en mesure de savoir qui a fait quoi.
Le processus suivi pour affecter un nom à un commit s'effectue dans l'ordre suivant :
La solution à préférer est l'utilisation du fichier de configuration ~/.hgrc, qui doit se présenter ainsi :
# This is a Mercurial configuration file. [ui] username = Christophe Guyeux <christophe.guyeux@univ-fcomte.fr>
Si les mécanismes ci-dessus échouent, un message d'erreur est retourné, et le commit n'est pas possible.
Venons-en au commit :
$ hg commit
Suite à cette commande, un éditeur de texte apparaît, vous permettant de détailler les raisons du commit. La première de ces lignes apparaîtra dans hg log, donc doit être bien choisie.
Les lignes de l'éditeur commençant par HG: seront ignorées. Elles ne sont là que pour vous rappeler les changements effectués : quels ont été les fichiers modifiés, dans quelle branche de développement on se situe...
Signalons pour finir que :
Le tip fait référence au dernier commit, c'est-à-dire à la dernière version enregistrée de notre projet.
On peut voir ce tip, en d'autres termes le dernier changeset, par la commande hg tip, qui admet les mêmes options que hg log :
$ hg tip -vp
changeset: 5:b6fed4f21233
tag: tip
user: Bryan O'Sullivan <bos@serpentine.com>
date: Tue May 05 06:55:53 2009 +0000
files: hello.c
description:
Added an extra line of output
diff -r 2278160e78d4 -r b6fed4f21233 hello.c
--- a/hello.c Sat Aug 16 22:16:53 2008 +0200
+++ b/hello.c Tue May 05 06:55:53 2009 +0000
-8,5 +8,6
int main(int argc, char **argv)
{
printf("hello, world!\");
+ printf("hello again!\n");
return 0;
}
Le changement dans le dépôt réel, réalisé lors du dernier commit, n'a affecté que le dépôt local. On voit ici comment récupérer cette modification dans un autre dépôt.
Il s'agit donc ici de récupérer les modifications d'un autre dépôt. Pour déposer ses propres modifications dans un autre dépôt, voir plus loin.
On suppose, pour illustrer notre propos, que l'on a récupéré un premier dépôt (hello) à partir du net, et que l'on a cloné ce dernier dans un nouveau dépôt (hello2) :
$ hg clone http://hg.serpentine.com/tutorial/hello $ hg clone hello hello2 $ cd hello2/
On suppose de plus que le code du ficher hello.c a été modifié, comme ci-dessus.
$ hg log -pvr 5
changeset: 5:9f634748bf7e
tag: tip
user: Christophe Guyeux <christophe.guyeux@univ-fcomte.fr>
date: Sat May 23 16:44:09 2009 +0200
files: hello.c
description:
Un plus beau coucou.
J'ai modifié hello.c, en ajoutant un deuxième printf
diff -r 2278160e78d4 -r 9f634748bf7e hello.c
--- a/hello.c Sat Aug 16 22:16:53 2008 +0200
+++ b/hello.c Sat May 23 16:44:09 2009 +0200
-8,5 +8,6
int main(int argc, char **argv)
{
printf("hello, world!\");
+ printf("hello, again!\");
return 0;
}
Maintenant, clonons une nouvelle fois le dépôt local originel. Nous obtenons ainsi un troisième dépôt, hello-pull, à partir du dépôt hello :
$ hg clone hello hello-pull
Nous souhaitons porter nos modifications du dépôt hello2 vers le dépôt hello-pull. Pour ce faire, on utilisera la commande hg pull. Mais pour ne pas faire ce portage en aveugle, on commence par faire le point des changements, avec hg incomming :
$ cd hello-pull/ $ hg incoming ../hello2 comparing with ../hello2 searching for changes changeset: 5:9f634748bf7e tag: tip user: Christophe Guyeux <christophe.guyeux@univ-fcomte.fr> date: Sat May 23 16:44:09 2009 +0200 summary: Un plus beau coucou.
La commande hg incomming permet ainsi de savoir ce que fera hg pull.
On peut alors effectuer notre changement, en s'assurant avec hg tip que le changeset n'est plus le même :
hello-pull$ hg tip changeset: 4:2278160e78d4 tag: tip user: Bryan O'Sullivan <bos@serpentine.com> date: Sat Aug 16 22:16:53 2008 +0200 summary: Trim comments. hello-pull$ hg pull ../hello2 pulling from ../hello2 searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (run 'hg update' to get a working copy) hello-pull$ hg tip changeset: 5:9f634748bf7e tag: tip user: Christophe Guyeux <christophe.guyeux@univ-fcomte.fr> date: Sat May 23 16:44:09 2009 +0200 summary: Un plus beau coucou.
Notez qu'il est possible de préciser quel changeset on souhaite récupérer, avec un hg pull -r9f634748bf7e, afin d'être sûr qu'il n'y a pas eu de nouveau commit sur le dépôt distant entre le hg incomming et le hg pull.
Le dépôt réel a donc été modifié. Cependant, le répertoire de travail est toujours le même :
hello-pull$ cat hello.c /* * Placed in the public domain by Bryan O'Sullivan. This program is * not covered by patents in the United States or other countries. */ #include <stdio.h> int main(int argc, char **argv) { printf("hello, world!\"); return 0; }
En effet, comme l'a signalé Mercurial au moment du hg pull, il faut mettre à jour le répertoire de travail, avec la commande hg update. Pour Mercurial, dépôt réel et répertoire de travail sont deux choses séparées.
Faisons donc la mise à jour de notre répertoire de travail, et vérifions que hello.c est modifié :
$ hg update tip 1 files updated, 0 files merged, 0 files removed, 0 files unresolved $ grep printf hello.c printf("hello, world!\"); printf("hello again!\n");
Cette séparation des deux mises à jour (du dépôt réel, et du répertoire de travail) est saine, et permet d'éviter des erreurs. On peut cependant réaliser les deux mises à jour en une commande, en passant l'argument -u à hg pull :
$ hg pull -u
Pour mettre à jour son répertoire de travail vers une révision présente dans son dépôt local, on utilise la commande hg update n, où n est le numéro de révision qui nous intéresse.
Si l'on ne précise pas de numéro de révision, alors c'est la dernière enregistrée (la tip) dans le dépôt local qui sera prise.
Pour connaître quelle est la révision à la base du répertoire de travail, on utilise la commande hg parents.
Illustrons cela :
$ hg update 2 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg parents changeset: 2:fef857204a0c user: Bryan O'Sullivan <bos@serpentine.com> date: Sat Aug 16 22:05:04 2008 +0200 summary: Introduce a typo into hello.c. $ hg update 2 files updated, 0 files merged, 0 files removed, 0 files unresolved $ hg parents changeset: 5:b6fed4f21233 tag: tip user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:53 2009 +0000 summary: Added an extra line of output
Plutôt que de récupérer les modifications à partir d'un autre dépôt, on peut vouloir déposer ses modifications sur un autre dépôt.
A nouveau, le répertoire de travail cible n'est pas modifié : il faut un update, pour. Seulement, il n'existe pas d'option -u pour hg push : il ne faudrait pas que quelqu'un d'extérieur mette à jour notre répertoire de travail...
Quand un dépôt est cloné, Mercurial enregistre la location du dépôt d'origine dans le fichier .hg/hgrc du nouveau dépôt.
$ cat .hgrc [paths] default = http://www.selenic.com/repo/hg
Quand aucune location n'est précisée lors d'un hg push/pull, ou d'un hg incoming/outgoing, ce sera cette location qui sera prise en compte.
On peut distinguer le dépôt par défaut pour le push de celui du pull, comme suit :
$ cat .hgrc [paths] default = http://www.selenic.com/repo/hg default-push = http://hg.example.com/hg
Pour créer un nouveau projet, on utilise la commande hg init :
$ hg init monprojet
Un répertoire du nom de monprojet, qui contient un sous-répertoire caché .hg, est alors créé.
Pour ajouter des fichiers au nouveau projet, il suffit de les copier dans le nouveau répertoire, et d'utiliser la commande hg add.
$ cd monprojet $ cp ../hello.c . $ cp ../goodbye.c . $ hg add adding goodbye.c adding hello.c $ hg status A goodbye.c A hello.c
Il reste à commiter cet import initial :
$ hg commit -m 'Import initial'
Supposons qu'au moment d'un hg pull, un récupère un fichier hello.c différent de celui stocké dans notre tip. Mercurial nous signale qu'il faudra fusionner (merge) ces fichiers :
$ hg pull ../my-hello pulling from ../my-hello searching for changes adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files (+1 heads) (run 'hg heads' to see heads, 'hg merge' to merge)
Les heads («têtes de dépôts») sont fondamentales à comprendre à ce niveau.
En fait, à chaque nouvelle révision, Mercurial stocke de quel parent cette révision est l'enfant. Une révision sans fille est une head.
Dans le problème qui nous intéresse, il y a plusieurs heads :
On peut le constater avec la commande hg heads :
$ hg heads changeset: 6:b6fed4f21233 tag: tip parent: 4:2278160e78d4 user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:53 2009 +0000 summary: Added an extra line of output changeset: 5:5218ee8aecf3 user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:55 2009 +0000 summary: A new hello for a new day.
On remarque, au passage, que le numéro de révision du tip a été automatiquement incrémenté de 1, vu que le tip doit toujours représenter la dernière révision.
Quand on a plusieurs heads, il faut les fusionner en un seul.
En effet, tant que l'on n'a pas fusionné les deux révisions heads, il n'est pas possible d'actualiser le répertoire de travail par un hg update :
$ hg update abort: crosses branches (use 'hg merge' or 'hg update -C')
A ce stade, Mercurial pense qu'il faut fusionner ces branches. Il nous signale cependant que l'on peut forcer la mise à jour, avec l'option -C.
Pour faire cette fusion, on utilise la commande hg merge :
$ hg merge merging hello.c 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit)
Cette commande met à jour le répertoire de travail, en fusionnant les données des deux parents. La commande hg parents permet de connaître les parents du répertoire de travail (on peut préciser une révision en option) :
$ hg parents changeset: 5:5218ee8aecf3 user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:55 2009 +0000 summary: A new hello for a new day. changeset: 6:b6fed4f21233 tag: tip parent: 4:2278160e78d4 user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:53 2009 +0000 summary: Added an extra line of output
Pour finaliser cette fusion, il faut encore commiter le répertoire de travail, contenant les données fusionnées :
$ hg commit -m 'Données fusionnées'
On aboutit à une nouvelle révision tip, qui admet deux parents :
$ hg tip changeset: 7:ecb0e17b2a4e tag: tip parent: 5:5218ee8aecf3 parent: 6:b6fed4f21233 user: Bryan O'Sullivan <bos@serpentine.com> date: Tue May 05 06:55:56 2009 +0000 summary: Données fusionnées
Les fusions peuvent être conflictuelles, quand par exemple les deux heads modifient la même ligne. Mercurial ne pouvant pas prendre tout seul la bonne décision, c'est à nous d'agir.
Pour nous aider, Mercurial fait appel à des outils externes, pouvant faciliter la résolution du conflit :
Le programme de votre choix peut être spécifié dans le fichier de configuration ~/.hgrc, comme suit :
# This is a Mercurial configuration file. [ui] username = Christophe Guyeux <christophe.guyeux@univ-fcomte.fr> editor = vim merge = kdiff3
ou simplement dans votre terminal, en fixant la valeur de HGMERGE au programme souhaité :
$ export HGMERGE=merge $ hg merge
Le programme linux merge, en ligne de commande, réalise automatiquement les fusions sans conflit, et insère du texte (des marqueurs) là où il y a eu conflit, comme suit :
$ cat letter.txt Greetings! <<<<<<< /tmp/tour-merge-conflictgW7-1Z/scam-merge/letter.txt I am Shehu Musa Abacha, cousin to the former I am Alhaji Abba Abacha, son of the former >>>>>>> /tmp/letter.txt~other.c6Rq0s Nigerian dictator Sani Abacha.
La syntaxe se comprend assez bien. Il faudra alors éditer le fichier posant problème, régler chaque conflit à la main, et sauvegarder le tout. Dans notre exemple, on pourrait avoir, après sauvegarde des modifications :
$ cat letter.txt Greetings! I am Shehu Musa Abacha, cousin to the former Nigerian dictator Sani Abacha.
Dans le cas de l'utilisation de l'outil système merge, quand ce dernier n'a pas été capable de réaliser la fusion, ou quand tous les conflits n'ont pas été réglés au travers d'un quelconque outil graphique, Mercurial en est informé, et attend que l'on y revienne.
Alors, quand tous les conflits ont été solutionnés à la main, ou grâce à meld, kdiff3, il faut en informer Mercurial, avec la commande hg resolve :
$ hg resolve -m letter.txt $ hg commit
Mercurial ne s'occupe que des fichiers du répertoire de travail pour lesquels on a demandé une surveillance.
Illustrons cela...
$ hg init un_exemple $ cd un_exemple $ echo a > unfichier.txt $ hg status ? unfichier.txt $ hg add unfichier.txt $ hg status A un fichier.txt
Le fichier unfichier.txt a changé de statut : il sera surveillé.
Signalons que...
Si l'on souhaite supprimer un fichier de notre répertoire de travail, et donc ne plus le surveiller, on peut utiliser la commande hg remove, comme suit :
$ hg remove unfichier.txt $ hg status D un fichier.txt
Le fichier sera supprimé du répertoire de travail, et ne sera plus surveillé lors des prochaines révisions. Bien sûr, il reste présent dans l'historique des dépôts, on ne peut pas toucher aux révisions passées.
Si un fichier a été supprimé à la main, sciemment ou non, avec un simple rm, is sera considéré comme manquant (signalé par un !) :
$ rm unfichier.txt $ hg status ! un fichier.txt
Deux situations sont possibles :
$ hg remove --after unfichier.txt
$ hg revert unfichier.txt
Il existe une commande, hg addremove, qui permet de réaliser d'un bloc
On peut encore le signaler lors du commit, avec l'option -A. En d'autres termes,
$ hg commit -A
est équivalent à
$ hg addremove $ hg commit
Mercurial possède une commande hg copy pour réaliser la copie d'un fichier du répertoire réel.
L'avantage de cette commande sur un simple cp s'éclaire sur l'exemple suivant...
Supposons qu'une autre personne possède un clone de notre projet, et qu'elle corrige un bug sur un fichier toto, que l'on a entre temps copié en toto2. Cette personne a donc un toto, et nous deux, mais les corrections qu'elle apporte à toto intéressent nos deux fichiers.
Au moment de faire la fusion des dépôts, Mercurial se souviendra de l'histoire commune de toto et toto2. S'il n'y a pas conflit, il portera les modifications à toto et toto2. Sinon, il lancera deux fois l'interface graphique kdiff3 : pour toto et toto2, par exemple.
Par contre, si les deux dépôts ont les deux fichiers toto et toto2, copiés comme ci-dessus, alors une modification sur toto n'affectera pas toto2.
Si ce comportement n'est pas souhaité, on peut utiliser la commande système cp, suivi d'un hg add : les modifications sur le fichier source n'affecteront pas la cible de la copie.
Notons pour finir les points suivants :
Mercurial possède une commande hg mv pour renommer des fichiers surveillés.
Le comportement est similaire à hg copy, quant au suivi des modifications, à l'argument --after de hg mv, et à l'argument -C de hg status.
Il sait enfin gérer les conflits qui peuvent intervenir dans de tels déplacements, quand par exemple un même fichier est renommé de deux manières différentes dans deux dépôts différents.
Quand on a fait une modification dans le répertoire de travail, que l'on regrette, par exemple,
$ hg add toto
alors que l'on ne souhaite pas surveiller le fichier toto, on peut utiliser la commande revert pour annuler ladite modification.
$ hg revert toto
Les commandes Mercurial acceptent encore des motifs, comme les expressions rationnelles. La syntaxe pour les utiliser est la suivante :
commande 'type_de_motif:motif'
où type_de_motif correspond au type de motif :
Si on ne précise rien, alors glob sera utilisé.
L'exemple suivant
$ hg add 'glob:*py'
permet de relever tous les scripts python du répertoire courant, quand
$ hg add 'glob:**py'
permet de faire cette recherche en incluant les sous-répertoires. La commande d'après
$ hg status 'glob:**[nr-t]'
S'effectue sur tous les fichiers contenant n, r, s ou t, dans le répertoire courant ou ses sous-répertoires, quand
$ hg status 'glob:**[nr-t]!'
fait pareil, mais pour tous les fichiers ne contenant pas n, r, s ou t.
Enfin,
$ hg status 'glob:*.{in,py}'
recherche tous les fichiers du répertoire courant, qui ont pour extension .in ou .py.
Les expressions rationnelles sont celles de Python, qui est celle de Perl. C'est la syntaxe la plus commune, utilisée aussi par Java.
A la différence de glob, re recherche le motif dans tout le dépôt. Si, par exemple, on veut effectuer une recherche de motifs dans le sous-répertoire toto dans lequel on se situe, il faut quand même le mettre dans le motif :
toto/$ hg status 're:toto/*.{in,py}'
Dans la ligne suivante, on recherche le statut de tous les fichiers se terminant par .in :
$ hg status -I '*.in' ? MANIFEST.in
L'option -I signifie inclure. On peut, a contrario, exclure les fichiers, avec l'option -X.
On peut cumuler plusieurs -I et -X.
Tant que les conflits n'ont pas été résolu, automatiquement ou non, le commit est impossible.
Chaque fichier nécessitant une fusion, au cours d'un merge, possède l'un des deux états suivant :
Pour connaître l'état de ces fichiers, en obtenir la liste :
$ hg resolve -l U myfile.txt
Bien sûr, pour que la commande hg merge puisse se faire, il faut que tous les états soient à R.
Pour se faire, on rappelle que l'on peut relancer l'outil de résolution des conflits, avec la commande suivante
$ hg resolve
Cette commande accepte que lui soit spécifié :
On peut fixer à la main l'état d'un fichier :
$ hg resolve --mark toto
Cela peut s'avérer nécessaire, dans des cas non évident de fusion.
À la base, la commande hg diff renvoie des informations semblables à la commande système diff :
$ hg rename a b $ hg diff diff -r 4b300eaa7199 a --- a/a Tue May 05 06:55:21 2009 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000-1,1 +0,0
-a diff -r 4b300eaa7199 b --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/b Tue May 05 06:55:21 2009 +0000-0,0 +1,1
+a
On peut obtenir un affichage plus lisible, peut-être plus riche en informations, avec l'option -g (--git) :
$ hg diff -g diff --git a/a b/b rename from a rename to b
Cette option permet aussi d'afficher des changements de droits portant sur des fichiers, ce que ne fait pas le diff de base :
$ chmod +x a $ hg status M a $ hg diff $ hg diff -g diff --git a/a b/a old mode 100644 new mode 100755
On peut donner des noms (de version, etc.) à certaines révisions, qui à notre sens constituent une étape dans notre projet.
On utilise pour se faire la commande hg tag, comme suit
$ hg tags tip 5:018c8e652c1f $ hg tag v0.1 $ hg tags tip 6:610d446fa0ff v0.1 5:018c8e652c1f
On rappelle que, pour obtenir le répertoire tel qu'il était à ce moment-là, on procède ainsi :
$ hg update v0.1
Cette commande update peut d'ailleurs être utilisée pour retourner à une vieille révision, afin de lui donner un nom :
$ hg update 2 $ hg tag v0.05 $ hg tags tip 7:057ff583e6f7 v0.1 5:018c8e652c1f v0.05 2:fef857204a0c
On peut cloner le dépôt réel tel qu'il était à une révision donnée, avec l'option -r (seuls les révisions précédant ce tag seront présent dans le nouveau dépôt réel) :
$ cd .. $ hg clone -rv1.0 main stable
On peut tirer profit de plusieurs manières de cette gestion des versions. On peut par exemple penser avoir :
Le dépôt stable ne reçoit plus que d'éventuels corrections de bugs : si quelqu'un souhaite fixe un bug, présent dans cette version stable,
On peut aussi penser à des dépôts spécialisés, travaillant séparément sur une version stable, et œuvrant sur un point précis (interface graphique, sécurité, etc.)
Pour supprimer un tag :
$ hg tag --remove nom_du_tag
et pour le renommer, on le tag à nouveau, avec l'option -f (forcer).
Les tags sont stockés dans .hgtags :
Il peut être intéressant de s'en souvenir, si une fusion pose problème à cause d'un conflit de tags.
On peut passer une option -l à hg tag, pour signaler que l'on souhaite un tag local. Ce nom de révision ne sera alors pas transmis aux autres dépôts.
Cela peut s'avérer utile pour annoter des révisions, genre : le bug machin semble apparaître à cette révision.
Ces tags locaux sont enregistrés dans .hg/localtags.
Une fois que l'on a atteint ce qui peut être considéré comme une version stable du projet, le mieux à faire est de le cloner :
$ hg clone mon_projet mon_projet-1.0
Il ne faudrait plus toucher à ce clone, à moins que ça ne soit pour une correction de bugs.
Auquel cas, il conviendrait de procéder ainsi :
Cette méthode artisanale est simple, et souvent suffisante.
Plutôt que d'œuvrer sur plusieurs dépôts, on peut créer des branches au sein d'un unique dépôt.
On peut voir les branches existantes par la commande.
$ hg branches
et savoir dans quelle branche on se situe par la commande
$ hg branch
Il existe toujours au moins une branche, pour tout dépôt : la branche default. On la voit en commentaire lors d'un hg commit.
Pour créer une nouvelle branche, on utilise la commande
$ hg branch nom_nouvelle_branche
Les prochains commits se feront dans cette nouvelle branche, et cela sera signalé dans hg log et hg tip...
La commande hg update mettra à jour le répertoire de travail, à partir du tip de la branche dans laquelle ce répertoire se situe. Il en va de même pour hg pull -u.
Pour changer la branche lors de cet update, on passe l'option -c à hg update, en précisant le nom de la branche souhaitée.
Commençons par signaler que la fusion n'est pas symétrique dans Mercurial.
En effet, considérons que l'on souhaite réaliser une fusion entre les révisions 17 et 23, appartenant à deux branches distinctes.
Si les dépôts sur lesquels on travaille sont publiques, on peut s'arranger pour que des modifications ne puissent se faire d'une branche de développement vers une branche stable.
On parle de crochet, et en pratique, on place un texte semblable au suivant, dans le fichier hgrc du dépôt publique :
[hooks] pretxnchangegroup.branch = hg heads --template '{branches} ' | grep mybranch
Tous les changements n'ayant pas le bon nom de branche seront alors bloqués.
Avec la commande hg serve lancée à l'intérieur d'un dépôt Mercurial, on lance un serveur HTTP, accessible localement :
$ firefox http://localhost:8000
voire de l'extérieur, si votre machine y est atteignable, en remplaçant localhost par votre IP ou nom de domaine. Noter que l'on peut changer le port, avec une option -p.
Ce serveur permet :
$ hg clone http://localhost:8000 mon_clone_local
Dans la mesure où aucun firewall ne pose problème...
Il n'est bien sûr pas possible d'écrire dedans de l'extérieur.
Il peut arriver que l'on fasse un commit malencontreux, que l'on modifie à tord un fichier, etc. Mercurial propose différentes commandes pour annuler ces modifications.
On peut annuler le dernier commit à l'aide de la commande
$ hg rollback
Cette commande marche aussi pour annuler un hg pull commis par erreur, mais pas pour un hg push, dont les conséquences se font sentir sur un autre dépôt que le nôtre.
Ce rollback ne marche qu'une fois, on ne peut pas annuler deux commits de suite.
Si les modifications que l'on a apporté à un fichier, dans notre répertoire de travail, ne nous conviennent pas, et si l'on souhaite revenir à la version tip de ce fichier, on peut utiliser la commande revert :
$ hg revert mon_fichier
L'état du fichier, avant la commande revert, est sauvegardée automatiquement, au cas où, dans mon_fichier.orig.
Cette commande revert sert dans les situations suivantes :
Si l'on a déposé une première révision a, suivi d'une seconde révision b, et que l'on s'aperçoit ensuite que a était incorrect, on peut utiliser la commande hg backout, qui permettra l'ajout d'une révision c correcte.
Cette commande permet d'annuler automatiquement les effet d'une révision. Il n'y a pas de suppression, ou de changement d'une révision passée. La nouvelle révision issue de cette commande sera constituée du répertoire actuel dans lequel les effets de la révision incriminée ont été intégralement supprimés.
Donnons un exemple complet. On commence par effectuer quelques commits...
$ hg init depot $ cd depot/ $ echo premier changement >> monfichier $ hg add monfichier $ hg commit -m "premier changement" $ echo deuxieme changement >> monfichier $ hg commit -m "deuxieme changement" $ echo troisieme changement >> monfichier $ hg commit -m "troisieme changement" $ cat monfichier premier changement deuxieme changement troisieme changement
Le premier commit a créé la première ligne de monfichier, le deuxième commit a rajouté la deuxième ligne, et le troisième commit est à l'origine de la dernière ligne de monfichier.
On souhaite annuler le «deuxième changement», correspondant à la révision n°1 (i.e. la seconde révision), que l'on passe en argument.
$ hg backout -m "Annulation du 1er changement" 1 reverting monfichier created new head changeset 3:c09ca3edf5c0 backs out changeset 1:c16fe0cd5b48 the backout changeset is a new head - do not forget to merge (use "backout --merge" if you want to auto-merge)
Il reste à effectuer les étapes traditionnelles : une fusion, suivie d'un ultime commit...
$ hg merge merging monfichier 0 files updated, 1 files merged, 0 files removed, 0 files unresolved (branch merge, don't forget to commit) $ hg ci -m "Annulation" $ hg update 0 files updated, 0 files merged, 0 files removed, 0 files unresolved $ cat monfichier premier changement troisieme changement
Tout est bon. Et on en est à la cinquième révision :
$ hg tip changeset: 4:34318326ff8f tag: tip parent: 2:05b8baa7a882 parent: 3:c09ca3edf5c0 user: Christophe Guyeux <christophe.guyeux@univ-fcomte.fr> date: Sat May 30 14:08:04 2009 +0200 summary: Annulation
Notons pour finir que l'on peut effectuer plus rapidement ces opérations, en passant l'option --merge à hg backout. En fait, la plupart du temps, il est plus judicieux de passer systématiquement cette option.
Signalons aussi que l'étape de fusion n'intervient pas quand on annule la dernière révision.
Quand
alors on se retrouve avec deux «heads» :
Le dépôt contient deux «heads» : $r_1$ et $r_3$.
Du fait de l'option --merge, ces deux heads sont ensuite fusionnés, et le résultat de cette fusion est le nouveau répertoire de travail.
Il reste à étudier le résultat, et à le commiter s'il convient.
En l'absence de l'option --merge, toutes ces étapes doivent se faire à la main.
Signalons pour finir qu'un backout ne peut s'effectuer que si le répertoire de travail est propre, i.e. si hg status ne renvoie rien.
On donne, dans ce qui suit, un exemple non élémentaire de l'utilisation de backout.
Supposons que l'on ait fait une mauvaise fusion (révision $r_4$) des révisions $r_2$ et $r_3$, et que l'on ait en plus commité de nouveaux changements (par exemple, une révision $r_5$).
On souhaiterait donc refaire la fusion, sans pour autant perdre les nouveaux changements $r_5$.
On peut procéder ainsi...
$ hg backout --rev=4 --parent=2
$ hg backout --rev=4 --parent=3
On peut cloner, faire un push/pull d'un dépôt situé sur un serveur ssh distant, cela ne pose aucun problème :
$ hg clone ssh://login@nom_de_machine//emplacement_depot_distant nom_depot_local
On met deux // pour préciser un chemin absolu, et un seul / pour un chemin relatif, à partir du home de M. login.
On peut exiger que les données transférées soient compressées, pour réduire la durée du transfert. Pour ce faire, il faut mettre ceci dans son ~/.hgrc
[ui] ssh = ssh -C
Mercurial dispose d'un système pour ignorer des fichiers (dans le statut, etc.) tels que les résidus de compilation.
Pour ce faire, il faut créer un fichier appelé .hgignore à la racine du dépôt, qui contient une ligne par motif refusé. Voici un exemple :
syntax: glob *.orig *.swp *.rej *~ *.o tests/*.err syntax: regexp .*\#.*\#$
Il est judicieux d'ajouter ce fichier à votre dépôt.
Supposons que la dernière version de votre programme présente un bogue, qui n'était pas présent il y a quelques temps. Vous souhaitez annuler la révision de ce bogue... encore faut-il savoir de quelle révision il s'agit !
La commande hg bisect permet de le savoir, comme elle permet de déterminer quelle révision a introduit un ralentissement de votre code, ou une augmentation surprenante de sa taille...
Le principe est de faire une recherche dichotomique à travers les révisions, entre une révision que l'on sait exempte du bogue incriminé, et une révision qui le contient.
La recherche s'initialise ainsi :
$ hg bisect --reset
Ensuite, il faut signaler
afin de pouvoir démarrer la recherche dichotimique.
Supposons que la révision actuelle, numérotée 34, est mauvaise («bad»), alors que la révision n°10 était sans bogue («good»). On le signale ainsi :
$ hg bisect --bad $ hg bisect --good 10
(On n'a pas spécifié de numéro de révision derrière «bad», vu que la mauvaise révision considérée est la dernière révision).
La recherche dichotomique commence alors. Mercurial nous informe qu'il reste 24 révisions à analyser, que l'on teste actuellement la révision 22 (à mi-chemin entre 10 et 34), et que cette recherche pourra se terminer en 4 tests.
Le répertoire de travail contient donc la révision 22. On la teste, et l'on s'aperçoit qu'elle ne contient pas le bogue. On le signale ainsi :
$ hg bisect --good
Et la recherche dichotomique se poursuit, jusqu'à trouver la révision fautive.
On peut penser créer un petit script pour tester la révision proposée. Cela permet d'automatiser un brin le processus, en évitant les erreurs.
Notons enfin que si, pour une quelconque raison, on n'est pas en mesure d'identifier le problème dans la révision proposée, on peut faire un
$ hg bisect --skip
pour sauter cette révision.
Mercurial peut réaliser automatiquement certaines actions lorsqu'un événement particulier se produit dans le dépôt. On parle de «hook».
Ces «hooks» sont exécutés sur notre système, avec nos droits utilisateurs. Ils sont situés :
(En cas de doublon, le second est prioritaire.)
On peut voir la liste des «hooks» ainsi :
$ hg showconfig hooks
Remarquons les points suivants :
Voici une liste de «hooks» disponibles :
Par exemple, pour afficher la valeur hachée de la révision à chaque commit, on ajoutera ce qui suit dans le .hg/hgrc :
[hooks] commit = echo "Un commit de", $HG_NODE
Si l'on veut, par exemple, exécuter plusieurs tâches lors d'un commit, alors on devra écrire plusieurs lignes de la forme :
commit.nom_action = action
Par exemple,
[hooks] commit = echo Un commit de $HG_NODE commit.nom_action = echo -n "date du commit : ", date
Ces actions seront effectuées en suivant l'ordre lexicographique.
Les sites suivants :
propose un hébergement gratuit de vos projets Mercurial.