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