Chapter 5. Utilisation quotidienne de Mercurial

Table of Contents

5.1. Informer Mercurial des fichiers à suivre
5.1.1. Nommage des fichiers explicite versus implicite
5.1.2. Mercurial suit les fichiers, pas les répertoires
5.2. Comment arrêter de suivre un fichier
5.2.1. Supprimer un fichier n'affecte pas son historique
5.2.2. Fichiers manquants
5.2.3. Aparté : Pourquoi dire explicitement à Mercurial de supprimer un fichier ?
5.2.4. Raccourci utile—ajouter et supprimer des fichiers en une seule étape.
5.3. Copier des fichiers
5.3.1. Les résultats d'une copie durant une fusion (merge)
5.3.2. Pourquoi les changements devraient-ils suivre les copies ?
5.3.3. Comment faire des changements qui ne suivent pas une copie
5.3.4. Comportement de la commande hg copy
5.4. Renommer les fichiers
5.4.1. Renommer les fichiers et fusionner (merge) les changements
5.4.2. Renommages divergeants et fusion (merge)
5.4.3. Renommages et fusion convergeants
5.4.4. Autres cas épineux relatifs aux noms
5.5. Récupération d'erreurs
5.6. Traiter avec les fusions (merge) malicieuses
5.6.1. États de résolution des fichiers
5.6.2. Résoudre une fusion de fichier
5.7. Des diffs plus utiles
5.8. Quels fichiers suivre et lesquels éviter
5.9. Sauvegardes et miroirs

5.1. Informer Mercurial des fichiers à suivre

Mercurial ne suit pas les fichiers de votre dépôt tant que vous ne lui avez pas dit de les gérer. La commande hg status vous dira quels fichiers sont inconnus de Mercurial. Il utilise un ? pour montrer ces fichiers.

Pour informer Mercurial de suivre un fichier, utilisez la commande hg add. Une fois que vous avez ajouté un fichier, la ligne correspondante à ce fichier dans la sortie de hg status change de ? à A.

$ hg init add-example
$ cd add-example
$ echo a > myfile.txt
$ hg status
? myfile.txt
$ hg add myfile.txt
$ hg status
A myfile.txt
$ hg commit -m 'Added one file'
$ hg status

Après avoir exécuté un hg commit, les fichiers que vous avez ajoutés avant le commit ne seront plus listés dans la sortie de hg status. La raison de ceci est que, par défaut, hg status ne vous montre que les fichiers intéressants —ceux que vous avez (par exemple) modifiés, supprimés ou renommés. Si vous avez un dépôt qui contient un millier de fichiers, vous ne voudriez certainement que rarement entendre parler des fichiers que Mercurial suit, mais qui n'ont pas changés. (Vous pouvez quand même avoir cette information, nous y reviendrons plus tard.)

Une fois que vous ajoutez un fichier, Mercurial ne fait rien du tout avec celui-ci immédiatement. Au lieu de ça, il va prendre un "snapshot" de l'état du fichier la prochaine fois que vous exécuterez un commit. Il continuera ensuite à suivre les changements que vous avez fait au fichier chaque fois que vous committerez, et ce, jusqu'à ce que vous supprimiez le fichier.

5.1.1. Nommage des fichiers explicite versus implicite

Un comportement utile que Mercurial possède est que si vous passez le nom d'un répertoire à une commande, toute commande Mercurial la traitera comme : Je veux opérer sur chaque fichier dans ce répertoire et ses sous-répertoires.

$ mkdir b
$ echo b > b/somefile.txt
$ echo c > b/source.cpp
$ mkdir b/d
$ echo d > b/d/test.h
$ hg add b
adding b/d/test.h
adding b/somefile.txt
adding b/source.cpp
$ hg commit -m 'Added all files in subdirectory'

Remarquez que dans cet exemple, Mercurial affiche le nom des fichiers qu'il a ajouté, alors qu'il ne l'a pas fait lorsque nous avons ajouté le fichier nommé myfile.txt dans l'exemple précédent.

Ce qu'il se passe est que dans le premier cas, nous avons nommé explicitement le fichier à ajouter sur la ligne de commande. Ce que Mercurial suppose dans ce cas est que nous savons ce que nous faisons, il n'affiche donc rien en sortie.

