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 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 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 }
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}