view mercurial/dirstateutils/docket.py @ 47674:ff97e793ed36

dirstate-v2: Introduce a docket file .hg/dirstate now only contains some metadata to point to a separate data file named .hg/dirstate.{}.d with a random hexadecimal identifier. For now every update creates a new data file and removes the old one, but later we?ll (usually) append to an existing file. Separating into two files allows doing the "write to a temporary file then atomically rename into destination" dance with only a small docket file, without always rewriting a lot of data. Differential Revision: https://phab.mercurial-scm.org/D11088
author Simon Sapin <simon.sapin@octobus.net>
date Thu, 08 Jul 2021 12:18:21 +0200
parents
children 78f7f0d490ee
line wrap: on
line source

# dirstatedocket.py - docket file for dirstate-v2
#
# Copyright Mercurial Contributors
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2 or any later version.

from __future__ import absolute_import

import struct

from ..revlogutils import docket as docket_mod


V2_FORMAT_MARKER = b"dirstate-v2\n"

# * 12 bytes: format marker
# * 32 bytes: node ID of the working directory's first parent
# * 32 bytes: node ID of the working directory's second parent
# * 4 bytes: big-endian used size of the data file
# * 1 byte: length of the data file's UUID
# * variable: data file's UUID
#
# Node IDs are null-padded if shorter than 32 bytes.
# A data file shorter than the specified used size is corrupted (truncated)
HEADER = struct.Struct(">{}s32s32sLB".format(len(V2_FORMAT_MARKER)))


class DirstateDocket(object):
    data_filename_pattern = b'dirstate.%s.d'

    def __init__(self, parents, data_size, uuid):
        self.parents = parents
        self.data_size = data_size
        self.uuid = uuid

    @classmethod
    def with_new_uuid(cls, parents, data):
        return cls(parents, data, docket_mod.make_uid())

    @classmethod
    def parse(cls, data, nodeconstants):
        if not data:
            parents = (nodeconstants.nullid, nodeconstants.nullid)
            return cls(parents, 0, None)
        marker, p1, p2, data_size, uuid_size = HEADER.unpack_from(data)
        if marker != V2_FORMAT_MARKER:
            raise ValueError("expected dirstate-v2 marker")
        uuid = data[HEADER.size : HEADER.size + uuid_size]
        p1 = p1[: nodeconstants.nodelen]
        p2 = p2[: nodeconstants.nodelen]
        return cls((p1, p2), data_size, uuid)

    def serialize(self):
        p1, p2 = self.parents
        header = HEADER.pack(
            V2_FORMAT_MARKER, p1, p2, self.data_size, len(self.uuid)
        )
        return header + self.uuid

    def data_filename(self):
        return self.data_filename_pattern % self.uuid