Table of Contents
À 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.
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
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.
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.
À 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”.
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].
Les fondements des “changelogs”, des “manifests” et des “filelogs” sont fournis par une unique structure appelée le revlog.
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.
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.
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.
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.
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.
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.
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.
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.
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” 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.
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.
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”.
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””.
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””.
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.
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.
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.
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.
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
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”.
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.)
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.
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.
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”.