Cependant, lorsque nous avons implicitement donné les fichiers à l'aide du nom d'un répertoire, Mercurial prend l'initiative d'afficher le nom de chaque fichier avec lequel il fait quelque chose. Ceci clarifie ce qu'il se passe et réduit la probabilité d'une mauvaise surprise restée silencieuse. Ce comportement est commun à la plupart des commandes Mercurial.

5.1.2. Mercurial suit les fichiers, pas les répertoires

Mercurial ne suit pas les informations sur les répertoires. En contrepartie, il suit le chemin vers un fichier. Avant de créer un fichier, il crée au préalable les répertoires manquants dans le chemin. Après avoir supprimé un fichier, il supprime chaque répertoire vide qui apparaît dans le chemin du fichier. Ceci apparaît comme une distinction triviale, cependant, cela a une conséquence pratique mineure : il n'est pas possible de représenter un répertoire totalement vide dans Mercurial.

Les répertoires vides sont rarement utiles. Il existe cependant des solutions alternatives et non intrusives que vous pouvez utiliser pour obtenir l'effet approprié. Les développeurs de Mercurial ont ainsi pensé que la complexité requise pour gérer les répertoires n'était pas aussi importante que le bénéfice que cette fonctionnalité apporterait.

Si vous avez besoin d'un répertoire vide dans votre dépôt, il existe quelques façons d'y arriver. L'une d'elles est de créer un répertoire et ensuite, de faire un hg add sur un fichier caché dans ce répertoire. Sur les systèmes de type Unix, tout fichier dont le nom commence avec un point (.) est considéré comme caché par la plupart des commandes et outils graphiques. Cette approche est illustrée ci-après.

$ hg init hidden-example
$ cd hidden-example
$ mkdir empty
$ touch empty/.hidden
$ hg add empty/.hidden
$ hg commit -m 'Manage an empty-looking directory'
$ ls empty
$ cd ..
$ hg clone hidden-example tmp
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ ls tmp
empty
$ ls tmp/empty

Une autre façon de s'attaquer au besoin d'un répertoire vide est de simplement d'en créer un dans vos scripts de construction avant qu'ils n'en aient le besoin.

5.2. Comment arrêter de suivre un fichier

Une fois que vous décidez qu'un fichier n'appartient plus à votre dépôt, utilisez la commande hg remove. Ceci supprime le fichier et informe Mercurial d'arrêter de le suivre (ce qui prendra effet lors du prochain commit). Un fichier supprimé est représenté dans la sortie de la commande hg status par un R.

$ hg init remove-example
$ cd remove-example
$ echo a > a
$ mkdir b
$ echo b > b/b
$ hg add a b
adding b/b
$ hg commit -m 'Small example for file removal'
$ hg remove a
$ hg status
R a
$ hg remove b
removing b/b

Après avoir fait un hg remove sur un fichier, Mercurial ne suivra plus aucun changement sur ce fichier, même si vous recréez un fichier avec le même nom dans votre répertoire de travail. Si vous recréez un fichier avec le même nom et que vous désirez que Mercurial suive ce dernier, faite simplement un hg add sur celui-ci. Mercurial saura alors que le nouveau fichier ne fait pas référence à l'ancien fichier qui portait le même nom.

5.2.1. Supprimer un fichier n'affecte pas son historique

Il est important de comprendre que supprimer un fichier n'a que deux effets.

  • Il supprime la version actuelle de ce fichier du répertoire de travail.

  • Il arrête, à partir du prochain commit, le suivi de Mercurial sur les changements qui ont lieu sur ce fichier.

Supprimer un fichier n'affecte en aucun cas l'historique du fichier.

Si vous mettez à jour le répertoire de travail à un changeset qui a été committé alors que le fichier que vous venez de supprimer était encore suivi, ce fichier réapparaîtra dans le répertoire de travail, avec le contenu qu'il avait lorsque vous aviez committé ce changeset. Si vous mettez à jour (update) le répertoire de travail à un changeset ultérieur dans lequel le fichier a été supprimé, Mercurial supprimera une nouvelle fois le fichier du répertoire de travail.

5.2.2. Fichiers manquants

Mercurial considère qu'un fichier que vous avez supprimé sans utiliserhg remove comme étant manquant. Un fichier manquant est représenté avec un ! en sortie de hg status. Les commandes Mercurial ne feront rien avec les fichiers manquants.

$ hg init missing-example
$ cd missing-example
$ echo a > a
$ hg add a
$ hg commit -m 'File about to be missing'
$ rm a
$ hg status
! a

