Show More
@@ -1,280 +1,300 b'' | |||||
1 | use clap::Parser; |
|
1 | use clap::{ArgGroup, Parser}; | |
2 | use itertools::Itertools; |
|
2 | use itertools::Itertools; | |
3 | use regex::bytes::Regex; |
|
3 | use regex::bytes::Regex; | |
4 | use similar::ChangeTag; |
|
4 | use similar::ChangeTag; | |
5 | use std::cmp::{max, min, Ordering}; |
|
5 | use std::cmp::{max, min, Ordering}; | |
6 | use std::collections::HashSet; |
|
6 | use std::collections::HashSet; | |
7 | use std::ffi::OsString; |
|
7 | use std::ffi::OsString; | |
8 | use std::ops::Range; |
|
8 | use std::ops::Range; | |
9 | use std::path::PathBuf; |
|
9 | use std::path::PathBuf; | |
10 |
|
10 | |||
11 | fn find_unchanged_ranges( |
|
11 | fn find_unchanged_ranges( | |
12 | old_bytes: &[u8], |
|
12 | old_bytes: &[u8], | |
13 | new_bytes: &[u8], |
|
13 | new_bytes: &[u8], | |
14 | ) -> Vec<(Range<usize>, Range<usize>)> { |
|
14 | ) -> Vec<(Range<usize>, Range<usize>)> { | |
15 | let diff = similar::TextDiff::configure() |
|
15 | let diff = similar::TextDiff::configure() | |
16 | .algorithm(similar::Algorithm::Patience) |
|
16 | .algorithm(similar::Algorithm::Patience) | |
17 | .diff_lines(old_bytes, new_bytes); |
|
17 | .diff_lines(old_bytes, new_bytes); | |
18 | let mut new_unchanged_ranges = vec![]; |
|
18 | let mut new_unchanged_ranges = vec![]; | |
19 | let mut old_index = 0; |
|
19 | let mut old_index = 0; | |
20 | let mut new_index = 0; |
|
20 | let mut new_index = 0; | |
21 | for diff in diff.iter_all_changes() { |
|
21 | for diff in diff.iter_all_changes() { | |
22 | match diff.tag() { |
|
22 | match diff.tag() { | |
23 | ChangeTag::Equal => { |
|
23 | ChangeTag::Equal => { | |
24 | new_unchanged_ranges.push(( |
|
24 | new_unchanged_ranges.push(( | |
25 | old_index..old_index + diff.value().len(), |
|
25 | old_index..old_index + diff.value().len(), | |
26 | new_index..new_index + diff.value().len(), |
|
26 | new_index..new_index + diff.value().len(), | |
27 | )); |
|
27 | )); | |
28 | old_index += diff.value().len(); |
|
28 | old_index += diff.value().len(); | |
29 | new_index += diff.value().len(); |
|
29 | new_index += diff.value().len(); | |
30 | } |
|
30 | } | |
31 | ChangeTag::Delete => { |
|
31 | ChangeTag::Delete => { | |
32 | old_index += diff.value().len(); |
|
32 | old_index += diff.value().len(); | |
33 | } |
|
33 | } | |
34 | ChangeTag::Insert => { |
|
34 | ChangeTag::Insert => { | |
35 | new_index += diff.value().len(); |
|
35 | new_index += diff.value().len(); | |
36 | } |
|
36 | } | |
37 | } |
|
37 | } | |
38 | } |
|
38 | } | |
39 | new_unchanged_ranges |
|
39 | new_unchanged_ranges | |
40 | } |
|
40 | } | |
41 |
|
41 | |||
42 | /// Returns a list of all the lines in the input (including trailing newlines), |
|
42 | /// Returns a list of all the lines in the input (including trailing newlines), | |
43 | /// but only if they all match the regex and they are sorted. |
|
43 | /// but only if they all match the regex and they are sorted. | |
44 | fn get_lines<'input>( |
|
44 | fn get_lines<'input>( | |
45 | input: &'input [u8], |
|
45 | input: &'input [u8], | |
46 | regex: &Regex, |
|
46 | regex: &Regex, | |
47 | ) -> Option<Vec<&'input [u8]>> { |
|
47 | ) -> Option<Vec<&'input [u8]>> { | |
48 | let lines = input.split_inclusive(|x| *x == b'\n').collect_vec(); |
|
48 | let lines = input.split_inclusive(|x| *x == b'\n').collect_vec(); | |
49 | let mut previous_line = "".as_bytes(); |
|
49 | let mut previous_line = "".as_bytes(); | |
50 | for line in &lines { |
|
50 | for line in &lines { | |
51 | if *line < previous_line { |
|
51 | if *line < previous_line { | |
52 | return None; |
|
52 | return None; | |
53 | } |
|
53 | } | |
54 | if !regex.is_match(line) { |
|
54 | if !regex.is_match(line) { | |
55 | return None; |
|
55 | return None; | |
56 | } |
|
56 | } | |
57 | previous_line = line; |
|
57 | previous_line = line; | |
58 | } |
|
58 | } | |
59 | Some(lines) |
|
59 | Some(lines) | |
60 | } |
|
60 | } | |
61 |
|
61 | |||
62 | fn resolve_conflict( |
|
62 | fn resolve_conflict( | |
63 | base_slice: &[u8], |
|
63 | base_slice: &[u8], | |
64 | local_slice: &[u8], |
|
64 | local_slice: &[u8], | |
65 | other_slice: &[u8], |
|
65 | other_slice: &[u8], | |
66 | regex: &Regex, |
|
66 | regex: &Regex, | |
67 | ) -> Option<Vec<u8>> { |
|
67 | ) -> Option<Vec<u8>> { | |
68 | let base_lines = get_lines(base_slice, regex)?; |
|
68 | let base_lines = get_lines(base_slice, regex)?; | |
69 | let local_lines = get_lines(local_slice, regex)?; |
|
69 | let local_lines = get_lines(local_slice, regex)?; | |
70 | let other_lines = get_lines(other_slice, regex)?; |
|
70 | let other_lines = get_lines(other_slice, regex)?; | |
71 | let base_lines_set: HashSet<_> = base_lines.iter().copied().collect(); |
|
71 | let base_lines_set: HashSet<_> = base_lines.iter().copied().collect(); | |
72 | let local_lines_set: HashSet<_> = local_lines.iter().copied().collect(); |
|
72 | let local_lines_set: HashSet<_> = local_lines.iter().copied().collect(); | |
73 | let other_lines_set: HashSet<_> = other_lines.iter().copied().collect(); |
|
73 | let other_lines_set: HashSet<_> = other_lines.iter().copied().collect(); | |
74 | let mut result = local_lines_set; |
|
74 | let mut result = local_lines_set; | |
75 | for to_add in other_lines_set.difference(&base_lines_set) { |
|
75 | for to_add in other_lines_set.difference(&base_lines_set) { | |
76 | result.insert(to_add); |
|
76 | result.insert(to_add); | |
77 | } |
|
77 | } | |
78 | for to_remove in base_lines_set.difference(&other_lines_set) { |
|
78 | for to_remove in base_lines_set.difference(&other_lines_set) { | |
79 | result.remove(to_remove); |
|
79 | result.remove(to_remove); | |
80 | } |
|
80 | } | |
81 | Some(result.into_iter().sorted().collect_vec().concat()) |
|
81 | Some(result.into_iter().sorted().collect_vec().concat()) | |
82 | } |
|
82 | } | |
83 |
|
83 | |||
84 | fn resolve( |
|
84 | fn resolve( | |
85 | base_bytes: &[u8], |
|
85 | base_bytes: &[u8], | |
86 | local_bytes: &[u8], |
|
86 | local_bytes: &[u8], | |
87 | other_bytes: &[u8], |
|
87 | other_bytes: &[u8], | |
88 | regex: &Regex, |
|
88 | regex: &Regex, | |
89 | ) -> (Vec<u8>, Vec<u8>, Vec<u8>) { |
|
89 | ) -> (Vec<u8>, Vec<u8>, Vec<u8>) { | |
90 | // Find unchanged ranges between the base and the two sides. We do that by |
|
90 | // Find unchanged ranges between the base and the two sides. We do that by | |
91 | // initially considering the whole base unchanged. Then we compare each |
|
91 | // initially considering the whole base unchanged. Then we compare each | |
92 | // side with the base and intersect the unchanged ranges we find with |
|
92 | // side with the base and intersect the unchanged ranges we find with | |
93 | // what we had before. |
|
93 | // what we had before. | |
94 | let unchanged_ranges = vec![UnchangedRange { |
|
94 | let unchanged_ranges = vec![UnchangedRange { | |
95 | base_range: 0..base_bytes.len(), |
|
95 | base_range: 0..base_bytes.len(), | |
96 | offsets: vec![], |
|
96 | offsets: vec![], | |
97 | }]; |
|
97 | }]; | |
98 | let unchanged_ranges = intersect_regions( |
|
98 | let unchanged_ranges = intersect_regions( | |
99 | unchanged_ranges, |
|
99 | unchanged_ranges, | |
100 | &find_unchanged_ranges(base_bytes, local_bytes), |
|
100 | &find_unchanged_ranges(base_bytes, local_bytes), | |
101 | ); |
|
101 | ); | |
102 | let mut unchanged_ranges = intersect_regions( |
|
102 | let mut unchanged_ranges = intersect_regions( | |
103 | unchanged_ranges, |
|
103 | unchanged_ranges, | |
104 | &find_unchanged_ranges(base_bytes, other_bytes), |
|
104 | &find_unchanged_ranges(base_bytes, other_bytes), | |
105 | ); |
|
105 | ); | |
106 | // Add an empty UnchangedRange at the end to make it easier to find change |
|
106 | // Add an empty UnchangedRange at the end to make it easier to find change | |
107 | // ranges. That way there's a changed range before each UnchangedRange. |
|
107 | // ranges. That way there's a changed range before each UnchangedRange. | |
108 | unchanged_ranges.push(UnchangedRange { |
|
108 | unchanged_ranges.push(UnchangedRange { | |
109 | base_range: base_bytes.len()..base_bytes.len(), |
|
109 | base_range: base_bytes.len()..base_bytes.len(), | |
110 | offsets: vec![ |
|
110 | offsets: vec![ | |
111 | local_bytes.len().wrapping_sub(base_bytes.len()) as isize, |
|
111 | local_bytes.len().wrapping_sub(base_bytes.len()) as isize, | |
112 | other_bytes.len().wrapping_sub(base_bytes.len()) as isize, |
|
112 | other_bytes.len().wrapping_sub(base_bytes.len()) as isize, | |
113 | ], |
|
113 | ], | |
114 | }); |
|
114 | }); | |
115 |
|
115 | |||
116 | let mut new_base_bytes: Vec<u8> = vec![]; |
|
116 | let mut new_base_bytes: Vec<u8> = vec![]; | |
117 | let mut new_local_bytes: Vec<u8> = vec![]; |
|
117 | let mut new_local_bytes: Vec<u8> = vec![]; | |
118 | let mut new_other_bytes: Vec<u8> = vec![]; |
|
118 | let mut new_other_bytes: Vec<u8> = vec![]; | |
119 | let mut previous = UnchangedRange { |
|
119 | let mut previous = UnchangedRange { | |
120 | base_range: 0..0, |
|
120 | base_range: 0..0, | |
121 | offsets: vec![0, 0], |
|
121 | offsets: vec![0, 0], | |
122 | }; |
|
122 | }; | |
123 | for current in unchanged_ranges { |
|
123 | for current in unchanged_ranges { | |
124 | let base_slice = |
|
124 | let base_slice = | |
125 | &base_bytes[previous.base_range.end..current.base_range.start]; |
|
125 | &base_bytes[previous.base_range.end..current.base_range.start]; | |
126 | let local_slice = &local_bytes[previous.end(0)..current.start(0)]; |
|
126 | let local_slice = &local_bytes[previous.end(0)..current.start(0)]; | |
127 | let other_slice = &other_bytes[previous.end(1)..current.start(1)]; |
|
127 | let other_slice = &other_bytes[previous.end(1)..current.start(1)]; | |
128 | if let Some(resolution) = |
|
128 | if let Some(resolution) = | |
129 | resolve_conflict(base_slice, local_slice, other_slice, regex) |
|
129 | resolve_conflict(base_slice, local_slice, other_slice, regex) | |
130 | { |
|
130 | { | |
131 | new_base_bytes.extend(&resolution); |
|
131 | new_base_bytes.extend(&resolution); | |
132 | new_local_bytes.extend(&resolution); |
|
132 | new_local_bytes.extend(&resolution); | |
133 | new_other_bytes.extend(&resolution); |
|
133 | new_other_bytes.extend(&resolution); | |
134 | } else { |
|
134 | } else { | |
135 | new_base_bytes.extend(base_slice); |
|
135 | new_base_bytes.extend(base_slice); | |
136 | new_local_bytes.extend(local_slice); |
|
136 | new_local_bytes.extend(local_slice); | |
137 | new_other_bytes.extend(other_slice); |
|
137 | new_other_bytes.extend(other_slice); | |
138 | } |
|
138 | } | |
139 | new_base_bytes.extend(&base_bytes[current.base_range.clone()]); |
|
139 | new_base_bytes.extend(&base_bytes[current.base_range.clone()]); | |
140 | new_local_bytes.extend(&local_bytes[current.start(0)..current.end(0)]); |
|
140 | new_local_bytes.extend(&local_bytes[current.start(0)..current.end(0)]); | |
141 | new_other_bytes.extend(&other_bytes[current.start(1)..current.end(1)]); |
|
141 | new_other_bytes.extend(&other_bytes[current.start(1)..current.end(1)]); | |
142 | previous = current; |
|
142 | previous = current; | |
143 | } |
|
143 | } | |
144 |
|
144 | |||
145 | (new_base_bytes, new_local_bytes, new_other_bytes) |
|
145 | (new_base_bytes, new_local_bytes, new_other_bytes) | |
146 | } |
|
146 | } | |
147 |
|
147 | |||
148 | /// A tool that performs a 3-way merge, resolving conflicts in sorted lists and |
|
148 | /// A tool that performs a 3-way merge, resolving conflicts in sorted lists and | |
149 | /// leaving other conflicts unchanged. This is useful with Mercurial's support |
|
149 | /// leaving other conflicts unchanged. This is useful with Mercurial's support | |
150 | /// for partial merge tools (configured in `[partial-merge-tools]`). |
|
150 | /// for partial merge tools (configured in `[partial-merge-tools]`). | |
151 | #[derive(Parser, Debug)] |
|
151 | #[derive(Parser, Debug)] | |
152 | #[clap(version, about, long_about = None)] |
|
152 | #[clap(version, about, long_about = None)] | |
|
153 | #[clap(group(ArgGroup::new("match").required(true).args(&["pattern", "python-imports"])))] | |||
153 | struct Args { |
|
154 | struct Args { | |
154 | /// Path to the file's content in the "local" side |
|
155 | /// Path to the file's content in the "local" side | |
155 | local: OsString, |
|
156 | local: OsString, | |
156 |
|
157 | |||
157 | /// Path to the file's content in the base |
|
158 | /// Path to the file's content in the base | |
158 | base: OsString, |
|
159 | base: OsString, | |
159 |
|
160 | |||
160 | /// Path to the file's content in the "other" side |
|
161 | /// Path to the file's content in the "other" side | |
161 | other: OsString, |
|
162 | other: OsString, | |
|
163 | ||||
|
164 | /// Regular expression to use | |||
|
165 | #[clap(long, short)] | |||
|
166 | pattern: Option<String>, | |||
|
167 | ||||
|
168 | /// Use built-in regular expression for Python imports | |||
|
169 | #[clap(long)] | |||
|
170 | python_imports: bool, | |||
|
171 | } | |||
|
172 | ||||
|
173 | fn get_regex(args: &Args) -> Regex { | |||
|
174 | let pattern = if args.python_imports { | |||
|
175 | r"import \w+(\.\w+)*( +#.*)?\n|from (\w+(\.\w+)* import \w+( as \w+)?(, \w+( as \w+)?)*( +#.*)?)" | |||
|
176 | } else if let Some(pattern) = &args.pattern { | |||
|
177 | pattern | |||
|
178 | } else { | |||
|
179 | ".*" | |||
|
180 | }; | |||
|
181 | let pattern = format!(r"{}\r?\n?", pattern); | |||
|
182 | regex::bytes::Regex::new(&pattern).unwrap() | |||
162 | } |
|
183 | } | |
163 |
|
184 | |||
164 | fn main() { |
|
185 | fn main() { | |
165 | let args: Args = Args::parse(); |
|
186 | let args: Args = Args::parse(); | |
166 |
|
187 | |||
167 | let base_path = PathBuf::from(&args.base); |
|
188 | let base_path = PathBuf::from(&args.base); | |
168 | let local_path = PathBuf::from(&args.local); |
|
189 | let local_path = PathBuf::from(&args.local); | |
169 | let other_path = PathBuf::from(&args.other); |
|
190 | let other_path = PathBuf::from(&args.other); | |
170 |
|
191 | |||
171 | let base_bytes = std::fs::read(&base_path).unwrap(); |
|
192 | let base_bytes = std::fs::read(&base_path).unwrap(); | |
172 | let local_bytes = std::fs::read(&local_path).unwrap(); |
|
193 | let local_bytes = std::fs::read(&local_path).unwrap(); | |
173 | let other_bytes = std::fs::read(&other_path).unwrap(); |
|
194 | let other_bytes = std::fs::read(&other_path).unwrap(); | |
174 |
|
195 | |||
175 | let regex = |
|
196 | let regex = get_regex(&args); | |
176 | regex::bytes::Regex::new(r"import \w+(\.\w+)*( +#.*)?\n|from (\w+(\.\w+)* import \w+( as \w+)?(, \w+( as \w+)?)*( +#.*)?)\r?\n?").unwrap(); |
|
|||
177 | let (new_base_bytes, new_local_bytes, new_other_bytes) = |
|
197 | let (new_base_bytes, new_local_bytes, new_other_bytes) = | |
178 | resolve(&base_bytes, &local_bytes, &other_bytes, ®ex); |
|
198 | resolve(&base_bytes, &local_bytes, &other_bytes, ®ex); | |
179 |
|
199 | |||
180 | // Write out the result if anything changed |
|
200 | // Write out the result if anything changed | |
181 | if new_base_bytes != base_bytes { |
|
201 | if new_base_bytes != base_bytes { | |
182 | std::fs::write(&base_path, new_base_bytes).unwrap(); |
|
202 | std::fs::write(&base_path, new_base_bytes).unwrap(); | |
183 | } |
|
203 | } | |
184 | if new_local_bytes != local_bytes { |
|
204 | if new_local_bytes != local_bytes { | |
185 | std::fs::write(&local_path, new_local_bytes).unwrap(); |
|
205 | std::fs::write(&local_path, new_local_bytes).unwrap(); | |
186 | } |
|
206 | } | |
187 | if new_other_bytes != other_bytes { |
|
207 | if new_other_bytes != other_bytes { | |
188 | std::fs::write(&other_path, new_other_bytes).unwrap(); |
|
208 | std::fs::write(&other_path, new_other_bytes).unwrap(); | |
189 | } |
|
209 | } | |
190 | } |
|
210 | } | |
191 |
|
211 | |||
192 | fn checked_add(base: usize, offset: isize) -> usize { |
|
212 | fn checked_add(base: usize, offset: isize) -> usize { | |
193 | if offset < 0 { |
|
213 | if offset < 0 { | |
194 | base.checked_sub(offset.checked_abs().unwrap() as usize) |
|
214 | base.checked_sub(offset.checked_abs().unwrap() as usize) | |
195 | .unwrap() |
|
215 | .unwrap() | |
196 | } else { |
|
216 | } else { | |
197 | base.checked_add(offset as usize).unwrap() |
|
217 | base.checked_add(offset as usize).unwrap() | |
198 | } |
|
218 | } | |
199 | } |
|
219 | } | |
200 |
|
220 | |||
201 | // The remainder of the file is copied from |
|
221 | // The remainder of the file is copied from | |
202 | // https://github.com/martinvonz/jj/blob/main/lib/src/diff.rs |
|
222 | // https://github.com/martinvonz/jj/blob/main/lib/src/diff.rs | |
203 |
|
223 | |||
204 | #[derive(Clone, PartialEq, Eq, Debug)] |
|
224 | #[derive(Clone, PartialEq, Eq, Debug)] | |
205 | struct UnchangedRange { |
|
225 | struct UnchangedRange { | |
206 | base_range: Range<usize>, |
|
226 | base_range: Range<usize>, | |
207 | offsets: Vec<isize>, |
|
227 | offsets: Vec<isize>, | |
208 | } |
|
228 | } | |
209 |
|
229 | |||
210 | impl UnchangedRange { |
|
230 | impl UnchangedRange { | |
211 | fn start(&self, side: usize) -> usize { |
|
231 | fn start(&self, side: usize) -> usize { | |
212 | checked_add(self.base_range.start, self.offsets[side]) |
|
232 | checked_add(self.base_range.start, self.offsets[side]) | |
213 | } |
|
233 | } | |
214 |
|
234 | |||
215 | fn end(&self, side: usize) -> usize { |
|
235 | fn end(&self, side: usize) -> usize { | |
216 | checked_add(self.base_range.end, self.offsets[side]) |
|
236 | checked_add(self.base_range.end, self.offsets[side]) | |
217 | } |
|
237 | } | |
218 | } |
|
238 | } | |
219 |
|
239 | |||
220 | impl PartialOrd for UnchangedRange { |
|
240 | impl PartialOrd for UnchangedRange { | |
221 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { |
|
241 | fn partial_cmp(&self, other: &Self) -> Option<Ordering> { | |
222 | Some(self.cmp(other)) |
|
242 | Some(self.cmp(other)) | |
223 | } |
|
243 | } | |
224 | } |
|
244 | } | |
225 |
|
245 | |||
226 | impl Ord for UnchangedRange { |
|
246 | impl Ord for UnchangedRange { | |
227 | fn cmp(&self, other: &Self) -> Ordering { |
|
247 | fn cmp(&self, other: &Self) -> Ordering { | |
228 | self.base_range |
|
248 | self.base_range | |
229 | .start |
|
249 | .start | |
230 | .cmp(&other.base_range.start) |
|
250 | .cmp(&other.base_range.start) | |
231 | .then_with(|| self.base_range.end.cmp(&other.base_range.end)) |
|
251 | .then_with(|| self.base_range.end.cmp(&other.base_range.end)) | |
232 | } |
|
252 | } | |
233 | } |
|
253 | } | |
234 |
|
254 | |||
235 | /// Takes the current regions and intersects it with the new unchanged ranges |
|
255 | /// Takes the current regions and intersects it with the new unchanged ranges | |
236 | /// from a 2-way diff. The result is a map of unchanged regions with one more |
|
256 | /// from a 2-way diff. The result is a map of unchanged regions with one more | |
237 | /// offset in the map's values. |
|
257 | /// offset in the map's values. | |
238 | fn intersect_regions( |
|
258 | fn intersect_regions( | |
239 | current_ranges: Vec<UnchangedRange>, |
|
259 | current_ranges: Vec<UnchangedRange>, | |
240 | new_unchanged_ranges: &[(Range<usize>, Range<usize>)], |
|
260 | new_unchanged_ranges: &[(Range<usize>, Range<usize>)], | |
241 | ) -> Vec<UnchangedRange> { |
|
261 | ) -> Vec<UnchangedRange> { | |
242 | let mut result = vec![]; |
|
262 | let mut result = vec![]; | |
243 | let mut current_ranges_iter = current_ranges.into_iter().peekable(); |
|
263 | let mut current_ranges_iter = current_ranges.into_iter().peekable(); | |
244 | for (new_base_range, other_range) in new_unchanged_ranges.iter() { |
|
264 | for (new_base_range, other_range) in new_unchanged_ranges.iter() { | |
245 | assert_eq!(new_base_range.len(), other_range.len()); |
|
265 | assert_eq!(new_base_range.len(), other_range.len()); | |
246 | while let Some(UnchangedRange { |
|
266 | while let Some(UnchangedRange { | |
247 | base_range, |
|
267 | base_range, | |
248 | offsets, |
|
268 | offsets, | |
249 | }) = current_ranges_iter.peek() |
|
269 | }) = current_ranges_iter.peek() | |
250 | { |
|
270 | { | |
251 | // No need to look further if we're past the new range. |
|
271 | // No need to look further if we're past the new range. | |
252 | if base_range.start >= new_base_range.end { |
|
272 | if base_range.start >= new_base_range.end { | |
253 | break; |
|
273 | break; | |
254 | } |
|
274 | } | |
255 | // Discard any current unchanged regions that don't match between |
|
275 | // Discard any current unchanged regions that don't match between | |
256 | // the base and the new input. |
|
276 | // the base and the new input. | |
257 | if base_range.end <= new_base_range.start { |
|
277 | if base_range.end <= new_base_range.start { | |
258 | current_ranges_iter.next(); |
|
278 | current_ranges_iter.next(); | |
259 | continue; |
|
279 | continue; | |
260 | } |
|
280 | } | |
261 | let new_start = max(base_range.start, new_base_range.start); |
|
281 | let new_start = max(base_range.start, new_base_range.start); | |
262 | let new_end = min(base_range.end, new_base_range.end); |
|
282 | let new_end = min(base_range.end, new_base_range.end); | |
263 | let mut new_offsets = offsets.clone(); |
|
283 | let mut new_offsets = offsets.clone(); | |
264 | new_offsets |
|
284 | new_offsets | |
265 | .push(other_range.start.wrapping_sub(new_base_range.start) |
|
285 | .push(other_range.start.wrapping_sub(new_base_range.start) | |
266 | as isize); |
|
286 | as isize); | |
267 | result.push(UnchangedRange { |
|
287 | result.push(UnchangedRange { | |
268 | base_range: new_start..new_end, |
|
288 | base_range: new_start..new_end, | |
269 | offsets: new_offsets, |
|
289 | offsets: new_offsets, | |
270 | }); |
|
290 | }); | |
271 | if base_range.end >= new_base_range.end { |
|
291 | if base_range.end >= new_base_range.end { | |
272 | // Break without consuming the item; there may be other new |
|
292 | // Break without consuming the item; there may be other new | |
273 | // ranges that overlap with it. |
|
293 | // ranges that overlap with it. | |
274 | break; |
|
294 | break; | |
275 | } |
|
295 | } | |
276 | current_ranges_iter.next(); |
|
296 | current_ranges_iter.next(); | |
277 | } |
|
297 | } | |
278 | } |
|
298 | } | |
279 | result |
|
299 | result | |
280 | } |
|
300 | } |
@@ -1,156 +1,204 b'' | |||||
1 | use similar::DiffableStr; |
|
1 | use similar::DiffableStr; | |
|
2 | use std::ffi::OsStr; | |||
2 | use tempdir::TempDir; |
|
3 | use tempdir::TempDir; | |
3 |
|
4 | |||
4 | fn run_test(input: &str) -> String { |
|
5 | fn run_test(arg: &str, input: &str) -> String { | |
5 | let mut cmd = assert_cmd::Command::cargo_bin("merge-lists").unwrap(); |
|
6 | let mut cmd = assert_cmd::Command::cargo_bin("merge-lists").unwrap(); | |
6 | let temp_dir = TempDir::new("test").unwrap(); |
|
7 | let temp_dir = TempDir::new("test").unwrap(); | |
7 | let base_path = temp_dir.path().join("base"); |
|
8 | let base_path = temp_dir.path().join("base"); | |
8 | let local_path = temp_dir.path().join("local"); |
|
9 | let local_path = temp_dir.path().join("local"); | |
9 | let other_path = temp_dir.path().join("other"); |
|
10 | let other_path = temp_dir.path().join("other"); | |
10 |
|
11 | |||
11 | let rest = input.strip_prefix("\nbase:\n").unwrap(); |
|
12 | let rest = input.strip_prefix("\nbase:\n").unwrap(); | |
12 | let mut split = rest.split("\nlocal:\n"); |
|
13 | let mut split = rest.split("\nlocal:\n"); | |
13 | std::fs::write(&base_path, split.next().unwrap()).unwrap(); |
|
14 | std::fs::write(&base_path, split.next().unwrap()).unwrap(); | |
14 | let rest = split.next().unwrap(); |
|
15 | let rest = split.next().unwrap(); | |
15 | let mut split = rest.split("\nother:\n"); |
|
16 | let mut split = rest.split("\nother:\n"); | |
16 | std::fs::write(&local_path, split.next().unwrap()).unwrap(); |
|
17 | std::fs::write(&local_path, split.next().unwrap()).unwrap(); | |
17 | std::fs::write(&other_path, split.next().unwrap()).unwrap(); |
|
18 | std::fs::write(&other_path, split.next().unwrap()).unwrap(); | |
18 | cmd.args(&[ |
|
19 | cmd.args(&[ | |
|
20 | OsStr::new(arg), | |||
19 | local_path.as_os_str(), |
|
21 | local_path.as_os_str(), | |
20 | base_path.as_os_str(), |
|
22 | base_path.as_os_str(), | |
21 | other_path.as_os_str(), |
|
23 | other_path.as_os_str(), | |
22 | ]) |
|
24 | ]) | |
23 | .assert() |
|
25 | .assert() | |
24 | .success(); |
|
26 | .success(); | |
25 |
|
27 | |||
26 | let new_base_bytes = std::fs::read(&base_path).unwrap(); |
|
28 | let new_base_bytes = std::fs::read(&base_path).unwrap(); | |
27 | let new_local_bytes = std::fs::read(&local_path).unwrap(); |
|
29 | let new_local_bytes = std::fs::read(&local_path).unwrap(); | |
28 | let new_other_bytes = std::fs::read(&other_path).unwrap(); |
|
30 | let new_other_bytes = std::fs::read(&other_path).unwrap(); | |
29 | // No newline before "base:" because of https://github.com/mitsuhiko/insta/issues/117 |
|
31 | // No newline before "base:" because of https://github.com/mitsuhiko/insta/issues/117 | |
30 | format!( |
|
32 | format!( | |
31 | "base:\n{}\nlocal:\n{}\nother:\n{}", |
|
33 | "base:\n{}\nlocal:\n{}\nother:\n{}", | |
32 | new_base_bytes.as_str().unwrap(), |
|
34 | new_base_bytes.as_str().unwrap(), | |
33 | new_local_bytes.as_str().unwrap(), |
|
35 | new_local_bytes.as_str().unwrap(), | |
34 | new_other_bytes.as_str().unwrap() |
|
36 | new_other_bytes.as_str().unwrap() | |
35 | ) |
|
37 | ) | |
36 | } |
|
38 | } | |
37 |
|
39 | |||
38 | #[test] |
|
40 | #[test] | |
39 | fn test_merge_lists_basic() { |
|
41 | fn test_merge_lists_basic() { | |
40 | let output = run_test( |
|
42 | let output = run_test( | |
|
43 | "--python-imports", | |||
41 | r" |
|
44 | r" | |
42 | base: |
|
45 | base: | |
43 | import lib1 |
|
46 | import lib1 | |
44 | import lib2 |
|
47 | import lib2 | |
45 |
|
48 | |||
46 | local: |
|
49 | local: | |
47 | import lib2 |
|
50 | import lib2 | |
48 | import lib3 |
|
51 | import lib3 | |
49 |
|
52 | |||
50 | other: |
|
53 | other: | |
51 | import lib3 |
|
54 | import lib3 | |
52 | import lib4 |
|
55 | import lib4 | |
53 | ", |
|
56 | ", | |
54 | ); |
|
57 | ); | |
55 | insta::assert_snapshot!(output, @r###" |
|
58 | insta::assert_snapshot!(output, @r###" | |
56 | base: |
|
59 | base: | |
57 | import lib3 |
|
60 | import lib3 | |
58 | import lib4 |
|
61 | import lib4 | |
59 |
|
62 | |||
60 | local: |
|
63 | local: | |
61 | import lib3 |
|
64 | import lib3 | |
62 | import lib4 |
|
65 | import lib4 | |
63 |
|
66 | |||
64 | other: |
|
67 | other: | |
65 | import lib3 |
|
68 | import lib3 | |
66 | import lib4 |
|
69 | import lib4 | |
67 | "###); |
|
70 | "###); | |
68 | } |
|
71 | } | |
69 |
|
72 | |||
70 | #[test] |
|
73 | #[test] | |
71 | fn test_merge_lists_from() { |
|
74 | fn test_merge_lists_from() { | |
72 | // Test some "from x import y" statements and some non-import conflicts |
|
75 | // Test some "from x import y" statements and some non-import conflicts | |
73 | // (unresolvable) |
|
76 | // (unresolvable) | |
74 | let output = run_test( |
|
77 | let output = run_test( | |
|
78 | "--python-imports", | |||
75 | r" |
|
79 | r" | |
76 | base: |
|
80 | base: | |
77 | from . import x |
|
81 | from . import x | |
78 |
|
82 | |||
79 | 1+1 |
|
83 | 1+1 | |
80 |
|
84 | |||
81 | local: |
|
85 | local: | |
82 | from . import x |
|
86 | from . import x | |
83 | from a import b |
|
87 | from a import b | |
84 |
|
88 | |||
85 | 2+2 |
|
89 | 2+2 | |
86 |
|
90 | |||
87 | other: |
|
91 | other: | |
88 | from a import c |
|
92 | from a import c | |
89 |
|
93 | |||
90 | 3+3 |
|
94 | 3+3 | |
91 | ", |
|
95 | ", | |
92 | ); |
|
96 | ); | |
93 | insta::assert_snapshot!(output, @r###" |
|
97 | insta::assert_snapshot!(output, @r###" | |
94 | base: |
|
98 | base: | |
95 | from a import b |
|
99 | from a import b | |
96 | from a import c |
|
100 | from a import c | |
97 |
|
101 | |||
98 | 1+1 |
|
102 | 1+1 | |
99 |
|
103 | |||
100 | local: |
|
104 | local: | |
101 | from a import b |
|
105 | from a import b | |
102 | from a import c |
|
106 | from a import c | |
103 |
|
107 | |||
104 | 2+2 |
|
108 | 2+2 | |
105 |
|
109 | |||
106 | other: |
|
110 | other: | |
107 | from a import b |
|
111 | from a import b | |
108 | from a import c |
|
112 | from a import c | |
109 |
|
113 | |||
110 | 3+3 |
|
114 | 3+3 | |
111 | "###); |
|
115 | "###); | |
112 | } |
|
116 | } | |
113 |
|
117 | |||
114 | #[test] |
|
118 | #[test] | |
115 | fn test_merge_lists_not_sorted() { |
|
119 | fn test_merge_lists_not_sorted() { | |
116 | // Test that nothing is done if the elements in the conflicting hunks are |
|
120 | // Test that nothing is done if the elements in the conflicting hunks are | |
117 | // not sorted |
|
121 | // not sorted | |
118 | let output = run_test( |
|
122 | let output = run_test( | |
|
123 | "--python-imports", | |||
119 | r" |
|
124 | r" | |
120 | base: |
|
125 | base: | |
121 | import x |
|
126 | import x | |
122 |
|
127 | |||
123 | 1+1 |
|
128 | 1+1 | |
124 |
|
129 | |||
125 | local: |
|
130 | local: | |
126 | import a |
|
131 | import a | |
127 | import x |
|
132 | import x | |
128 |
|
133 | |||
129 | 2+2 |
|
134 | 2+2 | |
130 |
|
135 | |||
131 | other: |
|
136 | other: | |
132 | import z |
|
137 | import z | |
133 | import y |
|
138 | import y | |
134 |
|
139 | |||
135 | 3+3 |
|
140 | 3+3 | |
136 | ", |
|
141 | ", | |
137 | ); |
|
142 | ); | |
138 | insta::assert_snapshot!(output, @r###" |
|
143 | insta::assert_snapshot!(output, @r###" | |
139 | base: |
|
144 | base: | |
140 | import x |
|
145 | import x | |
141 |
|
146 | |||
142 | 1+1 |
|
147 | 1+1 | |
143 |
|
148 | |||
144 | local: |
|
149 | local: | |
145 | import a |
|
150 | import a | |
146 | import x |
|
151 | import x | |
147 |
|
152 | |||
148 | 2+2 |
|
153 | 2+2 | |
149 |
|
154 | |||
150 | other: |
|
155 | other: | |
151 | import z |
|
156 | import z | |
152 | import y |
|
157 | import y | |
153 |
|
158 | |||
154 | 3+3 |
|
159 | 3+3 | |
155 | "###); |
|
160 | "###); | |
156 | } |
|
161 | } | |
|
162 | ||||
|
163 | #[test] | |||
|
164 | fn test_custom_regex() { | |||
|
165 | // Test merging of all lines (by matching anything) | |||
|
166 | let output = run_test( | |||
|
167 | "--pattern=.*", | |||
|
168 | r" | |||
|
169 | base: | |||
|
170 | aardvark | |||
|
171 | baboon | |||
|
172 | camel | |||
|
173 | ||||
|
174 | local: | |||
|
175 | aardvark | |||
|
176 | camel | |||
|
177 | eagle | |||
|
178 | ||||
|
179 | other: | |||
|
180 | aardvark | |||
|
181 | camel | |||
|
182 | deer | |||
|
183 | ", | |||
|
184 | ); | |||
|
185 | insta::assert_snapshot!(output, @r###" | |||
|
186 | base: | |||
|
187 | aardvark | |||
|
188 | camel | |||
|
189 | deer | |||
|
190 | eagle | |||
|
191 | ||||
|
192 | local: | |||
|
193 | aardvark | |||
|
194 | camel | |||
|
195 | deer | |||
|
196 | eagle | |||
|
197 | ||||
|
198 | other: | |||
|
199 | aardvark | |||
|
200 | camel | |||
|
201 | deer | |||
|
202 | eagle | |||
|
203 | "###); | |||
|
204 | } |
General Comments 0
You need to be logged in to leave comments.
Login now