Show More
@@ -999,11 +999,7 b' def debugdirstateignorepatternshash(ui, ' | |||||
999 | if repo.dirstate._use_dirstate_v2: |
|
999 | if repo.dirstate._use_dirstate_v2: | |
1000 | docket = repo.dirstate._map.docket |
|
1000 | docket = repo.dirstate._map.docket | |
1001 | hash_len = 20 # 160 bits for SHA-1 |
|
1001 | hash_len = 20 # 160 bits for SHA-1 | |
1002 |
hash_ |
|
1002 | hash_bytes = docket.tree_metadata[-hash_len:] | |
1003 | data_filename = docket.data_filename() |
|
|||
1004 | with repo.vfs(data_filename) as f: |
|
|||
1005 | f.seek(hash_offset) |
|
|||
1006 | hash_bytes = f.read(hash_len) |
|
|||
1007 | ui.write(binascii.hexlify(hash_bytes) + b'\n') |
|
1003 | ui.write(binascii.hexlify(hash_bytes) + b'\n') | |
1008 |
|
1004 | |||
1009 |
|
1005 |
@@ -638,7 +638,7 b' if rustmod is not None:' | |||||
638 | else: |
|
638 | else: | |
639 | data = b'' |
|
639 | data = b'' | |
640 | self._rustmap = rustmod.DirstateMap.new_v2( |
|
640 | self._rustmap = rustmod.DirstateMap.new_v2( | |
641 | data, self.docket.data_size |
|
641 | data, self.docket.data_size, self.docket.tree_metadata | |
642 | ) |
|
642 | ) | |
643 | parents = self.docket.parents |
|
643 | parents = self.docket.parents | |
644 | else: |
|
644 | else: | |
@@ -665,7 +665,7 b' if rustmod is not None:' | |||||
665 |
|
665 | |||
666 | # We can only append to an existing data file if there is one |
|
666 | # We can only append to an existing data file if there is one | |
667 | can_append = self.docket.uuid is not None |
|
667 | can_append = self.docket.uuid is not None | |
668 | packed, append = self._rustmap.write_v2(now, can_append) |
|
668 | packed, meta, append = self._rustmap.write_v2(now, can_append) | |
669 | if append: |
|
669 | if append: | |
670 | docket = self.docket |
|
670 | docket = self.docket | |
671 | data_filename = docket.data_filename() |
|
671 | data_filename = docket.data_filename() | |
@@ -679,12 +679,13 b' if rustmod is not None:' | |||||
679 | assert written == len(packed), (written, len(packed)) |
|
679 | assert written == len(packed), (written, len(packed)) | |
680 | docket.data_size += len(packed) |
|
680 | docket.data_size += len(packed) | |
681 | docket.parents = self.parents() |
|
681 | docket.parents = self.parents() | |
|
682 | docket.tree_metadata = meta | |||
682 | st.write(docket.serialize()) |
|
683 | st.write(docket.serialize()) | |
683 | st.close() |
|
684 | st.close() | |
684 | else: |
|
685 | else: | |
685 | old_docket = self.docket |
|
686 | old_docket = self.docket | |
686 | new_docket = docketmod.DirstateDocket.with_new_uuid( |
|
687 | new_docket = docketmod.DirstateDocket.with_new_uuid( | |
687 | self.parents(), len(packed) |
|
688 | self.parents(), len(packed), meta | |
688 | ) |
|
689 | ) | |
689 | data_filename = new_docket.data_filename() |
|
690 | data_filename = new_docket.data_filename() | |
690 | if tr: |
|
691 | if tr: |
@@ -14,47 +14,60 b' from ..revlogutils import docket as dock' | |||||
14 |
|
14 | |||
15 | V2_FORMAT_MARKER = b"dirstate-v2\n" |
|
15 | V2_FORMAT_MARKER = b"dirstate-v2\n" | |
16 |
|
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 = 40 | |||
|
20 | ||||
17 | # * 12 bytes: format marker |
|
21 | # * 12 bytes: format marker | |
18 | # * 32 bytes: node ID of the working directory's first parent |
|
22 | # * 32 bytes: node ID of the working directory's first parent | |
19 | # * 32 bytes: node ID of the working directory's second parent |
|
23 | # * 32 bytes: node ID of the working directory's second parent | |
20 | # * 4 bytes: big-endian used size of the data file |
|
24 | # * 4 bytes: big-endian used size of the data file | |
|
25 | # * {TREE_METADATA_SIZE} bytes: tree metadata, parsed separately | |||
21 | # * 1 byte: length of the data file's UUID |
|
26 | # * 1 byte: length of the data file's UUID | |
22 | # * variable: data file's UUID |
|
27 | # * variable: data file's UUID | |
23 | # |
|
28 | # | |
24 | # Node IDs are null-padded if shorter than 32 bytes. |
|
29 | # Node IDs are null-padded if shorter than 32 bytes. | |
25 | # A data file shorter than the specified used size is corrupted (truncated) |
|
30 | # A data file shorter than the specified used size is corrupted (truncated) | |
26 | HEADER = struct.Struct(">{}s32s32sLB".format(len(V2_FORMAT_MARKER))) |
|
31 | HEADER = struct.Struct( | |
|
32 | ">{}s32s32sL{}sB".format(len(V2_FORMAT_MARKER), TREE_METADATA_SIZE) | |||
|
33 | ) | |||
27 |
|
34 | |||
28 |
|
35 | |||
29 | class DirstateDocket(object): |
|
36 | class DirstateDocket(object): | |
30 | data_filename_pattern = b'dirstate.%s.d' |
|
37 | data_filename_pattern = b'dirstate.%s.d' | |
31 |
|
38 | |||
32 | def __init__(self, parents, data_size, uuid): |
|
39 | def __init__(self, parents, data_size, tree_metadata, uuid): | |
33 | self.parents = parents |
|
40 | self.parents = parents | |
34 | self.data_size = data_size |
|
41 | self.data_size = data_size | |
|
42 | self.tree_metadata = tree_metadata | |||
35 | self.uuid = uuid |
|
43 | self.uuid = uuid | |
36 |
|
44 | |||
37 | @classmethod |
|
45 | @classmethod | |
38 | def with_new_uuid(cls, parents, data): |
|
46 | def with_new_uuid(cls, parents, data_size, tree_metadata): | |
39 | return cls(parents, data, docket_mod.make_uid()) |
|
47 | return cls(parents, data_size, tree_metadata, docket_mod.make_uid()) | |
40 |
|
48 | |||
41 | @classmethod |
|
49 | @classmethod | |
42 | def parse(cls, data, nodeconstants): |
|
50 | def parse(cls, data, nodeconstants): | |
43 | if not data: |
|
51 | if not data: | |
44 | parents = (nodeconstants.nullid, nodeconstants.nullid) |
|
52 | parents = (nodeconstants.nullid, nodeconstants.nullid) | |
45 | return cls(parents, 0, None) |
|
53 | return cls(parents, 0, b'', None) | |
46 | marker, p1, p2, data_size, uuid_size = HEADER.unpack_from(data) |
|
54 | marker, p1, p2, data_size, meta, uuid_size = HEADER.unpack_from(data) | |
47 | if marker != V2_FORMAT_MARKER: |
|
55 | if marker != V2_FORMAT_MARKER: | |
48 | raise ValueError("expected dirstate-v2 marker") |
|
56 | raise ValueError("expected dirstate-v2 marker") | |
49 | uuid = data[HEADER.size : HEADER.size + uuid_size] |
|
57 | uuid = data[HEADER.size : HEADER.size + uuid_size] | |
50 | p1 = p1[: nodeconstants.nodelen] |
|
58 | p1 = p1[: nodeconstants.nodelen] | |
51 | p2 = p2[: nodeconstants.nodelen] |
|
59 | p2 = p2[: nodeconstants.nodelen] | |
52 | return cls((p1, p2), data_size, uuid) |
|
60 | return cls((p1, p2), data_size, meta, uuid) | |
53 |
|
61 | |||
54 | def serialize(self): |
|
62 | def serialize(self): | |
55 | p1, p2 = self.parents |
|
63 | p1, p2 = self.parents | |
56 | header = HEADER.pack( |
|
64 | header = HEADER.pack( | |
57 | V2_FORMAT_MARKER, p1, p2, self.data_size, len(self.uuid) |
|
65 | V2_FORMAT_MARKER, | |
|
66 | p1, | |||
|
67 | p2, | |||
|
68 | self.data_size, | |||
|
69 | self.tree_metadata, | |||
|
70 | len(self.uuid), | |||
58 | ) |
|
71 | ) | |
59 | return header + self.uuid |
|
72 | return header + self.uuid | |
60 |
|
73 |
@@ -424,9 +424,10 b" impl<'on_disk> DirstateMap<'on_disk> {" | |||||
424 | pub fn new_v2( |
|
424 | pub fn new_v2( | |
425 | on_disk: &'on_disk [u8], |
|
425 | on_disk: &'on_disk [u8], | |
426 | data_size: usize, |
|
426 | data_size: usize, | |
|
427 | metadata: &[u8], | |||
427 | ) -> Result<Self, DirstateError> { |
|
428 | ) -> Result<Self, DirstateError> { | |
428 | if let Some(data) = on_disk.get(..data_size) { |
|
429 | if let Some(data) = on_disk.get(..data_size) { | |
429 | Ok(on_disk::read(data)?) |
|
430 | Ok(on_disk::read(data, metadata)?) | |
430 | } else { |
|
431 | } else { | |
431 | Err(DirstateV2ParseError.into()) |
|
432 | Err(DirstateV2ParseError.into()) | |
432 | } |
|
433 | } | |
@@ -1094,15 +1095,16 b" impl<'on_disk> super::dispatch::Dirstate" | |||||
1094 | Ok(packed) |
|
1095 | Ok(packed) | |
1095 | } |
|
1096 | } | |
1096 |
|
1097 | |||
1097 |
/// Returns new data together with whether that data should be |
|
1098 | /// Returns new data and metadata together with whether that data should be | |
1098 |
/// the existing data file whose content is at |
|
1099 | /// appended to the existing data file whose content is at | |
1099 |
/// instead of written to a new data file |
|
1100 | /// `self.on_disk` (true), instead of written to a new data file | |
|
1101 | /// (false). | |||
1100 | #[timed] |
|
1102 | #[timed] | |
1101 | fn pack_v2( |
|
1103 | fn pack_v2( | |
1102 | &mut self, |
|
1104 | &mut self, | |
1103 | now: Timestamp, |
|
1105 | now: Timestamp, | |
1104 | can_append: bool, |
|
1106 | can_append: bool, | |
1105 | ) -> Result<(Vec<u8>, bool), DirstateError> { |
|
1107 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | |
1106 | // TODO: how do we want to handle this in 2038? |
|
1108 | // TODO: how do we want to handle this in 2038? | |
1107 | let now: i32 = now.0.try_into().expect("time overflow"); |
|
1109 | let now: i32 = now.0.try_into().expect("time overflow"); | |
1108 | let mut paths = Vec::new(); |
|
1110 | let mut paths = Vec::new(); |
@@ -182,16 +182,17 b' pub trait DirstateMapMethods {' | |||||
182 | /// serialize bytes to write a dirstate data file to disk in dirstate-v2 |
|
182 | /// serialize bytes to write a dirstate data file to disk in dirstate-v2 | |
183 | /// format. |
|
183 | /// format. | |
184 | /// |
|
184 | /// | |
185 |
/// Returns new data together with whether that data should be |
|
185 | /// Returns new data and metadata together with whether that data should be | |
186 |
/// the existing data file whose content is at |
|
186 | /// appended to the existing data file whose content is at | |
187 |
/// instead of written to a new data file |
|
187 | /// `self.on_disk` (true), instead of written to a new data file | |
|
188 | /// (false). | |||
188 | /// |
|
189 | /// | |
189 | /// Note: this is only supported by the tree dirstate map. |
|
190 | /// Note: this is only supported by the tree dirstate map. | |
190 | fn pack_v2( |
|
191 | fn pack_v2( | |
191 | &mut self, |
|
192 | &mut self, | |
192 | now: Timestamp, |
|
193 | now: Timestamp, | |
193 | can_append: bool, |
|
194 | can_append: bool, | |
194 | ) -> Result<(Vec<u8>, bool), DirstateError>; |
|
195 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError>; | |
195 |
|
196 | |||
196 | /// Run the status algorithm. |
|
197 | /// Run the status algorithm. | |
197 | /// |
|
198 | /// | |
@@ -395,7 +396,7 b' impl DirstateMapMethods for DirstateMap ' | |||||
395 | &mut self, |
|
396 | &mut self, | |
396 | _now: Timestamp, |
|
397 | _now: Timestamp, | |
397 | _can_append: bool, |
|
398 | _can_append: bool, | |
398 | ) -> Result<(Vec<u8>, bool), DirstateError> { |
|
399 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | |
399 | panic!( |
|
400 | panic!( | |
400 | "should have used dirstate_tree::DirstateMap to use the v2 format" |
|
401 | "should have used dirstate_tree::DirstateMap to use the v2 format" | |
401 | ) |
|
402 | ) |
@@ -47,6 +47,18 b' const USED_NODE_ID_BYTES: usize = 20;' | |||||
47 | pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20; |
|
47 | pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20; | |
48 | pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN]; |
|
48 | pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN]; | |
49 |
|
49 | |||
|
50 | /// Must match the constant of the same name in | |||
|
51 | /// `mercurial/dirstateutils/docket.py` | |||
|
52 | const TREE_METADATA_SIZE: usize = 40; | |||
|
53 | ||||
|
54 | /// Make sure that size-affecting changes are made knowingly | |||
|
55 | #[allow(unused)] | |||
|
56 | fn static_assert_size_of() { | |||
|
57 | let _ = std::mem::transmute::<DocketHeader, [u8; 121]>; | |||
|
58 | let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>; | |||
|
59 | let _ = std::mem::transmute::<Node, [u8; 43]>; | |||
|
60 | } | |||
|
61 | ||||
50 | // Must match `HEADER` in `mercurial/dirstateutils/docket.py` |
|
62 | // Must match `HEADER` in `mercurial/dirstateutils/docket.py` | |
51 | #[derive(BytesCast)] |
|
63 | #[derive(BytesCast)] | |
52 | #[repr(C)] |
|
64 | #[repr(C)] | |
@@ -58,6 +70,8 b' struct DocketHeader {' | |||||
58 | /// Counted in bytes |
|
70 | /// Counted in bytes | |
59 | data_size: Size, |
|
71 | data_size: Size, | |
60 |
|
72 | |||
|
73 | metadata: TreeMetadata, | |||
|
74 | ||||
61 | uuid_size: u8, |
|
75 | uuid_size: u8, | |
62 | } |
|
76 | } | |
63 |
|
77 | |||
@@ -68,7 +82,7 b" pub struct Docket<'on_disk> {" | |||||
68 |
|
82 | |||
69 | #[derive(BytesCast)] |
|
83 | #[derive(BytesCast)] | |
70 | #[repr(C)] |
|
84 | #[repr(C)] | |
71 | struct Root { |
|
85 | struct TreeMetadata { | |
72 | root_nodes: ChildNodes, |
|
86 | root_nodes: ChildNodes, | |
73 | nodes_with_entry_count: Size, |
|
87 | nodes_with_entry_count: Size, | |
74 | nodes_with_copy_source_count: Size, |
|
88 | nodes_with_copy_source_count: Size, | |
@@ -134,7 +148,7 b' pub(super) struct Node {' | |||||
134 | /// - All direct children of this directory (as returned by |
|
148 | /// - All direct children of this directory (as returned by | |
135 | /// `std::fs::read_dir`) either have a corresponding dirstate node, or |
|
149 | /// `std::fs::read_dir`) either have a corresponding dirstate node, or | |
136 | /// are ignored by ignore patterns whose hash is in |
|
150 | /// are ignored by ignore patterns whose hash is in | |
137 |
/// ` |
|
151 | /// `TreeMetadata::ignore_patterns_hash`. | |
138 | /// |
|
152 | /// | |
139 | /// This means that if `std::fs::symlink_metadata` later reports the |
|
153 | /// This means that if `std::fs::symlink_metadata` later reports the | |
140 | /// same modification time and ignored patterns haven’t changed, a run |
|
154 | /// same modification time and ignored patterns haven’t changed, a run | |
@@ -205,13 +219,6 b' struct PathSlice {' | |||||
205 | /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes |
|
219 | /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes | |
206 | type OptPathSlice = PathSlice; |
|
220 | type OptPathSlice = PathSlice; | |
207 |
|
221 | |||
208 | /// Make sure that size-affecting changes are made knowingly |
|
|||
209 | fn _static_assert_size_of() { |
|
|||
210 | let _ = std::mem::transmute::<DocketHeader, [u8; 81]>; |
|
|||
211 | let _ = std::mem::transmute::<Root, [u8; 40]>; |
|
|||
212 | let _ = std::mem::transmute::<Node, [u8; 43]>; |
|
|||
213 | } |
|
|||
214 |
|
||||
215 | /// Unexpected file format found in `.hg/dirstate` with the "v2" format. |
|
222 | /// Unexpected file format found in `.hg/dirstate` with the "v2" format. | |
216 | /// |
|
223 | /// | |
217 | /// This should only happen if Mercurial is buggy or a repository is corrupted. |
|
224 | /// This should only happen if Mercurial is buggy or a repository is corrupted. | |
@@ -242,6 +249,10 b" impl<'on_disk> Docket<'on_disk> {" | |||||
242 | DirstateParents { p1, p2 } |
|
249 | DirstateParents { p1, p2 } | |
243 | } |
|
250 | } | |
244 |
|
251 | |||
|
252 | pub fn tree_metadata(&self) -> &[u8] { | |||
|
253 | self.header.metadata.as_bytes() | |||
|
254 | } | |||
|
255 | ||||
245 | pub fn data_size(&self) -> usize { |
|
256 | pub fn data_size(&self) -> usize { | |
246 | // This `unwrap` could only panic on a 16-bit CPU |
|
257 | // This `unwrap` could only panic on a 16-bit CPU | |
247 | self.header.data_size.get().try_into().unwrap() |
|
258 | self.header.data_size.get().try_into().unwrap() | |
@@ -265,40 +276,25 b' pub fn read_docket(' | |||||
265 | } |
|
276 | } | |
266 | } |
|
277 | } | |
267 |
|
278 | |||
268 | fn read_root<'on_disk>( |
|
|||
269 | on_disk: &'on_disk [u8], |
|
|||
270 | ) -> Result<&'on_disk Root, DirstateV2ParseError> { |
|
|||
271 | // Find the `Root` at the end of the given slice |
|
|||
272 | let root_offset = on_disk |
|
|||
273 | .len() |
|
|||
274 | .checked_sub(std::mem::size_of::<Root>()) |
|
|||
275 | // A non-empty slice too short is an error |
|
|||
276 | .ok_or(DirstateV2ParseError)?; |
|
|||
277 | let (root, _) = Root::from_bytes(&on_disk[root_offset..]) |
|
|||
278 | .map_err(|_| DirstateV2ParseError)?; |
|
|||
279 | Ok(root) |
|
|||
280 | } |
|
|||
281 |
|
||||
282 | pub(super) fn read<'on_disk>( |
|
279 | pub(super) fn read<'on_disk>( | |
283 | on_disk: &'on_disk [u8], |
|
280 | on_disk: &'on_disk [u8], | |
|
281 | metadata: &[u8], | |||
284 | ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> { |
|
282 | ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> { | |
285 | if on_disk.is_empty() { |
|
283 | if on_disk.is_empty() { | |
286 | return Ok(DirstateMap::empty(on_disk)); |
|
284 | return Ok(DirstateMap::empty(on_disk)); | |
287 | } |
|
285 | } | |
288 | let root = read_root(on_disk)?; |
|
286 | let (meta, _) = TreeMetadata::from_bytes(metadata) | |
289 | let mut unreachable_bytes = root.unreachable_bytes.get(); |
|
287 | .map_err(|_| DirstateV2ParseError)?; | |
290 | // Each append writes a new `Root`, so it’s never reused |
|
|||
291 | unreachable_bytes += std::mem::size_of::<Root>() as u32; |
|
|||
292 | let dirstate_map = DirstateMap { |
|
288 | let dirstate_map = DirstateMap { | |
293 | on_disk, |
|
289 | on_disk, | |
294 | root: dirstate_map::ChildNodes::OnDisk(read_nodes( |
|
290 | root: dirstate_map::ChildNodes::OnDisk(read_nodes( | |
295 | on_disk, |
|
291 | on_disk, | |
296 |
|
|
292 | meta.root_nodes, | |
297 | )?), |
|
293 | )?), | |
298 |
nodes_with_entry_count: |
|
294 | nodes_with_entry_count: meta.nodes_with_entry_count.get(), | |
299 |
nodes_with_copy_source_count: |
|
295 | nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(), | |
300 |
ignore_patterns_hash: |
|
296 | ignore_patterns_hash: meta.ignore_patterns_hash, | |
301 | unreachable_bytes, |
|
297 | unreachable_bytes: meta.unreachable_bytes.get(), | |
302 | }; |
|
298 | }; | |
303 | Ok(dirstate_map) |
|
299 | Ok(dirstate_map) | |
304 | } |
|
300 | } | |
@@ -530,9 +526,11 b' where' | |||||
530 |
|
526 | |||
531 | pub(crate) fn for_each_tracked_path<'on_disk>( |
|
527 | pub(crate) fn for_each_tracked_path<'on_disk>( | |
532 | on_disk: &'on_disk [u8], |
|
528 | on_disk: &'on_disk [u8], | |
|
529 | metadata: &[u8], | |||
533 | mut f: impl FnMut(&'on_disk HgPath), |
|
530 | mut f: impl FnMut(&'on_disk HgPath), | |
534 | ) -> Result<(), DirstateV2ParseError> { |
|
531 | ) -> Result<(), DirstateV2ParseError> { | |
535 | let root = read_root(on_disk)?; |
|
532 | let (meta, _) = TreeMetadata::from_bytes(metadata) | |
|
533 | .map_err(|_| DirstateV2ParseError)?; | |||
536 | fn recur<'on_disk>( |
|
534 | fn recur<'on_disk>( | |
537 | on_disk: &'on_disk [u8], |
|
535 | on_disk: &'on_disk [u8], | |
538 | nodes: ChildNodes, |
|
536 | nodes: ChildNodes, | |
@@ -548,22 +546,22 b" pub(crate) fn for_each_tracked_path<'on_" | |||||
548 | } |
|
546 | } | |
549 | Ok(()) |
|
547 | Ok(()) | |
550 | } |
|
548 | } | |
551 |
recur(on_disk, |
|
549 | recur(on_disk, meta.root_nodes, &mut f) | |
552 | } |
|
550 | } | |
553 |
|
551 | |||
554 |
/// Returns new data together with whether that data should be |
|
552 | /// Returns new data and metadata, together with whether that data should be | |
555 |
/// existing data file whose content is at |
|
553 | /// appended to the existing data file whose content is at | |
556 |
/// instead of written to a new data file |
|
554 | /// `dirstate_map.on_disk` (true), instead of written to a new data file | |
|
555 | /// (false). | |||
557 | pub(super) fn write( |
|
556 | pub(super) fn write( | |
558 | dirstate_map: &mut DirstateMap, |
|
557 | dirstate_map: &mut DirstateMap, | |
559 | can_append: bool, |
|
558 | can_append: bool, | |
560 | ) -> Result<(Vec<u8>, bool), DirstateError> { |
|
559 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | |
561 | let append = can_append && dirstate_map.write_should_append(); |
|
560 | let append = can_append && dirstate_map.write_should_append(); | |
562 |
|
561 | |||
563 | // This ignores the space for paths, and for nodes without an entry. |
|
562 | // This ignores the space for paths, and for nodes without an entry. | |
564 | // TODO: better estimate? Skip the `Vec` and write to a file directly? |
|
563 | // TODO: better estimate? Skip the `Vec` and write to a file directly? | |
565 |
let size_guess = std::mem::size_of::< |
|
564 | let size_guess = std::mem::size_of::<Node>() | |
566 | + std::mem::size_of::<Node>() |
|
|||
567 |
|
|
565 | * dirstate_map.nodes_with_entry_count as usize; | |
568 |
|
566 | |||
569 | let mut writer = Writer { |
|
567 | let mut writer = Writer { | |
@@ -574,7 +572,7 b' pub(super) fn write(' | |||||
574 |
|
572 | |||
575 | let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?; |
|
573 | let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?; | |
576 |
|
574 | |||
577 |
let |
|
575 | let meta = TreeMetadata { | |
578 | root_nodes, |
|
576 | root_nodes, | |
579 | nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(), |
|
577 | nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(), | |
580 | nodes_with_copy_source_count: dirstate_map |
|
578 | nodes_with_copy_source_count: dirstate_map | |
@@ -583,8 +581,7 b' pub(super) fn write(' | |||||
583 | unreachable_bytes: dirstate_map.unreachable_bytes.into(), |
|
581 | unreachable_bytes: dirstate_map.unreachable_bytes.into(), | |
584 | ignore_patterns_hash: dirstate_map.ignore_patterns_hash, |
|
582 | ignore_patterns_hash: dirstate_map.ignore_patterns_hash, | |
585 | }; |
|
583 | }; | |
586 |
writer.out |
|
584 | Ok((writer.out, meta.as_bytes().to_vec(), append)) | |
587 | Ok((writer.out, append)) |
|
|||
588 | } |
|
585 | } | |
589 |
|
586 | |||
590 | struct Writer<'dmap, 'on_disk> { |
|
587 | struct Writer<'dmap, 'on_disk> { |
@@ -22,27 +22,33 b' use rayon::prelude::*;' | |||||
22 | pub struct Dirstate { |
|
22 | pub struct Dirstate { | |
23 | /// The `dirstate` content. |
|
23 | /// The `dirstate` content. | |
24 | content: Vec<u8>, |
|
24 | content: Vec<u8>, | |
25 | dirstate_v2: bool, |
|
25 | v2_metadata: Option<Vec<u8>>, | |
26 | } |
|
26 | } | |
27 |
|
27 | |||
28 | impl Dirstate { |
|
28 | impl Dirstate { | |
29 | pub fn new(repo: &Repo) -> Result<Self, HgError> { |
|
29 | pub fn new(repo: &Repo) -> Result<Self, HgError> { | |
30 | let mut content = repo.hg_vfs().read("dirstate")?; |
|
30 | let mut content = repo.hg_vfs().read("dirstate")?; | |
31 | if repo.has_dirstate_v2() { |
|
31 | let v2_metadata = if repo.has_dirstate_v2() { | |
32 | let docket = read_docket(&content)?; |
|
32 | let docket = read_docket(&content)?; | |
|
33 | let meta = docket.tree_metadata().to_vec(); | |||
33 | content = repo.hg_vfs().read(docket.data_filename())?; |
|
34 | content = repo.hg_vfs().read(docket.data_filename())?; | |
34 | } |
|
35 | Some(meta) | |
|
36 | } else { | |||
|
37 | None | |||
|
38 | }; | |||
35 | Ok(Self { |
|
39 | Ok(Self { | |
36 | content, |
|
40 | content, | |
37 | dirstate_v2: repo.has_dirstate_v2(), |
|
41 | v2_metadata, | |
38 | }) |
|
42 | }) | |
39 | } |
|
43 | } | |
40 |
|
44 | |||
41 | pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> { |
|
45 | pub fn tracked_files(&self) -> Result<Vec<&HgPath>, DirstateError> { | |
42 | let mut files = Vec::new(); |
|
46 | let mut files = Vec::new(); | |
43 | if !self.content.is_empty() { |
|
47 | if !self.content.is_empty() { | |
44 | if self.dirstate_v2 { |
|
48 | if let Some(meta) = &self.v2_metadata { | |
45 |
for_each_tracked_path(&self.content, |path| |
|
49 | for_each_tracked_path(&self.content, meta, |path| { | |
|
50 | files.push(path) | |||
|
51 | })? | |||
46 | } else { |
|
52 | } else { | |
47 | let _parents = parse_dirstate_entries( |
|
53 | let _parents = parse_dirstate_entries( | |
48 | &self.content, |
|
54 | &self.content, |
@@ -84,12 +84,14 b' py_class!(pub class DirstateMap |py| {' | |||||
84 | def new_v2( |
|
84 | def new_v2( | |
85 | on_disk: PyBytes, |
|
85 | on_disk: PyBytes, | |
86 | data_size: usize, |
|
86 | data_size: usize, | |
|
87 | tree_metadata: PyBytes, | |||
87 | ) -> PyResult<PyObject> { |
|
88 | ) -> PyResult<PyObject> { | |
88 | let dirstate_error = |e: DirstateError| { |
|
89 | let dirstate_error = |e: DirstateError| { | |
89 | PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e)) |
|
90 | PyErr::new::<exc::OSError, _>(py, format!("Dirstate error: {:?}", e)) | |
90 | }; |
|
91 | }; | |
91 |
let inner = OwningDirstateMap::new_v2( |
|
92 | let inner = OwningDirstateMap::new_v2( | |
92 | .map_err(dirstate_error)?; |
|
93 | py, on_disk, data_size, tree_metadata, | |
|
94 | ).map_err(dirstate_error)?; | |||
93 | let map = Self::create_instance(py, Box::new(inner))?; |
|
95 | let map = Self::create_instance(py, Box::new(inner))?; | |
94 | Ok(map.into_object()) |
|
96 | Ok(map.into_object()) | |
95 | } |
|
97 | } | |
@@ -353,9 +355,11 b' py_class!(pub class DirstateMap |py| {' | |||||
353 | let mut inner = self.inner(py).borrow_mut(); |
|
355 | let mut inner = self.inner(py).borrow_mut(); | |
354 | let result = inner.pack_v2(now, can_append); |
|
356 | let result = inner.pack_v2(now, can_append); | |
355 | match result { |
|
357 | match result { | |
356 | Ok((packed, append)) => { |
|
358 | Ok((packed, tree_metadata, append)) => { | |
357 | let packed = PyBytes::new(py, &packed); |
|
359 | let packed = PyBytes::new(py, &packed); | |
358 | Ok((packed, append).to_py_object(py).into_object()) |
|
360 | let tree_metadata = PyBytes::new(py, &tree_metadata); | |
|
361 | let tuple = (packed, tree_metadata, append); | |||
|
362 | Ok(tuple.to_py_object(py).into_object()) | |||
359 | }, |
|
363 | }, | |
360 | Err(_) => Err(PyErr::new::<exc::OSError, _>( |
|
364 | Err(_) => Err(PyErr::new::<exc::OSError, _>( | |
361 | py, |
|
365 | py, |
@@ -128,7 +128,7 b' impl DirstateMapMethods for OwningDirsta' | |||||
128 | &mut self, |
|
128 | &mut self, | |
129 | now: Timestamp, |
|
129 | now: Timestamp, | |
130 | can_append: bool, |
|
130 | can_append: bool, | |
131 | ) -> Result<(Vec<u8>, bool), DirstateError> { |
|
131 | ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> { | |
132 | self.get_mut().pack_v2(now, can_append) |
|
132 | self.get_mut().pack_v2(now, can_append) | |
133 | } |
|
133 | } | |
134 |
|
134 |
@@ -49,9 +49,11 b' impl OwningDirstateMap {' | |||||
49 | py: Python, |
|
49 | py: Python, | |
50 | on_disk: PyBytes, |
|
50 | on_disk: PyBytes, | |
51 | data_size: usize, |
|
51 | data_size: usize, | |
|
52 | tree_metadata: PyBytes, | |||
52 | ) -> Result<Self, DirstateError> { |
|
53 | ) -> Result<Self, DirstateError> { | |
53 | let bytes: &'_ [u8] = on_disk.data(py); |
|
54 | let bytes: &'_ [u8] = on_disk.data(py); | |
54 | let map = DirstateMap::new_v2(bytes, data_size)?; |
|
55 | let map = | |
|
56 | DirstateMap::new_v2(bytes, data_size, tree_metadata.data(py))?; | |||
55 |
|
57 | |||
56 | // Like in `bytes` above, this `'_` lifetime parameter borrows from |
|
58 | // Like in `bytes` above, this `'_` lifetime parameter borrows from | |
57 | // the bytes buffer owned by `on_disk`. |
|
59 | // the bytes buffer owned by `on_disk`. |
@@ -168,13 +168,16 b' pub fn run(invocation: &crate::CliInvoca' | |||||
168 | let repo = invocation.repo?; |
|
168 | let repo = invocation.repo?; | |
169 | let dirstate_data_mmap; |
|
169 | let dirstate_data_mmap; | |
170 | let (mut dmap, parents) = if repo.has_dirstate_v2() { |
|
170 | let (mut dmap, parents) = if repo.has_dirstate_v2() { | |
|
171 | let docket_data = | |||
|
172 | repo.hg_vfs().read("dirstate").io_not_found_as_none()?; | |||
171 | let parents; |
|
173 | let parents; | |
172 | let dirstate_data; |
|
174 | let dirstate_data; | |
173 | let data_size; |
|
175 | let data_size; | |
174 |
|
|
176 | let docket; | |
175 | repo.hg_vfs().read("dirstate").io_not_found_as_none()? |
|
177 | let tree_metadata; | |
176 | { |
|
178 | if let Some(docket_data) = &docket_data { | |
177 |
|
|
179 | docket = on_disk::read_docket(docket_data)?; | |
|
180 | tree_metadata = docket.tree_metadata(); | |||
178 | parents = Some(docket.parents()); |
|
181 | parents = Some(docket.parents()); | |
179 | data_size = docket.data_size(); |
|
182 | data_size = docket.data_size(); | |
180 | dirstate_data_mmap = repo |
|
183 | dirstate_data_mmap = repo | |
@@ -184,10 +187,12 b' pub fn run(invocation: &crate::CliInvoca' | |||||
184 | dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b""); |
|
187 | dirstate_data = dirstate_data_mmap.as_deref().unwrap_or(b""); | |
185 | } else { |
|
188 | } else { | |
186 | parents = None; |
|
189 | parents = None; | |
|
190 | tree_metadata = b""; | |||
187 | data_size = 0; |
|
191 | data_size = 0; | |
188 | dirstate_data = b""; |
|
192 | dirstate_data = b""; | |
189 | } |
|
193 | } | |
190 | let dmap = DirstateMap::new_v2(dirstate_data, data_size)?; |
|
194 | let dmap = | |
|
195 | DirstateMap::new_v2(dirstate_data, data_size, tree_metadata)?; | |||
191 | (dmap, parents) |
|
196 | (dmap, parents) | |
192 | } else { |
|
197 | } else { | |
193 | dirstate_data_mmap = |
|
198 | dirstate_data_mmap = |
General Comments 0
You need to be logged in to leave comments.
Login now