1use log::{debug, error, trace};
2use std::path::Path;
3use std::{io, path::PathBuf};
4
5use crate::{
6 compare_two_files, list_files, zip_dir_entries, EitherOrBoth, FileCompResult, Options,
7};
8
9#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
10pub enum DirCmpEntry {
11 Both(PathBuf, PathBuf, FileCompResult),
12 Left(PathBuf),
13 Right(PathBuf),
14}
15
16fn compare_dirs_inner(
17 left_path: &Path,
18 right_path: &Path,
19 left_base: &str,
20 right_base: &str,
21 options: &Options,
22) -> io::Result<Vec<DirCmpEntry>> {
23 trace!("comparing 2 dirs");
24
25 let mut results: Vec<DirCmpEntry> = Vec::new();
26 for dir_entry in zip_dir_entries(
27 &left_path.to_path_buf(),
28 &right_path.to_path_buf(),
29 left_base,
30 right_base,
31 &options.filter,
32 )? {
33 match dir_entry {
34 EitherOrBoth::Both(left_entry, right_entry) => {
35 if left_entry.is_file() && right_entry.is_file() {
37 let comp_result = compare_two_files(&left_entry, &right_entry)?;
38 if FileCompResult::Equal != comp_result || !options.ignore_equal {
39 results.push(DirCmpEntry::Both(
40 left_entry.to_owned(),
41 right_entry.to_owned(),
42 comp_result,
43 ));
44 }
45 }
46
47 if left_entry.is_dir() && right_entry.is_dir() {
49 if options.recursive {
50 let subtree_results = compare_dirs_inner(
51 left_entry.as_path(),
52 right_entry.as_path(),
53 left_base,
54 right_base,
55 options,
56 )?;
57 results.extend(subtree_results);
58 } else {
59 results.push(DirCmpEntry::Both(
60 left_entry.to_owned(),
61 right_entry.to_owned(),
62 FileCompResult::Equal,
63 ));
64 }
65 }
66
67 }
69 EitherOrBoth::Left(left_entry) => {
70 if !options.ignore_left_only {
71 if left_entry.is_dir() {
72 let entry_list = list_files(&left_entry);
73 for file_path in entry_list {
74 results.push(DirCmpEntry::Left(file_path));
75 }
76 continue;
77 }
78 if left_entry.is_file() {
79 results.push(DirCmpEntry::Left(left_entry));
80 continue;
81 }
82 if left_entry.is_symlink() {
83 continue;
85 }
86 }
87 }
88 EitherOrBoth::Right(right_entry) => {
89 if !options.ignore_right_only {
90 if right_entry.is_dir() {
91 let entry_list = list_files(&right_entry);
92 for file_path in entry_list {
93 results.push(DirCmpEntry::Right(file_path));
94 }
95 continue;
96 }
97 if right_entry.is_file() {
98 results.push(DirCmpEntry::Right(right_entry));
99 continue;
100 }
101 if right_entry.is_symlink() {
102 continue;
104 }
105 }
106 }
107 }
108 }
109 Ok(results)
110}
111
112#[cfg(test)]
113mod tests_compare_dirs_inner {
114 use super::*;
115 use std::fs;
116
117 fn init_logger() {
118 let _ = env_logger::builder().is_test(true).try_init();
119 }
120
121 #[test]
122 fn no_restictions() {
123 init_logger();
124 let left_dir = tempfile::Builder::new().tempdir().unwrap();
126 let file_left_both_equal = left_dir.path().join("both_equal.txt");
127 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
128 let file_left_both_diff = left_dir.path().join("both_diff.txt");
129 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
130 let file_left_only = left_dir.path().join("left_only.txt");
131 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
132
133 let right_dir = tempfile::Builder::new().tempdir().unwrap();
135 let file_right_both_equal = right_dir.path().join("both_equal.txt");
136 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
137 let file_right_both_diff = right_dir.path().join("both_diff.txt");
138 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
139 let file_right_only = right_dir.path().join("right_only.txt");
140 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
141
142 let diff_options = Options {
144 ignore_left_only: false,
145 ignore_right_only: false,
146 filter: None,
147 ignore_equal: false,
148 recursive: false,
149 };
150
151 let mut expected: Vec<DirCmpEntry> = vec![
152 DirCmpEntry::Left(file_left_only.as_path().to_path_buf()),
153 DirCmpEntry::Both(
154 file_left_both_diff.as_path().to_path_buf(),
155 file_right_both_diff.as_path().to_path_buf(),
156 FileCompResult::Different,
157 ),
158 DirCmpEntry::Both(
159 file_left_both_equal.as_path().to_path_buf(),
160 file_right_both_equal.as_path().to_path_buf(),
161 FileCompResult::Equal,
162 ),
163 DirCmpEntry::Right(file_right_only.as_path().to_path_buf()),
164 ];
165 expected.sort();
166 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
168 result.sort();
169 assert_eq!(result, expected);
170 }
171 #[test]
172 fn ignore_equal() {
173 init_logger();
174 let left_dir = tempfile::Builder::new().tempdir().unwrap();
176 let file_left_both_equal = left_dir.path().join("both_equal.txt");
177 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
178 let file_left_both_diff = left_dir.path().join("both_diff.txt");
179 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
180 let file_left_only = left_dir.path().join("left_only.txt");
181 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
182
183 let right_dir = tempfile::Builder::new().tempdir().unwrap();
185 let file_right_both_equal = right_dir.path().join("both_equal.txt");
186 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
187 let file_right_both_diff = right_dir.path().join("both_diff.txt");
188 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
189 let file_right_only = right_dir.path().join("right_only.txt");
190 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
191
192 let diff_options = Options {
194 ignore_equal: true,
195 ignore_left_only: false,
196 ignore_right_only: false,
197 filter: None,
198 recursive: false,
199 };
200
201 let mut expected: Vec<DirCmpEntry> = vec![
202 DirCmpEntry::Left(file_left_only.as_path().to_path_buf()),
203 DirCmpEntry::Both(
204 file_left_both_diff.as_path().to_path_buf(),
205 file_right_both_diff.as_path().to_path_buf(),
206 FileCompResult::Different,
207 ),
208 DirCmpEntry::Right(file_right_only.as_path().to_path_buf()),
209 ];
210 expected.sort();
211 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
213 result.sort();
214 assert_eq!(result, expected);
215 }
216
217 #[test]
218 fn ignore_left_only() {
219 init_logger();
220 let left_dir = tempfile::Builder::new().tempdir().unwrap();
222 let file_left_both_equal = left_dir.path().join("both_equal.txt");
223 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
224 let file_left_both_diff = left_dir.path().join("both_diff.txt");
225 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
226 let file_left_only = left_dir.path().join("left_only.txt");
227 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
228
229 let right_dir = tempfile::Builder::new().tempdir().unwrap();
231 let file_right_both_equal = right_dir.path().join("both_equal.txt");
232 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
233 let file_right_both_diff = right_dir.path().join("both_diff.txt");
234 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
235 let file_right_only = right_dir.path().join("right_only.txt");
236 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
237
238 let diff_options = Options {
240 ignore_equal: false,
241 ignore_left_only: true,
242 ignore_right_only: false,
243 filter: None,
244 recursive: false,
245 };
246
247 let mut expected: Vec<DirCmpEntry> = vec![
248 DirCmpEntry::Both(
249 file_left_both_diff.as_path().to_path_buf(),
250 file_right_both_diff.as_path().to_path_buf(),
251 FileCompResult::Different,
252 ),
253 DirCmpEntry::Both(
254 file_left_both_equal.as_path().to_path_buf(),
255 file_right_both_equal.as_path().to_path_buf(),
256 FileCompResult::Equal,
257 ),
258 DirCmpEntry::Right(file_right_only.as_path().to_path_buf()),
259 ];
260 expected.sort();
261 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
263 result.sort();
264 assert_eq!(result, expected);
265 }
266
267 #[test]
268 fn ignore_right_only() {
269 init_logger();
270 let left_dir = tempfile::Builder::new().tempdir().unwrap();
272 let file_left_both_equal = left_dir.path().join("both_equal.txt");
273 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
274 let file_left_both_diff = left_dir.path().join("both_diff.txt");
275 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
276 let file_left_only = left_dir.path().join("left_only.txt");
277 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
278
279 let right_dir = tempfile::Builder::new().tempdir().unwrap();
281 let file_right_both_equal = right_dir.path().join("both_equal.txt");
282 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
283 let file_right_both_diff = right_dir.path().join("both_diff.txt");
284 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
285 let file_right_only = right_dir.path().join("right_only.txt");
286 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
287
288 let diff_options = Options {
290 ignore_equal: false,
291 ignore_left_only: false,
292 ignore_right_only: true,
293 filter: None,
294 recursive: false,
295 };
296
297 let mut expected: Vec<DirCmpEntry> = vec![
298 DirCmpEntry::Left(file_left_only.as_path().to_path_buf()),
299 DirCmpEntry::Both(
300 file_left_both_diff.as_path().to_path_buf(),
301 file_right_both_diff.as_path().to_path_buf(),
302 FileCompResult::Different,
303 ),
304 DirCmpEntry::Both(
305 file_left_both_equal.as_path().to_path_buf(),
306 file_right_both_equal.as_path().to_path_buf(),
307 FileCompResult::Equal,
308 ),
309 ];
310 expected.sort();
311
312 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
314 result.sort();
315 assert_eq!(result, expected);
316 }
317
318 #[test]
319 fn recursive_false() {
320 init_logger();
321 let left_dir = tempfile::Builder::new().tempdir().unwrap();
323 let left_sub_dir = left_dir.path().join("subdir");
324 fs::create_dir(left_sub_dir.as_path()).unwrap();
325 let file_left_both_equal = left_sub_dir.join("both_equal.txt");
326 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
327 let file_left_both_diff = left_sub_dir.join("both_diff.txt");
328 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
329 let file_left_only = left_sub_dir.join("left_only.txt");
330 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
331
332 let right_dir = tempfile::Builder::new().tempdir().unwrap();
334 let right_sub_dir = right_dir.path().join("subdir");
335 fs::create_dir(right_sub_dir.as_path()).unwrap();
336 let file_right_both_equal = right_sub_dir.join("both_equal.txt");
337 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
338 let file_right_both_diff = right_sub_dir.join("both_diff.txt");
339 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
340 let file_right_only = right_sub_dir.join("right_only.txt");
341 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
342
343 let diff_options = Options {
345 ignore_equal: false,
346 ignore_left_only: false,
347 ignore_right_only: false,
348 filter: None,
349 recursive: false,
350 };
351
352 let mut expected: Vec<DirCmpEntry> = vec![DirCmpEntry::Both(
353 left_sub_dir.as_path().to_path_buf(),
354 right_sub_dir.as_path().to_path_buf(),
355 FileCompResult::Equal,
356 )];
357 expected.sort();
358 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
360 result.sort();
361 assert_eq!(result, expected);
362 }
363
364 #[test]
365 fn recursive_true() {
366 init_logger();
367 let left_dir = tempfile::Builder::new().tempdir().unwrap();
369 let left_sub_dir = left_dir.path().join("subdir");
370 fs::create_dir(left_sub_dir.as_path()).unwrap();
371 let file_left_both_equal = left_sub_dir.join("both_equal.txt");
372 fs::write(file_left_both_equal.as_path(), b"same same").unwrap();
373 let file_left_both_diff = left_sub_dir.join("both_diff.txt");
374 fs::write(file_left_both_diff.as_path(), b"differnt").unwrap();
375 let file_left_only = left_sub_dir.join("left_only.txt");
376 fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
377
378 let right_dir = tempfile::Builder::new().tempdir().unwrap();
380 let right_sub_dir = right_dir.path().join("subdir");
381 fs::create_dir(right_sub_dir.as_path()).unwrap();
382 let file_right_both_equal = right_sub_dir.join("both_equal.txt");
383 fs::write(file_right_both_equal.as_path(), b"same same").unwrap();
384 let file_right_both_diff = right_sub_dir.join("both_diff.txt");
385 fs::write(file_right_both_diff.as_path(), b"more different").unwrap();
386 let file_right_only = right_sub_dir.join("right_only.txt");
387 fs::write(file_right_only.as_path(), b"Righty right").unwrap();
388
389 let diff_options = Options {
391 ignore_equal: false,
392 ignore_left_only: false,
393 ignore_right_only: false,
394 filter: None,
395 recursive: true,
396 };
397
398 let mut expected: Vec<DirCmpEntry> = vec![
399 DirCmpEntry::Left(file_left_only.as_path().to_path_buf()),
400 DirCmpEntry::Both(
401 file_left_both_diff.as_path().to_path_buf(),
402 file_right_both_diff.as_path().to_path_buf(),
403 FileCompResult::Different,
404 ),
405 DirCmpEntry::Both(
406 file_left_both_equal.as_path().to_path_buf(),
407 file_right_both_equal.as_path().to_path_buf(),
408 FileCompResult::Equal,
409 ),
410 DirCmpEntry::Right(file_right_only.as_path().to_path_buf()),
411 ];
412 expected.sort();
413 let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
415 result.sort();
416 assert_eq!(result, expected);
417 }
418}
419
420pub fn compare_dirs(
421 left_path: &Path,
422 right_path: &Path,
423 options: Options,
424) -> io::Result<Vec<DirCmpEntry>> {
425 debug!(
426 "starting to compare for {:?} vs {:?}",
427 left_path, right_path
428 );
429
430 if !left_path.exists() {
431 error!("The left path does not exists!");
432 panic!();
433 }
434
435 if !left_path.is_dir() {
436 error!("The left path is not a directory!");
437 panic!();
438 }
439
440 if !right_path.exists() {
441 error!("The right path does not exists!");
442 panic!();
443 }
444
445 if !right_path.is_dir() {
446 error!("The right path is not a directory!");
447 panic!();
448 }
449
450 let left_base = left_path.to_str().unwrap();
451 let right_base = right_path.to_str().unwrap();
452
453 compare_dirs_inner(left_path, right_path, left_base, right_base, &options)
454}