Skip to main content

fluidattacks_core_git/
fs.rs

1use anyhow::Result;
2use ignore::gitignore::GitignoreBuilder;
3use std::fs;
4use std::path::Path;
5use walkdir::WalkDir;
6
7pub fn remove_symlinks(dir: &Path) -> Result<()> {
8    for entry in WalkDir::new(dir) {
9        let entry = entry?;
10        let metadata = entry.path().symlink_metadata()?;
11        if metadata.file_type().is_symlink() {
12            fs::remove_file(entry.path())?;
13        }
14    }
15    Ok(())
16}
17
18pub fn delete_gitignored_files(repo_path: &Path, patterns: &[String]) -> Result<()> {
19    let mut builder = GitignoreBuilder::new(repo_path);
20    for pattern in patterns {
21        builder.add_line(None, pattern)?;
22    }
23    let gitignore = builder.build()?;
24
25    for entry in WalkDir::new(repo_path) {
26        let entry = entry?;
27        let path = entry.path();
28
29        let Ok(rel) = path.strip_prefix(repo_path) else {
30            continue;
31        };
32
33        let rel_str = rel.to_string_lossy();
34
35        if rel_str == ".git" || rel_str.starts_with(".git/") || rel_str.starts_with(".git\\") {
36            continue;
37        }
38
39        if rel_str.is_empty() {
40            continue;
41        }
42
43        if !entry.file_type().is_dir() && gitignore.matched(rel, false).is_ignore() {
44            let _ = fs::remove_file(path);
45        }
46    }
47
48    remove_empty_dirs(repo_path)?;
49    Ok(())
50}
51
52pub fn copy_dir_all(src: &Path, dst: &Path) -> Result<()> {
53    fs::create_dir_all(dst)?;
54    for entry in fs::read_dir(src)? {
55        let entry = entry?;
56        let ty = entry.file_type()?;
57        let dst_path = dst.join(entry.file_name());
58        if ty.is_dir() {
59            copy_dir_all(&entry.path(), &dst_path)?;
60        } else {
61            fs::copy(entry.path(), &dst_path)?;
62            copy_modified(&entry.path(), &dst_path);
63        }
64    }
65    Ok(())
66}
67
68fn copy_modified(src: &Path, dst: &Path) {
69    let Ok(modified) = fs::metadata(src).and_then(|meta| meta.modified()) else {
70        return;
71    };
72    if let Ok(file) = fs::File::open(dst) {
73        let _ = file.set_modified(modified);
74    }
75}
76
77fn remove_empty_dirs(root: &Path) -> Result<()> {
78    let mut dirs: Vec<_> = Vec::new();
79
80    for entry in WalkDir::new(root) {
81        let entry = entry?;
82        let path = entry.path();
83
84        let Ok(rel) = path.strip_prefix(root) else {
85            continue;
86        };
87
88        let rel_str = rel.to_string_lossy();
89
90        if rel_str == ".git" || rel_str.starts_with(".git/") || rel_str.starts_with(".git\\") {
91            continue;
92        }
93
94        if entry.file_type().is_dir() && path != root {
95            dirs.push(path.to_path_buf());
96        }
97    }
98
99    dirs.sort();
100    dirs.reverse();
101
102    for dir in dirs {
103        if let Ok(entries) = fs::read_dir(&dir) {
104            if entries.count() == 0 {
105                let _ = fs::remove_dir(&dir);
106            }
107        }
108    }
109
110    Ok(())
111}
112
113#[cfg(test)]
114mod tests {
115    use super::copy_dir_all;
116    use std::time::{Duration, SystemTime};
117
118    #[test]
119    fn a_copied_tree_keeps_the_mode_and_the_modification_time() {
120        let dir = tempfile::tempdir().unwrap();
121        let src = dir.path().join("src/objects");
122        std::fs::create_dir_all(&src).unwrap();
123        let object = src.join("a1b2c3");
124        std::fs::write(&object, b"blob").unwrap();
125
126        let modified = SystemTime::UNIX_EPOCH
127            .checked_add(Duration::from_secs(1_700_000_000))
128            .unwrap();
129        std::fs::File::open(&object)
130            .unwrap()
131            .set_modified(modified)
132            .unwrap();
133        #[cfg(unix)]
134        {
135            use std::os::unix::fs::PermissionsExt;
136            std::fs::set_permissions(&object, std::fs::Permissions::from_mode(0o444)).unwrap();
137        }
138
139        let dst = dir.path().join("dst");
140        copy_dir_all(&dir.path().join("src"), &dst).unwrap();
141
142        let copied = dst.join("objects/a1b2c3");
143        let meta = std::fs::metadata(&copied).unwrap();
144        assert_eq!(meta.modified().unwrap(), modified);
145        #[cfg(unix)]
146        {
147            use std::os::unix::fs::PermissionsExt;
148            assert_eq!(meta.permissions().mode() & 0o777, 0o444);
149        }
150    }
151}