dir_cmp/
light.rs

1use log::{debug, error, trace};
2
3use std::io;
4use std::path::Path;
5
6use crate::{list_files, zip_dir_entries, EitherOrBoth, Options};
7
8fn compare_dirs_inner(
9    left_path: &Path,
10    right_path: &Path,
11    left_base: &str,
12    right_base: &str,
13    options: &Options,
14) -> io::Result<Vec<EitherOrBoth>> {
15    trace!("comparing 2 dirs");
16
17    let mut results: Vec<EitherOrBoth> = Vec::new();
18    for dir_entry in zip_dir_entries(
19        &left_path.to_path_buf(),
20        &right_path.to_path_buf(),
21        left_base,
22        right_base,
23        &options.filter,
24    )? {
25        match dir_entry {
26            EitherOrBoth::Both(left_entry, right_entry) => {
27                trace!("handling EitherOrBoth::Both");
28                debug!("comparing{:?} vs {:?}", left_entry, right_entry);
29                if !options.ignore_equal {
30                    if left_entry.is_dir() && right_entry.is_dir() {
31                        //handle two dirs
32                        let subtree_results = compare_dirs_inner(
33                            left_entry.as_path(),
34                            right_entry.as_path(),
35                            left_base,
36                            right_base,
37                            options,
38                        )?;
39                        results.extend(subtree_results);
40                    }
41
42                    //handle two files
43                    if left_entry.is_file() && right_entry.is_file() {
44                        results.push(EitherOrBoth::Both(
45                            left_entry.to_owned(),
46                            right_entry.to_owned(),
47                        ));
48                    }
49
50                    //ignore symlinks and mismatches
51                }
52            }
53            EitherOrBoth::Left(left_entry) => {
54                trace!("handling EitherOrBoth::Left");
55                if !options.ignore_left_only {
56                    if left_entry.is_dir() {
57                        let entry_list = list_files(&left_entry);
58                        for file_path in entry_list {
59                            results.push(EitherOrBoth::Left(file_path));
60                        }
61                        continue;
62                    }
63                    if left_entry.is_file() {
64                        results.push(EitherOrBoth::Left(left_entry));
65                        continue;
66                    }
67                    if left_entry.is_symlink() {
68                        //ignore
69                        continue;
70                    }
71                }
72            }
73            EitherOrBoth::Right(right_entry) => {
74                trace!("handling EitherOrBoth::Right");
75                if !options.ignore_right_only {
76                    if right_entry.is_dir() {
77                        let entry_list = list_files(&right_entry);
78                        for file_path in entry_list {
79                            results.push(EitherOrBoth::Right(file_path));
80                        }
81                        continue;
82                    }
83                    if right_entry.is_file() {
84                        results.push(EitherOrBoth::Right(right_entry));
85                        continue;
86                    }
87                    if right_entry.is_symlink() {
88                        //ignore
89                        continue;
90                    }
91                }
92            }
93        }
94    }
95    Ok(results)
96}
97
98#[cfg(test)]
99mod tests_compare_dirs_inner {
100    use super::*;
101    use std::fs;
102
103    fn init_logger() {
104        let _ = env_logger::builder().is_test(true).try_init();
105    }
106
107    #[test]
108    fn no_restictions() {
109        init_logger();
110        //prepare left dir
111        let left_dir = tempfile::Builder::new().tempdir().unwrap();
112        let file_left_both = left_dir.path().join("both.txt");
113        fs::write(file_left_both.as_path(), b"Left and Right").unwrap();
114        let file_left_only = left_dir.path().join("left_only.txt");
115        fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
116
117        //prepare right dir
118        let right_dir = tempfile::Builder::new().tempdir().unwrap();
119        let file_right_both = right_dir.path().join("both.txt");
120        fs::write(file_right_both.as_path(), b"Right and Left").unwrap();
121        let file_right_only = right_dir.path().join("right_only.txt");
122        fs::write(file_right_only.as_path(), b"Righty right").unwrap();
123
124        //create options without any restrictions
125        let diff_options = Options {
126            ignore_equal: false,
127            ignore_left_only: false,
128            ignore_right_only: false,
129            filter: None,
130            recursive: false,
131        };
132
133        let mut expected: Vec<EitherOrBoth> = vec![
134            EitherOrBoth::Left(file_left_only.as_path().to_path_buf()),
135            EitherOrBoth::Both(
136                file_left_both.as_path().to_path_buf(),
137                file_right_both.as_path().to_path_buf(),
138            ),
139            EitherOrBoth::Right(file_right_only.as_path().to_path_buf()),
140        ];
141        expected.sort();
142        //compare
143        let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
144        result.sort();
145        assert_eq!(result, expected);
146    }
147
148    #[test]
149    fn ignore_equal() {
150        init_logger();
151        //prepare left dir
152        let left_dir = tempfile::Builder::new().tempdir().unwrap();
153        let file_left_both = left_dir.path().join("both.txt");
154        fs::write(file_left_both.as_path(), b"Left and Right").unwrap();
155        let file_left_only = left_dir.path().join("left_only.txt");
156        fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
157
158        //prepare right dir
159        let right_dir = tempfile::Builder::new().tempdir().unwrap();
160        let file_right_both = right_dir.path().join("both.txt");
161        fs::write(file_right_both.as_path(), b"Right and Left").unwrap();
162        let file_right_only = right_dir.path().join("right_only.txt");
163        fs::write(file_right_only.as_path(), b"Righty right").unwrap();
164
165        //create options without any restrictions
166        let diff_options = Options {
167            ignore_equal: true,
168            ignore_left_only: false,
169            ignore_right_only: false,
170            filter: None,
171            recursive: false,
172        };
173
174        let mut expected: Vec<EitherOrBoth> = vec![
175            EitherOrBoth::Left(file_left_only.as_path().to_path_buf()),
176            EitherOrBoth::Right(file_right_only.as_path().to_path_buf()),
177        ];
178        expected.sort();
179        //compare
180        let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
181        result.sort();
182        assert_eq!(result, expected);
183    }
184
185    #[test]
186    fn ignore_left_only() {
187        init_logger();
188        //prepare left dir
189        let left_dir = tempfile::Builder::new().tempdir().unwrap();
190        let file_left_both = left_dir.path().join("both.txt");
191        fs::write(file_left_both.as_path(), b"Left and Right").unwrap();
192        let file_left_only = left_dir.path().join("left_only.txt");
193        fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
194
195        //prepare right dir
196        let right_dir = tempfile::Builder::new().tempdir().unwrap();
197        let file_right_both = right_dir.path().join("both.txt");
198        fs::write(file_right_both.as_path(), b"Right and Left").unwrap();
199        let file_right_only = right_dir.path().join("right_only.txt");
200        fs::write(file_right_only.as_path(), b"Righty right").unwrap();
201
202        //create options without any restrictions
203        let diff_options = Options {
204            ignore_equal: false,
205            ignore_left_only: true,
206            ignore_right_only: false,
207            filter: None,
208            recursive: false,
209        };
210
211        let mut expected: Vec<EitherOrBoth> = vec![
212            EitherOrBoth::Both(
213                file_left_both.as_path().to_path_buf(),
214                file_right_both.as_path().to_path_buf(),
215            ),
216            EitherOrBoth::Right(file_right_only.as_path().to_path_buf()),
217        ];
218        expected.sort();
219        //compare
220        let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
221        result.sort();
222        assert_eq!(result, expected);
223    }
224
225    #[test]
226    fn ignore_right_only() {
227        init_logger();
228        //prepare left dir
229        let left_dir = tempfile::Builder::new().tempdir().unwrap();
230        let file_left_both = left_dir.path().join("both.txt");
231        fs::write(file_left_both.as_path(), b"Left and Right").unwrap();
232        let file_left_only = left_dir.path().join("left_only.txt");
233        fs::write(file_left_only.as_path(), b"Lefty left").unwrap();
234
235        //prepare right dir
236        let right_dir = tempfile::Builder::new().tempdir().unwrap();
237        let file_right_both = right_dir.path().join("both.txt");
238        fs::write(file_right_both.as_path(), b"Right and Left").unwrap();
239        let file_right_only = right_dir.path().join("right_only.txt");
240        fs::write(file_right_only.as_path(), b"Righty right").unwrap();
241
242        //create options without any restrictions
243        let diff_options = Options {
244            ignore_equal: false,
245            ignore_left_only: false,
246            ignore_right_only: true,
247            filter: None,
248            recursive: false,
249        };
250
251        let mut expected: Vec<EitherOrBoth> = vec![
252            EitherOrBoth::Left(file_left_only.as_path().to_path_buf()),
253            EitherOrBoth::Both(
254                file_left_both.as_path().to_path_buf(),
255                file_right_both.as_path().to_path_buf(),
256            ),
257        ];
258        expected.sort();
259        //compare
260        let mut result = compare_dirs(left_dir.path(), right_dir.path(), diff_options).unwrap();
261        result.sort();
262        assert_eq!(result, expected);
263    }
264}
265
266pub fn compare_dirs(
267    left_path: &Path,
268    right_path: &Path,
269    options: Options,
270) -> io::Result<Vec<EitherOrBoth>> {
271    debug!(
272        "starting to compare for {:?} vs {:?}",
273        left_path, right_path
274    );
275
276    if !left_path.exists() {
277        error!("The left path does not exists!");
278        panic!();
279    }
280
281    if !left_path.is_dir() {
282        error!("The left path is not a directory!");
283        panic!();
284    }
285
286    if !right_path.exists() {
287        error!("The right path does not exists!");
288        panic!();
289    }
290
291    if !right_path.is_dir() {
292        error!("The right path is not a directory!");
293        panic!();
294    }
295
296    let left_base = left_path.to_str().unwrap();
297    let right_base = right_path.to_str().unwrap();
298
299    compare_dirs_inner(left_path, right_path, left_base, right_base, &options)
300}