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>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program.
22 * Email: <neilb@suse.de>
26 * Wiggle is a tool for working with patches that don't quite apply properly.
27 * It provides functionality similar to 'diff' and 'merge' but can
28 * work at the level of individual words thus allowing the merging of
29 * two changes that affect the same line, but not the same parts of that line.
31 * Wiggle can also read patch and merge files. Unlike 'merge' it does not
32 * need to be given three separate files, but can be given a file and a patch
33 * and it will extract the pieces of the two other files that it needs from
36 * Wiggle performs one of three core function:
37 * --extract -x extract part of a patch or merge file
38 * --diff -d report differences between two files
39 * --merge -m merge the changes between two files into a third file
41 * This is also a --browse (-B) mode which provides interactive access
44 * To perform these, wiggle requires 1, 2, or 3 input streams respectively.
45 * I can get these from individual files, from a diff (unified or context) or
49 * If one file is given, it is a merge file (output of 'merge').
50 * If two files are given, the second is assumed to be a patch,
51 * the first is a normal file.
52 * If three files are given, they are taken to be normal files.
55 * If one file is given, it is a patch
56 * If two files are given, they are normal files.
59 * Only one file can be given. -p indicates it is a patch,
60 * otherwise it is a merge.
61 * One of the flags -1 -2 or -3 must also be given and they indicate which
62 * part of the patch or merge to extract.
64 * Difference calculation and merging is performed on lines (-l) or words (-w).
65 * Each 'word' is either 1/all alphnumeric (or '_'), 2/ all space or tab,
66 * or 3/ any other single character.
68 * In the case of -w, an initial diff is computed based on non-trivial words
69 * which includes alhpanumeric words and newlines.
71 * This diff is computed from the ends of the file and is used to find
72 * a suitable starting point and range. Then a more precise diff is
73 * computed over that restricted range
75 * Other options available are:
76 * --replace -r replace first file with result of merge.
77 * --help -h provide help
78 * --version -v version
80 * Defaults are --merge --words
96 void die(char *reason
)
98 fprintf(stderr
, "%s: fatal error: %s failure\n", Cmd
, reason
);
102 void check_dir(char *name
, int fd
)
105 if (fstat(fd
, &st
) != 0) {
106 fprintf(stderr
, "%s: fatal: %s is strange\n", Cmd
, name
);
109 if (S_ISDIR(st
.st_mode
)) {
110 fprintf(stderr
, "%s: %s is a directory\n", Cmd
, name
);
115 void *xmalloc(int size
)
117 void *rv
= malloc(size
);
119 char *msg
= "Failed to allocate memory - aborting\n";
120 write(2, msg
, strlen(msg
));
126 void printword(FILE *f
, struct elmnt e
)
129 fprintf(f
, "%.*s", e
.plen
+ e
.prefix
,
133 sscanf(e
.start
+1, "%d %d %d", &a
, &b
, &c
);
134 fprintf(f
, "*** %d,%d **** %d%s", b
, c
, a
, e
.start
+18);
138 static void printsep(struct elmnt e1
, struct elmnt e2
)
140 int a
, b
, c
, d
, e
, f
;
141 sscanf(e1
.start
+1, "%d %d %d", &a
, &b
, &c
);
142 sscanf(e2
.start
+1, "%d %d %d", &d
, &e
, &f
);
143 printf("@@ -%d,%d +%d,%d @@%s", b
, c
, e
, f
, e1
.start
+18);
146 static int extract(int argc
, char *argv
[], int ispatch
, int which
)
148 /* extract a branch of a diff or diff3 or merge output
151 struct stream f
, flist
[3];
155 "%s: no file given for --extract\n", Cmd
);
160 "%s: only give one file for --extract\n", Cmd
);
163 f
= load_file(argv
[0]);
164 if (f
.body
== NULL
) {
166 "%s: cannot load file '%s' - %s\n", Cmd
,
167 argv
[0], strerror(errno
));
171 if (split_patch(f
, &flist
[0], &flist
[1]) == 0) {
173 "%s: No chunk found in patch: %s\n", Cmd
,
178 if (!split_merge(f
, &flist
[0], &flist
[1], &flist
[2])) {
180 "%s: merge file %s looks bad.\n", Cmd
,
185 if (flist
[which
-'1'].body
== NULL
) {
187 "%s: %s has no -%c component.\n", Cmd
,
191 if (write(1, flist
[which
-'1'].body
,
192 flist
[which
-'1'].len
)
193 != flist
[which
-'1'].len
)
199 static int do_diff_lines(struct file fl
[2], struct csl
*csl
)
204 while (a
< fl
[0].elcnt
|| b
< fl
[1].elcnt
) {
206 if (fl
[0].list
[a
].start
[0]) {
213 } else if (b
< csl
->b
) {
214 if (fl
[1].list
[b
].start
[0]) {
222 if (fl
[0].list
[a
].start
[0] == '\0')
223 printsep(fl
[0].list
[a
],
232 if (a
>= csl
->a
+csl
->len
)
239 static int do_diff_words(struct file fl
[2], struct csl
*csl
)
243 int sol
= 1; /* start of line */
245 while (a
< fl
[0].elcnt
|| b
< fl
[1].elcnt
) {
255 for (a1
= a
; a1
< csl
->a
; a1
++)
256 if (ends_line(fl
[0].list
[a1
])) {
262 for (; a
< csl
->a
; a
++) {
263 printword(stdout
, fl
[0].list
[a
]);
264 if (ends_line(fl
[0].list
[a
])) {
277 printword(stdout
, fl
[0].list
[a
]);
278 sol
= ends_line(fl
[0].list
[a
]);
280 } while (a
< csl
->a
);
281 printf("%s-->>>", sol
? "|" : "");
284 } else if (b
< csl
->b
) {
289 for (b1
= b
; b1
< csl
->b
; b1
++)
290 if (ends_line(fl
[1].list
[b1
])) {
296 for (; b
< csl
->b
; b
++) {
297 printword(stdout
, fl
[1].list
[b
]);
298 if (ends_line(fl
[1].list
[b
])) {
311 printword(stdout
, fl
[1].list
[b
]);
312 sol
= ends_line(fl
[1].list
[b
]);
314 } while (b
< csl
->b
);
315 printf("%s++>>>", sol
? "|" : "");
322 for (a1
= a
; a1
< csl
->a
+csl
->len
; a1
++)
323 if (ends_line(fl
[0].list
[a1
]))
326 if (fl
[0].list
[a
].start
[0]) {
328 for (; a
< csl
->a
+csl
->len
; a
++, b
++) {
329 printword(stdout
, fl
[0].list
[a
]);
330 if (ends_line(fl
[0].list
[a
])) {
336 printsep(fl
[0].list
[a
], fl
[1].list
[b
]);
343 printword(stdout
, fl
[0].list
[a
]);
344 if (ends_line(fl
[0].list
[a
]))
349 if (a
>= csl
->a
+csl
->len
)
356 static int do_diff(int argc
, char *argv
[], int obj
, int ispatch
,
357 int which
, int reverse
)
359 /* create a diff (line or char) of two streams */
360 struct stream f
, flist
[3];
361 int chunks1
= 0, chunks2
= 0, chunks3
= 0;
368 fprintf(stderr
, "%s: no file given for --diff\n", Cmd
);
371 f
= load_file(argv
[0]);
372 if (f
.body
== NULL
) {
374 "%s: cannot load file '%s' - %s\n", Cmd
,
375 argv
[0], strerror(errno
));
379 split_patch(f
, &flist
[0], &flist
[1]);
380 if (!flist
[0].body
|| !flist
[1].body
) {
382 "%s: couldn't parse patch %s\n", Cmd
,
388 flist
[0] = load_file(argv
[0]);
389 if (flist
[0].body
== NULL
) {
391 "%s: cannot load file '%s' - %s\n", Cmd
,
392 argv
[0], strerror(errno
));
396 f
= load_file(argv
[1]);
397 if (f
.body
== NULL
) {
399 "%s: cannot load patch '%s' - %s\n", Cmd
,
400 argv
[1], strerror(errno
));
405 split_patch(f
, &flist
[2],
409 split_patch(f
, &flist
[1],
413 flist
[1] = load_file(argv
[1]);
414 if (flist
[1].body
== NULL
) {
416 "%s: cannot load file '%s' - %s\n", Cmd
,
417 argv
[1], strerror(errno
));
423 "%s: too many files given for --diff\n", Cmd
);
431 fl
[0] = split_stream(flist
[0], obj
);
432 fl
[1] = split_stream(flist
[1], obj
);
433 if (chunks2
&& !chunks1
)
434 csl
= pdiff(fl
[0], fl
[1], chunks2
);
436 csl
= diff_patch(fl
[0], fl
[1]);
437 if ((obj
& ByMask
) == ByLine
) {
439 printf("@@ -1,%d +1,%d @@\n",
440 fl
[0].elcnt
, fl
[1].elcnt
);
441 exit_status
= do_diff_lines(fl
, csl
);
444 /* count lines in each file */
447 for (i
= 0 ; i
< fl
[0].elcnt
; i
++)
448 if (ends_line(fl
[0].list
[i
]))
450 for (i
= 0 ; i
< fl
[1].elcnt
; i
++)
451 if (ends_line(fl
[1].list
[i
]))
453 printf("@@ -1,%d +1,%d @@\n", l1
, l2
);
455 exit_status
= do_diff_words(fl
, csl
);
460 static int do_merge(int argc
, char *argv
[], int obj
, int blanks
,
461 int reverse
, int replace
, char *outfilename
,
462 int ignore
, int show_wiggles
,
465 /* merge three files, A B C, so changed between B and C get made to A
467 struct stream f
, flist
[3];
470 int chunks1
= 0, chunks2
= 0, chunks3
= 0;
471 char *replacename
= NULL
, *orignew
= NULL
;
472 struct csl
*csl1
, *csl2
;
474 FILE *outfile
= stdout
;
478 fprintf(stderr
, "%s: no files given for --merge\n", Cmd
);
483 for (i
= 0; i
< argc
; i
++) {
484 flist
[i
] = load_file(argv
[i
]);
485 if (flist
[i
].body
== NULL
) {
486 fprintf(stderr
, "%s: cannot load file '%s' - %s\n",
488 argv
[i
], strerror(errno
));
494 fprintf(stderr
, "%s: too many files given for --merge\n",
499 case 1: /* a merge file */
501 if (!split_merge(f
, &flist
[0], &flist
[1], &flist
[2])) {
502 fprintf(stderr
, "%s: merge file %s looks bad.\n",
508 case 2: /* a file and a patch */
510 chunks2
= chunks3
= split_patch(f
, &flist
[1], &flist
[2]);
512 case 3: /* three separate files */
521 for (i
= 0; i
< 3; i
++) {
522 if (flist
[i
].body
== NULL
) {
523 fprintf(stderr
, "%s: file %d missing\n", Cmd
, i
);
528 outfile
= fopen(outfilename
, "w");
530 fprintf(stderr
, "%s: could not create %s\n",
534 } else if (replace
) {
536 replacename
= xmalloc(strlen(argv
[0]) + 20);
537 orignew
= xmalloc(strlen(argv
[0]) + 20);
538 strcpy(replacename
, argv
[0]);
539 strcpy(orignew
, argv
[0]);
540 strcat(orignew
, ".porig");
541 if (open(orignew
, O_RDONLY
) >= 0 ||
543 fprintf(stderr
, "%s: %s already exists\n",
548 strcat(replacename
, "XXXXXX");
549 fd
= mkstemp(replacename
);
552 "%s: could not create temporary file for %s\n",
557 outfile
= fdopen(fd
, "w");
564 fl
[0] = split_stream(flist
[0], blanks
);
565 fl
[1] = split_stream(flist
[1], blanks
);
566 fl
[2] = split_stream(flist
[2], blanks
);
568 if (chunks2
&& !chunks1
)
569 csl1
= pdiff(fl
[0], fl
[1], chunks2
);
571 csl1
= diff(fl
[0], fl
[1]);
572 csl2
= diff_patch(fl
[1], fl
[2]);
574 ci
= make_merger(fl
[0], fl
[1], fl
[2], csl1
, csl2
,
575 obj
== 'w', ignore
, show_wiggles
> 1);
576 print_merge(outfile
, &fl
[0], &fl
[1], &fl
[2],
577 obj
== 'w', ci
.merger
, NULL
, 0, 0);
578 if (!quiet
&& ci
.conflicts
)
580 "%d unresolved conflict%s found\n",
582 ci
.conflicts
== 1 ? "" : "s");
583 if (!quiet
&& ci
.ignored
)
585 "%d already-applied change%s ignored\n",
587 ci
.ignored
== 1 ? "" : "s");
593 if (rename(argv
[0], orignew
) == 0 &&
594 rename(replacename
, argv
[0]) == 0)
598 "%s: failed to move new file into place.\n",
604 return ci
.conflicts
+ ci
.wiggles
> 0;
606 return ci
.conflicts
> 0;
609 static int multi_merge(int argc
, char *argv
[], int obj
, int blanks
,
610 int reverse
, int ignore
, int show_wiggles
,
611 int replace
, int strip
,
623 "%s: -p in merge mode requires -r\n",
629 "%s: -p in merge mode requires exactly one file\n",
634 f
= fopen(filename
, "r");
636 fprintf(stderr
, "%s: cannot open %s\n",
640 check_dir(filename
, fileno(f
));
641 pl
= parse_patch(f
, NULL
, &num_patches
);
643 if (set_prefix(pl
, num_patches
, strip
) == 0) {
644 fprintf(stderr
, "%s: aborting\n", Cmd
);
647 for (i
= 0; i
< num_patches
; i
++) {
650 asprintf(&name
, "_wiggle_:%d:%d:%s",
651 pl
[i
].start
, pl
[i
].end
, filename
);
654 rv
|= do_merge(2, av
, obj
, blanks
, reverse
, 1, NULL
, ignore
,
655 show_wiggles
, quiet
);
660 int main(int argc
, char *argv
[])
670 int verbose
= 0, quiet
= 0;
674 int show_wiggles
= 0;
677 char *outfile
= NULL
;
679 int ignore_blanks
= 0;
681 trace
= getenv("WIGGLE_TRACE");
685 while ((opt
= getopt_long(argc
, argv
,
686 short_options
, long_options
,
687 &option_index
)) != -1)
693 helpmsg
= HelpExtract
;
702 helpmsg
= HelpBrowse
;
705 fputs(helpmsg
, stderr
);
709 fputs(Version
, stderr
);
714 fputs(Usage
, stderr
);
725 if (mode
== 'B' && opt
== 'd') {
726 /* Browse/diff mode */
731 "%s: mode is '%c' - cannot set to '%c'\n",
737 if (obj
== 0 || obj
== opt
) {
742 "%s: cannot select both words and lines.\n", Cmd
);
757 ignore_blanks
= IgnoreBlanks
;
774 if (which
== 0 || which
== opt
) {
779 "%s: can only select one of -1, -2, -3\n", Cmd
);
782 case 'p': /* 'patch' or 'strip' */
784 strip
= atol(optarg
);
803 vpatch(argc
-optind
, argv
+optind
, ispatch
,
804 strip
, reverse
, replace
, outfile
, selftest
,
806 /* should not return */
810 if (obj
&& mode
== 'x') {
812 "%s: cannot specify --line or --word with --extract\n",
816 if (mode
!= 'm' && !obj
)
818 if (ispatch
&& outfile
) {
819 fprintf(stderr
, "%s: --output incompatible with --patch\n",
823 if (replace
&& mode
!= 'm') {
825 "%s: --replace or --output only allowed with --merge\n", Cmd
);
828 if (mode
== 'x' && !which
) {
830 "%s: must specify -1, -2 or -3 with --extract\n", Cmd
);
833 if (mode
!= 'x' && mode
!= 'd' && which
) {
835 "%s: -1, -2 or -3 only allowed with --extract or --diff\n",
840 if (ispatch
&& which
== '3') {
842 "%s: cannot extract -3 from a patch.\n", Cmd
);
848 exit_status
= extract(argc
-optind
, argv
+optind
, ispatch
, which
);
851 exit_status
= do_diff(argc
-optind
, argv
+optind
,
852 (obj
== 'l' ? ByLine
: ByWord
)
854 ispatch
, which
, reverse
);
858 exit_status
= multi_merge(argc
-optind
,
866 exit_status
= do_merge(
867 argc
-optind
, argv
+optind
,
868 obj
, ignore_blanks
, reverse
, replace
,
870 ignore
, show_wiggles
, quiet
);