|
|
… |
383 unchanged lines |
| 384 |
384 |
|
if uncommitted { |
| 385 |
385 |
|
return Ok(Some(true)); |
| 386 |
386 |
|
} |
| 387 |
|
- |
let mut seen = 0usize; |
|
387 |
+ |
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed}; |
| 388 |
388 |
|
let nested = std::sync::Arc::new(std::sync::Mutex::new(Vec::new())); |
| 389 |
389 |
|
let wb = self.walker(&nested); |
| 390 |
|
- |
for entry in wb.build() { |
| 391 |
|
- |
let Ok(entry) = entry else { continue }; |
| 392 |
|
- |
let Some(ft) = entry.file_type() else { |
| 393 |
|
- |
continue; |
| 394 |
|
- |
}; |
| 395 |
|
- |
if ft.is_dir() { |
| 396 |
|
- |
continue; |
| 397 |
|
- |
} |
| 398 |
|
- |
let rel: String = entry |
| 399 |
|
- |
.path() |
| 400 |
|
- |
.strip_prefix(&self.root) |
| 401 |
|
- |
.unwrap() |
| 402 |
|
- |
.components() |
| 403 |
|
- |
.map(|c| c.as_os_str().to_string_lossy().to_string()) |
| 404 |
|
- |
.collect::<Vec<_>>() |
| 405 |
|
- |
.join("/"); |
| 406 |
|
- |
if !self.in_scope(&rel) { |
| 407 |
|
- |
continue; |
| 408 |
|
- |
} |
| 409 |
|
- |
let Some(me) = manifest.entries.get(&rel) else { |
| 410 |
|
- |
return Ok(Some(true)); |
| 411 |
|
- |
}; |
| 412 |
|
- |
seen += 1; |
| 413 |
|
- |
let m = fs::symlink_metadata(entry.path())?; |
| 414 |
|
- |
let now_mtime = mtime_ns(&m); |
| 415 |
|
- |
if ft.is_symlink() { |
| 416 |
|
- |
let target = fs::read_link(entry.path())?; |
| 417 |
|
- |
if blake3::hash(target.to_string_lossy().as_bytes()) |
| 418 |
|
- |
.to_hex() |
| 419 |
|
- |
.as_str() |
| 420 |
|
- |
!= me.hash |
| 421 |
|
- |
{ |
| 422 |
|
- |
return Ok(Some(true)); |
|
390 |
+ |
// Every file is checked on its own, so the walk runs in parallel and |
|
391 |
+ |
// stops at the first difference. |
|
392 |
+ |
let dirty = AtomicBool::new(false); |
|
393 |
+ |
let seen = AtomicUsize::new(0); |
|
394 |
+ |
let (root, sparse) = (&self.root, &self.config.sparse_paths); |
|
395 |
+ |
let in_scope = |p: &str| { |
|
396 |
+ |
sparse.is_empty() |
|
397 |
+ |
|| sparse |
|
398 |
+ |
.iter() |
|
399 |
+ |
.any(|s| p == s || p.starts_with(&format!("{s}/"))) |
|
400 |
+ |
}; |
|
401 |
+ |
wb.build_parallel().run(|| { |
|
402 |
+ |
Box::new(|entry| { |
|
403 |
+ |
use ignore::WalkState; |
|
404 |
+ |
if dirty.load(Relaxed) { |
|
405 |
+ |
return WalkState::Quit; |
|
406 |
+ |
} |
|
407 |
+ |
let Ok(entry) = entry else { |
|
408 |
+ |
return WalkState::Continue; |
|
409 |
+ |
}; |
|
410 |
+ |
let Some(ft) = entry.file_type() else { |
|
411 |
+ |
return WalkState::Continue; |
|
412 |
+ |
}; |
|
413 |
+ |
if ft.is_dir() { |
|
414 |
+ |
return WalkState::Continue; |
|
415 |
+ |
} |
|
416 |
+ |
let rel: String = entry |
|
417 |
+ |
.path() |
|
418 |
+ |
.strip_prefix(root) |
|
419 |
+ |
.unwrap() |
|
420 |
+ |
.components() |
|
421 |
+ |
.map(|c| c.as_os_str().to_string_lossy().to_string()) |
|
422 |
+ |
.collect::<Vec<_>>() |
|
423 |
+ |
.join("/"); |
|
424 |
+ |
if !in_scope(&rel) { |
|
425 |
+ |
return WalkState::Continue; |
|
426 |
+ |
} |
|
427 |
+ |
// Whether this file differs from its entry; an unreadable |
|
428 |
+ |
// one counts as different. |
|
429 |
+ |
let differs = match manifest.entries.get(&rel) { |
|
430 |
+ |
None => true, |
|
431 |
+ |
Some(me) => { |
|
432 |
+ |
seen.fetch_add(1, Relaxed); |
|
433 |
+ |
match fs::symlink_metadata(entry.path()) { |
|
434 |
+ |
Err(_) => true, |
|
435 |
+ |
Ok(_) if ft.is_symlink() => { |
|
436 |
+ |
!fs::read_link(entry.path()).is_ok_and(|t| { |
|
437 |
+ |
blake3::hash(t.to_string_lossy().as_bytes()) |
|
438 |
+ |
.to_hex() |
|
439 |
+ |
.as_str() |
|
440 |
+ |
== me.hash |
|
441 |
+ |
}) |
|
442 |
+ |
} |
|
443 |
+ |
Ok(m) => { |
|
444 |
+ |
m.len() != me.size |
|
445 |
+ |
|| super::is_executable(&m) != me.exec |
|
446 |
+ |
|| (mtime_ns(&m) != me.mtime_ns |
|
447 |
+ |
&& !fs::read(entry.path()).is_ok_and(|b| { |
|
448 |
+ |
blake3::hash(&b).to_hex().as_str() == me.hash |
|
449 |
+ |
})) |
|
450 |
+ |
} |
|
451 |
+ |
} |
|
452 |
+ |
} |
|
453 |
+ |
}; |
|
454 |
+ |
if differs { |
|
455 |
+ |
dirty.store(true, Relaxed); |
|
456 |
+ |
return WalkState::Quit; |
| 423 |
457 |
|
} |
| 424 |
|
- |
continue; |
| 425 |
|
- |
} |
| 426 |
|
- |
if m.len() != me.size || super::is_executable(&m) != me.exec { |
| 427 |
|
- |
return Ok(Some(true)); |
| 428 |
|
- |
} |
| 429 |
|
- |
if now_mtime != me.mtime_ns |
| 430 |
|
- |
&& blake3::hash(&fs::read(entry.path())?).to_hex().as_str() != me.hash |
| 431 |
|
- |
{ |
| 432 |
|
- |
return Ok(Some(true)); |
| 433 |
|
- |
} |
|
458 |
+ |
WalkState::Continue |
|
459 |
+ |
}) |
|
460 |
+ |
}); |
|
461 |
+ |
if dirty.load(Relaxed) { |
|
462 |
+ |
return Ok(Some(true)); |
| 434 |
463 |
|
} |
|
464 |
+ |
let seen = seen.load(Relaxed); |
| 435 |
465 |
|
if seen == manifest.entries.len() { |
| 436 |
466 |
|
return Ok(Some(false)); |
| 437 |
467 |
|
} |
|
|
… |
96 unchanged lines |