2 * wiggle - apply rejected patches
4 * Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
6 * Copyright (C) 2014-2020 Neil Brown <neil@brown.name>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program.
23 * Email: <neil@brown.name>
27 * Wiggle is a tool for working with patches that don't quite apply properly.
28 * It provides functionality similar to 'diff' and 'merge' but can
29 * work at the level of individual words thus allowing the merging of
30 * two changes that affect the same line, but not the same parts of that line.
32 * Wiggle can also read patch and merge files. Unlike 'merge' it does not
33 * need to be given three separate files, but can be given a file and a patch
34 * and it will extract the pieces of the two other files that it needs from
37 * Wiggle performs one of three core function:
38 * --extract -x extract part of a patch or merge file
39 * --diff -d report differences between two files
40 * --merge -m merge the changes between two files into a third file
42 * This is also a --browse (-B) mode which provides interactive access
45 * To perform these, wiggle requires 1, 2, or 3 input streams respectively.
46 * I can get these from individual files, from a diff (unified or context) or
50 * If one file is given, it is a merge file (output of 'merge').
51 * If two files are given, the second is assumed to be a patch,
52 * the first is a normal file.
53 * If three files are given, they are taken to be normal files.
56 * If one file is given, it is a patch
57 * If two files are given, they are normal files.
60 * Only one file can be given. -p indicates it is a patch,
61 * otherwise it is a merge.
62 * One of the flags -1 -2 or -3 must also be given and they indicate which
63 * part of the patch or merge to extract.
65 * Difference calculation and merging is performed on lines (-l) or words (-w).
66 * Each 'word' is either 1/all alphnumeric (or '_'), 2/ all space or tab,
67 * or 3/ any other single character.
69 * In the case of -w, an initial diff is computed based on non-trivial words
70 * which includes alhpanumeric words and newlines.
72 * This diff is computed from the ends of the file and is used to find
73 * a suitable starting point and range. Then a more precise diff is
74 * computed over that restricted range
76 * Other options available are:
77 * --replace -r replace first file with result of merge.
78 * --help -h provide help
79 * --version -v version
81 * Defaults are --merge --words
94 static void printsep(struct elmnt e1
, struct elmnt e2
)
97 sscanf(e1
.start
+1, "%d %d %d", &a
, &b
, &c
);
98 sscanf(e2
.start
+1, "%d %d %d", &d
, &e
, &f
);
99 printf("@@ -%d,%d +%d,%d @@%s", b
, c
, e
, f
, e1
.start
+18);
102 static int extract(int argc
, char *argv
[], int ispatch
, int which
)
104 /* extract a branch of a diff or diff3 or merge output
107 struct stream f
, flist
[3];
111 "%s: no file given for --extract\n", wiggle_Cmd
);
116 "%s: only give one file for --extract\n", wiggle_Cmd
);
119 f
= wiggle_load_file(argv
[0]);
120 if (f
.body
== NULL
) {
122 "%s: cannot load file '%s' - %s\n", wiggle_Cmd
,
123 argv
[0], strerror(errno
));
127 if (wiggle_split_patch(f
, &flist
[0], &flist
[1]) == 0) {
129 "%s: No chunk found in patch: %s\n", wiggle_Cmd
,
134 if (!wiggle_split_merge(f
, &flist
[0], &flist
[1], &flist
[2])) {
136 "%s: merge file %s looks bad.\n", wiggle_Cmd
,
141 if (flist
[which
-'1'].body
== NULL
) {
143 "%s: %s has no -%c component.\n", wiggle_Cmd
,
147 if (write(1, flist
[which
-'1'].body
,
148 flist
[which
-'1'].len
)
149 != flist
[which
-'1'].len
)
155 static int do_diff_lines(struct file fl
[2], struct csl
*csl
)
160 while (a
< fl
[0].elcnt
|| b
< fl
[1].elcnt
) {
162 if (fl
[0].list
[a
].start
[0]) {
164 wiggle_printword(stdout
,
169 } else if (b
< csl
->b
) {
170 if (fl
[1].list
[b
].start
[0]) {
172 wiggle_printword(stdout
,
178 if (fl
[0].list
[a
].start
[0] == '\0')
179 printsep(fl
[0].list
[a
],
183 wiggle_printword(stdout
,
188 if (a
>= csl
->a
+csl
->len
)
195 static int do_diff_words(struct file fl
[2], struct csl
*csl
)
199 int sol
= 1; /* start of line */
201 while (a
< fl
[0].elcnt
|| b
< fl
[1].elcnt
) {
211 for (a1
= a
; a1
< csl
->a
; a1
++)
212 if (ends_line(fl
[0].list
[a1
])) {
218 for (; a
< csl
->a
; a
++) {
219 wiggle_printword(stdout
, fl
[0].list
[a
]);
220 if (ends_line(fl
[0].list
[a
])) {
233 wiggle_printword(stdout
, fl
[0].list
[a
]);
234 sol
= ends_line(fl
[0].list
[a
]);
236 } while (a
< csl
->a
);
237 printf("%s-->>>", sol
? "|" : "");
240 } else if (b
< csl
->b
) {
245 for (b1
= b
; b1
< csl
->b
; b1
++)
246 if (ends_line(fl
[1].list
[b1
])) {
252 for (; b
< csl
->b
; b
++) {
253 wiggle_printword(stdout
, fl
[1].list
[b
]);
254 if (ends_line(fl
[1].list
[b
])) {
267 wiggle_printword(stdout
, fl
[1].list
[b
]);
268 sol
= ends_line(fl
[1].list
[b
]);
270 } while (b
< csl
->b
);
271 printf("%s++>>>", sol
? "|" : "");
278 for (a1
= a
; a1
< csl
->a
+csl
->len
; a1
++)
279 if (ends_line(fl
[0].list
[a1
]))
282 if (fl
[0].list
[a
].start
[0]) {
284 for (; a
< csl
->a
+csl
->len
; a
++, b
++) {
285 wiggle_printword(stdout
, fl
[0].list
[a
]);
286 if (ends_line(fl
[0].list
[a
])) {
292 printsep(fl
[0].list
[a
], fl
[1].list
[b
]);
299 wiggle_printword(stdout
, fl
[0].list
[a
]);
300 if (ends_line(fl
[0].list
[a
]))
305 if (a
>= csl
->a
+csl
->len
)
312 static int do_diff(int argc
, char *argv
[], int obj
, int ispatch
,
313 int which
, int reverse
, int shortest
)
315 /* create a diff (line or char) of two streams */
316 struct stream f
, flist
[3];
317 int chunks1
= 0, chunks2
= 0, chunks3
= 0;
324 fprintf(stderr
, "%s: no file given for --diff\n", wiggle_Cmd
);
327 f
= wiggle_load_file(argv
[0]);
328 if (f
.body
== NULL
) {
330 "%s: cannot load file '%s' - %s\n", wiggle_Cmd
,
331 argv
[0], strerror(errno
));
335 wiggle_split_patch(f
, &flist
[0], &flist
[1]);
336 if (!flist
[0].body
|| !flist
[1].body
) {
338 "%s: couldn't parse patch %s\n", wiggle_Cmd
,
344 flist
[0] = wiggle_load_file(argv
[0]);
345 if (flist
[0].body
== NULL
) {
347 "%s: cannot load file '%s' - %s\n", wiggle_Cmd
,
348 argv
[0], strerror(errno
));
352 f
= wiggle_load_file(argv
[1]);
353 if (f
.body
== NULL
) {
355 "%s: cannot load patch '%s' - %s\n", wiggle_Cmd
,
356 argv
[1], strerror(errno
));
361 wiggle_split_patch(f
, &flist
[2],
365 wiggle_split_patch(f
, &flist
[1],
369 flist
[1] = wiggle_load_file(argv
[1]);
370 if (flist
[1].body
== NULL
) {
372 "%s: cannot load file '%s' - %s\n", wiggle_Cmd
,
373 argv
[1], strerror(errno
));
379 "%s: too many files given for --diff\n", wiggle_Cmd
);
387 fl
[0] = wiggle_split_stream(flist
[0], obj
);
388 fl
[1] = wiggle_split_stream(flist
[1], obj
);
389 if (!(obj
& WholeWord
) && fl
[0].elcnt
> 50000 && fl
[1].elcnt
> 50000) {
390 /* Too big - use fewer words if possible */
394 fl
[0] = wiggle_split_stream(flist
[0], obj
);
395 fl
[1] = wiggle_split_stream(flist
[1], obj
);
397 if (chunks2
&& !chunks1
)
398 csl
= wiggle_pdiff(fl
[0], fl
[1], chunks2
);
400 csl
= wiggle_diff_patch(fl
[0], fl
[1], shortest
);
401 if ((obj
& ByMask
) == ByLine
) {
403 printf("@@ -1,%d +1,%d @@\n",
404 fl
[0].elcnt
, fl
[1].elcnt
);
405 exit_status
= do_diff_lines(fl
, csl
);
408 /* count lines in each file */
411 for (i
= 0 ; i
< fl
[0].elcnt
; i
++)
412 if (ends_line(fl
[0].list
[i
]))
414 for (i
= 0 ; i
< fl
[1].elcnt
; i
++)
415 if (ends_line(fl
[1].list
[i
]))
417 printf("@@ -1,%d +1,%d @@\n", l1
, l2
);
419 exit_status
= do_diff_words(fl
, csl
);
424 static int do_merge(int argc
, char *argv
[], int obj
, int blanks
,
425 int reverse
, int replace
, char *outfilename
,
426 int ignore
, int show_wiggles
,
427 int quiet
, int shortest
, int backup
)
429 /* merge three files, A B C, so changed between B and C get made to A
431 struct stream f
, flist
[3];
434 int chunks1
= 0, chunks2
= 0, chunks3
= 0;
435 char *replacename
= NULL
, *orignew
= NULL
;
436 struct csl
*csl1
, *csl2
;
438 FILE *outfile
= stdout
;
442 fprintf(stderr
, "%s: no files given for --merge\n", wiggle_Cmd
);
447 for (i
= 0; i
< argc
; i
++) {
448 flist
[i
] = wiggle_load_file(argv
[i
]);
449 if (flist
[i
].body
== NULL
) {
450 fprintf(stderr
, "%s: cannot load file '%s' - %s\n",
452 argv
[i
], strerror(errno
));
458 fprintf(stderr
, "%s: too many files given for --merge\n",
463 case 1: /* a merge file */
465 if (!wiggle_split_merge(f
, &flist
[0], &flist
[1], &flist
[2])) {
466 fprintf(stderr
, "%s: merge file %s looks bad.\n",
472 case 2: /* a file and a patch */
474 chunks2
= chunks3
= wiggle_split_patch(f
, &flist
[1], &flist
[2]);
476 case 3: /* three separate files */
485 for (i
= 0; i
< 3; i
++) {
486 if (flist
[i
].body
== NULL
) {
487 fprintf(stderr
, "%s: file %d missing\n", wiggle_Cmd
, i
);
492 outfile
= fopen(outfilename
, "w");
494 fprintf(stderr
, "%s: could not create %s\n",
495 wiggle_Cmd
, outfilename
);
498 } else if (replace
) {
500 replacename
= wiggle_xmalloc(strlen(argv
[0]) + 20);
501 orignew
= wiggle_xmalloc(strlen(argv
[0]) + 20);
502 strcpy(replacename
, argv
[0]);
503 strcpy(orignew
, argv
[0]);
504 strcat(orignew
, ".porig");
505 if (backup
&& (open(orignew
, O_RDONLY
) >= 0 ||
507 fprintf(stderr
, "%s: %s already exists\n",
514 strcat(replacename
, "XXXXXX");
515 fd
= mkstemp(replacename
);
518 "%s: could not create temporary file for %s\n",
525 outfile
= fdopen(fd
, "w");
532 fl
[0] = wiggle_split_stream(flist
[0], blanks
);
533 fl
[1] = wiggle_split_stream(flist
[1], blanks
);
534 fl
[2] = wiggle_split_stream(flist
[2], blanks
);
535 if (!(blanks
& WholeWord
) &&
536 fl
[1].elcnt
> 50000 &&
537 (fl
[0].elcnt
> 50000 || fl
[2].elcnt
> 50000)) {
543 fl
[0] = wiggle_split_stream(flist
[0], blanks
);
544 fl
[1] = wiggle_split_stream(flist
[1], blanks
);
545 fl
[2] = wiggle_split_stream(flist
[2], blanks
);
548 if (chunks2
&& !chunks1
)
549 csl1
= wiggle_pdiff(fl
[0], fl
[1], chunks2
);
551 csl1
= wiggle_diff(fl
[0], fl
[1], shortest
);
552 csl2
= wiggle_diff_patch(fl
[1], fl
[2], shortest
);
554 ci
= wiggle_make_merger(fl
[0], fl
[1], fl
[2], csl1
, csl2
,
555 obj
== 'w', ignore
, show_wiggles
> 1);
556 wiggle_print_merge(outfile
, &fl
[0], &fl
[1], &fl
[2],
557 obj
== 'w', ci
.merger
, NULL
, 0, 0);
558 if (!quiet
&& ci
.conflicts
)
560 "%d unresolved conflict%s found\n",
562 ci
.conflicts
== 1 ? "" : "s");
563 if (!quiet
&& ci
.ignored
)
565 "%d already-applied change%s ignored\n",
567 ci
.ignored
== 1 ? "" : "s");
574 if (stat(argv
[0], &statbuf
) != 0) {
576 "%s: failed to stat original file. - %s\n",
577 wiggle_Cmd
, strerror(errno
));
582 if (fchmod(fileno(outfile
), statbuf
.st_mode
) != 0) {
584 "%s: failed to change permission of new file. - %s\n",
585 wiggle_Cmd
, strerror(errno
));
591 if ((!backup
|| rename(argv
[0], orignew
) == 0) &&
592 rename(replacename
, argv
[0]) == 0)
596 "%s: failed to move new file into place.\n",
606 return ci
.conflicts
+ ci
.wiggles
> 0;
608 return ci
.conflicts
> 0;
611 static int multi_merge(int argc
, char *argv
[], int obj
, int blanks
,
612 int reverse
, int ignore
, int show_wiggles
,
613 int replace
, int strip
,
614 int quiet
, int shortest
, int backup
)
625 "%s: -p in merge mode requires -r\n",
631 "%s: -p in merge mode requires exactly one file\n",
636 f
= fopen(filename
, "r");
638 fprintf(stderr
, "%s: cannot open %s\n",
639 wiggle_Cmd
, filename
);
642 wiggle_check_dir(filename
, fileno(f
));
643 pl
= wiggle_parse_patch(f
, NULL
, &num_patches
);
645 if (wiggle_set_prefix(pl
, num_patches
, strip
) == 0) {
646 fprintf(stderr
, "%s: aborting\n", wiggle_Cmd
);
649 for (i
= 0; i
< num_patches
; i
++) {
652 asprintf(&name
, "_wiggle_:%d:%d:%s",
653 pl
[i
].start
, pl
[i
].end
, filename
);
656 rv
|= do_merge(2, av
, obj
, blanks
, reverse
, 1, NULL
, ignore
,
657 show_wiggles
, quiet
, shortest
, backup
);
662 int main(int argc
, char *argv
[])
673 int verbose
= 0, quiet
= 0;
678 int show_wiggles
= 0;
681 char *outfile
= NULL
;
683 int ignore_blanks
= 0;
685 trace
= getenv("WIGGLE_TRACE");
689 while ((opt
= getopt_long(argc
, argv
,
690 short_options
, long_options
,
691 &option_index
)) != -1)
697 helpmsg
= HelpExtract
;
706 helpmsg
= HelpBrowse
;
709 fputs(helpmsg
, stderr
);
713 fputs(Version
, stderr
);
718 fputs(Usage
, stderr
);
729 if (mode
== 'B' && opt
== 'd') {
730 /* Browse/diff mode */
735 "%s: mode is '%c' - cannot set to '%c'\n",
736 wiggle_Cmd
, mode
, opt
);
740 ignore_blanks
|= WholeWord
;
749 if (obj
== 0 || obj
== opt
) {
754 "%s: cannot select both words and lines.\n", wiggle_Cmd
);
772 ignore_blanks
|= IgnoreBlanks
;
789 if (which
== 0 || which
== opt
) {
794 "%s: can only select one of -1, -2, -3\n", wiggle_Cmd
);
797 case 'p': /* 'patch' or 'strip' */
799 strip
= atol(optarg
);
818 vpatch(argc
-optind
, argv
+optind
, ispatch
,
819 strip
, reverse
, replace
, outfile
, selftest
,
820 ignore_blanks
, backup
);
821 /* should not return */
825 if (obj
&& mode
== 'x') {
827 "%s: cannot specify --line or --word with --extract\n",
831 if (mode
!= 'm' && !obj
)
833 if (ispatch
&& outfile
) {
834 fprintf(stderr
, "%s: --output incompatible with --patch\n",
838 if (replace
&& mode
!= 'm') {
840 "%s: --replace or --output only allowed with --merge\n", wiggle_Cmd
);
843 if (mode
== 'x' && !which
) {
845 "%s: must specify -1, -2 or -3 with --extract\n", wiggle_Cmd
);
848 if (mode
!= 'x' && mode
!= 'd' && which
) {
850 "%s: -1, -2 or -3 only allowed with --extract or --diff\n",
855 if (ispatch
&& which
== '3') {
857 "%s: cannot extract -3 from a patch.\n", wiggle_Cmd
);
863 exit_status
= extract(argc
-optind
, argv
+optind
, ispatch
, which
);
866 exit_status
= do_diff(argc
-optind
, argv
+optind
,
867 (obj
== 'l' ? ByLine
: ByWord
)
869 ispatch
, which
, reverse
, shortest
);
873 exit_status
= multi_merge(argc
-optind
,
882 exit_status
= do_merge(
883 argc
-optind
, argv
+optind
,
884 obj
, ignore_blanks
, reverse
, replace
,
886 ignore
, show_wiggles
, quiet
, shortest
,