mercurial/dirstateutils/v2.py
changeset 48233 a32a96079e2d
child 48234 7e78c72ee3ea
equal deleted inserted replaced
48232:e7b5e8ba7cab 48233:a32a96079e2d
       
     1 # v2.py - Pure-Python implementation of the dirstate-v2 file format
       
     2 #
       
     3 # Copyright Mercurial Contributors
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 from __future__ import absolute_import
       
     9 
       
    10 import struct
       
    11 
       
    12 from .. import policy
       
    13 
       
    14 parsers = policy.importmod('parsers')
       
    15 
       
    16 
       
    17 # Must match the constant of the same name in
       
    18 # `rust/hg-core/src/dirstate_tree/on_disk.rs`
       
    19 TREE_METADATA_SIZE = 44
       
    20 NODE_SIZE = 43
       
    21 
       
    22 
       
    23 # Must match the `TreeMetadata` Rust struct in
       
    24 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
       
    25 #
       
    26 # * 4 bytes: start offset of root nodes
       
    27 # * 4 bytes: number of root nodes
       
    28 # * 4 bytes: total number of nodes in the tree that have an entry
       
    29 # * 4 bytes: total number of nodes in the tree that have a copy source
       
    30 # * 4 bytes: number of bytes in the data file that are not used anymore
       
    31 # * 4 bytes: unused
       
    32 # * 20 bytes: SHA-1 hash of ignore patterns
       
    33 TREE_METADATA = struct.Struct('>LLLLL4s20s')
       
    34 
       
    35 
       
    36 # Must match the `Node` Rust struct in
       
    37 # `rust/hg-core/src/dirstate_tree/on_disk.rs`. See doc-comments there.
       
    38 #
       
    39 # * 4 bytes: start offset of full path
       
    40 # * 2 bytes: length of the full path
       
    41 # * 2 bytes: length within the full path before its "base name"
       
    42 # * 4 bytes: start offset of the copy source if any, or zero for no copy source
       
    43 # * 2 bytes: length of the copy source if any, or unused
       
    44 # * 4 bytes: start offset of child nodes
       
    45 # * 4 bytes: number of child nodes
       
    46 # * 4 bytes: number of descendant nodes that have an entry
       
    47 # * 4 bytes: number of descendant nodes that have a "tracked" state
       
    48 # * 1 byte: flags
       
    49 # * 4 bytes: expected size
       
    50 # * 4 bytes: mtime seconds
       
    51 # * 4 bytes: mtime nanoseconds
       
    52 NODE = struct.Struct('>LHHLHLLLLBlll')
       
    53 
       
    54 
       
    55 assert TREE_METADATA_SIZE == TREE_METADATA.size
       
    56 assert NODE_SIZE == NODE.size
       
    57 
       
    58 
       
    59 def parse_dirstate(map, copy_map, data, tree_metadata):
       
    60     """parse a full v2-dirstate from a binary data into dictionnaries:
       
    61 
       
    62     - map: a {path: entry} mapping that will be filled
       
    63     - copy_map: a {path: copy-source} mapping that will be filled
       
    64     - data: a binary blob contains v2 nodes data
       
    65     - tree_metadata:: a binary blob of the top level node (from the docket)
       
    66     """
       
    67     (
       
    68         root_nodes_start,
       
    69         root_nodes_len,
       
    70         _nodes_with_entry_count,
       
    71         _nodes_with_copy_source_count,
       
    72         _unreachable_bytes,
       
    73         _unused,
       
    74         _ignore_patterns_hash,
       
    75     ) = TREE_METADATA.unpack(tree_metadata)
       
    76     parse_nodes(map, copy_map, data, root_nodes_start, root_nodes_len)
       
    77 
       
    78 
       
    79 def parse_nodes(map, copy_map, data, start, len):
       
    80     """parse <len> nodes from <data> starting at offset <start>
       
    81 
       
    82     This is used by parse_dirstate to recursively fill `map` and `copy_map`.
       
    83     """
       
    84     for i in range(len):
       
    85         node_start = start + NODE_SIZE * i
       
    86         node_bytes = slice_with_len(data, node_start, NODE_SIZE)
       
    87         (
       
    88             path_start,
       
    89             path_len,
       
    90             _basename_start,
       
    91             copy_source_start,
       
    92             copy_source_len,
       
    93             children_start,
       
    94             children_count,
       
    95             _descendants_with_entry_count,
       
    96             _tracked_descendants_count,
       
    97             flags,
       
    98             size,
       
    99             mtime_s,
       
   100             _mtime_ns,
       
   101         ) = NODE.unpack(node_bytes)
       
   102 
       
   103         # Parse child nodes of this node recursively
       
   104         parse_nodes(map, copy_map, data, children_start, children_count)
       
   105 
       
   106         item = parsers.DirstateItem.from_v2_data(flags, size, mtime_s)
       
   107         if not item.any_tracked:
       
   108             continue
       
   109         path = slice_with_len(data, path_start, path_len)
       
   110         map[path] = item
       
   111         if copy_source_start:
       
   112             copy_map[path] = slice_with_len(
       
   113                 data, copy_source_start, copy_source_len
       
   114             )
       
   115 
       
   116 
       
   117 def slice_with_len(data, start, len):
       
   118     return data[start : start + len]