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