Chapter 4. Derrière le décor

Table of Contents

4.1. Conservation de l'historique sous Mercurial
4.1.1. Suivi de l'historique pour un seul fichier
4.1.2. Gestion des fichiers suivis
4.1.3. Enregistrer les informations des changesets
4.1.4. Relations entre les révisions
4.2. Stockage sûr et efficace
4.2.1. Stockage efficace
4.2.2. Opérations sûres
4.2.3. Récupération rapide
4.2.4. Identification et intégrité forte
4.3. Historique des révisions, branches et fusions (merge)
4.4. Le répertoire de travail
4.4.1. Que se passe-t-il lorsque vous committez
4.4.2. Création d'une nouvelle head
4.4.3. Fusionner (merge) les changements
4.4.4. Fusions et renommages
4.5. D'autres fonctionnalités intéressantes
4.5.1. Compression astucieuse
4.5.2. Ordre de lecture/écriture et atomicité
4.5.3. Accès concurrent
4.5.4. Empêcher les recherches
4.5.5. Autres contenus du dirstate

À la différence de beaucoup d'outils de gestion de révisions, les concepts sur lesquels se base Mercurial sont assez simples pour qu'il soit facile de comprendre comment le logiciel fonctionne. Bien que leur connaissance ne soit pas indispensable, je trouve utile d'avoir un modèle mental de ce qui se passe.

En effet, cette compréhension m'apporte la confiance que Mercurial a été développé avec soin pour être à la fois sûr et efficace. De surcroît, s'il m'est facile de garder en tête ce que le logiciel fait lorsque j'accomplis des tâches de révision, j'aurai moins de risques d'être surpris par son comportement.

Dans ce chapitre, nous décrirons tout d'abord les concepts essentiels de l'architecture de Mercurial, pour ensuite discuter quelques détails intéressants de son implémentation.

4.1. Conservation de l'historique sous Mercurial

4.1.1. Suivi de l'historique pour un seul fichier

Lorsque Mercurial effectue un suivi des modifications faites à un fichier, il conserve l'historique pour ce fichier dans un filelog sous forme de métadonnées. Chaque entrée dans le filelog contient assez d'informations pour reconstituer une révision du fichier correspondant. Les filelogs sont des fichiers stockés dans le répertoire .hg/store/data. Un filelog contient des informations de deux types : les données de révision, et un index pour permettre à Mercurial une recherche efficace d'une révision donnée.

Lorsqu'un fichier devient trop gros ou a un long historique, son filelog se voit stocké dans un fichier de données (avec un suffixe .d) et un fichier index (avec un suffixe.i) distincts. La relation entre un fichier dans le répertoire de travail et le filelog couvrant le suivi de son historique dans le dépôt est illustré à la figure Figure 4.1, “Relations entre les fichiers dans le répertoire de travail et leurs filelogs dans le dépôt”.

Figure 4.1. Relations entre les fichiers dans le répertoire de travail et leurs filelogs dans le dépôt

XXX add text

4.1.2. Gestion des fichiers suivis

Mercurial a recours à une structure nommée manifest pour rassembler les informations sur les fichiers dont il gère le suivi. Chaque entrée dans ce manifest contient des informations sur les fichiers présents dans une révision donnée. Une entrée enregistre la liste des fichiers faisant partie de la révision, la version de chaque fichier, et quelques autres métadonnées sur ces fichiers.

4.1.3. Enregistrer les informations des changesets

Le changelog contient les informations sur chaque changeset. Chaque révision enregistre qui a committé un changement, le commentaire du changeset, d'autres morceaux d'information relatives au changeset et la révision du manifest à utiliser.

4.1.4. Relations entre les révisions

