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