dir_cmp/
full.rs

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                //handle two files
36                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                //handle two dirs
48                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                //ignore symlinks and mismatches
68            }
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                        //ignore
84                        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                        //ignore
103                        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        //prepare left dir
125        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        //prepare right dir
134        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        //create options without any restrictions
143        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        //compare
167        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        //prepare left dir
175        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        //prepare right dir
184        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        //create options without any restrictions
193        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        //compare
212        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        //prepare left dir
221        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        //prepare right dir
230        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        //create options without any restrictions
239        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        //compare
262        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        //prepare left dir
271        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        //prepare right dir
280        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        //create options without any restrictions
289        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        //compare
313        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        //prepare left dir
322        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        //prepare right dir
333        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        //create options without any restrictions
344        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        //compare
359        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        //prepare left dir
368        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        //prepare right dir
379        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        //create options without any restrictions
390        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        //compare
414        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}