Si votre dépôt contient un fichier que hg status reporte comme manquant, et que vous voulez que ce fichier reste supprimé, vous pouvez exécuter hg remove --after à tout moment pour dire à Mercurial que vous aviez bien voulu supprimer ce fichier.

$ hg remove --after a
$ hg status
R a

D'un autre côté, si vous avez supprimé le fichier manquant par accident, donnez à la commande hg revert le nom du fichier à retrouver. Il réapparaitra dans sa forme non modifiée.

$ hg revert a
$ cat a
a
$ hg status

5.2.3. Aparté : Pourquoi dire explicitement à Mercurial de supprimer un fichier ?

Vous pourriez vous demander pourquoi il est nécessaire de dire explicitement à Mercurial que vous souhaitez supprimer un fichier. Au début du développement de Mercurial, celui ci vous laissait pourtant supprimer un fichier sans souci ; Mercurial vous aurait automatiquement informé de l'absence du fichier lorsque vous auriez lancé un hg commit et arrêté de le suivre. En pratique, ceci a montré qu'il était trop facile de supprimer accidentellement un fichier sans le remarquer.

5.2.4. Raccourci utile—ajouter et supprimer des fichiers en une seule étape.

Mercurial offre une commande combinée, hg addremove, qui ajoute les fichiers non suivis et marque les fichiers manquants comme supprimés.

$ hg init addremove-example
$ cd addremove-example
$ echo a > a
$ echo b > b
$ hg addremove
adding a
adding b

La commande hg commit fournit aussi une option -A qui exécute le même ajouter-et-supprimer, immédiatement suivi d'un commit.

$ echo c > c
$ hg commit -A -m 'Commit with addremove'
adding c

5.3. Copier des fichiers

Mercurial fournit une commande hg copy qui vous permet de faire une nouvelle copie d'un fichier. Lorsque vous copiez un fichier en utilisant cette commande, Mercurial crée un enregistrement du fait que ce nouveau fichier est une copie du fichier originel. Il traite ces fichiers copiés spécialement lorsque vous fusionnez (merge) votre travail avec quelqu'un d'autre.

5.3.1. Les résultats d'une copie durant une fusion (merge)

Ce qu'il se passe durant une fusion (merge) est que les changements suivent une copie. Pour illustrer ce que cela veut dire de la meilleure façon, créons un exemple. Nous allons commencer avec le mini dépôt usuel qui contient un simple fichier.

$ hg init my-copy
$ cd my-copy
$ echo line > file
$ hg add file
$ hg commit -m 'Added a file'

Nous devons faire du travail en parallèle, ainsi, nous aurons quelque chose à fusionner (merge). Donc clonons notre dépôt.

$ cd ..
$ hg clone my-copy your-copy
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

De retour dans notre dépôt initial, utilisons la commande hg copy pour faire une copie du premier fichier que nous avons créé.

$ cd my-copy
$ hg copy file new-file

Si nous regardons ensuite à la sortie de la commande hg status, les fichiers copiés ont l'air de fichiers normalement ajoutés.

$ hg status
A new-file

Mais si nous passons l'option -C à hg status, il affiche une autre ligne de sortie : il s'agit du fichier source pour notre copie.

$ hg status -C
A new-file
  file
$ hg commit -m 'Copied file'

Maintenant, de retour dans le dépôt que nous avons cloné, créons un changement en parallèle. Nous allons ajouter une ligne de contenu au fichier original qui a été créé.

$ cd ../your-copy
$ echo 'new contents' >> file
$ hg commit -m 'Changed file'

Nous avons alors un fichier file modifié dans ce dépôt. Lorsque nous récupérons (pull) les changements depuis le premier répertoire et fusionnons (merge) les deux "heads", Mercurial propagera les changements que nous avons faits localement au fichier file dans sa copie new-file.

$ hg pull ../my-copy
pulling from ../my-copy
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)
$ hg merge
merging file and new-file to new-file
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ cat new-file
line
new contents

5.3.2. Pourquoi les changements devraient-ils suivre les copies ?

Ce comportement—des changements d'un fichier qui se propagent aux copies de ce fichier—peut sembler ésotérique, mais, dans la plupart des cas, c'est fortement souhaitable.

