Jul 03, 2024

Wiki

Python

Aide

edit SideBar

Search

Mercurial


Préliminaires

Installation

Pour installer Mercurial :

  $ apt-get install mercurial

De l'aide

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/

Le dépôt

Présentation

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

  • la version actuelle,
  • l'historique des changements,
  • etc.

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 :

Le dépôt réel
C'est ce qui se trouve dans .hg, qui contient l'historique différentiel de toutes les révisions.
Le répertoire de travail
Le reste du dépôt, sur lequel on œuvre. Notre bac à sable, quoi.

Copier un dépôt

Pour copier un dépôt, on préférera la commande clone à un simple cp (qui marcherait), comme par exemple :

Les raisons de préférer cette commande sont multiples :

  • Possibilité de copier à travers le web : en HTTP comme dans l'exemple ci-dessus, en SSH...
  • Mémorisation du lieu d'où vient la copie, pour ensuite pouvoir communiquer avec.

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.

Backup d'un dépôt

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.

Les logs

Visionner l'historique des changements

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.

  • La partie hexadécimale est unique, seule cette révision possèdera cette valeur hachée sur terre.
  • Le premier nombre identifie localement la révision : c'est un compteur qui s'incrémente à chaque commit. Il est plus facile à utiliser, mais n'est pas unique. En effet, il dépend du dépôt considéré.

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.

Visualisation avancée

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;
   }

Le statut

La commande status

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

La commande diff

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;
   }

L'enregistrement d'une version

Présentation

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 nom du responsable du commit

Le processus suivi pour affecter un nom à un commit s'effectue dans l'ordre suivant :

  1. Si un argument -u est fourni à la commande commit, alors ce qui le suit est le nom d'utilisateur.
  2. Sinon, on prend le contenu de la variable d'environnement \$HGUSER, si elle existe.
  3. Sinon, on regarde le contenu de l'entrée username du fichier ~/.hgrc, s'il existe (voir ci-dessous, cette solution étant à préférer).
  4. Sinon, on regarde si une variable d'environnement EMAIL existe.
  5. Sinon, enfin, mercurial interroge le système pour trouver un nom d'utilisateur, et un nom de machine. Il construira son nom à partir de ces infos. Auquel cas, un warning signalera que ce genre de procédé ne retourne pas une information très pertinente.

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.

Le commit

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 :

  • Un hg commit, sans argument, intégrera toutes les modifications de tout le répertoire de travail dans le dépôt réel. Ce comportement est différent de svn, qui n'intègre que les modifications du répertoire courant (et de ses sous-répertoires).
  • Si l'on abandonne, sans sauvegarder, l'éditeur, alors le commit n'aura pas lieu.

Le tip

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;
   }

Récupérer (pull) des changements

Présentation

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.

État des lieux

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

Le point sur les différences (incomming)

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.