À l'intérieur d'un changelog, d'un manifest, ou d'un filelog, chaque révision enregistre un pointeur vers son parent immédiat (ou à ses deux parents s'il s'agit d'une révision correspondant à une fusion (merge)). Comme mentionné plus haut, il y a aussi des relations entre les révisions à travers ces structures, qui sont de nature hiérarchique.

Pour chaque changeset dans un dépôt, il y a exactement une révision stockée dans le changelog. Chaque révision du changelog contient un pointeur vers une unique révision du manifest. Une révision du manifest garde un pointeur vers une unique révision pour chaque filelog suivi lorsque le changeset est créé. Ces relations sont illustrées dans Figure 4.2, “Metadata relationships”.

Figure 4.2. Metadata relationships

XXX add text

Comme l'illustration le montre, il n'y a pas de relation un à un entre les révisions dans un changelog, manifest ou filelog. Si un fichier que Mercurial suit n'a pas changé entre deux changesets, l'entrée pour ce fichier dans les deux révisions du manifest pointera vers la même révision de son filelog [3].

4.2. Stockage sûr et efficace

Les fondements des changelogs, des manifests et des filelogs sont fournis par une unique structure appelée le revlog.

4.2.1. Stockage efficace

Le revlog fournit un stockage efficace des révisions en utilisant un mécanisme delta. Au lieu de stocker une copie complète d'un fichier à chaque révision, il stocke les changements requis pour transformer une révision plus ancienne en une nouvelle révision. Pour plusieurs types de données, ces deltas sont typiquement une fraction de pourcentage de la taille de la copie complète d'un fichier.

Certains systèmes de gestion de révisions obsolètes peuvent seulement travailler avec les deltas de fichiers texte. Il doivent d'ailleurs stocker les fichiers binaires comme des images complètes ou encodées avec une représentation texte, chacune de ces approches étant gaspilleuse. Mercurial peut traiter les deltas de fichiers avec du contenu binaire arbitraire ; il n'a pas besoin de traiter spécialement du texte.

4.2.2. Opérations sûres

Mercurial empile toujours les données à la fin d'un fichier revlog. Il ne modifie jamais la section d'un fichier après qu'il l'ait écrite. C'est à la fois plus robuste et efficace que les schémas qui ont besoin de modifier ou réécrire les données.

De plus, Mercurial traite chaque écriture comme la partie d'une transaction qui peut comprendre plusieurs fichiers. Une transaction est atomique : soit la transaction entière réussit et ses effets sont tous visibles aux lecteurs en une étape, soit la totalité est annulée. Cette garantie de l'atomicité signifie que si vous exécutez deux copies de Mercurial, où une lit les données et l'autre les écrit, le lecteur ne verra jamais un résultat partiellement écrit qui pourrait le perturber.

Le fait que Mercurial ne fasse qu'ajouter aux fichiers fait qu'il est facile de fournir cette garantie de transaction. Plus les choses sont faites simplement comme ça, plus vous pouvez être rassurés qu'elles sont bien faites.

4.2.3. Récupération rapide

Mercurial évite habillement un piège commun à tous les vieux systèmes de gestion de révisions : le problème de la récupération inefficace. La plupart des systèmes de gestion de révisions stockent le contenu d'une révision comme une série incrémentale de modifications faites à un snapshot. (Certains basent le snapshot sur la plus vieille révision, d'autres sur la plus récente.) Pour reconstruire une révision spécifique, vous devez d'abord lire le snapshot, et ensuite toutes les révisions entre le snapshot et votre révision cible. Plus vous avez d'historique accumulé dans un fichier, plus de révisions vous avez à lire, d'où la longueur que cela prend à reconstruire une révision particulière.

Figure 4.3. Snapshot d'un revlog, avec des deltas incrémentaux

XXX add text

L'innovation que Mercurial apporte à ce problème est simple mais efficace. Une fois que la quantité cumulée de deltas d'informations stockées depuis le dernier snapshot excède un seuil fixé, il stocke un nouveau snapshot (compressé bien sûr), plutôt qu'un nouveau delta. Ceci rend possible la reconstruction de toute révision d'un fichier rapidement. Cette approche fonctionne si bien que depuis, elle a été copiée par plusieurs autres systèmes de gestion de révisions.

Figure 4.3, “Snapshot d'un revlog, avec des deltas incrémentaux” illustre l'idée. Dans une entrée d'un fichier d'index de revlog, Mercurial stocke l'intervalle des entrées depuis le fichier de données qu'il doit lire pour reconstruire une révision particulière.

4.2.3.1. Aparté : l'influence de la compression vidéo

Si vous êtes familiés de la compression vidéo ou avez déjà regardé un programme TV par câble ou par un service satellite, vous devez savoir que la plupart des schémas de compression vidéo stockent chaque trame de vidéo comme un delta vis-à-vis de la trame précédente.

Mercurial emprunte cette idée pour rendre possible la reconstruction d'une révision à partir d'un snapshot et d'un petit nombre de deltas.

