##// END OF EJS Templates
dirstate-v2: Store a bitfield on disk instead of v1-like state...
Simon Sapin -
r48951:ab5a7fdb default
parent child Browse files
Show More
@@ -1,413 +1,429 b''
1 use crate::errors::HgError;
1 use crate::errors::HgError;
2 use bitflags::bitflags;
2 use bitflags::bitflags;
3 use std::convert::TryFrom;
3 use std::convert::TryFrom;
4
4
5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
5 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
6 pub enum EntryState {
6 pub enum EntryState {
7 Normal,
7 Normal,
8 Added,
8 Added,
9 Removed,
9 Removed,
10 Merged,
10 Merged,
11 }
11 }
12
12
13 /// The C implementation uses all signed types. This will be an issue
13 /// The C implementation uses all signed types. This will be an issue
14 /// either when 4GB+ source files are commonplace or in 2038, whichever
14 /// either when 4GB+ source files are commonplace or in 2038, whichever
15 /// comes first.
15 /// comes first.
16 #[derive(Debug, PartialEq, Copy, Clone)]
16 #[derive(Debug, PartialEq, Copy, Clone)]
17 pub struct DirstateEntry {
17 pub struct DirstateEntry {
18 flags: Flags,
18 pub(crate) flags: Flags,
19 mode_size: Option<(i32, i32)>,
19 mode_size: Option<(i32, i32)>,
20 mtime: Option<i32>,
20 mtime: Option<i32>,
21 }
21 }
22
22
23 bitflags! {
23 bitflags! {
24 struct Flags: u8 {
24 pub(crate) struct Flags: u8 {
25 const WDIR_TRACKED = 1 << 0;
25 const WDIR_TRACKED = 1 << 0;
26 const P1_TRACKED = 1 << 1;
26 const P1_TRACKED = 1 << 1;
27 const P2_INFO = 1 << 2;
27 const P2_INFO = 1 << 2;
28 }
28 }
29 }
29 }
30
30
31 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
31 pub const V1_RANGEMASK: i32 = 0x7FFFFFFF;
32
32
33 pub const MTIME_UNSET: i32 = -1;
33 pub const MTIME_UNSET: i32 = -1;
34
34
35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
35 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
36 /// other parent. This allows revert to pick the right status back during a
36 /// other parent. This allows revert to pick the right status back during a
37 /// merge.
37 /// merge.
38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
38 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
39 /// A special value used for internal representation of special case in
39 /// A special value used for internal representation of special case in
40 /// dirstate v1 format.
40 /// dirstate v1 format.
41 pub const SIZE_NON_NORMAL: i32 = -1;
41 pub const SIZE_NON_NORMAL: i32 = -1;
42
42
43 impl DirstateEntry {
43 impl DirstateEntry {
44 pub fn new(
44 pub fn from_v2_data(
45 wdir_tracked: bool,
45 wdir_tracked: bool,
46 p1_tracked: bool,
46 p1_tracked: bool,
47 p2_info: bool,
47 p2_info: bool,
48 mode_size: Option<(i32, i32)>,
48 mode_size: Option<(i32, i32)>,
49 mtime: Option<i32>,
49 mtime: Option<i32>,
50 ) -> Self {
50 ) -> Self {
51 let mut flags = Flags::empty();
51 let mut flags = Flags::empty();
52 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
52 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
53 flags.set(Flags::P1_TRACKED, p1_tracked);
53 flags.set(Flags::P1_TRACKED, p1_tracked);
54 flags.set(Flags::P2_INFO, p2_info);
54 flags.set(Flags::P2_INFO, p2_info);
55 Self {
55 Self {
56 flags,
56 flags,
57 mode_size,
57 mode_size,
58 mtime,
58 mtime,
59 }
59 }
60 }
60 }
61
61
62 pub fn from_v1_data(
62 pub fn from_v1_data(
63 state: EntryState,
63 state: EntryState,
64 mode: i32,
64 mode: i32,
65 size: i32,
65 size: i32,
66 mtime: i32,
66 mtime: i32,
67 ) -> Self {
67 ) -> Self {
68 match state {
68 match state {
69 EntryState::Normal => {
69 EntryState::Normal => {
70 if size == SIZE_FROM_OTHER_PARENT {
70 if size == SIZE_FROM_OTHER_PARENT {
71 Self::new_from_p2()
71 Self::new_from_p2()
72 } else if size == SIZE_NON_NORMAL {
72 } else if size == SIZE_NON_NORMAL {
73 Self::new_possibly_dirty()
73 Self::new_possibly_dirty()
74 } else if mtime == MTIME_UNSET {
74 } else if mtime == MTIME_UNSET {
75 Self {
75 Self {
76 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
76 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
77 mode_size: Some((mode, size)),
77 mode_size: Some((mode, size)),
78 mtime: None,
78 mtime: None,
79 }
79 }
80 } else {
80 } else {
81 Self::new_normal(mode, size, mtime)
81 Self::new_normal(mode, size, mtime)
82 }
82 }
83 }
83 }
84 EntryState::Added => Self::new_added(),
84 EntryState::Added => Self::new_added(),
85 EntryState::Removed => Self {
85 EntryState::Removed => Self {
86 flags: if size == SIZE_NON_NORMAL {
86 flags: if size == SIZE_NON_NORMAL {
87 Flags::P1_TRACKED | Flags::P2_INFO
87 Flags::P1_TRACKED | Flags::P2_INFO
88 } else if size == SIZE_FROM_OTHER_PARENT {
88 } else if size == SIZE_FROM_OTHER_PARENT {
89 // We don’t know if P1_TRACKED should be set (file history)
89 // We don’t know if P1_TRACKED should be set (file history)
90 Flags::P2_INFO
90 Flags::P2_INFO
91 } else {
91 } else {
92 Flags::P1_TRACKED
92 Flags::P1_TRACKED
93 },
93 },
94 mode_size: None,
94 mode_size: None,
95 mtime: None,
95 mtime: None,
96 },
96 },
97 EntryState::Merged => Self::new_merged(),
97 EntryState::Merged => Self::new_merged(),
98 }
98 }
99 }
99 }
100
100
101 pub fn new_from_p2() -> Self {
101 pub fn new_from_p2() -> Self {
102 Self {
102 Self {
103 // might be missing P1_TRACKED
103 // might be missing P1_TRACKED
104 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
104 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
105 mode_size: None,
105 mode_size: None,
106 mtime: None,
106 mtime: None,
107 }
107 }
108 }
108 }
109
109
110 pub fn new_possibly_dirty() -> Self {
110 pub fn new_possibly_dirty() -> Self {
111 Self {
111 Self {
112 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
112 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
113 mode_size: None,
113 mode_size: None,
114 mtime: None,
114 mtime: None,
115 }
115 }
116 }
116 }
117
117
118 pub fn new_added() -> Self {
118 pub fn new_added() -> Self {
119 Self {
119 Self {
120 flags: Flags::WDIR_TRACKED,
120 flags: Flags::WDIR_TRACKED,
121 mode_size: None,
121 mode_size: None,
122 mtime: None,
122 mtime: None,
123 }
123 }
124 }
124 }
125
125
126 pub fn new_merged() -> Self {
126 pub fn new_merged() -> Self {
127 Self {
127 Self {
128 flags: Flags::WDIR_TRACKED
128 flags: Flags::WDIR_TRACKED
129 | Flags::P1_TRACKED // might not be true because of rename ?
129 | Flags::P1_TRACKED // might not be true because of rename ?
130 | Flags::P2_INFO, // might not be true because of rename ?
130 | Flags::P2_INFO, // might not be true because of rename ?
131 mode_size: None,
131 mode_size: None,
132 mtime: None,
132 mtime: None,
133 }
133 }
134 }
134 }
135
135
136 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
136 pub fn new_normal(mode: i32, size: i32, mtime: i32) -> Self {
137 Self {
137 Self {
138 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
138 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
139 mode_size: Some((mode, size)),
139 mode_size: Some((mode, size)),
140 mtime: Some(mtime),
140 mtime: Some(mtime),
141 }
141 }
142 }
142 }
143
143
144 /// Creates a new entry in "removed" state.
144 /// Creates a new entry in "removed" state.
145 ///
145 ///
146 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
146 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
147 /// `SIZE_FROM_OTHER_PARENT`
147 /// `SIZE_FROM_OTHER_PARENT`
148 pub fn new_removed(size: i32) -> Self {
148 pub fn new_removed(size: i32) -> Self {
149 Self::from_v1_data(EntryState::Removed, 0, size, 0)
149 Self::from_v1_data(EntryState::Removed, 0, size, 0)
150 }
150 }
151
151
152 pub fn tracked(&self) -> bool {
152 pub fn tracked(&self) -> bool {
153 self.flags.contains(Flags::WDIR_TRACKED)
153 self.flags.contains(Flags::WDIR_TRACKED)
154 }
154 }
155
155
156 fn in_either_parent(&self) -> bool {
156 fn in_either_parent(&self) -> bool {
157 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
157 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
158 }
158 }
159
159
160 pub fn removed(&self) -> bool {
160 pub fn removed(&self) -> bool {
161 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
161 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
162 }
162 }
163
163
164 pub fn merged(&self) -> bool {
164 pub fn merged(&self) -> bool {
165 self.flags
165 self.flags
166 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
166 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
167 }
167 }
168
168
169 pub fn added(&self) -> bool {
169 pub fn added(&self) -> bool {
170 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
170 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
171 }
171 }
172
172
173 pub fn from_p2(&self) -> bool {
173 pub fn from_p2(&self) -> bool {
174 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
174 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
175 && !self.flags.contains(Flags::P1_TRACKED)
175 && !self.flags.contains(Flags::P1_TRACKED)
176 }
176 }
177
177
178 pub fn maybe_clean(&self) -> bool {
178 pub fn maybe_clean(&self) -> bool {
179 if !self.flags.contains(Flags::WDIR_TRACKED) {
179 if !self.flags.contains(Flags::WDIR_TRACKED) {
180 false
180 false
181 } else if !self.flags.contains(Flags::P1_TRACKED) {
181 } else if !self.flags.contains(Flags::P1_TRACKED) {
182 false
182 false
183 } else if self.flags.contains(Flags::P2_INFO) {
183 } else if self.flags.contains(Flags::P2_INFO) {
184 false
184 false
185 } else {
185 } else {
186 true
186 true
187 }
187 }
188 }
188 }
189
189
190 pub fn any_tracked(&self) -> bool {
190 pub fn any_tracked(&self) -> bool {
191 self.flags.intersects(
191 self.flags.intersects(
192 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
192 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
193 )
193 )
194 }
194 }
195
195
196 /// Returns `(wdir_tracked, p1_tracked, p2_info, mode_size, mtime)`
197 pub(crate) fn v2_data(
198 &self,
199 ) -> (bool, bool, bool, Option<(i32, i32)>, Option<i32>) {
200 if !self.any_tracked() {
201 // TODO: return an Option instead?
202 panic!("Accessing v1_state of an untracked DirstateEntry")
203 }
204 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
205 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
206 let p2_info = self.flags.contains(Flags::P2_INFO);
207 let mode_size = self.mode_size;
208 let mtime = self.mtime;
209 (wdir_tracked, p1_tracked, p2_info, mode_size, mtime)
210 }
211
196 fn v1_state(&self) -> EntryState {
212 fn v1_state(&self) -> EntryState {
197 if !self.any_tracked() {
213 if !self.any_tracked() {
198 // TODO: return an Option instead?
214 // TODO: return an Option instead?
199 panic!("Accessing v1_state of an untracked DirstateEntry")
215 panic!("Accessing v1_state of an untracked DirstateEntry")
200 }
216 }
201 if self.removed() {
217 if self.removed() {
202 EntryState::Removed
218 EntryState::Removed
203 } else if self.merged() {
219 } else if self.merged() {
204 EntryState::Merged
220 EntryState::Merged
205 } else if self.added() {
221 } else if self.added() {
206 EntryState::Added
222 EntryState::Added
207 } else {
223 } else {
208 EntryState::Normal
224 EntryState::Normal
209 }
225 }
210 }
226 }
211
227
212 fn v1_mode(&self) -> i32 {
228 fn v1_mode(&self) -> i32 {
213 if let Some((mode, _size)) = self.mode_size {
229 if let Some((mode, _size)) = self.mode_size {
214 mode
230 mode
215 } else {
231 } else {
216 0
232 0
217 }
233 }
218 }
234 }
219
235
220 fn v1_size(&self) -> i32 {
236 fn v1_size(&self) -> i32 {
221 if !self.any_tracked() {
237 if !self.any_tracked() {
222 // TODO: return an Option instead?
238 // TODO: return an Option instead?
223 panic!("Accessing v1_size of an untracked DirstateEntry")
239 panic!("Accessing v1_size of an untracked DirstateEntry")
224 }
240 }
225 if self.removed()
241 if self.removed()
226 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
242 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
227 {
243 {
228 SIZE_NON_NORMAL
244 SIZE_NON_NORMAL
229 } else if self.removed() && self.flags.contains(Flags::P2_INFO) {
245 } else if self.removed() && self.flags.contains(Flags::P2_INFO) {
230 SIZE_FROM_OTHER_PARENT
246 SIZE_FROM_OTHER_PARENT
231 } else if self.removed() {
247 } else if self.removed() {
232 0
248 0
233 } else if self.merged() {
249 } else if self.merged() {
234 SIZE_FROM_OTHER_PARENT
250 SIZE_FROM_OTHER_PARENT
235 } else if self.added() {
251 } else if self.added() {
236 SIZE_NON_NORMAL
252 SIZE_NON_NORMAL
237 } else if self.from_p2() {
253 } else if self.from_p2() {
238 SIZE_FROM_OTHER_PARENT
254 SIZE_FROM_OTHER_PARENT
239 } else if let Some((_mode, size)) = self.mode_size {
255 } else if let Some((_mode, size)) = self.mode_size {
240 size
256 size
241 } else {
257 } else {
242 SIZE_NON_NORMAL
258 SIZE_NON_NORMAL
243 }
259 }
244 }
260 }
245
261
246 fn v1_mtime(&self) -> i32 {
262 fn v1_mtime(&self) -> i32 {
247 if !self.any_tracked() {
263 if !self.any_tracked() {
248 // TODO: return an Option instead?
264 // TODO: return an Option instead?
249 panic!("Accessing v1_mtime of an untracked DirstateEntry")
265 panic!("Accessing v1_mtime of an untracked DirstateEntry")
250 }
266 }
251 if self.removed() {
267 if self.removed() {
252 0
268 0
253 } else if self.flags.contains(Flags::P2_INFO) {
269 } else if self.flags.contains(Flags::P2_INFO) {
254 MTIME_UNSET
270 MTIME_UNSET
255 } else if !self.flags.contains(Flags::P1_TRACKED) {
271 } else if !self.flags.contains(Flags::P1_TRACKED) {
256 MTIME_UNSET
272 MTIME_UNSET
257 } else {
273 } else {
258 self.mtime.unwrap_or(MTIME_UNSET)
274 self.mtime.unwrap_or(MTIME_UNSET)
259 }
275 }
260 }
276 }
261
277
262 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
278 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
263 pub fn state(&self) -> EntryState {
279 pub fn state(&self) -> EntryState {
264 self.v1_state()
280 self.v1_state()
265 }
281 }
266
282
267 // TODO: return Option?
283 // TODO: return Option?
268 pub fn mode(&self) -> i32 {
284 pub fn mode(&self) -> i32 {
269 self.v1_mode()
285 self.v1_mode()
270 }
286 }
271
287
272 // TODO: return Option?
288 // TODO: return Option?
273 pub fn size(&self) -> i32 {
289 pub fn size(&self) -> i32 {
274 self.v1_size()
290 self.v1_size()
275 }
291 }
276
292
277 // TODO: return Option?
293 // TODO: return Option?
278 pub fn mtime(&self) -> i32 {
294 pub fn mtime(&self) -> i32 {
279 self.v1_mtime()
295 self.v1_mtime()
280 }
296 }
281
297
282 pub fn drop_merge_data(&mut self) {
298 pub fn drop_merge_data(&mut self) {
283 if self.flags.contains(Flags::P2_INFO) {
299 if self.flags.contains(Flags::P2_INFO) {
284 self.flags.remove(Flags::P2_INFO);
300 self.flags.remove(Flags::P2_INFO);
285 self.mode_size = None;
301 self.mode_size = None;
286 self.mtime = None;
302 self.mtime = None;
287 }
303 }
288 }
304 }
289
305
290 pub fn set_possibly_dirty(&mut self) {
306 pub fn set_possibly_dirty(&mut self) {
291 self.mtime = None
307 self.mtime = None
292 }
308 }
293
309
294 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
310 pub fn set_clean(&mut self, mode: i32, size: i32, mtime: i32) {
295 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
311 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
296 self.mode_size = Some((mode, size));
312 self.mode_size = Some((mode, size));
297 self.mtime = Some(mtime);
313 self.mtime = Some(mtime);
298 }
314 }
299
315
300 pub fn set_tracked(&mut self) {
316 pub fn set_tracked(&mut self) {
301 self.flags.insert(Flags::WDIR_TRACKED);
317 self.flags.insert(Flags::WDIR_TRACKED);
302 // `set_tracked` is replacing various `normallookup` call. So we mark
318 // `set_tracked` is replacing various `normallookup` call. So we mark
303 // the files as needing lookup
319 // the files as needing lookup
304 //
320 //
305 // Consider dropping this in the future in favor of something less
321 // Consider dropping this in the future in favor of something less
306 // broad.
322 // broad.
307 self.mtime = None;
323 self.mtime = None;
308 }
324 }
309
325
310 pub fn set_untracked(&mut self) {
326 pub fn set_untracked(&mut self) {
311 self.flags.remove(Flags::WDIR_TRACKED);
327 self.flags.remove(Flags::WDIR_TRACKED);
312 self.mode_size = None;
328 self.mode_size = None;
313 self.mtime = None;
329 self.mtime = None;
314 }
330 }
315
331
316 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
332 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
317 /// in the dirstate-v1 format.
333 /// in the dirstate-v1 format.
318 ///
334 ///
319 /// This includes marker values such as `mtime == -1`. In the future we may
335 /// This includes marker values such as `mtime == -1`. In the future we may
320 /// want to not represent these cases that way in memory, but serialization
336 /// want to not represent these cases that way in memory, but serialization
321 /// will need to keep the same format.
337 /// will need to keep the same format.
322 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
338 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
323 (
339 (
324 self.v1_state().into(),
340 self.v1_state().into(),
325 self.v1_mode(),
341 self.v1_mode(),
326 self.v1_size(),
342 self.v1_size(),
327 self.v1_mtime(),
343 self.v1_mtime(),
328 )
344 )
329 }
345 }
330
346
331 pub(crate) fn is_from_other_parent(&self) -> bool {
347 pub(crate) fn is_from_other_parent(&self) -> bool {
332 self.state() == EntryState::Normal
348 self.state() == EntryState::Normal
333 && self.size() == SIZE_FROM_OTHER_PARENT
349 && self.size() == SIZE_FROM_OTHER_PARENT
334 }
350 }
335
351
336 // TODO: other platforms
352 // TODO: other platforms
337 #[cfg(unix)]
353 #[cfg(unix)]
338 pub fn mode_changed(
354 pub fn mode_changed(
339 &self,
355 &self,
340 filesystem_metadata: &std::fs::Metadata,
356 filesystem_metadata: &std::fs::Metadata,
341 ) -> bool {
357 ) -> bool {
342 use std::os::unix::fs::MetadataExt;
358 use std::os::unix::fs::MetadataExt;
343 const EXEC_BIT_MASK: u32 = 0o100;
359 const EXEC_BIT_MASK: u32 = 0o100;
344 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
360 let dirstate_exec_bit = (self.mode() as u32) & EXEC_BIT_MASK;
345 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
361 let fs_exec_bit = filesystem_metadata.mode() & EXEC_BIT_MASK;
346 dirstate_exec_bit != fs_exec_bit
362 dirstate_exec_bit != fs_exec_bit
347 }
363 }
348
364
349 /// Returns a `(state, mode, size, mtime)` tuple as for
365 /// Returns a `(state, mode, size, mtime)` tuple as for
350 /// `DirstateMapMethods::debug_iter`.
366 /// `DirstateMapMethods::debug_iter`.
351 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
367 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
352 (self.state().into(), self.mode(), self.size(), self.mtime())
368 (self.state().into(), self.mode(), self.size(), self.mtime())
353 }
369 }
354
370
355 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
371 pub fn mtime_is_ambiguous(&self, now: i32) -> bool {
356 self.state() == EntryState::Normal && self.mtime() == now
372 self.state() == EntryState::Normal && self.mtime() == now
357 }
373 }
358
374
359 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
375 pub fn clear_ambiguous_mtime(&mut self, now: i32) -> bool {
360 let ambiguous = self.mtime_is_ambiguous(now);
376 let ambiguous = self.mtime_is_ambiguous(now);
361 if ambiguous {
377 if ambiguous {
362 // The file was last modified "simultaneously" with the current
378 // The file was last modified "simultaneously" with the current
363 // write to dirstate (i.e. within the same second for file-
379 // write to dirstate (i.e. within the same second for file-
364 // systems with a granularity of 1 sec). This commonly happens
380 // systems with a granularity of 1 sec). This commonly happens
365 // for at least a couple of files on 'update'.
381 // for at least a couple of files on 'update'.
366 // The user could change the file without changing its size
382 // The user could change the file without changing its size
367 // within the same second. Invalidate the file's mtime in
383 // within the same second. Invalidate the file's mtime in
368 // dirstate, forcing future 'status' calls to compare the
384 // dirstate, forcing future 'status' calls to compare the
369 // contents of the file if the size is the same. This prevents
385 // contents of the file if the size is the same. This prevents
370 // mistakenly treating such files as clean.
386 // mistakenly treating such files as clean.
371 self.set_possibly_dirty()
387 self.set_possibly_dirty()
372 }
388 }
373 ambiguous
389 ambiguous
374 }
390 }
375 }
391 }
376
392
377 impl EntryState {
393 impl EntryState {
378 pub fn is_tracked(self) -> bool {
394 pub fn is_tracked(self) -> bool {
379 use EntryState::*;
395 use EntryState::*;
380 match self {
396 match self {
381 Normal | Added | Merged => true,
397 Normal | Added | Merged => true,
382 Removed => false,
398 Removed => false,
383 }
399 }
384 }
400 }
385 }
401 }
386
402
387 impl TryFrom<u8> for EntryState {
403 impl TryFrom<u8> for EntryState {
388 type Error = HgError;
404 type Error = HgError;
389
405
390 fn try_from(value: u8) -> Result<Self, Self::Error> {
406 fn try_from(value: u8) -> Result<Self, Self::Error> {
391 match value {
407 match value {
392 b'n' => Ok(EntryState::Normal),
408 b'n' => Ok(EntryState::Normal),
393 b'a' => Ok(EntryState::Added),
409 b'a' => Ok(EntryState::Added),
394 b'r' => Ok(EntryState::Removed),
410 b'r' => Ok(EntryState::Removed),
395 b'm' => Ok(EntryState::Merged),
411 b'm' => Ok(EntryState::Merged),
396 _ => Err(HgError::CorruptedRepository(format!(
412 _ => Err(HgError::CorruptedRepository(format!(
397 "Incorrect dirstate entry state {}",
413 "Incorrect dirstate entry state {}",
398 value
414 value
399 ))),
415 ))),
400 }
416 }
401 }
417 }
402 }
418 }
403
419
404 impl Into<u8> for EntryState {
420 impl Into<u8> for EntryState {
405 fn into(self) -> u8 {
421 fn into(self) -> u8 {
406 match self {
422 match self {
407 EntryState::Normal => b'n',
423 EntryState::Normal => b'n',
408 EntryState::Added => b'a',
424 EntryState::Added => b'a',
409 EntryState::Removed => b'r',
425 EntryState::Removed => b'r',
410 EntryState::Merged => b'm',
426 EntryState::Merged => b'm',
411 }
427 }
412 }
428 }
413 }
429 }
@@ -1,1193 +1,1188 b''
1 use bytes_cast::BytesCast;
1 use bytes_cast::BytesCast;
2 use micro_timer::timed;
2 use micro_timer::timed;
3 use std::borrow::Cow;
3 use std::borrow::Cow;
4 use std::convert::TryInto;
4 use std::convert::TryInto;
5 use std::path::PathBuf;
5 use std::path::PathBuf;
6
6
7 use super::on_disk;
7 use super::on_disk;
8 use super::on_disk::DirstateV2ParseError;
8 use super::on_disk::DirstateV2ParseError;
9 use super::owning::OwningDirstateMap;
9 use super::owning::OwningDirstateMap;
10 use super::path_with_basename::WithBasename;
10 use super::path_with_basename::WithBasename;
11 use crate::dirstate::parsers::pack_entry;
11 use crate::dirstate::parsers::pack_entry;
12 use crate::dirstate::parsers::packed_entry_size;
12 use crate::dirstate::parsers::packed_entry_size;
13 use crate::dirstate::parsers::parse_dirstate_entries;
13 use crate::dirstate::parsers::parse_dirstate_entries;
14 use crate::dirstate::parsers::Timestamp;
14 use crate::dirstate::parsers::Timestamp;
15 use crate::dirstate::CopyMapIter;
15 use crate::dirstate::CopyMapIter;
16 use crate::dirstate::StateMapIter;
16 use crate::dirstate::StateMapIter;
17 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
17 use crate::dirstate::SIZE_FROM_OTHER_PARENT;
18 use crate::dirstate::SIZE_NON_NORMAL;
18 use crate::dirstate::SIZE_NON_NORMAL;
19 use crate::matchers::Matcher;
19 use crate::matchers::Matcher;
20 use crate::utils::hg_path::{HgPath, HgPathBuf};
20 use crate::utils::hg_path::{HgPath, HgPathBuf};
21 use crate::DirstateEntry;
21 use crate::DirstateEntry;
22 use crate::DirstateError;
22 use crate::DirstateError;
23 use crate::DirstateParents;
23 use crate::DirstateParents;
24 use crate::DirstateStatus;
24 use crate::DirstateStatus;
25 use crate::EntryState;
25 use crate::EntryState;
26 use crate::FastHashMap;
26 use crate::FastHashMap;
27 use crate::PatternFileWarning;
27 use crate::PatternFileWarning;
28 use crate::StatusError;
28 use crate::StatusError;
29 use crate::StatusOptions;
29 use crate::StatusOptions;
30
30
31 /// Append to an existing data file if the amount of unreachable data (not used
31 /// Append to an existing data file if the amount of unreachable data (not used
32 /// anymore) is less than this fraction of the total amount of existing data.
32 /// anymore) is less than this fraction of the total amount of existing data.
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
33 const ACCEPTABLE_UNREACHABLE_BYTES_RATIO: f32 = 0.5;
34
34
35 pub struct DirstateMap<'on_disk> {
35 pub struct DirstateMap<'on_disk> {
36 /// Contents of the `.hg/dirstate` file
36 /// Contents of the `.hg/dirstate` file
37 pub(super) on_disk: &'on_disk [u8],
37 pub(super) on_disk: &'on_disk [u8],
38
38
39 pub(super) root: ChildNodes<'on_disk>,
39 pub(super) root: ChildNodes<'on_disk>,
40
40
41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
41 /// Number of nodes anywhere in the tree that have `.entry.is_some()`.
42 pub(super) nodes_with_entry_count: u32,
42 pub(super) nodes_with_entry_count: u32,
43
43
44 /// Number of nodes anywhere in the tree that have
44 /// Number of nodes anywhere in the tree that have
45 /// `.copy_source.is_some()`.
45 /// `.copy_source.is_some()`.
46 pub(super) nodes_with_copy_source_count: u32,
46 pub(super) nodes_with_copy_source_count: u32,
47
47
48 /// See on_disk::Header
48 /// See on_disk::Header
49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
49 pub(super) ignore_patterns_hash: on_disk::IgnorePatternsHash,
50
50
51 /// How many bytes of `on_disk` are not used anymore
51 /// How many bytes of `on_disk` are not used anymore
52 pub(super) unreachable_bytes: u32,
52 pub(super) unreachable_bytes: u32,
53 }
53 }
54
54
55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
55 /// Using a plain `HgPathBuf` of the full path from the repository root as a
56 /// map key would also work: all paths in a given map have the same parent
56 /// map key would also work: all paths in a given map have the same parent
57 /// path, so comparing full paths gives the same result as comparing base
57 /// path, so comparing full paths gives the same result as comparing base
58 /// names. However `HashMap` would waste time always re-hashing the same
58 /// names. However `HashMap` would waste time always re-hashing the same
59 /// string prefix.
59 /// string prefix.
60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
60 pub(super) type NodeKey<'on_disk> = WithBasename<Cow<'on_disk, HgPath>>;
61
61
62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
62 /// Similar to `&'tree Cow<'on_disk, HgPath>`, but can also be returned
63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
63 /// for on-disk nodes that don’t actually have a `Cow` to borrow.
64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
64 pub(super) enum BorrowedPath<'tree, 'on_disk> {
65 InMemory(&'tree HgPathBuf),
65 InMemory(&'tree HgPathBuf),
66 OnDisk(&'on_disk HgPath),
66 OnDisk(&'on_disk HgPath),
67 }
67 }
68
68
69 pub(super) enum ChildNodes<'on_disk> {
69 pub(super) enum ChildNodes<'on_disk> {
70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
70 InMemory(FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
71 OnDisk(&'on_disk [on_disk::Node]),
71 OnDisk(&'on_disk [on_disk::Node]),
72 }
72 }
73
73
74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
74 pub(super) enum ChildNodesRef<'tree, 'on_disk> {
75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
75 InMemory(&'tree FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>),
76 OnDisk(&'on_disk [on_disk::Node]),
76 OnDisk(&'on_disk [on_disk::Node]),
77 }
77 }
78
78
79 pub(super) enum NodeRef<'tree, 'on_disk> {
79 pub(super) enum NodeRef<'tree, 'on_disk> {
80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
80 InMemory(&'tree NodeKey<'on_disk>, &'tree Node<'on_disk>),
81 OnDisk(&'on_disk on_disk::Node),
81 OnDisk(&'on_disk on_disk::Node),
82 }
82 }
83
83
84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
84 impl<'tree, 'on_disk> BorrowedPath<'tree, 'on_disk> {
85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
85 pub fn detach_from_tree(&self) -> Cow<'on_disk, HgPath> {
86 match *self {
86 match *self {
87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
87 BorrowedPath::InMemory(in_memory) => Cow::Owned(in_memory.clone()),
88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
88 BorrowedPath::OnDisk(on_disk) => Cow::Borrowed(on_disk),
89 }
89 }
90 }
90 }
91 }
91 }
92
92
93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
93 impl<'tree, 'on_disk> std::ops::Deref for BorrowedPath<'tree, 'on_disk> {
94 type Target = HgPath;
94 type Target = HgPath;
95
95
96 fn deref(&self) -> &HgPath {
96 fn deref(&self) -> &HgPath {
97 match *self {
97 match *self {
98 BorrowedPath::InMemory(in_memory) => in_memory,
98 BorrowedPath::InMemory(in_memory) => in_memory,
99 BorrowedPath::OnDisk(on_disk) => on_disk,
99 BorrowedPath::OnDisk(on_disk) => on_disk,
100 }
100 }
101 }
101 }
102 }
102 }
103
103
104 impl Default for ChildNodes<'_> {
104 impl Default for ChildNodes<'_> {
105 fn default() -> Self {
105 fn default() -> Self {
106 ChildNodes::InMemory(Default::default())
106 ChildNodes::InMemory(Default::default())
107 }
107 }
108 }
108 }
109
109
110 impl<'on_disk> ChildNodes<'on_disk> {
110 impl<'on_disk> ChildNodes<'on_disk> {
111 pub(super) fn as_ref<'tree>(
111 pub(super) fn as_ref<'tree>(
112 &'tree self,
112 &'tree self,
113 ) -> ChildNodesRef<'tree, 'on_disk> {
113 ) -> ChildNodesRef<'tree, 'on_disk> {
114 match self {
114 match self {
115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
115 ChildNodes::InMemory(nodes) => ChildNodesRef::InMemory(nodes),
116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
116 ChildNodes::OnDisk(nodes) => ChildNodesRef::OnDisk(nodes),
117 }
117 }
118 }
118 }
119
119
120 pub(super) fn is_empty(&self) -> bool {
120 pub(super) fn is_empty(&self) -> bool {
121 match self {
121 match self {
122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
122 ChildNodes::InMemory(nodes) => nodes.is_empty(),
123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
123 ChildNodes::OnDisk(nodes) => nodes.is_empty(),
124 }
124 }
125 }
125 }
126
126
127 fn make_mut(
127 fn make_mut(
128 &mut self,
128 &mut self,
129 on_disk: &'on_disk [u8],
129 on_disk: &'on_disk [u8],
130 unreachable_bytes: &mut u32,
130 unreachable_bytes: &mut u32,
131 ) -> Result<
131 ) -> Result<
132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
132 &mut FastHashMap<NodeKey<'on_disk>, Node<'on_disk>>,
133 DirstateV2ParseError,
133 DirstateV2ParseError,
134 > {
134 > {
135 match self {
135 match self {
136 ChildNodes::InMemory(nodes) => Ok(nodes),
136 ChildNodes::InMemory(nodes) => Ok(nodes),
137 ChildNodes::OnDisk(nodes) => {
137 ChildNodes::OnDisk(nodes) => {
138 *unreachable_bytes +=
138 *unreachable_bytes +=
139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
139 std::mem::size_of_val::<[on_disk::Node]>(nodes) as u32;
140 let nodes = nodes
140 let nodes = nodes
141 .iter()
141 .iter()
142 .map(|node| {
142 .map(|node| {
143 Ok((
143 Ok((
144 node.path(on_disk)?,
144 node.path(on_disk)?,
145 node.to_in_memory_node(on_disk)?,
145 node.to_in_memory_node(on_disk)?,
146 ))
146 ))
147 })
147 })
148 .collect::<Result<_, _>>()?;
148 .collect::<Result<_, _>>()?;
149 *self = ChildNodes::InMemory(nodes);
149 *self = ChildNodes::InMemory(nodes);
150 match self {
150 match self {
151 ChildNodes::InMemory(nodes) => Ok(nodes),
151 ChildNodes::InMemory(nodes) => Ok(nodes),
152 ChildNodes::OnDisk(_) => unreachable!(),
152 ChildNodes::OnDisk(_) => unreachable!(),
153 }
153 }
154 }
154 }
155 }
155 }
156 }
156 }
157 }
157 }
158
158
159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
159 impl<'tree, 'on_disk> ChildNodesRef<'tree, 'on_disk> {
160 pub(super) fn get(
160 pub(super) fn get(
161 &self,
161 &self,
162 base_name: &HgPath,
162 base_name: &HgPath,
163 on_disk: &'on_disk [u8],
163 on_disk: &'on_disk [u8],
164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
164 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
165 match self {
165 match self {
166 ChildNodesRef::InMemory(nodes) => Ok(nodes
166 ChildNodesRef::InMemory(nodes) => Ok(nodes
167 .get_key_value(base_name)
167 .get_key_value(base_name)
168 .map(|(k, v)| NodeRef::InMemory(k, v))),
168 .map(|(k, v)| NodeRef::InMemory(k, v))),
169 ChildNodesRef::OnDisk(nodes) => {
169 ChildNodesRef::OnDisk(nodes) => {
170 let mut parse_result = Ok(());
170 let mut parse_result = Ok(());
171 let search_result = nodes.binary_search_by(|node| {
171 let search_result = nodes.binary_search_by(|node| {
172 match node.base_name(on_disk) {
172 match node.base_name(on_disk) {
173 Ok(node_base_name) => node_base_name.cmp(base_name),
173 Ok(node_base_name) => node_base_name.cmp(base_name),
174 Err(e) => {
174 Err(e) => {
175 parse_result = Err(e);
175 parse_result = Err(e);
176 // Dummy comparison result, `search_result` won’t
176 // Dummy comparison result, `search_result` won’t
177 // be used since `parse_result` is an error
177 // be used since `parse_result` is an error
178 std::cmp::Ordering::Equal
178 std::cmp::Ordering::Equal
179 }
179 }
180 }
180 }
181 });
181 });
182 parse_result.map(|()| {
182 parse_result.map(|()| {
183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
183 search_result.ok().map(|i| NodeRef::OnDisk(&nodes[i]))
184 })
184 })
185 }
185 }
186 }
186 }
187 }
187 }
188
188
189 /// Iterate in undefined order
189 /// Iterate in undefined order
190 pub(super) fn iter(
190 pub(super) fn iter(
191 &self,
191 &self,
192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
192 ) -> impl Iterator<Item = NodeRef<'tree, 'on_disk>> {
193 match self {
193 match self {
194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
194 ChildNodesRef::InMemory(nodes) => itertools::Either::Left(
195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
195 nodes.iter().map(|(k, v)| NodeRef::InMemory(k, v)),
196 ),
196 ),
197 ChildNodesRef::OnDisk(nodes) => {
197 ChildNodesRef::OnDisk(nodes) => {
198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
198 itertools::Either::Right(nodes.iter().map(NodeRef::OnDisk))
199 }
199 }
200 }
200 }
201 }
201 }
202
202
203 /// Iterate in parallel in undefined order
203 /// Iterate in parallel in undefined order
204 pub(super) fn par_iter(
204 pub(super) fn par_iter(
205 &self,
205 &self,
206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
206 ) -> impl rayon::iter::ParallelIterator<Item = NodeRef<'tree, 'on_disk>>
207 {
207 {
208 use rayon::prelude::*;
208 use rayon::prelude::*;
209 match self {
209 match self {
210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
210 ChildNodesRef::InMemory(nodes) => rayon::iter::Either::Left(
211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
211 nodes.par_iter().map(|(k, v)| NodeRef::InMemory(k, v)),
212 ),
212 ),
213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
213 ChildNodesRef::OnDisk(nodes) => rayon::iter::Either::Right(
214 nodes.par_iter().map(NodeRef::OnDisk),
214 nodes.par_iter().map(NodeRef::OnDisk),
215 ),
215 ),
216 }
216 }
217 }
217 }
218
218
219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
219 pub(super) fn sorted(&self) -> Vec<NodeRef<'tree, 'on_disk>> {
220 match self {
220 match self {
221 ChildNodesRef::InMemory(nodes) => {
221 ChildNodesRef::InMemory(nodes) => {
222 let mut vec: Vec<_> = nodes
222 let mut vec: Vec<_> = nodes
223 .iter()
223 .iter()
224 .map(|(k, v)| NodeRef::InMemory(k, v))
224 .map(|(k, v)| NodeRef::InMemory(k, v))
225 .collect();
225 .collect();
226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
226 fn sort_key<'a>(node: &'a NodeRef) -> &'a HgPath {
227 match node {
227 match node {
228 NodeRef::InMemory(path, _node) => path.base_name(),
228 NodeRef::InMemory(path, _node) => path.base_name(),
229 NodeRef::OnDisk(_) => unreachable!(),
229 NodeRef::OnDisk(_) => unreachable!(),
230 }
230 }
231 }
231 }
232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
232 // `sort_unstable_by_key` doesn’t allow keys borrowing from the
233 // value: https://github.com/rust-lang/rust/issues/34162
233 // value: https://github.com/rust-lang/rust/issues/34162
234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
234 vec.sort_unstable_by(|a, b| sort_key(a).cmp(sort_key(b)));
235 vec
235 vec
236 }
236 }
237 ChildNodesRef::OnDisk(nodes) => {
237 ChildNodesRef::OnDisk(nodes) => {
238 // Nodes on disk are already sorted
238 // Nodes on disk are already sorted
239 nodes.iter().map(NodeRef::OnDisk).collect()
239 nodes.iter().map(NodeRef::OnDisk).collect()
240 }
240 }
241 }
241 }
242 }
242 }
243 }
243 }
244
244
245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
245 impl<'tree, 'on_disk> NodeRef<'tree, 'on_disk> {
246 pub(super) fn full_path(
246 pub(super) fn full_path(
247 &self,
247 &self,
248 on_disk: &'on_disk [u8],
248 on_disk: &'on_disk [u8],
249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
249 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
250 match self {
250 match self {
251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
251 NodeRef::InMemory(path, _node) => Ok(path.full_path()),
252 NodeRef::OnDisk(node) => node.full_path(on_disk),
252 NodeRef::OnDisk(node) => node.full_path(on_disk),
253 }
253 }
254 }
254 }
255
255
256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
256 /// Returns a `BorrowedPath`, which can be turned into a `Cow<'on_disk,
257 /// HgPath>` detached from `'tree`
257 /// HgPath>` detached from `'tree`
258 pub(super) fn full_path_borrowed(
258 pub(super) fn full_path_borrowed(
259 &self,
259 &self,
260 on_disk: &'on_disk [u8],
260 on_disk: &'on_disk [u8],
261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
261 ) -> Result<BorrowedPath<'tree, 'on_disk>, DirstateV2ParseError> {
262 match self {
262 match self {
263 NodeRef::InMemory(path, _node) => match path.full_path() {
263 NodeRef::InMemory(path, _node) => match path.full_path() {
264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
264 Cow::Borrowed(on_disk) => Ok(BorrowedPath::OnDisk(on_disk)),
265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
265 Cow::Owned(in_memory) => Ok(BorrowedPath::InMemory(in_memory)),
266 },
266 },
267 NodeRef::OnDisk(node) => {
267 NodeRef::OnDisk(node) => {
268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
268 Ok(BorrowedPath::OnDisk(node.full_path(on_disk)?))
269 }
269 }
270 }
270 }
271 }
271 }
272
272
273 pub(super) fn base_name(
273 pub(super) fn base_name(
274 &self,
274 &self,
275 on_disk: &'on_disk [u8],
275 on_disk: &'on_disk [u8],
276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
276 ) -> Result<&'tree HgPath, DirstateV2ParseError> {
277 match self {
277 match self {
278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
278 NodeRef::InMemory(path, _node) => Ok(path.base_name()),
279 NodeRef::OnDisk(node) => node.base_name(on_disk),
279 NodeRef::OnDisk(node) => node.base_name(on_disk),
280 }
280 }
281 }
281 }
282
282
283 pub(super) fn children(
283 pub(super) fn children(
284 &self,
284 &self,
285 on_disk: &'on_disk [u8],
285 on_disk: &'on_disk [u8],
286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
286 ) -> Result<ChildNodesRef<'tree, 'on_disk>, DirstateV2ParseError> {
287 match self {
287 match self {
288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
288 NodeRef::InMemory(_path, node) => Ok(node.children.as_ref()),
289 NodeRef::OnDisk(node) => {
289 NodeRef::OnDisk(node) => {
290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
290 Ok(ChildNodesRef::OnDisk(node.children(on_disk)?))
291 }
291 }
292 }
292 }
293 }
293 }
294
294
295 pub(super) fn has_copy_source(&self) -> bool {
295 pub(super) fn has_copy_source(&self) -> bool {
296 match self {
296 match self {
297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
297 NodeRef::InMemory(_path, node) => node.copy_source.is_some(),
298 NodeRef::OnDisk(node) => node.has_copy_source(),
298 NodeRef::OnDisk(node) => node.has_copy_source(),
299 }
299 }
300 }
300 }
301
301
302 pub(super) fn copy_source(
302 pub(super) fn copy_source(
303 &self,
303 &self,
304 on_disk: &'on_disk [u8],
304 on_disk: &'on_disk [u8],
305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
305 ) -> Result<Option<&'tree HgPath>, DirstateV2ParseError> {
306 match self {
306 match self {
307 NodeRef::InMemory(_path, node) => {
307 NodeRef::InMemory(_path, node) => {
308 Ok(node.copy_source.as_ref().map(|s| &**s))
308 Ok(node.copy_source.as_ref().map(|s| &**s))
309 }
309 }
310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
310 NodeRef::OnDisk(node) => node.copy_source(on_disk),
311 }
311 }
312 }
312 }
313
313
314 pub(super) fn entry(
314 pub(super) fn entry(
315 &self,
315 &self,
316 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
316 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
317 match self {
317 match self {
318 NodeRef::InMemory(_path, node) => {
318 NodeRef::InMemory(_path, node) => {
319 Ok(node.data.as_entry().copied())
319 Ok(node.data.as_entry().copied())
320 }
320 }
321 NodeRef::OnDisk(node) => node.entry(),
321 NodeRef::OnDisk(node) => node.entry(),
322 }
322 }
323 }
323 }
324
324
325 pub(super) fn state(
325 pub(super) fn state(
326 &self,
326 &self,
327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
327 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
328 match self {
328 Ok(self.entry()?.map(|e| e.state()))
329 NodeRef::InMemory(_path, node) => {
330 Ok(node.data.as_entry().map(|entry| entry.state()))
331 }
332 NodeRef::OnDisk(node) => node.state(),
333 }
334 }
329 }
335
330
336 pub(super) fn cached_directory_mtime(
331 pub(super) fn cached_directory_mtime(
337 &self,
332 &self,
338 ) -> Option<&'tree on_disk::Timestamp> {
333 ) -> Option<&'tree on_disk::Timestamp> {
339 match self {
334 match self {
340 NodeRef::InMemory(_path, node) => match &node.data {
335 NodeRef::InMemory(_path, node) => match &node.data {
341 NodeData::CachedDirectory { mtime } => Some(mtime),
336 NodeData::CachedDirectory { mtime } => Some(mtime),
342 _ => None,
337 _ => None,
343 },
338 },
344 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
339 NodeRef::OnDisk(node) => node.cached_directory_mtime(),
345 }
340 }
346 }
341 }
347
342
348 pub(super) fn descendants_with_entry_count(&self) -> u32 {
343 pub(super) fn descendants_with_entry_count(&self) -> u32 {
349 match self {
344 match self {
350 NodeRef::InMemory(_path, node) => {
345 NodeRef::InMemory(_path, node) => {
351 node.descendants_with_entry_count
346 node.descendants_with_entry_count
352 }
347 }
353 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
348 NodeRef::OnDisk(node) => node.descendants_with_entry_count.get(),
354 }
349 }
355 }
350 }
356
351
357 pub(super) fn tracked_descendants_count(&self) -> u32 {
352 pub(super) fn tracked_descendants_count(&self) -> u32 {
358 match self {
353 match self {
359 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
354 NodeRef::InMemory(_path, node) => node.tracked_descendants_count,
360 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
355 NodeRef::OnDisk(node) => node.tracked_descendants_count.get(),
361 }
356 }
362 }
357 }
363 }
358 }
364
359
365 /// Represents a file or a directory
360 /// Represents a file or a directory
366 #[derive(Default)]
361 #[derive(Default)]
367 pub(super) struct Node<'on_disk> {
362 pub(super) struct Node<'on_disk> {
368 pub(super) data: NodeData,
363 pub(super) data: NodeData,
369
364
370 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
365 pub(super) copy_source: Option<Cow<'on_disk, HgPath>>,
371
366
372 pub(super) children: ChildNodes<'on_disk>,
367 pub(super) children: ChildNodes<'on_disk>,
373
368
374 /// How many (non-inclusive) descendants of this node have an entry.
369 /// How many (non-inclusive) descendants of this node have an entry.
375 pub(super) descendants_with_entry_count: u32,
370 pub(super) descendants_with_entry_count: u32,
376
371
377 /// How many (non-inclusive) descendants of this node have an entry whose
372 /// How many (non-inclusive) descendants of this node have an entry whose
378 /// state is "tracked".
373 /// state is "tracked".
379 pub(super) tracked_descendants_count: u32,
374 pub(super) tracked_descendants_count: u32,
380 }
375 }
381
376
382 pub(super) enum NodeData {
377 pub(super) enum NodeData {
383 Entry(DirstateEntry),
378 Entry(DirstateEntry),
384 CachedDirectory { mtime: on_disk::Timestamp },
379 CachedDirectory { mtime: on_disk::Timestamp },
385 None,
380 None,
386 }
381 }
387
382
388 impl Default for NodeData {
383 impl Default for NodeData {
389 fn default() -> Self {
384 fn default() -> Self {
390 NodeData::None
385 NodeData::None
391 }
386 }
392 }
387 }
393
388
394 impl NodeData {
389 impl NodeData {
395 fn has_entry(&self) -> bool {
390 fn has_entry(&self) -> bool {
396 match self {
391 match self {
397 NodeData::Entry(_) => true,
392 NodeData::Entry(_) => true,
398 _ => false,
393 _ => false,
399 }
394 }
400 }
395 }
401
396
402 fn as_entry(&self) -> Option<&DirstateEntry> {
397 fn as_entry(&self) -> Option<&DirstateEntry> {
403 match self {
398 match self {
404 NodeData::Entry(entry) => Some(entry),
399 NodeData::Entry(entry) => Some(entry),
405 _ => None,
400 _ => None,
406 }
401 }
407 }
402 }
408 }
403 }
409
404
410 impl<'on_disk> DirstateMap<'on_disk> {
405 impl<'on_disk> DirstateMap<'on_disk> {
411 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
406 pub(super) fn empty(on_disk: &'on_disk [u8]) -> Self {
412 Self {
407 Self {
413 on_disk,
408 on_disk,
414 root: ChildNodes::default(),
409 root: ChildNodes::default(),
415 nodes_with_entry_count: 0,
410 nodes_with_entry_count: 0,
416 nodes_with_copy_source_count: 0,
411 nodes_with_copy_source_count: 0,
417 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
412 ignore_patterns_hash: [0; on_disk::IGNORE_PATTERNS_HASH_LEN],
418 unreachable_bytes: 0,
413 unreachable_bytes: 0,
419 }
414 }
420 }
415 }
421
416
422 #[timed]
417 #[timed]
423 pub fn new_v2(
418 pub fn new_v2(
424 on_disk: &'on_disk [u8],
419 on_disk: &'on_disk [u8],
425 data_size: usize,
420 data_size: usize,
426 metadata: &[u8],
421 metadata: &[u8],
427 ) -> Result<Self, DirstateError> {
422 ) -> Result<Self, DirstateError> {
428 if let Some(data) = on_disk.get(..data_size) {
423 if let Some(data) = on_disk.get(..data_size) {
429 Ok(on_disk::read(data, metadata)?)
424 Ok(on_disk::read(data, metadata)?)
430 } else {
425 } else {
431 Err(DirstateV2ParseError.into())
426 Err(DirstateV2ParseError.into())
432 }
427 }
433 }
428 }
434
429
435 #[timed]
430 #[timed]
436 pub fn new_v1(
431 pub fn new_v1(
437 on_disk: &'on_disk [u8],
432 on_disk: &'on_disk [u8],
438 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
433 ) -> Result<(Self, Option<DirstateParents>), DirstateError> {
439 let mut map = Self::empty(on_disk);
434 let mut map = Self::empty(on_disk);
440 if map.on_disk.is_empty() {
435 if map.on_disk.is_empty() {
441 return Ok((map, None));
436 return Ok((map, None));
442 }
437 }
443
438
444 let parents = parse_dirstate_entries(
439 let parents = parse_dirstate_entries(
445 map.on_disk,
440 map.on_disk,
446 |path, entry, copy_source| {
441 |path, entry, copy_source| {
447 let tracked = entry.state().is_tracked();
442 let tracked = entry.state().is_tracked();
448 let node = Self::get_or_insert_node(
443 let node = Self::get_or_insert_node(
449 map.on_disk,
444 map.on_disk,
450 &mut map.unreachable_bytes,
445 &mut map.unreachable_bytes,
451 &mut map.root,
446 &mut map.root,
452 path,
447 path,
453 WithBasename::to_cow_borrowed,
448 WithBasename::to_cow_borrowed,
454 |ancestor| {
449 |ancestor| {
455 if tracked {
450 if tracked {
456 ancestor.tracked_descendants_count += 1
451 ancestor.tracked_descendants_count += 1
457 }
452 }
458 ancestor.descendants_with_entry_count += 1
453 ancestor.descendants_with_entry_count += 1
459 },
454 },
460 )?;
455 )?;
461 assert!(
456 assert!(
462 !node.data.has_entry(),
457 !node.data.has_entry(),
463 "duplicate dirstate entry in read"
458 "duplicate dirstate entry in read"
464 );
459 );
465 assert!(
460 assert!(
466 node.copy_source.is_none(),
461 node.copy_source.is_none(),
467 "duplicate dirstate entry in read"
462 "duplicate dirstate entry in read"
468 );
463 );
469 node.data = NodeData::Entry(*entry);
464 node.data = NodeData::Entry(*entry);
470 node.copy_source = copy_source.map(Cow::Borrowed);
465 node.copy_source = copy_source.map(Cow::Borrowed);
471 map.nodes_with_entry_count += 1;
466 map.nodes_with_entry_count += 1;
472 if copy_source.is_some() {
467 if copy_source.is_some() {
473 map.nodes_with_copy_source_count += 1
468 map.nodes_with_copy_source_count += 1
474 }
469 }
475 Ok(())
470 Ok(())
476 },
471 },
477 )?;
472 )?;
478 let parents = Some(parents.clone());
473 let parents = Some(parents.clone());
479
474
480 Ok((map, parents))
475 Ok((map, parents))
481 }
476 }
482
477
483 /// Assuming dirstate-v2 format, returns whether the next write should
478 /// Assuming dirstate-v2 format, returns whether the next write should
484 /// append to the existing data file that contains `self.on_disk` (true),
479 /// append to the existing data file that contains `self.on_disk` (true),
485 /// or create a new data file from scratch (false).
480 /// or create a new data file from scratch (false).
486 pub(super) fn write_should_append(&self) -> bool {
481 pub(super) fn write_should_append(&self) -> bool {
487 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
482 let ratio = self.unreachable_bytes as f32 / self.on_disk.len() as f32;
488 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
483 ratio < ACCEPTABLE_UNREACHABLE_BYTES_RATIO
489 }
484 }
490
485
491 fn get_node<'tree>(
486 fn get_node<'tree>(
492 &'tree self,
487 &'tree self,
493 path: &HgPath,
488 path: &HgPath,
494 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
489 ) -> Result<Option<NodeRef<'tree, 'on_disk>>, DirstateV2ParseError> {
495 let mut children = self.root.as_ref();
490 let mut children = self.root.as_ref();
496 let mut components = path.components();
491 let mut components = path.components();
497 let mut component =
492 let mut component =
498 components.next().expect("expected at least one components");
493 components.next().expect("expected at least one components");
499 loop {
494 loop {
500 if let Some(child) = children.get(component, self.on_disk)? {
495 if let Some(child) = children.get(component, self.on_disk)? {
501 if let Some(next_component) = components.next() {
496 if let Some(next_component) = components.next() {
502 component = next_component;
497 component = next_component;
503 children = child.children(self.on_disk)?;
498 children = child.children(self.on_disk)?;
504 } else {
499 } else {
505 return Ok(Some(child));
500 return Ok(Some(child));
506 }
501 }
507 } else {
502 } else {
508 return Ok(None);
503 return Ok(None);
509 }
504 }
510 }
505 }
511 }
506 }
512
507
513 /// Returns a mutable reference to the node at `path` if it exists
508 /// Returns a mutable reference to the node at `path` if it exists
514 ///
509 ///
515 /// This takes `root` instead of `&mut self` so that callers can mutate
510 /// This takes `root` instead of `&mut self` so that callers can mutate
516 /// other fields while the returned borrow is still valid
511 /// other fields while the returned borrow is still valid
517 fn get_node_mut<'tree>(
512 fn get_node_mut<'tree>(
518 on_disk: &'on_disk [u8],
513 on_disk: &'on_disk [u8],
519 unreachable_bytes: &mut u32,
514 unreachable_bytes: &mut u32,
520 root: &'tree mut ChildNodes<'on_disk>,
515 root: &'tree mut ChildNodes<'on_disk>,
521 path: &HgPath,
516 path: &HgPath,
522 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
517 ) -> Result<Option<&'tree mut Node<'on_disk>>, DirstateV2ParseError> {
523 let mut children = root;
518 let mut children = root;
524 let mut components = path.components();
519 let mut components = path.components();
525 let mut component =
520 let mut component =
526 components.next().expect("expected at least one components");
521 components.next().expect("expected at least one components");
527 loop {
522 loop {
528 if let Some(child) = children
523 if let Some(child) = children
529 .make_mut(on_disk, unreachable_bytes)?
524 .make_mut(on_disk, unreachable_bytes)?
530 .get_mut(component)
525 .get_mut(component)
531 {
526 {
532 if let Some(next_component) = components.next() {
527 if let Some(next_component) = components.next() {
533 component = next_component;
528 component = next_component;
534 children = &mut child.children;
529 children = &mut child.children;
535 } else {
530 } else {
536 return Ok(Some(child));
531 return Ok(Some(child));
537 }
532 }
538 } else {
533 } else {
539 return Ok(None);
534 return Ok(None);
540 }
535 }
541 }
536 }
542 }
537 }
543
538
544 pub(super) fn get_or_insert<'tree, 'path>(
539 pub(super) fn get_or_insert<'tree, 'path>(
545 &'tree mut self,
540 &'tree mut self,
546 path: &HgPath,
541 path: &HgPath,
547 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
542 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
548 Self::get_or_insert_node(
543 Self::get_or_insert_node(
549 self.on_disk,
544 self.on_disk,
550 &mut self.unreachable_bytes,
545 &mut self.unreachable_bytes,
551 &mut self.root,
546 &mut self.root,
552 path,
547 path,
553 WithBasename::to_cow_owned,
548 WithBasename::to_cow_owned,
554 |_| {},
549 |_| {},
555 )
550 )
556 }
551 }
557
552
558 fn get_or_insert_node<'tree, 'path>(
553 fn get_or_insert_node<'tree, 'path>(
559 on_disk: &'on_disk [u8],
554 on_disk: &'on_disk [u8],
560 unreachable_bytes: &mut u32,
555 unreachable_bytes: &mut u32,
561 root: &'tree mut ChildNodes<'on_disk>,
556 root: &'tree mut ChildNodes<'on_disk>,
562 path: &'path HgPath,
557 path: &'path HgPath,
563 to_cow: impl Fn(
558 to_cow: impl Fn(
564 WithBasename<&'path HgPath>,
559 WithBasename<&'path HgPath>,
565 ) -> WithBasename<Cow<'on_disk, HgPath>>,
560 ) -> WithBasename<Cow<'on_disk, HgPath>>,
566 mut each_ancestor: impl FnMut(&mut Node),
561 mut each_ancestor: impl FnMut(&mut Node),
567 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
562 ) -> Result<&'tree mut Node<'on_disk>, DirstateV2ParseError> {
568 let mut child_nodes = root;
563 let mut child_nodes = root;
569 let mut inclusive_ancestor_paths =
564 let mut inclusive_ancestor_paths =
570 WithBasename::inclusive_ancestors_of(path);
565 WithBasename::inclusive_ancestors_of(path);
571 let mut ancestor_path = inclusive_ancestor_paths
566 let mut ancestor_path = inclusive_ancestor_paths
572 .next()
567 .next()
573 .expect("expected at least one inclusive ancestor");
568 .expect("expected at least one inclusive ancestor");
574 loop {
569 loop {
575 // TODO: can we avoid allocating an owned key in cases where the
570 // TODO: can we avoid allocating an owned key in cases where the
576 // map already contains that key, without introducing double
571 // map already contains that key, without introducing double
577 // lookup?
572 // lookup?
578 let child_node = child_nodes
573 let child_node = child_nodes
579 .make_mut(on_disk, unreachable_bytes)?
574 .make_mut(on_disk, unreachable_bytes)?
580 .entry(to_cow(ancestor_path))
575 .entry(to_cow(ancestor_path))
581 .or_default();
576 .or_default();
582 if let Some(next) = inclusive_ancestor_paths.next() {
577 if let Some(next) = inclusive_ancestor_paths.next() {
583 each_ancestor(child_node);
578 each_ancestor(child_node);
584 ancestor_path = next;
579 ancestor_path = next;
585 child_nodes = &mut child_node.children;
580 child_nodes = &mut child_node.children;
586 } else {
581 } else {
587 return Ok(child_node);
582 return Ok(child_node);
588 }
583 }
589 }
584 }
590 }
585 }
591
586
592 fn add_or_remove_file(
587 fn add_or_remove_file(
593 &mut self,
588 &mut self,
594 path: &HgPath,
589 path: &HgPath,
595 old_state: Option<EntryState>,
590 old_state: Option<EntryState>,
596 new_entry: DirstateEntry,
591 new_entry: DirstateEntry,
597 ) -> Result<(), DirstateV2ParseError> {
592 ) -> Result<(), DirstateV2ParseError> {
598 let had_entry = old_state.is_some();
593 let had_entry = old_state.is_some();
599 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
594 let was_tracked = old_state.map_or(false, |s| s.is_tracked());
600 let tracked_count_increment =
595 let tracked_count_increment =
601 match (was_tracked, new_entry.state().is_tracked()) {
596 match (was_tracked, new_entry.state().is_tracked()) {
602 (false, true) => 1,
597 (false, true) => 1,
603 (true, false) => -1,
598 (true, false) => -1,
604 _ => 0,
599 _ => 0,
605 };
600 };
606
601
607 let node = Self::get_or_insert_node(
602 let node = Self::get_or_insert_node(
608 self.on_disk,
603 self.on_disk,
609 &mut self.unreachable_bytes,
604 &mut self.unreachable_bytes,
610 &mut self.root,
605 &mut self.root,
611 path,
606 path,
612 WithBasename::to_cow_owned,
607 WithBasename::to_cow_owned,
613 |ancestor| {
608 |ancestor| {
614 if !had_entry {
609 if !had_entry {
615 ancestor.descendants_with_entry_count += 1;
610 ancestor.descendants_with_entry_count += 1;
616 }
611 }
617
612
618 // We can’t use `+= increment` because the counter is unsigned,
613 // We can’t use `+= increment` because the counter is unsigned,
619 // and we want debug builds to detect accidental underflow
614 // and we want debug builds to detect accidental underflow
620 // through zero
615 // through zero
621 match tracked_count_increment {
616 match tracked_count_increment {
622 1 => ancestor.tracked_descendants_count += 1,
617 1 => ancestor.tracked_descendants_count += 1,
623 -1 => ancestor.tracked_descendants_count -= 1,
618 -1 => ancestor.tracked_descendants_count -= 1,
624 _ => {}
619 _ => {}
625 }
620 }
626 },
621 },
627 )?;
622 )?;
628 if !had_entry {
623 if !had_entry {
629 self.nodes_with_entry_count += 1
624 self.nodes_with_entry_count += 1
630 }
625 }
631 node.data = NodeData::Entry(new_entry);
626 node.data = NodeData::Entry(new_entry);
632 Ok(())
627 Ok(())
633 }
628 }
634
629
635 fn iter_nodes<'tree>(
630 fn iter_nodes<'tree>(
636 &'tree self,
631 &'tree self,
637 ) -> impl Iterator<
632 ) -> impl Iterator<
638 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
633 Item = Result<NodeRef<'tree, 'on_disk>, DirstateV2ParseError>,
639 > + 'tree {
634 > + 'tree {
640 // Depth first tree traversal.
635 // Depth first tree traversal.
641 //
636 //
642 // If we could afford internal iteration and recursion,
637 // If we could afford internal iteration and recursion,
643 // this would look like:
638 // this would look like:
644 //
639 //
645 // ```
640 // ```
646 // fn traverse_children(
641 // fn traverse_children(
647 // children: &ChildNodes,
642 // children: &ChildNodes,
648 // each: &mut impl FnMut(&Node),
643 // each: &mut impl FnMut(&Node),
649 // ) {
644 // ) {
650 // for child in children.values() {
645 // for child in children.values() {
651 // traverse_children(&child.children, each);
646 // traverse_children(&child.children, each);
652 // each(child);
647 // each(child);
653 // }
648 // }
654 // }
649 // }
655 // ```
650 // ```
656 //
651 //
657 // However we want an external iterator and therefore can’t use the
652 // However we want an external iterator and therefore can’t use the
658 // call stack. Use an explicit stack instead:
653 // call stack. Use an explicit stack instead:
659 let mut stack = Vec::new();
654 let mut stack = Vec::new();
660 let mut iter = self.root.as_ref().iter();
655 let mut iter = self.root.as_ref().iter();
661 std::iter::from_fn(move || {
656 std::iter::from_fn(move || {
662 while let Some(child_node) = iter.next() {
657 while let Some(child_node) = iter.next() {
663 let children = match child_node.children(self.on_disk) {
658 let children = match child_node.children(self.on_disk) {
664 Ok(children) => children,
659 Ok(children) => children,
665 Err(error) => return Some(Err(error)),
660 Err(error) => return Some(Err(error)),
666 };
661 };
667 // Pseudo-recursion
662 // Pseudo-recursion
668 let new_iter = children.iter();
663 let new_iter = children.iter();
669 let old_iter = std::mem::replace(&mut iter, new_iter);
664 let old_iter = std::mem::replace(&mut iter, new_iter);
670 stack.push((child_node, old_iter));
665 stack.push((child_node, old_iter));
671 }
666 }
672 // Found the end of a `children.iter()` iterator.
667 // Found the end of a `children.iter()` iterator.
673 if let Some((child_node, next_iter)) = stack.pop() {
668 if let Some((child_node, next_iter)) = stack.pop() {
674 // "Return" from pseudo-recursion by restoring state from the
669 // "Return" from pseudo-recursion by restoring state from the
675 // explicit stack
670 // explicit stack
676 iter = next_iter;
671 iter = next_iter;
677
672
678 Some(Ok(child_node))
673 Some(Ok(child_node))
679 } else {
674 } else {
680 // Reached the bottom of the stack, we’re done
675 // Reached the bottom of the stack, we’re done
681 None
676 None
682 }
677 }
683 })
678 })
684 }
679 }
685
680
686 fn clear_known_ambiguous_mtimes(
681 fn clear_known_ambiguous_mtimes(
687 &mut self,
682 &mut self,
688 paths: &[impl AsRef<HgPath>],
683 paths: &[impl AsRef<HgPath>],
689 ) -> Result<(), DirstateV2ParseError> {
684 ) -> Result<(), DirstateV2ParseError> {
690 for path in paths {
685 for path in paths {
691 if let Some(node) = Self::get_node_mut(
686 if let Some(node) = Self::get_node_mut(
692 self.on_disk,
687 self.on_disk,
693 &mut self.unreachable_bytes,
688 &mut self.unreachable_bytes,
694 &mut self.root,
689 &mut self.root,
695 path.as_ref(),
690 path.as_ref(),
696 )? {
691 )? {
697 if let NodeData::Entry(entry) = &mut node.data {
692 if let NodeData::Entry(entry) = &mut node.data {
698 entry.set_possibly_dirty();
693 entry.set_possibly_dirty();
699 }
694 }
700 }
695 }
701 }
696 }
702 Ok(())
697 Ok(())
703 }
698 }
704
699
705 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
700 fn count_dropped_path(unreachable_bytes: &mut u32, path: &Cow<HgPath>) {
706 if let Cow::Borrowed(path) = path {
701 if let Cow::Borrowed(path) = path {
707 *unreachable_bytes += path.len() as u32
702 *unreachable_bytes += path.len() as u32
708 }
703 }
709 }
704 }
710 }
705 }
711
706
712 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
707 /// Like `Iterator::filter_map`, but over a fallible iterator of `Result`s.
713 ///
708 ///
714 /// The callback is only called for incoming `Ok` values. Errors are passed
709 /// The callback is only called for incoming `Ok` values. Errors are passed
715 /// through as-is. In order to let it use the `?` operator the callback is
710 /// through as-is. In order to let it use the `?` operator the callback is
716 /// expected to return a `Result` of `Option`, instead of an `Option` of
711 /// expected to return a `Result` of `Option`, instead of an `Option` of
717 /// `Result`.
712 /// `Result`.
718 fn filter_map_results<'a, I, F, A, B, E>(
713 fn filter_map_results<'a, I, F, A, B, E>(
719 iter: I,
714 iter: I,
720 f: F,
715 f: F,
721 ) -> impl Iterator<Item = Result<B, E>> + 'a
716 ) -> impl Iterator<Item = Result<B, E>> + 'a
722 where
717 where
723 I: Iterator<Item = Result<A, E>> + 'a,
718 I: Iterator<Item = Result<A, E>> + 'a,
724 F: Fn(A) -> Result<Option<B>, E> + 'a,
719 F: Fn(A) -> Result<Option<B>, E> + 'a,
725 {
720 {
726 iter.filter_map(move |result| match result {
721 iter.filter_map(move |result| match result {
727 Ok(node) => f(node).transpose(),
722 Ok(node) => f(node).transpose(),
728 Err(e) => Some(Err(e)),
723 Err(e) => Some(Err(e)),
729 })
724 })
730 }
725 }
731
726
732 impl OwningDirstateMap {
727 impl OwningDirstateMap {
733 pub fn clear(&mut self) {
728 pub fn clear(&mut self) {
734 let map = self.get_map_mut();
729 let map = self.get_map_mut();
735 map.root = Default::default();
730 map.root = Default::default();
736 map.nodes_with_entry_count = 0;
731 map.nodes_with_entry_count = 0;
737 map.nodes_with_copy_source_count = 0;
732 map.nodes_with_copy_source_count = 0;
738 }
733 }
739
734
740 pub fn set_entry(
735 pub fn set_entry(
741 &mut self,
736 &mut self,
742 filename: &HgPath,
737 filename: &HgPath,
743 entry: DirstateEntry,
738 entry: DirstateEntry,
744 ) -> Result<(), DirstateV2ParseError> {
739 ) -> Result<(), DirstateV2ParseError> {
745 let map = self.get_map_mut();
740 let map = self.get_map_mut();
746 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
741 map.get_or_insert(&filename)?.data = NodeData::Entry(entry);
747 Ok(())
742 Ok(())
748 }
743 }
749
744
750 pub fn add_file(
745 pub fn add_file(
751 &mut self,
746 &mut self,
752 filename: &HgPath,
747 filename: &HgPath,
753 entry: DirstateEntry,
748 entry: DirstateEntry,
754 ) -> Result<(), DirstateError> {
749 ) -> Result<(), DirstateError> {
755 let old_state = self.get(filename)?.map(|e| e.state());
750 let old_state = self.get(filename)?.map(|e| e.state());
756 let map = self.get_map_mut();
751 let map = self.get_map_mut();
757 Ok(map.add_or_remove_file(filename, old_state, entry)?)
752 Ok(map.add_or_remove_file(filename, old_state, entry)?)
758 }
753 }
759
754
760 pub fn remove_file(
755 pub fn remove_file(
761 &mut self,
756 &mut self,
762 filename: &HgPath,
757 filename: &HgPath,
763 in_merge: bool,
758 in_merge: bool,
764 ) -> Result<(), DirstateError> {
759 ) -> Result<(), DirstateError> {
765 let old_entry_opt = self.get(filename)?;
760 let old_entry_opt = self.get(filename)?;
766 let old_state = old_entry_opt.map(|e| e.state());
761 let old_state = old_entry_opt.map(|e| e.state());
767 let mut size = 0;
762 let mut size = 0;
768 if in_merge {
763 if in_merge {
769 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
764 // XXX we should not be able to have 'm' state and 'FROM_P2' if not
770 // during a merge. So I (marmoute) am not sure we need the
765 // during a merge. So I (marmoute) am not sure we need the
771 // conditionnal at all. Adding double checking this with assert
766 // conditionnal at all. Adding double checking this with assert
772 // would be nice.
767 // would be nice.
773 if let Some(old_entry) = old_entry_opt {
768 if let Some(old_entry) = old_entry_opt {
774 // backup the previous state
769 // backup the previous state
775 if old_entry.state() == EntryState::Merged {
770 if old_entry.state() == EntryState::Merged {
776 size = SIZE_NON_NORMAL;
771 size = SIZE_NON_NORMAL;
777 } else if old_entry.state() == EntryState::Normal
772 } else if old_entry.state() == EntryState::Normal
778 && old_entry.size() == SIZE_FROM_OTHER_PARENT
773 && old_entry.size() == SIZE_FROM_OTHER_PARENT
779 {
774 {
780 // other parent
775 // other parent
781 size = SIZE_FROM_OTHER_PARENT;
776 size = SIZE_FROM_OTHER_PARENT;
782 }
777 }
783 }
778 }
784 }
779 }
785 if size == 0 {
780 if size == 0 {
786 self.copy_map_remove(filename)?;
781 self.copy_map_remove(filename)?;
787 }
782 }
788 let map = self.get_map_mut();
783 let map = self.get_map_mut();
789 let entry = DirstateEntry::new_removed(size);
784 let entry = DirstateEntry::new_removed(size);
790 Ok(map.add_or_remove_file(filename, old_state, entry)?)
785 Ok(map.add_or_remove_file(filename, old_state, entry)?)
791 }
786 }
792
787
793 pub fn drop_entry_and_copy_source(
788 pub fn drop_entry_and_copy_source(
794 &mut self,
789 &mut self,
795 filename: &HgPath,
790 filename: &HgPath,
796 ) -> Result<(), DirstateError> {
791 ) -> Result<(), DirstateError> {
797 let was_tracked = self
792 let was_tracked = self
798 .get(filename)?
793 .get(filename)?
799 .map_or(false, |e| e.state().is_tracked());
794 .map_or(false, |e| e.state().is_tracked());
800 let map = self.get_map_mut();
795 let map = self.get_map_mut();
801 struct Dropped {
796 struct Dropped {
802 was_tracked: bool,
797 was_tracked: bool,
803 had_entry: bool,
798 had_entry: bool,
804 had_copy_source: bool,
799 had_copy_source: bool,
805 }
800 }
806
801
807 /// If this returns `Ok(Some((dropped, removed)))`, then
802 /// If this returns `Ok(Some((dropped, removed)))`, then
808 ///
803 ///
809 /// * `dropped` is about the leaf node that was at `filename`
804 /// * `dropped` is about the leaf node that was at `filename`
810 /// * `removed` is whether this particular level of recursion just
805 /// * `removed` is whether this particular level of recursion just
811 /// removed a node in `nodes`.
806 /// removed a node in `nodes`.
812 fn recur<'on_disk>(
807 fn recur<'on_disk>(
813 on_disk: &'on_disk [u8],
808 on_disk: &'on_disk [u8],
814 unreachable_bytes: &mut u32,
809 unreachable_bytes: &mut u32,
815 nodes: &mut ChildNodes<'on_disk>,
810 nodes: &mut ChildNodes<'on_disk>,
816 path: &HgPath,
811 path: &HgPath,
817 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
812 ) -> Result<Option<(Dropped, bool)>, DirstateV2ParseError> {
818 let (first_path_component, rest_of_path) =
813 let (first_path_component, rest_of_path) =
819 path.split_first_component();
814 path.split_first_component();
820 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
815 let nodes = nodes.make_mut(on_disk, unreachable_bytes)?;
821 let node = if let Some(node) = nodes.get_mut(first_path_component)
816 let node = if let Some(node) = nodes.get_mut(first_path_component)
822 {
817 {
823 node
818 node
824 } else {
819 } else {
825 return Ok(None);
820 return Ok(None);
826 };
821 };
827 let dropped;
822 let dropped;
828 if let Some(rest) = rest_of_path {
823 if let Some(rest) = rest_of_path {
829 if let Some((d, removed)) = recur(
824 if let Some((d, removed)) = recur(
830 on_disk,
825 on_disk,
831 unreachable_bytes,
826 unreachable_bytes,
832 &mut node.children,
827 &mut node.children,
833 rest,
828 rest,
834 )? {
829 )? {
835 dropped = d;
830 dropped = d;
836 if dropped.had_entry {
831 if dropped.had_entry {
837 node.descendants_with_entry_count -= 1;
832 node.descendants_with_entry_count -= 1;
838 }
833 }
839 if dropped.was_tracked {
834 if dropped.was_tracked {
840 node.tracked_descendants_count -= 1;
835 node.tracked_descendants_count -= 1;
841 }
836 }
842
837
843 // Directory caches must be invalidated when removing a
838 // Directory caches must be invalidated when removing a
844 // child node
839 // child node
845 if removed {
840 if removed {
846 if let NodeData::CachedDirectory { .. } = &node.data {
841 if let NodeData::CachedDirectory { .. } = &node.data {
847 node.data = NodeData::None
842 node.data = NodeData::None
848 }
843 }
849 }
844 }
850 } else {
845 } else {
851 return Ok(None);
846 return Ok(None);
852 }
847 }
853 } else {
848 } else {
854 let had_entry = node.data.has_entry();
849 let had_entry = node.data.has_entry();
855 if had_entry {
850 if had_entry {
856 node.data = NodeData::None
851 node.data = NodeData::None
857 }
852 }
858 if let Some(source) = &node.copy_source {
853 if let Some(source) = &node.copy_source {
859 DirstateMap::count_dropped_path(unreachable_bytes, source);
854 DirstateMap::count_dropped_path(unreachable_bytes, source);
860 node.copy_source = None
855 node.copy_source = None
861 }
856 }
862 dropped = Dropped {
857 dropped = Dropped {
863 was_tracked: node
858 was_tracked: node
864 .data
859 .data
865 .as_entry()
860 .as_entry()
866 .map_or(false, |entry| entry.state().is_tracked()),
861 .map_or(false, |entry| entry.state().is_tracked()),
867 had_entry,
862 had_entry,
868 had_copy_source: node.copy_source.take().is_some(),
863 had_copy_source: node.copy_source.take().is_some(),
869 };
864 };
870 }
865 }
871 // After recursion, for both leaf (rest_of_path is None) nodes and
866 // After recursion, for both leaf (rest_of_path is None) nodes and
872 // parent nodes, remove a node if it just became empty.
867 // parent nodes, remove a node if it just became empty.
873 let remove = !node.data.has_entry()
868 let remove = !node.data.has_entry()
874 && node.copy_source.is_none()
869 && node.copy_source.is_none()
875 && node.children.is_empty();
870 && node.children.is_empty();
876 if remove {
871 if remove {
877 let (key, _) =
872 let (key, _) =
878 nodes.remove_entry(first_path_component).unwrap();
873 nodes.remove_entry(first_path_component).unwrap();
879 DirstateMap::count_dropped_path(
874 DirstateMap::count_dropped_path(
880 unreachable_bytes,
875 unreachable_bytes,
881 key.full_path(),
876 key.full_path(),
882 )
877 )
883 }
878 }
884 Ok(Some((dropped, remove)))
879 Ok(Some((dropped, remove)))
885 }
880 }
886
881
887 if let Some((dropped, _removed)) = recur(
882 if let Some((dropped, _removed)) = recur(
888 map.on_disk,
883 map.on_disk,
889 &mut map.unreachable_bytes,
884 &mut map.unreachable_bytes,
890 &mut map.root,
885 &mut map.root,
891 filename,
886 filename,
892 )? {
887 )? {
893 if dropped.had_entry {
888 if dropped.had_entry {
894 map.nodes_with_entry_count -= 1
889 map.nodes_with_entry_count -= 1
895 }
890 }
896 if dropped.had_copy_source {
891 if dropped.had_copy_source {
897 map.nodes_with_copy_source_count -= 1
892 map.nodes_with_copy_source_count -= 1
898 }
893 }
899 } else {
894 } else {
900 debug_assert!(!was_tracked);
895 debug_assert!(!was_tracked);
901 }
896 }
902 Ok(())
897 Ok(())
903 }
898 }
904
899
905 pub fn has_tracked_dir(
900 pub fn has_tracked_dir(
906 &mut self,
901 &mut self,
907 directory: &HgPath,
902 directory: &HgPath,
908 ) -> Result<bool, DirstateError> {
903 ) -> Result<bool, DirstateError> {
909 let map = self.get_map_mut();
904 let map = self.get_map_mut();
910 if let Some(node) = map.get_node(directory)? {
905 if let Some(node) = map.get_node(directory)? {
911 // A node without a `DirstateEntry` was created to hold child
906 // A node without a `DirstateEntry` was created to hold child
912 // nodes, and is therefore a directory.
907 // nodes, and is therefore a directory.
913 let state = node.state()?;
908 let state = node.state()?;
914 Ok(state.is_none() && node.tracked_descendants_count() > 0)
909 Ok(state.is_none() && node.tracked_descendants_count() > 0)
915 } else {
910 } else {
916 Ok(false)
911 Ok(false)
917 }
912 }
918 }
913 }
919
914
920 pub fn has_dir(
915 pub fn has_dir(
921 &mut self,
916 &mut self,
922 directory: &HgPath,
917 directory: &HgPath,
923 ) -> Result<bool, DirstateError> {
918 ) -> Result<bool, DirstateError> {
924 let map = self.get_map_mut();
919 let map = self.get_map_mut();
925 if let Some(node) = map.get_node(directory)? {
920 if let Some(node) = map.get_node(directory)? {
926 // A node without a `DirstateEntry` was created to hold child
921 // A node without a `DirstateEntry` was created to hold child
927 // nodes, and is therefore a directory.
922 // nodes, and is therefore a directory.
928 let state = node.state()?;
923 let state = node.state()?;
929 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
924 Ok(state.is_none() && node.descendants_with_entry_count() > 0)
930 } else {
925 } else {
931 Ok(false)
926 Ok(false)
932 }
927 }
933 }
928 }
934
929
935 #[timed]
930 #[timed]
936 pub fn pack_v1(
931 pub fn pack_v1(
937 &mut self,
932 &mut self,
938 parents: DirstateParents,
933 parents: DirstateParents,
939 now: Timestamp,
934 now: Timestamp,
940 ) -> Result<Vec<u8>, DirstateError> {
935 ) -> Result<Vec<u8>, DirstateError> {
941 let map = self.get_map_mut();
936 let map = self.get_map_mut();
942 let now: i32 = now.0.try_into().expect("time overflow");
937 let now: i32 = now.0.try_into().expect("time overflow");
943 let mut ambiguous_mtimes = Vec::new();
938 let mut ambiguous_mtimes = Vec::new();
944 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
939 // Optizimation (to be measured?): pre-compute size to avoid `Vec`
945 // reallocations
940 // reallocations
946 let mut size = parents.as_bytes().len();
941 let mut size = parents.as_bytes().len();
947 for node in map.iter_nodes() {
942 for node in map.iter_nodes() {
948 let node = node?;
943 let node = node?;
949 if let Some(entry) = node.entry()? {
944 if let Some(entry) = node.entry()? {
950 size += packed_entry_size(
945 size += packed_entry_size(
951 node.full_path(map.on_disk)?,
946 node.full_path(map.on_disk)?,
952 node.copy_source(map.on_disk)?,
947 node.copy_source(map.on_disk)?,
953 );
948 );
954 if entry.mtime_is_ambiguous(now) {
949 if entry.mtime_is_ambiguous(now) {
955 ambiguous_mtimes.push(
950 ambiguous_mtimes.push(
956 node.full_path_borrowed(map.on_disk)?
951 node.full_path_borrowed(map.on_disk)?
957 .detach_from_tree(),
952 .detach_from_tree(),
958 )
953 )
959 }
954 }
960 }
955 }
961 }
956 }
962 map.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
957 map.clear_known_ambiguous_mtimes(&ambiguous_mtimes)?;
963
958
964 let mut packed = Vec::with_capacity(size);
959 let mut packed = Vec::with_capacity(size);
965 packed.extend(parents.as_bytes());
960 packed.extend(parents.as_bytes());
966
961
967 for node in map.iter_nodes() {
962 for node in map.iter_nodes() {
968 let node = node?;
963 let node = node?;
969 if let Some(entry) = node.entry()? {
964 if let Some(entry) = node.entry()? {
970 pack_entry(
965 pack_entry(
971 node.full_path(map.on_disk)?,
966 node.full_path(map.on_disk)?,
972 &entry,
967 &entry,
973 node.copy_source(map.on_disk)?,
968 node.copy_source(map.on_disk)?,
974 &mut packed,
969 &mut packed,
975 );
970 );
976 }
971 }
977 }
972 }
978 Ok(packed)
973 Ok(packed)
979 }
974 }
980
975
981 /// Returns new data and metadata together with whether that data should be
976 /// Returns new data and metadata together with whether that data should be
982 /// appended to the existing data file whose content is at
977 /// appended to the existing data file whose content is at
983 /// `map.on_disk` (true), instead of written to a new data file
978 /// `map.on_disk` (true), instead of written to a new data file
984 /// (false).
979 /// (false).
985 #[timed]
980 #[timed]
986 pub fn pack_v2(
981 pub fn pack_v2(
987 &mut self,
982 &mut self,
988 now: Timestamp,
983 now: Timestamp,
989 can_append: bool,
984 can_append: bool,
990 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
985 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
991 let map = self.get_map_mut();
986 let map = self.get_map_mut();
992 // TODO: how do we want to handle this in 2038?
987 // TODO: how do we want to handle this in 2038?
993 let now: i32 = now.0.try_into().expect("time overflow");
988 let now: i32 = now.0.try_into().expect("time overflow");
994 let mut paths = Vec::new();
989 let mut paths = Vec::new();
995 for node in map.iter_nodes() {
990 for node in map.iter_nodes() {
996 let node = node?;
991 let node = node?;
997 if let Some(entry) = node.entry()? {
992 if let Some(entry) = node.entry()? {
998 if entry.mtime_is_ambiguous(now) {
993 if entry.mtime_is_ambiguous(now) {
999 paths.push(
994 paths.push(
1000 node.full_path_borrowed(map.on_disk)?
995 node.full_path_borrowed(map.on_disk)?
1001 .detach_from_tree(),
996 .detach_from_tree(),
1002 )
997 )
1003 }
998 }
1004 }
999 }
1005 }
1000 }
1006 // Borrow of `self` ends here since we collect cloned paths
1001 // Borrow of `self` ends here since we collect cloned paths
1007
1002
1008 map.clear_known_ambiguous_mtimes(&paths)?;
1003 map.clear_known_ambiguous_mtimes(&paths)?;
1009
1004
1010 on_disk::write(map, can_append)
1005 on_disk::write(map, can_append)
1011 }
1006 }
1012
1007
1013 pub fn status<'a>(
1008 pub fn status<'a>(
1014 &'a mut self,
1009 &'a mut self,
1015 matcher: &'a (dyn Matcher + Sync),
1010 matcher: &'a (dyn Matcher + Sync),
1016 root_dir: PathBuf,
1011 root_dir: PathBuf,
1017 ignore_files: Vec<PathBuf>,
1012 ignore_files: Vec<PathBuf>,
1018 options: StatusOptions,
1013 options: StatusOptions,
1019 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1014 ) -> Result<(DirstateStatus<'a>, Vec<PatternFileWarning>), StatusError>
1020 {
1015 {
1021 let map = self.get_map_mut();
1016 let map = self.get_map_mut();
1022 super::status::status(map, matcher, root_dir, ignore_files, options)
1017 super::status::status(map, matcher, root_dir, ignore_files, options)
1023 }
1018 }
1024
1019
1025 pub fn copy_map_len(&self) -> usize {
1020 pub fn copy_map_len(&self) -> usize {
1026 let map = self.get_map();
1021 let map = self.get_map();
1027 map.nodes_with_copy_source_count as usize
1022 map.nodes_with_copy_source_count as usize
1028 }
1023 }
1029
1024
1030 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1025 pub fn copy_map_iter(&self) -> CopyMapIter<'_> {
1031 let map = self.get_map();
1026 let map = self.get_map();
1032 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1027 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1033 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1028 Ok(if let Some(source) = node.copy_source(map.on_disk)? {
1034 Some((node.full_path(map.on_disk)?, source))
1029 Some((node.full_path(map.on_disk)?, source))
1035 } else {
1030 } else {
1036 None
1031 None
1037 })
1032 })
1038 }))
1033 }))
1039 }
1034 }
1040
1035
1041 pub fn copy_map_contains_key(
1036 pub fn copy_map_contains_key(
1042 &self,
1037 &self,
1043 key: &HgPath,
1038 key: &HgPath,
1044 ) -> Result<bool, DirstateV2ParseError> {
1039 ) -> Result<bool, DirstateV2ParseError> {
1045 let map = self.get_map();
1040 let map = self.get_map();
1046 Ok(if let Some(node) = map.get_node(key)? {
1041 Ok(if let Some(node) = map.get_node(key)? {
1047 node.has_copy_source()
1042 node.has_copy_source()
1048 } else {
1043 } else {
1049 false
1044 false
1050 })
1045 })
1051 }
1046 }
1052
1047
1053 pub fn copy_map_get(
1048 pub fn copy_map_get(
1054 &self,
1049 &self,
1055 key: &HgPath,
1050 key: &HgPath,
1056 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1051 ) -> Result<Option<&HgPath>, DirstateV2ParseError> {
1057 let map = self.get_map();
1052 let map = self.get_map();
1058 if let Some(node) = map.get_node(key)? {
1053 if let Some(node) = map.get_node(key)? {
1059 if let Some(source) = node.copy_source(map.on_disk)? {
1054 if let Some(source) = node.copy_source(map.on_disk)? {
1060 return Ok(Some(source));
1055 return Ok(Some(source));
1061 }
1056 }
1062 }
1057 }
1063 Ok(None)
1058 Ok(None)
1064 }
1059 }
1065
1060
1066 pub fn copy_map_remove(
1061 pub fn copy_map_remove(
1067 &mut self,
1062 &mut self,
1068 key: &HgPath,
1063 key: &HgPath,
1069 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1064 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1070 let map = self.get_map_mut();
1065 let map = self.get_map_mut();
1071 let count = &mut map.nodes_with_copy_source_count;
1066 let count = &mut map.nodes_with_copy_source_count;
1072 let unreachable_bytes = &mut map.unreachable_bytes;
1067 let unreachable_bytes = &mut map.unreachable_bytes;
1073 Ok(DirstateMap::get_node_mut(
1068 Ok(DirstateMap::get_node_mut(
1074 map.on_disk,
1069 map.on_disk,
1075 unreachable_bytes,
1070 unreachable_bytes,
1076 &mut map.root,
1071 &mut map.root,
1077 key,
1072 key,
1078 )?
1073 )?
1079 .and_then(|node| {
1074 .and_then(|node| {
1080 if let Some(source) = &node.copy_source {
1075 if let Some(source) = &node.copy_source {
1081 *count -= 1;
1076 *count -= 1;
1082 DirstateMap::count_dropped_path(unreachable_bytes, source);
1077 DirstateMap::count_dropped_path(unreachable_bytes, source);
1083 }
1078 }
1084 node.copy_source.take().map(Cow::into_owned)
1079 node.copy_source.take().map(Cow::into_owned)
1085 }))
1080 }))
1086 }
1081 }
1087
1082
1088 pub fn copy_map_insert(
1083 pub fn copy_map_insert(
1089 &mut self,
1084 &mut self,
1090 key: HgPathBuf,
1085 key: HgPathBuf,
1091 value: HgPathBuf,
1086 value: HgPathBuf,
1092 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1087 ) -> Result<Option<HgPathBuf>, DirstateV2ParseError> {
1093 let map = self.get_map_mut();
1088 let map = self.get_map_mut();
1094 let node = DirstateMap::get_or_insert_node(
1089 let node = DirstateMap::get_or_insert_node(
1095 map.on_disk,
1090 map.on_disk,
1096 &mut map.unreachable_bytes,
1091 &mut map.unreachable_bytes,
1097 &mut map.root,
1092 &mut map.root,
1098 &key,
1093 &key,
1099 WithBasename::to_cow_owned,
1094 WithBasename::to_cow_owned,
1100 |_ancestor| {},
1095 |_ancestor| {},
1101 )?;
1096 )?;
1102 if node.copy_source.is_none() {
1097 if node.copy_source.is_none() {
1103 map.nodes_with_copy_source_count += 1
1098 map.nodes_with_copy_source_count += 1
1104 }
1099 }
1105 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1100 Ok(node.copy_source.replace(value.into()).map(Cow::into_owned))
1106 }
1101 }
1107
1102
1108 pub fn len(&self) -> usize {
1103 pub fn len(&self) -> usize {
1109 let map = self.get_map();
1104 let map = self.get_map();
1110 map.nodes_with_entry_count as usize
1105 map.nodes_with_entry_count as usize
1111 }
1106 }
1112
1107
1113 pub fn contains_key(
1108 pub fn contains_key(
1114 &self,
1109 &self,
1115 key: &HgPath,
1110 key: &HgPath,
1116 ) -> Result<bool, DirstateV2ParseError> {
1111 ) -> Result<bool, DirstateV2ParseError> {
1117 Ok(self.get(key)?.is_some())
1112 Ok(self.get(key)?.is_some())
1118 }
1113 }
1119
1114
1120 pub fn get(
1115 pub fn get(
1121 &self,
1116 &self,
1122 key: &HgPath,
1117 key: &HgPath,
1123 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1118 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
1124 let map = self.get_map();
1119 let map = self.get_map();
1125 Ok(if let Some(node) = map.get_node(key)? {
1120 Ok(if let Some(node) = map.get_node(key)? {
1126 node.entry()?
1121 node.entry()?
1127 } else {
1122 } else {
1128 None
1123 None
1129 })
1124 })
1130 }
1125 }
1131
1126
1132 pub fn iter(&self) -> StateMapIter<'_> {
1127 pub fn iter(&self) -> StateMapIter<'_> {
1133 let map = self.get_map();
1128 let map = self.get_map();
1134 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1129 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1135 Ok(if let Some(entry) = node.entry()? {
1130 Ok(if let Some(entry) = node.entry()? {
1136 Some((node.full_path(map.on_disk)?, entry))
1131 Some((node.full_path(map.on_disk)?, entry))
1137 } else {
1132 } else {
1138 None
1133 None
1139 })
1134 })
1140 }))
1135 }))
1141 }
1136 }
1142
1137
1143 pub fn iter_tracked_dirs(
1138 pub fn iter_tracked_dirs(
1144 &mut self,
1139 &mut self,
1145 ) -> Result<
1140 ) -> Result<
1146 Box<
1141 Box<
1147 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1142 dyn Iterator<Item = Result<&HgPath, DirstateV2ParseError>>
1148 + Send
1143 + Send
1149 + '_,
1144 + '_,
1150 >,
1145 >,
1151 DirstateError,
1146 DirstateError,
1152 > {
1147 > {
1153 let map = self.get_map_mut();
1148 let map = self.get_map_mut();
1154 let on_disk = map.on_disk;
1149 let on_disk = map.on_disk;
1155 Ok(Box::new(filter_map_results(
1150 Ok(Box::new(filter_map_results(
1156 map.iter_nodes(),
1151 map.iter_nodes(),
1157 move |node| {
1152 move |node| {
1158 Ok(if node.tracked_descendants_count() > 0 {
1153 Ok(if node.tracked_descendants_count() > 0 {
1159 Some(node.full_path(on_disk)?)
1154 Some(node.full_path(on_disk)?)
1160 } else {
1155 } else {
1161 None
1156 None
1162 })
1157 })
1163 },
1158 },
1164 )))
1159 )))
1165 }
1160 }
1166
1161
1167 pub fn debug_iter(
1162 pub fn debug_iter(
1168 &self,
1163 &self,
1169 all: bool,
1164 all: bool,
1170 ) -> Box<
1165 ) -> Box<
1171 dyn Iterator<
1166 dyn Iterator<
1172 Item = Result<
1167 Item = Result<
1173 (&HgPath, (u8, i32, i32, i32)),
1168 (&HgPath, (u8, i32, i32, i32)),
1174 DirstateV2ParseError,
1169 DirstateV2ParseError,
1175 >,
1170 >,
1176 > + Send
1171 > + Send
1177 + '_,
1172 + '_,
1178 > {
1173 > {
1179 let map = self.get_map();
1174 let map = self.get_map();
1180 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1175 Box::new(filter_map_results(map.iter_nodes(), move |node| {
1181 let debug_tuple = if let Some(entry) = node.entry()? {
1176 let debug_tuple = if let Some(entry) = node.entry()? {
1182 entry.debug_tuple()
1177 entry.debug_tuple()
1183 } else if !all {
1178 } else if !all {
1184 return Ok(None);
1179 return Ok(None);
1185 } else if let Some(mtime) = node.cached_directory_mtime() {
1180 } else if let Some(mtime) = node.cached_directory_mtime() {
1186 (b' ', 0, -1, mtime.seconds() as i32)
1181 (b' ', 0, -1, mtime.seconds() as i32)
1187 } else {
1182 } else {
1188 (b' ', 0, -1, -1)
1183 (b' ', 0, -1, -1)
1189 };
1184 };
1190 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1185 Ok(Some((node.full_path(map.on_disk)?, debug_tuple)))
1191 }))
1186 }))
1192 }
1187 }
1193 }
1188 }
@@ -1,763 +1,808 b''
1 //! The "version 2" disk representation of the dirstate
1 //! The "version 2" disk representation of the dirstate
2 //!
2 //!
3 //! # File format
3 //! # File format
4 //!
4 //!
5 //! In dirstate-v2 format, the `.hg/dirstate` file is a "docket that starts
5 //! In dirstate-v2 format, the `.hg/dirstate` file is a "docket that starts
6 //! with a fixed-sized header whose layout is defined by the `DocketHeader`
6 //! with a fixed-sized header whose layout is defined by the `DocketHeader`
7 //! struct, followed by the data file identifier.
7 //! struct, followed by the data file identifier.
8 //!
8 //!
9 //! A separate `.hg/dirstate.{uuid}.d` file contains most of the data. That
9 //! A separate `.hg/dirstate.{uuid}.d` file contains most of the data. That
10 //! file may be longer than the size given in the docket, but not shorter. Only
10 //! file may be longer than the size given in the docket, but not shorter. Only
11 //! the start of the data file up to the given size is considered. The
11 //! the start of the data file up to the given size is considered. The
12 //! fixed-size "root" of the dirstate tree whose layout is defined by the
12 //! fixed-size "root" of the dirstate tree whose layout is defined by the
13 //! `Root` struct is found at the end of that slice of data.
13 //! `Root` struct is found at the end of that slice of data.
14 //!
14 //!
15 //! Its `root_nodes` field contains the slice (offset and length) to
15 //! Its `root_nodes` field contains the slice (offset and length) to
16 //! the nodes representing the files and directories at the root of the
16 //! the nodes representing the files and directories at the root of the
17 //! repository. Each node is also fixed-size, defined by the `Node` struct.
17 //! repository. Each node is also fixed-size, defined by the `Node` struct.
18 //! Nodes in turn contain slices to variable-size paths, and to their own child
18 //! Nodes in turn contain slices to variable-size paths, and to their own child
19 //! nodes (if any) for nested files and directories.
19 //! nodes (if any) for nested files and directories.
20
20
21 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
21 use crate::dirstate_tree::dirstate_map::{self, DirstateMap, NodeRef};
22 use crate::dirstate_tree::path_with_basename::WithBasename;
22 use crate::dirstate_tree::path_with_basename::WithBasename;
23 use crate::errors::HgError;
23 use crate::errors::HgError;
24 use crate::utils::hg_path::HgPath;
24 use crate::utils::hg_path::HgPath;
25 use crate::DirstateEntry;
25 use crate::DirstateEntry;
26 use crate::DirstateError;
26 use crate::DirstateError;
27 use crate::DirstateParents;
27 use crate::DirstateParents;
28 use crate::EntryState;
28 use bitflags::bitflags;
29 use bytes_cast::unaligned::{I32Be, I64Be, U16Be, U32Be};
29 use bytes_cast::unaligned::{I32Be, I64Be, U16Be, U32Be};
30 use bytes_cast::BytesCast;
30 use bytes_cast::BytesCast;
31 use format_bytes::format_bytes;
31 use format_bytes::format_bytes;
32 use std::borrow::Cow;
32 use std::borrow::Cow;
33 use std::convert::{TryFrom, TryInto};
33 use std::convert::{TryFrom, TryInto};
34 use std::time::{Duration, SystemTime, UNIX_EPOCH};
34 use std::time::{Duration, SystemTime, UNIX_EPOCH};
35
35
36 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
36 /// Added at the start of `.hg/dirstate` when the "v2" format is used.
37 /// This a redundant sanity check more than an actual "magic number" since
37 /// This a redundant sanity check more than an actual "magic number" since
38 /// `.hg/requires` already governs which format should be used.
38 /// `.hg/requires` already governs which format should be used.
39 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
39 pub const V2_FORMAT_MARKER: &[u8; 12] = b"dirstate-v2\n";
40
40
41 /// Keep space for 256-bit hashes
41 /// Keep space for 256-bit hashes
42 const STORED_NODE_ID_BYTES: usize = 32;
42 const STORED_NODE_ID_BYTES: usize = 32;
43
43
44 /// … even though only 160 bits are used for now, with SHA-1
44 /// … even though only 160 bits are used for now, with SHA-1
45 const USED_NODE_ID_BYTES: usize = 20;
45 const USED_NODE_ID_BYTES: usize = 20;
46
46
47 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
47 pub(super) const IGNORE_PATTERNS_HASH_LEN: usize = 20;
48 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
48 pub(super) type IgnorePatternsHash = [u8; IGNORE_PATTERNS_HASH_LEN];
49
49
50 /// Must match the constant of the same name in
50 /// Must match the constant of the same name in
51 /// `mercurial/dirstateutils/docket.py`
51 /// `mercurial/dirstateutils/docket.py`
52 const TREE_METADATA_SIZE: usize = 44;
52 const TREE_METADATA_SIZE: usize = 44;
53
53
54 /// Make sure that size-affecting changes are made knowingly
54 /// Make sure that size-affecting changes are made knowingly
55 #[allow(unused)]
55 #[allow(unused)]
56 fn static_assert_size_of() {
56 fn static_assert_size_of() {
57 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
57 let _ = std::mem::transmute::<TreeMetadata, [u8; TREE_METADATA_SIZE]>;
58 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
58 let _ = std::mem::transmute::<DocketHeader, [u8; TREE_METADATA_SIZE + 81]>;
59 let _ = std::mem::transmute::<Node, [u8; 43]>;
59 let _ = std::mem::transmute::<Node, [u8; 43]>;
60 }
60 }
61
61
62 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
62 // Must match `HEADER` in `mercurial/dirstateutils/docket.py`
63 #[derive(BytesCast)]
63 #[derive(BytesCast)]
64 #[repr(C)]
64 #[repr(C)]
65 struct DocketHeader {
65 struct DocketHeader {
66 marker: [u8; V2_FORMAT_MARKER.len()],
66 marker: [u8; V2_FORMAT_MARKER.len()],
67 parent_1: [u8; STORED_NODE_ID_BYTES],
67 parent_1: [u8; STORED_NODE_ID_BYTES],
68 parent_2: [u8; STORED_NODE_ID_BYTES],
68 parent_2: [u8; STORED_NODE_ID_BYTES],
69
69
70 /// Counted in bytes
70 /// Counted in bytes
71 data_size: Size,
71 data_size: Size,
72
72
73 metadata: TreeMetadata,
73 metadata: TreeMetadata,
74
74
75 uuid_size: u8,
75 uuid_size: u8,
76 }
76 }
77
77
78 pub struct Docket<'on_disk> {
78 pub struct Docket<'on_disk> {
79 header: &'on_disk DocketHeader,
79 header: &'on_disk DocketHeader,
80 uuid: &'on_disk [u8],
80 uuid: &'on_disk [u8],
81 }
81 }
82
82
83 #[derive(BytesCast)]
83 #[derive(BytesCast)]
84 #[repr(C)]
84 #[repr(C)]
85 struct TreeMetadata {
85 struct TreeMetadata {
86 root_nodes: ChildNodes,
86 root_nodes: ChildNodes,
87 nodes_with_entry_count: Size,
87 nodes_with_entry_count: Size,
88 nodes_with_copy_source_count: Size,
88 nodes_with_copy_source_count: Size,
89
89
90 /// How many bytes of this data file are not used anymore
90 /// How many bytes of this data file are not used anymore
91 unreachable_bytes: Size,
91 unreachable_bytes: Size,
92
92
93 /// Current version always sets these bytes to zero when creating or
93 /// Current version always sets these bytes to zero when creating or
94 /// updating a dirstate. Future versions could assign some bits to signal
94 /// updating a dirstate. Future versions could assign some bits to signal
95 /// for example "the version that last wrote/updated this dirstate did so
95 /// for example "the version that last wrote/updated this dirstate did so
96 /// in such and such way that can be relied on by versions that know to."
96 /// in such and such way that can be relied on by versions that know to."
97 unused: [u8; 4],
97 unused: [u8; 4],
98
98
99 /// If non-zero, a hash of ignore files that were used for some previous
99 /// If non-zero, a hash of ignore files that were used for some previous
100 /// run of the `status` algorithm.
100 /// run of the `status` algorithm.
101 ///
101 ///
102 /// We define:
102 /// We define:
103 ///
103 ///
104 /// * "Root" ignore files are `.hgignore` at the root of the repository if
104 /// * "Root" ignore files are `.hgignore` at the root of the repository if
105 /// it exists, and files from `ui.ignore.*` config. This set of files is
105 /// it exists, and files from `ui.ignore.*` config. This set of files is
106 /// then sorted by the string representation of their path.
106 /// then sorted by the string representation of their path.
107 /// * The "expanded contents" of an ignore files is the byte string made
107 /// * The "expanded contents" of an ignore files is the byte string made
108 /// by concatenating its contents with the "expanded contents" of other
108 /// by concatenating its contents with the "expanded contents" of other
109 /// files included with `include:` or `subinclude:` files, in inclusion
109 /// files included with `include:` or `subinclude:` files, in inclusion
110 /// order. This definition is recursive, as included files can
110 /// order. This definition is recursive, as included files can
111 /// themselves include more files.
111 /// themselves include more files.
112 ///
112 ///
113 /// This hash is defined as the SHA-1 of the concatenation (in sorted
113 /// This hash is defined as the SHA-1 of the concatenation (in sorted
114 /// order) of the "expanded contents" of each "root" ignore file.
114 /// order) of the "expanded contents" of each "root" ignore file.
115 /// (Note that computing this does not require actually concatenating byte
115 /// (Note that computing this does not require actually concatenating byte
116 /// strings into contiguous memory, instead SHA-1 hashing can be done
116 /// strings into contiguous memory, instead SHA-1 hashing can be done
117 /// incrementally.)
117 /// incrementally.)
118 ignore_patterns_hash: IgnorePatternsHash,
118 ignore_patterns_hash: IgnorePatternsHash,
119 }
119 }
120
120
121 #[derive(BytesCast)]
121 #[derive(BytesCast)]
122 #[repr(C)]
122 #[repr(C)]
123 pub(super) struct Node {
123 pub(super) struct Node {
124 full_path: PathSlice,
124 full_path: PathSlice,
125
125
126 /// In bytes from `self.full_path.start`
126 /// In bytes from `self.full_path.start`
127 base_name_start: PathSize,
127 base_name_start: PathSize,
128
128
129 copy_source: OptPathSlice,
129 copy_source: OptPathSlice,
130 children: ChildNodes,
130 children: ChildNodes,
131 pub(super) descendants_with_entry_count: Size,
131 pub(super) descendants_with_entry_count: Size,
132 pub(super) tracked_descendants_count: Size,
132 pub(super) tracked_descendants_count: Size,
133
133
134 /// Depending on the value of `state`:
134 /// Depending on the bits in `flags`:
135 ///
136 /// * If any of `WDIR_TRACKED`, `P1_TRACKED`, or `P2_INFO` are set, the
137 /// node has an entry.
135 ///
138 ///
136 /// * A null byte: `data` is not used.
139 /// - If `HAS_MODE_AND_SIZE` is set, `data.mode` and `data.size` are
140 /// meaningful. Otherwise they are set to zero
141 /// - If `HAS_MTIME` is set, `data.mtime` is meaningful. Otherwise it is
142 /// set to zero.
137 ///
143 ///
138 /// * A `n`, `a`, `r`, or `m` ASCII byte: `state` and `data` together
144 /// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO`, or `HAS_MTIME`
139 /// represent a dirstate entry like in the v1 format.
145 /// are set, the node does not have an entry and `data` is set to all
146 /// zeros.
140 ///
147 ///
141 /// * A `d` ASCII byte: the bytes of `data` should instead be interpreted
148 /// * If none of `WDIR_TRACKED`, `P1_TRACKED`, `P2_INFO` are set, but
142 /// as the `Timestamp` for the mtime of a cached directory.
149 /// `HAS_MTIME` is set, the bytes of `data` should instead be
150 /// interpreted as the `Timestamp` for the mtime of a cached directory.
143 ///
151 ///
144 /// The presence of this state means that at some point, this path in
152 /// The presence of this combination of flags means that at some point,
145 /// the working directory was observed:
153 /// this path in the working directory was observed:
146 ///
154 ///
147 /// - To be a directory
155 /// - To be a directory
148 /// - With the modification time as given by `Timestamp`
156 /// - With the modification time as given by `Timestamp`
149 /// - That timestamp was already strictly in the past when observed,
157 /// - That timestamp was already strictly in the past when observed,
150 /// meaning that later changes cannot happen in the same clock tick
158 /// meaning that later changes cannot happen in the same clock tick
151 /// and must cause a different modification time (unless the system
159 /// and must cause a different modification time (unless the system
152 /// clock jumps back and we get unlucky, which is not impossible but
160 /// clock jumps back and we get unlucky, which is not impossible but
153 /// but deemed unlikely enough).
161 /// but deemed unlikely enough).
154 /// - All direct children of this directory (as returned by
162 /// - All direct children of this directory (as returned by
155 /// `std::fs::read_dir`) either have a corresponding dirstate node, or
163 /// `std::fs::read_dir`) either have a corresponding dirstate node, or
156 /// are ignored by ignore patterns whose hash is in
164 /// are ignored by ignore patterns whose hash is in
157 /// `TreeMetadata::ignore_patterns_hash`.
165 /// `TreeMetadata::ignore_patterns_hash`.
158 ///
166 ///
159 /// This means that if `std::fs::symlink_metadata` later reports the
167 /// This means that if `std::fs::symlink_metadata` later reports the
160 /// same modification time and ignored patterns haven’t changed, a run
168 /// same modification time and ignored patterns haven’t changed, a run
161 /// of status that is not listing ignored files can skip calling
169 /// of status that is not listing ignored files can skip calling
162 /// `std::fs::read_dir` again for this directory, iterate child
170 /// `std::fs::read_dir` again for this directory, iterate child
163 /// dirstate nodes instead.
171 /// dirstate nodes instead.
164 state: u8,
172 flags: Flags,
165 data: Entry,
173 data: Entry,
166 }
174 }
167
175
168 #[derive(BytesCast, Copy, Clone)]
176 bitflags! {
177 #[derive(BytesCast)]
178 #[repr(C)]
179 struct Flags: u8 {
180 const WDIR_TRACKED = 1 << 0;
181 const P1_TRACKED = 1 << 1;
182 const P2_INFO = 1 << 2;
183 const HAS_MODE_AND_SIZE = 1 << 3;
184 const HAS_MTIME = 1 << 4;
185 }
186 }
187
188 #[derive(BytesCast, Copy, Clone, Debug)]
169 #[repr(C)]
189 #[repr(C)]
170 struct Entry {
190 struct Entry {
171 mode: I32Be,
191 mode: I32Be,
172 mtime: I32Be,
192 mtime: I32Be,
173 size: I32Be,
193 size: I32Be,
174 }
194 }
175
195
176 /// Duration since the Unix epoch
196 /// Duration since the Unix epoch
177 #[derive(BytesCast, Copy, Clone, PartialEq)]
197 #[derive(BytesCast, Copy, Clone, PartialEq)]
178 #[repr(C)]
198 #[repr(C)]
179 pub(super) struct Timestamp {
199 pub(super) struct Timestamp {
180 seconds: I64Be,
200 seconds: I64Be,
181
201
182 /// In `0 .. 1_000_000_000`.
202 /// In `0 .. 1_000_000_000`.
183 ///
203 ///
184 /// This timestamp is later or earlier than `(seconds, 0)` by this many
204 /// This timestamp is later or earlier than `(seconds, 0)` by this many
185 /// nanoseconds, if `seconds` is non-negative or negative, respectively.
205 /// nanoseconds, if `seconds` is non-negative or negative, respectively.
186 nanoseconds: U32Be,
206 nanoseconds: U32Be,
187 }
207 }
188
208
189 /// Counted in bytes from the start of the file
209 /// Counted in bytes from the start of the file
190 ///
210 ///
191 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
211 /// NOTE: not supporting `.hg/dirstate` files larger than 4 GiB.
192 type Offset = U32Be;
212 type Offset = U32Be;
193
213
194 /// Counted in number of items
214 /// Counted in number of items
195 ///
215 ///
196 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
216 /// NOTE: we choose not to support counting more than 4 billion nodes anywhere.
197 type Size = U32Be;
217 type Size = U32Be;
198
218
199 /// Counted in bytes
219 /// Counted in bytes
200 ///
220 ///
201 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
221 /// NOTE: we choose not to support file names/paths longer than 64 KiB.
202 type PathSize = U16Be;
222 type PathSize = U16Be;
203
223
204 /// A contiguous sequence of `len` times `Node`, representing the child nodes
224 /// A contiguous sequence of `len` times `Node`, representing the child nodes
205 /// of either some other node or of the repository root.
225 /// of either some other node or of the repository root.
206 ///
226 ///
207 /// Always sorted by ascending `full_path`, to allow binary search.
227 /// Always sorted by ascending `full_path`, to allow binary search.
208 /// Since nodes with the same parent nodes also have the same parent path,
228 /// Since nodes with the same parent nodes also have the same parent path,
209 /// only the `base_name`s need to be compared during binary search.
229 /// only the `base_name`s need to be compared during binary search.
210 #[derive(BytesCast, Copy, Clone)]
230 #[derive(BytesCast, Copy, Clone)]
211 #[repr(C)]
231 #[repr(C)]
212 struct ChildNodes {
232 struct ChildNodes {
213 start: Offset,
233 start: Offset,
214 len: Size,
234 len: Size,
215 }
235 }
216
236
217 /// A `HgPath` of `len` bytes
237 /// A `HgPath` of `len` bytes
218 #[derive(BytesCast, Copy, Clone)]
238 #[derive(BytesCast, Copy, Clone)]
219 #[repr(C)]
239 #[repr(C)]
220 struct PathSlice {
240 struct PathSlice {
221 start: Offset,
241 start: Offset,
222 len: PathSize,
242 len: PathSize,
223 }
243 }
224
244
225 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
245 /// Either nothing if `start == 0`, or a `HgPath` of `len` bytes
226 type OptPathSlice = PathSlice;
246 type OptPathSlice = PathSlice;
227
247
228 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
248 /// Unexpected file format found in `.hg/dirstate` with the "v2" format.
229 ///
249 ///
230 /// This should only happen if Mercurial is buggy or a repository is corrupted.
250 /// This should only happen if Mercurial is buggy or a repository is corrupted.
231 #[derive(Debug)]
251 #[derive(Debug)]
232 pub struct DirstateV2ParseError;
252 pub struct DirstateV2ParseError;
233
253
234 impl From<DirstateV2ParseError> for HgError {
254 impl From<DirstateV2ParseError> for HgError {
235 fn from(_: DirstateV2ParseError) -> Self {
255 fn from(_: DirstateV2ParseError) -> Self {
236 HgError::corrupted("dirstate-v2 parse error")
256 HgError::corrupted("dirstate-v2 parse error")
237 }
257 }
238 }
258 }
239
259
240 impl From<DirstateV2ParseError> for crate::DirstateError {
260 impl From<DirstateV2ParseError> for crate::DirstateError {
241 fn from(error: DirstateV2ParseError) -> Self {
261 fn from(error: DirstateV2ParseError) -> Self {
242 HgError::from(error).into()
262 HgError::from(error).into()
243 }
263 }
244 }
264 }
245
265
246 impl<'on_disk> Docket<'on_disk> {
266 impl<'on_disk> Docket<'on_disk> {
247 pub fn parents(&self) -> DirstateParents {
267 pub fn parents(&self) -> DirstateParents {
248 use crate::Node;
268 use crate::Node;
249 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
269 let p1 = Node::try_from(&self.header.parent_1[..USED_NODE_ID_BYTES])
250 .unwrap()
270 .unwrap()
251 .clone();
271 .clone();
252 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
272 let p2 = Node::try_from(&self.header.parent_2[..USED_NODE_ID_BYTES])
253 .unwrap()
273 .unwrap()
254 .clone();
274 .clone();
255 DirstateParents { p1, p2 }
275 DirstateParents { p1, p2 }
256 }
276 }
257
277
258 pub fn tree_metadata(&self) -> &[u8] {
278 pub fn tree_metadata(&self) -> &[u8] {
259 self.header.metadata.as_bytes()
279 self.header.metadata.as_bytes()
260 }
280 }
261
281
262 pub fn data_size(&self) -> usize {
282 pub fn data_size(&self) -> usize {
263 // This `unwrap` could only panic on a 16-bit CPU
283 // This `unwrap` could only panic on a 16-bit CPU
264 self.header.data_size.get().try_into().unwrap()
284 self.header.data_size.get().try_into().unwrap()
265 }
285 }
266
286
267 pub fn data_filename(&self) -> String {
287 pub fn data_filename(&self) -> String {
268 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
288 String::from_utf8(format_bytes!(b"dirstate.{}", self.uuid)).unwrap()
269 }
289 }
270 }
290 }
271
291
272 pub fn read_docket(
292 pub fn read_docket(
273 on_disk: &[u8],
293 on_disk: &[u8],
274 ) -> Result<Docket<'_>, DirstateV2ParseError> {
294 ) -> Result<Docket<'_>, DirstateV2ParseError> {
275 let (header, uuid) =
295 let (header, uuid) =
276 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
296 DocketHeader::from_bytes(on_disk).map_err(|_| DirstateV2ParseError)?;
277 let uuid_size = header.uuid_size as usize;
297 let uuid_size = header.uuid_size as usize;
278 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
298 if header.marker == *V2_FORMAT_MARKER && uuid.len() == uuid_size {
279 Ok(Docket { header, uuid })
299 Ok(Docket { header, uuid })
280 } else {
300 } else {
281 Err(DirstateV2ParseError)
301 Err(DirstateV2ParseError)
282 }
302 }
283 }
303 }
284
304
285 pub(super) fn read<'on_disk>(
305 pub(super) fn read<'on_disk>(
286 on_disk: &'on_disk [u8],
306 on_disk: &'on_disk [u8],
287 metadata: &[u8],
307 metadata: &[u8],
288 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
308 ) -> Result<DirstateMap<'on_disk>, DirstateV2ParseError> {
289 if on_disk.is_empty() {
309 if on_disk.is_empty() {
290 return Ok(DirstateMap::empty(on_disk));
310 return Ok(DirstateMap::empty(on_disk));
291 }
311 }
292 let (meta, _) = TreeMetadata::from_bytes(metadata)
312 let (meta, _) = TreeMetadata::from_bytes(metadata)
293 .map_err(|_| DirstateV2ParseError)?;
313 .map_err(|_| DirstateV2ParseError)?;
294 let dirstate_map = DirstateMap {
314 let dirstate_map = DirstateMap {
295 on_disk,
315 on_disk,
296 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
316 root: dirstate_map::ChildNodes::OnDisk(read_nodes(
297 on_disk,
317 on_disk,
298 meta.root_nodes,
318 meta.root_nodes,
299 )?),
319 )?),
300 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
320 nodes_with_entry_count: meta.nodes_with_entry_count.get(),
301 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
321 nodes_with_copy_source_count: meta.nodes_with_copy_source_count.get(),
302 ignore_patterns_hash: meta.ignore_patterns_hash,
322 ignore_patterns_hash: meta.ignore_patterns_hash,
303 unreachable_bytes: meta.unreachable_bytes.get(),
323 unreachable_bytes: meta.unreachable_bytes.get(),
304 };
324 };
305 Ok(dirstate_map)
325 Ok(dirstate_map)
306 }
326 }
307
327
308 impl Node {
328 impl Node {
309 pub(super) fn full_path<'on_disk>(
329 pub(super) fn full_path<'on_disk>(
310 &self,
330 &self,
311 on_disk: &'on_disk [u8],
331 on_disk: &'on_disk [u8],
312 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
332 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
313 read_hg_path(on_disk, self.full_path)
333 read_hg_path(on_disk, self.full_path)
314 }
334 }
315
335
316 pub(super) fn base_name_start<'on_disk>(
336 pub(super) fn base_name_start<'on_disk>(
317 &self,
337 &self,
318 ) -> Result<usize, DirstateV2ParseError> {
338 ) -> Result<usize, DirstateV2ParseError> {
319 let start = self.base_name_start.get();
339 let start = self.base_name_start.get();
320 if start < self.full_path.len.get() {
340 if start < self.full_path.len.get() {
321 let start = usize::try_from(start)
341 let start = usize::try_from(start)
322 // u32 -> usize, could only panic on a 16-bit CPU
342 // u32 -> usize, could only panic on a 16-bit CPU
323 .expect("dirstate-v2 base_name_start out of bounds");
343 .expect("dirstate-v2 base_name_start out of bounds");
324 Ok(start)
344 Ok(start)
325 } else {
345 } else {
326 Err(DirstateV2ParseError)
346 Err(DirstateV2ParseError)
327 }
347 }
328 }
348 }
329
349
330 pub(super) fn base_name<'on_disk>(
350 pub(super) fn base_name<'on_disk>(
331 &self,
351 &self,
332 on_disk: &'on_disk [u8],
352 on_disk: &'on_disk [u8],
333 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
353 ) -> Result<&'on_disk HgPath, DirstateV2ParseError> {
334 let full_path = self.full_path(on_disk)?;
354 let full_path = self.full_path(on_disk)?;
335 let base_name_start = self.base_name_start()?;
355 let base_name_start = self.base_name_start()?;
336 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
356 Ok(HgPath::new(&full_path.as_bytes()[base_name_start..]))
337 }
357 }
338
358
339 pub(super) fn path<'on_disk>(
359 pub(super) fn path<'on_disk>(
340 &self,
360 &self,
341 on_disk: &'on_disk [u8],
361 on_disk: &'on_disk [u8],
342 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
362 ) -> Result<dirstate_map::NodeKey<'on_disk>, DirstateV2ParseError> {
343 Ok(WithBasename::from_raw_parts(
363 Ok(WithBasename::from_raw_parts(
344 Cow::Borrowed(self.full_path(on_disk)?),
364 Cow::Borrowed(self.full_path(on_disk)?),
345 self.base_name_start()?,
365 self.base_name_start()?,
346 ))
366 ))
347 }
367 }
348
368
349 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
369 pub(super) fn has_copy_source<'on_disk>(&self) -> bool {
350 self.copy_source.start.get() != 0
370 self.copy_source.start.get() != 0
351 }
371 }
352
372
353 pub(super) fn copy_source<'on_disk>(
373 pub(super) fn copy_source<'on_disk>(
354 &self,
374 &self,
355 on_disk: &'on_disk [u8],
375 on_disk: &'on_disk [u8],
356 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
376 ) -> Result<Option<&'on_disk HgPath>, DirstateV2ParseError> {
357 Ok(if self.has_copy_source() {
377 Ok(if self.has_copy_source() {
358 Some(read_hg_path(on_disk, self.copy_source)?)
378 Some(read_hg_path(on_disk, self.copy_source)?)
359 } else {
379 } else {
360 None
380 None
361 })
381 })
362 }
382 }
363
383
384 fn has_entry(&self) -> bool {
385 self.flags.intersects(
386 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
387 )
388 }
389
364 pub(super) fn node_data(
390 pub(super) fn node_data(
365 &self,
391 &self,
366 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
392 ) -> Result<dirstate_map::NodeData, DirstateV2ParseError> {
367 let entry = |state| {
393 if self.has_entry() {
368 dirstate_map::NodeData::Entry(self.entry_with_given_state(state))
394 Ok(dirstate_map::NodeData::Entry(self.assume_entry()))
369 };
395 } else if let Some(&mtime) = self.cached_directory_mtime() {
370
396 Ok(dirstate_map::NodeData::CachedDirectory { mtime })
371 match self.state {
397 } else {
372 b'\0' => Ok(dirstate_map::NodeData::None),
398 Ok(dirstate_map::NodeData::None)
373 b'd' => Ok(dirstate_map::NodeData::CachedDirectory {
374 mtime: *self.data.as_timestamp(),
375 }),
376 b'n' => Ok(entry(EntryState::Normal)),
377 b'a' => Ok(entry(EntryState::Added)),
378 b'r' => Ok(entry(EntryState::Removed)),
379 b'm' => Ok(entry(EntryState::Merged)),
380 _ => Err(DirstateV2ParseError),
381 }
399 }
382 }
400 }
383
401
384 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
402 pub(super) fn cached_directory_mtime(&self) -> Option<&Timestamp> {
385 if self.state == b'd' {
403 if self.flags.contains(Flags::HAS_MTIME) && !self.has_entry() {
386 Some(self.data.as_timestamp())
404 Some(self.data.as_timestamp())
387 } else {
405 } else {
388 None
406 None
389 }
407 }
390 }
408 }
391
409
392 pub(super) fn state(
410 fn assume_entry(&self) -> DirstateEntry {
393 &self,
411 // TODO: convert through raw bits instead?
394 ) -> Result<Option<EntryState>, DirstateV2ParseError> {
412 let wdir_tracked = self.flags.contains(Flags::WDIR_TRACKED);
395 match self.state {
413 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
396 b'\0' | b'd' => Ok(None),
414 let p2_info = self.flags.contains(Flags::P2_INFO);
397 b'n' => Ok(Some(EntryState::Normal)),
415 let mode_size = if self.flags.contains(Flags::HAS_MODE_AND_SIZE) {
398 b'a' => Ok(Some(EntryState::Added)),
416 Some((self.data.mode.into(), self.data.size.into()))
399 b'r' => Ok(Some(EntryState::Removed)),
417 } else {
400 b'm' => Ok(Some(EntryState::Merged)),
418 None
401 _ => Err(DirstateV2ParseError),
419 };
402 }
420 let mtime = if self.flags.contains(Flags::HAS_MTIME) {
403 }
421 Some(self.data.mtime.into())
404
422 } else {
405 fn entry_with_given_state(&self, state: EntryState) -> DirstateEntry {
423 None
406 // For now, the on-disk representation of DirstateEntry in dirstate-v2
424 };
407 // format is equivalent to that of dirstate-v1. When that changes, add
425 DirstateEntry::from_v2_data(
408 // a new constructor.
426 wdir_tracked,
409 DirstateEntry::from_v1_data(
427 p1_tracked,
410 state,
428 p2_info,
411 self.data.mode.get(),
429 mode_size,
412 self.data.size.get(),
430 mtime,
413 self.data.mtime.get(),
414 )
431 )
415 }
432 }
416
433
417 pub(super) fn entry(
434 pub(super) fn entry(
418 &self,
435 &self,
419 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
436 ) -> Result<Option<DirstateEntry>, DirstateV2ParseError> {
420 Ok(self
437 if self.has_entry() {
421 .state()?
438 Ok(Some(self.assume_entry()))
422 .map(|state| self.entry_with_given_state(state)))
439 } else {
440 Ok(None)
441 }
423 }
442 }
424
443
425 pub(super) fn children<'on_disk>(
444 pub(super) fn children<'on_disk>(
426 &self,
445 &self,
427 on_disk: &'on_disk [u8],
446 on_disk: &'on_disk [u8],
428 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
447 ) -> Result<&'on_disk [Node], DirstateV2ParseError> {
429 read_nodes(on_disk, self.children)
448 read_nodes(on_disk, self.children)
430 }
449 }
431
450
432 pub(super) fn to_in_memory_node<'on_disk>(
451 pub(super) fn to_in_memory_node<'on_disk>(
433 &self,
452 &self,
434 on_disk: &'on_disk [u8],
453 on_disk: &'on_disk [u8],
435 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
454 ) -> Result<dirstate_map::Node<'on_disk>, DirstateV2ParseError> {
436 Ok(dirstate_map::Node {
455 Ok(dirstate_map::Node {
437 children: dirstate_map::ChildNodes::OnDisk(
456 children: dirstate_map::ChildNodes::OnDisk(
438 self.children(on_disk)?,
457 self.children(on_disk)?,
439 ),
458 ),
440 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
459 copy_source: self.copy_source(on_disk)?.map(Cow::Borrowed),
441 data: self.node_data()?,
460 data: self.node_data()?,
442 descendants_with_entry_count: self
461 descendants_with_entry_count: self
443 .descendants_with_entry_count
462 .descendants_with_entry_count
444 .get(),
463 .get(),
445 tracked_descendants_count: self.tracked_descendants_count.get(),
464 tracked_descendants_count: self.tracked_descendants_count.get(),
446 })
465 })
447 }
466 }
448 }
467 }
449
468
450 impl Entry {
469 impl Entry {
470 fn from_dirstate_entry(entry: &DirstateEntry) -> (Flags, Self) {
471 let (wdir_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt) =
472 entry.v2_data();
473 // TODO: convert throug raw flag bits instead?
474 let mut flags = Flags::empty();
475 flags.set(Flags::WDIR_TRACKED, wdir_tracked);
476 flags.set(Flags::P1_TRACKED, p1_tracked);
477 flags.set(Flags::P2_INFO, p2_info);
478 let (mode, size, mtime);
479 if let Some((m, s)) = mode_size_opt {
480 mode = m;
481 size = s;
482 flags.insert(Flags::HAS_MODE_AND_SIZE)
483 } else {
484 mode = 0;
485 size = 0;
486 }
487 if let Some(m) = mtime_opt {
488 mtime = m;
489 flags.insert(Flags::HAS_MTIME);
490 } else {
491 mtime = 0;
492 }
493 let raw_entry = Entry {
494 mode: mode.into(),
495 size: size.into(),
496 mtime: mtime.into(),
497 };
498 (flags, raw_entry)
499 }
500
451 fn from_timestamp(timestamp: Timestamp) -> Self {
501 fn from_timestamp(timestamp: Timestamp) -> Self {
452 // Safety: both types implement the `ByteCast` trait, so we could
502 // Safety: both types implement the `ByteCast` trait, so we could
453 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
503 // safely use `as_bytes` and `from_bytes` to do this conversion. Using
454 // `transmute` instead makes the compiler check that the two types
504 // `transmute` instead makes the compiler check that the two types
455 // have the same size, which eliminates the error case of
505 // have the same size, which eliminates the error case of
456 // `from_bytes`.
506 // `from_bytes`.
457 unsafe { std::mem::transmute::<Timestamp, Entry>(timestamp) }
507 unsafe { std::mem::transmute::<Timestamp, Entry>(timestamp) }
458 }
508 }
459
509
460 fn as_timestamp(&self) -> &Timestamp {
510 fn as_timestamp(&self) -> &Timestamp {
461 // Safety: same as above in `from_timestamp`
511 // Safety: same as above in `from_timestamp`
462 unsafe { &*(self as *const Entry as *const Timestamp) }
512 unsafe { &*(self as *const Entry as *const Timestamp) }
463 }
513 }
464 }
514 }
465
515
466 impl Timestamp {
516 impl Timestamp {
467 pub fn seconds(&self) -> i64 {
517 pub fn seconds(&self) -> i64 {
468 self.seconds.get()
518 self.seconds.get()
469 }
519 }
470 }
520 }
471
521
472 impl From<SystemTime> for Timestamp {
522 impl From<SystemTime> for Timestamp {
473 fn from(system_time: SystemTime) -> Self {
523 fn from(system_time: SystemTime) -> Self {
474 let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) {
524 let (secs, nanos) = match system_time.duration_since(UNIX_EPOCH) {
475 Ok(duration) => {
525 Ok(duration) => {
476 (duration.as_secs() as i64, duration.subsec_nanos())
526 (duration.as_secs() as i64, duration.subsec_nanos())
477 }
527 }
478 Err(error) => {
528 Err(error) => {
479 let negative = error.duration();
529 let negative = error.duration();
480 (-(negative.as_secs() as i64), negative.subsec_nanos())
530 (-(negative.as_secs() as i64), negative.subsec_nanos())
481 }
531 }
482 };
532 };
483 Timestamp {
533 Timestamp {
484 seconds: secs.into(),
534 seconds: secs.into(),
485 nanoseconds: nanos.into(),
535 nanoseconds: nanos.into(),
486 }
536 }
487 }
537 }
488 }
538 }
489
539
490 impl From<&'_ Timestamp> for SystemTime {
540 impl From<&'_ Timestamp> for SystemTime {
491 fn from(timestamp: &'_ Timestamp) -> Self {
541 fn from(timestamp: &'_ Timestamp) -> Self {
492 let secs = timestamp.seconds.get();
542 let secs = timestamp.seconds.get();
493 let nanos = timestamp.nanoseconds.get();
543 let nanos = timestamp.nanoseconds.get();
494 if secs >= 0 {
544 if secs >= 0 {
495 UNIX_EPOCH + Duration::new(secs as u64, nanos)
545 UNIX_EPOCH + Duration::new(secs as u64, nanos)
496 } else {
546 } else {
497 UNIX_EPOCH - Duration::new((-secs) as u64, nanos)
547 UNIX_EPOCH - Duration::new((-secs) as u64, nanos)
498 }
548 }
499 }
549 }
500 }
550 }
501
551
502 fn read_hg_path(
552 fn read_hg_path(
503 on_disk: &[u8],
553 on_disk: &[u8],
504 slice: PathSlice,
554 slice: PathSlice,
505 ) -> Result<&HgPath, DirstateV2ParseError> {
555 ) -> Result<&HgPath, DirstateV2ParseError> {
506 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
556 read_slice(on_disk, slice.start, slice.len.get()).map(HgPath::new)
507 }
557 }
508
558
509 fn read_nodes(
559 fn read_nodes(
510 on_disk: &[u8],
560 on_disk: &[u8],
511 slice: ChildNodes,
561 slice: ChildNodes,
512 ) -> Result<&[Node], DirstateV2ParseError> {
562 ) -> Result<&[Node], DirstateV2ParseError> {
513 read_slice(on_disk, slice.start, slice.len.get())
563 read_slice(on_disk, slice.start, slice.len.get())
514 }
564 }
515
565
516 fn read_slice<T, Len>(
566 fn read_slice<T, Len>(
517 on_disk: &[u8],
567 on_disk: &[u8],
518 start: Offset,
568 start: Offset,
519 len: Len,
569 len: Len,
520 ) -> Result<&[T], DirstateV2ParseError>
570 ) -> Result<&[T], DirstateV2ParseError>
521 where
571 where
522 T: BytesCast,
572 T: BytesCast,
523 Len: TryInto<usize>,
573 Len: TryInto<usize>,
524 {
574 {
525 // Either `usize::MAX` would result in "out of bounds" error since a single
575 // Either `usize::MAX` would result in "out of bounds" error since a single
526 // `&[u8]` cannot occupy the entire addess space.
576 // `&[u8]` cannot occupy the entire addess space.
527 let start = start.get().try_into().unwrap_or(std::usize::MAX);
577 let start = start.get().try_into().unwrap_or(std::usize::MAX);
528 let len = len.try_into().unwrap_or(std::usize::MAX);
578 let len = len.try_into().unwrap_or(std::usize::MAX);
529 on_disk
579 on_disk
530 .get(start..)
580 .get(start..)
531 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
581 .and_then(|bytes| T::slice_from_bytes(bytes, len).ok())
532 .map(|(slice, _rest)| slice)
582 .map(|(slice, _rest)| slice)
533 .ok_or_else(|| DirstateV2ParseError)
583 .ok_or_else(|| DirstateV2ParseError)
534 }
584 }
535
585
536 pub(crate) fn for_each_tracked_path<'on_disk>(
586 pub(crate) fn for_each_tracked_path<'on_disk>(
537 on_disk: &'on_disk [u8],
587 on_disk: &'on_disk [u8],
538 metadata: &[u8],
588 metadata: &[u8],
539 mut f: impl FnMut(&'on_disk HgPath),
589 mut f: impl FnMut(&'on_disk HgPath),
540 ) -> Result<(), DirstateV2ParseError> {
590 ) -> Result<(), DirstateV2ParseError> {
541 let (meta, _) = TreeMetadata::from_bytes(metadata)
591 let (meta, _) = TreeMetadata::from_bytes(metadata)
542 .map_err(|_| DirstateV2ParseError)?;
592 .map_err(|_| DirstateV2ParseError)?;
543 fn recur<'on_disk>(
593 fn recur<'on_disk>(
544 on_disk: &'on_disk [u8],
594 on_disk: &'on_disk [u8],
545 nodes: ChildNodes,
595 nodes: ChildNodes,
546 f: &mut impl FnMut(&'on_disk HgPath),
596 f: &mut impl FnMut(&'on_disk HgPath),
547 ) -> Result<(), DirstateV2ParseError> {
597 ) -> Result<(), DirstateV2ParseError> {
548 for node in read_nodes(on_disk, nodes)? {
598 for node in read_nodes(on_disk, nodes)? {
549 if let Some(state) = node.state()? {
599 if let Some(entry) = node.entry()? {
550 if state.is_tracked() {
600 if entry.state().is_tracked() {
551 f(node.full_path(on_disk)?)
601 f(node.full_path(on_disk)?)
552 }
602 }
553 }
603 }
554 recur(on_disk, node.children, f)?
604 recur(on_disk, node.children, f)?
555 }
605 }
556 Ok(())
606 Ok(())
557 }
607 }
558 recur(on_disk, meta.root_nodes, &mut f)
608 recur(on_disk, meta.root_nodes, &mut f)
559 }
609 }
560
610
561 /// Returns new data and metadata, together with whether that data should be
611 /// Returns new data and metadata, together with whether that data should be
562 /// appended to the existing data file whose content is at
612 /// appended to the existing data file whose content is at
563 /// `dirstate_map.on_disk` (true), instead of written to a new data file
613 /// `dirstate_map.on_disk` (true), instead of written to a new data file
564 /// (false).
614 /// (false).
565 pub(super) fn write(
615 pub(super) fn write(
566 dirstate_map: &mut DirstateMap,
616 dirstate_map: &mut DirstateMap,
567 can_append: bool,
617 can_append: bool,
568 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
618 ) -> Result<(Vec<u8>, Vec<u8>, bool), DirstateError> {
569 let append = can_append && dirstate_map.write_should_append();
619 let append = can_append && dirstate_map.write_should_append();
570
620
571 // This ignores the space for paths, and for nodes without an entry.
621 // This ignores the space for paths, and for nodes without an entry.
572 // TODO: better estimate? Skip the `Vec` and write to a file directly?
622 // TODO: better estimate? Skip the `Vec` and write to a file directly?
573 let size_guess = std::mem::size_of::<Node>()
623 let size_guess = std::mem::size_of::<Node>()
574 * dirstate_map.nodes_with_entry_count as usize;
624 * dirstate_map.nodes_with_entry_count as usize;
575
625
576 let mut writer = Writer {
626 let mut writer = Writer {
577 dirstate_map,
627 dirstate_map,
578 append,
628 append,
579 out: Vec::with_capacity(size_guess),
629 out: Vec::with_capacity(size_guess),
580 };
630 };
581
631
582 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
632 let root_nodes = writer.write_nodes(dirstate_map.root.as_ref())?;
583
633
584 let meta = TreeMetadata {
634 let meta = TreeMetadata {
585 root_nodes,
635 root_nodes,
586 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
636 nodes_with_entry_count: dirstate_map.nodes_with_entry_count.into(),
587 nodes_with_copy_source_count: dirstate_map
637 nodes_with_copy_source_count: dirstate_map
588 .nodes_with_copy_source_count
638 .nodes_with_copy_source_count
589 .into(),
639 .into(),
590 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
640 unreachable_bytes: dirstate_map.unreachable_bytes.into(),
591 unused: [0; 4],
641 unused: [0; 4],
592 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
642 ignore_patterns_hash: dirstate_map.ignore_patterns_hash,
593 };
643 };
594 Ok((writer.out, meta.as_bytes().to_vec(), append))
644 Ok((writer.out, meta.as_bytes().to_vec(), append))
595 }
645 }
596
646
597 struct Writer<'dmap, 'on_disk> {
647 struct Writer<'dmap, 'on_disk> {
598 dirstate_map: &'dmap DirstateMap<'on_disk>,
648 dirstate_map: &'dmap DirstateMap<'on_disk>,
599 append: bool,
649 append: bool,
600 out: Vec<u8>,
650 out: Vec<u8>,
601 }
651 }
602
652
603 impl Writer<'_, '_> {
653 impl Writer<'_, '_> {
604 fn write_nodes(
654 fn write_nodes(
605 &mut self,
655 &mut self,
606 nodes: dirstate_map::ChildNodesRef,
656 nodes: dirstate_map::ChildNodesRef,
607 ) -> Result<ChildNodes, DirstateError> {
657 ) -> Result<ChildNodes, DirstateError> {
608 // Reuse already-written nodes if possible
658 // Reuse already-written nodes if possible
609 if self.append {
659 if self.append {
610 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
660 if let dirstate_map::ChildNodesRef::OnDisk(nodes_slice) = nodes {
611 let start = self.on_disk_offset_of(nodes_slice).expect(
661 let start = self.on_disk_offset_of(nodes_slice).expect(
612 "dirstate-v2 OnDisk nodes not found within on_disk",
662 "dirstate-v2 OnDisk nodes not found within on_disk",
613 );
663 );
614 let len = child_nodes_len_from_usize(nodes_slice.len());
664 let len = child_nodes_len_from_usize(nodes_slice.len());
615 return Ok(ChildNodes { start, len });
665 return Ok(ChildNodes { start, len });
616 }
666 }
617 }
667 }
618
668
619 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
669 // `dirstate_map::ChildNodes::InMemory` contains a `HashMap` which has
620 // undefined iteration order. Sort to enable binary search in the
670 // undefined iteration order. Sort to enable binary search in the
621 // written file.
671 // written file.
622 let nodes = nodes.sorted();
672 let nodes = nodes.sorted();
623 let nodes_len = nodes.len();
673 let nodes_len = nodes.len();
624
674
625 // First accumulate serialized nodes in a `Vec`
675 // First accumulate serialized nodes in a `Vec`
626 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
676 let mut on_disk_nodes = Vec::with_capacity(nodes_len);
627 for node in nodes {
677 for node in nodes {
628 let children =
678 let children =
629 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
679 self.write_nodes(node.children(self.dirstate_map.on_disk)?)?;
630 let full_path = node.full_path(self.dirstate_map.on_disk)?;
680 let full_path = node.full_path(self.dirstate_map.on_disk)?;
631 let full_path = self.write_path(full_path.as_bytes());
681 let full_path = self.write_path(full_path.as_bytes());
632 let copy_source = if let Some(source) =
682 let copy_source = if let Some(source) =
633 node.copy_source(self.dirstate_map.on_disk)?
683 node.copy_source(self.dirstate_map.on_disk)?
634 {
684 {
635 self.write_path(source.as_bytes())
685 self.write_path(source.as_bytes())
636 } else {
686 } else {
637 PathSlice {
687 PathSlice {
638 start: 0.into(),
688 start: 0.into(),
639 len: 0.into(),
689 len: 0.into(),
640 }
690 }
641 };
691 };
642 on_disk_nodes.push(match node {
692 on_disk_nodes.push(match node {
643 NodeRef::InMemory(path, node) => {
693 NodeRef::InMemory(path, node) => {
644 let (state, data) = match &node.data {
694 let (flags, data) = match &node.data {
645 dirstate_map::NodeData::Entry(entry) => (
695 dirstate_map::NodeData::Entry(entry) => {
646 entry.state().into(),
696 Entry::from_dirstate_entry(entry)
647 Entry {
697 }
648 mode: entry.mode().into(),
649 mtime: entry.mtime().into(),
650 size: entry.size().into(),
651 },
652 ),
653 dirstate_map::NodeData::CachedDirectory { mtime } => {
698 dirstate_map::NodeData::CachedDirectory { mtime } => {
654 (b'd', Entry::from_timestamp(*mtime))
699 (Flags::HAS_MTIME, Entry::from_timestamp(*mtime))
655 }
700 }
656 dirstate_map::NodeData::None => (
701 dirstate_map::NodeData::None => (
657 b'\0',
702 Flags::empty(),
658 Entry {
703 Entry {
659 mode: 0.into(),
704 mode: 0.into(),
705 size: 0.into(),
660 mtime: 0.into(),
706 mtime: 0.into(),
661 size: 0.into(),
662 },
707 },
663 ),
708 ),
664 };
709 };
665 Node {
710 Node {
666 children,
711 children,
667 copy_source,
712 copy_source,
668 full_path,
713 full_path,
669 base_name_start: u16::try_from(path.base_name_start())
714 base_name_start: u16::try_from(path.base_name_start())
670 // Could only panic for paths over 64 KiB
715 // Could only panic for paths over 64 KiB
671 .expect("dirstate-v2 path length overflow")
716 .expect("dirstate-v2 path length overflow")
672 .into(),
717 .into(),
673 descendants_with_entry_count: node
718 descendants_with_entry_count: node
674 .descendants_with_entry_count
719 .descendants_with_entry_count
675 .into(),
720 .into(),
676 tracked_descendants_count: node
721 tracked_descendants_count: node
677 .tracked_descendants_count
722 .tracked_descendants_count
678 .into(),
723 .into(),
679 state,
724 flags,
680 data,
725 data,
681 }
726 }
682 }
727 }
683 NodeRef::OnDisk(node) => Node {
728 NodeRef::OnDisk(node) => Node {
684 children,
729 children,
685 copy_source,
730 copy_source,
686 full_path,
731 full_path,
687 ..*node
732 ..*node
688 },
733 },
689 })
734 })
690 }
735 }
691 // … so we can write them contiguously, after writing everything else
736 // … so we can write them contiguously, after writing everything else
692 // they refer to.
737 // they refer to.
693 let start = self.current_offset();
738 let start = self.current_offset();
694 let len = child_nodes_len_from_usize(nodes_len);
739 let len = child_nodes_len_from_usize(nodes_len);
695 self.out.extend(on_disk_nodes.as_bytes());
740 self.out.extend(on_disk_nodes.as_bytes());
696 Ok(ChildNodes { start, len })
741 Ok(ChildNodes { start, len })
697 }
742 }
698
743
699 /// If the given slice of items is within `on_disk`, returns its offset
744 /// If the given slice of items is within `on_disk`, returns its offset
700 /// from the start of `on_disk`.
745 /// from the start of `on_disk`.
701 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
746 fn on_disk_offset_of<T>(&self, slice: &[T]) -> Option<Offset>
702 where
747 where
703 T: BytesCast,
748 T: BytesCast,
704 {
749 {
705 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
750 fn address_range(slice: &[u8]) -> std::ops::RangeInclusive<usize> {
706 let start = slice.as_ptr() as usize;
751 let start = slice.as_ptr() as usize;
707 let end = start + slice.len();
752 let end = start + slice.len();
708 start..=end
753 start..=end
709 }
754 }
710 let slice_addresses = address_range(slice.as_bytes());
755 let slice_addresses = address_range(slice.as_bytes());
711 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
756 let on_disk_addresses = address_range(self.dirstate_map.on_disk);
712 if on_disk_addresses.contains(slice_addresses.start())
757 if on_disk_addresses.contains(slice_addresses.start())
713 && on_disk_addresses.contains(slice_addresses.end())
758 && on_disk_addresses.contains(slice_addresses.end())
714 {
759 {
715 let offset = slice_addresses.start() - on_disk_addresses.start();
760 let offset = slice_addresses.start() - on_disk_addresses.start();
716 Some(offset_from_usize(offset))
761 Some(offset_from_usize(offset))
717 } else {
762 } else {
718 None
763 None
719 }
764 }
720 }
765 }
721
766
722 fn current_offset(&mut self) -> Offset {
767 fn current_offset(&mut self) -> Offset {
723 let mut offset = self.out.len();
768 let mut offset = self.out.len();
724 if self.append {
769 if self.append {
725 offset += self.dirstate_map.on_disk.len()
770 offset += self.dirstate_map.on_disk.len()
726 }
771 }
727 offset_from_usize(offset)
772 offset_from_usize(offset)
728 }
773 }
729
774
730 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
775 fn write_path(&mut self, slice: &[u8]) -> PathSlice {
731 let len = path_len_from_usize(slice.len());
776 let len = path_len_from_usize(slice.len());
732 // Reuse an already-written path if possible
777 // Reuse an already-written path if possible
733 if self.append {
778 if self.append {
734 if let Some(start) = self.on_disk_offset_of(slice) {
779 if let Some(start) = self.on_disk_offset_of(slice) {
735 return PathSlice { start, len };
780 return PathSlice { start, len };
736 }
781 }
737 }
782 }
738 let start = self.current_offset();
783 let start = self.current_offset();
739 self.out.extend(slice.as_bytes());
784 self.out.extend(slice.as_bytes());
740 PathSlice { start, len }
785 PathSlice { start, len }
741 }
786 }
742 }
787 }
743
788
744 fn offset_from_usize(x: usize) -> Offset {
789 fn offset_from_usize(x: usize) -> Offset {
745 u32::try_from(x)
790 u32::try_from(x)
746 // Could only panic for a dirstate file larger than 4 GiB
791 // Could only panic for a dirstate file larger than 4 GiB
747 .expect("dirstate-v2 offset overflow")
792 .expect("dirstate-v2 offset overflow")
748 .into()
793 .into()
749 }
794 }
750
795
751 fn child_nodes_len_from_usize(x: usize) -> Size {
796 fn child_nodes_len_from_usize(x: usize) -> Size {
752 u32::try_from(x)
797 u32::try_from(x)
753 // Could only panic with over 4 billion nodes
798 // Could only panic with over 4 billion nodes
754 .expect("dirstate-v2 slice length overflow")
799 .expect("dirstate-v2 slice length overflow")
755 .into()
800 .into()
756 }
801 }
757
802
758 fn path_len_from_usize(x: usize) -> PathSize {
803 fn path_len_from_usize(x: usize) -> PathSize {
759 u16::try_from(x)
804 u16::try_from(x)
760 // Could only panic for paths over 64 KiB
805 // Could only panic for paths over 64 KiB
761 .expect("dirstate-v2 path length overflow")
806 .expect("dirstate-v2 path length overflow")
762 .into()
807 .into()
763 }
808 }
@@ -1,219 +1,219 b''
1 use cpython::exc;
1 use cpython::exc;
2 use cpython::PyBytes;
2 use cpython::PyBytes;
3 use cpython::PyErr;
3 use cpython::PyErr;
4 use cpython::PyNone;
4 use cpython::PyNone;
5 use cpython::PyObject;
5 use cpython::PyObject;
6 use cpython::PyResult;
6 use cpython::PyResult;
7 use cpython::Python;
7 use cpython::Python;
8 use cpython::PythonObject;
8 use cpython::PythonObject;
9 use hg::dirstate::DirstateEntry;
9 use hg::dirstate::DirstateEntry;
10 use hg::dirstate::EntryState;
10 use hg::dirstate::EntryState;
11 use std::cell::Cell;
11 use std::cell::Cell;
12 use std::convert::TryFrom;
12 use std::convert::TryFrom;
13
13
14 py_class!(pub class DirstateItem |py| {
14 py_class!(pub class DirstateItem |py| {
15 data entry: Cell<DirstateEntry>;
15 data entry: Cell<DirstateEntry>;
16
16
17 def __new__(
17 def __new__(
18 _cls,
18 _cls,
19 wc_tracked: bool = false,
19 wc_tracked: bool = false,
20 p1_tracked: bool = false,
20 p1_tracked: bool = false,
21 p2_info: bool = false,
21 p2_info: bool = false,
22 has_meaningful_data: bool = true,
22 has_meaningful_data: bool = true,
23 has_meaningful_mtime: bool = true,
23 has_meaningful_mtime: bool = true,
24 parentfiledata: Option<(i32, i32, i32)> = None,
24 parentfiledata: Option<(i32, i32, i32)> = None,
25
25
26 ) -> PyResult<DirstateItem> {
26 ) -> PyResult<DirstateItem> {
27 let mut mode_size_opt = None;
27 let mut mode_size_opt = None;
28 let mut mtime_opt = None;
28 let mut mtime_opt = None;
29 if let Some((mode, size, mtime)) = parentfiledata {
29 if let Some((mode, size, mtime)) = parentfiledata {
30 if has_meaningful_data {
30 if has_meaningful_data {
31 mode_size_opt = Some((mode, size))
31 mode_size_opt = Some((mode, size))
32 }
32 }
33 if has_meaningful_mtime {
33 if has_meaningful_mtime {
34 mtime_opt = Some(mtime)
34 mtime_opt = Some(mtime)
35 }
35 }
36 }
36 }
37 let entry = DirstateEntry::new(
37 let entry = DirstateEntry::from_v2_data(
38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
38 wc_tracked, p1_tracked, p2_info, mode_size_opt, mtime_opt,
39 );
39 );
40 DirstateItem::create_instance(py, Cell::new(entry))
40 DirstateItem::create_instance(py, Cell::new(entry))
41 }
41 }
42
42
43 @property
43 @property
44 def state(&self) -> PyResult<PyBytes> {
44 def state(&self) -> PyResult<PyBytes> {
45 let state_byte: u8 = self.entry(py).get().state().into();
45 let state_byte: u8 = self.entry(py).get().state().into();
46 Ok(PyBytes::new(py, &[state_byte]))
46 Ok(PyBytes::new(py, &[state_byte]))
47 }
47 }
48
48
49 @property
49 @property
50 def mode(&self) -> PyResult<i32> {
50 def mode(&self) -> PyResult<i32> {
51 Ok(self.entry(py).get().mode())
51 Ok(self.entry(py).get().mode())
52 }
52 }
53
53
54 @property
54 @property
55 def size(&self) -> PyResult<i32> {
55 def size(&self) -> PyResult<i32> {
56 Ok(self.entry(py).get().size())
56 Ok(self.entry(py).get().size())
57 }
57 }
58
58
59 @property
59 @property
60 def mtime(&self) -> PyResult<i32> {
60 def mtime(&self) -> PyResult<i32> {
61 Ok(self.entry(py).get().mtime())
61 Ok(self.entry(py).get().mtime())
62 }
62 }
63
63
64 @property
64 @property
65 def tracked(&self) -> PyResult<bool> {
65 def tracked(&self) -> PyResult<bool> {
66 Ok(self.entry(py).get().tracked())
66 Ok(self.entry(py).get().tracked())
67 }
67 }
68
68
69 @property
69 @property
70 def added(&self) -> PyResult<bool> {
70 def added(&self) -> PyResult<bool> {
71 Ok(self.entry(py).get().added())
71 Ok(self.entry(py).get().added())
72 }
72 }
73
73
74 @property
74 @property
75 def merged(&self) -> PyResult<bool> {
75 def merged(&self) -> PyResult<bool> {
76 Ok(self.entry(py).get().merged())
76 Ok(self.entry(py).get().merged())
77 }
77 }
78
78
79 @property
79 @property
80 def removed(&self) -> PyResult<bool> {
80 def removed(&self) -> PyResult<bool> {
81 Ok(self.entry(py).get().removed())
81 Ok(self.entry(py).get().removed())
82 }
82 }
83
83
84 @property
84 @property
85 def from_p2(&self) -> PyResult<bool> {
85 def from_p2(&self) -> PyResult<bool> {
86 Ok(self.entry(py).get().from_p2())
86 Ok(self.entry(py).get().from_p2())
87 }
87 }
88
88
89 @property
89 @property
90 def maybe_clean(&self) -> PyResult<bool> {
90 def maybe_clean(&self) -> PyResult<bool> {
91 Ok(self.entry(py).get().maybe_clean())
91 Ok(self.entry(py).get().maybe_clean())
92 }
92 }
93
93
94 @property
94 @property
95 def any_tracked(&self) -> PyResult<bool> {
95 def any_tracked(&self) -> PyResult<bool> {
96 Ok(self.entry(py).get().any_tracked())
96 Ok(self.entry(py).get().any_tracked())
97 }
97 }
98
98
99 def v1_state(&self) -> PyResult<PyBytes> {
99 def v1_state(&self) -> PyResult<PyBytes> {
100 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
100 let (state, _mode, _size, _mtime) = self.entry(py).get().v1_data();
101 let state_byte: u8 = state.into();
101 let state_byte: u8 = state.into();
102 Ok(PyBytes::new(py, &[state_byte]))
102 Ok(PyBytes::new(py, &[state_byte]))
103 }
103 }
104
104
105 def v1_mode(&self) -> PyResult<i32> {
105 def v1_mode(&self) -> PyResult<i32> {
106 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
106 let (_state, mode, _size, _mtime) = self.entry(py).get().v1_data();
107 Ok(mode)
107 Ok(mode)
108 }
108 }
109
109
110 def v1_size(&self) -> PyResult<i32> {
110 def v1_size(&self) -> PyResult<i32> {
111 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
111 let (_state, _mode, size, _mtime) = self.entry(py).get().v1_data();
112 Ok(size)
112 Ok(size)
113 }
113 }
114
114
115 def v1_mtime(&self) -> PyResult<i32> {
115 def v1_mtime(&self) -> PyResult<i32> {
116 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
116 let (_state, _mode, _size, mtime) = self.entry(py).get().v1_data();
117 Ok(mtime)
117 Ok(mtime)
118 }
118 }
119
119
120 def need_delay(&self, now: i32) -> PyResult<bool> {
120 def need_delay(&self, now: i32) -> PyResult<bool> {
121 Ok(self.entry(py).get().mtime_is_ambiguous(now))
121 Ok(self.entry(py).get().mtime_is_ambiguous(now))
122 }
122 }
123
123
124 @classmethod
124 @classmethod
125 def from_v1_data(
125 def from_v1_data(
126 _cls,
126 _cls,
127 state: PyBytes,
127 state: PyBytes,
128 mode: i32,
128 mode: i32,
129 size: i32,
129 size: i32,
130 mtime: i32,
130 mtime: i32,
131 ) -> PyResult<Self> {
131 ) -> PyResult<Self> {
132 let state = <[u8; 1]>::try_from(state.data(py))
132 let state = <[u8; 1]>::try_from(state.data(py))
133 .ok()
133 .ok()
134 .and_then(|state| EntryState::try_from(state[0]).ok())
134 .and_then(|state| EntryState::try_from(state[0]).ok())
135 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
135 .ok_or_else(|| PyErr::new::<exc::ValueError, _>(py, "invalid state"))?;
136 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
136 let entry = DirstateEntry::from_v1_data(state, mode, size, mtime);
137 DirstateItem::create_instance(py, Cell::new(entry))
137 DirstateItem::create_instance(py, Cell::new(entry))
138 }
138 }
139
139
140 @classmethod
140 @classmethod
141 def new_added(_cls) -> PyResult<Self> {
141 def new_added(_cls) -> PyResult<Self> {
142 let entry = DirstateEntry::new_added();
142 let entry = DirstateEntry::new_added();
143 DirstateItem::create_instance(py, Cell::new(entry))
143 DirstateItem::create_instance(py, Cell::new(entry))
144 }
144 }
145
145
146 @classmethod
146 @classmethod
147 def new_merged(_cls) -> PyResult<Self> {
147 def new_merged(_cls) -> PyResult<Self> {
148 let entry = DirstateEntry::new_merged();
148 let entry = DirstateEntry::new_merged();
149 DirstateItem::create_instance(py, Cell::new(entry))
149 DirstateItem::create_instance(py, Cell::new(entry))
150 }
150 }
151
151
152 @classmethod
152 @classmethod
153 def new_from_p2(_cls) -> PyResult<Self> {
153 def new_from_p2(_cls) -> PyResult<Self> {
154 let entry = DirstateEntry::new_from_p2();
154 let entry = DirstateEntry::new_from_p2();
155 DirstateItem::create_instance(py, Cell::new(entry))
155 DirstateItem::create_instance(py, Cell::new(entry))
156 }
156 }
157
157
158 @classmethod
158 @classmethod
159 def new_possibly_dirty(_cls) -> PyResult<Self> {
159 def new_possibly_dirty(_cls) -> PyResult<Self> {
160 let entry = DirstateEntry::new_possibly_dirty();
160 let entry = DirstateEntry::new_possibly_dirty();
161 DirstateItem::create_instance(py, Cell::new(entry))
161 DirstateItem::create_instance(py, Cell::new(entry))
162 }
162 }
163
163
164 @classmethod
164 @classmethod
165 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
165 def new_normal(_cls, mode: i32, size: i32, mtime: i32) -> PyResult<Self> {
166 let entry = DirstateEntry::new_normal(mode, size, mtime);
166 let entry = DirstateEntry::new_normal(mode, size, mtime);
167 DirstateItem::create_instance(py, Cell::new(entry))
167 DirstateItem::create_instance(py, Cell::new(entry))
168 }
168 }
169
169
170 def drop_merge_data(&self) -> PyResult<PyNone> {
170 def drop_merge_data(&self) -> PyResult<PyNone> {
171 self.update(py, |entry| entry.drop_merge_data());
171 self.update(py, |entry| entry.drop_merge_data());
172 Ok(PyNone)
172 Ok(PyNone)
173 }
173 }
174
174
175 def set_clean(
175 def set_clean(
176 &self,
176 &self,
177 mode: i32,
177 mode: i32,
178 size: i32,
178 size: i32,
179 mtime: i32,
179 mtime: i32,
180 ) -> PyResult<PyNone> {
180 ) -> PyResult<PyNone> {
181 self.update(py, |entry| entry.set_clean(mode, size, mtime));
181 self.update(py, |entry| entry.set_clean(mode, size, mtime));
182 Ok(PyNone)
182 Ok(PyNone)
183 }
183 }
184
184
185 def set_possibly_dirty(&self) -> PyResult<PyNone> {
185 def set_possibly_dirty(&self) -> PyResult<PyNone> {
186 self.update(py, |entry| entry.set_possibly_dirty());
186 self.update(py, |entry| entry.set_possibly_dirty());
187 Ok(PyNone)
187 Ok(PyNone)
188 }
188 }
189
189
190 def set_tracked(&self) -> PyResult<PyNone> {
190 def set_tracked(&self) -> PyResult<PyNone> {
191 self.update(py, |entry| entry.set_tracked());
191 self.update(py, |entry| entry.set_tracked());
192 Ok(PyNone)
192 Ok(PyNone)
193 }
193 }
194
194
195 def set_untracked(&self) -> PyResult<PyNone> {
195 def set_untracked(&self) -> PyResult<PyNone> {
196 self.update(py, |entry| entry.set_untracked());
196 self.update(py, |entry| entry.set_untracked());
197 Ok(PyNone)
197 Ok(PyNone)
198 }
198 }
199 });
199 });
200
200
201 impl DirstateItem {
201 impl DirstateItem {
202 pub fn new_as_pyobject(
202 pub fn new_as_pyobject(
203 py: Python<'_>,
203 py: Python<'_>,
204 entry: DirstateEntry,
204 entry: DirstateEntry,
205 ) -> PyResult<PyObject> {
205 ) -> PyResult<PyObject> {
206 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
206 Ok(DirstateItem::create_instance(py, Cell::new(entry))?.into_object())
207 }
207 }
208
208
209 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
209 pub fn get_entry(&self, py: Python<'_>) -> DirstateEntry {
210 self.entry(py).get()
210 self.entry(py).get()
211 }
211 }
212
212
213 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
213 // TODO: Use https://doc.rust-lang.org/std/cell/struct.Cell.html#method.update instead when it’s stable
214 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
214 pub fn update(&self, py: Python<'_>, f: impl FnOnce(&mut DirstateEntry)) {
215 let mut entry = self.entry(py).get();
215 let mut entry = self.entry(py).get();
216 f(&mut entry);
216 f(&mut entry);
217 self.entry(py).set(entry)
217 self.entry(py).set(entry)
218 }
218 }
219 }
219 }
General Comments 0
You need to be logged in to leave comments. Login now