Still more updates to 'p'
[wiggle.git] / wiggle.c
blob2bbb90fac5c77aae57ec72f8670dbc8f29eabd57
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2003 Neil Brown <neilb@cse.unsw.edu.au>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 * Author: Neil Brown
22 * Email: <neilb@cse.unsw.edu.au>
23 * Paper: Neil Brown
24 * School of Computer Science and Engineering
25 * The University of New South Wales
26 * Sydney, 2052
27 * Australia
31 * Wiggle is a tool for working with patches that don't quite apply properly.
32 * It provides functionality similar to 'diff' and 'merge' but can
33 * work at the level of individual words thus allowing the merging of
34 * two changes that affect the same line, but not the same parts of that line.
36 * Wiggle can also read patch and merge files. Unlike 'merge' it does not
37 * need to be given three separate files, but can be given a file and a patch
38 * and it will extract the pieces of the two two other files that it needs from
39 * the patch.
41 * Wiggle performs one of three core function:
42 * --extract -x extract part of a patch or merge file
43 * --diff -d report differences between two files
44 * --merge -m merge the changes between two files into a third file
46 * To perform these, wiggle requires 1, 2, or 3 input streams respectively.
47 * I can get there from individual files, from a diff (unified or context) or
48 * from a merge file.
50 * For merge:
51 * If one file is given, it is a merge file (output of 'merge').
52 * If two files are given, the second is assumed to be a patch, the first is a normal file.
53 * If three files are given, they are taken to be normal files.
55 * For diff:
56 * If one file is given, it is a patch
57 * If two files are given, they are normal files.
59 * For extract:
60 * Only one file can be given. -p indicates it is a patch, 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 calculate and merging is performed on lines (-l) or words (-w).
65 * In the case of -w, an initial diff is computed based on non-trivial words.
66 * i.e. spaces are ignored
67 * This diff is computed from the ends of the file and is used to find a suitable
68 * starting point and range. Then a more precise diff is computed over that
69 * restricted range
71 * Other options available are:
72 * --replace -r replace first file with result of merge.
73 * --help -h provide help
74 * --version -v version
76 * Defaults are --merge --words
80 #include "wiggle.h"
81 #include <errno.h>
82 #include <string.h>
83 #include <fcntl.h>
84 #include <unistd.h>
85 #include <stdlib.h>
86 #include <ctype.h>
88 void die()
90 fprintf(stderr,"wiggle: fatal error\n");
91 abort();
92 exit(3);
95 void printword(FILE *f, struct elmnt e)
97 if (e.start[0])
98 fprintf(f, "%.*s", e.len, e.start);
99 else {
100 int a,b,c;
101 sscanf(e.start+1, "%d %d %d", &a, &b, &c);
102 fprintf(f, "*** %d,%d **** %d\n", b,c,a);
106 static void printsep(struct elmnt e1, struct elmnt e2)
108 int a,b,c,d,e,f;
109 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
110 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
111 printf("@@ -%d,%d +%d,%d @@\n", b,c,e,f);
115 /* Remove any entries from the common-sublist that are
116 * just spaces, tabs, or newlines
118 void cleanlist(struct file a, struct file b, struct csl *list)
120 struct csl *new = list;
122 while (list->len) {
123 int i;
124 int ap;
125 for( ap = list->a; ap< list->a+list->len; ap++) {
126 for (i=0; i<a.list[ap].len; i++) {
127 char c = a.list[ap].start[i];
128 if (isalnum(c))
129 break;
131 if (i != a.list[ap].len)
132 break;
134 if (ap == list->a+list->len)
135 list++;
136 else
137 *new++ = *list++;
139 *new = *list;
142 int main(int argc, char *argv[])
144 int opt;
145 int option_index;
146 int mode = 0;
147 int obj = 0;
148 int replace = 0;
149 char *replacename=NULL, *orignew=NULL;
150 int which = 0;
151 int ispatch = 0;
152 int reverse = 0;
153 int verbose=0, quiet=0;
154 int i;
155 int chunks1=0, chunks2=0, chunks3=0;
156 int exit_status = 0;
157 FILE *outfile = stdout;
158 char *helpmsg;
160 struct stream f, flist[3];
161 struct file fl[3];
162 struct csl *csl1, *csl2;
164 while ((opt = getopt_long(argc, argv,
165 short_options, long_options,
166 &option_index)) != -1)
167 switch(opt) {
168 case 'h':
169 helpmsg = Help;
170 switch(mode) {
171 case 'x': helpmsg = HelpExtract; break;
172 case 'd': helpmsg = HelpDiff; break;
173 case 'm': helpmsg = HelpMerge; break;
175 fputs(helpmsg, stderr);
176 exit(0);
178 case 'V':
179 fputs(Version, stderr);
180 exit(0);
181 case ':':
182 case '?':
183 default:
184 fputs(Usage, stderr);
185 exit(2);
187 case 'x':
188 case 'd':
189 case 'm':
190 if (mode ==0){
191 mode = opt;
192 continue;
194 fprintf(stderr, "wiggle: mode is '%c' - cannot set to '%c'\n",
195 mode, opt);
196 exit(2);
198 case 'w':
199 case 'l':
200 if (obj == 0 || obj == opt) {
201 obj = opt;
202 continue;
204 fprintf(stderr, "wiggle: cannot select both words and lines.\n");
205 exit(2);
207 case 'r':
208 replace = 1;
209 continue;
210 case 'R':
211 reverse = 1;
212 continue;
214 case '1':
215 case '2':
216 case '3':
217 if (which == 0 || which == opt) {
218 which = opt;
219 continue;
221 fprintf(stderr, "wiggle: can only select one of -1, -2, -3\n");
222 exit(2);
224 case 'p':
225 ispatch = 1;
226 continue;
228 case 'v': verbose++; continue;
229 case 'q': quiet=1 ; continue;
231 if (!mode)
232 mode = 'm';
234 if (obj && mode == 'x') {
235 fprintf(stderr,"wiggle: cannot specify --line or --word with --extract\n");
236 exit(2);
238 if (mode != 'm' && !obj) obj = 'w';
239 if (replace && mode != 'm') {
240 fprintf(stderr, "wiggle: --replace only allowed with --merge\n");
241 exit(2);
243 if (mode == 'x' && !which) {
244 fprintf(stderr, "wiggle: must specify -1, -2 or -3 with --extract\n");
245 exit(2);
247 if (mode != 'x' && mode != 'd' && which) {
248 fprintf(stderr, "wiggle: -1, -2 or -3 only allowed with --extract or --diff\n");
249 exit(2);
251 if (ispatch && (mode != 'x' && mode != 'd')) {
252 fprintf(stderr, "wiggle: --patch only allowed with --extract or --diff\n");
253 exit(2);
255 if (ispatch && which == '3') {
256 fprintf(stderr, "wiggle: cannot extract -3 from a patch.\n");
257 exit(2);
260 switch(mode) {
261 case 'x':
262 /* extract a branch of a diff or diff3 or merge output
263 * We need one file
265 if (optind == argc) {
266 fprintf(stderr, "wiggle: no file given for --extract\n");
267 exit(2);
269 if (optind < argc-1) {
270 fprintf(stderr, "wiggle: only give one file for --extract\n");
271 exit(2);
273 f = load_file(argv[optind]);
274 if (f.body==NULL) {
275 fprintf(stderr, "wiggle: cannot load file '%s' - %s\n",
276 argv[optind], strerror(errno));
277 exit(2);
279 if (ispatch)
280 chunks1 = chunks2 = split_patch(f, &flist[0], &flist[1]);
281 else {
282 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
283 fprintf(stderr, "wiggle: merge file %s looks bad.\n",
284 argv[optind]);
285 exit(2);
288 if (flist[which-'1'].body == NULL) {
289 fprintf(stderr, "wiggle: %s has no -%c component.\n",
290 argv[optind], which);
291 exit(2);
292 } else {
293 write(1, flist[which-'1'].body, flist[which-'1'].len);
296 break;
297 case 'd':
298 /* create a diff (line or char) of two streams */
299 switch (argc-optind) {
300 case 0:
301 fprintf(stderr, "wiggle: no file given for --diff\n");
302 exit(2);
303 case 1:
304 f = load_file(argv[optind]);
305 if (f.body == NULL) {
306 fprintf(stderr, "wiggle: cannot load file '%s' - %s\n",
307 argv[optind], strerror(errno));
308 exit(2);
310 chunks1 = chunks2 = split_patch(f, &flist[0], &flist[1]);
311 if (!flist[0].body || !flist[1].body) {
312 fprintf(stderr, "wiggle: couldn't parse patch %s\n",
313 argv[optind]);
314 exit(2);
316 break;
317 case 2:
318 flist[0] = load_file(argv[optind]);
319 if (flist[0].body == NULL) {
320 fprintf(stderr, "wiggle: cannot load file '%s' - %s\n",
321 argv[optind], strerror(errno));
322 exit(2);
324 if (ispatch) {
325 f = load_file(argv[optind+1]);
326 if (f.body == NULL) {
327 fprintf(stderr, "wiggle: cannot load patch '%s' - %s\n",
328 argv[optind], strerror(errno));
329 exit(2);
331 if (which == '2')
332 chunks2 = chunks3 = split_patch(f, &flist[2], &flist[1]);
333 else
334 chunks2 = chunks3 = split_patch(f, &flist[1], &flist[2]);
336 } else
337 flist[1] = load_file(argv[optind+1]);
338 if (flist[1].body == NULL) {
339 fprintf(stderr, "wiggle: cannot load file '%s' - %s\n",
340 argv[optind+1], strerror(errno));
341 exit(2);
343 break;
344 default:
345 fprintf(stderr, "wiggle: too many files given for --diff\n");
346 exit(2);
348 if (reverse) {
349 f=flist[1];
350 flist[1] = flist[2];
351 flist[2]= f;
353 if (obj == 'l') {
354 int a,b;
355 fl[0] = split_stream(flist[0], ByLine, 0);
356 fl[1] = split_stream(flist[1], ByLine, 0);
357 if (chunks2 && ! chunks1)
358 csl1 = pdiff(fl[0], fl[1], chunks2);
359 else
360 csl1 = diff(fl[0], fl[1]);
362 if (!chunks1)
363 printf("@@ -1,%d +1,%d @@\n", fl[0].elcnt, fl[1].elcnt);
364 a = b = 0;
365 while (a<fl[0].elcnt || b < fl[1].elcnt) {
366 if (a < csl1->a) {
367 if (fl[0].list[a].start[0]) {
368 printf("-");
369 printword(stdout, fl[0].list[a]);
371 a++;
372 exit_status++;
373 } else if (b < csl1->b) {
374 if (fl[1].list[b].start[0]) {
375 printf("+");
376 printword(stdout, fl[1].list[b]);
378 b++;
379 exit_status++;
380 } else {
381 if (fl[0].list[a].start[0] == '\0')
382 printsep(fl[0].list[a], fl[1].list[b]);
383 else {
384 printf(" ");
385 printword(stdout, fl[0].list[a]);
387 a++;
388 b++;
389 if (a >= csl1->a+csl1->len)
390 csl1++;
393 } else {
394 int a,b;
395 int sol = 1; /* start of line */
396 fl[0] = split_stream(flist[0], ByWord, 0);
397 fl[1] = split_stream(flist[1], ByWord, 0);
398 if (chunks2 && !chunks1)
399 csl1 = pdiff(fl[0], fl[1], chunks2);
400 else
401 csl1 = diff(fl[0], fl[1]);
403 if (!chunks1) {
404 /* count lines in each file */
405 int l1, l2, i;
406 l1=l2=0;
407 for (i=0;i<fl[0].elcnt;i++)
408 if (ends_line(fl[0].list[i]))
409 l1++;
410 for (i=0;i<fl[1].elcnt;i++)
411 if (ends_line(fl[1].list[i]))
412 l2++;
413 printf("@@ -1,%d +1,%d @@\n", l1,l2);
415 a = b = 0;
416 while (a < fl[0].elcnt || b < fl[1].elcnt) {
417 if (a < csl1->a) {
418 exit_status++;
419 if (sol) {
420 int a1;
421 /* If we remove a whole line, output +line
422 * else clear sol and retry */
423 sol = 0;
424 for (a1=a; a1<csl1->a;a1++)
425 if (ends_line(fl[0].list[a1])) {
426 sol=1;
427 break;
429 if (sol) {
430 printf("-");
431 for (; a<csl1->a; a++) {
432 printword(stdout, fl[0].list[a]);
433 if (ends_line(fl[0].list[a])) {
434 a++;
435 break;
438 } else printf("|");
440 if (!sol) {
441 printf("<<<--");
442 do {
443 if (sol) printf("|");
444 printword(stdout, fl[0].list[a]);
445 sol = ends_line(fl[0].list[a]);
446 a++;
447 } while (a < csl1->a);
448 printf("%s-->>>", sol?"|":"");
449 sol=0;
451 } else if (b < csl1->b) {
452 exit_status++;
453 if (sol) {
454 int b1;
455 sol = 0;
456 for (b1=b; b1<csl1->b;b1++)
457 if(ends_line(fl[1].list[b1])) {
458 sol=1;
459 break;
461 if (sol) {
462 printf("+");
463 for(; b<csl1->b ; b++) {
464 printword(stdout, fl[1].list[b]);
465 if(ends_line(fl[1].list[b])) {
466 b++;
467 break;
470 } else printf("|");
472 if (!sol) {
473 printf("<<<++");
474 do {
475 if (sol) printf("|");
476 printword(stdout, fl[1].list[b]);
477 sol = ends_line(fl[1].list[b]);
478 b++;
479 } while (b < csl1->b);
480 printf("%s++>>>",sol?"|":"");
481 sol=0;
483 } else {
484 if (sol) {
485 int a1;
486 sol = 0;
487 for (a1=a; a1<csl1->a+csl1->len; a1++)
488 if (ends_line(fl[0].list[a1]))
489 sol=1;
490 if (sol) {
491 if (fl[0].list[a].start[0]) {
492 printf(" ");
493 for(; a<csl1->a+csl1->len; a++,b++) {
494 printword(stdout, fl[0].list[a]);
495 if (ends_line(fl[0].list[a])) {
496 a++,b++;
497 break;
500 } else {
501 printsep(fl[0].list[a], fl[1].list[b]);
502 a++; b++;
505 else printf("|");
507 if (!sol) {
508 printword(stdout, fl[0].list[a]);
509 if (ends_line(fl[0].list[a]))
510 sol=1;
511 a++;
512 b++;
514 if (a >= csl1->a+csl1->len)
515 csl1++;
520 break;
521 case 'm':
522 /* merge three files, A B C, so changed between B and C get made to A
524 switch (argc-optind) {
525 case 0:
526 fprintf(stderr, "wiggle: no files given for --merge\n");
527 exit(2);
528 case 3:
529 case 2:
530 case 1:
531 for (i=0; i< argc-optind; i++) {
532 flist[i] = load_file(argv[optind+i]);
533 if (flist[i].body == NULL) {
534 fprintf(stderr, "wiggle: cannot load file '%s' - %s\n",
535 argv[optind+i], strerror(errno));
536 exit(2);
539 break;
540 default:
541 fprintf(stderr, "wiggle: too many files given for --merge\n");
542 exit(2);
544 switch(argc-optind) {
545 case 1: /* a merge file */
546 f = flist[0];
547 if (!split_merge(f, &flist[0], &flist[1], &flist[2])) {
548 fprintf(stderr,"wiggle: merge file %s looks bad.\n",
549 argv[optind]);
550 exit(2);
552 break;
553 case 2: /* a file and a patch */
554 f = flist[1];
555 chunks2 = chunks3 = split_patch(f, &flist[1], &flist[2]);
556 break;
557 case 3: /* three separate files */
558 break;
560 if (reverse) {
561 f=flist[1];
562 flist[1] = flist[2];
563 flist[2]= f;
566 for (i=0; i<3; i++) {
567 if (flist[i].body==NULL) {
568 fprintf(stderr, "wiggle: file %d missing\n", i);
569 exit(2);
572 if (replace) {
573 int fd;
574 replacename = malloc(strlen(argv[optind])+ 20);
575 if (!replacename) die();
576 orignew = malloc(strlen(argv[optind])+20);
577 if (!orignew) die();
578 strcpy(replacename, argv[optind]);
579 strcpy(orignew, argv[optind]);
580 strcat(orignew, ".porig");
581 if (open(orignew, O_RDONLY) >= 0 ||
582 errno != ENOENT) {
583 fprintf(stderr,"wiggle: %s already exists\n",
584 orignew);
585 exit(2);
587 strcat(replacename,"XXXXXX");
588 fd = mkstemp(replacename);
589 if (fd == -1) {
590 fprintf(stderr,"wiggle: could not create temporary file for %s\n",
591 replacename);
592 exit(2);
594 outfile = fdopen(fd, "w");
598 if (obj == 'l') {
599 fl[0] = split_stream(flist[0], ByLine, 0);
600 fl[1] = split_stream(flist[1], ByLine, 0);
601 fl[2] = split_stream(flist[2], ByLine, 0);
602 } else {
603 fl[0] = split_stream(flist[0], ByWord, 0);
604 fl[1] = split_stream(flist[1], ByWord, 0);
605 fl[2] = split_stream(flist[2], ByWord, 0);
607 if (chunks2 && !chunks1)
608 csl1 = pdiff(fl[0], fl[1], chunks2);
609 else
610 csl1 = diff(fl[0], fl[1]);
611 csl2 = diff(fl[1], fl[2]);
613 #if 0
614 cleanlist(fl[0],fl[1],csl1);
615 cleanlist(fl[1],fl[2],csl2);
616 #endif
619 struct ci ci;
621 ci = print_merge(outfile, &fl[0], &fl[1], &fl[2],
622 csl1, csl2, obj=='w');
623 if (!quiet && ci.conflicts)
624 fprintf(stderr, "%d unresolved conflict%s found\n", ci.conflicts, ci.conflicts==1?"":"s");
625 if (!quiet && ci.ignored)
626 fprintf(stderr, "%d already-applied change%s ignored\n", ci.ignored, ci.ignored==1?"":"s");
627 exit_status = (ci.conflicts > 0);
629 if (replace) {
630 fclose(outfile);
631 if (rename(argv[optind], orignew) ==0 &&
632 rename(replacename, argv[optind]) ==0)
633 /* all ok */;
634 else {
635 fprintf(stderr, "wiggle: failed to move new file into place.\n");
636 exit(2);
639 break;
642 exit(exit_status);