La récupération (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.

Modifications du dépôt réel et du répertoire de travail

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

Changer la révision du répertoire de travail

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

Déposer (push) des changements

Comment déposer des changements

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.

  • hg push est alors la commande correspondant à hg pull,
  • hg outgoing correspond à hg incomming.

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

Les locations par défaut

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

Création d'un nouveau projet

Création du projet

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

Ajout de fichiers

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'

La fusion (merge)

Présentation du problème

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

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 :

  • La tip, résultat du dernier commit local.
  • Le head résultant du pull : ce dépôt distant avait aussi son propre tip.

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.

Réaliser la fusion

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

Finalisation

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

Le cas des fusions conflictuelles

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 :

  • merge, un outils en ligne de commande,
  • des outils graphiques : meld, kdiff3 (peut-être le meilleur choix pour Mercurial), etc.

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

Les fichiers surveillés

Ajouter des fichiers à surveiller

Mercurial ne s'occupe que des fichiers du répertoire de travail pour lesquels on a demandé une surveillance.

  • Pour savoir quel est le status d'un fichier, on utilise hg status.
  • Pour signaler qu'un fichier/dossier doit être surveillé, on utilise la commande hg add.

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

  • On peut utiliser l'option -n (genre, hg add -n), pour afficher ce qui sera réalisé par la commande, sans la réaliser.
  • On peut aussi passer l'option -q pour ne pas afficher les fichiers affectés, et -v pour au contraire être prolixe.
  • La commande status ne donne des informations que sur les fichiers intéressants : ceux qui ne sont pas surveillés, ceux qui ont été modifiés, etc.
  • Par défaut, l'information est retournée par rapport au répertoire racine du dépôt, et concerne tous les fichiers et répertoires du dépôt. On peut forcer un affichage relatif au répertoire courant, en ajoutant `hg root` en argument à la commande considérée.
  • Mercurial ne sait pas surveiller des répertoires vides. Si, vraiment, on souhaite une telle surveillance, alors il faut remplir ce répertoire, par exemple avec un fichier caché .hidden vide.

Arrêter de surveiller un fichier, que l'on supprime

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.

Les fichiers supprimés à la main

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 :

  • Nous souhaitons effectivement supprimer ce fichier, mais nous avons oublié d'utiliser la commande hg remove. Alors on peut procéder ainsi :
  $ hg remove --after unfichier.txt
Le statut de unfichier.txt passe alors à D.
  • On a fait cette suppression par erreur. On peut alors récupérer le fichier concerné, ainsi :
  $ hg revert unfichier.txt

Ajout et suppression automatique

Il existe une commande, hg addremove, qui permet de réaliser d'un bloc

  • la surveillance des fichiers nouvellement créés,
  • la fin de la surveillance pour les fichiers qui ont disparus

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

Manipuler les fichiers surveillés

La copie de fichiers

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 :

  • On peut voir qu'un fichier a ainsi été copié, avec la commande hg status -C.
  • Quand des répertoires sont passés à hg copy, la copie est récursive.
  • Si on a fait un cp toto tutu en voulant faire un hg copy, on peut le repréciser après coup avec un hg copy --after toto tutu.

Le déplacement

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.

Annuler des modifications

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

Utilisation de motifs (pattern)

Présentation

Les commandes Mercurial acceptent encore des motifs, comme les expressions rationnelles. La syntaxe pour les utiliser est la suivante :

commande 'type_de_motif:motif'

type_de_motif correspond au type de motif :

glob
les motifs du shell Unix,
re
les expressions rationnelles.

Si on ne précise rien, alors glob sera utilisé.

Exemples d'utilisation de glob

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.

Le cas des expressions rationnelles

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}'

Motifs à accepter, motifs à refuser

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.

La gestion des conflits

L'état des fichiers conflictuels

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 :

  • R (resolved), pour les fichiers dont la fusion a réussi, automatiquement ou à la main,
  • U (unresolved), pour ceux dont la fusion pose toujours problème, reste encore à faire.

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.

Résolution par resolve

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é :

  • un fichier, ou un répertoire : seuls les fichiers concernés, et à l'état U, seront considérés.
  • l'option -a, pour retenter la fusion de tous les fichiers conflictuels.

Résolution à la main

On peut fixer à la main l'état d'un fichier :

  • à resolved (R), ainsi :
  $ hg resolve --mark toto
  • à unresolved, avec l'option --unmark.

Cela peut s'avérer nécessaire, dans des cas non évident de fusion.

Diffs évolués

À 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

Les noms de révision

Nommer une révision

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

Tags et clones, mises à jour

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

Utilisation

On peut tirer profit de plusieurs manières de cette gestion des versions. On peut par exemple penser avoir :

  • plein de dépôts de développements, maintenus par diverses personnes,
  • un dépôt principal, qui reçoit les versions abouties des dépôts de développements,
  • un dépôt stable, qui est le clone d'une révision stable du dépôt principal.

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,

  1. il récupère ladite version stable,
  2. il la corrige dans son dépôt local,
  3. il dépose sa correction sur le dépôt stable,
  4. il porte cette modification du dépôt stable vers le dépôt principal, afin que tout le monde en profite.

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

Renommer, supprimer un tag

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 :

  • une ligne par nouveau tag,
  • chaque ligne est constituée de la valeur hachée du changeset, suivi du nom (tag) de la révision concernée.

Il peut être intéressant de s'en souvenir, si une fusion pose problème à cause d'un conflit de tags.

Les tags locaux

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.

Les branches de développement

La manière artisanale

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 :

  1. Cloner mon_projet-1.0 en mon_projet-1.0-bugfix.
  2. Œuvrer dans mon_projet-1.0-bugfix, jusqu'à la correction du bug.
  3. Porter la modification de mon_projet-1.0-bugfix vers mon_projet-1.0, puis de mon_projet-1.0 vers mon_projet.

Cette méthode artisanale est simple, et souvent suffisante.

Utiliser la commande branch

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.

