Man page updates.
[wiggle/upstream.git] / wiggle.c
blob4289573724d73883a5be8e0d4baad47a029a17d8
1 /*
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.
21 * Author: Neil Brown
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
34 * the patch.
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
42 * to the merger.
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
46 * from a merge file.
48 * For merge:
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.
54 * For diff:
55 * If one file is given, it is a patch
56 * If two files are given, they are normal files.
58 * For extract:
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
83 #define _GNU_SOURCE
84 #include "wiggle.h"
85 #include <errno.h>
86 #include <fcntl.h>
87 #include <unistd.h>
88 #include <stdlib.h>
89 #include <stdio.h>
90 #include <ctype.h>
91 #include <sys/stat.h>
93 char *Cmd = "wiggle";
94 int do_trace = 0;
96 void die(char *reason)
98 fprintf(stderr, "%s: fatal error: %s failure\n", Cmd, reason);
99 exit(3);
102 void check_dir(char *name, int fd)
104 struct stat st;
105 if (fstat(fd, &st) != 0) {
106 fprintf(stderr, "%s: fatal: %s is strange\n", Cmd, name);
107 exit(3);
109 if (S_ISDIR(st.st_mode)) {
110 fprintf(stderr, "%s: %s is a directory\n", Cmd, name);
111 exit(3);
115 void *xmalloc(int size)
117 void *rv = malloc(size);
118 if (size && !rv) {
119 char *msg = "Failed to allocate memory - aborting\n";
120 write(2, msg, strlen(msg));
121 exit(3);
123 return rv;
126 void printword(FILE *f, struct elmnt e)
128 if (e.start[0])
129 fprintf(f, "%.*s", e.plen + e.prefix,
130 e.start - e.prefix);
131 else {
132 int a, b, c;
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
149 * We need one file
151 struct stream f, flist[3];
153 if (argc == 0) {
154 fprintf(stderr,
155 "%s: no file given for --extract\n", Cmd);
156 return 2;
158 if (argc > 1) {
159 fprintf(stderr,
160 "%s: only give one file for --extract\n", Cmd);
161 return 2;
163 f = load_file(argv[0]);
164 if (f.body == NULL) {
165 fprintf(stderr,
166 "%s: cannot load file '%s' - %s\n", Cmd,
167 argv[0], strerror(errno));
168 return 2;
170 if (ispatch) {
171 if (split_patch(f, &flist[0], &flist[1]) == 0) {
172 fprintf(stderr,
173 "%s: No chunk found in patch: %s\n", Cmd,
174 argv[0]);
175 return 0;
177 } else {
178 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
179 fprintf(stderr,
180 "%s: merge file %s looks bad.\n", Cmd,
181 argv[0]);
182 return 2;
185 if (flist[which-'1'].body == NULL) {
186 fprintf(stderr,
187 "%s: %s has no -%c component.\n", Cmd,
188 argv[0], which);
189 return 2;
190 } else {
191 if (write(1, flist[which-'1'].body,
192 flist[which-'1'].len)
193 != flist[which-'1'].len)
194 return 2;
196 return 0;
199 static int do_diff_lines(struct file fl[2], struct csl *csl)
201 int a, b;
202 int exit_status = 0;
203 a = b = 0;
204 while (a < fl[0].elcnt || b < fl[1].elcnt) {
205 if (a < csl->a) {
206 if (fl[0].list[a].start[0]) {
207 printf("-");
208 printword(stdout,
209 fl[0].list[a]);
211 a++;
212 exit_status++;
213 } else if (b < csl->b) {
214 if (fl[1].list[b].start[0]) {
215 printf("+");
216 printword(stdout,
217 fl[1].list[b]);
219 b++;
220 exit_status++;
221 } else {
222 if (fl[0].list[a].start[0] == '\0')
223 printsep(fl[0].list[a],
224 fl[1].list[b]);
225 else {
226 printf(" ");
227 printword(stdout,
228 fl[0].list[a]);
230 a++;
231 b++;
232 if (a >= csl->a+csl->len)
233 csl++;
236 return exit_status;
239 static int do_diff_words(struct file fl[2], struct csl *csl)
241 int a, b;
242 int exit_status = 0;
243 int sol = 1; /* start of line */
244 a = b = 0;
245 while (a < fl[0].elcnt || b < fl[1].elcnt) {
246 if (a < csl->a) {
247 exit_status++;
248 if (sol) {
249 int a1;
250 /* If we remove a
251 * whole line, output
252 * +line else clear
253 * sol and retry */
254 sol = 0;
255 for (a1 = a; a1 < csl->a ; a1++)
256 if (ends_line(fl[0].list[a1])) {
257 sol = 1;
258 break;
260 if (sol) {
261 printf("-");
262 for (; a < csl->a ; a++) {
263 printword(stdout, fl[0].list[a]);
264 if (ends_line(fl[0].list[a])) {
265 a++;
266 break;
269 } else
270 printf("|");
272 if (!sol) {
273 printf("<<<--");
274 do {
275 if (sol)
276 printf("|");
277 printword(stdout, fl[0].list[a]);
278 sol = ends_line(fl[0].list[a]);
279 a++;
280 } while (a < csl->a);
281 printf("%s-->>>", sol ? "|" : "");
282 sol = 0;
284 } else if (b < csl->b) {
285 exit_status++;
286 if (sol) {
287 int b1;
288 sol = 0;
289 for (b1 = b; b1 < csl->b; b1++)
290 if (ends_line(fl[1].list[b1])) {
291 sol = 1;
292 break;
294 if (sol) {
295 printf("+");
296 for (; b < csl->b ; b++) {
297 printword(stdout, fl[1].list[b]);
298 if (ends_line(fl[1].list[b])) {
299 b++;
300 break;
303 } else
304 printf("|");
306 if (!sol) {
307 printf("<<<++");
308 do {
309 if (sol)
310 printf("|");
311 printword(stdout, fl[1].list[b]);
312 sol = ends_line(fl[1].list[b]);
313 b++;
314 } while (b < csl->b);
315 printf("%s++>>>", sol ? "|" : "");
316 sol = 0;
318 } else {
319 if (sol) {
320 int a1;
321 sol = 0;
322 for (a1 = a; a1 < csl->a+csl->len; a1++)
323 if (ends_line(fl[0].list[a1]))
324 sol = 1;
325 if (sol) {
326 if (fl[0].list[a].start[0]) {
327 printf(" ");
328 for (; a < csl->a+csl->len; a++, b++) {
329 printword(stdout, fl[0].list[a]);
330 if (ends_line(fl[0].list[a])) {
331 a++, b++;
332 break;
335 } else {
336 printsep(fl[0].list[a], fl[1].list[b]);
337 a++; b++;
339 } else
340 printf("|");
342 if (!sol) {
343 printword(stdout, fl[0].list[a]);
344 if (ends_line(fl[0].list[a]))
345 sol = 1;
346 a++;
347 b++;
349 if (a >= csl->a+csl->len)
350 csl++;
353 return exit_status;
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;
362 int exit_status = 0;
363 struct file fl[2];
364 struct csl *csl;
366 switch (argc) {
367 case 0:
368 fprintf(stderr, "%s: no file given for --diff\n", Cmd);
369 return 2;
370 case 1:
371 f = load_file(argv[0]);
372 if (f.body == NULL) {
373 fprintf(stderr,
374 "%s: cannot load file '%s' - %s\n", Cmd,
375 argv[0], strerror(errno));
376 return 2;
378 chunks1 = chunks2 =
379 split_patch(f, &flist[0], &flist[1]);
380 if (!flist[0].body || !flist[1].body) {
381 fprintf(stderr,
382 "%s: couldn't parse patch %s\n", Cmd,
383 argv[0]);
384 return 2;
386 break;
387 case 2:
388 flist[0] = load_file(argv[0]);
389 if (flist[0].body == NULL) {
390 fprintf(stderr,
391 "%s: cannot load file '%s' - %s\n", Cmd,
392 argv[0], strerror(errno));
393 return 2;
395 if (ispatch) {
396 f = load_file(argv[1]);
397 if (f.body == NULL) {
398 fprintf(stderr,
399 "%s: cannot load patch '%s' - %s\n", Cmd,
400 argv[1], strerror(errno));
401 return 2;
403 if (which == '2')
404 chunks2 = chunks3 =
405 split_patch(f, &flist[2],
406 &flist[1]);
407 else
408 chunks2 = chunks3 =
409 split_patch(f, &flist[1],
410 &flist[2]);
412 } else
413 flist[1] = load_file(argv[1]);
414 if (flist[1].body == NULL) {
415 fprintf(stderr,
416 "%s: cannot load file '%s' - %s\n", Cmd,
417 argv[1], strerror(errno));
418 return 2;
420 break;
421 default:
422 fprintf(stderr,
423 "%s: too many files given for --diff\n", Cmd);
424 return 2;
426 if (reverse) {
427 f = flist[1];
428 flist[1] = flist[2];
429 flist[2] = f;
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);
435 else
436 csl = diff_patch(fl[0], fl[1]);
437 if ((obj & ByMask) == ByLine) {
438 if (!chunks1)
439 printf("@@ -1,%d +1,%d @@\n",
440 fl[0].elcnt, fl[1].elcnt);
441 exit_status = do_diff_lines(fl, csl);
442 } else {
443 if (!chunks1) {
444 /* count lines in each file */
445 int l1, l2, i;
446 l1 = l2 = 0;
447 for (i = 0 ; i < fl[0].elcnt ; i++)
448 if (ends_line(fl[0].list[i]))
449 l1++;
450 for (i = 0 ; i < fl[1].elcnt ; i++)
451 if (ends_line(fl[1].list[i]))
452 l2++;
453 printf("@@ -1,%d +1,%d @@\n", l1, l2);
455 exit_status = do_diff_words(fl, csl);
457 return exit_status;
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,
463 int quiet)
465 /* merge three files, A B C, so changed between B and C get made to A
467 struct stream f, flist[3];
468 struct file fl[3];
469 int i;
470 int chunks1 = 0, chunks2 = 0, chunks3 = 0;
471 char *replacename = NULL, *orignew = NULL;
472 struct csl *csl1, *csl2;
473 struct ci ci;
474 FILE *outfile = stdout;
476 switch (argc) {
477 case 0:
478 fprintf(stderr, "%s: no files given for --merge\n", Cmd);
479 return 2;
480 case 3:
481 case 2:
482 case 1:
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",
487 Cmd,
488 argv[i], strerror(errno));
489 return 2;
492 break;
493 default:
494 fprintf(stderr, "%s: too many files given for --merge\n",
495 Cmd);
496 return 2;
498 switch (argc) {
499 case 1: /* a merge file */
500 f = flist[0];
501 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
502 fprintf(stderr, "%s: merge file %s looks bad.\n",
503 Cmd,
504 argv[0]);
505 return 2;
507 break;
508 case 2: /* a file and a patch */
509 f = flist[1];
510 chunks2 = chunks3 = split_patch(f, &flist[1], &flist[2]);
511 break;
512 case 3: /* three separate files */
513 break;
515 if (reverse) {
516 f = flist[1];
517 flist[1] = flist[2];
518 flist[2] = f;
521 for (i = 0; i < 3; i++) {
522 if (flist[i].body == NULL) {
523 fprintf(stderr, "%s: file %d missing\n", Cmd, i);
524 return 2;
527 if (outfilename) {
528 outfile = fopen(outfilename, "w");
529 if (!outfile) {
530 fprintf(stderr, "%s: could not create %s\n",
531 Cmd, outfilename);
532 return 2;
534 } else if (replace) {
535 int fd;
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 ||
542 errno != ENOENT) {
543 fprintf(stderr, "%s: %s already exists\n",
544 Cmd,
545 orignew);
546 return 2;
548 strcat(replacename, "XXXXXX");
549 fd = mkstemp(replacename);
550 if (fd == -1) {
551 fprintf(stderr,
552 "%s: could not create temporary file for %s\n",
553 Cmd,
554 replacename);
555 return 2;
557 outfile = fdopen(fd, "w");
560 if (obj == 'l')
561 blanks |= ByLine;
562 else
563 blanks |= ByWord;
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);
570 else
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)
579 fprintf(stderr,
580 "%d unresolved conflict%s found\n",
581 ci.conflicts,
582 ci.conflicts == 1 ? "" : "s");
583 if (!quiet && ci.ignored)
584 fprintf(stderr,
585 "%d already-applied change%s ignored\n",
586 ci.ignored,
587 ci.ignored == 1 ? "" : "s");
589 if (outfilename)
590 fclose(outfile);
591 else if (replace) {
592 fclose(outfile);
593 if (rename(argv[0], orignew) == 0 &&
594 rename(replacename, argv[0]) == 0)
595 /* all ok */;
596 else {
597 fprintf(stderr,
598 "%s: failed to move new file into place.\n",
599 Cmd);
600 return 2;
603 if (show_wiggles)
604 return ci.conflicts + ci.wiggles > 0;
605 else
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,
612 int quiet)
614 FILE *f;
615 char *filename;
616 struct plist *pl;
617 int num_patches;
618 int rv = 0;
619 int i;
621 if (!replace) {
622 fprintf(stderr,
623 "%s: -p in merge mode requires -r\n",
624 Cmd);
625 return 2;
627 if (argc != 1) {
628 fprintf(stderr,
629 "%s: -p in merge mode requires exactly one file\n",
630 Cmd);
631 return 2;
633 filename = argv[0];
634 f = fopen(filename, "r");
635 if (!f) {
636 fprintf(stderr, "%s: cannot open %s\n",
637 Cmd, filename);
638 return 2;
640 check_dir(filename, fileno(f));
641 pl = parse_patch(f, NULL, &num_patches);
642 fclose(f);
643 if (set_prefix(pl, num_patches, strip) == 0) {
644 fprintf(stderr, "%s: aborting\n", Cmd);
645 return 2;
647 for (i = 0; i < num_patches; i++) {
648 char *name;
649 char *av[2];
650 asprintf(&name, "_wiggle_:%d:%d:%s",
651 pl[i].start, pl[i].end, filename);
652 av[0] = pl[i].file;
653 av[1] = name;
654 rv |= do_merge(2, av, obj, blanks, reverse, 1, NULL, ignore,
655 show_wiggles, quiet);
657 return rv;
660 int main(int argc, char *argv[])
662 int opt;
663 int option_index;
664 int mode = 0;
665 int obj = 0;
666 int replace = 0;
667 int which = 0;
668 int ispatch = 0;
669 int reverse = 0;
670 int verbose = 0, quiet = 0;
671 int strip = -1;
672 int exit_status = 0;
673 int ignore = 1;
674 int show_wiggles = 0;
675 char *helpmsg;
676 char *trace;
677 char *outfile = NULL;
678 int selftest = 0;
679 int ignore_blanks = 0;
681 trace = getenv("WIGGLE_TRACE");
682 if (trace && *trace)
683 do_trace = 1;
685 while ((opt = getopt_long(argc, argv,
686 short_options, long_options,
687 &option_index)) != -1)
688 switch (opt) {
689 case 'h':
690 helpmsg = Help;
691 switch (mode) {
692 case 'x':
693 helpmsg = HelpExtract;
694 break;
695 case 'd':
696 helpmsg = HelpDiff;
697 break;
698 case 'm':
699 helpmsg = HelpMerge;
700 break;
701 case 'B':
702 helpmsg = HelpBrowse;
703 break;
705 fputs(helpmsg, stderr);
706 exit(0);
708 case 'V':
709 fputs(Version, stderr);
710 exit(0);
711 case ':':
712 case '?':
713 default:
714 fputs(Usage, stderr);
715 exit(2);
717 case 'B':
718 case 'x':
719 case 'd':
720 case 'm':
721 if (mode == 0) {
722 mode = opt;
723 continue;
725 if (mode == 'B' && opt == 'd') {
726 /* Browse/diff mode */
727 ispatch = 2;
728 continue;
730 fprintf(stderr,
731 "%s: mode is '%c' - cannot set to '%c'\n",
732 Cmd, mode, opt);
733 exit(2);
735 case 'w':
736 case 'l':
737 if (obj == 0 || obj == opt) {
738 obj = opt;
739 continue;
741 fprintf(stderr,
742 "%s: cannot select both words and lines.\n", Cmd);
743 exit(2);
745 case 'r':
746 replace = 1;
747 continue;
748 case 'o':
749 outfile = optarg;
750 replace = 1;
751 continue;
752 case 'R':
753 reverse = 1;
754 continue;
756 case 'b':
757 ignore_blanks = IgnoreBlanks;
758 continue;
760 case 'i':
761 ignore = 0;
762 continue;
763 case 'W':
764 show_wiggles = 2;
765 ignore = 0;
766 continue;
767 case REPORT_WIGGLES:
768 show_wiggles = 1;
769 continue;
771 case '1':
772 case '2':
773 case '3':
774 if (which == 0 || which == opt) {
775 which = opt;
776 continue;
778 fprintf(stderr,
779 "%s: can only select one of -1, -2, -3\n", Cmd);
780 exit(2);
782 case 'p': /* 'patch' or 'strip' */
783 if (optarg)
784 strip = atol(optarg);
785 ispatch = 1;
786 continue;
788 case 'v':
789 verbose++;
790 continue;
791 case 'q':
792 quiet = 1;
793 continue;
795 case SELF_TEST:
796 selftest = 1;
797 continue;
799 if (!mode)
800 mode = 'm';
802 if (mode == 'B') {
803 vpatch(argc-optind, argv+optind, ispatch,
804 strip, reverse, replace, outfile, selftest,
805 ignore_blanks);
806 /* should not return */
807 exit(1);
810 if (obj && mode == 'x') {
811 fprintf(stderr,
812 "%s: cannot specify --line or --word with --extract\n",
813 Cmd);
814 exit(2);
816 if (mode != 'm' && !obj)
817 obj = 'w';
818 if (ispatch && outfile) {
819 fprintf(stderr, "%s: --output incompatible with --patch\n",
820 Cmd);
821 exit(2);
823 if (replace && mode != 'm') {
824 fprintf(stderr,
825 "%s: --replace or --output only allowed with --merge\n", Cmd);
826 exit(2);
828 if (mode == 'x' && !which) {
829 fprintf(stderr,
830 "%s: must specify -1, -2 or -3 with --extract\n", Cmd);
831 exit(2);
833 if (mode != 'x' && mode != 'd' && which) {
834 fprintf(stderr,
835 "%s: -1, -2 or -3 only allowed with --extract or --diff\n",
836 Cmd);
837 exit(2);
840 if (ispatch && which == '3') {
841 fprintf(stderr,
842 "%s: cannot extract -3 from a patch.\n", Cmd);
843 exit(2);
846 switch (mode) {
847 case 'x':
848 exit_status = extract(argc-optind, argv+optind, ispatch, which);
849 break;
850 case 'd':
851 exit_status = do_diff(argc-optind, argv+optind,
852 (obj == 'l' ? ByLine : ByWord)
853 | ignore_blanks,
854 ispatch, which, reverse);
855 break;
856 case 'm':
857 if (ispatch)
858 exit_status = multi_merge(argc-optind,
859 argv+optind, obj,
860 ignore_blanks,
861 reverse, ignore,
862 show_wiggles,
863 replace, strip,
864 quiet);
865 else
866 exit_status = do_merge(
867 argc-optind, argv+optind,
868 obj, ignore_blanks, reverse, replace,
869 outfile,
870 ignore, show_wiggles, quiet);
871 break;
873 exit(exit_status);