##// END OF EJS Templates
dirstate-v2: Name a constant in the Rust implementation...
Simon Sapin -
r49010:47fabca8 default
parent child Browse files
Show More
@@ -1,725 +1,727 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
3 //! See `mercurial/helptext/internals/dirstate-v2.txt`
4
4
5 use crate::dirstate::TruncatedTimestamp;
5 use crate::dirstate::TruncatedTimestamp;
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
6 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
7 use crate::dirstate_tree::path_with_basename::WithBasename;
7 use crate::dirstate_tree::path_with_basename::WithBasename;
8 use crate::errors::HgError;
8 use crate::errors::HgError;
9 use crate::utils::hg_path::HgPath;
9 use crate::utils::hg_path::HgPath;
10 use crate::DirstateEntry;
10 use crate::DirstateEntry;
11 use crate::DirstateError;
11 use crate::DirstateError;
12 use crate::DirstateParents;
12 use crate::DirstateParents;
13 use bitflags::bitflags;
13 use bitflags::bitflags;
14 use bytes_cast::unaligned::{U16Be, U32Be};
14 use bytes_cast::unaligned::{U16Be, U32Be};
15 use bytes_cast::BytesCast;
15 use bytes_cast::BytesCast;
16 use format_bytes::format_bytes;
16 use format_bytes::format_bytes;
17 use std::borrow::Cow;
17 use std::borrow::Cow;
18 use std::convert::{TryFrom, TryInto};
18 use std::convert::{TryFrom, TryInto};
19
19
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
20 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
21 /// This a redundant sanity check more than an actual "magic number" since
21 /// This a redundant sanity check more than an actual "magic number" since
22 /// `.hg/requires` already governs which format should be used.
22 /// `.hg/requires` already governs which format should be used.
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
23 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
24
24
25 /// Keep space for 256-bit hashes
25 /// Keep space for 256-bit hashes
26 const STORED_NODE_ID_BYTES: usize = 32;
26 const STORED_NODE_ID_BYTES: usize = 32;
27
27
28 /// … even though only 160 bits are used for now, with SHA-1
28 /// … even though only 160 bits are used for now, with SHA-1
29 const USED_NODE_ID_BYTES: usize = 20;
29 const USED_NODE_ID_BYTES: usize = 20;
30
30
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
31 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
32 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
33
33
34 /// Must match the constant of the same name in
34 /// Must match the constant of the same name in
35 /// `mercurial/dirstateutils/docket.py`
35 /// `mercurial/dirstateutils/docket.py`
36 const TREE_METADATA_SIZE: usize = 44;
36 const TREE_METADATA_SIZE: usize = 44;
37
37
38 const NODE_SIZE: usize = 43;
39
38 /// Make sure that size-affecting changes are made knowingly
40 /// Make sure that size-affecting changes are made knowingly
39 #[allow(unused)]
41 #[allow(unused)]
40 fn static_assert_size_of() {
42 fn static_assert_size_of() {
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
43 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
44 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
43 let _ = std::mem::transmute::<Node, [u8; 43]>;
45 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
44 }
46 }
45
47
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
48 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
47 #[derive(BytesCast)]
49 #[derive(BytesCast)]
48 #[repr(C)]
50 #[repr(C)]
49 struct DocketHeader {
51 struct DocketHeader {
50 marker: [u8; V2_FORMAT_MARKER.len()],
52 marker: [u8; V2_FORMAT_MARKER.len()],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
53 parent_1: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
54 parent_2: [u8; STORED_NODE_ID_BYTES],
53
55
54 metadata: TreeMetadata,
56 metadata: TreeMetadata,
55
57
56 /// Counted in bytes
58 /// Counted in bytes
57 data_size: Size,
59 data_size: Size,
58
60
59 uuid_size: u8,
61 uuid_size: u8,
60 }
62 }
61
63
62 pub struct Docket<'on_disk> {
64 pub struct Docket<'on_disk> {
63 header: &'on_disk DocketHeader,
65 header: &'on_disk DocketHeader,
64 uuid: &'on_disk [u8],
66 uuid: &'on_disk [u8],
65 }
67 }
66
68
67 /// Fields are documented in the *Tree metadata in the docket file*
69 /// Fields are documented in the *Tree metadata in the docket file*
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
70 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
69 #[derive(BytesCast)]
71 #[derive(BytesCast)]
70 #[repr(C)]
72 #[repr(C)]
71 struct TreeMetadata {
73 struct TreeMetadata {
72 root_nodes: ChildNodes,
74 root_nodes: ChildNodes,
73 nodes_with_entry_count: Size,
75 nodes_with_entry_count: Size,
74 nodes_with_copy_source_count: Size,
76 nodes_with_copy_source_count: Size,
75 unreachable_bytes: Size,
77 unreachable_bytes: Size,
76 unused: [u8; 4],
78 unused: [u8; 4],
77
79
78 /// See *Optional hash of ignore patterns* section of
80 /// See *Optional hash of ignore patterns* section of
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
81 /// `mercurial/helptext/internals/dirstate-v2.txt`
80 ignore_patterns_hash: IgnorePatternsHash,
82 ignore_patterns_hash: IgnorePatternsHash,
81 }
83 }
82
84
83 /// Fields are documented in the *The data file format*
85 /// Fields are documented in the *The data file format*
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
86 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
85 #[derive(BytesCast)]
87 #[derive(BytesCast)]
86 #[repr(C)]
88 #[repr(C)]
87 pub(super) struct Node {
89 pub(super) struct Node {
88 full_path: PathSlice,
90 full_path: PathSlice,
89
91
90 /// In bytes from `self.full_path.start`
92 /// In bytes from `self.full_path.start`
91 base_name_start: PathSize,
93 base_name_start: PathSize,
92
94
93 copy_source: OptPathSlice,
95 copy_source: OptPathSlice,
94 children: ChildNodes,
96 children: ChildNodes,
95 pub(super) descendants_with_entry_count: Size,
97 pub(super) descendants_with_entry_count: Size,
96 pub(super) tracked_descendants_count: Size,
98 pub(super) tracked_descendants_count: Size,
97 flags: Flags,
99 flags: Flags,
98 data: Entry,
100 data: Entry,
99 }
101 }
100
102
101 bitflags! {
103 bitflags! {
102 #[derive(BytesCast)]
104 #[derive(BytesCast)]
103 #[repr(C)]
105 #[repr(C)]
104 struct Flags: u8 {
106 struct Flags: u8 {
105 const WDIR_TRACKED = 1 << 0;
107 const WDIR_TRACKED = 1 << 0;
106 const P1_TRACKED = 1 << 1;
108 const P1_TRACKED = 1 << 1;
107 const P2_INFO = 1 << 2;
109 const P2_INFO = 1 << 2;
108 const HAS_MODE_AND_SIZE = 1 << 3;
110 const HAS_MODE_AND_SIZE = 1 << 3;
109 const HAS_MTIME = 1 << 4;
111 const HAS_MTIME = 1 << 4;
110 const MODE_EXEC_PERM = 1 << 5;
112 const MODE_EXEC_PERM = 1 << 5;
111 const MODE_IS_SYMLINK = 1 << 7;
113 const MODE_IS_SYMLINK = 1 << 7;
112 }
114 }
113 }
115 }
114
116
115 #[derive(BytesCast, Copy, Clone, Debug)]
117 #[derive(BytesCast, Copy, Clone, Debug)]
116 #[repr(C)]
118 #[repr(C)]
117 struct Entry {
119 struct Entry {
118 _padding: U32Be,
120 _padding: U32Be,
119 size: U32Be,
121 size: U32Be,
120 mtime: U32Be,
122 mtime: U32Be,
121 }
123 }
122
124
123 /// Duration since the Unix epoch
125 /// Duration since the Unix epoch
124 #[derive(BytesCast, Copy, Clone)]
126 #[derive(BytesCast, Copy, Clone)]
125 #[repr(C)]
127 #[repr(C)]
126 struct PackedTimestamp {
128 struct PackedTimestamp {
127 _padding: U32Be,
129 _padding: U32Be,
128 truncated_seconds: U32Be,
130 truncated_seconds: U32Be,
129 nanoseconds: U32Be,
131 nanoseconds: U32Be,
130 }
132 }
131
133
132 /// Counted in bytes from the start of the file
134 /// Counted in bytes from the start of the file
133 ///
135 ///
134 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
136 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
135 type Offset = U32Be;
137 type Offset = U32Be;
136
138
137 /// Counted in number of items
139 /// Counted in number of items
138 ///
140 ///
139 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
141 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
140 type Size = U32Be;
142 type Size = U32Be;
141
143
142 /// Counted in bytes
144 /// Counted in bytes
143 ///
145 ///
144 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
146 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
145 type PathSize = U16Be;
147 type PathSize = U16Be;
146
148
147 /// A contiguous sequence of `len` times `Node`, representing the child nodes
149 /// A contiguous sequence of `len` times `Node`, representing the child nodes
148 /// of either some other node or of the repository root.
150 /// of either some other node or of the repository root.
149 ///
151 ///
150 /// Always sorted by ascending `full_path`, to allow binary search.
152 /// Always sorted by ascending `full_path`, to allow binary search.
151 /// Since nodes with the same parent nodes also have the same parent path,
153 /// Since nodes with the same parent nodes also have the same parent path,
152 /// only the `base_name`s need to be compared during binary search.
154 /// only the `base_name`s need to be compared during binary search.
153 #[derive(BytesCast, Copy, Clone)]
155 #[derive(BytesCast, Copy, Clone)]
154 #[repr(C)]
156 #[repr(C)]
155 struct ChildNodes {
157 struct ChildNodes {
156 start: Offset,
158 start: Offset,
157 len: Size,
159 len: Size,
158 }
160 }
159
161
160 /// A `HgPath` of `len` bytes
162 /// A `HgPath` of `len` bytes
161 #[derive(BytesCast, Copy, Clone)]
163 #[derive(BytesCast, Copy, Clone)]
162 #[repr(C)]
164 #[repr(C)]
163 struct PathSlice {
165 struct PathSlice {
164 start: Offset,
166 start: Offset,
165 len: PathSize,
167 len: PathSize,
166 }
168 }
167
169
168 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
170 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
169 type OptPathSlice = PathSlice;
171 type OptPathSlice = PathSlice;
170
172
171 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
173 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
172 ///
174 ///
173 /// This should only happen if Mercurial is buggy or a repository is corrupted.
175 /// This should only happen if Mercurial is buggy or a repository is corrupted.
174 #[derive(Debug)]
176 #[derive(Debug)]
175 pub struct DirstateV2ParseError;
177 pub struct DirstateV2ParseError;
176
178
177 impl From<DirstateV2ParseError> for HgError {
179 impl From<DirstateV2ParseError> for HgError {
178 fn from(_: DirstateV2ParseError) -> Self {
180 fn from(_: DirstateV2ParseError) -> Self {
179 HgError::corrupted("dirstate-v2 parse error")
181 HgError::corrupted("dirstate-v2 parse error")
180 }
182 }
181 }
183 }
182
184
183 impl From<DirstateV2ParseError> for crate::DirstateError {
185 impl From<DirstateV2ParseError> for crate::DirstateError {
184 fn from(error: DirstateV2ParseError) -> Self {
186 fn from(error: DirstateV2ParseError) -> Self {
185 HgError::from(error).into()
187 HgError::from(error).into()
186 }
188 }
187 }
189 }
188
190
189 impl<'on_disk> Docket<'on_disk> {
191 impl<'on_disk> Docket<'on_disk> {
190 pub fn parents(&self) -> DirstateParents {
192 pub fn parents(&self) -> DirstateParents {
191 use crate::Node;
193 use crate::Node;
192 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
194 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
193 .unwrap()
195 .unwrap()
194 .clone();
196 .clone();
195 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
197 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
196 .unwrap()
198 .unwrap()
197 .clone();
199 .clone();
198 DirstateParents { p1, p2 }
200 DirstateParents { p1, p2 }
199 }
201 }
200
202
201 pub fn tree_metadata(&self) -> &[u8] {
203 pub fn tree_metadata(&self) -> &[u8] {
202 self.header.metadata.as_bytes()
204 self.header.metadata.as_bytes()
203 }
205 }
204
206
205 pub fn data_size(&self) -> usize {
207 pub fn data_size(&self) -> usize {
206 // This `unwrap` could only panic on a 16-bit CPU
208 // This `unwrap` could only panic on a 16-bit CPU
207 self.header.data_size.get().try_into().unwrap()
209 self.header.data_size.get().try_into().unwrap()
208 }
210 }
209
211
210 pub fn data_filename(&self) -> String {
212 pub fn data_filename(&self) -> String {
211 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
213 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
212 }
214 }
213 }
215 }
214
216
215 pub fn read_docket(
217 pub fn read_docket(
216 on_disk: &[u8],
218 on_disk: &[u8],
217 ) -> Result<Docket<'_>, DirstateV2ParseError> {
219 ) -> Result<Docket<'_>, DirstateV2ParseError> {
218 let (header, uuid) =
220 let (header, uuid) =
219 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
221 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
220 let uuid_size = header.uuid_size as usize;
222 let uuid_size = header.uuid_size as usize;
221 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
223 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
222 Ok(Docket { header, uuid })
224 Ok(Docket { header, uuid })
223 } else {
225 } else {
224 Err(DirstateV2ParseError)
226 Err(DirstateV2ParseError)
225 }
227 }
226 }
228 }
227
229
228 pub(super) fn read<'on_disk>(
230 pub(super) fn read<'on_disk>(
229 on_disk: &'on_disk [u8],
231 on_disk: &'on_disk [u8],
230 metadata: &[u8],
232 metadata: &[u8],
231 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
233 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
232 if on_disk.is_empty() {
234 if on_disk.is_empty() {
233 return Ok(DirstateMap::empty(on_disk));
235 return Ok(DirstateMap::empty(on_disk));
234 }
236 }
235 let (meta, _) = TreeMetadata::from_bytes(metadata)
237 let (meta, _) = TreeMetadata::from_bytes(metadata)
236 .map_err(|_| DirstateV2ParseError)?;
238 .map_err(|_| DirstateV2ParseError)?;
237 let dirstate_map = DirstateMap {
239 let dirstate_map = DirstateMap {
238 on_disk,
240 on_disk,
239 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
241 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
240 on_disk,
242 on_disk,
241 meta.root_nodes,
243 meta.root_nodes,
242 )?),
244 )?),
243 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
245 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
244 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
246 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
245 ignore_patterns_hash: meta.ignore_patterns_hash,
247 ignore_patterns_hash: meta.ignore_patterns_hash,
246 unreachable_bytes: meta.unreachable_bytes.get(),
248 unreachable_bytes: meta.unreachable_bytes.get(),
247 };
249 };
248 Ok(dirstate_map)
250 Ok(dirstate_map)
249 }
251 }
250
252
251 impl Node {
253 impl Node {
252 pub(super) fn full_path<'on_disk>(
254 pub(super) fn full_path<'on_disk>(
253 &self,
255 &self,
254 on_disk: &'on_disk [u8],
256 on_disk: &'on_disk [u8],
255 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
257 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
256 read_hg_path(on_disk, self.full_path)
258 read_hg_path(on_disk, self.full_path)
257 }
259 }
258
260
259 pub(super) fn base_name_start<'on_disk>(
261 pub(super) fn base_name_start<'on_disk>(
260 &self,
262 &self,
261 ) -> Result<usize, DirstateV2ParseError> {
263 ) -> Result<usize, DirstateV2ParseError> {
262 let start = self.base_name_start.get();
264 let start = self.base_name_start.get();
263 if start < self.full_path.len.get() {
265 if start < self.full_path.len.get() {
264 let start = usize::try_from(start)
266 let start = usize::try_from(start)
265 // u32 -> usize, could only panic on a 16-bit CPU
267 // u32 -> usize, could only panic on a 16-bit CPU
266 .expect("dirstate-v2 base_name_start out of bounds");
268 .expect("dirstate-v2 base_name_start out of bounds");
267 Ok(start)
269 Ok(start)
268 } else {
270 } else {
269 Err(DirstateV2ParseError)
271 Err(DirstateV2ParseError)
270 }
272 }
271 }
273 }
272
274
273 pub(super) fn base_name<'on_disk>(
275 pub(super) fn base_name<'on_disk>(
274 &self,
276 &self,
275 on_disk: &'on_disk [u8],
277 on_disk: &'on_disk [u8],
276 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
278 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
277 let full_path = self.full_path(on_disk)?;
279 let full_path = self.full_path(on_disk)?;
278 let base_name_start = self.base_name_start()?;
280 let base_name_start = self.base_name_start()?;
279 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
281 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
280 }
282 }
281
283
282 pub(super) fn path<'on_disk>(
284 pub(super) fn path<'on_disk>(
283 &self,
285 &self,
284 on_disk: &'on_disk [u8],
286 on_disk: &'on_disk [u8],
285 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
287 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
286 Ok(WithBasename::from_raw_parts(
288 Ok(WithBasename::from_raw_parts(
287 Cow::Borrowed(self.full_path(on_disk)?),
289 Cow::Borrowed(self.full_path(on_disk)?),
288 self.base_name_start()?,
290 self.base_name_start()?,
289 ))
291 ))
290 }
292 }
291
293
292 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
294 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
293 self.copy_source.start.get() != 0
295 self.copy_source.start.get() != 0
294 }
296 }
295
297
296 pub(super) fn copy_source<'on_disk>(
298 pub(super) fn copy_source<'on_disk>(
297 &self,
299 &self,
298 on_disk: &'on_disk [u8],
300 on_disk: &'on_disk [u8],
299 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
301 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
300 Ok(if self.has_copy_source() {
302 Ok(if self.has_copy_source() {
301 Some(read_hg_path(on_disk, self.copy_source)?)
303 Some(read_hg_path(on_disk, self.copy_source)?)
302 } else {
304 } else {
303 None
305 None
304 })
306 })
305 }
307 }
306
308
307 fn has_entry(&self) -> bool {
309 fn has_entry(&self) -> bool {
308 self.flags.intersects(
310 self.flags.intersects(
309 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
311 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
310 )
312 )
311 }
313 }
312
314
313 pub(super) fn node_data(
315 pub(super) fn node_data(
314 &self,
316 &self,
315 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
317 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
316 if self.has_entry() {
318 if self.has_entry() {
317 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
319 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
318 } else if let Some(mtime) = self.cached_directory_mtime()? {
320 } else if let Some(mtime) = self.cached_directory_mtime()? {
319 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
321 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
320 } else {
322 } else {
321 Ok(dirstate_map::NodeData::None)
323 Ok(dirstate_map::NodeData::None)
322 }
324 }
323 }
325 }
324
326
325 pub(super) fn cached_directory_mtime(
327 pub(super) fn cached_directory_mtime(
326 &self,
328 &self,
327 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
329 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
328 Ok(
330 Ok(
329 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
331 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
330 Some(self.data.as_timestamp()?)
332 Some(self.data.as_timestamp()?)
331 } else {
333 } else {
332 None
334 None
333 },
335 },
334 )
336 )
335 }
337 }
336
338
337 fn synthesize_unix_mode(&self) -> u32 {
339 fn synthesize_unix_mode(&self) -> u32 {
338 let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) {
340 let file_type = if self.flags.contains(Flags::MODE_IS_SYMLINK) {
339 libc::S_IFLNK
341 libc::S_IFLNK
340 } else {
342 } else {
341 libc::S_IFREG
343 libc::S_IFREG
342 };
344 };
343 let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) {
345 let permisions = if self.flags.contains(Flags::MODE_EXEC_PERM) {
344 0o755
346 0o755
345 } else {
347 } else {
346 0o644
348 0o644
347 };
349 };
348 file_type | permisions
350 file_type | permisions
349 }
351 }
350
352
351 fn assume_entry(&self) -> DirstateEntry {
353 fn assume_entry(&self) -> DirstateEntry {
352 // TODO: convert through raw bits instead?
354 // TODO: convert through raw bits instead?
353 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
355 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
354 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
356 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
355 let p2_info = self.flags.contains(Flags::P2_INFO);
357 let p2_info = self.flags.contains(Flags::P2_INFO);
356 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
358 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
357 Some((self.synthesize_unix_mode(), self.data.size.into()))
359 Some((self.synthesize_unix_mode(), self.data.size.into()))
358 } else {
360 } else {
359 None
361 None
360 };
362 };
361 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
363 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
362 Some(self.data.mtime.into())
364 Some(self.data.mtime.into())
363 } else {
365 } else {
364 None
366 None
365 };
367 };
366 DirstateEntry::from_v2_data(
368 DirstateEntry::from_v2_data(
367 wdir_tracked,
369 wdir_tracked,
368 p1_tracked,
370 p1_tracked,
369 p2_info,
371 p2_info,
370 mode_size,
372 mode_size,
371 mtime,
373 mtime,
372 )
374 )
373 }
375 }
374
376
375 pub(super) fn entry(
377 pub(super) fn entry(
376 &self,
378 &self,
377 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
379 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
378 if self.has_entry() {
380 if self.has_entry() {
379 Ok(Some(self.assume_entry()))
381 Ok(Some(self.assume_entry()))
380 } else {
382 } else {
381 Ok(None)
383 Ok(None)
382 }
384 }
383 }
385 }
384
386
385 pub(super) fn children<'on_disk>(
387 pub(super) fn children<'on_disk>(
386 &self,
388 &self,
387 on_disk: &'on_disk [u8],
389 on_disk: &'on_disk [u8],
388 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
390 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
389 read_nodes(on_disk, self.children)
391 read_nodes(on_disk, self.children)
390 }
392 }
391
393
392 pub(super) fn to_in_memory_node<'on_disk>(
394 pub(super) fn to_in_memory_node<'on_disk>(
393 &self,
395 &self,
394 on_disk: &'on_disk [u8],
396 on_disk: &'on_disk [u8],
395 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
397 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
396 Ok(dirstate_map::Node {
398 Ok(dirstate_map::Node {
397 children: dirstate_map::ChildNodes::OnDisk(
399 children: dirstate_map::ChildNodes::OnDisk(
398 self.children(on_disk)?,
400 self.children(on_disk)?,
399 ),
401 ),
400 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
402 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
401 data: self.node_data()?,
403 data: self.node_data()?,
402 descendants_with_entry_count: self
404 descendants_with_entry_count: self
403 .descendants_with_entry_count
405 .descendants_with_entry_count
404 .get(),
406 .get(),
405 tracked_descendants_count: self.tracked_descendants_count.get(),
407 tracked_descendants_count: self.tracked_descendants_count.get(),
406 })
408 })
407 }
409 }
408 }
410 }
409
411
410 impl Entry {
412 impl Entry {
411 fn from_dirstate_entry(entry: &DirstateEntry) -> (Flags, Self) {
413 fn from_dirstate_entry(entry: &DirstateEntry) -> (Flags, Self) {
412 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
414 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
413 entry.v2_data();
415 entry.v2_data();
414 // TODO: convert throug raw flag bits instead?
416 // TODO: convert throug raw flag bits instead?
415 let mut flags = Flags::empty();
417 let mut flags = Flags::empty();
416 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
418 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
417 flags.set(Flags::P1_TRACKED, p1_tracked);
419 flags.set(Flags::P1_TRACKED, p1_tracked);
418 flags.set(Flags::P2_INFO, p2_info);
420 flags.set(Flags::P2_INFO, p2_info);
419 let (size, mtime);
421 let (size, mtime);
420 if let Some((m, s)) = mode_size_opt {
422 if let Some((m, s)) = mode_size_opt {
421 let exec_perm = m & libc::S_IXUSR != 0;
423 let exec_perm = m & libc::S_IXUSR != 0;
422 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
424 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
423 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
425 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
424 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
426 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
425 size = s;
427 size = s;
426 flags.insert(Flags::HAS_MODE_AND_SIZE)
428 flags.insert(Flags::HAS_MODE_AND_SIZE)
427 } else {
429 } else {
428 size = 0;
430 size = 0;
429 }
431 }
430 if let Some(m) = mtime_opt {
432 if let Some(m) = mtime_opt {
431 mtime = m;
433 mtime = m;
432 flags.insert(Flags::HAS_MTIME);
434 flags.insert(Flags::HAS_MTIME);
433 } else {
435 } else {
434 mtime = 0;
436 mtime = 0;
435 }
437 }
436 let raw_entry = Entry {
438 let raw_entry = Entry {
437 _padding: 0.into(),
439 _padding: 0.into(),
438 size: size.into(),
440 size: size.into(),
439 mtime: mtime.into(),
441 mtime: mtime.into(),
440 };
442 };
441 (flags, raw_entry)
443 (flags, raw_entry)
442 }
444 }
443
445
444 fn from_timestamp(timestamp: TruncatedTimestamp) -> Self {
446 fn from_timestamp(timestamp: TruncatedTimestamp) -> Self {
445 let packed = PackedTimestamp {
447 let packed = PackedTimestamp {
446 _padding: 0.into(),
448 _padding: 0.into(),
447 truncated_seconds: timestamp.truncated_seconds().into(),
449 truncated_seconds: timestamp.truncated_seconds().into(),
448 nanoseconds: timestamp.nanoseconds().into(),
450 nanoseconds: timestamp.nanoseconds().into(),
449 };
451 };
450 // Safety: both types implement the `ByteCast` trait, so we could
452 // Safety: both types implement the `ByteCast` trait, so we could
451 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
453 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
452 // `transmute` instead makes the compiler check that the two types
454 // `transmute` instead makes the compiler check that the two types
453 // have the same size, which eliminates the error case of
455 // have the same size, which eliminates the error case of
454 // `from_bytes`.
456 // `from_bytes`.
455 unsafe { std::mem::transmute::<PackedTimestamp, Entry>(packed) }
457 unsafe { std::mem::transmute::<PackedTimestamp, Entry>(packed) }
456 }
458 }
457
459
458 fn as_timestamp(self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
460 fn as_timestamp(self) -> Result<TruncatedTimestamp, DirstateV2ParseError> {
459 // Safety: same as above in `from_timestamp`
461 // Safety: same as above in `from_timestamp`
460 let packed =
462 let packed =
461 unsafe { std::mem::transmute::<Entry, PackedTimestamp>(self) };
463 unsafe { std::mem::transmute::<Entry, PackedTimestamp>(self) };
462 TruncatedTimestamp::from_already_truncated(
464 TruncatedTimestamp::from_already_truncated(
463 packed.truncated_seconds.get(),
465 packed.truncated_seconds.get(),
464 packed.nanoseconds.get(),
466 packed.nanoseconds.get(),
465 )
467 )
466 }
468 }
467 }
469 }
468
470
469 fn read_hg_path(
471 fn read_hg_path(
470 on_disk: &[u8],
472 on_disk: &[u8],
471 slice: PathSlice,
473 slice: PathSlice,
472 ) -> Result<&HgPath, DirstateV2ParseError> {
474 ) -> Result<&HgPath, DirstateV2ParseError> {
473 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
475 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
474 }
476 }
475
477
476 fn read_nodes(
478 fn read_nodes(
477 on_disk: &[u8],
479 on_disk: &[u8],
478 slice: ChildNodes,
480 slice: ChildNodes,
479 ) -> Result<&[Node], DirstateV2ParseError> {
481 ) -> Result<&[Node], DirstateV2ParseError> {
480 read_slice(on_disk, slice.start, slice.len.get())
482 read_slice(on_disk, slice.start, slice.len.get())
481 }
483 }
482
484
483 fn read_slice<T, Len>(
485 fn read_slice<T, Len>(
484 on_disk: &[u8],
486 on_disk: &[u8],
485 start: Offset,
487 start: Offset,
486 len: Len,
488 len: Len,
487 ) -> Result<&[T], DirstateV2ParseError>
489 ) -> Result<&[T], DirstateV2ParseError>
488 where
490 where
489 T: BytesCast,
491 T: BytesCast,
490 Len: TryInto<usize>,
492 Len: TryInto<usize>,
491 {
493 {
492 // Either `usize::MAX` would result in "out of bounds" error since a single
494 // Either `usize::MAX` would result in "out of bounds" error since a single
493 // `&[u8]` cannot occupy the entire addess space.
495 // `&[u8]` cannot occupy the entire addess space.
494 let start = start.get().try_into().unwrap_or(std::usize::MAX);
496 let start = start.get().try_into().unwrap_or(std::usize::MAX);
495 let len = len.try_into().unwrap_or(std::usize::MAX);
497 let len = len.try_into().unwrap_or(std::usize::MAX);
496 on_disk
498 on_disk
497 .get(start..)
499 .get(start..)
498 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
500 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
499 .map(|(slice, _rest)| slice)
501 .map(|(slice, _rest)| slice)
500 .ok_or_else(|| DirstateV2ParseError)
502 .ok_or_else(|| DirstateV2ParseError)
501 }
503 }
502
504
503 pub(crate) fn for_each_tracked_path<'on_disk>(
505 pub(crate) fn for_each_tracked_path<'on_disk>(
504 on_disk: &'on_disk [u8],
506 on_disk: &'on_disk [u8],
505 metadata: &[u8],
507 metadata: &[u8],
506 mut f: impl FnMut(&'on_disk HgPath),
508 mut f: impl FnMut(&'on_disk HgPath),
507 ) -> Result<(), DirstateV2ParseError> {
509 ) -> Result<(), DirstateV2ParseError> {
508 let (meta, _) = TreeMetadata::from_bytes(metadata)
510 let (meta, _) = TreeMetadata::from_bytes(metadata)
509 .map_err(|_| DirstateV2ParseError)?;
511 .map_err(|_| DirstateV2ParseError)?;
510 fn recur<'on_disk>(
512 fn recur<'on_disk>(
511 on_disk: &'on_disk [u8],
513 on_disk: &'on_disk [u8],
512 nodes: ChildNodes,
514 nodes: ChildNodes,
513 f: &mut impl FnMut(&'on_disk HgPath),
515 f: &mut impl FnMut(&'on_disk HgPath),
514 ) -> Result<(), DirstateV2ParseError> {
516 ) -> Result<(), DirstateV2ParseError> {
515 for node in read_nodes(on_disk, nodes)? {
517 for node in read_nodes(on_disk, nodes)? {
516 if let Some(entry) = node.entry()? {
518 if let Some(entry) = node.entry()? {
517 if entry.state().is_tracked() {
519 if entry.state().is_tracked() {
518 f(node.full_path(on_disk)?)
520 f(node.full_path(on_disk)?)
519 }
521 }
520 }
522 }
521 recur(on_disk, node.children, f)?
523 recur(on_disk, node.children, f)?
522 }
524 }
523 Ok(())
525 Ok(())
524 }
526 }
525 recur(on_disk, meta.root_nodes, &mut f)
527 recur(on_disk, meta.root_nodes, &mut f)
526 }
528 }
527
529
528 /// Returns new data and metadata, together with whether that data should be
530 /// Returns new data and metadata, together with whether that data should be
529 /// appended to the existing data file whose content is at
531 /// appended to the existing data file whose content is at
530 /// `dirstate_map.on_disk` (true), instead of written to a new data file
532 /// `dirstate_map.on_disk` (true), instead of written to a new data file
531 /// (false).
533 /// (false).
532 pub(super) fn write(
534 pub(super) fn write(
533 dirstate_map: &mut DirstateMap,
535 dirstate_map: &mut DirstateMap,
534 can_append: bool,
536 can_append: bool,
535 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
537 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
536 let append = can_append && dirstate_map.write_should_append();
538 let append = can_append && dirstate_map.write_should_append();
537
539
538 // This ignores the space for paths, and for nodes without an entry.
540 // This ignores the space for paths, and for nodes without an entry.
539 // TODO: better estimate? Skip the `Vec` and write to a file directly?
541 // TODO: better estimate? Skip the `Vec` and write to a file directly?
540 let size_guess = std::mem::size_of::<Node>()
542 let size_guess = std::mem::size_of::<Node>()
541 * dirstate_map.nodes_with_entry_count as usize;
543 * dirstate_map.nodes_with_entry_count as usize;
542
544
543 let mut writer = Writer {
545 let mut writer = Writer {
544 dirstate_map,
546 dirstate_map,
545 append,
547 append,
546 out: Vec::with_capacity(size_guess),
548 out: Vec::with_capacity(size_guess),
547 };
549 };
548
550
549 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
551 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
550
552
551 let meta = TreeMetadata {
553 let meta = TreeMetadata {
552 root_nodes,
554 root_nodes,
553 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
555 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
554 nodes_with_copy_source_count: dirstate_map
556 nodes_with_copy_source_count: dirstate_map
555 .nodes_with_copy_source_count
557 .nodes_with_copy_source_count
556 .into(),
558 .into(),
557 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
559 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
558 unused: [0; 4],
560 unused: [0; 4],
559 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
561 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
560 };
562 };
561 Ok((writer.out, meta.as_bytes().to_vec(), append))
563 Ok((writer.out, meta.as_bytes().to_vec(), append))
562 }
564 }
563
565
564 struct Writer<'dmap, 'on_disk> {
566 struct Writer<'dmap, 'on_disk> {
565 dirstate_map: &'dmap DirstateMap<'on_disk>,
567 dirstate_map: &'dmap DirstateMap<'on_disk>,
566 append: bool,
568 append: bool,
567 out: Vec<u8>,
569 out: Vec<u8>,
568 }
570 }
569
571
570 impl Writer<'_, '_> {
572 impl Writer<'_, '_> {
571 fn write_nodes(
573 fn write_nodes(
572 &mut self,
574 &mut self,
573 nodes: dirstate_map::ChildNodesRef,
575 nodes: dirstate_map::ChildNodesRef,
574 ) -> Result<ChildNodes, DirstateError> {
576 ) -> Result<ChildNodes, DirstateError> {
575 // Reuse already-written nodes if possible
577 // Reuse already-written nodes if possible
576 if self.append {
578 if self.append {
577 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
579 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
578 let start = self.on_disk_offset_of(nodes_slice).expect(
580 let start = self.on_disk_offset_of(nodes_slice).expect(
579 "dirstate-v2 OnDisk nodes not found within on_disk",
581 "dirstate-v2 OnDisk nodes not found within on_disk",
580 );
582 );
581 let len = child_nodes_len_from_usize(nodes_slice.len());
583 let len = child_nodes_len_from_usize(nodes_slice.len());
582 return Ok(ChildNodes { start, len });
584 return Ok(ChildNodes { start, len });
583 }
585 }
584 }
586 }
585
587
586 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
588 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
587 // undefined iteration order. Sort to enable binary search in the
589 // undefined iteration order. Sort to enable binary search in the
588 // written file.
590 // written file.
589 let nodes = nodes.sorted();
591 let nodes = nodes.sorted();
590 let nodes_len = nodes.len();
592 let nodes_len = nodes.len();
591
593
592 // First accumulate serialized nodes in a `Vec`
594 // First accumulate serialized nodes in a `Vec`
593 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
595 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
594 for node in nodes {
596 for node in nodes {
595 let children =
597 let children =
596 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
598 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
597 let full_path = node.full_path(self.dirstate_map.on_disk)?;
599 let full_path = node.full_path(self.dirstate_map.on_disk)?;
598 let full_path = self.write_path(full_path.as_bytes());
600 let full_path = self.write_path(full_path.as_bytes());
599 let copy_source = if let Some(source) =
601 let copy_source = if let Some(source) =
600 node.copy_source(self.dirstate_map.on_disk)?
602 node.copy_source(self.dirstate_map.on_disk)?
601 {
603 {
602 self.write_path(source.as_bytes())
604 self.write_path(source.as_bytes())
603 } else {
605 } else {
604 PathSlice {
606 PathSlice {
605 start: 0.into(),
607 start: 0.into(),
606 len: 0.into(),
608 len: 0.into(),
607 }
609 }
608 };
610 };
609 on_disk_nodes.push(match node {
611 on_disk_nodes.push(match node {
610 NodeRef::InMemory(path, node) => {
612 NodeRef::InMemory(path, node) => {
611 let (flags, data) = match &node.data {
613 let (flags, data) = match &node.data {
612 dirstate_map::NodeData::Entry(entry) => {
614 dirstate_map::NodeData::Entry(entry) => {
613 Entry::from_dirstate_entry(entry)
615 Entry::from_dirstate_entry(entry)
614 }
616 }
615 dirstate_map::NodeData::CachedDirectory { mtime } => {
617 dirstate_map::NodeData::CachedDirectory { mtime } => {
616 (Flags::HAS_MTIME, Entry::from_timestamp(*mtime))
618 (Flags::HAS_MTIME, Entry::from_timestamp(*mtime))
617 }
619 }
618 dirstate_map::NodeData::None => (
620 dirstate_map::NodeData::None => (
619 Flags::empty(),
621 Flags::empty(),
620 Entry {
622 Entry {
621 _padding: 0.into(),
623 _padding: 0.into(),
622 size: 0.into(),
624 size: 0.into(),
623 mtime: 0.into(),
625 mtime: 0.into(),
624 },
626 },
625 ),
627 ),
626 };
628 };
627 Node {
629 Node {
628 children,
630 children,
629 copy_source,
631 copy_source,
630 full_path,
632 full_path,
631 base_name_start: u16::try_from(path.base_name_start())
633 base_name_start: u16::try_from(path.base_name_start())
632 // Could only panic for paths over 64 KiB
634 // Could only panic for paths over 64 KiB
633 .expect("dirstate-v2 path length overflow")
635 .expect("dirstate-v2 path length overflow")
634 .into(),
636 .into(),
635 descendants_with_entry_count: node
637 descendants_with_entry_count: node
636 .descendants_with_entry_count
638 .descendants_with_entry_count
637 .into(),
639 .into(),
638 tracked_descendants_count: node
640 tracked_descendants_count: node
639 .tracked_descendants_count
641 .tracked_descendants_count
640 .into(),
642 .into(),
641 flags,
643 flags,
642 data,
644 data,
643 }
645 }
644 }
646 }
645 NodeRef::OnDisk(node) => Node {
647 NodeRef::OnDisk(node) => Node {
646 children,
648 children,
647 copy_source,
649 copy_source,
648 full_path,
650 full_path,
649 ..*node
651 ..*node
650 },
652 },
651 })
653 })
652 }
654 }
653 // … so we can write them contiguously, after writing everything else
655 // … so we can write them contiguously, after writing everything else
654 // they refer to.
656 // they refer to.
655 let start = self.current_offset();
657 let start = self.current_offset();
656 let len = child_nodes_len_from_usize(nodes_len);
658 let len = child_nodes_len_from_usize(nodes_len);
657 self.out.extend(on_disk_nodes.as_bytes());
659 self.out.extend(on_disk_nodes.as_bytes());
658 Ok(ChildNodes { start, len })
660 Ok(ChildNodes { start, len })
659 }
661 }
660
662
661 /// If the given slice of items is within `on_disk`, returns its offset
663 /// If the given slice of items is within `on_disk`, returns its offset
662 /// from the start of `on_disk`.
664 /// from the start of `on_disk`.
663 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
665 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
664 where
666 where
665 T: BytesCast,
667 T: BytesCast,
666 {
668 {
667 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
669 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
668 let start = slice.as_ptr() as usize;
670 let start = slice.as_ptr() as usize;
669 let end = start + slice.len();
671 let end = start + slice.len();
670 start..=end
672 start..=end
671 }
673 }
672 let slice_addresses = address_range(slice.as_bytes());
674 let slice_addresses = address_range(slice.as_bytes());
673 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
675 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
674 if on_disk_addresses.contains(slice_addresses.start())
676 if on_disk_addresses.contains(slice_addresses.start())
675 && on_disk_addresses.contains(slice_addresses.end())
677 && on_disk_addresses.contains(slice_addresses.end())
676 {
678 {
677 let offset = slice_addresses.start() - on_disk_addresses.start();
679 let offset = slice_addresses.start() - on_disk_addresses.start();
678 Some(offset_from_usize(offset))
680 Some(offset_from_usize(offset))
679 } else {
681 } else {
680 None
682 None
681 }
683 }
682 }
684 }
683
685
684 fn current_offset(&mut self) -> Offset {
686 fn current_offset(&mut self) -> Offset {
685 let mut offset = self.out.len();
687 let mut offset = self.out.len();
686 if self.append {
688 if self.append {
687 offset += self.dirstate_map.on_disk.len()
689 offset += self.dirstate_map.on_disk.len()
688 }
690 }
689 offset_from_usize(offset)
691 offset_from_usize(offset)
690 }
692 }
691
693
692 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
694 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
693 let len = path_len_from_usize(slice.len());
695 let len = path_len_from_usize(slice.len());
694 // Reuse an already-written path if possible
696 // Reuse an already-written path if possible
695 if self.append {
697 if self.append {
696 if let Some(start) = self.on_disk_offset_of(slice) {
698 if let Some(start) = self.on_disk_offset_of(slice) {
697 return PathSlice { start, len };
699 return PathSlice { start, len };
698 }
700 }
699 }
701 }
700 let start = self.current_offset();
702 let start = self.current_offset();
701 self.out.extend(slice.as_bytes());
703 self.out.extend(slice.as_bytes());
702 PathSlice { start, len }
704 PathSlice { start, len }
703 }
705 }
704 }
706 }
705
707
706 fn offset_from_usize(x: usize) -> Offset {
708 fn offset_from_usize(x: usize) -> Offset {
707 u32::try_from(x)
709 u32::try_from(x)
708 // Could only panic for a dirstate file larger than 4 GiB
710 // Could only panic for a dirstate file larger than 4 GiB
709 .expect("dirstate-v2 offset overflow")
711 .expect("dirstate-v2 offset overflow")
710 .into()
712 .into()
711 }
713 }
712
714
713 fn child_nodes_len_from_usize(x: usize) -> Size {
715 fn child_nodes_len_from_usize(x: usize) -> Size {
714 u32::try_from(x)
716 u32::try_from(x)
715 // Could only panic with over 4 billion nodes
717 // Could only panic with over 4 billion nodes
716 .expect("dirstate-v2 slice length overflow")
718 .expect("dirstate-v2 slice length overflow")
717 .into()
719 .into()
718 }
720 }
719
721
720 fn path_len_from_usize(x: usize) -> PathSize {
722 fn path_len_from_usize(x: usize) -> PathSize {
721 u16::try_from(x)
723 u16::try_from(x)
722 // Could only panic for paths over 64 KiB
724 // Could only panic for paths over 64 KiB
723 .expect("dirstate-v2 path length overflow")
725 .expect("dirstate-v2 path length overflow")
724 .into()
726 .into()
725 }
727 }
General Comments 0
You need to be logged in to leave comments. Login now