##// END OF EJS Templates
rhg: Add lazy/cached dirstate data file ID parsing on Repo...
Simon Sapin -
r49248:c7c23bb0 default
parent child Browse files
Show More
@@ -1,791 +1,791 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 constants of the same names in `mercurial/dirstateutils/v2.py`
34 /// Must match constants of the same names in `mercurial/dirstateutils/v2.py`
35 const TREE_METADATA_SIZE: usize = 44;
35 const TREE_METADATA_SIZE: usize = 44;
36 const NODE_SIZE: usize = 44;
36 const NODE_SIZE: usize = 44;
37
37
38 /// Make sure that size-affecting changes are made knowingly
38 /// Make sure that size-affecting changes are made knowingly
39 #[allow(unused)]
39 #[allow(unused)]
40 fn static_assert_size_of() {
40 fn static_assert_size_of() {
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
41 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
42 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
43 let _ = std::mem::transmute::<Node, [u8; NODE_SIZE]>;
44 }
44 }
45
45
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
46 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
47 #[derive(BytesCast)]
47 #[derive(BytesCast)]
48 #[repr(C)]
48 #[repr(C)]
49 struct DocketHeader {
49 struct DocketHeader {
50 marker: [u8; V2_FORMAT_MARKER.len()],
50 marker: [u8; V2_FORMAT_MARKER.len()],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
51 parent_1: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
52 parent_2: [u8; STORED_NODE_ID_BYTES],
53
53
54 metadata: TreeMetadata,
54 metadata: TreeMetadata,
55
55
56 /// Counted in bytes
56 /// Counted in bytes
57 data_size: Size,
57 data_size: Size,
58
58
59 uuid_size: u8,
59 uuid_size: u8,
60 }
60 }
61
61
62 pub struct Docket<'on_disk> {
62 pub struct Docket<'on_disk> {
63 header: &'on_disk DocketHeader,
63 header: &'on_disk DocketHeader,
64 uuid: &'on_disk [u8],
64 pub uuid: &'on_disk [u8],
65 }
65 }
66
66
67 /// Fields are documented in the *Tree metadata in the docket file*
67 /// Fields are documented in the *Tree metadata in the docket file*
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
68 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
69 #[derive(BytesCast)]
69 #[derive(BytesCast)]
70 #[repr(C)]
70 #[repr(C)]
71 struct TreeMetadata {
71 struct TreeMetadata {
72 root_nodes: ChildNodes,
72 root_nodes: ChildNodes,
73 nodes_with_entry_count: Size,
73 nodes_with_entry_count: Size,
74 nodes_with_copy_source_count: Size,
74 nodes_with_copy_source_count: Size,
75 unreachable_bytes: Size,
75 unreachable_bytes: Size,
76 unused: [u8; 4],
76 unused: [u8; 4],
77
77
78 /// See *Optional hash of ignore patterns* section of
78 /// See *Optional hash of ignore patterns* section of
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
79 /// `mercurial/helptext/internals/dirstate-v2.txt`
80 ignore_patterns_hash: IgnorePatternsHash,
80 ignore_patterns_hash: IgnorePatternsHash,
81 }
81 }
82
82
83 /// Fields are documented in the *The data file format*
83 /// Fields are documented in the *The data file format*
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
84 /// section of `mercurial/helptext/internals/dirstate-v2.txt`
85 #[derive(BytesCast)]
85 #[derive(BytesCast)]
86 #[repr(C)]
86 #[repr(C)]
87 pub(super) struct Node {
87 pub(super) struct Node {
88 full_path: PathSlice,
88 full_path: PathSlice,
89
89
90 /// In bytes from `self.full_path.start`
90 /// In bytes from `self.full_path.start`
91 base_name_start: PathSize,
91 base_name_start: PathSize,
92
92
93 copy_source: OptPathSlice,
93 copy_source: OptPathSlice,
94 children: ChildNodes,
94 children: ChildNodes,
95 pub(super) descendants_with_entry_count: Size,
95 pub(super) descendants_with_entry_count: Size,
96 pub(super) tracked_descendants_count: Size,
96 pub(super) tracked_descendants_count: Size,
97 flags: U16Be,
97 flags: U16Be,
98 size: U32Be,
98 size: U32Be,
99 mtime: PackedTruncatedTimestamp,
99 mtime: PackedTruncatedTimestamp,
100 }
100 }
101
101
102 bitflags! {
102 bitflags! {
103 #[repr(C)]
103 #[repr(C)]
104 struct Flags: u16 {
104 struct Flags: u16 {
105 const WDIR_TRACKED = 1 << 0;
105 const WDIR_TRACKED = 1 << 0;
106 const P1_TRACKED = 1 << 1;
106 const P1_TRACKED = 1 << 1;
107 const P2_INFO = 1 << 2;
107 const P2_INFO = 1 << 2;
108 const MODE_EXEC_PERM = 1 << 3;
108 const MODE_EXEC_PERM = 1 << 3;
109 const MODE_IS_SYMLINK = 1 << 4;
109 const MODE_IS_SYMLINK = 1 << 4;
110 const HAS_FALLBACK_EXEC = 1 << 5;
110 const HAS_FALLBACK_EXEC = 1 << 5;
111 const FALLBACK_EXEC = 1 << 6;
111 const FALLBACK_EXEC = 1 << 6;
112 const HAS_FALLBACK_SYMLINK = 1 << 7;
112 const HAS_FALLBACK_SYMLINK = 1 << 7;
113 const FALLBACK_SYMLINK = 1 << 8;
113 const FALLBACK_SYMLINK = 1 << 8;
114 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
114 const EXPECTED_STATE_IS_MODIFIED = 1 << 9;
115 const HAS_MODE_AND_SIZE = 1 <<10;
115 const HAS_MODE_AND_SIZE = 1 <<10;
116 const HAS_MTIME = 1 <<11;
116 const HAS_MTIME = 1 <<11;
117 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
117 const MTIME_SECOND_AMBIGUOUS = 1 << 12;
118 const DIRECTORY = 1 <<13;
118 const DIRECTORY = 1 <<13;
119 const ALL_UNKNOWN_RECORDED = 1 <<14;
119 const ALL_UNKNOWN_RECORDED = 1 <<14;
120 const ALL_IGNORED_RECORDED = 1 <<15;
120 const ALL_IGNORED_RECORDED = 1 <<15;
121 }
121 }
122 }
122 }
123
123
124 /// Duration since the Unix epoch
124 /// Duration since the Unix epoch
125 #[derive(BytesCast, Copy, Clone)]
125 #[derive(BytesCast, Copy, Clone)]
126 #[repr(C)]
126 #[repr(C)]
127 struct PackedTruncatedTimestamp {
127 struct PackedTruncatedTimestamp {
128 truncated_seconds: U32Be,
128 truncated_seconds: U32Be,
129 nanoseconds: U32Be,
129 nanoseconds: U32Be,
130 }
130 }
131
131
132 /// Counted in bytes from the start of the file
132 /// Counted in bytes from the start of the file
133 ///
133 ///
134 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
134 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
135 type Offset = U32Be;
135 type Offset = U32Be;
136
136
137 /// Counted in number of items
137 /// Counted in number of items
138 ///
138 ///
139 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
139 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
140 type Size = U32Be;
140 type Size = U32Be;
141
141
142 /// Counted in bytes
142 /// Counted in bytes
143 ///
143 ///
144 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
144 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
145 type PathSize = U16Be;
145 type PathSize = U16Be;
146
146
147 /// A contiguous sequence of `len` times `Node`, representing the child nodes
147 /// A contiguous sequence of `len` times `Node`, representing the child nodes
148 /// of either some other node or of the repository root.
148 /// of either some other node or of the repository root.
149 ///
149 ///
150 /// Always sorted by ascending `full_path`, to allow binary search.
150 /// Always sorted by ascending `full_path`, to allow binary search.
151 /// Since nodes with the same parent nodes also have the same parent path,
151 /// 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.
152 /// only the `base_name`s need to be compared during binary search.
153 #[derive(BytesCast, Copy, Clone)]
153 #[derive(BytesCast, Copy, Clone)]
154 #[repr(C)]
154 #[repr(C)]
155 struct ChildNodes {
155 struct ChildNodes {
156 start: Offset,
156 start: Offset,
157 len: Size,
157 len: Size,
158 }
158 }
159
159
160 /// A `HgPath` of `len` bytes
160 /// A `HgPath` of `len` bytes
161 #[derive(BytesCast, Copy, Clone)]
161 #[derive(BytesCast, Copy, Clone)]
162 #[repr(C)]
162 #[repr(C)]
163 struct PathSlice {
163 struct PathSlice {
164 start: Offset,
164 start: Offset,
165 len: PathSize,
165 len: PathSize,
166 }
166 }
167
167
168 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
168 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
169 type OptPathSlice = PathSlice;
169 type OptPathSlice = PathSlice;
170
170
171 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
171 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
172 ///
172 ///
173 /// This should only happen if Mercurial is buggy or a repository is corrupted.
173 /// This should only happen if Mercurial is buggy or a repository is corrupted.
174 #[derive(Debug)]
174 #[derive(Debug)]
175 pub struct DirstateV2ParseError;
175 pub struct DirstateV2ParseError;
176
176
177 impl From<DirstateV2ParseError> for HgError {
177 impl From<DirstateV2ParseError> for HgError {
178 fn from(_: DirstateV2ParseError) -> Self {
178 fn from(_: DirstateV2ParseError) -> Self {
179 HgError::corrupted("dirstate-v2 parse error")
179 HgError::corrupted("dirstate-v2 parse error")
180 }
180 }
181 }
181 }
182
182
183 impl From<DirstateV2ParseError> for crate::DirstateError {
183 impl From<DirstateV2ParseError> for crate::DirstateError {
184 fn from(error: DirstateV2ParseError) -> Self {
184 fn from(error: DirstateV2ParseError) -> Self {
185 HgError::from(error).into()
185 HgError::from(error).into()
186 }
186 }
187 }
187 }
188
188
189 impl<'on_disk> Docket<'on_disk> {
189 impl<'on_disk> Docket<'on_disk> {
190 pub fn parents(&self) -> DirstateParents {
190 pub fn parents(&self) -> DirstateParents {
191 use crate::Node;
191 use crate::Node;
192 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
192 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
193 .unwrap()
193 .unwrap()
194 .clone();
194 .clone();
195 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
195 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
196 .unwrap()
196 .unwrap()
197 .clone();
197 .clone();
198 DirstateParents { p1, p2 }
198 DirstateParents { p1, p2 }
199 }
199 }
200
200
201 pub fn tree_metadata(&self) -> &[u8] {
201 pub fn tree_metadata(&self) -> &[u8] {
202 self.header.metadata.as_bytes()
202 self.header.metadata.as_bytes()
203 }
203 }
204
204
205 pub fn data_size(&self) -> usize {
205 pub fn data_size(&self) -> usize {
206 // This `unwrap` could only panic on a 16-bit CPU
206 // This `unwrap` could only panic on a 16-bit CPU
207 self.header.data_size.get().try_into().unwrap()
207 self.header.data_size.get().try_into().unwrap()
208 }
208 }
209
209
210 pub fn data_filename(&self) -> String {
210 pub fn data_filename(&self) -> String {
211 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
211 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
212 }
212 }
213 }
213 }
214
214
215 pub fn read_docket(
215 pub fn read_docket(
216 on_disk: &[u8],
216 on_disk: &[u8],
217 ) -> Result<Docket<'_>, DirstateV2ParseError> {
217 ) -> Result<Docket<'_>, DirstateV2ParseError> {
218 let (header, uuid) =
218 let (header, uuid) =
219 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
219 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
220 let uuid_size = header.uuid_size as usize;
220 let uuid_size = header.uuid_size as usize;
221 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
221 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
222 Ok(Docket { header, uuid })
222 Ok(Docket { header, uuid })
223 } else {
223 } else {
224 Err(DirstateV2ParseError)
224 Err(DirstateV2ParseError)
225 }
225 }
226 }
226 }
227
227
228 pub(super) fn read<'on_disk>(
228 pub(super) fn read<'on_disk>(
229 on_disk: &'on_disk [u8],
229 on_disk: &'on_disk [u8],
230 metadata: &[u8],
230 metadata: &[u8],
231 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
231 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
232 if on_disk.is_empty() {
232 if on_disk.is_empty() {
233 return Ok(DirstateMap::empty(on_disk));
233 return Ok(DirstateMap::empty(on_disk));
234 }
234 }
235 let (meta, _) = TreeMetadata::from_bytes(metadata)
235 let (meta, _) = TreeMetadata::from_bytes(metadata)
236 .map_err(|_| DirstateV2ParseError)?;
236 .map_err(|_| DirstateV2ParseError)?;
237 let dirstate_map = DirstateMap {
237 let dirstate_map = DirstateMap {
238 on_disk,
238 on_disk,
239 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
239 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
240 on_disk,
240 on_disk,
241 meta.root_nodes,
241 meta.root_nodes,
242 )?),
242 )?),
243 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
243 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
244 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
244 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
245 ignore_patterns_hash: meta.ignore_patterns_hash,
245 ignore_patterns_hash: meta.ignore_patterns_hash,
246 unreachable_bytes: meta.unreachable_bytes.get(),
246 unreachable_bytes: meta.unreachable_bytes.get(),
247 };
247 };
248 Ok(dirstate_map)
248 Ok(dirstate_map)
249 }
249 }
250
250
251 impl Node {
251 impl Node {
252 pub(super) fn full_path<'on_disk>(
252 pub(super) fn full_path<'on_disk>(
253 &self,
253 &self,
254 on_disk: &'on_disk [u8],
254 on_disk: &'on_disk [u8],
255 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
255 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
256 read_hg_path(on_disk, self.full_path)
256 read_hg_path(on_disk, self.full_path)
257 }
257 }
258
258
259 pub(super) fn base_name_start<'on_disk>(
259 pub(super) fn base_name_start<'on_disk>(
260 &self,
260 &self,
261 ) -> Result<usize, DirstateV2ParseError> {
261 ) -> Result<usize, DirstateV2ParseError> {
262 let start = self.base_name_start.get();
262 let start = self.base_name_start.get();
263 if start < self.full_path.len.get() {
263 if start < self.full_path.len.get() {
264 let start = usize::try_from(start)
264 let start = usize::try_from(start)
265 // u32 -> usize, could only panic on a 16-bit CPU
265 // u32 -> usize, could only panic on a 16-bit CPU
266 .expect("dirstate-v2 base_name_start out of bounds");
266 .expect("dirstate-v2 base_name_start out of bounds");
267 Ok(start)
267 Ok(start)
268 } else {
268 } else {
269 Err(DirstateV2ParseError)
269 Err(DirstateV2ParseError)
270 }
270 }
271 }
271 }
272
272
273 pub(super) fn base_name<'on_disk>(
273 pub(super) fn base_name<'on_disk>(
274 &self,
274 &self,
275 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
276 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
276 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
277 let full_path = self.full_path(on_disk)?;
277 let full_path = self.full_path(on_disk)?;
278 let base_name_start = self.base_name_start()?;
278 let base_name_start = self.base_name_start()?;
279 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
279 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
280 }
280 }
281
281
282 pub(super) fn path<'on_disk>(
282 pub(super) fn path<'on_disk>(
283 &self,
283 &self,
284 on_disk: &'on_disk [u8],
284 on_disk: &'on_disk [u8],
285 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
285 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
286 Ok(WithBasename::from_raw_parts(
286 Ok(WithBasename::from_raw_parts(
287 Cow::Borrowed(self.full_path(on_disk)?),
287 Cow::Borrowed(self.full_path(on_disk)?),
288 self.base_name_start()?,
288 self.base_name_start()?,
289 ))
289 ))
290 }
290 }
291
291
292 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
292 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
293 self.copy_source.start.get() != 0
293 self.copy_source.start.get() != 0
294 }
294 }
295
295
296 pub(super) fn copy_source<'on_disk>(
296 pub(super) fn copy_source<'on_disk>(
297 &self,
297 &self,
298 on_disk: &'on_disk [u8],
298 on_disk: &'on_disk [u8],
299 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
299 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
300 Ok(if self.has_copy_source() {
300 Ok(if self.has_copy_source() {
301 Some(read_hg_path(on_disk, self.copy_source)?)
301 Some(read_hg_path(on_disk, self.copy_source)?)
302 } else {
302 } else {
303 None
303 None
304 })
304 })
305 }
305 }
306
306
307 fn flags(&self) -> Flags {
307 fn flags(&self) -> Flags {
308 Flags::from_bits_truncate(self.flags.get())
308 Flags::from_bits_truncate(self.flags.get())
309 }
309 }
310
310
311 fn has_entry(&self) -> bool {
311 fn has_entry(&self) -> bool {
312 self.flags().intersects(
312 self.flags().intersects(
313 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
313 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
314 )
314 )
315 }
315 }
316
316
317 pub(super) fn node_data(
317 pub(super) fn node_data(
318 &self,
318 &self,
319 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
319 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
320 if self.has_entry() {
320 if self.has_entry() {
321 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
321 Ok(dirstate_map::NodeData::Entry(self.assume_entry()?))
322 } else if let Some(mtime) = self.cached_directory_mtime()? {
322 } else if let Some(mtime) = self.cached_directory_mtime()? {
323 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
323 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
324 } else {
324 } else {
325 Ok(dirstate_map::NodeData::None)
325 Ok(dirstate_map::NodeData::None)
326 }
326 }
327 }
327 }
328
328
329 pub(super) fn cached_directory_mtime(
329 pub(super) fn cached_directory_mtime(
330 &self,
330 &self,
331 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
331 ) -> Result<Option<TruncatedTimestamp>, DirstateV2ParseError> {
332 // For now we do not have code to handle the absence of
332 // For now we do not have code to handle the absence of
333 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
333 // ALL_UNKNOWN_RECORDED, so we ignore the mtime if the flag is
334 // unset.
334 // unset.
335 if self.flags().contains(Flags::DIRECTORY)
335 if self.flags().contains(Flags::DIRECTORY)
336 && self.flags().contains(Flags::HAS_MTIME)
336 && self.flags().contains(Flags::HAS_MTIME)
337 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
337 && self.flags().contains(Flags::ALL_UNKNOWN_RECORDED)
338 {
338 {
339 Ok(Some(self.mtime.try_into()?))
339 Ok(Some(self.mtime.try_into()?))
340 } else {
340 } else {
341 Ok(None)
341 Ok(None)
342 }
342 }
343 }
343 }
344
344
345 fn synthesize_unix_mode(&self) -> u32 {
345 fn synthesize_unix_mode(&self) -> u32 {
346 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
346 let file_type = if self.flags().contains(Flags::MODE_IS_SYMLINK) {
347 libc::S_IFLNK
347 libc::S_IFLNK
348 } else {
348 } else {
349 libc::S_IFREG
349 libc::S_IFREG
350 };
350 };
351 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
351 let permisions = if self.flags().contains(Flags::MODE_EXEC_PERM) {
352 0o755
352 0o755
353 } else {
353 } else {
354 0o644
354 0o644
355 };
355 };
356 file_type | permisions
356 file_type | permisions
357 }
357 }
358
358
359 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
359 fn assume_entry(&self) -> Result<DirstateEntry, DirstateV2ParseError> {
360 // TODO: convert through raw bits instead?
360 // TODO: convert through raw bits instead?
361 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
361 let wdir_tracked = self.flags().contains(Flags::WDIR_TRACKED);
362 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
362 let p1_tracked = self.flags().contains(Flags::P1_TRACKED);
363 let p2_info = self.flags().contains(Flags::P2_INFO);
363 let p2_info = self.flags().contains(Flags::P2_INFO);
364 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
364 let mode_size = if self.flags().contains(Flags::HAS_MODE_AND_SIZE)
365 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
365 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
366 {
366 {
367 Some((self.synthesize_unix_mode(), self.size.into()))
367 Some((self.synthesize_unix_mode(), self.size.into()))
368 } else {
368 } else {
369 None
369 None
370 };
370 };
371 let mtime = if self.flags().contains(Flags::HAS_MTIME)
371 let mtime = if self.flags().contains(Flags::HAS_MTIME)
372 && !self.flags().contains(Flags::DIRECTORY)
372 && !self.flags().contains(Flags::DIRECTORY)
373 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
373 && !self.flags().contains(Flags::EXPECTED_STATE_IS_MODIFIED)
374 {
374 {
375 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
375 let mut m: TruncatedTimestamp = self.mtime.try_into()?;
376 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
376 if self.flags().contains(Flags::MTIME_SECOND_AMBIGUOUS) {
377 m.second_ambiguous = true;
377 m.second_ambiguous = true;
378 }
378 }
379 Some(m)
379 Some(m)
380 } else {
380 } else {
381 None
381 None
382 };
382 };
383 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
383 let fallback_exec = if self.flags().contains(Flags::HAS_FALLBACK_EXEC)
384 {
384 {
385 Some(self.flags().contains(Flags::FALLBACK_EXEC))
385 Some(self.flags().contains(Flags::FALLBACK_EXEC))
386 } else {
386 } else {
387 None
387 None
388 };
388 };
389 let fallback_symlink =
389 let fallback_symlink =
390 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
390 if self.flags().contains(Flags::HAS_FALLBACK_SYMLINK) {
391 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
391 Some(self.flags().contains(Flags::FALLBACK_SYMLINK))
392 } else {
392 } else {
393 None
393 None
394 };
394 };
395 Ok(DirstateEntry::from_v2_data(
395 Ok(DirstateEntry::from_v2_data(
396 wdir_tracked,
396 wdir_tracked,
397 p1_tracked,
397 p1_tracked,
398 p2_info,
398 p2_info,
399 mode_size,
399 mode_size,
400 mtime,
400 mtime,
401 fallback_exec,
401 fallback_exec,
402 fallback_symlink,
402 fallback_symlink,
403 ))
403 ))
404 }
404 }
405
405
406 pub(super) fn entry(
406 pub(super) fn entry(
407 &self,
407 &self,
408 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
408 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
409 if self.has_entry() {
409 if self.has_entry() {
410 Ok(Some(self.assume_entry()?))
410 Ok(Some(self.assume_entry()?))
411 } else {
411 } else {
412 Ok(None)
412 Ok(None)
413 }
413 }
414 }
414 }
415
415
416 pub(super) fn children<'on_disk>(
416 pub(super) fn children<'on_disk>(
417 &self,
417 &self,
418 on_disk: &'on_disk [u8],
418 on_disk: &'on_disk [u8],
419 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
419 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
420 read_nodes(on_disk, self.children)
420 read_nodes(on_disk, self.children)
421 }
421 }
422
422
423 pub(super) fn to_in_memory_node<'on_disk>(
423 pub(super) fn to_in_memory_node<'on_disk>(
424 &self,
424 &self,
425 on_disk: &'on_disk [u8],
425 on_disk: &'on_disk [u8],
426 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
426 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
427 Ok(dirstate_map::Node {
427 Ok(dirstate_map::Node {
428 children: dirstate_map::ChildNodes::OnDisk(
428 children: dirstate_map::ChildNodes::OnDisk(
429 self.children(on_disk)?,
429 self.children(on_disk)?,
430 ),
430 ),
431 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
431 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
432 data: self.node_data()?,
432 data: self.node_data()?,
433 descendants_with_entry_count: self
433 descendants_with_entry_count: self
434 .descendants_with_entry_count
434 .descendants_with_entry_count
435 .get(),
435 .get(),
436 tracked_descendants_count: self.tracked_descendants_count.get(),
436 tracked_descendants_count: self.tracked_descendants_count.get(),
437 })
437 })
438 }
438 }
439
439
440 fn from_dirstate_entry(
440 fn from_dirstate_entry(
441 entry: &DirstateEntry,
441 entry: &DirstateEntry,
442 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
442 ) -> (Flags, U32Be, PackedTruncatedTimestamp) {
443 let (
443 let (
444 wdir_tracked,
444 wdir_tracked,
445 p1_tracked,
445 p1_tracked,
446 p2_info,
446 p2_info,
447 mode_size_opt,
447 mode_size_opt,
448 mtime_opt,
448 mtime_opt,
449 fallback_exec,
449 fallback_exec,
450 fallback_symlink,
450 fallback_symlink,
451 ) = entry.v2_data();
451 ) = entry.v2_data();
452 // TODO: convert throug raw flag bits instead?
452 // TODO: convert throug raw flag bits instead?
453 let mut flags = Flags::empty();
453 let mut flags = Flags::empty();
454 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
454 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
455 flags.set(Flags::P1_TRACKED, p1_tracked);
455 flags.set(Flags::P1_TRACKED, p1_tracked);
456 flags.set(Flags::P2_INFO, p2_info);
456 flags.set(Flags::P2_INFO, p2_info);
457 let size = if let Some((m, s)) = mode_size_opt {
457 let size = if let Some((m, s)) = mode_size_opt {
458 let exec_perm = m & libc::S_IXUSR != 0;
458 let exec_perm = m & libc::S_IXUSR != 0;
459 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
459 let is_symlink = m & libc::S_IFMT == libc::S_IFLNK;
460 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
460 flags.set(Flags::MODE_EXEC_PERM, exec_perm);
461 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
461 flags.set(Flags::MODE_IS_SYMLINK, is_symlink);
462 flags.insert(Flags::HAS_MODE_AND_SIZE);
462 flags.insert(Flags::HAS_MODE_AND_SIZE);
463 s.into()
463 s.into()
464 } else {
464 } else {
465 0.into()
465 0.into()
466 };
466 };
467 let mtime = if let Some(m) = mtime_opt {
467 let mtime = if let Some(m) = mtime_opt {
468 flags.insert(Flags::HAS_MTIME);
468 flags.insert(Flags::HAS_MTIME);
469 if m.second_ambiguous {
469 if m.second_ambiguous {
470 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
470 flags.insert(Flags::MTIME_SECOND_AMBIGUOUS);
471 };
471 };
472 m.into()
472 m.into()
473 } else {
473 } else {
474 PackedTruncatedTimestamp::null()
474 PackedTruncatedTimestamp::null()
475 };
475 };
476 if let Some(f_exec) = fallback_exec {
476 if let Some(f_exec) = fallback_exec {
477 flags.insert(Flags::HAS_FALLBACK_EXEC);
477 flags.insert(Flags::HAS_FALLBACK_EXEC);
478 if f_exec {
478 if f_exec {
479 flags.insert(Flags::FALLBACK_EXEC);
479 flags.insert(Flags::FALLBACK_EXEC);
480 }
480 }
481 }
481 }
482 if let Some(f_symlink) = fallback_symlink {
482 if let Some(f_symlink) = fallback_symlink {
483 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
483 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
484 if f_symlink {
484 if f_symlink {
485 flags.insert(Flags::FALLBACK_SYMLINK);
485 flags.insert(Flags::FALLBACK_SYMLINK);
486 }
486 }
487 }
487 }
488 (flags, size, mtime)
488 (flags, size, mtime)
489 }
489 }
490 }
490 }
491
491
492 fn read_hg_path(
492 fn read_hg_path(
493 on_disk: &[u8],
493 on_disk: &[u8],
494 slice: PathSlice,
494 slice: PathSlice,
495 ) -> Result<&HgPath, DirstateV2ParseError> {
495 ) -> Result<&HgPath, DirstateV2ParseError> {
496 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
496 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
497 }
497 }
498
498
499 fn read_nodes(
499 fn read_nodes(
500 on_disk: &[u8],
500 on_disk: &[u8],
501 slice: ChildNodes,
501 slice: ChildNodes,
502 ) -> Result<&[Node], DirstateV2ParseError> {
502 ) -> Result<&[Node], DirstateV2ParseError> {
503 read_slice(on_disk, slice.start, slice.len.get())
503 read_slice(on_disk, slice.start, slice.len.get())
504 }
504 }
505
505
506 fn read_slice<T, Len>(
506 fn read_slice<T, Len>(
507 on_disk: &[u8],
507 on_disk: &[u8],
508 start: Offset,
508 start: Offset,
509 len: Len,
509 len: Len,
510 ) -> Result<&[T], DirstateV2ParseError>
510 ) -> Result<&[T], DirstateV2ParseError>
511 where
511 where
512 T: BytesCast,
512 T: BytesCast,
513 Len: TryInto<usize>,
513 Len: TryInto<usize>,
514 {
514 {
515 // Either `usize::MAX` would result in "out of bounds" error since a single
515 // Either `usize::MAX` would result in "out of bounds" error since a single
516 // `&[u8]` cannot occupy the entire addess space.
516 // `&[u8]` cannot occupy the entire addess space.
517 let start = start.get().try_into().unwrap_or(std::usize::MAX);
517 let start = start.get().try_into().unwrap_or(std::usize::MAX);
518 let len = len.try_into().unwrap_or(std::usize::MAX);
518 let len = len.try_into().unwrap_or(std::usize::MAX);
519 on_disk
519 on_disk
520 .get(start..)
520 .get(start..)
521 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
521 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
522 .map(|(slice, _rest)| slice)
522 .map(|(slice, _rest)| slice)
523 .ok_or_else(|| DirstateV2ParseError)
523 .ok_or_else(|| DirstateV2ParseError)
524 }
524 }
525
525
526 pub(crate) fn for_each_tracked_path<'on_disk>(
526 pub(crate) fn for_each_tracked_path<'on_disk>(
527 on_disk: &'on_disk [u8],
527 on_disk: &'on_disk [u8],
528 metadata: &[u8],
528 metadata: &[u8],
529 mut f: impl FnMut(&'on_disk HgPath),
529 mut f: impl FnMut(&'on_disk HgPath),
530 ) -> Result<(), DirstateV2ParseError> {
530 ) -> Result<(), DirstateV2ParseError> {
531 let (meta, _) = TreeMetadata::from_bytes(metadata)
531 let (meta, _) = TreeMetadata::from_bytes(metadata)
532 .map_err(|_| DirstateV2ParseError)?;
532 .map_err(|_| DirstateV2ParseError)?;
533 fn recur<'on_disk>(
533 fn recur<'on_disk>(
534 on_disk: &'on_disk [u8],
534 on_disk: &'on_disk [u8],
535 nodes: ChildNodes,
535 nodes: ChildNodes,
536 f: &mut impl FnMut(&'on_disk HgPath),
536 f: &mut impl FnMut(&'on_disk HgPath),
537 ) -> Result<(), DirstateV2ParseError> {
537 ) -> Result<(), DirstateV2ParseError> {
538 for node in read_nodes(on_disk, nodes)? {
538 for node in read_nodes(on_disk, nodes)? {
539 if let Some(entry) = node.entry()? {
539 if let Some(entry) = node.entry()? {
540 if entry.state().is_tracked() {
540 if entry.state().is_tracked() {
541 f(node.full_path(on_disk)?)
541 f(node.full_path(on_disk)?)
542 }
542 }
543 }
543 }
544 recur(on_disk, node.children, f)?
544 recur(on_disk, node.children, f)?
545 }
545 }
546 Ok(())
546 Ok(())
547 }
547 }
548 recur(on_disk, meta.root_nodes, &mut f)
548 recur(on_disk, meta.root_nodes, &mut f)
549 }
549 }
550
550
551 /// Returns new data and metadata, together with whether that data should be
551 /// Returns new data and metadata, together with whether that data should be
552 /// appended to the existing data file whose content is at
552 /// appended to the existing data file whose content is at
553 /// `dirstate_map.on_disk` (true), instead of written to a new data file
553 /// `dirstate_map.on_disk` (true), instead of written to a new data file
554 /// (false).
554 /// (false).
555 pub(super) fn write(
555 pub(super) fn write(
556 dirstate_map: &DirstateMap,
556 dirstate_map: &DirstateMap,
557 can_append: bool,
557 can_append: bool,
558 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
558 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
559 let append = can_append && dirstate_map.write_should_append();
559 let append = can_append && dirstate_map.write_should_append();
560
560
561 // This ignores the space for paths, and for nodes without an entry.
561 // This ignores the space for paths, and for nodes without an entry.
562 // TODO: better estimate? Skip the `Vec` and write to a file directly?
562 // TODO: better estimate? Skip the `Vec` and write to a file directly?
563 let size_guess = std::mem::size_of::<Node>()
563 let size_guess = std::mem::size_of::<Node>()
564 * dirstate_map.nodes_with_entry_count as usize;
564 * dirstate_map.nodes_with_entry_count as usize;
565
565
566 let mut writer = Writer {
566 let mut writer = Writer {
567 dirstate_map,
567 dirstate_map,
568 append,
568 append,
569 out: Vec::with_capacity(size_guess),
569 out: Vec::with_capacity(size_guess),
570 };
570 };
571
571
572 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
572 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
573
573
574 let meta = TreeMetadata {
574 let meta = TreeMetadata {
575 root_nodes,
575 root_nodes,
576 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
576 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
577 nodes_with_copy_source_count: dirstate_map
577 nodes_with_copy_source_count: dirstate_map
578 .nodes_with_copy_source_count
578 .nodes_with_copy_source_count
579 .into(),
579 .into(),
580 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
580 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
581 unused: [0; 4],
581 unused: [0; 4],
582 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
582 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
583 };
583 };
584 Ok((writer.out, meta.as_bytes().to_vec(), append))
584 Ok((writer.out, meta.as_bytes().to_vec(), append))
585 }
585 }
586
586
587 struct Writer<'dmap, 'on_disk> {
587 struct Writer<'dmap, 'on_disk> {
588 dirstate_map: &'dmap DirstateMap<'on_disk>,
588 dirstate_map: &'dmap DirstateMap<'on_disk>,
589 append: bool,
589 append: bool,
590 out: Vec<u8>,
590 out: Vec<u8>,
591 }
591 }
592
592
593 impl Writer<'_, '_> {
593 impl Writer<'_, '_> {
594 fn write_nodes(
594 fn write_nodes(
595 &mut self,
595 &mut self,
596 nodes: dirstate_map::ChildNodesRef,
596 nodes: dirstate_map::ChildNodesRef,
597 ) -> Result<ChildNodes, DirstateError> {
597 ) -> Result<ChildNodes, DirstateError> {
598 // Reuse already-written nodes if possible
598 // Reuse already-written nodes if possible
599 if self.append {
599 if self.append {
600 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
600 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
601 let start = self.on_disk_offset_of(nodes_slice).expect(
601 let start = self.on_disk_offset_of(nodes_slice).expect(
602 "dirstate-v2 OnDisk nodes not found within on_disk",
602 "dirstate-v2 OnDisk nodes not found within on_disk",
603 );
603 );
604 let len = child_nodes_len_from_usize(nodes_slice.len());
604 let len = child_nodes_len_from_usize(nodes_slice.len());
605 return Ok(ChildNodes { start, len });
605 return Ok(ChildNodes { start, len });
606 }
606 }
607 }
607 }
608
608
609 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
609 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
610 // undefined iteration order. Sort to enable binary search in the
610 // undefined iteration order. Sort to enable binary search in the
611 // written file.
611 // written file.
612 let nodes = nodes.sorted();
612 let nodes = nodes.sorted();
613 let nodes_len = nodes.len();
613 let nodes_len = nodes.len();
614
614
615 // First accumulate serialized nodes in a `Vec`
615 // First accumulate serialized nodes in a `Vec`
616 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
616 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
617 for node in nodes {
617 for node in nodes {
618 let children =
618 let children =
619 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
619 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
620 let full_path = node.full_path(self.dirstate_map.on_disk)?;
620 let full_path = node.full_path(self.dirstate_map.on_disk)?;
621 let full_path = self.write_path(full_path.as_bytes());
621 let full_path = self.write_path(full_path.as_bytes());
622 let copy_source = if let Some(source) =
622 let copy_source = if let Some(source) =
623 node.copy_source(self.dirstate_map.on_disk)?
623 node.copy_source(self.dirstate_map.on_disk)?
624 {
624 {
625 self.write_path(source.as_bytes())
625 self.write_path(source.as_bytes())
626 } else {
626 } else {
627 PathSlice {
627 PathSlice {
628 start: 0.into(),
628 start: 0.into(),
629 len: 0.into(),
629 len: 0.into(),
630 }
630 }
631 };
631 };
632 on_disk_nodes.push(match node {
632 on_disk_nodes.push(match node {
633 NodeRef::InMemory(path, node) => {
633 NodeRef::InMemory(path, node) => {
634 let (flags, size, mtime) = match &node.data {
634 let (flags, size, mtime) = match &node.data {
635 dirstate_map::NodeData::Entry(entry) => {
635 dirstate_map::NodeData::Entry(entry) => {
636 Node::from_dirstate_entry(entry)
636 Node::from_dirstate_entry(entry)
637 }
637 }
638 dirstate_map::NodeData::CachedDirectory { mtime } => (
638 dirstate_map::NodeData::CachedDirectory { mtime } => (
639 // we currently never set a mtime if unknown file
639 // we currently never set a mtime if unknown file
640 // are present.
640 // are present.
641 // So if we have a mtime for a directory, we know
641 // So if we have a mtime for a directory, we know
642 // they are no unknown
642 // they are no unknown
643 // files and we
643 // files and we
644 // blindly set ALL_UNKNOWN_RECORDED.
644 // blindly set ALL_UNKNOWN_RECORDED.
645 //
645 //
646 // We never set ALL_IGNORED_RECORDED since we
646 // We never set ALL_IGNORED_RECORDED since we
647 // don't track that case
647 // don't track that case
648 // currently.
648 // currently.
649 Flags::DIRECTORY
649 Flags::DIRECTORY
650 | Flags::HAS_MTIME
650 | Flags::HAS_MTIME
651 | Flags::ALL_UNKNOWN_RECORDED,
651 | Flags::ALL_UNKNOWN_RECORDED,
652 0.into(),
652 0.into(),
653 (*mtime).into(),
653 (*mtime).into(),
654 ),
654 ),
655 dirstate_map::NodeData::None => (
655 dirstate_map::NodeData::None => (
656 Flags::DIRECTORY,
656 Flags::DIRECTORY,
657 0.into(),
657 0.into(),
658 PackedTruncatedTimestamp::null(),
658 PackedTruncatedTimestamp::null(),
659 ),
659 ),
660 };
660 };
661 Node {
661 Node {
662 children,
662 children,
663 copy_source,
663 copy_source,
664 full_path,
664 full_path,
665 base_name_start: u16::try_from(path.base_name_start())
665 base_name_start: u16::try_from(path.base_name_start())
666 // Could only panic for paths over 64 KiB
666 // Could only panic for paths over 64 KiB
667 .expect("dirstate-v2 path length overflow")
667 .expect("dirstate-v2 path length overflow")
668 .into(),
668 .into(),
669 descendants_with_entry_count: node
669 descendants_with_entry_count: node
670 .descendants_with_entry_count
670 .descendants_with_entry_count
671 .into(),
671 .into(),
672 tracked_descendants_count: node
672 tracked_descendants_count: node
673 .tracked_descendants_count
673 .tracked_descendants_count
674 .into(),
674 .into(),
675 flags: flags.bits().into(),
675 flags: flags.bits().into(),
676 size,
676 size,
677 mtime,
677 mtime,
678 }
678 }
679 }
679 }
680 NodeRef::OnDisk(node) => Node {
680 NodeRef::OnDisk(node) => Node {
681 children,
681 children,
682 copy_source,
682 copy_source,
683 full_path,
683 full_path,
684 ..*node
684 ..*node
685 },
685 },
686 })
686 })
687 }
687 }
688 // … so we can write them contiguously, after writing everything else
688 // … so we can write them contiguously, after writing everything else
689 // they refer to.
689 // they refer to.
690 let start = self.current_offset();
690 let start = self.current_offset();
691 let len = child_nodes_len_from_usize(nodes_len);
691 let len = child_nodes_len_from_usize(nodes_len);
692 self.out.extend(on_disk_nodes.as_bytes());
692 self.out.extend(on_disk_nodes.as_bytes());
693 Ok(ChildNodes { start, len })
693 Ok(ChildNodes { start, len })
694 }
694 }
695
695
696 /// If the given slice of items is within `on_disk`, returns its offset
696 /// If the given slice of items is within `on_disk`, returns its offset
697 /// from the start of `on_disk`.
697 /// from the start of `on_disk`.
698 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
698 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
699 where
699 where
700 T: BytesCast,
700 T: BytesCast,
701 {
701 {
702 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
702 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
703 let start = slice.as_ptr() as usize;
703 let start = slice.as_ptr() as usize;
704 let end = start + slice.len();
704 let end = start + slice.len();
705 start..=end
705 start..=end
706 }
706 }
707 let slice_addresses = address_range(slice.as_bytes());
707 let slice_addresses = address_range(slice.as_bytes());
708 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
708 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
709 if on_disk_addresses.contains(slice_addresses.start())
709 if on_disk_addresses.contains(slice_addresses.start())
710 && on_disk_addresses.contains(slice_addresses.end())
710 && on_disk_addresses.contains(slice_addresses.end())
711 {
711 {
712 let offset = slice_addresses.start() - on_disk_addresses.start();
712 let offset = slice_addresses.start() - on_disk_addresses.start();
713 Some(offset_from_usize(offset))
713 Some(offset_from_usize(offset))
714 } else {
714 } else {
715 None
715 None
716 }
716 }
717 }
717 }
718
718
719 fn current_offset(&mut self) -> Offset {
719 fn current_offset(&mut self) -> Offset {
720 let mut offset = self.out.len();
720 let mut offset = self.out.len();
721 if self.append {
721 if self.append {
722 offset += self.dirstate_map.on_disk.len()
722 offset += self.dirstate_map.on_disk.len()
723 }
723 }
724 offset_from_usize(offset)
724 offset_from_usize(offset)
725 }
725 }
726
726
727 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
727 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
728 let len = path_len_from_usize(slice.len());
728 let len = path_len_from_usize(slice.len());
729 // Reuse an already-written path if possible
729 // Reuse an already-written path if possible
730 if self.append {
730 if self.append {
731 if let Some(start) = self.on_disk_offset_of(slice) {
731 if let Some(start) = self.on_disk_offset_of(slice) {
732 return PathSlice { start, len };
732 return PathSlice { start, len };
733 }
733 }
734 }
734 }
735 let start = self.current_offset();
735 let start = self.current_offset();
736 self.out.extend(slice.as_bytes());
736 self.out.extend(slice.as_bytes());
737 PathSlice { start, len }
737 PathSlice { start, len }
738 }
738 }
739 }
739 }
740
740
741 fn offset_from_usize(x: usize) -> Offset {
741 fn offset_from_usize(x: usize) -> Offset {
742 u32::try_from(x)
742 u32::try_from(x)
743 // Could only panic for a dirstate file larger than 4 GiB
743 // Could only panic for a dirstate file larger than 4 GiB
744 .expect("dirstate-v2 offset overflow")
744 .expect("dirstate-v2 offset overflow")
745 .into()
745 .into()
746 }
746 }
747
747
748 fn child_nodes_len_from_usize(x: usize) -> Size {
748 fn child_nodes_len_from_usize(x: usize) -> Size {
749 u32::try_from(x)
749 u32::try_from(x)
750 // Could only panic with over 4 billion nodes
750 // Could only panic with over 4 billion nodes
751 .expect("dirstate-v2 slice length overflow")
751 .expect("dirstate-v2 slice length overflow")
752 .into()
752 .into()
753 }
753 }
754
754
755 fn path_len_from_usize(x: usize) -> PathSize {
755 fn path_len_from_usize(x: usize) -> PathSize {
756 u16::try_from(x)
756 u16::try_from(x)
757 // Could only panic for paths over 64 KiB
757 // Could only panic for paths over 64 KiB
758 .expect("dirstate-v2 path length overflow")
758 .expect("dirstate-v2 path length overflow")
759 .into()
759 .into()
760 }
760 }
761
761
762 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
762 impl From<TruncatedTimestamp> for PackedTruncatedTimestamp {
763 fn from(timestamp: TruncatedTimestamp) -> Self {
763 fn from(timestamp: TruncatedTimestamp) -> Self {
764 Self {
764 Self {
765 truncated_seconds: timestamp.truncated_seconds().into(),
765 truncated_seconds: timestamp.truncated_seconds().into(),
766 nanoseconds: timestamp.nanoseconds().into(),
766 nanoseconds: timestamp.nanoseconds().into(),
767 }
767 }
768 }
768 }
769 }
769 }
770
770
771 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
771 impl TryFrom<PackedTruncatedTimestamp> for TruncatedTimestamp {
772 type Error = DirstateV2ParseError;
772 type Error = DirstateV2ParseError;
773
773
774 fn try_from(
774 fn try_from(
775 timestamp: PackedTruncatedTimestamp,
775 timestamp: PackedTruncatedTimestamp,
776 ) -> Result<Self, Self::Error> {
776 ) -> Result<Self, Self::Error> {
777 Self::from_already_truncated(
777 Self::from_already_truncated(
778 timestamp.truncated_seconds.get(),
778 timestamp.truncated_seconds.get(),
779 timestamp.nanoseconds.get(),
779 timestamp.nanoseconds.get(),
780 false,
780 false,
781 )
781 )
782 }
782 }
783 }
783 }
784 impl PackedTruncatedTimestamp {
784 impl PackedTruncatedTimestamp {
785 fn null() -> Self {
785 fn null() -> Self {
786 Self {
786 Self {
787 truncated_seconds: 0.into(),
787 truncated_seconds: 0.into(),
788 nanoseconds: 0.into(),
788 nanoseconds: 0.into(),
789 }
789 }
790 }
790 }
791 }
791 }
@@ -1,429 +1,464 b''
1 use crate::changelog::Changelog;
1 use crate::changelog::Changelog;
2 use crate::config::{Config, ConfigError, ConfigParseError};
2 use crate::config::{Config, ConfigError, ConfigParseError};
3 use crate::dirstate::DirstateParents;
3 use crate::dirstate::DirstateParents;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
4 use crate::dirstate_tree::dirstate_map::DirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
5 use crate::dirstate_tree::owning::OwningDirstateMap;
6 use crate::errors::HgError;
6 use crate::errors::HgError;
7 use crate::errors::HgResultExt;
7 use crate::errors::HgResultExt;
8 use crate::exit_codes;
8 use crate::exit_codes;
9 use crate::lock::{try_with_lock_no_wait, LockError};
9 use crate::lock::{try_with_lock_no_wait, LockError};
10 use crate::manifest::{Manifest, Manifestlog};
10 use crate::manifest::{Manifest, Manifestlog};
11 use crate::revlog::filelog::Filelog;
11 use crate::revlog::filelog::Filelog;
12 use crate::revlog::revlog::RevlogError;
12 use crate::revlog::revlog::RevlogError;
13 use crate::utils::files::get_path_from_bytes;
13 use crate::utils::files::get_path_from_bytes;
14 use crate::utils::hg_path::HgPath;
14 use crate::utils::hg_path::HgPath;
15 use crate::utils::SliceExt;
15 use crate::utils::SliceExt;
16 use crate::vfs::{is_dir, is_file, Vfs};
16 use crate::vfs::{is_dir, is_file, Vfs};
17 use crate::{requirements, NodePrefix};
17 use crate::{requirements, NodePrefix};
18 use crate::{DirstateError, Revision};
18 use crate::{DirstateError, Revision};
19 use std::cell::{Ref, RefCell, RefMut};
19 use std::cell::{Ref, RefCell, RefMut};
20 use std::collections::HashSet;
20 use std::collections::HashSet;
21 use std::path::{Path, PathBuf};
21 use std::path::{Path, PathBuf};
22
22
23 /// A repository on disk
23 /// A repository on disk
24 pub struct Repo {
24 pub struct Repo {
25 working_directory: PathBuf,
25 working_directory: PathBuf,
26 dot_hg: PathBuf,
26 dot_hg: PathBuf,
27 store: PathBuf,
27 store: PathBuf,
28 requirements: HashSet<String>,
28 requirements: HashSet<String>,
29 config: Config,
29 config: Config,
30 dirstate_parents: LazyCell<DirstateParents, HgError>,
30 dirstate_parents: LazyCell<DirstateParents, HgError>,
31 dirstate_data_file_uuid: LazyCell<Option<Vec<u8>>, HgError>,
31 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 dirstate_map: LazyCell<OwningDirstateMap, DirstateError>,
32 changelog: LazyCell<Changelog, HgError>,
33 changelog: LazyCell<Changelog, HgError>,
33 manifestlog: LazyCell<Manifestlog, HgError>,
34 manifestlog: LazyCell<Manifestlog, HgError>,
34 }
35 }
35
36
36 #[derive(Debug, derive_more::From)]
37 #[derive(Debug, derive_more::From)]
37 pub enum RepoError {
38 pub enum RepoError {
38 NotFound {
39 NotFound {
39 at: PathBuf,
40 at: PathBuf,
40 },
41 },
41 #[from]
42 #[from]
42 ConfigParseError(ConfigParseError),
43 ConfigParseError(ConfigParseError),
43 #[from]
44 #[from]
44 Other(HgError),
45 Other(HgError),
45 }
46 }
46
47
47 impl From<ConfigError> for RepoError {
48 impl From<ConfigError> for RepoError {
48 fn from(error: ConfigError) -> Self {
49 fn from(error: ConfigError) -> Self {
49 match error {
50 match error {
50 ConfigError::Parse(error) => error.into(),
51 ConfigError::Parse(error) => error.into(),
51 ConfigError::Other(error) => error.into(),
52 ConfigError::Other(error) => error.into(),
52 }
53 }
53 }
54 }
54 }
55 }
55
56
56 impl Repo {
57 impl Repo {
57 /// tries to find nearest repository root in current working directory or
58 /// tries to find nearest repository root in current working directory or
58 /// its ancestors
59 /// its ancestors
59 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 pub fn find_repo_root() -> Result<PathBuf, RepoError> {
60 let current_directory = crate::utils::current_dir()?;
61 let current_directory = crate::utils::current_dir()?;
61 // ancestors() is inclusive: it first yields `current_directory`
62 // ancestors() is inclusive: it first yields `current_directory`
62 // as-is.
63 // as-is.
63 for ancestor in current_directory.ancestors() {
64 for ancestor in current_directory.ancestors() {
64 if is_dir(ancestor.join(".hg"))? {
65 if is_dir(ancestor.join(".hg"))? {
65 return Ok(ancestor.to_path_buf());
66 return Ok(ancestor.to_path_buf());
66 }
67 }
67 }
68 }
68 return Err(RepoError::NotFound {
69 return Err(RepoError::NotFound {
69 at: current_directory,
70 at: current_directory,
70 });
71 });
71 }
72 }
72
73
73 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// Find a repository, either at the given path (which must contain a `.hg`
74 /// sub-directory) or by searching the current directory and its
75 /// sub-directory) or by searching the current directory and its
75 /// ancestors.
76 /// ancestors.
76 ///
77 ///
77 /// A method with two very different "modes" like this usually a code smell
78 /// A method with two very different "modes" like this usually a code smell
78 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// to make two methods instead, but in this case an `Option` is what rhg
79 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// sub-commands get from Clap for the `-R` / `--repository` CLI argument.
80 /// Having two methods would just move that `if` to almost all callers.
81 /// Having two methods would just move that `if` to almost all callers.
81 pub fn find(
82 pub fn find(
82 config: &Config,
83 config: &Config,
83 explicit_path: Option<PathBuf>,
84 explicit_path: Option<PathBuf>,
84 ) -> Result<Self, RepoError> {
85 ) -> Result<Self, RepoError> {
85 if let Some(root) = explicit_path {
86 if let Some(root) = explicit_path {
86 if is_dir(root.join(".hg"))? {
87 if is_dir(root.join(".hg"))? {
87 Self::new_at_path(root.to_owned(), config)
88 Self::new_at_path(root.to_owned(), config)
88 } else if is_file(&root)? {
89 } else if is_file(&root)? {
89 Err(HgError::unsupported("bundle repository").into())
90 Err(HgError::unsupported("bundle repository").into())
90 } else {
91 } else {
91 Err(RepoError::NotFound {
92 Err(RepoError::NotFound {
92 at: root.to_owned(),
93 at: root.to_owned(),
93 })
94 })
94 }
95 }
95 } else {
96 } else {
96 let root = Self::find_repo_root()?;
97 let root = Self::find_repo_root()?;
97 Self::new_at_path(root, config)
98 Self::new_at_path(root, config)
98 }
99 }
99 }
100 }
100
101
101 /// To be called after checking that `.hg` is a sub-directory
102 /// To be called after checking that `.hg` is a sub-directory
102 fn new_at_path(
103 fn new_at_path(
103 working_directory: PathBuf,
104 working_directory: PathBuf,
104 config: &Config,
105 config: &Config,
105 ) -> Result<Self, RepoError> {
106 ) -> Result<Self, RepoError> {
106 let dot_hg = working_directory.join(".hg");
107 let dot_hg = working_directory.join(".hg");
107
108
108 let mut repo_config_files = Vec::new();
109 let mut repo_config_files = Vec::new();
109 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc"));
110 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111 repo_config_files.push(dot_hg.join("hgrc-not-shared"));
111
112
112 let hg_vfs = Vfs { base: &dot_hg };
113 let hg_vfs = Vfs { base: &dot_hg };
113 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let mut reqs = requirements::load_if_exists(hg_vfs)?;
114 let relative =
115 let relative =
115 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 reqs.contains(requirements::RELATIVE_SHARED_REQUIREMENT);
116 let shared =
117 let shared =
117 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118 reqs.contains(requirements::SHARED_REQUIREMENT) || relative;
118
119
119 // From `mercurial/localrepo.py`:
120 // From `mercurial/localrepo.py`:
120 //
121 //
121 // if .hg/requires contains the sharesafe requirement, it means
122 // if .hg/requires contains the sharesafe requirement, it means
122 // there exists a `.hg/store/requires` too and we should read it
123 // there exists a `.hg/store/requires` too and we should read it
123 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // NOTE: presence of SHARESAFE_REQUIREMENT imply that store requirement
124 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is present. We never write SHARESAFE_REQUIREMENT for a repo if store
125 // is not present, refer checkrequirementscompat() for that
126 // is not present, refer checkrequirementscompat() for that
126 //
127 //
127 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // However, if SHARESAFE_REQUIREMENT is not present, it means that the
128 // repository was shared the old way. We check the share source
129 // repository was shared the old way. We check the share source
129 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // .hg/requires for SHARESAFE_REQUIREMENT to detect whether the
130 // current repository needs to be reshared
131 // current repository needs to be reshared
131 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132 let share_safe = reqs.contains(requirements::SHARESAFE_REQUIREMENT);
132
133
133 let store_path;
134 let store_path;
134 if !shared {
135 if !shared {
135 store_path = dot_hg.join("store");
136 store_path = dot_hg.join("store");
136 } else {
137 } else {
137 let bytes = hg_vfs.read("sharedpath")?;
138 let bytes = hg_vfs.read("sharedpath")?;
138 let mut shared_path =
139 let mut shared_path =
139 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 get_path_from_bytes(bytes.trim_end_matches(|b| b == b'\n'))
140 .to_owned();
141 .to_owned();
141 if relative {
142 if relative {
142 shared_path = dot_hg.join(shared_path)
143 shared_path = dot_hg.join(shared_path)
143 }
144 }
144 if !is_dir(&shared_path)? {
145 if !is_dir(&shared_path)? {
145 return Err(HgError::corrupted(format!(
146 return Err(HgError::corrupted(format!(
146 ".hg/sharedpath points to nonexistent directory {}",
147 ".hg/sharedpath points to nonexistent directory {}",
147 shared_path.display()
148 shared_path.display()
148 ))
149 ))
149 .into());
150 .into());
150 }
151 }
151
152
152 store_path = shared_path.join("store");
153 store_path = shared_path.join("store");
153
154
154 let source_is_share_safe =
155 let source_is_share_safe =
155 requirements::load(Vfs { base: &shared_path })?
156 requirements::load(Vfs { base: &shared_path })?
156 .contains(requirements::SHARESAFE_REQUIREMENT);
157 .contains(requirements::SHARESAFE_REQUIREMENT);
157
158
158 if share_safe && !source_is_share_safe {
159 if share_safe && !source_is_share_safe {
159 return Err(match config
160 return Err(match config
160 .get(b"share", b"safe-mismatch.source-not-safe")
161 .get(b"share", b"safe-mismatch.source-not-safe")
161 {
162 {
162 Some(b"abort") | None => HgError::abort(
163 Some(b"abort") | None => HgError::abort(
163 "abort: share source does not support share-safe requirement\n\
164 "abort: share source does not support share-safe requirement\n\
164 (see `hg help config.format.use-share-safe` for more information)",
165 (see `hg help config.format.use-share-safe` for more information)",
165 exit_codes::ABORT,
166 exit_codes::ABORT,
166 ),
167 ),
167 _ => HgError::unsupported("share-safe downgrade"),
168 _ => HgError::unsupported("share-safe downgrade"),
168 }
169 }
169 .into());
170 .into());
170 } else if source_is_share_safe && !share_safe {
171 } else if source_is_share_safe && !share_safe {
171 return Err(
172 return Err(
172 match config.get(b"share", b"safe-mismatch.source-safe") {
173 match config.get(b"share", b"safe-mismatch.source-safe") {
173 Some(b"abort") | None => HgError::abort(
174 Some(b"abort") | None => HgError::abort(
174 "abort: version mismatch: source uses share-safe \
175 "abort: version mismatch: source uses share-safe \
175 functionality while the current share does not\n\
176 functionality while the current share does not\n\
176 (see `hg help config.format.use-share-safe` for more information)",
177 (see `hg help config.format.use-share-safe` for more information)",
177 exit_codes::ABORT,
178 exit_codes::ABORT,
178 ),
179 ),
179 _ => HgError::unsupported("share-safe upgrade"),
180 _ => HgError::unsupported("share-safe upgrade"),
180 }
181 }
181 .into(),
182 .into(),
182 );
183 );
183 }
184 }
184
185
185 if share_safe {
186 if share_safe {
186 repo_config_files.insert(0, shared_path.join("hgrc"))
187 repo_config_files.insert(0, shared_path.join("hgrc"))
187 }
188 }
188 }
189 }
189 if share_safe {
190 if share_safe {
190 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 reqs.extend(requirements::load(Vfs { base: &store_path })?);
191 }
192 }
192
193
193 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 let repo_config = if std::env::var_os("HGRCSKIPREPO").is_none() {
194 config.combine_with_repo(&repo_config_files)?
195 config.combine_with_repo(&repo_config_files)?
195 } else {
196 } else {
196 config.clone()
197 config.clone()
197 };
198 };
198
199
199 let repo = Self {
200 let repo = Self {
200 requirements: reqs,
201 requirements: reqs,
201 working_directory,
202 working_directory,
202 store: store_path,
203 store: store_path,
203 dot_hg,
204 dot_hg,
204 config: repo_config,
205 config: repo_config,
205 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
206 dirstate_parents: LazyCell::new(Self::read_dirstate_parents),
207 dirstate_data_file_uuid: LazyCell::new(
208 Self::read_dirstate_data_file_uuid,
209 ),
206 dirstate_map: LazyCell::new(Self::new_dirstate_map),
210 dirstate_map: LazyCell::new(Self::new_dirstate_map),
207 changelog: LazyCell::new(Changelog::open),
211 changelog: LazyCell::new(Changelog::open),
208 manifestlog: LazyCell::new(Manifestlog::open),
212 manifestlog: LazyCell::new(Manifestlog::open),
209 };
213 };
210
214
211 requirements::check(&repo)?;
215 requirements::check(&repo)?;
212
216
213 Ok(repo)
217 Ok(repo)
214 }
218 }
215
219
216 pub fn working_directory_path(&self) -> &Path {
220 pub fn working_directory_path(&self) -> &Path {
217 &self.working_directory
221 &self.working_directory
218 }
222 }
219
223
220 pub fn requirements(&self) -> &HashSet<String> {
224 pub fn requirements(&self) -> &HashSet<String> {
221 &self.requirements
225 &self.requirements
222 }
226 }
223
227
224 pub fn config(&self) -> &Config {
228 pub fn config(&self) -> &Config {
225 &self.config
229 &self.config
226 }
230 }
227
231
228 /// For accessing repository files (in `.hg`), except for the store
232 /// For accessing repository files (in `.hg`), except for the store
229 /// (`.hg/store`).
233 /// (`.hg/store`).
230 pub fn hg_vfs(&self) -> Vfs<'_> {
234 pub fn hg_vfs(&self) -> Vfs<'_> {
231 Vfs { base: &self.dot_hg }
235 Vfs { base: &self.dot_hg }
232 }
236 }
233
237
234 /// For accessing repository store files (in `.hg/store`)
238 /// For accessing repository store files (in `.hg/store`)
235 pub fn store_vfs(&self) -> Vfs<'_> {
239 pub fn store_vfs(&self) -> Vfs<'_> {
236 Vfs { base: &self.store }
240 Vfs { base: &self.store }
237 }
241 }
238
242
239 /// For accessing the working copy
243 /// For accessing the working copy
240 pub fn working_directory_vfs(&self) -> Vfs<'_> {
244 pub fn working_directory_vfs(&self) -> Vfs<'_> {
241 Vfs {
245 Vfs {
242 base: &self.working_directory,
246 base: &self.working_directory,
243 }
247 }
244 }
248 }
245
249
246 pub fn try_with_wlock_no_wait<R>(
250 pub fn try_with_wlock_no_wait<R>(
247 &self,
251 &self,
248 f: impl FnOnce() -> R,
252 f: impl FnOnce() -> R,
249 ) -> Result<R, LockError> {
253 ) -> Result<R, LockError> {
250 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
254 try_with_lock_no_wait(self.hg_vfs(), "wlock", f)
251 }
255 }
252
256
253 pub fn has_dirstate_v2(&self) -> bool {
257 pub fn has_dirstate_v2(&self) -> bool {
254 self.requirements
258 self.requirements
255 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
259 .contains(requirements::DIRSTATE_V2_REQUIREMENT)
256 }
260 }
257
261
258 pub fn has_sparse(&self) -> bool {
262 pub fn has_sparse(&self) -> bool {
259 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
263 self.requirements.contains(requirements::SPARSE_REQUIREMENT)
260 }
264 }
261
265
262 pub fn has_narrow(&self) -> bool {
266 pub fn has_narrow(&self) -> bool {
263 self.requirements.contains(requirements::NARROW_REQUIREMENT)
267 self.requirements.contains(requirements::NARROW_REQUIREMENT)
264 }
268 }
265
269
266 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
270 fn dirstate_file_contents(&self) -> Result<Vec<u8>, HgError> {
267 Ok(self
271 Ok(self
268 .hg_vfs()
272 .hg_vfs()
269 .read("dirstate")
273 .read("dirstate")
270 .io_not_found_as_none()?
274 .io_not_found_as_none()?
271 .unwrap_or(Vec::new()))
275 .unwrap_or(Vec::new()))
272 }
276 }
273
277
274 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
278 pub fn dirstate_parents(&self) -> Result<DirstateParents, HgError> {
275 Ok(*self.dirstate_parents.get_or_init(self)?)
279 Ok(*self.dirstate_parents.get_or_init(self)?)
276 }
280 }
277
281
278 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
282 fn read_dirstate_parents(&self) -> Result<DirstateParents, HgError> {
279 let dirstate = self.dirstate_file_contents()?;
283 let dirstate = self.dirstate_file_contents()?;
280 let parents = if dirstate.is_empty() {
284 let parents = if dirstate.is_empty() {
285 if self.has_dirstate_v2() {
286 self.dirstate_data_file_uuid.set(None);
287 }
281 DirstateParents::NULL
288 DirstateParents::NULL
282 } else if self.has_dirstate_v2() {
289 } else if self.has_dirstate_v2() {
283 crate::dirstate_tree::on_disk::read_docket(&dirstate)?.parents()
290 let docket =
291 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
292 self.dirstate_data_file_uuid
293 .set(Some(docket.uuid.to_owned()));
294 docket.parents()
284 } else {
295 } else {
285 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
296 crate::dirstate::parsers::parse_dirstate_parents(&dirstate)?
286 .clone()
297 .clone()
287 };
298 };
288 self.dirstate_parents.set(parents);
299 self.dirstate_parents.set(parents);
289 Ok(parents)
300 Ok(parents)
290 }
301 }
291
302
303 fn read_dirstate_data_file_uuid(
304 &self,
305 ) -> Result<Option<Vec<u8>>, HgError> {
306 assert!(
307 self.has_dirstate_v2(),
308 "accessing dirstate data file ID without dirstate-v2"
309 );
310 let dirstate = self.dirstate_file_contents()?;
311 if dirstate.is_empty() {
312 self.dirstate_parents.set(DirstateParents::NULL);
313 Ok(None)
314 } else {
315 let docket =
316 crate::dirstate_tree::on_disk::read_docket(&dirstate)?;
317 self.dirstate_parents.set(docket.parents());
318 Ok(Some(docket.uuid.to_owned()))
319 }
320 }
321
292 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
322 fn new_dirstate_map(&self) -> Result<OwningDirstateMap, DirstateError> {
293 let dirstate_file_contents = self.dirstate_file_contents()?;
323 let dirstate_file_contents = self.dirstate_file_contents()?;
294 if dirstate_file_contents.is_empty() {
324 if dirstate_file_contents.is_empty() {
295 self.dirstate_parents.set(DirstateParents::NULL);
325 self.dirstate_parents.set(DirstateParents::NULL);
326 if self.has_dirstate_v2() {
327 self.dirstate_data_file_uuid.set(None);
328 }
296 Ok(OwningDirstateMap::new_empty(Vec::new()))
329 Ok(OwningDirstateMap::new_empty(Vec::new()))
297 } else if self.has_dirstate_v2() {
330 } else if self.has_dirstate_v2() {
298 let docket = crate::dirstate_tree::on_disk::read_docket(
331 let docket = crate::dirstate_tree::on_disk::read_docket(
299 &dirstate_file_contents,
332 &dirstate_file_contents,
300 )?;
333 )?;
301 self.dirstate_parents.set(docket.parents());
334 self.dirstate_parents.set(docket.parents());
335 self.dirstate_data_file_uuid
336 .set(Some(docket.uuid.to_owned()));
302 let data_size = docket.data_size();
337 let data_size = docket.data_size();
303 let metadata = docket.tree_metadata();
338 let metadata = docket.tree_metadata();
304 let mut map = if let Some(data_mmap) = self
339 let mut map = if let Some(data_mmap) = self
305 .hg_vfs()
340 .hg_vfs()
306 .mmap_open(docket.data_filename())
341 .mmap_open(docket.data_filename())
307 .io_not_found_as_none()?
342 .io_not_found_as_none()?
308 {
343 {
309 OwningDirstateMap::new_empty(data_mmap)
344 OwningDirstateMap::new_empty(data_mmap)
310 } else {
345 } else {
311 OwningDirstateMap::new_empty(Vec::new())
346 OwningDirstateMap::new_empty(Vec::new())
312 };
347 };
313 let (on_disk, placeholder) = map.get_pair_mut();
348 let (on_disk, placeholder) = map.get_pair_mut();
314 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
349 *placeholder = DirstateMap::new_v2(on_disk, data_size, metadata)?;
315 Ok(map)
350 Ok(map)
316 } else {
351 } else {
317 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
352 let mut map = OwningDirstateMap::new_empty(dirstate_file_contents);
318 let (on_disk, placeholder) = map.get_pair_mut();
353 let (on_disk, placeholder) = map.get_pair_mut();
319 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
354 let (inner, parents) = DirstateMap::new_v1(on_disk)?;
320 self.dirstate_parents
355 self.dirstate_parents
321 .set(parents.unwrap_or(DirstateParents::NULL));
356 .set(parents.unwrap_or(DirstateParents::NULL));
322 *placeholder = inner;
357 *placeholder = inner;
323 Ok(map)
358 Ok(map)
324 }
359 }
325 }
360 }
326
361
327 pub fn dirstate_map(
362 pub fn dirstate_map(
328 &self,
363 &self,
329 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
364 ) -> Result<Ref<OwningDirstateMap>, DirstateError> {
330 self.dirstate_map.get_or_init(self)
365 self.dirstate_map.get_or_init(self)
331 }
366 }
332
367
333 pub fn dirstate_map_mut(
368 pub fn dirstate_map_mut(
334 &self,
369 &self,
335 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
370 ) -> Result<RefMut<OwningDirstateMap>, DirstateError> {
336 self.dirstate_map.get_mut_or_init(self)
371 self.dirstate_map.get_mut_or_init(self)
337 }
372 }
338
373
339 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
374 pub fn changelog(&self) -> Result<Ref<Changelog>, HgError> {
340 self.changelog.get_or_init(self)
375 self.changelog.get_or_init(self)
341 }
376 }
342
377
343 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
378 pub fn changelog_mut(&self) -> Result<RefMut<Changelog>, HgError> {
344 self.changelog.get_mut_or_init(self)
379 self.changelog.get_mut_or_init(self)
345 }
380 }
346
381
347 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
382 pub fn manifestlog(&self) -> Result<Ref<Manifestlog>, HgError> {
348 self.manifestlog.get_or_init(self)
383 self.manifestlog.get_or_init(self)
349 }
384 }
350
385
351 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
386 pub fn manifestlog_mut(&self) -> Result<RefMut<Manifestlog>, HgError> {
352 self.manifestlog.get_mut_or_init(self)
387 self.manifestlog.get_mut_or_init(self)
353 }
388 }
354
389
355 /// Returns the manifest of the *changeset* with the given node ID
390 /// Returns the manifest of the *changeset* with the given node ID
356 pub fn manifest_for_node(
391 pub fn manifest_for_node(
357 &self,
392 &self,
358 node: impl Into<NodePrefix>,
393 node: impl Into<NodePrefix>,
359 ) -> Result<Manifest, RevlogError> {
394 ) -> Result<Manifest, RevlogError> {
360 self.manifestlog()?.data_for_node(
395 self.manifestlog()?.data_for_node(
361 self.changelog()?
396 self.changelog()?
362 .data_for_node(node.into())?
397 .data_for_node(node.into())?
363 .manifest_node()?
398 .manifest_node()?
364 .into(),
399 .into(),
365 )
400 )
366 }
401 }
367
402
368 /// Returns the manifest of the *changeset* with the given revision number
403 /// Returns the manifest of the *changeset* with the given revision number
369 pub fn manifest_for_rev(
404 pub fn manifest_for_rev(
370 &self,
405 &self,
371 revision: Revision,
406 revision: Revision,
372 ) -> Result<Manifest, RevlogError> {
407 ) -> Result<Manifest, RevlogError> {
373 self.manifestlog()?.data_for_node(
408 self.manifestlog()?.data_for_node(
374 self.changelog()?
409 self.changelog()?
375 .data_for_rev(revision)?
410 .data_for_rev(revision)?
376 .manifest_node()?
411 .manifest_node()?
377 .into(),
412 .into(),
378 )
413 )
379 }
414 }
380
415
381 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
416 pub fn filelog(&self, path: &HgPath) -> Result<Filelog, HgError> {
382 Filelog::open(self, path)
417 Filelog::open(self, path)
383 }
418 }
384 }
419 }
385
420
386 /// Lazily-initialized component of `Repo` with interior mutability
421 /// Lazily-initialized component of `Repo` with interior mutability
387 ///
422 ///
388 /// This differs from `OnceCell` in that the value can still be "deinitialized"
423 /// This differs from `OnceCell` in that the value can still be "deinitialized"
389 /// later by setting its inner `Option` to `None`.
424 /// later by setting its inner `Option` to `None`.
390 struct LazyCell<T, E> {
425 struct LazyCell<T, E> {
391 value: RefCell<Option<T>>,
426 value: RefCell<Option<T>>,
392 // `Fn`s that don’t capture environment are zero-size, so this box does
427 // `Fn`s that don’t capture environment are zero-size, so this box does
393 // not allocate:
428 // not allocate:
394 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
429 init: Box<dyn Fn(&Repo) -> Result<T, E>>,
395 }
430 }
396
431
397 impl<T, E> LazyCell<T, E> {
432 impl<T, E> LazyCell<T, E> {
398 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
433 fn new(init: impl Fn(&Repo) -> Result<T, E> + 'static) -> Self {
399 Self {
434 Self {
400 value: RefCell::new(None),
435 value: RefCell::new(None),
401 init: Box::new(init),
436 init: Box::new(init),
402 }
437 }
403 }
438 }
404
439
405 fn set(&self, value: T) {
440 fn set(&self, value: T) {
406 *self.value.borrow_mut() = Some(value)
441 *self.value.borrow_mut() = Some(value)
407 }
442 }
408
443
409 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
444 fn get_or_init(&self, repo: &Repo) -> Result<Ref<T>, E> {
410 let mut borrowed = self.value.borrow();
445 let mut borrowed = self.value.borrow();
411 if borrowed.is_none() {
446 if borrowed.is_none() {
412 drop(borrowed);
447 drop(borrowed);
413 // Only use `borrow_mut` if it is really needed to avoid panic in
448 // Only use `borrow_mut` if it is really needed to avoid panic in
414 // case there is another outstanding borrow but mutation is not
449 // case there is another outstanding borrow but mutation is not
415 // needed.
450 // needed.
416 *self.value.borrow_mut() = Some((self.init)(repo)?);
451 *self.value.borrow_mut() = Some((self.init)(repo)?);
417 borrowed = self.value.borrow()
452 borrowed = self.value.borrow()
418 }
453 }
419 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
454 Ok(Ref::map(borrowed, |option| option.as_ref().unwrap()))
420 }
455 }
421
456
422 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
457 fn get_mut_or_init(&self, repo: &Repo) -> Result<RefMut<T>, E> {
423 let mut borrowed = self.value.borrow_mut();
458 let mut borrowed = self.value.borrow_mut();
424 if borrowed.is_none() {
459 if borrowed.is_none() {
425 *borrowed = Some((self.init)(repo)?);
460 *borrowed = Some((self.init)(repo)?);
426 }
461 }
427 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
462 Ok(RefMut::map(borrowed, |option| option.as_mut().unwrap()))
428 }
463 }
429 }
464 }
General Comments 0
You need to be logged in to leave comments. Login now