1#![warn(missing_docs)]
10#![forbid(unsafe_code)]
11pub mod abspath;
12pub mod error;
13pub mod ignore_rules;
14pub mod notify;
15pub mod pattern;
16pub mod sync;
17pub mod walk_parallel;
18pub mod walk_serial;
19
20pub use pattern::MatchResult;
21pub use pattern::PathKind;
22pub use pattern::Pattern;
23pub use pattern::PatternEffect;
24pub use pattern::PatternRelativity;
25pub use pattern::Source;
26
27pub use walk_parallel::walk_parallel;
28pub use walk_serial::walk_serial;
29
30pub use walk_serial::path_metadata_map_from_file_targets;
31
32pub use abspath::AbsolutePath;
33pub use error::{Error, Result};
34
35pub use ignore_rules::IgnoreRules;
36pub use ignore_rules::SharedIgnoreRules;
37
38pub use std::hash::Hash;
39pub use sync::{PathSync, PathSyncSingleton};
40use xvc_logging::warn;
41
42pub use notify::make_polling_watcher;
43pub use notify::make_watcher;
44pub use notify::PathEvent;
45pub use notify::RecommendedWatcher;
46
47pub use fast_glob::Glob;
48
49use xvc_logging::watch;
50
51use std::{
52 fmt::Debug,
53 fs::{self, Metadata},
54 path::{Path, PathBuf},
55};
56
57use anyhow::anyhow;
58
59static MAX_THREADS_PARALLEL_WALK: usize = 8;
60
61#[derive(Debug, Clone)]
63pub struct PathMetadata {
64 pub path: PathBuf,
66 pub metadata: Metadata,
68}
69
70#[derive(Debug, Clone)]
72pub struct WalkOptions {
73 pub ignore_filename: Option<String>,
76 pub include_dirs: bool,
80}
81
82impl WalkOptions {
83 pub fn gitignore() -> Self {
86 Self {
87 ignore_filename: Some(".gitignore".into()),
88 include_dirs: true,
89 }
90 }
91
92 pub fn xvcignore() -> Self {
95 Self {
96 ignore_filename: Some(".xvcignore".into()),
97 include_dirs: true,
98 }
99 }
100
101 pub fn without_dirs(self) -> Self {
105 Self {
106 ignore_filename: self.ignore_filename,
107 include_dirs: false,
108 }
109 }
110 pub fn with_dirs(self) -> Self {
112 Self {
113 ignore_filename: self.ignore_filename,
114 include_dirs: true,
115 }
116 }
117}
118
119pub fn build_ignore_patterns(
121 given: &str,
122 ignore_root: &Path,
123 ignore_filename: &str,
124) -> Result<IgnoreRules> {
125 watch!(ignore_filename);
126 watch!(ignore_root);
127
128 let ignore_rules = IgnoreRules::from_global_patterns(ignore_root, Some(ignore_filename), given);
129
130 let dirs_under = |p: &Path| -> Vec<PathBuf> {
131 p.read_dir()
132 .unwrap()
133 .filter_map(|p| {
134 if let Ok(p) = p {
135 if p.path().is_dir() {
136 Some(p.path())
137 } else {
138 None
139 }
140 } else {
141 None
142 }
143 })
144 .filter_map(|p| match ignore_rules.check(&p) {
145 MatchResult::NoMatch | MatchResult::Whitelist => Some(p),
146 MatchResult::Ignore => None,
147 })
148 .collect()
149 };
150
151 let mut dir_stack: Vec<PathBuf> = vec![ignore_root.to_path_buf()];
152
153 let ignore_fn = ignore_rules.ignore_filename.as_deref().unwrap();
154
155 while let Some(dir) = dir_stack.pop() {
156 let ignore_filename = dir.join(ignore_fn);
157 if ignore_filename.is_file() {
158 let ignore_content = fs::read_to_string(&ignore_filename)?;
159 let new_patterns =
160 content_to_patterns(ignore_root, Some(&ignore_filename), &ignore_content);
161 ignore_rules.add_patterns(new_patterns)?;
162 }
163 let mut new_dirs = dirs_under(&dir);
164 dir_stack.append(&mut new_dirs);
165 }
166
167 Ok(ignore_rules)
168}
169
170pub fn content_to_patterns(
174 ignore_root: &Path,
175 source: Option<&Path>,
176 content: &str,
177) -> Vec<Pattern> {
178 let patterns: Vec<Pattern> = content
179 .lines()
180 .enumerate()
181 .filter(|(_, line)| !(line.trim().is_empty() || line.starts_with('#')))
183 .map(|(i, line)| {
185 if !line.ends_with("\\ ") {
186 (i, line.trim_end())
187 } else {
188 (i, line)
189 }
190 })
191 .map(|(i, line)| {
193 (
194 line,
195 match source {
196 Some(p) => Source::File {
197 path: p
198 .strip_prefix(ignore_root)
199 .expect("path must be within ignore_root")
200 .to_path_buf(),
201 line: (i + 1),
202 },
203 None => Source::Global,
204 },
205 )
206 })
207 .map(|(line, source)| Pattern::new(source, line))
208 .collect();
209
210 patterns
211}
212
213pub fn update_ignore_rules(dir: &Path, ignore_rules: &IgnoreRules) -> Result<()> {
218 if let Some(ref ignore_filename) = ignore_rules.ignore_filename {
219 let ignore_root = &ignore_rules.root;
220 let ignore_path = dir.join(ignore_filename);
221 if ignore_path.is_file() {
222 let new_patterns: Vec<Pattern> = {
223 let content = fs::read_to_string(&ignore_path)?;
224 content_to_patterns(ignore_root, Some(ignore_path).as_deref(), &content)
225 };
226
227 ignore_rules.add_patterns(new_patterns)?;
228 }
229 }
230 Ok(())
231}
232pub fn directory_list(dir: &Path) -> Result<Vec<Result<PathMetadata>>> {
235 let elements = dir
236 .read_dir()
237 .map_err(|e| anyhow!("Error reading directory: {:?}, {:?}", dir, e))?;
238 let mut child_paths = Vec::<Result<PathMetadata>>::new();
239
240 for entry in elements {
241 match entry {
242 Err(err) => child_paths.push(Err(Error::from(anyhow!(
243 "Error reading entry in dir {:?} {:?}",
244 dir,
245 err
246 )))),
247 Ok(entry) => match entry.metadata() {
248 Err(err) => child_paths.push(Err(Error::from(anyhow!(
249 "Error getting metadata {:?} {:?}",
250 entry,
251 err
252 )))),
253 Ok(md) => {
254 child_paths.push(Ok(PathMetadata {
255 path: entry.path(),
256 metadata: md.clone(),
257 }));
258 }
259 },
260 }
261 }
262 Ok(child_paths)
263}
264
265#[cfg(test)]
266mod tests {
267
268 use super::*;
269
270 use log::LevelFilter;
271 use test_case::test_case;
272
273 use crate::error::Result;
274 use crate::AbsolutePath;
275 use xvc_test_helper::*;
276
277 #[test_case("!mydir/*/file" => matches PatternEffect::Whitelist ; "t1159938339")]
278 #[test_case("!mydir/myfile" => matches PatternEffect::Whitelist ; "t1302522194")]
279 #[test_case("!myfile" => matches PatternEffect::Whitelist ; "t3599739725")]
280 #[test_case("!myfile/" => matches PatternEffect::Whitelist ; "t389990097")]
281 #[test_case("/my/file" => matches PatternEffect::Ignore ; "t3310011546")]
282 #[test_case("mydir/*" => matches PatternEffect::Ignore ; "t1461510927")]
283 #[test_case("mydir/file" => matches PatternEffect::Ignore; "t4096563949")]
284 #[test_case("myfile" => matches PatternEffect::Ignore; "t4042406621")]
285 #[test_case("myfile*" => matches PatternEffect::Ignore ; "t3367706249")]
286 #[test_case("myfile/" => matches PatternEffect::Ignore ; "t1204466627")]
287 fn test_pattern_effect(line: &str) -> PatternEffect {
288 let pat = Pattern::new(Source::Global, line);
289 pat.effect
290 }
291
292 #[test_case("", "!mydir/*/file" => matches PatternRelativity::RelativeTo { directory } if directory.is_empty() ; "t500415168")]
293 #[test_case("", "!mydir/myfile" => matches PatternRelativity::RelativeTo {directory} if directory.is_empty() ; "t1158125354")]
294 #[test_case("dir/", "!mydir/*/file" => matches PatternRelativity::RelativeTo { directory } if directory == "/dir" ; "t3052699971")]
295 #[test_case("dir/", "!mydir/myfile" => matches PatternRelativity::RelativeTo {directory} if directory == "/dir" ; "t885029019")]
296 #[test_case("", "!myfile" => matches PatternRelativity::Anywhere; "t3101661374")]
297 #[test_case("", "!myfile/" => matches PatternRelativity::Anywhere ; "t3954695505")]
298 #[test_case("", "/my/file" => matches PatternRelativity::RelativeTo { directory } if directory.is_empty() ; "t1154256567")]
299 #[test_case("", "mydir/*" => matches PatternRelativity::RelativeTo { directory } if directory.is_empty() ; "t865348822")]
300 #[test_case("", "mydir/file" => matches PatternRelativity::RelativeTo { directory } if directory.is_empty() ; "t809589695")]
301 #[test_case("root/", "/my/file" => matches PatternRelativity::RelativeTo { directory } if directory == "/root" ; "t7154256567")]
302 #[test_case("root/", "mydir/*" => matches PatternRelativity::RelativeTo { directory } if directory == "/root" ; "t765348822")]
303 #[test_case("root/", "mydir/file" => matches PatternRelativity::RelativeTo { directory } if directory == "/root" ; "t709589695")]
304 #[test_case("", "myfile" => matches PatternRelativity::Anywhere; "t949952742")]
305 #[test_case("", "myfile*" => matches PatternRelativity::Anywhere ; "t2212007572")]
306 #[test_case("", "myfile/" => matches PatternRelativity::Anywhere; "t900104620")]
307 fn test_pattern_relativity(dir: &str, line: &str) -> PatternRelativity {
308 let source = Source::File {
309 path: PathBuf::from(dir).join(".gitignore"),
310 line: 1,
311 };
312 let pat = Pattern::new(source, line);
313 pat.relativity
314 }
315
316 #[test_case("", "!mydir/*/file" => matches PathKind::Any ; "t4069397926")]
317 #[test_case("", "!mydir/myfile" => matches PathKind::Any ; "t206435934")]
318 #[test_case("", "!myfile" => matches PathKind::Any ; "t4262638148")]
319 #[test_case("", "!myfile/" => matches PathKind::Directory ; "t214237847")]
320 #[test_case("", "/my/file" => matches PathKind::Any ; "t187692643")]
321 #[test_case("", "mydir/*" => matches PathKind::Any ; "t1159784957")]
322 #[test_case("", "mydir/file" => matches PathKind::Any ; "t2011171465")]
323 #[test_case("", "myfile" => matches PathKind::Any ; "t167946945")]
324 #[test_case("", "myfile*" => matches PathKind::Any ; "t3091563211")]
325 #[test_case("", "myfile/" => matches PathKind::Directory ; "t1443554623")]
326 fn test_path_kind(dir: &str, line: &str) -> PathKind {
327 let source = Source::File {
328 path: PathBuf::from(dir).join(".gitignore"),
329 line: 1,
330 };
331 let pat = Pattern::new(source, line);
332 pat.path_kind
333 }
334
335 #[test_case("" => 0)]
336 #[test_case("myfile" => 1)]
337 #[test_case("mydir/myfile" => 1)]
338 #[test_case("mydir/myfile\n!myfile" => 2)]
339 #[test_case("mydir/myfile\n/another" => 2)]
340 #[test_case("mydir/myfile\n\n\nanother" => 2)]
341 #[test_case("#comment\nmydir/myfile\n\n\nanother" => 2)]
342 #[test_case("#mydir/myfile" => 0)]
343 fn test_content_to_patterns_count(contents: &str) -> usize {
344 let patterns = content_to_patterns(Path::new(""), None, contents);
345 patterns.len()
346 }
347
348 fn create_patterns(root: &str, dir: Option<&str>, patterns: &str) -> Vec<Pattern> {
349 content_to_patterns(Path::new(root), dir.map(Path::new), patterns)
350 }
351
352 fn new_dir_with_ignores(
353 root: &str,
354 dir: Option<&str>,
355 initial_patterns: &str,
356 ) -> Result<IgnoreRules> {
357 let patterns = create_patterns(root, dir, initial_patterns);
358 let initialized = IgnoreRules::empty(&PathBuf::from(root), None);
359
360 initialized.add_patterns(patterns)?;
361 Ok(initialized)
362 }
363
364 #[test_case(".", "" ; "empty_dwi")]
365 #[test_case("dir", "myfile")]
366 #[test_case("dir", "mydir/myfile")]
367 #[test_case("dir", "mydir/myfile\n!myfile")]
368 #[test_case("dir", "mydir/myfile\n/another")]
369 #[test_case("dir", "mydir/myfile\n\n\nanother")]
370 #[test_case("dir", "#comment\nmydir/myfile\n\n\nanother")]
371 #[test_case("dir", "#mydir/myfile" ; "single ignored lined")]
372 fn test_dir_with_ignores(dir: &str, contents: &str) {
373 new_dir_with_ignores(dir, None, contents).unwrap();
374 }
375
376 #[test_case("/dir", "/mydir/myfile/" => matches PatternRelativity::RelativeTo { directory } if directory == "/dir" ; "t868594159")]
377 #[test_case("/dir", "mydir" => matches PatternRelativity::Anywhere ; "t4030766779")]
378 #[test_case("/dir/", "mydir/myfile" => matches PatternRelativity::RelativeTo { directory } if directory == "/dir" ; "t2043231107")]
379 #[test_case("dir", "myfile" => matches PatternRelativity::Anywhere; "t871610344" )]
380 #[test_case("dir/", "mydir/myfile" => matches PatternRelativity::RelativeTo { directory } if directory == "/dir" ; "t21398102")]
381 #[test_case("dir/", "myfile" => matches PatternRelativity::Anywhere ; "t1846637197")]
382 #[test_case("dir//", "/mydir/myfile" => matches PatternRelativity::RelativeTo { directory } if directory == "/dir" ; "t2556287848")]
383 fn test_path_relativity(dir: &str, pattern: &str) -> PatternRelativity {
384 let source = Source::File {
385 path: PathBuf::from(format!("{dir}/.gitignore")),
386 line: 1,
387 };
388 let pattern = Pattern::new(source, pattern);
389 pattern.relativity
390 }
391 #[test_case("dir", "myfile" => "**/myfile" ; "t1242345310")]
429 #[test_case("dir", "/myfile" => "/**/myfile" ; "t3427001291")]
430 #[test_case("dir", "myfile/" => "**/myfile/**" ; "t759151905")]
431 #[test_case("dir", "mydir/myfile" => "/**/mydir/myfile" ; "t21199018562")]
432 #[test_case("dir", "/my/file.*" => "/**/my/file.*" ; "t61199018162")]
433 #[test_case("dir", "/mydir/**.*" => "/**/mydir/**.*" ; "t47199018162")]
434 fn test_pattern_line(dir: &str, pattern: &str) -> String {
435 let source = Source::File {
436 path: PathBuf::from(format!("{dir}.gitignore")),
437 line: 1,
438 };
439 let pattern = Pattern::new(source, pattern);
440 pattern.glob
441 }
442
443 #[test_case("", "#mydir/myfile", "" => matches MatchResult::NoMatch ; "t01")]
445 #[test_case("", "", "" => matches MatchResult::NoMatch ; "t02" )]
446 #[test_case("", "\n\n \n", "" => matches MatchResult::NoMatch; "t03" )]
447 #[test_case("", "dir-0001", "" => matches MatchResult::NoMatch ; "t04" )]
448 #[test_case("", "dir-0001/file-0001.bin", "" => matches MatchResult::NoMatch ; "t05" )]
449 #[test_case("", "dir-0001/*", "" => matches MatchResult::NoMatch ; "t06" )]
450 #[test_case("", "dir-0001/**", "" => matches MatchResult::NoMatch ; "t07" )]
451 #[test_case("", "dir-0001/dir-0001**", "" => matches MatchResult::NoMatch ; "t08" )]
452 #[test_case("", "dir-0001/dir-00*", "" => matches MatchResult::NoMatch ; "t09" )]
453 #[test_case("", "dir-00**/", "" => matches MatchResult::NoMatch ; "t10" )]
454 #[test_case("", "dir-00**/*/file-0001.bin", "" => matches MatchResult::NoMatch ; "t11" )]
455 #[test_case("", "dir-00**/*/*.bin", "" => matches MatchResult::NoMatch ; "t12" )]
456 #[test_case("", "dir-00**/", "" => matches MatchResult::NoMatch ; "t13" )]
457 #[test_case("", "#mydir/myfile", "" => matches MatchResult::NoMatch ; "t148864489901")]
458 #[test_case("", "", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t172475356002" )]
460 #[test_case("", "\n\n \n", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch; "t8688937603" )]
461 #[test_case("", "dir-0001", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t132833780304" )]
462 #[test_case("", "dir-0001/file-0001.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t173193800505" )]
463 #[test_case("", "dir-0001/dir-0001**", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t318664043308" )]
464 #[test_case("", "dir-0001/dir-00*", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t269908728009" )]
465 #[test_case("", "dir-00**/*/file-0001.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t142240004811" )]
466 #[test_case("", "dir-00**/*/*.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t414921892712" )]
467 #[test_case("", "dir-00**/", "dir-0001/file-0002.bin" => matches MatchResult::Ignore; "t256322548613" )]
468 #[test_case("", "dir-0001/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t3378553489" )]
470 #[test_case("", "dir-0001/file-0001.*", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t3449646229" )]
471 #[test_case("", "dir-0001/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t1232001745" )]
472 #[test_case("", "dir-0001/*", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t2291655464" )]
473 #[test_case("", "dir-0001/**/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t355659763" )]
474 #[test_case("", "dir-0001/**", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t1888678340" )]
475 #[test_case("", "dir-000?/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t1603222532" )]
476 #[test_case("", "dir-000?/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t2528090273" )]
477 #[test_case("", "dir-*/*", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t3141482339" )]
478 #[test_case("", "!dir-0001", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t2963495371" )]
480 #[test_case("", "!dir-0001/file-0001.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t3935333051" )]
481 #[test_case("", "!dir-0001/dir-0001**", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t3536143628" )]
482 #[test_case("", "!dir-0001/dir-00*", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t4079058836" )]
483 #[test_case("", "!dir-00**/", "dir-0001/file-0002.bin" => matches MatchResult::Whitelist ; "t3713155445" )]
484 #[test_case("", "!dir-00**/*/file-0001.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t1434153118" )]
485 #[test_case("", "!dir-00**/*/*.bin", "dir-0001/file-0002.bin" => matches MatchResult::NoMatch ; "t1650195998" )]
486 #[test_case("", "!dir-0001/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t1569068369" )]
487 #[test_case("", "!dir-0001/file-0001.*", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t2919165396" )]
488 #[test_case("", "!dir-0001/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t2682012728" )]
489 #[test_case("", "!dir-0001/*", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t4009543743" )]
490 #[test_case("", "!dir-0001/**/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t3333689486" )]
491 #[test_case("", "!dir-0001/**", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t4259364613" )]
492 #[test_case("", "!dir-000?/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t3424909626" )]
493 #[test_case("", "!dir-000?/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t3741545053" )]
494 #[test_case("", "!dir-*/*", "dir-0001/file-0001.bin" => matches MatchResult::Whitelist ; "t1793504005" )]
495 #[test_case("dir-0001", "/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t1295565113" )]
497 #[test_case("dir-0001", "/file-0001.*", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t4048655621" )]
498 #[test_case("dir-0001", "/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t2580936986" )]
499 #[test_case("dir-0001", "/*", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t109602877" )]
500 #[test_case("dir-0001", "/**/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t112292599" )]
501 #[test_case("dir-0001", "/**", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t1323958164" )]
502 #[test_case("dir-0001", "/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t4225367752" )]
503 #[test_case("dir-0001", "/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::Ignore ; "t3478922394" )]
504 #[test_case("dir-0002", "/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t345532514" )]
506 #[test_case("dir-0002", "/file-0001.*", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t1313276210" )]
507 #[test_case("dir-0002", "/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t657078396" )]
508 #[test_case("dir-0002", "/*", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t2456576806" )]
509 #[test_case("dir-0002", "/**/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t2629832143" )]
510 #[test_case("dir-0002", "/**", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t2090580478" )]
511 #[test_case("dir-0002", "/file-0001.bin", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t1588943529" )]
512 #[test_case("dir-0002", "/*.bin", "dir-0001/file-0001.bin" => matches MatchResult::NoMatch ; "t371313784" )]
513 fn test_match_result(dir: &str, contents: &str, path: &str) -> MatchResult {
514 test_logging(LevelFilter::Trace);
515
516 let root = create_directory_hierarchy(false).unwrap();
517 let source_file = format!("{root}/{dir}/.gitignore");
518 let path = root.as_ref().join(path).to_owned();
519 let dwi =
520 new_dir_with_ignores(root.to_str().unwrap(), Some(&source_file), contents).unwrap();
521
522 dwi.check(&path)
523 }
524
525 #[test_case(true => matches Ok(_); "this is to refresh the dir for each test run")]
527 fn create_directory_hierarchy(force: bool) -> Result<AbsolutePath> {
529 let temp_dir: PathBuf = seeded_temp_dir("xvc-walker", Some(20220615));
530
531 if force && temp_dir.exists() {
532 fs::remove_dir_all(&temp_dir)?;
533 }
534
535 if !temp_dir.exists() {
536 fs::create_dir(&temp_dir)?;
538 create_directory_tree(&temp_dir, 10, 10, 1000, None)?;
539 let level_1 = &temp_dir.join("dir-0001");
541 create_directory_tree(level_1, 10, 10, 1000, None)?;
542 let level_2 = &level_1.join("dir-0001");
544 create_directory_tree(level_2, 10, 10, 1000, None)?;
545 }
546
547 Ok(AbsolutePath::from(temp_dir))
548 }
549}