Pour commencer, souvenez-vous que cette propagation a lieu seulement lors des fusions (merge). Donc, si vous faites un hg copy sur un fichier, et par la suite modifiez le fichier original durant le cours normal de votre travail, rien n'aura lieu.

La deuxième chose à savoir est que les modifications ne se propageront à travers une copie que si le changeset à partir duquel vous faites une fusion (merge) n'a pas encore vu la copie.

La raison pour laquelle Mercurial fait ainsi est une règle. Imaginons que je corrige un important bug dans un fichier source et que je commit mes changements. Pendant ce temps, vous avez décidé de faire un hg copy du fichier dans votre dépôt, sans rien savoir au sujet du bug ou à propos de la correction. Vous avez alors commencé à "hacker" sur votre copie du fichier.

Si vous aviez récupéré (pull) et fusionné (merge) mes changements, et que Mercurial n'avait pas propagé les changements à travers les copies, votre nouveau fichier source contiendrait maintenant le bug, et à moins que vous ne sachiez qu'il faille propager la correction du bug à la main, le bug aurait subsisté dans votre copie du fichier.

En propageant automatiquement les changements qui fixent les bugs à partir du fichier original vers les copies, Mercurial prévient ce type de problèmes. À ma connaissance, Mercurial est le seul système de gestion de révisions qui propage les changements à travers les copies comme ceci.

Une fois que votre historique des changements a un enregistrement concernant une copie et qu'une fusion postérieure a eu lieu, il n'y a d'habitude pas d'autre besoin de propager les changements du fichier originel vers le fichier copié. C'est pourquoi Mercurial ne propage les changements à travers les copies qu'à la première fusion, et pas après.

5.3.3. Comment faire des changements qui ne suivent pas une copie

Si pour une raison ou une autre, vous décidez que cette fonctionnalité de propager automatiquement les changements à travers les copies n'est pas pour vous, utilisez simplement la commande normale de copie de votre système (sur les systèmes de type Unix, il s'agit de cp) pour faire une copie d'un fichier. Utilisez ensuite hg add pour ajouter les nouveaux fichiers à la main. Cependant, avant d'en faire ainsi, relisez Section 5.3.2, “Pourquoi les changements devraient-ils suivre les copies ?”, et faites un choix en connaissance de cause comme quoi cette fonctionnalité n'est pas appropriée à votre cas spécifique.

5.3.4. Comportement de la commande hg copy

Lorsque vous utilisez la commande hg copy, Mercurial crée une copie de chaque fichier source tel qu'il est actuellement dans le répertoire de travail. Cela signifie que si vous effectuez des modifications sur un fichier, puis faites un hg copy sur celui-ci sans avoir au préalable committé ces changements, la nouvelle copie contiendra aussi les modifications que vous avez fait jusqu'à ce point. (Je trouve ce comportement quelque peu contre intuitif, c'est pourquoi j'en fais mention ici.)

