Skip to main content

worktree_setup_copy/
count.rs

1//! Fast file counting using jwalk.
2
3#![cfg_attr(feature = "fail-on-warnings", deny(warnings))]
4#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
5#![allow(clippy::multiple_crate_versions)]
6
7use std::path::Path;
8
9/// Count files in a path.
10///
11/// - If path is a file: returns 1
12/// - If path is a directory: returns count of all files recursively
13/// - If path doesn't exist or is a symlink: returns 0
14///
15/// Uses `jwalk` with sorting disabled for maximum speed.
16#[must_use]
17pub fn count_files(path: &Path) -> u64 {
18    count_files_with_progress(path, |_| {})
19}
20
21/// Count files in a directory with progress callback.
22///
23/// The callback is invoked every 100 files with the current count.
24/// This allows updating a progress display during enumeration of large directories.
25///
26/// - If path is a file: returns 1
27/// - If path is a directory: returns count of all files recursively
28/// - If path doesn't exist or is a symlink: returns 0
29///
30/// # Arguments
31///
32/// * `path` - Path to count files in
33/// * `on_progress` - Callback invoked every 100 files with current count
34///
35/// # Returns
36///
37/// Total file count
38pub fn count_files_with_progress<F>(path: &Path, on_progress: F) -> u64
39where
40    F: Fn(u64),
41{
42    if !path.exists() {
43        return 0;
44    }
45
46    // For symlinks, we don't count inside them
47    if path.is_symlink() {
48        return 0;
49    }
50
51    if path.is_file() {
52        on_progress(1);
53        return 1;
54    }
55
56    if !path.is_dir() {
57        return 0;
58    }
59
60    // Use jwalk with sort disabled for speed
61    let mut count = 0u64;
62    for _entry in jwalk::WalkDir::new(path)
63        .skip_hidden(false)
64        .sort(false)
65        .into_iter()
66        .filter_map(Result::ok)
67        .filter(|e| e.file_type().is_file())
68    {
69        count += 1;
70        if count.is_multiple_of(100) {
71            on_progress(count);
72        }
73    }
74
75    // Final callback with total
76    on_progress(count);
77    count
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83    use std::fs;
84    use tempfile::TempDir;
85
86    #[test]
87    fn test_count_files_single_file() {
88        let dir = TempDir::new().unwrap();
89        let file = dir.path().join("test.txt");
90        fs::write(&file, "content").unwrap();
91
92        assert_eq!(count_files(&file), 1);
93    }
94
95    #[test]
96    fn test_count_files_directory() {
97        let dir = TempDir::new().unwrap();
98        fs::write(dir.path().join("file1.txt"), "1").unwrap();
99        fs::write(dir.path().join("file2.txt"), "2").unwrap();
100        fs::create_dir(dir.path().join("subdir")).unwrap();
101        fs::write(dir.path().join("subdir/file3.txt"), "3").unwrap();
102
103        assert_eq!(count_files(dir.path()), 3);
104    }
105
106    #[test]
107    fn test_count_files_empty_directory() {
108        let dir = TempDir::new().unwrap();
109        assert_eq!(count_files(dir.path()), 0);
110    }
111
112    #[test]
113    fn test_count_files_nonexistent() {
114        let path = Path::new("/nonexistent/path/that/does/not/exist");
115        assert_eq!(count_files(path), 0);
116    }
117}