use std::path::Path;
pub fn dir_size_skipping_git<'a, I: IntoIterator<Item = &'a Path>>(roots: I) -> u64 {
let mut total: u64 = 0;
#[cfg(unix)]
let mut seen_inodes: std::collections::HashSet<(u64, u64)> =
std::collections::HashSet::new();
for root in roots {
if !root.exists() {
continue;
}
for entry in jwalk::WalkDir::new(root)
.process_read_dir(|_, _, _, children| {
children.retain(|child| {
child
.as_ref()
.map(|e| e.file_name().to_string_lossy() != ".git")
.unwrap_or(true)
});
})
.into_iter()
.flatten()
{
if let Ok(meta) = std::fs::metadata(entry.path()) {
if meta.is_file() {
#[cfg(unix)]
{
use std::os::unix::fs::MetadataExt;
if meta.nlink() > 1 && !seen_inodes.insert((meta.dev(), meta.ino())) {
continue;
}
}
total += filesize::file_real_size_fast(entry.path(), &meta)
.unwrap_or(meta.len());
}
}
}
}
total
}
pub fn filesystem_capacity_bytes(path: &Path) -> Option<u64> {
use sysinfo::Disks;
let probe = if path.exists() {
path.to_path_buf()
} else {
path.parent().unwrap_or(Path::new("/")).to_path_buf()
};
let disks = Disks::new_with_refreshed_list();
let mut best: Option<(&sysinfo::Disk, usize)> = None;
for disk in disks.list() {
let mount = disk.mount_point();
if probe.starts_with(mount) {
let len = mount.as_os_str().len();
if best.map_or(true, |(_, cur)| len > cur) {
best = Some((disk, len));
}
}
}
best.map(|(d, _)| d.total_space())
}
#[cfg(test)]
mod tests {
use super::*;
use tempfile::TempDir;
#[test]
fn dir_size_skips_git_dir() {
let dir = TempDir::new().unwrap();
let git = dir.path().join(".git");
std::fs::create_dir_all(&git).unwrap();
std::fs::write(git.join("HEAD"), vec![0u8; 100_000]).unwrap();
std::fs::write(dir.path().join("a.txt"), b"hello").unwrap();
let size = dir_size_skipping_git([dir.path()].iter().copied());
assert!(
size < 100_000,
"size {size} must exclude the 100KB blob under .git"
);
assert!(size > 0, "size must count a.txt");
}
#[test]
fn dir_size_missing_root_is_zero() {
let p = std::path::Path::new("/tmp/definitely-not-here-xyz-1234567890");
let size = dir_size_skipping_git([p].iter().copied());
assert_eq!(size, 0);
}
#[test]
fn filesystem_capacity_is_some_for_tmp() {
let cap = filesystem_capacity_bytes(std::env::temp_dir().as_path());
assert!(cap.is_some_and(|c| c > 0), "expected positive capacity");
}
}