Créer une nouvelle branche

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

Gérer les branches multiples

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.

La fusion au-travers de branches

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.

  • On peut commencer par faire un hg update 17, suivi d'un hg merge 23, alors la modification (ajout de révision) se fera dans la branche à laquelle appartient la révision 17.
  • Si on le fait dans l'autre sens, alors la nouvelle révision se fera dans la branche de 23.

Protéger des branches

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.

La commande hg serve

Avec la commande hg serve lancée à l'intérieur d'un dépôt Mercurial, on lance un serveur HTTP, accessible localement :

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 :

  • de parcourir agréablement l'historique des révisions, de voir (au travers d'un navigateur) les différences entre deux révisions données, etc.
  • de récupérer (hg clone ou hg pull) votre dépôt :
  $ 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.

Rollback et Revert

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.

Le rollback

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.

La commande revert

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 :

  • Annuler les modifications apportées à un fichier.
  • Annuler un hg add : enlever le statut A audit fichier (sans le modifier).
  • Restaurer un fichier que l'on a supprimé à la main (i.e. avec un simple rm).
  • Annuler le statut D d'un fichier sur lequel on a appliqué un hg remove, et restaurer ledit fichier tel qu'il était dans le parent du répertoire de travail.
  • Annuler le statut A d'un fichier copié par hg copy.

La commande backout

Présentation

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.

Exemple

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.

Le backout plus en détail

Quand

  • un backout est demandé pour une révision $r_1$,
  • le parent du répertoire était $r_2$,
  • l'option --merge a été passée,

alors on se retrouve avec deux «heads» :

  • $r_2$, d'une part,
  • et une nouvelle révision $r_3$, issue de l'annulation de $r_1$, et dont $r_1$ est un parent.

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.

Annuler les effets d'une mauvaise fusion

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

  • Lancer la commande
  $ hg backout --rev=4 --parent=2
pour annuler les effets de la révision $r_4$ (la fusion), et à chaque fois préférer la révision $r_2$ quand un choix de révision a lieu.
On obtient ainsi une révision $r_6$.
  • Faire de même, mais en préférant systématiquement la révision $r_3$, i.e. la deuxième révision en jeu lors de la fusion :
  $ hg backout --rev=4 --parent=3
Ce qui fournit une révision $r_7$.
  • Refaire la fusion, cette fois entre $r_6$ et $r_7$. On obtient une révision $r_8$.
  • Enfin, fusionner $r_8$ avec la dernière révision à garder, issue de la mauvaise fusion (dans notre exemple, $r_5$).

Mercurial au travers de ssh

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

Fichier .hgignore

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.

La bissection

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

  • une bonne révision, i.e. sans bogue,
  • une mauvaise révision,

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.

Les hooks

Présentation

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 :

  • dans le home de l'utilisateur, plus précisément là : ~/.hgrc,
  • ou dans le répertoire Mercurial du projet considéré, c'est-à-dire .hg/hgrc.

(En cas de doublon, le second est prioritaire.)

On peut voir la liste des «hooks» ainsi :

  $ hg showconfig hooks

Remarquons les points suivants :

  • Lors d'un pull, il faudrait aussi voir les «hooks» du dépôt distant.
  • Ces «hooks» ne sont pas transmis lors d'un clone, ou d'un pull.

Liste des hooks

Voici une liste de «hooks» disponibles :

commit
Action à exécuter après un commit local.
precommit
Test à effectuer avant tout commit local. Si le test échoue, alors ne pas permettre le commit.
incomming
Action à exécuter quand une nouvelle révision arrive dans le dépôt, en provenance de l'extérieur.
outgoing
Action à exécuter quand un groupe de révisions est transmis à un dépôt externe.
prechangegroup
: Action à exécuter avant d'ajouter un groupe de révisions externes dans le dépôt local.
changegroup
Comme ci-dessus, en remplaçant «avant» par «après».
preoutgoing
Test effectué avant d'apporter un groupe de révisions vers un dépôt externe. Si le test échoue, pas de transmission.
pretag
Test effectué avant de créer un tag.
tag
Comme ci-dessus, mais en remplaçant «avant» par «après».
preupdate
Test effectué avant un update ou un merge.
update
Comme ci-dessus, mais après.

Un exemple

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.

Hébergement

Les sites suivants :

propose un hébergement gratuit de vos projets Mercurial.

Page Actions

Recent Changes

Group & Page

Back Links