4.2.4. Identification et intégrité forte

Avec les deltas ou l'information du snapshot, une entrée d'un revlog contient un hash cryptographique des données qu'il représente. Ceci fait qu'il est difficile de construire les données d'une révision, mais facile de détecter une corruption accidentelle.

Les hash fournissent plus qu'un bon moyen de vérification contre la corruption ; il sont aussi utilisés comme identifiants pour les révisions. Les hashs d'identifications d'un changeset que vous voyez comme utilisateur final proviennent des révisions du changelog. Bien que les filelogs et le manifest utilisent aussi des hashs, Mercurial ne les utilise qu'en arrière-plan.

Mercurial vérifie que les hashs sont corrects lorsqu'il récupère les révisions de fichiers et lorsqu'il récupère (pull) les changements d'un autre dépôt. S'il rencontre un problème d'intégrité, il se plaindra et arrêtera tout ce qu'il est en train de faire.

En plus de l'effet qu'il a sur l'efficacité des récupérations, l'utilisation par Mercurial de snapshots périodiques fait qu'il est plus robuste contre la corruption partielle de données. Si un revlog devient partiellement corrompu à cause d'une erreur matérielle ou d'un bug système, il est souvent possible de reconstruire certaines ou la plupart des révisions à partir des sections non corrompues du revlog, avant et après la section corrompue. Ceci ne serait pas possible à partir d'un modèle de stockage de deltas seul.

4.3. Historique des révisions, branches et fusions (merge)

Chaque entrée dans un revlog Mercurial connaît l'identité de l'ancêtre immédiat de la révision, habituellement désignée comme son parent. En fait, une révision contient de la place pour non pas un parent, mais deux. Mercurial utilise un hash spécial, appelé le null ID pour représenter l'idée qu'il n'y a pas de parent ici. Ce hash est simplement une chaîne de zéros.

Dans Figure 4.4, “Le concept de la structure d'un revlog, vous pouvez voir un exemple de la structure conceptuelle d'un revlog. Les filelogs, manifests et changelogs ont tous cette même structure ; ils diffèrent simplement dans le type de donnée stockée dans chaque delta ou snapshot.

La première révision d'un revlog (au bas de l'image) a le null ID dans chacune de ses cases parent. Pour une révision normale, sa première case parent contient l'ID de sa révision parent et la seconde contient le null ID, indiquant que cette révision n'a qu'un seul vrai parent. Si deux révisions ont le même parent, il s'agit de branches. Une révision qui représente une fusion (merge) entre deux branches a deux identifiants de révision normaux dans ses cases parents.

Figure 4.4. Le concept de la structure d'un revlog

XXX add text

4.4. Le répertoire de travail

Dans un répertoire de travail, Mercurial stocke une image des fichiers du dépôt à un changeset particulier.

Le répertoire de travail sait quel changeset il contient. Lorsque vous mettez à jour (update) le répertoire de travail à un certain changeset, Mercurial regarde la révision appropriée du manifest pour trouver quels fichiers il suivait au moment où le changeset a été committé, et quelle révision de chaque fichier était alors courante. Il recrée ensuite une copie de chacun de ces fichiers, avec le même contenu qu'ils avaient lorsque le changeset a été committé.

La structure spéciale dirstate contient la connaissance de Mercurial sur le répertoire de travail. Elle est maintenue par un fichier appelé .hg/dirstate dans un dépôt. Les détails du dirstate sont le changeset vers lequel le répertoire de travail se met à jour (update), et tous les fichiers que Mercurial suit dans le répertoire de travail. Il permet aussi à Mercurial de connaître rapidement les fichiers modifiés, en enregistrant l'heure de dernière modification et la taille de chacun.

Puisqu'une révision de revlog a des emplacements pour deux parents et peut représenter aussi bien une révision normale (avec un parent) ou une fusion de deux révisions anciennes, le dirstate a des emplacements pour deux parents. Lorsque vous utilisez la commande hg update, le changeset que vous mettez à jour est stocké dans l'emplacement du premier parent, et le null ID l'est dans le second. Lorsque vous utilisez la commande hg merge avec un autre changeset, le premier parent reste inchangé, et le second est rempli avec le changeset à partir duquel vous êtes en train de fusionner. La commande hg parents vous donne les parents du dirstate.

