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