##// END OF EJS Templates
rust-dirstate: make the reliable timestamp comparison more usable from outside...
Raphaël Gomès -
r52948:0529e1a4 default
parent child Browse files
Show More
@@ -1,722 +1,727
1 1 use crate::dirstate_tree::on_disk::DirstateV2ParseError;
2 2 use crate::errors::HgError;
3 3 use bitflags::bitflags;
4 4 use std::fs;
5 5 use std::io;
6 6 use std::time::{SystemTime, UNIX_EPOCH};
7 7
8 8 #[derive(Copy, Clone, Debug, Eq, PartialEq)]
9 9 pub enum EntryState {
10 10 Normal,
11 11 Added,
12 12 Removed,
13 13 Merged,
14 14 }
15 15
16 16 /// `size` and `mtime.seconds` are truncated to 31 bits.
17 17 ///
18 18 /// TODO: double-check status algorithm correctness for files
19 19 /// larger than 2 GiB or modified after 2038.
20 20 #[derive(Debug, Copy, Clone)]
21 21 pub struct DirstateEntry {
22 22 pub(crate) flags: Flags,
23 23 mode_size: Option<(u32, u32)>,
24 24 mtime: Option<TruncatedTimestamp>,
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(Debug, 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 /// TODO this should be in DirstateEntry, but the current code needs
46 46 /// refactoring to use DirstateEntry instead of TruncatedTimestamp for
47 47 /// comparison.
48 48 pub second_ambiguous: bool,
49 49 }
50 50
51 51 impl TruncatedTimestamp {
52 52 /// Constructs from a timestamp potentially outside of the supported range,
53 53 /// and truncate the seconds components to its lower 31 bits.
54 54 ///
55 55 /// Panics if the nanoseconds components is not in the expected range.
56 56 pub fn new_truncate(
57 57 seconds: i64,
58 58 nanoseconds: u32,
59 59 second_ambiguous: bool,
60 60 ) -> Self {
61 61 assert!(nanoseconds < NSEC_PER_SEC);
62 62 Self {
63 63 truncated_seconds: seconds as u32 & RANGE_MASK_31BIT,
64 64 nanoseconds,
65 65 second_ambiguous,
66 66 }
67 67 }
68 68
69 69 /// Construct from components. Returns an error if they are not in the
70 70 /// expcted range.
71 71 pub fn from_already_truncated(
72 72 truncated_seconds: u32,
73 73 nanoseconds: u32,
74 74 second_ambiguous: bool,
75 75 ) -> Result<Self, DirstateV2ParseError> {
76 76 if truncated_seconds & !RANGE_MASK_31BIT == 0
77 77 && nanoseconds < NSEC_PER_SEC
78 78 {
79 79 Ok(Self {
80 80 truncated_seconds,
81 81 nanoseconds,
82 82 second_ambiguous,
83 83 })
84 84 } else {
85 85 Err(DirstateV2ParseError::new("when reading datetime"))
86 86 }
87 87 }
88 88
89 89 /// Returns a `TruncatedTimestamp` for the modification time of `metadata`.
90 90 ///
91 91 /// Propagates errors from `std` on platforms where modification time
92 92 /// is not available at all.
93 93 pub fn for_mtime_of(metadata: &fs::Metadata) -> io::Result<Self> {
94 94 #[cfg(unix)]
95 95 {
96 96 use std::os::unix::fs::MetadataExt;
97 97 let seconds = metadata.mtime();
98 98 // i64 -> u32 with value always in the `0 .. NSEC_PER_SEC` range
99 99 let nanoseconds = metadata.mtime_nsec().try_into().unwrap();
100 100 Ok(Self::new_truncate(seconds, nanoseconds, false))
101 101 }
102 102 #[cfg(not(unix))]
103 103 {
104 104 metadata.modified().map(Self::from)
105 105 }
106 106 }
107 107
108 108 /// Like `for_mtime_of`, but may return `None` or a value with
109 109 /// `second_ambiguous` set if the mtime is not "reliable".
110 110 ///
111 111 /// A modification time is reliable if it is older than `boundary` (or
112 112 /// sufficiently in the future).
113 113 ///
114 114 /// Otherwise a concurrent modification might happens with the same mtime.
115 115 pub fn for_reliable_mtime_of(
116 116 metadata: &fs::Metadata,
117 117 boundary: &Self,
118 118 ) -> io::Result<Option<Self>> {
119 let mut mtime = Self::for_mtime_of(metadata)?;
119 Ok(Self::for_mtime_of(metadata)?.for_reliable_mtime_of_self(boundary))
120 }
121
122 /// See [`Self::for_reliable_mtime_of`]
123 pub fn for_reliable_mtime_of_self(&self, boundary: &Self) -> Option<Self> {
124 let mut new = *self;
120 125 // If the mtime of the ambiguous file is younger (or equal) to the
121 126 // starting point of the `status` walk, we cannot garantee that
122 127 // another, racy, write will not happen right after with the same mtime
123 128 // and we cannot cache the information.
124 129 //
125 130 // However if the mtime is far away in the future, this is likely some
126 131 // mismatch between the current clock and previous file system
127 132 // operation. So mtime more than one days in the future are considered
128 133 // fine.
129 let reliable = if mtime.truncated_seconds == boundary.truncated_seconds
134 let reliable = if self.truncated_seconds == boundary.truncated_seconds
130 135 {
131 mtime.second_ambiguous = true;
132 mtime.nanoseconds != 0
136 new.second_ambiguous = true;
137 self.nanoseconds != 0
133 138 && boundary.nanoseconds != 0
134 && mtime.nanoseconds < boundary.nanoseconds
139 && self.nanoseconds < boundary.nanoseconds
135 140 } else {
136 141 // `truncated_seconds` is less than 2**31,
137 142 // so this does not overflow `u32`:
138 143 let one_day_later = boundary.truncated_seconds + 24 * 3600;
139 mtime.truncated_seconds < boundary.truncated_seconds
140 || mtime.truncated_seconds > one_day_later
144 self.truncated_seconds < boundary.truncated_seconds
145 || self.truncated_seconds > one_day_later
141 146 };
142 147 if reliable {
143 Ok(Some(mtime))
148 Some(new)
144 149 } else {
145 Ok(None)
150 None
146 151 }
147 152 }
148 153
149 154 /// The lower 31 bits of the number of seconds since the epoch.
150 155 pub fn truncated_seconds(&self) -> u32 {
151 156 self.truncated_seconds
152 157 }
153 158
154 159 /// The sub-second component of this timestamp, in nanoseconds.
155 160 /// Always in the `0 .. 1_000_000_000` range.
156 161 ///
157 162 /// This timestamp is after `(seconds, 0)` by this many nanoseconds.
158 163 pub fn nanoseconds(&self) -> u32 {
159 164 self.nanoseconds
160 165 }
161 166
162 167 /// Returns whether two timestamps are equal modulo 2**31 seconds.
163 168 ///
164 169 /// If this returns `true`, the original values converted from `SystemTime`
165 170 /// or given to `new_truncate` were very likely equal. A false positive is
166 171 /// possible if they were exactly a multiple of 2**31 seconds apart (around
167 172 /// 68 years). This is deemed very unlikely to happen by chance, especially
168 173 /// on filesystems that support sub-second precision.
169 174 ///
170 175 /// If someone is manipulating the modification times of some files to
171 176 /// intentionally make `hg status` return incorrect results, not truncating
172 177 /// wouldn’t help much since they can set exactly the expected timestamp.
173 178 ///
174 179 /// Sub-second precision is ignored if it is zero in either value.
175 180 /// Some APIs simply return zero when more precision is not available.
176 181 /// When comparing values from different sources, if only one is truncated
177 182 /// in that way, doing a simple comparison would cause many false
178 183 /// negatives.
179 184 pub fn likely_equal(self, other: Self) -> bool {
180 185 if self.truncated_seconds != other.truncated_seconds {
181 186 false
182 187 } else if self.nanoseconds == 0 || other.nanoseconds == 0 {
183 188 !self.second_ambiguous
184 189 } else {
185 190 self.nanoseconds == other.nanoseconds
186 191 }
187 192 }
188 193
189 194 pub fn likely_equal_to_mtime_of(
190 195 self,
191 196 metadata: &fs::Metadata,
192 197 ) -> io::Result<bool> {
193 198 Ok(self.likely_equal(Self::for_mtime_of(metadata)?))
194 199 }
195 200 }
196 201
197 202 impl From<SystemTime> for TruncatedTimestamp {
198 203 fn from(system_time: SystemTime) -> Self {
199 204 // On Unix, `SystemTime` is a wrapper for the `timespec` C struct:
200 205 // https://www.gnu.org/software/libc/manual/html_node/Time-Types.html#index-struct-timespec
201 206 // We want to effectively access its fields, but the Rust standard
202 207 // library does not expose them. The best we can do is:
203 208 let seconds;
204 209 let nanoseconds;
205 210 match system_time.duration_since(UNIX_EPOCH) {
206 211 Ok(duration) => {
207 212 seconds = duration.as_secs() as i64;
208 213 nanoseconds = duration.subsec_nanos();
209 214 }
210 215 Err(error) => {
211 216 // `system_time` is before `UNIX_EPOCH`.
212 217 // We need to undo this algorithm:
213 218 // https://github.com/rust-lang/rust/blob/6bed1f0bc3cc50c10aab26d5f94b16a00776b8a5/library/std/src/sys/unix/time.rs#L40-L41
214 219 let negative = error.duration();
215 220 let negative_secs = negative.as_secs() as i64;
216 221 let negative_nanos = negative.subsec_nanos();
217 222 if negative_nanos == 0 {
218 223 seconds = -negative_secs;
219 224 nanoseconds = 0;
220 225 } else {
221 226 // For example if `system_time` was 4.3 seconds before
222 227 // the Unix epoch we get a Duration that represents
223 228 // `(-4, -0.3)` but we want `(-5, +0.7)`:
224 229 seconds = -1 - negative_secs;
225 230 nanoseconds = NSEC_PER_SEC - negative_nanos;
226 231 }
227 232 }
228 233 };
229 234 Self::new_truncate(seconds, nanoseconds, false)
230 235 }
231 236 }
232 237
233 238 const NSEC_PER_SEC: u32 = 1_000_000_000;
234 239 pub const RANGE_MASK_31BIT: u32 = 0x7FFF_FFFF;
235 240
236 241 pub const MTIME_UNSET: i32 = -1;
237 242
238 243 /// A `DirstateEntry` with a size of `-2` means that it was merged from the
239 244 /// other parent. This allows revert to pick the right status back during a
240 245 /// merge.
241 246 pub const SIZE_FROM_OTHER_PARENT: i32 = -2;
242 247 /// A special value used for internal representation of special case in
243 248 /// dirstate v1 format.
244 249 pub const SIZE_NON_NORMAL: i32 = -1;
245 250
246 251 #[derive(Debug, Default, Copy, Clone)]
247 252 pub struct DirstateV2Data {
248 253 pub wc_tracked: bool,
249 254 pub p1_tracked: bool,
250 255 pub p2_info: bool,
251 256 pub mode_size: Option<(u32, u32)>,
252 257 pub mtime: Option<TruncatedTimestamp>,
253 258 pub fallback_exec: Option<bool>,
254 259 pub fallback_symlink: Option<bool>,
255 260 }
256 261
257 262 #[derive(Debug, Default, Copy, Clone)]
258 263 pub struct ParentFileData {
259 264 pub mode_size: Option<(u32, u32)>,
260 265 pub mtime: Option<TruncatedTimestamp>,
261 266 }
262 267
263 268 impl DirstateEntry {
264 269 pub fn from_v2_data(v2_data: DirstateV2Data) -> Self {
265 270 let DirstateV2Data {
266 271 wc_tracked,
267 272 p1_tracked,
268 273 p2_info,
269 274 mode_size,
270 275 mtime,
271 276 fallback_exec,
272 277 fallback_symlink,
273 278 } = v2_data;
274 279 if let Some((mode, size)) = mode_size {
275 280 // TODO: return an error for out of range values?
276 281 assert!(mode & !RANGE_MASK_31BIT == 0);
277 282 assert!(size & !RANGE_MASK_31BIT == 0);
278 283 }
279 284 let mut flags = Flags::empty();
280 285 flags.set(Flags::WDIR_TRACKED, wc_tracked);
281 286 flags.set(Flags::P1_TRACKED, p1_tracked);
282 287 flags.set(Flags::P2_INFO, p2_info);
283 288 if let Some(exec) = fallback_exec {
284 289 flags.insert(Flags::HAS_FALLBACK_EXEC);
285 290 if exec {
286 291 flags.insert(Flags::FALLBACK_EXEC);
287 292 }
288 293 }
289 294 if let Some(exec) = fallback_symlink {
290 295 flags.insert(Flags::HAS_FALLBACK_SYMLINK);
291 296 if exec {
292 297 flags.insert(Flags::FALLBACK_SYMLINK);
293 298 }
294 299 }
295 300 Self {
296 301 flags,
297 302 mode_size,
298 303 mtime,
299 304 }
300 305 }
301 306
302 307 pub fn from_v1_data(
303 308 state: EntryState,
304 309 mode: i32,
305 310 size: i32,
306 311 mtime: i32,
307 312 ) -> Self {
308 313 match state {
309 314 EntryState::Normal => {
310 315 if size == SIZE_FROM_OTHER_PARENT {
311 316 Self {
312 317 // might be missing P1_TRACKED
313 318 flags: Flags::WDIR_TRACKED | Flags::P2_INFO,
314 319 mode_size: None,
315 320 mtime: None,
316 321 }
317 322 } else if size == SIZE_NON_NORMAL {
318 323 Self {
319 324 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
320 325 mode_size: None,
321 326 mtime: None,
322 327 }
323 328 } else if mtime == MTIME_UNSET {
324 329 // TODO: return an error for negative values?
325 330 let mode = u32::try_from(mode).unwrap();
326 331 let size = u32::try_from(size).unwrap();
327 332 Self {
328 333 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
329 334 mode_size: Some((mode, size)),
330 335 mtime: None,
331 336 }
332 337 } else {
333 338 // TODO: return an error for negative values?
334 339 let mode = u32::try_from(mode).unwrap();
335 340 let size = u32::try_from(size).unwrap();
336 341 let mtime = u32::try_from(mtime).unwrap();
337 342 let mtime = TruncatedTimestamp::from_already_truncated(
338 343 mtime, 0, false,
339 344 )
340 345 .unwrap();
341 346 Self {
342 347 flags: Flags::WDIR_TRACKED | Flags::P1_TRACKED,
343 348 mode_size: Some((mode, size)),
344 349 mtime: Some(mtime),
345 350 }
346 351 }
347 352 }
348 353 EntryState::Added => Self {
349 354 flags: Flags::WDIR_TRACKED,
350 355 mode_size: None,
351 356 mtime: None,
352 357 },
353 358 EntryState::Removed => Self {
354 359 flags: if size == SIZE_NON_NORMAL {
355 360 Flags::P1_TRACKED | Flags::P2_INFO
356 361 } else if size == SIZE_FROM_OTHER_PARENT {
357 362 // We don’t know if P1_TRACKED should be set (file history)
358 363 Flags::P2_INFO
359 364 } else {
360 365 Flags::P1_TRACKED
361 366 },
362 367 mode_size: None,
363 368 mtime: None,
364 369 },
365 370 EntryState::Merged => Self {
366 371 flags: Flags::WDIR_TRACKED
367 372 | Flags::P1_TRACKED // might not be true because of rename ?
368 373 | Flags::P2_INFO, // might not be true because of rename ?
369 374 mode_size: None,
370 375 mtime: None,
371 376 },
372 377 }
373 378 }
374 379
375 380 /// Creates a new entry in "removed" state.
376 381 ///
377 382 /// `size` is expected to be zero, `SIZE_NON_NORMAL`, or
378 383 /// `SIZE_FROM_OTHER_PARENT`
379 384 pub fn new_removed(size: i32) -> Self {
380 385 Self::from_v1_data(EntryState::Removed, 0, size, 0)
381 386 }
382 387
383 388 pub fn new_tracked() -> Self {
384 389 let data = DirstateV2Data {
385 390 wc_tracked: true,
386 391 ..Default::default()
387 392 };
388 393 Self::from_v2_data(data)
389 394 }
390 395
391 396 pub fn tracked(&self) -> bool {
392 397 self.flags.contains(Flags::WDIR_TRACKED)
393 398 }
394 399
395 400 pub fn p1_tracked(&self) -> bool {
396 401 self.flags.contains(Flags::P1_TRACKED)
397 402 }
398 403
399 404 fn in_either_parent(&self) -> bool {
400 405 self.flags.intersects(Flags::P1_TRACKED | Flags::P2_INFO)
401 406 }
402 407
403 408 pub fn removed(&self) -> bool {
404 409 self.in_either_parent() && !self.flags.contains(Flags::WDIR_TRACKED)
405 410 }
406 411
407 412 pub fn p2_info(&self) -> bool {
408 413 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
409 414 }
410 415
411 416 pub fn added(&self) -> bool {
412 417 self.flags.contains(Flags::WDIR_TRACKED) && !self.in_either_parent()
413 418 }
414 419
415 420 pub fn modified(&self) -> bool {
416 421 self.flags
417 422 .contains(Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO)
418 423 }
419 424
420 425 pub fn maybe_clean(&self) -> bool {
421 426 #[allow(clippy::if_same_then_else)]
422 427 #[allow(clippy::needless_bool)]
423 428 if !self.flags.contains(Flags::WDIR_TRACKED) {
424 429 false
425 430 } else if !self.flags.contains(Flags::P1_TRACKED) {
426 431 false
427 432 } else if self.flags.contains(Flags::P2_INFO) {
428 433 false
429 434 } else {
430 435 true
431 436 }
432 437 }
433 438
434 439 pub fn any_tracked(&self) -> bool {
435 440 self.flags.intersects(
436 441 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
437 442 )
438 443 }
439 444
440 445 pub(crate) fn v2_data(&self) -> DirstateV2Data {
441 446 if !self.any_tracked() {
442 447 // TODO: return an Option instead?
443 448 panic!("Accessing v2_data of an untracked DirstateEntry")
444 449 }
445 450 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
446 451 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
447 452 let p2_info = self.flags.contains(Flags::P2_INFO);
448 453 let mode_size = self.mode_size;
449 454 let mtime = self.mtime;
450 455 DirstateV2Data {
451 456 wc_tracked,
452 457 p1_tracked,
453 458 p2_info,
454 459 mode_size,
455 460 mtime,
456 461 fallback_exec: self.get_fallback_exec(),
457 462 fallback_symlink: self.get_fallback_symlink(),
458 463 }
459 464 }
460 465
461 466 fn v1_state(&self) -> EntryState {
462 467 if !self.any_tracked() {
463 468 // TODO: return an Option instead?
464 469 panic!("Accessing v1_state of an untracked DirstateEntry")
465 470 }
466 471 if self.removed() {
467 472 EntryState::Removed
468 473 } else if self.modified() {
469 474 EntryState::Merged
470 475 } else if self.added() {
471 476 EntryState::Added
472 477 } else {
473 478 EntryState::Normal
474 479 }
475 480 }
476 481
477 482 fn v1_mode(&self) -> i32 {
478 483 if let Some((mode, _size)) = self.mode_size {
479 484 i32::try_from(mode).unwrap()
480 485 } else {
481 486 0
482 487 }
483 488 }
484 489
485 490 fn v1_size(&self) -> i32 {
486 491 if !self.any_tracked() {
487 492 // TODO: return an Option instead?
488 493 panic!("Accessing v1_size of an untracked DirstateEntry")
489 494 }
490 495 if self.removed()
491 496 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
492 497 {
493 498 SIZE_NON_NORMAL
494 499 } else if self.flags.contains(Flags::P2_INFO) {
495 500 SIZE_FROM_OTHER_PARENT
496 501 } else if self.removed() {
497 502 0
498 503 } else if self.added() {
499 504 SIZE_NON_NORMAL
500 505 } else if let Some((_mode, size)) = self.mode_size {
501 506 i32::try_from(size).unwrap()
502 507 } else {
503 508 SIZE_NON_NORMAL
504 509 }
505 510 }
506 511
507 512 fn v1_mtime(&self) -> i32 {
508 513 if !self.any_tracked() {
509 514 // TODO: return an Option instead?
510 515 panic!("Accessing v1_mtime of an untracked DirstateEntry")
511 516 }
512 517
513 518 #[allow(clippy::if_same_then_else)]
514 519 if self.removed() {
515 520 0
516 521 } else if self.flags.contains(Flags::P2_INFO) {
517 522 MTIME_UNSET
518 523 } else if !self.flags.contains(Flags::P1_TRACKED) {
519 524 MTIME_UNSET
520 525 } else if let Some(mtime) = self.mtime {
521 526 if mtime.second_ambiguous {
522 527 MTIME_UNSET
523 528 } else {
524 529 i32::try_from(mtime.truncated_seconds()).unwrap()
525 530 }
526 531 } else {
527 532 MTIME_UNSET
528 533 }
529 534 }
530 535
531 536 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
532 537 pub fn state(&self) -> EntryState {
533 538 self.v1_state()
534 539 }
535 540
536 541 // TODO: return Option?
537 542 pub fn mode(&self) -> i32 {
538 543 self.v1_mode()
539 544 }
540 545
541 546 // TODO: return Option?
542 547 pub fn size(&self) -> i32 {
543 548 self.v1_size()
544 549 }
545 550
546 551 // TODO: return Option?
547 552 pub fn mtime(&self) -> i32 {
548 553 self.v1_mtime()
549 554 }
550 555
551 556 pub fn get_fallback_exec(&self) -> Option<bool> {
552 557 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
553 558 Some(self.flags.contains(Flags::FALLBACK_EXEC))
554 559 } else {
555 560 None
556 561 }
557 562 }
558 563
559 564 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
560 565 match value {
561 566 None => {
562 567 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
563 568 self.flags.remove(Flags::FALLBACK_EXEC);
564 569 }
565 570 Some(exec) => {
566 571 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
567 572 if exec {
568 573 self.flags.insert(Flags::FALLBACK_EXEC);
569 574 }
570 575 }
571 576 }
572 577 }
573 578
574 579 pub fn get_fallback_symlink(&self) -> Option<bool> {
575 580 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
576 581 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
577 582 } else {
578 583 None
579 584 }
580 585 }
581 586
582 587 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
583 588 match value {
584 589 None => {
585 590 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
586 591 self.flags.remove(Flags::FALLBACK_SYMLINK);
587 592 }
588 593 Some(symlink) => {
589 594 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
590 595 if symlink {
591 596 self.flags.insert(Flags::FALLBACK_SYMLINK);
592 597 }
593 598 }
594 599 }
595 600 }
596 601
597 602 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
598 603 self.mtime
599 604 }
600 605
601 606 pub fn drop_merge_data(&mut self) {
602 607 if self.flags.contains(Flags::P2_INFO) {
603 608 self.flags.remove(Flags::P2_INFO);
604 609 self.mode_size = None;
605 610 self.mtime = None;
606 611 }
607 612 }
608 613
609 614 pub fn set_possibly_dirty(&mut self) {
610 615 self.mtime = None
611 616 }
612 617
613 618 pub fn set_clean(
614 619 &mut self,
615 620 mode: u32,
616 621 size: u32,
617 622 mtime: TruncatedTimestamp,
618 623 ) {
619 624 let size = size & RANGE_MASK_31BIT;
620 625 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
621 626 self.mode_size = Some((mode, size));
622 627 self.mtime = Some(mtime);
623 628 }
624 629
625 630 pub fn set_tracked(&mut self) {
626 631 self.flags.insert(Flags::WDIR_TRACKED);
627 632 // `set_tracked` is replacing various `normallookup` call. So we mark
628 633 // the files as needing lookup
629 634 //
630 635 // Consider dropping this in the future in favor of something less
631 636 // broad.
632 637 self.mtime = None;
633 638 }
634 639
635 640 pub fn set_untracked(&mut self) {
636 641 self.flags.remove(Flags::WDIR_TRACKED);
637 642 self.mode_size = None;
638 643 self.mtime = None;
639 644 }
640 645
641 646 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
642 647 /// in the dirstate-v1 format.
643 648 ///
644 649 /// This includes marker values such as `mtime == -1`. In the future we may
645 650 /// want to not represent these cases that way in memory, but serialization
646 651 /// will need to keep the same format.
647 652 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
648 653 (
649 654 self.v1_state().into(),
650 655 self.v1_mode(),
651 656 self.v1_size(),
652 657 self.v1_mtime(),
653 658 )
654 659 }
655 660
656 661 pub(crate) fn is_from_other_parent(&self) -> bool {
657 662 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
658 663 }
659 664
660 665 // TODO: other platforms
661 666 #[cfg(unix)]
662 667 pub fn mode_changed(
663 668 &self,
664 669 filesystem_metadata: &std::fs::Metadata,
665 670 ) -> bool {
666 671 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
667 672 let fs_exec_bit = has_exec_bit(filesystem_metadata);
668 673 dirstate_exec_bit != fs_exec_bit
669 674 }
670 675
671 676 /// Returns a `(state, mode, size, mtime)` tuple as for
672 677 /// `DirstateMapMethods::debug_iter`.
673 678 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
674 679 (self.state().into(), self.mode(), self.size(), self.mtime())
675 680 }
676 681 }
677 682
678 683 impl EntryState {
679 684 pub fn is_tracked(self) -> bool {
680 685 use EntryState::*;
681 686 match self {
682 687 Normal | Added | Merged => true,
683 688 Removed => false,
684 689 }
685 690 }
686 691 }
687 692
688 693 impl TryFrom<u8> for EntryState {
689 694 type Error = HgError;
690 695
691 696 fn try_from(value: u8) -> Result<Self, Self::Error> {
692 697 match value {
693 698 b'n' => Ok(EntryState::Normal),
694 699 b'a' => Ok(EntryState::Added),
695 700 b'r' => Ok(EntryState::Removed),
696 701 b'm' => Ok(EntryState::Merged),
697 702 _ => Err(HgError::CorruptedRepository(format!(
698 703 "Incorrect dirstate entry state {}",
699 704 value
700 705 ))),
701 706 }
702 707 }
703 708 }
704 709
705 710 impl From<EntryState> for u8 {
706 711 fn from(val: EntryState) -> Self {
707 712 match val {
708 713 EntryState::Normal => b'n',
709 714 EntryState::Added => b'a',
710 715 EntryState::Removed => b'r',
711 716 EntryState::Merged => b'm',
712 717 }
713 718 }
714 719 }
715 720
716 721 const EXEC_BIT_MASK: u32 = 0o100;
717 722
718 723 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
719 724 // TODO: How to handle executable permissions on Windows?
720 725 use std::os::unix::fs::MetadataExt;
721 726 (metadata.mode() & EXEC_BIT_MASK) != 0
722 727 }
General Comments 0
You need to be logged in to leave comments. Login now