4.4.1. Que se passe-t-il lorsque vous committez

Le dirstate stocke les informations sur les parents pour plus qu'une simple comptabilité. Mercurial utilise les parents du dirstate comme les parents d'un nouveau changeset lorsque vous committez.

Figure 4.5. Le répertoire de travail peut avoir deux parents

XXX add text

Figure 4.5, “Le répertoire de travail peut avoir deux parents” montre l'état normal d'un répertoire de travail, où il n'y a qu'un seul changeset comme parent. Ce changeset est le tip, le changeset le plus récent dans le dépôt n'a pas d'enfant.

Figure 4.6. Le répertoire de travail gagne de nouveaux parents après un commit

XXX add text

On peut se représenter le répertoire de travail comme le changeset que je vais committer. Chaque fichier que vous demandez à Mercurial d'ajouter, de supprimer, de renommer ou de copier va être intégré dans ce changeset, tout comme les modifications de n'importe quel fichier que Mercurial est déjà en train de suivre ; le nouveau changeset aura les mêmes parents que le répertoire de travail.

Après un commit, Mercurial va mettre à jour les parents du répertoire de travail, ainsi, le premier parent est l'ID du nouveau changeset, et le second, le null ID. Ceci est illustré dans Figure 4.6, “Le répertoire de travail gagne de nouveaux parents après un commit. Mercurial ne touche à aucun des fichiers du répertoire de travail lorsque vous committez ; il modifie simplement le dirstate pour noter ses nouveaux parents.

4.4.2. Création d'une nouvelle head

Il est parfaitement normal de faire un update du répertoire de travail à un changeset autre que le tip courant. Par exemple, vous pourriez vouloir savoir à quoi votre projet ressemblait mardi dernier, ou regarder le changeset qui a introduit un bug. Dans des cas comme ça, la chose naturelle à faire est de faire un update du répertoire de travail au changeset qui vous intéresse, et ensuite d'en examiner les fichiers pour regarder leurs contenus comme ils l'étaient lorsque vous avez committé ce changeset. L'effet de ceci est montré dans Figure 4.7, “Le répertoire de travail, updaté pour un changeset plus ancien”.

Figure 4.7. Le répertoire de travail, updaté pour un changeset plus ancien

XXX add text

En ayant fait un update du répertoire de travail vers un changeset plus ancien, que se passe-t-il si vous faites des changements et ensuite committez ? Mercurial se comporte comme je l'ai fait remarqué plus haut. Les parents du répertoire de travail deviennent les parents du nouveau changeset. Ce nouveau changeset n'a pas d'enfant, donc il devient le nouveau tip. Le dépôt contient maintenant deux changesets qui n'ont pas d'enfant ; on appelle ceci des heads. Vous pouvez voir la structure que cela crée dans Figure 4.8, “Après un commit fait pendant la synchronisation avec un ancien changeset.

Figure 4.8. Après un commit fait pendant la synchronisation avec un ancien changeset

XXX add text

[Note] Note

Si vous êtes un nouvel utilisateur de Mercurial, vous devez garder à l'esprit une erreur commune, qui est d'utiliser la commande hg pull sans aucune option. Par défaut, la commande hg pull ne fait pas d'update sur le répertoire de travail, ainsi, vous allez récupérer les nouveaux changesets dans votre dépôt, mais le répertoire de travail va rester synchronisé au même changeset qu'il l'était avant le pull. Si vous faites des changements et committez ensuite, vous allez créer une nouvelle head puisque votre répertoire de travail n'est pas synchronisé au tip actuel. Pour combiner les opérations d'un pull suivi d'un update, exécutez hg pull -u.

Je place le mot erreur entre guillemets parce que tout ce dont vous avez besoin de faire pour rectifier la situation où vous avez créé une nouvelle head par accident est un hg merge suivi d'un hg commit. En d'autres mots, ceci n'a presque jamais de conséquences négatives ; il s'agit juste d'une surprise pour les nouveaux arrivants. Je discuterai d'autres moyens d'éviter ce comportement, et pourquoi Mercurial agit de cette façon surprenante plus tard.

4.4.3. Fusionner (merge) les changements

Lorsque vous exécutez la commande hg merge, Mercurial laisse le premier parent du répertoire de travail inchangé et fixe le second au changeset avec lequel vous fusionnez (merge), comme montré dans Figure 4.9, “Fusionner (merge) deux heads.

