##// END OF EJS Templates
rust-clippy: tell clippy we want to keep those clauses separate...
Raphaël Gomès -
r50814:bd42bd6e default
parent child Browse files
Show More
@@ -1,725 +1,726 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 425 #[allow(clippy::if_same_then_else)]
426 #[allow(clippy::needless_bool)]
426 427 if !self.flags.contains(Flags::WDIR_TRACKED) {
427 428 false
428 429 } else if !self.flags.contains(Flags::P1_TRACKED) {
429 430 false
430 431 } else if self.flags.contains(Flags::P2_INFO) {
431 432 false
432 433 } else {
433 434 true
434 435 }
435 436 }
436 437
437 438 pub fn any_tracked(&self) -> bool {
438 439 self.flags.intersects(
439 440 Flags::WDIR_TRACKED | Flags::P1_TRACKED | Flags::P2_INFO,
440 441 )
441 442 }
442 443
443 444 pub(crate) fn v2_data(&self) -> DirstateV2Data {
444 445 if !self.any_tracked() {
445 446 // TODO: return an Option instead?
446 447 panic!("Accessing v2_data of an untracked DirstateEntry")
447 448 }
448 449 let wc_tracked = self.flags.contains(Flags::WDIR_TRACKED);
449 450 let p1_tracked = self.flags.contains(Flags::P1_TRACKED);
450 451 let p2_info = self.flags.contains(Flags::P2_INFO);
451 452 let mode_size = self.mode_size;
452 453 let mtime = self.mtime;
453 454 DirstateV2Data {
454 455 wc_tracked,
455 456 p1_tracked,
456 457 p2_info,
457 458 mode_size,
458 459 mtime,
459 460 fallback_exec: self.get_fallback_exec(),
460 461 fallback_symlink: self.get_fallback_symlink(),
461 462 }
462 463 }
463 464
464 465 fn v1_state(&self) -> EntryState {
465 466 if !self.any_tracked() {
466 467 // TODO: return an Option instead?
467 468 panic!("Accessing v1_state of an untracked DirstateEntry")
468 469 }
469 470 if self.removed() {
470 471 EntryState::Removed
471 472 } else if self.modified() {
472 473 EntryState::Merged
473 474 } else if self.added() {
474 475 EntryState::Added
475 476 } else {
476 477 EntryState::Normal
477 478 }
478 479 }
479 480
480 481 fn v1_mode(&self) -> i32 {
481 482 if let Some((mode, _size)) = self.mode_size {
482 483 i32::try_from(mode).unwrap()
483 484 } else {
484 485 0
485 486 }
486 487 }
487 488
488 489 fn v1_size(&self) -> i32 {
489 490 if !self.any_tracked() {
490 491 // TODO: return an Option instead?
491 492 panic!("Accessing v1_size of an untracked DirstateEntry")
492 493 }
493 494 if self.removed()
494 495 && self.flags.contains(Flags::P1_TRACKED | Flags::P2_INFO)
495 496 {
496 497 SIZE_NON_NORMAL
497 498 } else if self.flags.contains(Flags::P2_INFO) {
498 499 SIZE_FROM_OTHER_PARENT
499 500 } else if self.removed() {
500 501 0
501 502 } else if self.added() {
502 503 SIZE_NON_NORMAL
503 504 } else if let Some((_mode, size)) = self.mode_size {
504 505 i32::try_from(size).unwrap()
505 506 } else {
506 507 SIZE_NON_NORMAL
507 508 }
508 509 }
509 510
510 511 fn v1_mtime(&self) -> i32 {
511 512 if !self.any_tracked() {
512 513 // TODO: return an Option instead?
513 514 panic!("Accessing v1_mtime of an untracked DirstateEntry")
514 515 }
515 516
516 517 #[allow(clippy::if_same_then_else)]
517 518 if self.removed() {
518 519 0
519 520 } else if self.flags.contains(Flags::P2_INFO) {
520 521 MTIME_UNSET
521 522 } else if !self.flags.contains(Flags::P1_TRACKED) {
522 523 MTIME_UNSET
523 524 } else if let Some(mtime) = self.mtime {
524 525 if mtime.second_ambiguous {
525 526 MTIME_UNSET
526 527 } else {
527 528 i32::try_from(mtime.truncated_seconds()).unwrap()
528 529 }
529 530 } else {
530 531 MTIME_UNSET
531 532 }
532 533 }
533 534
534 535 // TODO: return `Option<EntryState>`? None when `!self.any_tracked`
535 536 pub fn state(&self) -> EntryState {
536 537 self.v1_state()
537 538 }
538 539
539 540 // TODO: return Option?
540 541 pub fn mode(&self) -> i32 {
541 542 self.v1_mode()
542 543 }
543 544
544 545 // TODO: return Option?
545 546 pub fn size(&self) -> i32 {
546 547 self.v1_size()
547 548 }
548 549
549 550 // TODO: return Option?
550 551 pub fn mtime(&self) -> i32 {
551 552 self.v1_mtime()
552 553 }
553 554
554 555 pub fn get_fallback_exec(&self) -> Option<bool> {
555 556 if self.flags.contains(Flags::HAS_FALLBACK_EXEC) {
556 557 Some(self.flags.contains(Flags::FALLBACK_EXEC))
557 558 } else {
558 559 None
559 560 }
560 561 }
561 562
562 563 pub fn set_fallback_exec(&mut self, value: Option<bool>) {
563 564 match value {
564 565 None => {
565 566 self.flags.remove(Flags::HAS_FALLBACK_EXEC);
566 567 self.flags.remove(Flags::FALLBACK_EXEC);
567 568 }
568 569 Some(exec) => {
569 570 self.flags.insert(Flags::HAS_FALLBACK_EXEC);
570 571 if exec {
571 572 self.flags.insert(Flags::FALLBACK_EXEC);
572 573 }
573 574 }
574 575 }
575 576 }
576 577
577 578 pub fn get_fallback_symlink(&self) -> Option<bool> {
578 579 if self.flags.contains(Flags::HAS_FALLBACK_SYMLINK) {
579 580 Some(self.flags.contains(Flags::FALLBACK_SYMLINK))
580 581 } else {
581 582 None
582 583 }
583 584 }
584 585
585 586 pub fn set_fallback_symlink(&mut self, value: Option<bool>) {
586 587 match value {
587 588 None => {
588 589 self.flags.remove(Flags::HAS_FALLBACK_SYMLINK);
589 590 self.flags.remove(Flags::FALLBACK_SYMLINK);
590 591 }
591 592 Some(symlink) => {
592 593 self.flags.insert(Flags::HAS_FALLBACK_SYMLINK);
593 594 if symlink {
594 595 self.flags.insert(Flags::FALLBACK_SYMLINK);
595 596 }
596 597 }
597 598 }
598 599 }
599 600
600 601 pub fn truncated_mtime(&self) -> Option<TruncatedTimestamp> {
601 602 self.mtime
602 603 }
603 604
604 605 pub fn drop_merge_data(&mut self) {
605 606 if self.flags.contains(Flags::P2_INFO) {
606 607 self.flags.remove(Flags::P2_INFO);
607 608 self.mode_size = None;
608 609 self.mtime = None;
609 610 }
610 611 }
611 612
612 613 pub fn set_possibly_dirty(&mut self) {
613 614 self.mtime = None
614 615 }
615 616
616 617 pub fn set_clean(
617 618 &mut self,
618 619 mode: u32,
619 620 size: u32,
620 621 mtime: TruncatedTimestamp,
621 622 ) {
622 623 let size = size & RANGE_MASK_31BIT;
623 624 self.flags.insert(Flags::WDIR_TRACKED | Flags::P1_TRACKED);
624 625 self.mode_size = Some((mode, size));
625 626 self.mtime = Some(mtime);
626 627 }
627 628
628 629 pub fn set_tracked(&mut self) {
629 630 self.flags.insert(Flags::WDIR_TRACKED);
630 631 // `set_tracked` is replacing various `normallookup` call. So we mark
631 632 // the files as needing lookup
632 633 //
633 634 // Consider dropping this in the future in favor of something less
634 635 // broad.
635 636 self.mtime = None;
636 637 }
637 638
638 639 pub fn set_untracked(&mut self) {
639 640 self.flags.remove(Flags::WDIR_TRACKED);
640 641 self.mode_size = None;
641 642 self.mtime = None;
642 643 }
643 644
644 645 /// Returns `(state, mode, size, mtime)` for the puprose of serialization
645 646 /// in the dirstate-v1 format.
646 647 ///
647 648 /// This includes marker values such as `mtime == -1`. In the future we may
648 649 /// want to not represent these cases that way in memory, but serialization
649 650 /// will need to keep the same format.
650 651 pub fn v1_data(&self) -> (u8, i32, i32, i32) {
651 652 (
652 653 self.v1_state().into(),
653 654 self.v1_mode(),
654 655 self.v1_size(),
655 656 self.v1_mtime(),
656 657 )
657 658 }
658 659
659 660 pub(crate) fn is_from_other_parent(&self) -> bool {
660 661 self.flags.contains(Flags::WDIR_TRACKED | Flags::P2_INFO)
661 662 }
662 663
663 664 // TODO: other platforms
664 665 #[cfg(unix)]
665 666 pub fn mode_changed(
666 667 &self,
667 668 filesystem_metadata: &std::fs::Metadata,
668 669 ) -> bool {
669 670 let dirstate_exec_bit = (self.mode() as u32 & EXEC_BIT_MASK) != 0;
670 671 let fs_exec_bit = has_exec_bit(filesystem_metadata);
671 672 dirstate_exec_bit != fs_exec_bit
672 673 }
673 674
674 675 /// Returns a `(state, mode, size, mtime)` tuple as for
675 676 /// `DirstateMapMethods::debug_iter`.
676 677 pub fn debug_tuple(&self) -> (u8, i32, i32, i32) {
677 678 (self.state().into(), self.mode(), self.size(), self.mtime())
678 679 }
679 680 }
680 681
681 682 impl EntryState {
682 683 pub fn is_tracked(self) -> bool {
683 684 use EntryState::*;
684 685 match self {
685 686 Normal | Added | Merged => true,
686 687 Removed => false,
687 688 }
688 689 }
689 690 }
690 691
691 692 impl TryFrom<u8> for EntryState {
692 693 type Error = HgError;
693 694
694 695 fn try_from(value: u8) -> Result<Self, Self::Error> {
695 696 match value {
696 697 b'n' => Ok(EntryState::Normal),
697 698 b'a' => Ok(EntryState::Added),
698 699 b'r' => Ok(EntryState::Removed),
699 700 b'm' => Ok(EntryState::Merged),
700 701 _ => Err(HgError::CorruptedRepository(format!(
701 702 "Incorrect dirstate entry state {}",
702 703 value
703 704 ))),
704 705 }
705 706 }
706 707 }
707 708
708 709 impl Into<u8> for EntryState {
709 710 fn into(self) -> u8 {
710 711 match self {
711 712 EntryState::Normal => b'n',
712 713 EntryState::Added => b'a',
713 714 EntryState::Removed => b'r',
714 715 EntryState::Merged => b'm',
715 716 }
716 717 }
717 718 }
718 719
719 720 const EXEC_BIT_MASK: u32 = 0o100;
720 721
721 722 pub fn has_exec_bit(metadata: &std::fs::Metadata) -> bool {
722 723 // TODO: How to handle executable permissions on Windows?
723 724 use std::os::unix::fs::MetadataExt;
724 725 (metadata.mode() & EXEC_BIT_MASK) != 0
725 726 }
General Comments 0
You need to be logged in to leave comments. Login now