|
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] |