Get merge2 to pass all the tests...
[wiggle/upstream.git] / vpatch.c
blob7cbadeef7e51a647dcdd321cbe41b770a1492f19
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 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 * vpatch - visual front end for wiggle
33 * "files" display, lists all files with statistics
34 * - can hide various lines including subdirectories
35 * and files without wiggles or conflicts
36 * "diff" display shows merged file with different parts
37 * in different colours
38 * - untouched are pale A_DIM
39 * - matched/remaining are regular A_NORMAL
40 * - matched/removed are red/underlined A_UNDERLINE
41 * - unmatched in file are A_STANDOUT
42 * - unmatched in patch are A_STANDOUT|A_UNDERLINE ???
43 * - inserted are inverse/green ?? A_REVERSE
45 * The window can be split horiz or vert and two different
46 * views displayed. They will have different parts missing
48 * So a display of NORMAL, underline, standout|underline reverse
49 * should show a normal patch.
53 #include "wiggle.h"
54 #include <malloc.h>
55 #include <string.h>
56 #include <curses.h>
57 #include <unistd.h>
58 #include <stdlib.h>
59 #include <signal.h>
60 #include <fcntl.h>
62 #define assert(x) do { if (!(x)) abort(); } while (0)
64 struct plist {
65 char *file;
66 unsigned int start, end;
67 int parent;
68 int next, prev, last;
69 int open;
70 int chunks, wiggles, conflicts;
71 int calced;
74 struct plist *patch_add_file(struct plist *pl, int *np, char *file,
75 unsigned int start, unsigned int end)
77 /* size of pl is 0, 16, n^2 */
78 int n = *np;
79 int asize;
81 while (*file == '/') file++; /* leading '/' are bad... */
83 /* printf("adding %s at %d: %u %u\n", file, n, start, end); */
84 if (n==0) asize = 0;
85 else if (n<=16) asize = 16;
86 else if ((n&(n-1))==0) asize = n;
87 else asize = n+1; /* not accurate, but not too large */
88 if (asize <= n) {
89 /* need to extend array */
90 struct plist *npl;
91 if (asize < 16) asize = 16;
92 else asize += asize;
93 npl = realloc(pl, asize * sizeof(struct plist));
94 if (!npl) {
95 fprintf(stderr, "malloc failed - skipping %s\n", file);
96 return pl;
98 pl = npl;
100 pl[n].file = file;
101 pl[n].start = start;
102 pl[n].end = end;
103 pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
104 pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
105 pl[n].open = 1;
106 pl[n].calced = 0;
107 *np = n+1;
108 return pl;
113 struct plist *parse_patch(FILE *f, FILE *of, int *np)
115 /* read a multi-file patch from 'f' and record relevant
116 * details in a plist.
117 * if 'of' >= 0, fd might not be seekable so we write
118 * to 'of' and use lseek on 'of' to determine position
120 struct plist *plist = NULL;
122 while (!feof(f)) {
123 /* first, find the start of a patch: "\n+++ "
124 * grab the file name and scan to the end of a line
126 char *target="\n+++ ";
127 char *target2="\n--- ";
128 char *pos = target;
129 int c;
130 char name[1024];
131 unsigned start, end;
133 while (*pos && (c=fgetc(f)) != EOF ) {
134 if (of) fputc(c, of);
135 if (c == *pos)
136 pos++;
137 else pos = target;
139 if (c == EOF)
140 break;
141 assert(c == ' ');
142 /* now read a file name */
143 pos = name;
144 while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
145 pos - name < 1023) {
146 *pos++ = c;
147 if (of) fputc(c, of);
149 *pos = 0;
150 if (c == EOF)
151 break;
152 if (of) fputc(c, of);
153 while (c != '\n' && (c=fgetc(f)) != EOF) {
154 if (of) fputc(c, of);
156 start = of ? ftell(of) : ftell(f);
158 if (c == EOF) break;
160 /* now skip to end - "\n--- " */
161 pos = target2+1;
163 while (*pos && (c=fgetc(f)) != EOF) {
164 if (of) fputc(c, of);
165 if (c == *pos)
166 pos++;
167 else pos = target2;
169 end = of ? ftell(of) : ftell(f);
170 if (pos > target2)
171 end -= (pos - target2) - 1;
172 plist = patch_add_file(plist, np,
173 strdup(name), start, end);
175 return plist;
177 #if 0
178 void die()
180 fprintf(stderr,"vpatch: fatal error\n");
181 abort();
182 exit(3);
184 #endif
186 static struct stream load_segment(FILE *f,
187 unsigned int start, unsigned int end)
189 struct stream s;
190 s.len = end - start;
191 s.body = malloc(s.len);
192 if (s.body) {
193 fseek(f, start, 0);
194 if (fread(s.body, 1, s.len, f) != s.len) {
195 free(s.body);
196 s.body = NULL;
198 } else
199 die();
200 return s;
204 void catch(int sig)
206 if (sig == SIGINT) {
207 signal(sig, catch);
208 return;
210 nocbreak();nl();endwin();
211 printf("Died on signal %d\n", sig);
212 exit(2);
215 int pl_cmp(const void *av, const void *bv)
217 const struct plist *a = av;
218 const struct plist *b = bv;
219 return strcmp(a->file, b->file);
222 int common_depth(char *a, char *b)
224 /* find number of patch segments that these two have
225 * in common
227 int depth = 0;
228 while(1) {
229 char *c;
230 int al, bl;
231 c = strchr(a, '/');
232 if (c) al = c-a; else al = strlen(a);
233 c = strchr(b, '/');
234 if (c) bl = c-b; else bl = strlen(b);
235 if (al == 0 || al != bl || strncmp(a,b,al) != 0)
236 return depth;
237 a+= al;
238 while (*a=='/') a++;
239 b+= bl;
240 while(*b=='/') b++;
242 depth++;
246 struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
248 /* any parent of file that is not a parent of curr
249 * needs to be added to pl
251 int d = common_depth(file, curr);
252 char *buf = curr;
253 while (d) {
254 char *c = strchr(file, '/');
255 int l;
256 if (c) l = c-file; else l = strlen(file);
257 file += l;
258 curr += l;
259 while (*file == '/') file++;
260 while (*curr == '/') curr++;
261 d--;
263 while (*file) {
264 if (curr > buf && curr[-1] != '/')
265 *curr++ = '/';
266 while (*file && *file != '/')
267 *curr++ = *file++;
268 while (*file == '/') *file++;
269 *curr = '\0';
270 if (*file)
271 pl = patch_add_file(pl, np, strdup(buf),
272 0, 0);
274 return pl;
277 struct plist *sort_patches(struct plist *pl, int *np)
279 /* sort the patches, add directory names, and re-sort */
280 char curr[1024];
281 char *prev;
282 int parents[100];
283 int prevnode[100];
284 int i, n;
285 qsort(pl, *np, sizeof(struct plist), pl_cmp);
286 curr[0] = 0;
287 n = *np;
288 for (i=0; i<n; i++)
289 pl = add_dir(pl, np, pl[i].file, curr);
291 qsort(pl, *np, sizeof(struct plist), pl_cmp);
293 /* array is now stable, so set up parent pointers */
294 n = *np;
295 curr[0] = 0;
296 prevnode[0] = -1;
297 prev = "";
298 for (i=0; i<n; i++) {
299 int d = common_depth(prev, pl[i].file);
300 if (d == 0)
301 pl[i].parent = -1;
302 else {
303 pl[i].parent = parents[d-1];
304 pl[pl[i].parent].last = i;
306 pl[i].prev = prevnode[d];
307 if (pl[i].prev > -1)
308 pl[pl[i].prev].next = i;
309 prev = pl[i].file;
310 parents[d] = i;
311 prevnode[d] = i;
312 prevnode[d+1] = -1;
314 return pl;
317 int get_strip(char *file)
319 int fd;
320 int strip = 0;
322 while (file && *file) {
323 fd = open(file, O_RDONLY);
324 if (fd >= 0) {
325 close(fd);
326 return strip;
328 strip++;
329 file = strchr(file, '/');
330 if (file)
331 while(*file == '/')
332 file++;
334 return -1;
337 int set_prefix(struct plist *pl, int n, int strip)
339 int i;
340 for(i=0; i<4 && i<n && strip < 0; i++)
341 strip = get_strip(pl[i].file);
343 if (strip < 0) {
344 fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
345 Cmd);
346 return 0;
348 for (i=0; i<n; i++) {
349 char *p = pl[i].file;
350 int j;
351 for (j=0; j<strip; j++) {
352 if (p) p = strchr(p,'/');
353 while (p && *p == '/') p++;
355 if (p == NULL) {
356 fprintf(stderr, "%s: cannot strip %d segments from %s\n",
357 Cmd, strip, pl[i].file);
358 return 0;
360 pl[i].file = p;
362 return 1;
366 int get_prev(int pos, struct plist *pl, int n)
368 if (pos == -1) return pos;
369 if (pl[pos].prev == -1)
370 return pl[pos].parent;
371 pos = pl[pos].prev;
372 while (pl[pos].open &&
373 pl[pos].last >= 0)
374 pos = pl[pos].last;
375 return pos;
378 int get_next(int pos, struct plist *pl, int n)
380 if (pos == -1) return pos;
381 if (pl[pos].open) {
382 if (pos +1 < n)
383 return pos+1;
384 else
385 return -1;
387 while (pos >= 0 && pl[pos].next == -1)
388 pos = pl[pos].parent;
389 if (pos >= 0)
390 pos = pl[pos].next;
391 return pos;
394 /* global attributes */
395 int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
397 void draw_one(int row, struct plist *pl, FILE *f, int reverse)
399 char hdr[10];
400 hdr[0] = 0;
402 if (pl == NULL) {
403 move(row,0);
404 clrtoeol();
405 return;
407 if (pl->calced == 0 && pl->end) {
408 /* better load the patch and count the chunks */
409 struct stream s1, s2;
410 struct stream s = load_segment(f, pl->start, pl->end);
411 struct stream sf = load_file(pl->file);
412 if (reverse)
413 pl->chunks = split_patch(s, &s2, &s1);
414 else
415 pl->chunks = split_patch(s, &s1, &s2);
417 if (sf.body == NULL) {
418 pl->wiggles = pl->conflicts = -1;
419 } else {
420 struct file ff, fp1, fp2;
421 struct csl *csl1, *csl2;
422 struct ci ci;
423 ff = split_stream(sf, ByWord, 0);
424 fp1 = split_stream(s1, ByWord, 0);
425 fp2 = split_stream(s2, ByWord, 0);
426 csl1 = pdiff(ff, fp1, pl->chunks);
427 csl2 = diff(fp1,fp2);
428 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0);
429 pl->wiggles = ci.wiggles;
430 pl->conflicts = ci.conflicts;
431 free(csl1);
432 free(csl2);
433 free(ff.list);
434 free(fp1.list);
435 free(fp2.list);
438 free(s1.body);
439 free(s2.body);
440 free(s.body);
441 pl->calced = 1;
443 if (pl->end == 0) {
444 strcpy(hdr, " ");
445 } else {
446 if (pl->chunks > 99)
447 strcpy(hdr, "XX");
448 else sprintf(hdr, "%2d", pl->chunks);
449 if (pl->wiggles > 99)
450 strcpy(hdr+2, " XX");
451 else sprintf(hdr+2, " %2d", pl->wiggles);
452 if (pl->conflicts > 99)
453 strcpy(hdr+5, " XX ");
454 else sprintf(hdr+5, " %2d ", pl->conflicts);
456 if (pl->end)
457 strcpy(hdr+9, "= ");
458 else if (pl->open)
459 strcpy(hdr+9, "+ ");
460 else strcpy(hdr+9, "- ");
462 mvaddstr(row, 0, hdr);
463 mvaddstr(row, 11, pl->file);
464 clrtoeol();
467 void addword(struct elmnt e)
469 addnstr(e.start, e.len);
471 void addsep(struct elmnt e1, struct elmnt e2)
473 int a,b,c,d,e,f;
474 char buf[50];
475 attron(a_sep);
476 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
477 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
478 sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c,e,f);
479 addstr(buf);
480 attroff(a_sep);
482 #define BEFORE 1
483 #define AFTER 2
484 #define ORIG 4
485 #define RESULT 8
487 struct pos {
488 int a,b,c;
490 struct elmnt prev_elmnt(struct pos *pos, int mode,
491 struct file f1, struct file f2, struct csl *csl)
493 while(1) {
494 int a1, b1;
495 if (pos->a > csl[pos->c].a) {
496 assert(pos->b > csl[pos->c].b);
497 pos->a--;
498 pos->b--;
499 return f1.list[pos->a];
501 b1=0;
502 if (pos->c) b1 = csl[pos->c-1].b+csl[pos->c-1].len;
503 if (pos->b > b1) {
504 pos->b--;
505 if (!(mode&AFTER))
506 continue;
507 return f2.list[pos->b];
509 a1=0;
510 if (pos->c) a1 = csl[pos->c-1].a+csl[pos->c-1].len;
511 if (pos->a > a1) {
512 pos->a--;
513 if (!(mode&BEFORE))
514 continue;
515 return f1.list[pos->a];
517 /* must be time to go to previous common segment */
518 pos->c--;
519 if (pos->c < 0) {
520 struct elmnt e;
521 e.start = NULL; e.len = 0;
522 return e;
527 struct elmnt next_elmnt(struct pos *pos, int mode, int *type,
528 struct file f1, struct file f2, struct csl *csl)
530 if (pos->c < 0) {
531 struct elmnt e;
532 e.start = NULL; e.len = 0;
533 return e;
535 while(1) {
536 int a1;
537 *type = a_delete;
538 if (pos->a < csl[pos->c].a) {
539 if (mode & BEFORE)
540 return f1.list[pos->a++];
541 pos->a++;
542 continue;
544 *type = a_added;
545 if (pos->b < csl[pos->c].b) {
546 if (mode & AFTER)
547 return f2.list[pos->b++];
548 pos->b++;
549 continue;
551 *type = a_common;
552 a1 = csl[pos->c].a + csl[pos->c].len;
553 if (pos->a < a1) {
554 pos->b++;
555 return f1.list[pos->a++];
557 if (csl[pos->c].len == 0) {
558 struct elmnt e;
559 e.start = NULL; e.len = 0;
560 pos->c = -1;
561 return e;
563 pos->c++;
567 void pos_sol(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
569 /* find the start-of-line before 'pos' considering 'mode' only.
571 if (pos->c < 0) return;
572 while(1) {
573 struct pos tpos = *pos;
574 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
575 if (e.start == NULL) {
576 return;
578 if (e.start[0] == '\n' || e.start[0] == 0)
579 return;
580 *pos = tpos;
584 void prev_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
586 /* find the start-of-line before 'pos' considering 'mode' only.
588 int eol=0;
589 if (pos->c < 0) return;
590 while(1) {
591 struct pos tpos = *pos;
592 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
593 if (e.start == NULL) {
594 pos->c = -1;
595 return;
597 if (e.start[0] == '\n' || e.start[0] == 0) {
598 if (eol == 1)
599 return;
600 eol=1;
602 *pos = tpos;
603 if (e.start[0] == 0) return;
608 void next_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
610 /* find the start-of-line after 'pos' considering 'mode' only.
612 int type;
613 if (pos->c < 0) return;
614 while(1) {
615 struct pos tpos = *pos;
616 struct elmnt e = next_elmnt(&tpos, mode, &type, f1, f2, csl);
617 if (e.start == NULL) {
618 pos->c = -1;
619 return;
621 *pos = tpos;
622 if (e.start[0] == '\n') {
623 return;
625 if (e.start[0] == 0)
626 return;
630 void draw_line(int i, struct pos pos, int mode,
631 struct file f1, struct file f2, struct csl *csl, int start, int len)
633 int col = 0;
634 int attr;
635 struct elmnt e1;
636 move(i, col);
637 while (1) {
638 e1 = next_elmnt(&pos, mode, &attr, f1, f2, csl);
639 if (e1.start == NULL) {
640 attr = a_void;
641 break;
643 if (e1.start[0] == '\0') {
644 int a,b,c;
645 int d,e,f;
646 struct elmnt e2 = f2.list[pos.b-1];
647 char buf[50];
648 attrset(a_sep);
649 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
650 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
651 sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c, e,f);
652 addstr(buf);
653 break;
654 } else {
655 unsigned char *c;
656 int l;
657 attrset(attr);
658 if (e1.start[0] == '\n') {
659 break;
661 c = (unsigned char *)e1.start;
662 l = e1.len;
663 while (l) {
664 if (*c >= ' ' && *c != 0x7f) {
665 /* normal width char */
666 if (col >= start && col < len+start)
667 mvaddch(i,col-start, *c);
668 col++;
669 } else if (*c == '\t') {
670 do {
671 if (col >= start && col < len+start)
672 mvaddch(i, col-start, ' ');
673 col++;
674 } while ((col&7)!= 0);
675 } else {
676 if (col>=start && col < len+start)
677 mvaddch(i, col-start, *c=='\n'?'@':'?');
678 col++;
680 c++;
681 l--;
685 bkgdset(attr);
686 clrtoeol();
687 bkgdset(A_NORMAL);
690 void diff_window(struct plist *p, FILE *f)
692 /* display just the diff, either 'before' or 'after' or all.
694 * In any case highlighting is the same
696 * Current pos is given as a,b,csl where a and b are
697 * before or in the common segment, and one is immediately
698 * after a newline.
699 * The current row is given by 'row'.
700 * Lines do not wrap, be horizontal scrolling is supported.
702 * We don't insist that the top line lies at or above the top
703 * of the screen, as that allows alignment of different views
704 * more easily.
706 struct stream s;
707 struct stream s1, s2;
708 struct file f1, f2;
709 struct csl *csl;
710 char buf[100];
711 int ch;
712 int row, rows, cols;
713 int start = 0;
714 int i;
715 int c;
716 int refresh = 2;
717 int mode = BEFORE|AFTER;
719 struct pos pos, tpos;
721 s = load_segment(f, p->start, p->end);
722 ch = split_patch(s, &s1, &s2);
725 f1 = split_stream(s1, ByWord, 0);
726 f2 = split_stream(s2, ByWord, 0);
728 csl = diff(f1, f2);
730 pos.a=0; pos.b=0; pos.c=0;
731 row = 1;
733 while(1) {
734 if (refresh == 2) {
735 clear();
736 sprintf(buf, "File: %s\n", p->file);
737 attrset(A_BOLD); mvaddstr(0,0,buf); clrtoeol(); attrset(A_NORMAL);
738 refresh = 1;
740 if (row < 1 || row >= rows)
741 refresh = 1;
742 if (refresh) {
743 refresh = 0;
744 getmaxyx(stdscr,rows,cols);
745 if (row < -3)
746 row = (rows+1)/2;
747 if (row < 1)
748 row = 1;
749 if (row >= rows+3)
750 row = (rows+1)/2;
751 if (row >= rows)
752 row = rows-1;
753 tpos = pos;
754 pos_sol(&tpos, mode, f1, f2, csl);
755 draw_line(row, tpos, mode, f1, f2, csl, start, cols);
756 for (i=row-1; i>=1; i--) {
757 prev_pos(&tpos, mode, f1,f2,csl);
758 draw_line(i, tpos, mode, f1, f2,csl, start,cols);
760 tpos = pos;
761 for (i=row+1; i<rows; i++) {
762 next_pos(&tpos, mode, f1, f2, csl);
763 draw_line(i, tpos, mode, f1, f2, csl, start,cols);
766 move(row,0);
767 c = getch();
768 switch(c) {
769 case 27:
770 case 'q':
771 return;
772 case 'L'-64:
773 refresh = 2;
774 break;
776 case 'j':
777 case 'n':
778 case 'N':
779 case 'N'-64:
780 case KEY_DOWN:
781 tpos = pos;
782 next_pos(&tpos, mode, f1, f2, csl);
783 if (tpos.c >= 0) {
784 pos = tpos;
785 row++;
787 break;
788 case 'k':
789 case 'p':
790 case 'P':
791 case 'P'-64:
792 case KEY_UP:
793 tpos = pos;
794 prev_pos(&tpos, mode, f1, f2, csl);
795 if (tpos.c >= 0) {
796 pos = tpos;
797 row--;
799 break;
801 case 'a':
802 mode = AFTER;
803 refresh = 1;
804 break;
805 case 'b':
806 mode = BEFORE;
807 refresh = 1;
808 break;
809 case 'x':
810 mode = AFTER|BEFORE;
811 refresh = 1;
812 break;
814 case 'h':
815 case KEY_LEFT:
816 if (start > 0) start--;
817 refresh = 1;
818 break;
819 case 'l':
820 case KEY_RIGHT:
821 if (start < cols) start++;
822 refresh = 1;
823 break;
828 struct mpos {
829 int m; /* merger index */
830 int s; /* stream 0,1,2 for a,b,c */
831 int o; /* offset in that stream */
834 int invalid(int s, enum mergetype type)
836 switch(type) {
837 case End: return 0;
838 case Unmatched: return s>0;
839 case Unchanged: return s>0;
840 case Extraneous: return s<2;
841 case Changed: return s==1;
842 case Conflict: return 0;
843 case AlreadyApplied: return 0;
845 return 0;
848 struct elmnt next_melmnt(struct mpos *pos,
849 struct file fm, struct file fb, struct file fa,
850 struct merge *m)
852 int l;
853 pos->o++;
854 while(1) {
855 switch(pos->s) {
856 case 0: l = m[pos->m].al; break;
857 case 1: l = m[pos->m].bl; break;
858 case 2: l = m[pos->m].cl; break;
860 if (pos->o >= l) {
861 pos->o = 0;
862 do {
863 pos->s++;
864 if (pos->s > 2) {
865 pos->s = 0;
866 pos->m++;
868 } while (invalid(pos->s, m[pos->m].type));
869 } else
870 break;
872 if (pos->m == -1 || m[pos->m].type == End) {
873 struct elmnt e;
874 e.start = NULL; e.len = 0;
875 return e;
877 switch(pos->s) {
878 default: /* keep compiler happy */
879 case 0: return fm.list[m[pos->m].a + pos->o];
880 case 1: return fb.list[m[pos->m].b + pos->o];
881 case 2: return fa.list[m[pos->m].c + pos->o];
885 struct elmnt prev_melmnt(struct mpos *pos,
886 struct file fm, struct file fb, struct file fa,
887 struct merge *m)
889 pos->o--;
890 while (pos->m >=0 && pos->o < 0) {
891 do {
892 pos->s--;
893 if (pos->s < 0) {
894 pos->s = 2;
895 pos->m--;
897 } while (pos->m >= 0 && invalid(pos->s, m[pos->m].type));
898 if (pos->m>=0) {
899 switch(pos->s) {
900 case 0: pos->o = m[pos->m].al-1; break;
901 case 1: pos->o = m[pos->m].bl-1; break;
902 case 2: pos->o = m[pos->m].cl-1; break;
906 if (pos->m < 0) {
907 struct elmnt e;
908 e.start = NULL; e.len = 0;
909 return e;
911 switch(pos->s) {
912 default: /* keep compiler happy */
913 case 0: return fm.list[m[pos->m].a + pos->o];
914 case 1: return fb.list[m[pos->m].b + pos->o];
915 case 2: return fa.list[m[pos->m].c + pos->o];
919 int visible(int mode, enum mergetype type, int stream)
921 #if 0
922 switch(side){
923 case 0: /* left - orig plus merge */
924 switch(type) {
925 case End: return A_NORMAL;
926 case Unmatched: return stream == 0 ? a_unmatched : -1;
927 case Unchanged: return stream == 0 ? a_common : -1;
928 case Extraneous: return -1;
929 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
930 case Conflict: return stream == 0 ? a_unmatched : -1;
931 case AlreadyApplied: return stream == 0 ? a_unmatched : -1;
933 break;
934 case 1: /* right - old plus new */
935 switch(type) {
936 case End: return A_NORMAL;
937 case Unmatched: return -1;
938 case Unchanged: return stream == 0 ? a_common : -1;
939 case Extraneous: return stream==2 ? a_extra : -1;
940 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
941 case Conflict: return stream==0?-1:stream==1?(a_delete|A_UNDERLINE):a_added;
942 case AlreadyApplied: return stream==0?-1:stream==1?a_delete:a_added;
945 return 1;
946 #endif
947 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
948 switch(type) {
949 case End: /* The END is always visible */
950 return A_NORMAL;
951 case Unmatched: /* Visible in ORIG and RESULT */
952 if (mode & (ORIG|RESULT))
953 return a_unmatched;
954 break;
955 case Unchanged: /* visible everywhere, but only show stream 0 */
956 if (stream == 0) return a_common;
957 break;
958 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
959 if ((mode & (BEFORE|AFTER))
960 && stream == 2)
961 return a_extra;
962 break;
963 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
964 if (stream == 0 &&
965 (mode & (ORIG|BEFORE)))
966 return a_delete;
967 if (stream == 2 &&
968 (mode & (RESULT|AFTER)))
969 return a_added;
970 break;
971 case Conflict:
972 switch(stream) {
973 case 0:
974 if (mode & (ORIG|RESULT))
975 return a_unmatched | A_REVERSE;
976 break;
977 case 1:
978 if (mode & BEFORE)
979 return a_extra | A_UNDERLINE;
980 break;
981 case 2:
982 if (mode & (AFTER|RESULT))
983 return a_added | A_UNDERLINE;
984 break;
986 break;
987 case AlreadyApplied:
988 switch(stream) {
989 case 0:
990 if (mode & (ORIG|RESULT))
991 return a_already;
992 break;
993 case 1:
994 if (mode & BEFORE)
995 return a_delete | A_UNDERLINE;
996 break;
997 case 2:
998 if (mode & AFTER)
999 return a_added | A_UNDERLINE;
1000 break;
1002 break;
1004 return -1;
1009 void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1010 struct merge *m, int mode)
1012 if (pos->m < 0 || m[pos->m].type == End)
1013 return;
1014 while (1) {
1015 struct elmnt e = next_melmnt(pos, fm,fb,fa,m);
1016 if (e.start == NULL)
1017 return;
1018 if (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0)
1019 return;
1023 void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1024 struct merge *m, int mode)
1026 if (pos->m < 0)
1027 return;
1028 while(1) {
1029 struct elmnt e = prev_melmnt(pos, fm,fb,fa,m);
1030 if (e.start == NULL ||
1031 (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0))
1032 return;
1037 void blank(int row, int start, int cols, int attr)
1039 attrset(attr);
1040 move(row,start);
1041 while (cols-- > 0)
1042 addch(' ');
1045 void draw_mside(int mode, int row, int offset, int start, int cols,
1046 struct file fm, struct file fb, struct file fa, struct merge *m,
1047 struct mpos pos)
1049 /* find previous visible newline, or start of file */
1050 struct elmnt e;
1051 int col = 0;
1052 do {
1053 e = prev_melmnt(&pos, fm,fb,fa,m);
1054 } while (e.start != NULL &&
1055 (!ends_mline(e) || visible(mode, m[pos.m].type, pos.s)==-1));
1057 while (1) {
1058 unsigned char *c;
1059 int l;
1060 e = next_melmnt(&pos, fm,fb,fa,m);
1061 if (e.start == NULL ||
1062 (ends_mline(e) && visible(mode, m[pos.m].type, pos.s) != -1)) {
1063 if (col < start) col = start;
1064 if (e.start && e.start[0] == 0) {
1065 attrset(visible(mode, m[pos.m].type, pos.s));
1066 mvaddstr(row, col-start+offset, "SEP");
1067 col += 3;
1069 blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.m].type, pos.s):A_NORMAL );
1070 return;
1072 if (visible(mode, m[pos.m].type, pos.s) == -1) {
1073 continue;
1075 if (e.start[0] == 0)
1076 continue;
1077 attrset(visible(mode, m[pos.m].type, pos.s));
1078 c = (unsigned char *)e.start;
1079 l = e.len;
1080 while(l) {
1081 if (*c >= ' ' && *c != 0x7f) {
1082 if (col >= start && col < start+cols)
1083 mvaddch(row, col-start+offset, *c);
1084 col++;
1085 } else if (*c == '\t') {
1086 do {
1087 if (col >= start && col < start+cols)
1088 mvaddch(row, col-start+offset, ' ');
1089 col++;
1090 } while ((col&7)!= 0);
1091 } else {
1092 if (col >= start && col < start+cols)
1093 mvaddch(row, col-start+offset, '?');
1094 col++;
1096 c++;
1097 l--;
1102 void draw_mline(int row, struct mpos pos,
1103 struct file fm, struct file fb, struct file fa,
1104 struct merge *m,
1105 int start, int cols, int mode)
1108 * Draw the left and right images of this line
1109 * One side might be a_blank depending on the
1110 * visibility of this newline
1112 int lcols, rcols;
1113 lcols = (cols-1)/2;
1114 rcols = cols - lcols - 1;
1116 attrset(A_STANDOUT);
1117 mvaddch(row, lcols, '|');
1118 if (visible(mode&(ORIG|RESULT), m[pos.m].type, pos.s) >= 0)
1119 /* visible on left */
1120 draw_mside(mode&(ORIG|RESULT), row, 0, start, lcols,
1121 fm,fb,fa,m, pos);
1122 else
1123 blank(row, 0, lcols, a_void);
1125 if (visible(mode&(BEFORE|AFTER), m[pos.m].type, pos.s) >= 0)
1126 /* visible on right */
1127 draw_mside(mode&(BEFORE|AFTER), row, lcols+1, start, rcols,
1128 fm,fb,fa,m, pos);
1129 else
1130 blank(row, lcols+1, rcols, a_void);
1133 extern void cleanlist(struct file a, struct file b, struct csl *list);
1135 void merge_window(struct plist *p, FILE *f, int reverse)
1137 /* display the merge in two side-by-side
1138 * panes.
1139 * left side shows diff between original and new
1140 * right side shows the requested patch
1142 * Unmatched: c_unmatched - left only
1143 * Unchanged: c_normal - left and right
1144 * Extraneous: c_extra - right only
1145 * Changed-a: c_changed - left and right
1146 * Changed-c: c_new - left and right
1147 * AlreadyApplied-b: c_old - right only
1148 * AlreadyApplied-c: c_applied - left and right
1149 * Conflict-a: ?? left only
1150 * Conflict-b: ?? left and right
1151 * Conflict-c: ??
1153 * A Conflict is displayed as the original in the
1154 * left side, and the highlighted diff in the right.
1156 * Newlines are the key to display.
1157 * 'pos' is always a newline (or eof).
1158 * For each side that this newline is visible one, we
1159 * rewind the previous newline visible on this side, and
1160 * the display the stuff inbetween
1162 * A 'position' is an offset in the merger, a stream
1163 * choice (a,b,c - some aren't relevant) and an offset in
1164 * that stream
1167 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1168 struct file fm, fb, fa;
1169 struct csl *csl1, *csl2;
1170 struct ci ci;
1171 char buf[100];
1172 int ch;
1173 int refresh = 2;
1174 int rows,cols;
1175 int i, c;
1176 int mode = ORIG|RESULT | BEFORE|AFTER;
1178 int row,start = 0;
1179 struct mpos pos, tpos;
1181 sp = load_segment(f, p->start, p->end);
1182 if (reverse)
1183 ch = split_patch(sp, &sa, &sb);
1184 else
1185 ch = split_patch(sp, &sb, &sa);
1187 sm = load_file(p->file);
1188 fm = split_stream(sm, ByWord, 0);
1189 fb = split_stream(sb, ByWord, 0);
1190 fa = split_stream(sa, ByWord, 0);
1192 csl1 = pdiff(fm, fb, ch);
1193 csl2 = diff(fb, fa);
1194 cleanlist(fm, fb, csl1);
1195 cleanlist(fb, fa, csl2);
1197 ci = make_merger(fm, fb, fa, csl1, csl2, 0);
1199 row = 1;
1200 pos.m = 0; /* merge node */
1201 pos.s = 0; /* stream number */
1202 pos.o = -1; /* offset */
1203 next_mline(&pos, fm,fb,fa,ci.merger, mode);
1205 while(1) {
1206 if (refresh == 2) {
1207 clear();
1208 sprintf(buf, "File: %s%s\n", p->file,reverse?" - reversed":"");
1209 attrset(A_BOLD); mvaddstr(0,0,buf);
1210 clrtoeol();
1211 attrset(A_NORMAL);
1212 refresh = 1;
1214 if (row < 1 || rows >= rows)
1215 refresh = 1;
1216 if (refresh) {
1217 refresh = 0;
1218 getmaxyx(stdscr, rows, cols);
1219 if (row < -3)
1220 row = (rows-1)/2+1;
1221 if (row < 1)
1222 row = 1;
1223 if (row > rows+3)
1224 row = (rows-1)/2+1;
1225 if (row >= rows)
1226 row = rows-1;
1227 tpos = pos;
1228 draw_mline(row,tpos,fm,fb,fa,ci.merger,start,cols, mode);
1229 for (i=row-1; i>=1; i--) {
1230 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1231 draw_mline(i,tpos,fm,fb,fa,ci.merger,start,cols, mode);
1233 tpos = pos;
1234 for (i=row+1; i<rows; i++) {
1235 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1236 draw_mline(i,tpos,fm,fb,fa,ci.merger,start,cols, mode);
1239 move(row,0);
1240 c = getch();
1241 switch(c) {
1242 case 27:
1243 case 'q':
1244 return;
1245 case 'L'-64:
1246 refresh = 2;
1247 break;
1250 case 'j':
1251 case 'n':
1252 case 'N'-64:
1253 case KEY_DOWN:
1254 tpos = pos;
1255 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1256 if (ci.merger[tpos.m].type != End) {
1257 pos = tpos;
1258 row++;
1260 break;
1261 case 'N':
1262 /* Next 'patch' */
1263 while (pos.m >= 0 && ci.merger[pos.m].type == Unmatched)
1264 next_mline(&pos,fm,fb,fa,ci.merger, mode);
1265 row = -10;
1266 break;
1267 case 'k':
1268 case 'p':
1269 case 'P':
1270 case 'P'-64:
1271 case KEY_UP:
1272 tpos = pos;
1273 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1274 if (tpos.m >= 0) {
1275 pos = tpos;
1276 row--;
1278 break;
1280 case 'a': /* 'after' view in patch window */
1281 if (mode & AFTER)
1282 mode &= ~BEFORE;
1283 else
1284 mode |= AFTER;
1285 refresh = 1;
1286 break;
1287 case 'b': /* 'before' view in patch window */
1288 if (mode & BEFORE)
1289 mode &= ~AFTER;
1290 else
1291 mode |= BEFORE;
1292 refresh = 1;
1293 break;
1294 case 'o': /* 'original' view in the merge window */
1295 if (mode & ORIG)
1296 mode &= ~RESULT;
1297 else
1298 mode |= ORIG;
1299 refresh = 1;
1300 break;
1301 case 'r': /* the 'result' view in the merge window */
1302 if (mode & RESULT)
1303 mode &= ~ORIG;
1304 else
1305 mode |= RESULT;
1306 refresh = 1;
1307 break;
1309 case 'h':
1310 case KEY_LEFT:
1311 if (start > 0) start--;
1312 refresh = 1;
1313 break;
1314 case 'l':
1315 case KEY_RIGHT:
1316 if (start < cols) start++;
1317 refresh = 1;
1318 break;
1323 void main_window(struct plist *pl, int n, FILE *f, int reverse)
1325 /* The main window lists all files together with summary information:
1326 * number of chunks, number of wiggles, number of conflicts.
1327 * The list is scrollable
1328 * When a entry is 'selected', we switch to the 'file' window
1329 * The list can be condensed by removing files with no conflict
1330 * or no wiggles, or removing subdirectories
1332 * We record which file in the list is 'current', and which
1333 * screen line it is on. We try to keep things stable while
1334 * moving.
1336 * Counts are printed before the name using at most 2 digits.
1337 * Numbers greater than 99 are XX
1338 * Ch Wi Co File
1339 * 27 5 1 drivers/md/md.c
1341 * A directory show the sum in all children.
1343 * Commands:
1344 * select: enter, space, mouseclick
1345 * on file, go to file window
1346 * on directory, toggle open
1347 * up: k, p, control-p uparrow
1348 * Move to previous open object
1349 * down: j, n, control-n, downarrow
1350 * Move to next open object
1353 int pos=0; /* position in file */
1354 int row=1; /* position on screen */
1355 int rows; /* size of screen in rows */
1356 int cols;
1357 int tpos, i;
1358 int refresh = 2;
1359 int c=0;
1361 while(1) {
1362 if (refresh == 2) {
1363 clear(); attrset(0);
1364 attron(A_BOLD);
1365 mvaddstr(0,0,"Ch Wi Co Patched Files");
1366 move(2,0);
1367 attroff(A_BOLD);
1368 refresh = 1;
1370 if (row <1 || row >= rows)
1371 refresh = 1;
1372 if (refresh) {
1373 refresh = 0;
1374 getmaxyx(stdscr, rows, cols);
1375 if (row >= rows +3)
1376 row = (rows+1)/2;
1377 if (row >= rows)
1378 row = rows-1;
1379 tpos = pos;
1380 for (i=row; i>1; i--) {
1381 tpos = get_prev(tpos, pl, n);
1382 if (tpos == -1) {
1383 row = row - i + 1;
1384 break;
1387 /* Ok, row and pos could be trustworthy now */
1388 tpos = pos;
1389 for (i=row; i>=1; i--) {
1390 draw_one(i, &pl[tpos], f, reverse);
1391 tpos = get_prev(tpos, pl, n);
1393 tpos = pos;
1394 for (i=row+1; i<rows; i++) {
1395 tpos = get_next(tpos, pl, n);
1396 if (tpos >= 0)
1397 draw_one(i, &pl[tpos], f, reverse);
1398 else
1399 draw_one(i, NULL, f, reverse);
1402 {char bb[20]; sprintf(bb,"%d", c); mvaddstr(0, 70, bb); clrtoeol();}
1403 move(row, 9);
1404 c = getch();
1405 switch(c) {
1406 case 'j':
1407 case 'n':
1408 case 'N':
1409 case 'N'-64:
1410 case KEY_DOWN:
1411 tpos = get_next(pos, pl, n);
1412 if (tpos >= 0) {
1413 pos = tpos;
1414 row++;
1416 break;
1417 case 'k':
1418 case 'p':
1419 case 'P':
1420 case 'P'-64:
1421 case KEY_UP:
1422 tpos = get_prev(pos, pl, n);
1423 if (tpos >= 0) {
1424 pos = tpos;
1425 row--;
1427 break;
1429 case ' ':
1430 case 13:
1431 if (pl[pos].end == 0) {
1432 pl[pos].open = ! pl[pos].open;
1433 refresh = 1;
1434 } else {
1435 /* diff_window(&pl[pos], f); */
1436 merge_window(&pl[pos],f,reverse);
1437 refresh = 2;
1439 break;
1440 case 27: /* escape */
1441 case 'q':
1442 return;
1444 case KEY_RESIZE:
1445 refresh = 2;
1446 break;
1452 int vpatch(int argc, char *argv[], int strip, int reverse, int replace)
1454 /* NOT argv[0] is first arg... */
1455 int n = 0;
1456 FILE *f = NULL;
1457 FILE *in = stdin;
1458 struct plist *pl;
1460 if (argc >=1) {
1461 in = fopen(argv[0], "r");
1462 if (!in) {
1463 printf("No %s\n", argv[0]);
1464 exit(1);
1466 } else {
1467 in = stdin;
1469 if (lseek(fileno(in),0L, 1) == -1) {
1470 f = tmpfile();
1471 if (!f) {
1472 fprintf(stderr, "Cannot create a temp file\n");
1473 exit(1);
1477 pl = parse_patch(in, f, &n);
1479 if (set_prefix(pl, n, strip) == 0) {
1480 fprintf(stderr, "%s: aborting\n", Cmd);
1481 exit(2);
1483 pl = sort_patches(pl, &n);
1485 if (f) {
1486 if (fileno(in) == 0) {
1487 close(0);
1488 dup(2);
1489 } else
1490 fclose(in);
1491 in = f;
1493 #if 0
1494 int i;
1495 for (i=0; i<n ; i++) {
1496 printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
1498 exit(0);
1499 #endif
1500 signal(SIGINT, catch);
1501 signal(SIGQUIT, catch);
1502 signal(SIGTERM, catch);
1503 signal(SIGBUS, catch);
1504 signal(SIGSEGV, catch);
1506 initscr(); cbreak(); noecho();
1507 start_color();
1508 use_default_colors();
1509 if (!has_colors()) {
1510 a_delete = A_UNDERLINE;
1511 a_added = A_BOLD;
1512 a_common = A_NORMAL;
1513 a_sep = A_STANDOUT;
1514 a_already = A_STANDOUT;
1515 } else {
1516 init_pair(1, COLOR_WHITE, COLOR_RED);
1517 a_delete = COLOR_PAIR(1);
1518 init_pair(2, COLOR_WHITE, COLOR_BLUE);
1519 a_added = COLOR_PAIR(2);
1520 a_common = A_NORMAL;
1521 init_pair(3, COLOR_WHITE, COLOR_GREEN);
1522 a_sep = COLOR_PAIR(3);
1523 init_pair(4, COLOR_WHITE, COLOR_YELLOW);
1524 a_void = COLOR_PAIR(4);
1525 init_pair(5, COLOR_BLUE, COLOR_WHITE);
1526 a_unmatched = COLOR_PAIR(5);
1527 init_pair(6, COLOR_CYAN, COLOR_WHITE);
1528 a_extra = COLOR_PAIR(6);
1530 init_pair(7, COLOR_BLACK, COLOR_CYAN);
1531 a_already = COLOR_PAIR(7);
1533 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
1534 mousemask(ALL_MOUSE_EVENTS, NULL);
1536 main_window(pl, n, in, reverse);
1538 nocbreak();nl();endwin();
1539 return 0;
1540 exit(0);
1542 #if 0
1543 WiggleVerbose=1 ~/work/wiggle/wiggle -mR fs/nfsd/nfs4callback.c .patches/removed/144NfsdV4-033 |less
1544 neilb@notabene:/home/src/mm$
1546 ~/work/wiggle/wiggle -BR .patches/removed/144NfsdV4-033
1549 #endif