##// END OF EJS Templates
merge-lists: make it possible to specify pattern to match...
Martin von Zweigbergk -
r49875:b999edb1 default
parent child Browse files
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, &regex);
198 resolve(&base_bytes, &local_bytes, &other_bytes, &regex);
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