##// END OF EJS Templates
contrib: add a partial-merge tool for sorted lists (such as Python imports)...
Martin von Zweigbergk -
r49874:681b25ea default
parent child Browse files
Show More
This diff has been collapsed as it changes many lines, (560 lines changed) Show them Hide them
@@ -0,0 +1,560 b''
1 # This file is automatically @generated by Cargo.
2 # It is not intended for manual editing.
3 version = 3
4
5 [[package]]
6 name = "aho-corasick"
7 version = "0.7.18"
8 source = "registry+https://github.com/rust-lang/crates.io-index"
9 checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f"
10 dependencies = [
11 "memchr",
12 ]
13
14 [[package]]
15 name = "assert_cmd"
16 version = "2.0.4"
17 source = "registry+https://github.com/rust-lang/crates.io-index"
18 checksum = "93ae1ddd39efd67689deb1979d80bad3bf7f2b09c6e6117c8d1f2443b5e2f83e"
19 dependencies = [
20 "bstr",
21 "doc-comment",
22 "predicates",
23 "predicates-core",
24 "predicates-tree",
25 "wait-timeout",
26 ]
27
28 [[package]]
29 name = "atty"
30 version = "0.2.14"
31 source = "registry+https://github.com/rust-lang/crates.io-index"
32 checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
33 dependencies = [
34 "hermit-abi",
35 "libc",
36 "winapi",
37 ]
38
39 [[package]]
40 name = "autocfg"
41 version = "1.1.0"
42 source = "registry+https://github.com/rust-lang/crates.io-index"
43 checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
44
45 [[package]]
46 name = "bitflags"
47 version = "1.3.2"
48 source = "registry+https://github.com/rust-lang/crates.io-index"
49 checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
50
51 [[package]]
52 name = "bstr"
53 version = "0.2.17"
54 source = "registry+https://github.com/rust-lang/crates.io-index"
55 checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223"
56 dependencies = [
57 "lazy_static",
58 "memchr",
59 "regex-automata",
60 ]
61
62 [[package]]
63 name = "clap"
64 version = "3.1.6"
65 source = "registry+https://github.com/rust-lang/crates.io-index"
66 checksum = "d8c93436c21e4698bacadf42917db28b23017027a4deccb35dbe47a7e7840123"
67 dependencies = [
68 "atty",
69 "bitflags",
70 "clap_derive",
71 "indexmap",
72 "lazy_static",
73 "os_str_bytes",
74 "strsim",
75 "termcolor",
76 "textwrap",
77 ]
78
79 [[package]]
80 name = "clap_derive"
81 version = "3.1.4"
82 source = "registry+https://github.com/rust-lang/crates.io-index"
83 checksum = "da95d038ede1a964ce99f49cbe27a7fb538d1da595e4b4f70b8c8f338d17bf16"
84 dependencies = [
85 "heck",
86 "proc-macro-error",
87 "proc-macro2",
88 "quote",
89 "syn",
90 ]
91
92 [[package]]
93 name = "console"
94 version = "0.15.0"
95 source = "registry+https://github.com/rust-lang/crates.io-index"
96 checksum = "a28b32d32ca44b70c3e4acd7db1babf555fa026e385fb95f18028f88848b3c31"
97 dependencies = [
98 "encode_unicode",
99 "libc",
100 "once_cell",
101 "terminal_size",
102 "winapi",
103 ]
104
105 [[package]]
106 name = "difflib"
107 version = "0.4.0"
108 source = "registry+https://github.com/rust-lang/crates.io-index"
109 checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8"
110
111 [[package]]
112 name = "doc-comment"
113 version = "0.3.3"
114 source = "registry+https://github.com/rust-lang/crates.io-index"
115 checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
116
117 [[package]]
118 name = "either"
119 version = "1.6.1"
120 source = "registry+https://github.com/rust-lang/crates.io-index"
121 checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
122
123 [[package]]
124 name = "encode_unicode"
125 version = "0.3.6"
126 source = "registry+https://github.com/rust-lang/crates.io-index"
127 checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
128
129 [[package]]
130 name = "fuchsia-cprng"
131 version = "0.1.1"
132 source = "registry+https://github.com/rust-lang/crates.io-index"
133 checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
134
135 [[package]]
136 name = "hashbrown"
137 version = "0.11.2"
138 source = "registry+https://github.com/rust-lang/crates.io-index"
139 checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e"
140
141 [[package]]
142 name = "heck"
143 version = "0.4.0"
144 source = "registry+https://github.com/rust-lang/crates.io-index"
145 checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
146
147 [[package]]
148 name = "hermit-abi"
149 version = "0.1.19"
150 source = "registry+https://github.com/rust-lang/crates.io-index"
151 checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
152 dependencies = [
153 "libc",
154 ]
155
156 [[package]]
157 name = "indexmap"
158 version = "1.8.0"
159 source = "registry+https://github.com/rust-lang/crates.io-index"
160 checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223"
161 dependencies = [
162 "autocfg",
163 "hashbrown",
164 ]
165
166 [[package]]
167 name = "insta"
168 version = "1.13.0"
169 source = "registry+https://github.com/rust-lang/crates.io-index"
170 checksum = "30a7e1911532a662f6b08b68f884080850f2fd9544963c3ab23a5af42bda1eac"
171 dependencies = [
172 "console",
173 "once_cell",
174 "serde",
175 "serde_json",
176 "serde_yaml",
177 "similar",
178 ]
179
180 [[package]]
181 name = "itertools"
182 version = "0.10.3"
183 source = "registry+https://github.com/rust-lang/crates.io-index"
184 checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3"
185 dependencies = [
186 "either",
187 ]
188
189 [[package]]
190 name = "itoa"
191 version = "1.0.1"
192 source = "registry+https://github.com/rust-lang/crates.io-index"
193 checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35"
194
195 [[package]]
196 name = "lazy_static"
197 version = "1.4.0"
198 source = "registry+https://github.com/rust-lang/crates.io-index"
199 checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
200
201 [[package]]
202 name = "libc"
203 version = "0.2.119"
204 source = "registry+https://github.com/rust-lang/crates.io-index"
205 checksum = "1bf2e165bb3457c8e098ea76f3e3bc9db55f87aa90d52d0e6be741470916aaa4"
206
207 [[package]]
208 name = "linked-hash-map"
209 version = "0.5.4"
210 source = "registry+https://github.com/rust-lang/crates.io-index"
211 checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
212
213 [[package]]
214 name = "memchr"
215 version = "2.4.1"
216 source = "registry+https://github.com/rust-lang/crates.io-index"
217 checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a"
218
219 [[package]]
220 name = "merge-lists"
221 version = "0.1.0"
222 dependencies = [
223 "assert_cmd",
224 "clap",
225 "insta",
226 "itertools",
227 "regex",
228 "similar",
229 "tempdir",
230 ]
231
232 [[package]]
233 name = "once_cell"
234 version = "1.10.0"
235 source = "registry+https://github.com/rust-lang/crates.io-index"
236 checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9"
237
238 [[package]]
239 name = "os_str_bytes"
240 version = "6.0.0"
241 source = "registry+https://github.com/rust-lang/crates.io-index"
242 checksum = "8e22443d1643a904602595ba1cd8f7d896afe56d26712531c5ff73a15b2fbf64"
243 dependencies = [
244 "memchr",
245 ]
246
247 [[package]]
248 name = "predicates"
249 version = "2.1.1"
250 source = "registry+https://github.com/rust-lang/crates.io-index"
251 checksum = "a5aab5be6e4732b473071984b3164dbbfb7a3674d30ea5ff44410b6bcd960c3c"
252 dependencies = [
253 "difflib",
254 "itertools",
255 "predicates-core",
256 ]
257
258 [[package]]
259 name = "predicates-core"
260 version = "1.0.3"
261 source = "registry+https://github.com/rust-lang/crates.io-index"
262 checksum = "da1c2388b1513e1b605fcec39a95e0a9e8ef088f71443ef37099fa9ae6673fcb"
263
264 [[package]]
265 name = "predicates-tree"
266 version = "1.0.5"
267 source = "registry+https://github.com/rust-lang/crates.io-index"
268 checksum = "4d86de6de25020a36c6d3643a86d9a6a9f552107c0559c60ea03551b5e16c032"
269 dependencies = [
270 "predicates-core",
271 "termtree",
272 ]
273
274 [[package]]
275 name = "proc-macro-error"
276 version = "1.0.4"
277 source = "registry+https://github.com/rust-lang/crates.io-index"
278 checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
279 dependencies = [
280 "proc-macro-error-attr",
281 "proc-macro2",
282 "quote",
283 "syn",
284 "version_check",
285 ]
286
287 [[package]]
288 name = "proc-macro-error-attr"
289 version = "1.0.4"
290 source = "registry+https://github.com/rust-lang/crates.io-index"
291 checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
292 dependencies = [
293 "proc-macro2",
294 "quote",
295 "version_check",
296 ]
297
298 [[package]]
299 name = "proc-macro2"
300 version = "1.0.36"
301 source = "registry+https://github.com/rust-lang/crates.io-index"
302 checksum = "c7342d5883fbccae1cc37a2353b09c87c9b0f3afd73f5fb9bba687a1f733b029"
303 dependencies = [
304 "unicode-xid",
305 ]
306
307 [[package]]
308 name = "quote"
309 version = "1.0.15"
310 source = "registry+https://github.com/rust-lang/crates.io-index"
311 checksum = "864d3e96a899863136fc6e99f3d7cae289dafe43bf2c5ac19b70df7210c0a145"
312 dependencies = [
313 "proc-macro2",
314 ]
315
316 [[package]]
317 name = "rand"
318 version = "0.4.6"
319 source = "registry+https://github.com/rust-lang/crates.io-index"
320 checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
321 dependencies = [
322 "fuchsia-cprng",
323 "libc",
324 "rand_core 0.3.1",
325 "rdrand",
326 "winapi",
327 ]
328
329 [[package]]
330 name = "rand_core"
331 version = "0.3.1"
332 source = "registry+https://github.com/rust-lang/crates.io-index"
333 checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
334 dependencies = [
335 "rand_core 0.4.2",
336 ]
337
338 [[package]]
339 name = "rand_core"
340 version = "0.4.2"
341 source = "registry+https://github.com/rust-lang/crates.io-index"
342 checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
343
344 [[package]]
345 name = "rdrand"
346 version = "0.4.0"
347 source = "registry+https://github.com/rust-lang/crates.io-index"
348 checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
349 dependencies = [
350 "rand_core 0.3.1",
351 ]
352
353 [[package]]
354 name = "regex"
355 version = "1.5.5"
356 source = "registry+https://github.com/rust-lang/crates.io-index"
357 checksum = "1a11647b6b25ff05a515cb92c365cec08801e83423a235b51e231e1808747286"
358 dependencies = [
359 "aho-corasick",
360 "memchr",
361 "regex-syntax",
362 ]
363
364 [[package]]
365 name = "regex-automata"
366 version = "0.1.10"
367 source = "registry+https://github.com/rust-lang/crates.io-index"
368 checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132"
369
370 [[package]]
371 name = "regex-syntax"
372 version = "0.6.25"
373 source = "registry+https://github.com/rust-lang/crates.io-index"
374 checksum = "f497285884f3fcff424ffc933e56d7cbca511def0c9831a7f9b5f6153e3cc89b"
375
376 [[package]]
377 name = "remove_dir_all"
378 version = "0.5.3"
379 source = "registry+https://github.com/rust-lang/crates.io-index"
380 checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
381 dependencies = [
382 "winapi",
383 ]
384
385 [[package]]
386 name = "ryu"
387 version = "1.0.9"
388 source = "registry+https://github.com/rust-lang/crates.io-index"
389 checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f"
390
391 [[package]]
392 name = "serde"
393 version = "1.0.136"
394 source = "registry+https://github.com/rust-lang/crates.io-index"
395 checksum = "ce31e24b01e1e524df96f1c2fdd054405f8d7376249a5110886fb4b658484789"
396 dependencies = [
397 "serde_derive",
398 ]
399
400 [[package]]
401 name = "serde_derive"
402 version = "1.0.136"
403 source = "registry+https://github.com/rust-lang/crates.io-index"
404 checksum = "08597e7152fcd306f41838ed3e37be9eaeed2b61c42e2117266a554fab4662f9"
405 dependencies = [
406 "proc-macro2",
407 "quote",
408 "syn",
409 ]
410
411 [[package]]
412 name = "serde_json"
413 version = "1.0.79"
414 source = "registry+https://github.com/rust-lang/crates.io-index"
415 checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95"
416 dependencies = [
417 "itoa",
418 "ryu",
419 "serde",
420 ]
421
422 [[package]]
423 name = "serde_yaml"
424 version = "0.8.23"
425 source = "registry+https://github.com/rust-lang/crates.io-index"
426 checksum = "a4a521f2940385c165a24ee286aa8599633d162077a54bdcae2a6fd5a7bfa7a0"
427 dependencies = [
428 "indexmap",
429 "ryu",
430 "serde",
431 "yaml-rust",
432 ]
433
434 [[package]]
435 name = "similar"
436 version = "2.1.0"
437 source = "registry+https://github.com/rust-lang/crates.io-index"
438 checksum = "2e24979f63a11545f5f2c60141afe249d4f19f84581ea2138065e400941d83d3"
439 dependencies = [
440 "bstr",
441 ]
442
443 [[package]]
444 name = "strsim"
445 version = "0.10.0"
446 source = "registry+https://github.com/rust-lang/crates.io-index"
447 checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
448
449 [[package]]
450 name = "syn"
451 version = "1.0.87"
452 source = "registry+https://github.com/rust-lang/crates.io-index"
453 checksum = "1e59d925cf59d8151f25a3bedf97c9c157597c9df7324d32d68991cc399ed08b"
454 dependencies = [
455 "proc-macro2",
456 "quote",
457 "unicode-xid",
458 ]
459
460 [[package]]
461 name = "tempdir"
462 version = "0.3.7"
463 source = "registry+https://github.com/rust-lang/crates.io-index"
464 checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8"
465 dependencies = [
466 "rand",
467 "remove_dir_all",
468 ]
469
470 [[package]]
471 name = "termcolor"
472 version = "1.1.3"
473 source = "registry+https://github.com/rust-lang/crates.io-index"
474 checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755"
475 dependencies = [
476 "winapi-util",
477 ]
478
479 [[package]]
480 name = "terminal_size"
481 version = "0.1.17"
482 source = "registry+https://github.com/rust-lang/crates.io-index"
483 checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df"
484 dependencies = [
485 "libc",
486 "winapi",
487 ]
488
489 [[package]]
490 name = "termtree"
491 version = "0.2.4"
492 source = "registry+https://github.com/rust-lang/crates.io-index"
493 checksum = "507e9898683b6c43a9aa55b64259b721b52ba226e0f3779137e50ad114a4c90b"
494
495 [[package]]
496 name = "textwrap"
497 version = "0.15.0"
498 source = "registry+https://github.com/rust-lang/crates.io-index"
499 checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb"
500
501 [[package]]
502 name = "unicode-xid"
503 version = "0.2.2"
504 source = "registry+https://github.com/rust-lang/crates.io-index"
505 checksum = "8ccb82d61f80a663efe1f787a51b16b5a51e3314d6ac365b08639f52387b33f3"
506
507 [[package]]
508 name = "version_check"
509 version = "0.9.4"
510 source = "registry+https://github.com/rust-lang/crates.io-index"
511 checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
512
513 [[package]]
514 name = "wait-timeout"
515 version = "0.2.0"
516 source = "registry+https://github.com/rust-lang/crates.io-index"
517 checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6"
518 dependencies = [
519 "libc",
520 ]
521
522 [[package]]
523 name = "winapi"
524 version = "0.3.9"
525 source = "registry+https://github.com/rust-lang/crates.io-index"
526 checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
527 dependencies = [
528 "winapi-i686-pc-windows-gnu",
529 "winapi-x86_64-pc-windows-gnu",
530 ]
531
532 [[package]]
533 name = "winapi-i686-pc-windows-gnu"
534 version = "0.4.0"
535 source = "registry+https://github.com/rust-lang/crates.io-index"
536 checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
537
538 [[package]]
539 name = "winapi-util"
540 version = "0.1.5"
541 source = "registry+https://github.com/rust-lang/crates.io-index"
542 checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
543 dependencies = [
544 "winapi",
545 ]
546
547 [[package]]
548 name = "winapi-x86_64-pc-windows-gnu"
549 version = "0.4.0"
550 source = "registry+https://github.com/rust-lang/crates.io-index"
551 checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
552
553 [[package]]
554 name = "yaml-rust"
555 version = "0.4.5"
556 source = "registry+https://github.com/rust-lang/crates.io-index"
557 checksum = "56c1936c4cc7a1c9ab21a1ebb602eb942ba868cbd44a99cb7cdc5892335e1c85"
558 dependencies = [
559 "linked-hash-map",
560 ]
@@ -0,0 +1,21 b''
1 # A tool that performs a 3-way merge, resolving conflicts in sorted lists and
2 # leaving other conflicts unchanged. This is useful with Mercurial's support
3 # for partial merge tools (configured in `[partial-merge-tools]`).
4
5 [package]
6 name = "merge-lists"
7 version = "0.1.0"
8 edition = "2021"
9 # We need https://github.com/rust-lang/rust/pull/89825
10 rust-version = "1.59"
11
12 [dependencies]
13 clap = { version = "3.1.6", features = ["derive"] }
14 itertools = "0.10.3"
15 regex = "1.5.5"
16 similar = { version="2.1.0", features = ["bytes"] }
17
18 [dev-dependencies]
19 assert_cmd = "2.0.4"
20 insta = "1.13.0"
21 tempdir = "0.3.7"
@@ -0,0 +1,280 b''
1 use clap::Parser;
2 use itertools::Itertools;
3 use regex::bytes::Regex;
4 use similar::ChangeTag;
5 use std::cmp::{max, min, Ordering};
6 use std::collections::HashSet;
7 use std::ffi::OsString;
8 use std::ops::Range;
9 use std::path::PathBuf;
10
11 fn find_unchanged_ranges(
12 old_bytes: &[u8],
13 new_bytes: &[u8],
14 ) -> Vec<(Range<usize>, Range<usize>)> {
15 let diff = similar::TextDiff::configure()
16 .algorithm(similar::Algorithm::Patience)
17 .diff_lines(old_bytes, new_bytes);
18 let mut new_unchanged_ranges = vec![];
19 let mut old_index = 0;
20 let mut new_index = 0;
21 for diff in diff.iter_all_changes() {
22 match diff.tag() {
23 ChangeTag::Equal => {
24 new_unchanged_ranges.push((
25 old_index..old_index + diff.value().len(),
26 new_index..new_index + diff.value().len(),
27 ));
28 old_index += diff.value().len();
29 new_index += diff.value().len();
30 }
31 ChangeTag::Delete => {
32 old_index += diff.value().len();
33 }
34 ChangeTag::Insert => {
35 new_index += diff.value().len();
36 }
37 }
38 }
39 new_unchanged_ranges
40 }
41
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.
44 fn get_lines<'input>(
45 input: &'input [u8],
46 regex: &Regex,
47 ) -> Option<Vec<&'input [u8]>> {
48 let lines = input.split_inclusive(|x| *x == b'\n').collect_vec();
49 let mut previous_line = "".as_bytes();
50 for line in &lines {
51 if *line < previous_line {
52 return None;
53 }
54 if !regex.is_match(line) {
55 return None;
56 }
57 previous_line = line;
58 }
59 Some(lines)
60 }
61
62 fn resolve_conflict(
63 base_slice: &[u8],
64 local_slice: &[u8],
65 other_slice: &[u8],
66 regex: &Regex,
67 ) -> Option<Vec<u8>> {
68 let base_lines = get_lines(base_slice, regex)?;
69 let local_lines = get_lines(local_slice, regex)?;
70 let other_lines = get_lines(other_slice, regex)?;
71 let base_lines_set: HashSet<_> = base_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();
74 let mut result = local_lines_set;
75 for to_add in other_lines_set.difference(&base_lines_set) {
76 result.insert(to_add);
77 }
78 for to_remove in base_lines_set.difference(&other_lines_set) {
79 result.remove(to_remove);
80 }
81 Some(result.into_iter().sorted().collect_vec().concat())
82 }
83
84 fn resolve(
85 base_bytes: &[u8],
86 local_bytes: &[u8],
87 other_bytes: &[u8],
88 regex: &Regex,
89 ) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
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
92 // side with the base and intersect the unchanged ranges we find with
93 // what we had before.
94 let unchanged_ranges = vec![UnchangedRange {
95 base_range: 0..base_bytes.len(),
96 offsets: vec![],
97 }];
98 let unchanged_ranges = intersect_regions(
99 unchanged_ranges,
100 &find_unchanged_ranges(base_bytes, local_bytes),
101 );
102 let mut unchanged_ranges = intersect_regions(
103 unchanged_ranges,
104 &find_unchanged_ranges(base_bytes, other_bytes),
105 );
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.
108 unchanged_ranges.push(UnchangedRange {
109 base_range: base_bytes.len()..base_bytes.len(),
110 offsets: vec![
111 local_bytes.len().wrapping_sub(base_bytes.len()) as isize,
112 other_bytes.len().wrapping_sub(base_bytes.len()) as isize,
113 ],
114 });
115
116 let mut new_base_bytes: Vec<u8> = vec![];
117 let mut new_local_bytes: Vec<u8> = vec![];
118 let mut new_other_bytes: Vec<u8> = vec![];
119 let mut previous = UnchangedRange {
120 base_range: 0..0,
121 offsets: vec![0, 0],
122 };
123 for current in unchanged_ranges {
124 let base_slice =
125 &base_bytes[previous.base_range.end..current.base_range.start];
126 let local_slice = &local_bytes[previous.end(0)..current.start(0)];
127 let other_slice = &other_bytes[previous.end(1)..current.start(1)];
128 if let Some(resolution) =
129 resolve_conflict(base_slice, local_slice, other_slice, regex)
130 {
131 new_base_bytes.extend(&resolution);
132 new_local_bytes.extend(&resolution);
133 new_other_bytes.extend(&resolution);
134 } else {
135 new_base_bytes.extend(base_slice);
136 new_local_bytes.extend(local_slice);
137 new_other_bytes.extend(other_slice);
138 }
139 new_base_bytes.extend(&base_bytes[current.base_range.clone()]);
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)]);
142 previous = current;
143 }
144
145 (new_base_bytes, new_local_bytes, new_other_bytes)
146 }
147
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
150 /// for partial merge tools (configured in `[partial-merge-tools]`).
151 #[derive(Parser, Debug)]
152 #[clap(version, about, long_about = None)]
153 struct Args {
154 /// Path to the file's content in the "local" side
155 local: OsString,
156
157 /// Path to the file's content in the base
158 base: OsString,
159
160 /// Path to the file's content in the "other" side
161 other: OsString,
162 }
163
164 fn main() {
165 let args: Args = Args::parse();
166
167 let base_path = PathBuf::from(&args.base);
168 let local_path = PathBuf::from(&args.local);
169 let other_path = PathBuf::from(&args.other);
170
171 let base_bytes = std::fs::read(&base_path).unwrap();
172 let local_bytes = std::fs::read(&local_path).unwrap();
173 let other_bytes = std::fs::read(&other_path).unwrap();
174
175 let regex =
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) =
178 resolve(&base_bytes, &local_bytes, &other_bytes, &regex);
179
180 // Write out the result if anything changed
181 if new_base_bytes != base_bytes {
182 std::fs::write(&base_path, new_base_bytes).unwrap();
183 }
184 if new_local_bytes != local_bytes {
185 std::fs::write(&local_path, new_local_bytes).unwrap();
186 }
187 if new_other_bytes != other_bytes {
188 std::fs::write(&other_path, new_other_bytes).unwrap();
189 }
190 }
191
192 fn checked_add(base: usize, offset: isize) -> usize {
193 if offset < 0 {
194 base.checked_sub(offset.checked_abs().unwrap() as usize)
195 .unwrap()
196 } else {
197 base.checked_add(offset as usize).unwrap()
198 }
199 }
200
201 // The remainder of the file is copied from
202 // https://github.com/martinvonz/jj/blob/main/lib/src/diff.rs
203
204 #[derive(Clone, PartialEq, Eq, Debug)]
205 struct UnchangedRange {
206 base_range: Range<usize>,
207 offsets: Vec<isize>,
208 }
209
210 impl UnchangedRange {
211 fn start(&self, side: usize) -> usize {
212 checked_add(self.base_range.start, self.offsets[side])
213 }
214
215 fn end(&self, side: usize) -> usize {
216 checked_add(self.base_range.end, self.offsets[side])
217 }
218 }
219
220 impl PartialOrd for UnchangedRange {
221 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
222 Some(self.cmp(other))
223 }
224 }
225
226 impl Ord for UnchangedRange {
227 fn cmp(&self, other: &Self) -> Ordering {
228 self.base_range
229 .start
230 .cmp(&other.base_range.start)
231 .then_with(|| self.base_range.end.cmp(&other.base_range.end))
232 }
233 }
234
235 /// 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
237 /// offset in the map's values.
238 fn intersect_regions(
239 current_ranges: Vec<UnchangedRange>,
240 new_unchanged_ranges: &[(Range<usize>, Range<usize>)],
241 ) -> Vec<UnchangedRange> {
242 let mut result = vec![];
243 let mut current_ranges_iter = current_ranges.into_iter().peekable();
244 for (new_base_range, other_range) in new_unchanged_ranges.iter() {
245 assert_eq!(new_base_range.len(), other_range.len());
246 while let Some(UnchangedRange {
247 base_range,
248 offsets,
249 }) = current_ranges_iter.peek()
250 {
251 // No need to look further if we're past the new range.
252 if base_range.start >= new_base_range.end {
253 break;
254 }
255 // Discard any current unchanged regions that don't match between
256 // the base and the new input.
257 if base_range.end <= new_base_range.start {
258 current_ranges_iter.next();
259 continue;
260 }
261 let new_start = max(base_range.start, new_base_range.start);
262 let new_end = min(base_range.end, new_base_range.end);
263 let mut new_offsets = offsets.clone();
264 new_offsets
265 .push(other_range.start.wrapping_sub(new_base_range.start)
266 as isize);
267 result.push(UnchangedRange {
268 base_range: new_start..new_end,
269 offsets: new_offsets,
270 });
271 if base_range.end >= new_base_range.end {
272 // Break without consuming the item; there may be other new
273 // ranges that overlap with it.
274 break;
275 }
276 current_ranges_iter.next();
277 }
278 }
279 result
280 }
@@ -0,0 +1,156 b''
1 use similar::DiffableStr;
2 use tempdir::TempDir;
3
4 fn run_test(input: &str) -> String {
5 let mut cmd = assert_cmd::Command::cargo_bin("merge-lists").unwrap();
6 let temp_dir = TempDir::new("test").unwrap();
7 let base_path = temp_dir.path().join("base");
8 let local_path = temp_dir.path().join("local");
9 let other_path = temp_dir.path().join("other");
10
11 let rest = input.strip_prefix("\nbase:\n").unwrap();
12 let mut split = rest.split("\nlocal:\n");
13 std::fs::write(&base_path, split.next().unwrap()).unwrap();
14 let rest = split.next().unwrap();
15 let mut split = rest.split("\nother:\n");
16 std::fs::write(&local_path, split.next().unwrap()).unwrap();
17 std::fs::write(&other_path, split.next().unwrap()).unwrap();
18 cmd.args(&[
19 local_path.as_os_str(),
20 base_path.as_os_str(),
21 other_path.as_os_str(),
22 ])
23 .assert()
24 .success();
25
26 let new_base_bytes = std::fs::read(&base_path).unwrap();
27 let new_local_bytes = std::fs::read(&local_path).unwrap();
28 let new_other_bytes = std::fs::read(&other_path).unwrap();
29 // No newline before "base:" because of https://github.com/mitsuhiko/insta/issues/117
30 format!(
31 "base:\n{}\nlocal:\n{}\nother:\n{}",
32 new_base_bytes.as_str().unwrap(),
33 new_local_bytes.as_str().unwrap(),
34 new_other_bytes.as_str().unwrap()
35 )
36 }
37
38 #[test]
39 fn test_merge_lists_basic() {
40 let output = run_test(
41 r"
42 base:
43 import lib1
44 import lib2
45
46 local:
47 import lib2
48 import lib3
49
50 other:
51 import lib3
52 import lib4
53 ",
54 );
55 insta::assert_snapshot!(output, @r###"
56 base:
57 import lib3
58 import lib4
59
60 local:
61 import lib3
62 import lib4
63
64 other:
65 import lib3
66 import lib4
67 "###);
68 }
69
70 #[test]
71 fn test_merge_lists_from() {
72 // Test some "from x import y" statements and some non-import conflicts
73 // (unresolvable)
74 let output = run_test(
75 r"
76 base:
77 from . import x
78
79 1+1
80
81 local:
82 from . import x
83 from a import b
84
85 2+2
86
87 other:
88 from a import c
89
90 3+3
91 ",
92 );
93 insta::assert_snapshot!(output, @r###"
94 base:
95 from a import b
96 from a import c
97
98 1+1
99
100 local:
101 from a import b
102 from a import c
103
104 2+2
105
106 other:
107 from a import b
108 from a import c
109
110 3+3
111 "###);
112 }
113
114 #[test]
115 fn test_merge_lists_not_sorted() {
116 // Test that nothing is done if the elements in the conflicting hunks are
117 // not sorted
118 let output = run_test(
119 r"
120 base:
121 import x
122
123 1+1
124
125 local:
126 import a
127 import x
128
129 2+2
130
131 other:
132 import z
133 import y
134
135 3+3
136 ",
137 );
138 insta::assert_snapshot!(output, @r###"
139 base:
140 import x
141
142 1+1
143
144 local:
145 import a
146 import x
147
148 2+2
149
150 other:
151 import z
152 import y
153
154 3+3
155 "###);
156 }
@@ -1,80 +1,81 b''
1 syntax: glob
1 syntax: glob
2
2
3 *.elc
3 *.elc
4 *.tmp
4 *.tmp
5 *.orig
5 *.orig
6 *.rej
6 *.rej
7 *~
7 *~
8 *.mergebackup
8 *.mergebackup
9 *.o
9 *.o
10 *.so
10 *.so
11 *.dll
11 *.dll
12 *.exe
12 *.exe
13 *.pyd
13 *.pyd
14 *.pyc
14 *.pyc
15 *.pyo
15 *.pyo
16 *$py.class
16 *$py.class
17 *.swp
17 *.swp
18 *.prof
18 *.prof
19 *.zip
19 *.zip
20 \#*\#
20 \#*\#
21 .\#*
21 .\#*
22 tests/artifacts/cache/big-file-churn.hg
22 tests/artifacts/cache/big-file-churn.hg
23 tests/.coverage*
23 tests/.coverage*
24 tests/.testtimes*
24 tests/.testtimes*
25 # the file is written in the CWD when run-tests is run.
25 # the file is written in the CWD when run-tests is run.
26 .testtimes
26 .testtimes
27 tests/.hypothesis
27 tests/.hypothesis
28 tests/hypothesis-generated
28 tests/hypothesis-generated
29 tests/annotated
29 tests/annotated
30 tests/exceptions
30 tests/exceptions
31 tests/python3
31 tests/python3
32 tests/*.err
32 tests/*.err
33 tests/htmlcov
33 tests/htmlcov
34 build
34 build
35 contrib/chg/chg
35 contrib/chg/chg
36 contrib/hgsh/hgsh
36 contrib/hgsh/hgsh
37 contrib/vagrant/.vagrant
37 contrib/vagrant/.vagrant
38 contrib/merge-lists/target/
38 dist
39 dist
39 packages
40 packages
40 doc/common.txt
41 doc/common.txt
41 doc/*.[0-9]
42 doc/*.[0-9]
42 doc/*.[0-9].txt
43 doc/*.[0-9].txt
43 doc/*.[0-9].gendoc.txt
44 doc/*.[0-9].gendoc.txt
44 doc/*.[0-9].{x,ht}ml
45 doc/*.[0-9].{x,ht}ml
45 MANIFEST
46 MANIFEST
46 MANIFEST.in
47 MANIFEST.in
47 patches
48 patches
48 mercurial/__modulepolicy__.py
49 mercurial/__modulepolicy__.py
49 mercurial/__version__.py
50 mercurial/__version__.py
50 mercurial/hgpythonlib.h
51 mercurial/hgpythonlib.h
51 mercurial.egg-info
52 mercurial.egg-info
52 .DS_Store
53 .DS_Store
53 tags
54 tags
54 cscope.*
55 cscope.*
55 .vscode/*
56 .vscode/*
56 .idea/*
57 .idea/*
57 .asv/*
58 .asv/*
58 .pytype/*
59 .pytype/*
59 .mypy_cache
60 .mypy_cache
60 i18n/hg.pot
61 i18n/hg.pot
61 locale/*/LC_MESSAGES/hg.mo
62 locale/*/LC_MESSAGES/hg.mo
62 hgext/__index__.py
63 hgext/__index__.py
63
64
64 rust/target/
65 rust/target/
65 rust/*/target/
66 rust/*/target/
66
67
67 # Generated wheels
68 # Generated wheels
68 wheelhouse/
69 wheelhouse/
69
70
70 syntax: rootglob
71 syntax: rootglob
71 # See Profiling in rust/README.rst
72 # See Profiling in rust/README.rst
72 .cargo/config
73 .cargo/config
73
74
74 syntax: regexp
75 syntax: regexp
75 ^\.pc/
76 ^\.pc/
76 ^\.(pydev)?project
77 ^\.(pydev)?project
77
78
78 # hackable windows distribution additions
79 # hackable windows distribution additions
79 ^hg-python
80 ^hg-python
80 ^hg.py$
81 ^hg.py$
General Comments 0
You need to be logged in to leave comments. Login now