Figure 4.9. Fusionner (merge) deux heads

XXX add text

Mercurial doit aussi modifier le répertoire de travail pour fusionner les fichiers gérés dans les deux changesets. Un peu simplifié, le processus de fusion fonctionne comme ça : pour chaque fichier dans le manifest de chaque changeset.

  • Si aucun changeset n'a modifié un fichier, ne rien faire avec ce fichier.

  • Si un changeset a modifié un fichier et que l'autre ne l'a pas fait, créer une copie modifiée du fichier dans le répertoire de travail.

  • Si un changeset a modifié un fichier, et que l'autre ne l'a pas fait (ou l'a supprimé), supprimer le fichier du répertoire de travail.

  • Si un changeset a supprimé un fichier, mais que l'autre a modifié le fichier, demander à l'utilisateur quoi faire : garder le fichier modifié ou le supprimer ?

  • Si chacun des changesets a modifié un fichier, invoquer le programme externe de fusion pour choisir les nouveaux contenus pour le fichier fusionné. Ceci peut demander une intervention de l'utilisateur.

  • Si un changeset a modifié un fichier, et que l'autre a renommé ou copié le fichier, être sûr que les changements suivent le nouveau nom du fichier.

Il y a plus de détails—fusionner a beaucoup de cas épineux—mais ceux-ci sont des choix plus communs qui sont liés à une fusion (merge). Comme vous pouvez le voir, la plupart des cas sont entièrement automatiques, et effectivement, la plupart des fusions (merge) se terminent automatiquement, sans nécessiter votre intervention pour résoudre un conflit.

Lorsque vous pensez à ce qu'il se passe lorsque vous committez après un merge, une fois encore, le répertoire de travail est le changeset que je suis sur le point de committer. Après que la commande hg merge soit terminée, le répertoire de travail a deux parents ; ceux ci vont devenir les parents du nouveau changeset.

Mercurial vous permet d'exécuter de multiples fusions, mais vous devez committer le résultat de chaque fusion individuellement au fur et à mesure. Ceci est nécessaire puisque Mercurial ne stocke que deux parents pour chaque révision et le répertoire de travail. Alors qu'il serait techniquement faisable de fusionner de multiples changesets en même temps, Mercurial interdit cela pour être plus simple. Avec des fusions multiples, les risques de confusion pour l'utilisateur, de mauvaie résolution de conflits, et de pagaille dans les fusions augmenteraient de façon intolérable.

4.4.4. Fusions et renommages

Un nombre surprenant de systèmes de gestion de révisions fait peu ou pas attention à un nom de fichier au cours du temps. Par exemple, il était habituel que si un fichier était renommé d'un coté de la fusion, les changements à partir de l'autre coté étaient supprimés silencieusement.

Mercurial enregistre les metadata lorsque vous lui dites d'exécuter un renommage ou une copie. Il utilise ces metadatas durant une fusion pour faire les bonnes choses dans le cas d'un merge. Par exemple, si je renomme un fichier et que vous l'éditez sans le renommer, lorsque l'on fusionne, le fichier sera renommé et aura les changements appliqués.

4.5. D'autres fonctionnalités intéressantes

Dans les sections au-dessus, j'ai tenté de mettre l'accent sur certains aspects importants du design de Mercurial pour illustrer l'attention particulière qui a été portée à la fiabilité et à la performance. Cependant, l'attention aux détails ne s'arrête pas ici. Il y a de nombreux aspects sur la construction de Mercurial que je trouve personnellement intéressants. J'en détaillerai quelques-uns ici, séparément des éléments du big ticket ci-dessus, ainsi, si vous êtes intéressés, vous pourrez avoir une meilleure idée de la quantité d'ingéniosité qu'il y a derrière un système bien conçu.

4.5.1. Compression astucieuse

Lorsque cela est approprié, Mercurial stocke les snapshots et deltas sous une forme compressée. Il le fait en essayant toujours de compresser un snapshot ou un delta, mais en ne stockant la version compressée que si celle-ci est plus petite que la version non compressée.

Ceci signifie que Mercurial fait la bonne chose lorsqu'il stocke un fichier dont la forme native est compressée, comme une archive zip ou une image JPEG. Lorsque ces types de fichiers sont compressés une seconde fois, le fichier obtenu est habituellement plus gros que la forme compressée une seule fois et Mercurial stockera alors le zip ou JPEG.

Les deltas entre les révisions d'un fichier compressé sont habituellement plus gros que les snapshots du fichier, et Mercurial fait à nouveau la bonne chose dans ces cas. Il trouve qu'un delta dépasse le seuil auquel il devrait stocker un snapshot complet du fichier, alors il stocke le snapshot, en gagnant encore de la place en comparaison d'une approche naïve avec un delta seulement.

4.5.1.1. Recompression sur le réseau

Lors du stockage des révisions sur le disque, Mercurial utilise l'algorithme de compression deflate (le même que celui utilisé pour le format populaire d'archive zip), qui est un bon compromis entre la vitesse et le taux de compression. Cependant, lors de la transmission d'une révision de données par une connexion réseau, Mercurial décompresse les données de révision compressées.