La commande hg copy agit comme la commande Unix cp (vous pouvez utilisez l'alias hg cp si vous préférez). Nous devons lui donner deux ou plus arguments où le dernier est considéré comme la destination, et les autres comme les sources.

Si vous passez à hg copy un seul fichier source, et que la destination n'existe pas, ceci créera un nouveau fichier avec ce nom.

$ mkdir k
$ hg copy a k
$ ls k
a

Si la destination est un répertoire, Mercurial copie les sources dans ce répertoire.

$ mkdir d
$ hg copy a b d
$ ls d
a  b

La copie de répertoire est récursive et préserve la structure du répertoire source.

$ hg copy z e
copying z/a/c to e/a/c

Si la source et la destination sont tous deux des répertoires, l'arborescence de la source est recréée dans le répertoire destination.

$ hg copy z d
copying z/a/c to d/z/a/c

Comme avec la commande hg remove, si vous copiez un fichier manuellement et voulez que Mercurial sache qu'il s'agit d'une copie, utilisez simplement l'option --after avec hg copy.

$ cp a n
$ hg copy --after a n

5.4. Renommer les fichiers

Il est plus commun d'avoir besoin de renommer un fichier que d'en faire une copie. La raison pour laquelle j'ai discuté de la commande hg copy avant de parler de renommage des fichiers est que Mercurial traite les renommages essentiellement comme une copie. Ainsi, savoir comment Mercurial traite les copies de fichiers vous informe sur ce que vous êtes en droit d'attendre lorsque vous renommez un fichier.

Lorsque vous utilisez la commande hg rename, Mercurial crée une copie de tous les fichiers sources, les supprime et marque ces fichiers comme étant supprimés.

$ hg rename a b

La commande hg status montre les nouveaux fichiers comme ajoutés et les fichiers originaux comme supprimés.

$ hg status
A b
R a

À cause du hg copy, nous devons utiliser l'option -C pour la commande hg status afin d'observer que le fichier ajouté est bien suivi par Mercurial comme étant une copie de l'original maintenant supprimé.

$ hg status -C
A b
  a
R a

Comme avec hg remove et hg copy, vous pouvez informer Mercurial d'un renommage après coup en utilisant l'option --after. Dans la plupart des autres situations, le comportement de la commande hg rename, et les options qu'elle accepte sont similaires à la commande hg copy.

Si vous êtes familier avec la ligne de commande Unix, vous serez heureux d'apprendre que la commande hg rename peut être invoquée par hg mv.

5.4.1. Renommer les fichiers et fusionner (merge) les changements

Puisque le rename de Mercurial est implanté comme un copy-and-remove, la même propagation des changements a lieu après un rename qu'après un copy lorsque vous fusionnez (merge).

Si je modifie un fichier et que vous le renommez, si ensuite nous fusionnons nos changements respectifs, mes modifications sur le fichier sous son nom originel seront propagés vers le même fichier sous son nouveau nom. (C'est quelque chose que vous pourriez espérer voir fonctionner simplement, mais tous les systèmes de gestion de version ne le font pas.)

Tandis qu'avoir des changements qui suivent une copie est une fonctionnalité où vous hocheriez sûrement la tête en disant oui, cela pourrait être utile, il est clair que les voir suivre un renommage est sans aucun doute important. Sans cette facilité, il serait vraiment trop facile d'avoir des changements qui deviennent orphelins lorsque des fichiers sont renommés.

5.4.2. Renommages divergeants et fusion (merge)

Le cas de noms divergeants a lieu lorsque deux développeurs commencent avec un fichier—appelons le foo—dans leurs dépôts respectifs.

$ hg clone orig anne
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone orig bob
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Anne renomme le fichier en bar.

$ cd anne
$ hg rename foo bar
$ hg ci -m 'Rename foo to bar'

Pendant ce temps, Bob le renomme en quux. (Souvenez-vous que hg mv est un alias pour hg rename.)

$ cd ../bob
$ hg mv foo quux
$ hg ci -m 'Rename foo to quux'

J'aime à penser qu'il s'agit d'un conflit puisque chaque développeur a exprimé différentes intentions au sujet de ce que le nom de ce fichier aurait du être.

Que pensez-vous qu'il devrait se produire lorsqu'ils fusionnent (merge) leurs travaux ? Le comportement actuel de Mercurial est qu'il préserve toujours les deux noms lorsqu'il fusionne (merge) des changesets qui contiennent des renommages divergeants.

# See http://www.selenic.com/mercurial/bts/issue455
$ cd ../orig
$ hg pull -u ../anne
pulling from ../anne
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ hg pull ../bob
pulling from ../bob
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)
$ hg merge
warning: detected divergent renames of foo to:
 bar
 quux
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ ls
bar  quux

Remarquez que bien que Mercurial vous avertisse au sujet de la divergeance des renommages, il vous laisse faire quelque chose au sujet de la divergence après la fusion (merge).

5.4.3. Renommages et fusion convergeants

Un autre type de conflit de renommage intervient lorsque deux personnes choisissent de renommer différents fichiers source vers la même destination. Dans ce cas, Mercurial exécute la machinerie normale de fusion (merge) et vous guide vers une solution convenable.

5.4.4. Autres cas épineux relatifs aux noms

Mercurial possède un bug de longue date dans lequel il échoue à traiter une fusion (merge) où un côté a un fichier avec un nom donné, alors que l'autre côté possède un répertoire avec le même nom. Ceci est documenté dans l'issue 29.

$ hg init issue29
$ cd issue29
$ echo a > a
$ hg ci -Ama
adding a
$ echo b > b
$ hg ci -Amb
adding b
$ hg up 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ mkdir b
$ echo b > b/b
$ hg ci -Amc
adding b/b
created new head
$ hg merge
abort: Is a directory: /tmp/issue29wS6WTk/issue29/b

5.5. Récupération d'erreurs

Mercurial possède certaines commandes utiles qui vont vous aider à récupérer de certaines erreurs communes.

La commande hg revert vous permet d'annuler les changements que vous avez faits dans votre répertoire de travail. Par exemple, si vous faites un hg add sur un fichier par accident, exécutez juste hg revert avec le nom du fichier que vous avez ajouté et tandis que le fichier ne sera touché d'une quelconque manière, il ne sera plus suivi comme ajouté par Mercurial. Vous pouvez aussi utiliser la commande hg revert pour vous débarrasser de modifications erronées apportées à un fichier.

Il est utile de se souvenir que la commande hg revert est utile pour les modifications qui n'ont pas encore été committées. Une fois que vous avez committé un changement, si vous décidez qu'il s'agissait d'une erreur, vous pouvez toujours faire quelque chose à ce sujet, bien que vos options soient un peu plus limitées.

Pour plus d'informations au sujet de la commande hg revert, et des détails sur comment traiter les modifications que vous avez déjà committées, référez vous à Chapter 9, Finding and fixing mistakes.

5.6. Traiter avec les fusions (merge) malicieuses

Dans des projets compliqués ou conséquents, il n'est pas rare qu'une fusion (merge) de deux changesets finisse par une migraine. Supposez qu'il y ait un gros fichier source qui ait été largement édité de chaque coté de la fusion (merge) : ceci va inévitablement résulter en conflits, dont certains peuvent prendre plusieurs essais pour s'en sortir.

Développons en un cas simple pour voir comment le gérer. Nous allons commencer avec un dépôt contenant un fichier, et le cloner deux fois.

$ hg init conflict
$ cd conflict
$ echo first > myfile.txt
$ hg ci -A -m first
adding myfile.txt
$ cd ..
$ hg clone conflict left
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone conflict right
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Dans un des clones, nous allons modifier le fichier d'une façon.

$ cd left
$ echo left >> myfile.txt
$ hg ci -m left

Dans un autre, nous allons modifier le fichier différemment.

$ cd ../right
$ echo right >> myfile.txt
$ hg ci -m right

Ensuite, nous allons récupérer (pull) chaque ensemble de changement dans notre dépôt original.

$ cd ../conflict
$ hg pull -u ../left
pulling from ../left
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg pull -u ../right
pulling from ../right
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
not updating, since new heads added
(run 'hg heads' to see heads, 'hg merge' to merge)

Nous nous attendons à ce que notre dépôt contienne deux "heads".

$ hg heads
changeset:   2:c384aef92f94
tag:         tip
parent:      0:7b7b4f7a62a5
user:        Bryan O'Sullivan <bos@serpentine.com>
date:        Thu Mar 17 05:08:45 2011 +0000
summary:     right

changeset:   1:5d35cfc5a384
user:        Bryan O'Sullivan <bos@serpentine.com>
date:        Thu Mar 17 05:08:45 2011 +0000
summary:     left

Normalement, si nous lançons hg merge à ce point, il nous renverra vers une interface utilisateur qui nous permettra de résoudre manuellement les éditions conflictuelles sur le fichier myfile.txt. Cependant, pour simplifier ici les choses dans la présentation, nous aimerions plutôt que la fusion (merge) échoue immédiatement. Voici une façon de le faire.

$ export HGMERGE=false

Nous avons dit au processus de fusion de Mercurial d'exécuter la commande false (qui échoue immédiatement, à la demande) s'il détecte une fusion (merge) qu'il ne peut pas arranger automatiquement.

Si nous appelons maintenant hg merge, il devrait échouer et reporter une erreur.

$ hg merge
merging myfile.txt
merging myfile.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon

Même si nous ne remarquons pas qu'une fusion (merge) a échoué, Mercurial nous empêchera de committer le résultat d'une fusion ratée.

$ hg commit -m 'Attempt to commit a failed merge'
abort: unresolved merge conflicts (see hg resolve)

Lorsque hg commit échoue dans ce cas, il suggère que nous utilisons la commande peu connue hg resolve. Comme d'habitude, hg help resolve affichera une aide sommaire.

5.6.1. États de résolution des fichiers

Lorsqu'une fusion intervient, la plupart des fichiers vont, la plupart du temps, rester sans modification. Pour chaque fichier sur lequel Mercurial doit faire quelque chose, il suit l'état de celui-ci.

  • Un fichier resolved a été fusionné (merge) avec succès, que ce soit automatiquement par Mercurial ou manuellement par une intervention humaine.

  • Un fichier unresolved n'a pas été fusionné (merge) correctement et a besoin de plus d'attention.

Si Mercurial voit un fichier quelconque dans un état unresolved après une fusion (merge), il considère que la fusion (merge) a échoué. Heureusement, nous n'avons pas à recommencer la procédure à partir du début.

L'option --list ou -l passée à hg resolve liste l'état de chaque fichier fusionné (merge).

$ hg resolve -l
U myfile.txt

En sortie de hg resolve, un fichier resolved est marqué avec un R, alors qu'un fichier unresolved est marqué d'un U. S'il existe un fichier listé avec un U, nous savons qu'essayer de committer le résultat de la fusion (merge) échouera.

5.6.2. Résoudre une fusion de fichier

Nous avons plusieurs options pour changer l'état d'un fichier de unresolved à resolved. Le plus habituel est de relancer hg resolve. Si nous passons les noms des fichiers individuels ou des répertoires, ceci retentera la fusion de tous les fichiers présents à cet endroit. Nous pouvons aussi passer l'option --all ou -a qui tentera de fusionner tous les fichiers unresolved.

Mercurial nous laisse aussi modifier la résolution d'un fichier directement. Nous pouvons marquer un fichier resolved en utilisant l'option --mark, ou unresolved en utilisant l'option --unmark. Ceci nous autorise à nettoyer une fusion particulièrement compliquée à la main, et de garder un suivi de nos progrès avec chaque fichier pendant que nous avançons.

5.7. Des diffs plus utiles

La sortie par défaut de la commande hg diff est compatible rétrospectivement avec la commande régulière diff, mais ceci a quelques inconvénients.

Considérez le cas où nous utilisons hg rename pour renommer un fichier.

$ hg rename a b
$ hg diff
diff -r 1cb203eac078 a
--- a/a	Thu Mar 17 05:08:44 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-a
diff -r 1cb203eac078 b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/b	Thu Mar 17 05:08:44 2011 +0000
@@ -0,0 +1,1 @@
+a

La sortie de hg diff ci-dessus cache le fait que nous avons simplement renommé un fichier. La commande hg diff accepte l'option --git ou -g pour utiliser un nouveau format de diff qui montre ces informations sous une forme plus expressive.

$ hg diff -g
diff --git a/a b/b
rename from a
rename to b

Cette option peut aussi aider avec le cas qui peut être autrement perturbant : un fichier qui apparaît comme étant modifié en accord avec hg status, mais où hg diff n'affiche rien. Cette situation peut survenir si nous changeons les permissions d'exécution du fichier.

$ chmod +x a
$ hg st
M a
$ hg diff

La commande normale diff ne fait pas attention aux permissions des fichiers, ce qui explique pourquoi hg diff n'affiche rien du tout par défaut. Si nous lui passons l'option -g, ceci nous informe de ce qui s'est vraiment passé.

$ hg diff -g
diff --git a/a b/a
old mode 100644
new mode 100755

5.8. Quels fichiers suivre et lesquels éviter

Les systèmes de gestion de révisions sont en général meilleurs pour gérer les fichiers textes qui sont écrits par les humains, comme le code source, où les fichiers ne changent pas énormément d'une révision à l'autre. Certains systèmes de gestion de révisions centralisés peuvent aussi traiter très convenablement les fichiers binaires, tels que les images bitmap.

Par exemple, une équipe de développement de jeux va probablement gérer les deux types : ses codes source et tous ses binaires (ex. données géométriques, textures, schémas de cartes) dans un système de contrôle de révisions.

Puisqu'il est d'habitude impossible de fusionner (merge) deux modifications conflictuelles sur un fichier binaire, les systèmes de version centralisés offrent souvent un mécanisme de verrou (lock) qui permet à un utilisateur de dire Je suis la seule personne qui peut éditer ce fichier.

En comparaison avec un système centralisé, un système décentralisé de gestion de révision change certains facteurs qui guident les décisions sur quels fichiers gérer et comment.

Par exemple, un système distribué de gestion de révisions ne peut pas, par sa nature, offrir un système de verrou (lock) sur les fichiers. Il n'y a donc pas de mécanisme inclus pour empêcher deux personnes de faire des modifications conflictuelles sur un fichier binaire. Si vous avez une équipe où plusieurs personnes peuvent souvent éditer un fichier binaire, cela ne serait pas une très bonne idée d'utiliser Mercurial —ou tout autre système distribué de gestion de révisions—pour gérer ces fichiers.

Lorsque vous sauvegardez les modifications sur un fichier, Mercurial ne sauvegarde d'habitude que les différences entre la version précédente et la version actuelle d'un fichier. Pour la plupart des fichiers texte, ceci est très efficace. Cependant, certains fichiers (en particulier les fichiers binaires) sont construits d'une façon que même un petit changement sur un contenu logique résulte sur un changement de la plupart des octets du fichier. Par exemple, les fichiers compressés sont particulièrement sujets à ce comportement. Si les différences entre deux versions successives d'un fichier sont toujours très grandes, Mercurial ne sera pas capable de sauvegarder l'historique des révisions sur le fichier très efficacement. Ceci peut affecter aussi bien les besoins pour la sauvegarde locale que le temps nécessaire à cloner le dépôt.

Pour avoir une idée de comment ceci pourrait vous affecter en pratique, supposez que nous voulions que Mercurial gère des documents OpenOffice. OpenOffice sauvegarde les documents sur le disque comme des fichiers compressés zip. Même le fait d'éditer ces fichiers d'une seule lettre, changera les bits de la quasi totalité du fichier lorsque vous le sauvegarderez. Maintenant, supposez que ce fichier fasse une taille de 2 Mo. Puisque la plupart du fichier change à chaque fois que vous sauvegardez, Mercurial aura à sauvegarder tous les 2 Mo du fichier à chaque commit, alors que de votre point de vue, il n'y a que peu de mots qui changent à chaque fois. Un seul fichier souvent édité qui n'est pas bien traité par les hypothèses que Mercurial fait sur les sauvegardes peut facilement avoir un effet colossal sur la taille du dépôt.

Même pire, si vous et quelqu'un d'autre éditez le même document OpenOffice sur lequel vous travaillez, il n'y a pas de façon utile pour fusionner votre travail. En fait, il n'y a pas de moyen utile de montrer que les différences sont faites à partir de votre vision des modifications.

Il y a ainsi quelques recommandations claires sur les types de fichiers spécifiques avec lesquels faire très attention.

  • Les fichier qui sont très gros et incompressibles, comme les images ISO de CD-ROM, sont, par construction très gros et les cloner à travers un réseau sera très long.

  • Les fichiers qui changent beaucoup d'une révision à l'autre peuvent être très coûteux à sauvegarder si vous les éditez fréquemment, de même que les conflits entre deux éditions concurrentes peuvent être difficiles à résoudre.

5.9. Sauvegardes et miroirs

Puisque Mercurial maintient une copie complète de l'historique de chaque clone, toute personne qui utilise Mercurial pour collaborer à un projet peut potentiellement agir comme une source de sauvegarde si une catastrophe survenait. Si un dépôt central devient indisponible, vous pouvez construire un remplaçant en clonant une copie du dépôt à partir d'un des contributeurs en récupérant (pull) tous les changements qui n'auraient pas été vus par les autres.

Il est simple d'utiliser Mercurial pour construire des serveurs hors site de sauvegarde et des miroirs distants. Initiez une tâche périodique (ex. via la commande cron) sur un serveur distant pour récupérer (pull) les changements de votre dépôt distant chaque heure. Ceci sera difficile seulement dans le cas improbable où le nombre des dépôts maîtres que vous maintenez change souvent, auquel cas vous aurez besoin de faire un peu de scripting pour rafraichir la liste des dépôts à sauvegarder.

Si vous exécutez des sauvegardes traditionnelles de votre dépôt maître sur bande ou disque, et que vous voulez sauvegarder un dépôt nommé myrepo, utilisez la commande hg clone -U myrepo myrepo.bak pour créer un clone de myrepo avant de commencer vos backups. L'option -U ne crée pas de répertoire de travail après que le clone soit accompli, puisque ceci serait superflu et ferait que la sauvegarde prenne plus de temps.

Si vous voulez ensuite sauvegarder myrepo.bak au lieu de myrepo, vous aurez la garantie d'avoir une image (snapshot) cohérente de votre dépôt sur lequel un développeur insomniaque n'enverra (push) pas de changements en milieu de sauvegarde.