Table of Contents
Nous avons maintenant étudié comment cloner un dépôt, effectuer des changements dedans, et récupérer ou transférer depuis un autre dépôt. La prochaine étape est donc de fusionner les modifications de différents dépôts.
La fusion est un aspect fondamental lorsqu'on travaille avec un gestionnaire de révisions distribué.
Alice et Bob ont chacun une copie personnelle du dépôt d'un projet sur lequel ils collaborent. Alice corrige un bug dans son dépôt, et Bob ajoute une nouvelle fonctionnalité dans le sien. Ils veulent un dépôt partagé avec à la fois le correctif du bug et la nouvelle fonctionnalité.
Je travaille régulièrement sur plusieurs tâches différentes sur un seul projet en même temps, chacun isolé dans son propre dépôt. Travailler ainsi signifie que je dois régulièrement fusionner une partie de mon code avec celui des autres.
Parce que nous devons fusionner souvent, Mercurial rend cette opération facile. Étudions ensemble le déroulement des opérations. Nous commencerons encore par faire un clone d'un autre dépôt (vous voyez que l'on fait ça tout le temps ?) puis nous ferons quelques modifications dessus.
$
cd ..
$
hg clone hello my-new-hello
updating to branch default 2 files updated, 0 files merged, 0 files removed, 0 files unresolved$
cd my-new-hello
# Make some simple edits to hello.c.$
my-text-editor hello.c
$
hg commit -m 'A new hello for a new day.'
Nous devrions avoir maintenant deux copies de
hello.c
avec des contenus différents. Les
historiques de ces deux dépôts ont aussi divergés, comme illustré dans
la figure Figure 3.1, “Historique divergeant des dépôts my-hello
et my-new-hello
.”.
$
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("once more, hello.\n"); printf("hello, world!\"); printf("hello again!\n"); return 0; }
Et ici se trouve notre version légèrement différente du dépôt.
$
cat ../my-hello/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!\"); printf("hello again!\n"); return 0; }
Nous savons déjà que récupérer les modifications depuis
notre dépôt my-hello
n'aura
aucun effet sur l'espace de travail.
$
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)
Néanmoins, la commande hg pull nous indique quelque chose au sujet des “heads”.
Rappelez vous que Mercurial enregistre quelle révision est le parent de chaque révision. Si une révision a un parent, nous l'appelons un enfant “child” ou un descendant de ce parent. Une “head” est une révision qui n'a donc pas d'enfant. La révision “tip” est donc une “head”, car c'est la révision la plus récente du dépôt qui n'a pas d'enfant. Il y a des moments où un dépôt peut contenir plusieurs “heads”.
Figure 3.2. Contenu du dépôt après une récupération (pull) depuis le
dépôt my-hello
vers le dépôt my-new-hello
Dans la figure Figure 3.2, “Contenu du dépôt après une récupération (pull) depuis le
dépôt my-hello
vers le dépôt my-new-hello
”,
vous pouvez constater l'effet d'un “pull” depuis le dépôt
my-hello
dans le dépôt
my-new-hello
. L'historique qui
était déjà présent dans le dépôt my-new-hello
reste intact, mais une
nouvelle révision a été ajoutée. En vous reportant à la figure Figure 3.1, “Historique divergeant des dépôts my-hello
et my-new-hello
.”, vous pouvez voir que l'
ID de révision “changeset ID” reste le même dans
le nouveau dépôt, mais que le numéro de
révision reste le même. (Ceci est un parfait exemple de
pourquoi il n'est fiable d'utiliser les numéros de révision lorsque
l'on discute d'un “changeset”.) Vous pouvez voir les “heads”
présentes dans le dépôt en utilisant la commande hg heads.
$
hg heads
changeset: 6:ec142fc290c8 tag: tip parent: 4:2278160e78d4 user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:25 2011 +0000 summary: Added an extra line of output changeset: 5:2491baec1ec8 user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:35 2011 +0000 summary: A new hello for a new day.
Que se passe-t-il quand vous essayez d'utiliser la commande hg update pour mettre à jour votre espace de travail au nouveau “tip” ?
$
hg update
abort: crosses branches (use 'hg merge' or use 'hg update -c')
Mercurial nous prévient que la commande hg update n'effectuera pas la fusion, il ne veut pas mettre à jour l'espace de travail quand il estime que nous pourrions avoir besoin d'une fusion, à moins de lui forcer la main. À la place, il faut utiliser la commande hg merge pour fusionner les deux “heads”.
Pour commencer une fusion (merge) entre deux “heads”, nous utilisons 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)
Nous résolvons les conflits dans le fichier
hello.c
. Ceci met à jour le répertoire de travail
de sorte qu'il ne contienne les modifications en provenance des
deux “heads”, ce qui est indiqué par la
la sortie de la commande hg
parents et le contenu du fichier
hello.c
.
$
hg parents
changeset: 5:2491baec1ec8 user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:35 2011 +0000 summary: A new hello for a new day. changeset: 6:ec142fc290c8 tag: tip parent: 4:2278160e78d4 user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:25 2011 +0000 summary: Added an extra line of output$
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("once more, hello.\n"); printf("hello, world!\"); printf("hello again!\n"); return 0; }
Dès l'instant où vous avez effectué une fusion (merge), hg parents vous affichera deux parents, avant que vous n'exécutiez la commande hg commit sur le résultat de la fusion.
$
hg commit -m 'Merged changes'
Nous avons maintenant un nouveau tip, remarquez qu'il contient à la fois nos anciennes “heads” et leurs parents. Ce sont les mêmes révisions que nous avions affichées avec la commande hg parents.
$
hg tip
changeset: 7:3c1554942e80 tag: tip parent: 5:2491baec1ec8 parent: 6:ec142fc290c8 user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:36 2011 +0000 summary: Merged changes
Dans la figure Figure 3.3, “Répertoire de travail et dépôt pendant une fusion, et le “commit” qui suit”, vous pouvez voir une représentation de ce qui se passe dans l'espace de travail pendant la fusion, et comment ceci affecte le dépôt lors du “commit”. Pendant la fusion, l'espace de travail, qui a deux révisions (changesets) comme parents, voit ces derniers devenir le parent d'une nouvelle révision (changeset).
La plupart des fusions sont assez simples à réaliser, mais parfois vous vous retrouverez à fusionner des fichiers où la modification touche la même portion de code, au sein d'un même fichier. À moins que ces modification ne soient identiques, ceci aboutira à un conflit, et vous devrez décider comment réconcilier les différentes modifications dans un ensemble cohérent.
La figure Figure 3.4, “Modifications en conflit dans un document” illustre un cas de modifications conflictuelles dans un document. Nous avons commencé avec une version simple de ce fichier, puis nous avons ajouté des modifications, pendant que quelqu'un d'autre modifiait le même texte. Notre tâche dans la résolution du conflit est de décider à quoi le fichier devrait ressembler.
Mercurial n'a pas de mécanisme interne pour gérer les conflits. À la place, il exécute un programme externe appelé hgmerge. Il s'agit d'un script shell qui est compris avec Mercurial, vous pouvez le modifier si vous voulez. Ce qu'il fait par défaut est d'essayer de trouver un des différents outils de fusion qui seront probablement installés sur le système. Il commence par les outils totalement automatiques, et s'ils échouent (parce que la résolution du conflit nécessite une intervention humaine) ou s'ils sont absents, le script tente d'exécuter certains outils graphiques de fusion.
Il est aussi possible de demander à Mercurial d'exécuter
un autre programme ou un autre script en définissant la variable
d'environnement HGMERGE
avec le nom
du programme de votre choix.
Mon outil de fusion préféré est kdiff3, que j'utilise ici pour illustrer les fonctionnalités classiques des outils graphiques de fusion. Vous pouvez voir une capture d'écran de l'utilisation de kdiff3 dans la figure Figure 3.5, “Utiliser kdiff3 pour fusionner les différentes version d'un fichier.”. Cet outil effectue une fusion “three-way”, car il y a trois différentes versions du fichier qui nous intéressent. Le fichier découpe la partie supérieure de la fenêtre en trois panneaux :
À gauche on trouve la version de base du fichier, soit la plus récente version des deux versions qu'on souhaite fusionner.
Au centre, il y a “notre” version du fichier, avec le contenu que nous avons modifié.
Sur la droite, on trouve “leur” version du fichier, celui qui contient la révision que nous souhaitons intégrer.
Dans le panneau en dessous, on trouve le résultat actuel de notre fusion. Notre tâche consiste donc à remplacer tous les textes en rouge, qui indiquent des conflits non résolus, avec une fusion manuelle et pertinente de “notre” version et de la “leur”.
Les quatre panneaux sont accrochés ensemble, si nous déroulons les ascenseurs verticalement ou horizontalement dans chacun d'entre eux, les autres sont mis à jour avec la section correspondante dans leurs fichiers respectifs.
Pour chaque portion de fichier posant problème, nous pouvons choisir de résoudre le conflit en utilisant une combinaison de touches depuis la version de base, la nôtre, ou la leur. Nous pouvons aussi éditer manuellement les fichiers à tout moment, si c'est nécessaire.
Il y a beaucoup d'outils de fusion disponibles, bien trop pour parler de tous ici. Leurs disponibilités varient selon les plateformes ainsi que leurs avantages et inconvénients. La plupart sont optimisés pour la fusion de fichier contenant un texte plat, certains sont spécialisés dans un format de fichier précis (généralement XML).
Dans cet exemple, nous allons reproduire la modification de l'historique du fichier de la figure Figure 3.4, “Modifications en conflit dans un document” ci-dessus. Commençons par créer un dépôt avec une version de base de notre document.
$
cat > letter.txt <<EOF
>
Greetings!
>
I am Mariam Abacha, the wife of former
>
Nigerian dictator Sani Abacha.
>
EOF
$
hg add letter.txt
$
hg commit -m '419 scam, first draft'
Créons un clone de ce dépôt et effectuons une modification dans le fichier.
$
cd ..
$
hg clone scam scam-cousin
updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved$
cd scam-cousin
$
cat > letter.txt <<EOF
>
Greetings!
>
I am Shehu Musa Abacha, cousin to the former
>
Nigerian dictator Sani Abacha.
>
EOF
$
hg commit -m '419 scam, with cousin'
Et un autre clone, pour simuler que quelqu'un d'autre effectue une modification sur le fichier. (Ceci pour suggérer qu'il n'est pas rare de devoir effectuer des fusions (merges) avec vos propres travaux quand vous isolez les tâches dans des dépôts distincts. En effet, vous aurez alors à trouver et résoudre certains conflits).
$
cd ..
$
hg clone scam scam-son
updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved$
cd scam-son
$
cat > letter.txt <<EOF
>
Greetings!
>
I am Alhaji Abba Abacha, son of the former
>
Nigerian dictator Sani Abacha.
>
EOF
$
hg commit -m '419 scam, with son'
Maintenant que ces deux versions différentes du même fichier sont créées, nous allons configurer l'environnement de manière appropriée pour exécuter notre fusion (merge).
$
cd ..
$
hg clone scam-cousin scam-merge
updating to branch default 1 files updated, 0 files merged, 0 files removed, 0 files unresolved$
cd scam-merge
$
hg pull -u ../scam-son
pulling from ../scam-son 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)
Dans cette exemple, je n'utiliserais pas la commande Mercurial
habituelle hgmerge pour effectuer la
fusion (merge), car il me faudrait abandonner ce joli petit exemple automatisé
pour utiliser un outil graphique. À la place, je vais définir la
variable d'environnement HGMERGE
pour indiquer à
Mercurial d'utiliser la commande non-interactive merge.
Cette dernière est comprise dans de nombreux systèmes “à la Unix”.
Si vous exécutez cet exemple depuis votre ordinateur, ne vous
occupez pas de définir HGMERGE
.
$
export HGMERGE=merge
$
hg merge
merging letter.txt merge: warning: conflicts during merge merging letter.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$
cat letter.txt
Greetings! <<<<<<< /tmp/tour-merge-conflictJzT4po/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.LnSKVz Nigerian dictator Sani Abacha.
Parce que merge ne peut pas résoudre les modifications conflictuelles, il laisse des marqueurs de différences à l'intérieur du fichier qui a des conflits, indiquant clairement quelles lignes sont en conflit, et si elles viennent de notre fichier ou du fichier externe.
Mercurial peut distinguer, à la manière dont la commande merge se termine, qu'elle n'a pas été capable d'effectuer la fusion (merge), alors il nous indique que nous devons effectuer de nouveau cette opération. Ceci peut être très utile si, par exemple, nous exécutons un outil graphique de fusion et que nous le quittons sans nous rendre compte qu'il reste des conflits ou simplement par erreur.
Si la fusion (merge) automatique ou manuelle échoue, il n'y a rien pour nous empêcher de “corriger le tir” en modifiant nous-même les fichiers, et enfin effectuer le “commit” du fichier:
$
cat > letter.txt <<EOF
>
Greetings!
>
I am Bryan O'Sullivan, no relation of the former
>
Nigerian dictator Sani Abacha.
>
EOF
$
hg resolve -m letter.txt
$
hg commit -m 'Send me your money'
$
hg tip
changeset: 3:2eb6858f32d9 tag: tip parent: 1:3136513772a5 parent: 2:7d96870fc99d user: Bryan O'Sullivan <bos@serpentine.com> date: Thu Mar 17 05:09:37 2011 +0000 summary: Send me your money
La procédure pour effectuer la fusion indiquée ci-dessus est simple, mais requiert le lancement de trois commandes à la suite.
hg pull -u hg merge hg commit -m 'Merged remote changes'
Lors du “commit” final, vous devez également saisir un message, qui aura vraisemblablement assez peu d'intérêt.
Il serait assez sympathique de pouvoir réduire le
nombre d'opérations nécessaire, si possible. De fait Mercurial est
fournit avec une extension appelée fetch
qui fait justement cela.
Mercurial fournit un mécanisme d'extension flexible qui permet à chacun d'étendre ces fonctionnalités, tout en conservant le cœur de Mercurial léger et facile à utiliser. Certaines extensions ajoutent de nouvelles commandes que vous pouvez utiliser en ligne de commande, alors que d'autres travaillent “en coulisse”, par exemple en ajoutant des possibilités au serveur.
L'extension fetch
ajoute une nouvelle commande nommée, sans surprise, hg fetch. Cette extension consiste en une
combinaison de hg pull, hg update et hg
merge. Elle commence par récupérer les modifications d'un
autre dépôt dans le dépôt courant. Si elle trouve que les
modifications ajoutent une nouvelle “head”, elle effectue un “merge”,
et ensuite “commit” le résultat du “merge” avec un message généré
automatiquement. Si aucune “head” n'a été ajouté, elle met à jour le
répertoire de travail au niveau de la nouvelle révision “tip”.
Activer l'extension fetch
est facile. Modifiez votre fichier .hgrc
, et soit allez à la section extensions
soit créez une section
extensions
. Ensuite ajoutez
une ligne qui consiste simplement en “fetch =”.
[extensions] fetch =
(Normalement, sur la partie droite de
“=
” devrait apparaître le chemin de
l'extension, mais étant donné que l'extension fetch
fait partie de la distribution standard,
Mercurial sait où la trouver.)
En cours de la vie d'un projet, nous allons souvent vouloir changer la disposition de ses fichiers et de ses répertoires. Ceci peut être aussi simple que de changer le nom d'un seul fichier, et aussi compliqué que de restructurer une hiérarchie entière de fichiers au sein du projet.
Mercurial permet de faire ce genre de modification de manière fluide, à condition de l'informer de ce que nous faisons. Si vous voulez renommer un ficher, vous devriez utiliser la commande hg rename[2] pour changer son nom, ainsi Mercurial peut ensuite prendre la bonne décision, plus tard, en cas de fusion (merge).
Nous étudierons, en détail, l'utilisation de ces commandes dans le chapitre Section 5.3, “Copier des fichiers”.
[2] Si vous êtes un utilisateur d'Unix, vous serez content de savoir que la commande hg rename peut être abrégée en hg mv.