Si la connexion passe par HTTP, Mercurial recompresse le flux entier de données en utilisant un algorithme de compression qui donne un meilleur taux de compression (l'algorithme Burrows-Wheeler utilisé principalement par le logiciel de compression bzip2). Cette combinaison de l'algorithme et de la compression du flux entier (plutôt que pour une révision à la fois) réduit substantiellement le nombre de bits qui sont transférés, résultant en une performance réseau accrue sur la plupart des supports.

Si la connexion passe par ssh, Mercurial ne recompresse pas le flux puisque ssh peut déjà le faire par lui-même. Vous pouvez demander à Mercurial de toujours utiliser la compression ssh en éditant le fichier .hgrc de votre répertoire personnel comme ci-dessous.

[ui]
ssh = ssh -C

4.5.2. Ordre de lecture/écriture et atomicité

L'histoire ne se résume pas à ajouter à la fin des fichiers lorsque l'on cherche à garantir que le lecteur ne verra pas qu'une écriture partielle. Si vous relisez Figure 4.2, “Metadata relationships”, les révisions dans le changelog pointent vers les révisions dans le manifest, et les révisions du manifest pointent vers les révisions du filelog. Cette hiérarchie est délibérée.

L'écriture commence par une transaction en écrivant dans le filelog et dans les données du manifest, et n'écrit aucune donnée du changelog tant que ce n'est pas terminé. La lecture commence en lisant les données du changelog, puis les données du manifest, et enfin les données du filelog.

Puisque que l'écriture ne finit pas d'écrire les données du filelog et du manifest avant d'écrire dans le changelog, la lecture ne verra jamais un pointeur vers une révision du manifest partiellement écrite à partir du changelog, et ne lira jamais un pointeur vers une révision du filelog partiellement écrite dans le manifest.

4.5.3. Accès concurrent

La garantie de l'ordre de lecture/écriture et de l'atomicité signifie que Mercurial n'a jamais besoin de poser de lock sur un dépôt lorsqu'il lit des données, même si le dépôt est en train d'être écrit au même moment que la lecture a lieu. Ceci a un grand impact sur la fiabilité ; vous pouvez avoir un nombre arbitraire de processus Mercurial qui lisent sans risque les données d'un dépôt en même temps, peu importe s'il est en train d'être lu ou non.

La nature sans lock de la lecture signifie que si vous partagez un dépôt sur un système multi-utilisateurs, vous n'avez pas besoin de donner aux autres utilisateurs locaux la permission d'écrire sur votre dépôt pour qu'ils soient capable de faire un clone ou un pull des changements à partir de celui-ci ; ils ont seulement besoin de la permission en lecture. (Il ne s'agit pas d'une fonctionnalité commune à travers les systèmes de gestion de révisions, donc ne prenez pas ça pour argent comptant ! La plupart ont besoin que les lecteurs soient capables de mettre un lock sur le dépôt pour y accéder en toute sécurité, et ceci demande des permissions en écriture, sur au moins un répertoire, ce qui provoque bien sûr toutes sortes de problèmes pénibles et agaçants relatifs à la sécurité et à l'administration.)

Mercurial utilise des locks pour assurer qu'un seul processus peut écrire dans le dépôt à un moment donné (le mécanisme de lock est sûr, même sur des systèmes de fichiers qui sont connus pour être hostiles aux locks, comme NFS). Si un dépôt dispose d'un lock, un processus qui cherche à écrire va attendre un peu avant de retenter pour voir si le dépôt perd son lock, mais si le dépôt garde trop longtemps son lock, le processus qui tente d'écrire va expirer (time out) après un moment. Cela veut dire par exemple que vos scripts lancés quotidiennement n'attendront pas toujours et boucleront si un système plantait sans avertissement, par exemple. (Oui, le timeout est configurable, de zéro à l'infini.)

4.5.3.1. Accès dirstate sûr

Comme avec les données de révision, Mercurial n'utilise pas de lock pour lire le fichier dirstate ; il n'acquiert pas un lock pour y écrire. Pour empêcher la possibilité de lire une copie partiellement écrite du fichier dirstate, Mercurial écrit sur un fichier avec un nom unique dans le même répertoire que le fichier dirstate, ensuite renomme le fichier temporaire automatiquement en dirstate. Le fichier nommé dirstate est ainsi garanti d'être écrit totalement, et non partiellement.

4.5.4. Empêcher les recherches

L'absence de recherche sur les têtes de disques est critique pour la performance de Mercurial, puisque toute recherche est beaucoup plus coûteuse comparativement à une grosse opération de lecture.

C'est pour ça, par exemple, que le dirstate est stocké dans un fichier unique. S'il y avait eu un dirstate par répertoire que Mercurial suivrait, le disque aurait recherché une fois par répertoire. Au lieu de ça, Mercurial lit entièrement un fichier unique, en une étape.

Mercurial utilise aussi un schéma copie à l'écriture lorsqu'il clone un dépôt sur un stockage local. Au lieu de copier chaque fichier revlog depuis l'ancien dépôt vers le nouveau dépôt, il crée un lien physique, qui est le plus court chemin pour dire Ces deux noms pointent vers le même fichier. Lorsque Mercurial est sur le point d'écrire sur l'un des revlogs de ces fichiers, il vérifie si le nombre de noms pointant sur ce fichier est plus grand que un. Si c'est le cas, plus d'un dépôt utilise le fichier, donc Mercurial crée une nouvelle copie du fichier qui est privée à ce dépôt.

Quelques développeurs de systèmes de gestion de révisions ont montré que cette idée de faire une copie privée complète d'un fichier n'est pas vraiment efficace au niveau du stockage. Bien que ce soit vrai, le stockage est peu onéreux, et cette méthode donne la plus grande performance lorsque l'on reporte la plupart des journalisations au système d'exploitation. Un schéma alternatif réduirait certainement la performance tout en augmentant la complexité du logiciel, mais la vitesse et la simplicité sont les clefs du confort de l'utilisation quotidienne.

4.5.5. Autres contenus du dirstate

Puisque Mercurial ne vous force pas à signaler que vous modifiez un fichier, il utilise le dirstate pour stocker certaines informations supplémentaires pour déterminer efficacement si vous avez ou non modifié un fichier. Pour chaque fichier du répertoire de travail, il stocke l'heure à laquelle il a été modifié, ainsi que la taille du fichier à cette heure.

Lorsque vous faites explicitement un hg add, hg remove, hg rename ou hg copy sur des fichiers, Mercurial met à jour le dirstate afin de savoir que faire lorsque vous effectuez un commit.

Le dirstate aide Mercurial à vérifier efficacement le statut des fichiers dans un dépôt.

  • Lorsque Mercurial vérifie l'état d'un fichier du répertoire de travail, il compare d'abord la date de dernière modification du fichier avec celle enregistrée dans le dirstate qui correspond à celle que Mercurial a écrit en dernier sur ce fichier. Si la date de dernière modification correspond à la date où Mercurial a écrit le fichier, celui ci n'a pas été modifié, donc Mercurial n'a pas besoin de revérifier.

  • Si la taille du fichier a changé, celui-ci a été modifié. Si la date de modification a changé mais que la taille est restée inchangée, seulement à ce moment là Mercurial doit vérifier le contenu du fichier pour savoir s'il a été modifié.

Enregistrer la date de modification et la taille réduit grandement le nombre d'opérations de lecture que Mercurial doit effectuer lorsque l'on utilise une commande comme hg status. Le résultat est un grand gain de performance.



[3] Il est possible (bien qu'inhabituel) qu'un manifest reste le même entre deux changesets, auquel cas l'entrée du changelog pour ces changesets pointera vers la même révision du manifest.