Remove all trailing blanks.
[wiggle/upstream.git] / vpatch.c
blob77dc6c7de8edaae3175087971249fd072f9cace6
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
486 #define CHANGED 32 /* The RESULT is different to ORIG */
487 #define CHANGES 64 /* AFTER is different to BEFORE */
489 struct pos {
490 int a,b,c;
492 struct elmnt prev_elmnt(struct pos *pos, int mode,
493 struct file f1, struct file f2, struct csl *csl)
495 while(1) {
496 int a1, b1;
497 if (pos->a > csl[pos->c].a) {
498 assert(pos->b > csl[pos->c].b);
499 pos->a--;
500 pos->b--;
501 return f1.list[pos->a];
503 b1=0;
504 if (pos->c) b1 = csl[pos->c-1].b+csl[pos->c-1].len;
505 if (pos->b > b1) {
506 pos->b--;
507 if (!(mode&AFTER))
508 continue;
509 return f2.list[pos->b];
511 a1=0;
512 if (pos->c) a1 = csl[pos->c-1].a+csl[pos->c-1].len;
513 if (pos->a > a1) {
514 pos->a--;
515 if (!(mode&BEFORE))
516 continue;
517 return f1.list[pos->a];
519 /* must be time to go to previous common segment */
520 pos->c--;
521 if (pos->c < 0) {
522 struct elmnt e;
523 e.start = NULL; e.len = 0;
524 return e;
529 struct elmnt next_elmnt(struct pos *pos, int mode, int *type,
530 struct file f1, struct file f2, struct csl *csl)
532 if (pos->c < 0) {
533 struct elmnt e;
534 e.start = NULL; e.len = 0;
535 return e;
537 while(1) {
538 int a1;
539 *type = a_delete;
540 if (pos->a < csl[pos->c].a) {
541 if (mode & BEFORE)
542 return f1.list[pos->a++];
543 pos->a++;
544 continue;
546 *type = a_added;
547 if (pos->b < csl[pos->c].b) {
548 if (mode & AFTER)
549 return f2.list[pos->b++];
550 pos->b++;
551 continue;
553 *type = a_common;
554 a1 = csl[pos->c].a + csl[pos->c].len;
555 if (pos->a < a1) {
556 pos->b++;
557 return f1.list[pos->a++];
559 if (csl[pos->c].len == 0) {
560 struct elmnt e;
561 e.start = NULL; e.len = 0;
562 pos->c = -1;
563 return e;
565 pos->c++;
569 void pos_sol(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
571 /* find the start-of-line before 'pos' considering 'mode' only.
573 if (pos->c < 0) return;
574 while(1) {
575 struct pos tpos = *pos;
576 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
577 if (e.start == NULL) {
578 return;
580 if (e.start[0] == '\n' || e.start[0] == 0)
581 return;
582 *pos = tpos;
586 void prev_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
588 /* find the start-of-line before 'pos' considering 'mode' only.
590 int eol=0;
591 if (pos->c < 0) return;
592 while(1) {
593 struct pos tpos = *pos;
594 struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
595 if (e.start == NULL) {
596 pos->c = -1;
597 return;
599 if (e.start[0] == '\n' || e.start[0] == 0) {
600 if (eol == 1)
601 return;
602 eol=1;
604 *pos = tpos;
605 if (e.start[0] == 0) return;
610 void next_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
612 /* find the start-of-line after 'pos' considering 'mode' only.
614 int type;
615 if (pos->c < 0) return;
616 while(1) {
617 struct pos tpos = *pos;
618 struct elmnt e = next_elmnt(&tpos, mode, &type, f1, f2, csl);
619 if (e.start == NULL) {
620 pos->c = -1;
621 return;
623 *pos = tpos;
624 if (e.start[0] == '\n') {
625 return;
627 if (e.start[0] == 0)
628 return;
632 void draw_line(int i, struct pos pos, int mode,
633 struct file f1, struct file f2, struct csl *csl, int start, int len)
635 int col = 0;
636 int attr;
637 struct elmnt e1;
638 move(i, col);
639 while (1) {
640 e1 = next_elmnt(&pos, mode, &attr, f1, f2, csl);
641 if (e1.start == NULL) {
642 attr = a_void;
643 break;
645 if (e1.start[0] == '\0') {
646 int a,b,c;
647 int d,e,f;
648 struct elmnt e2 = f2.list[pos.b-1];
649 char buf[50];
650 attrset(a_sep);
651 sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
652 sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
653 sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c, e,f);
654 addstr(buf);
655 break;
656 } else {
657 unsigned char *c;
658 int l;
659 attrset(attr);
660 if (e1.start[0] == '\n') {
661 break;
663 c = (unsigned char *)e1.start;
664 l = e1.len;
665 while (l) {
666 if (*c >= ' ' && *c != 0x7f) {
667 /* normal width char */
668 if (col >= start && col < len+start)
669 mvaddch(i,col-start, *c);
670 col++;
671 } else if (*c == '\t') {
672 do {
673 if (col >= start && col < len+start)
674 mvaddch(i, col-start, ' ');
675 col++;
676 } while ((col&7)!= 0);
677 } else {
678 if (col>=start && col < len+start)
679 mvaddch(i, col-start, *c=='\n'?'@':'?');
680 col++;
682 c++;
683 l--;
687 bkgdset(attr);
688 clrtoeol();
689 bkgdset(A_NORMAL);
692 void diff_window(struct plist *p, FILE *f)
694 /* display just the diff, either 'before' or 'after' or all.
696 * In any case highlighting is the same
698 * Current pos is given as a,b,csl where a and b are
699 * before or in the common segment, and one is immediately
700 * after a newline.
701 * The current row is given by 'row'.
702 * Lines do not wrap, be horizontal scrolling is supported.
704 * We don't insist that the top line lies at or above the top
705 * of the screen, as that allows alignment of different views
706 * more easily.
708 struct stream s;
709 struct stream s1, s2;
710 struct file f1, f2;
711 struct csl *csl;
712 char buf[100];
713 int ch;
714 int row, rows, cols;
715 int start = 0;
716 int i;
717 int c;
718 int refresh = 2;
719 int mode = BEFORE|AFTER;
721 struct pos pos, tpos;
723 s = load_segment(f, p->start, p->end);
724 ch = split_patch(s, &s1, &s2);
727 f1 = split_stream(s1, ByWord, 0);
728 f2 = split_stream(s2, ByWord, 0);
730 csl = diff(f1, f2);
732 pos.a=0; pos.b=0; pos.c=0;
733 row = 1;
735 while(1) {
736 if (refresh == 2) {
737 clear();
738 sprintf(buf, "File: %s\n", p->file);
739 attrset(A_BOLD); mvaddstr(0,0,buf); clrtoeol(); attrset(A_NORMAL);
740 refresh = 1;
742 if (row < 1 || row >= rows)
743 refresh = 1;
744 if (refresh) {
745 refresh = 0;
746 getmaxyx(stdscr,rows,cols);
747 if (row < -3)
748 row = (rows+1)/2;
749 if (row < 1)
750 row = 1;
751 if (row >= rows+3)
752 row = (rows+1)/2;
753 if (row >= rows)
754 row = rows-1;
755 tpos = pos;
756 pos_sol(&tpos, mode, f1, f2, csl);
757 draw_line(row, tpos, mode, f1, f2, csl, start, cols);
758 for (i=row-1; i>=1; i--) {
759 prev_pos(&tpos, mode, f1,f2,csl);
760 draw_line(i, tpos, mode, f1, f2,csl, start,cols);
762 tpos = pos;
763 for (i=row+1; i<rows; i++) {
764 next_pos(&tpos, mode, f1, f2, csl);
765 draw_line(i, tpos, mode, f1, f2, csl, start,cols);
768 move(row,0);
769 c = getch();
770 switch(c) {
771 case 27:
772 case 'q':
773 return;
774 case 'L'-64:
775 refresh = 2;
776 break;
778 case 'j':
779 case 'n':
780 case 'N':
781 case 'N'-64:
782 case KEY_DOWN:
783 tpos = pos;
784 next_pos(&tpos, mode, f1, f2, csl);
785 if (tpos.c >= 0) {
786 pos = tpos;
787 row++;
789 break;
790 case 'k':
791 case 'p':
792 case 'P':
793 case 'P'-64:
794 case KEY_UP:
795 tpos = pos;
796 prev_pos(&tpos, mode, f1, f2, csl);
797 if (tpos.c >= 0) {
798 pos = tpos;
799 row--;
801 break;
803 case 'a':
804 mode = AFTER;
805 refresh = 1;
806 break;
807 case 'b':
808 mode = BEFORE;
809 refresh = 1;
810 break;
811 case 'x':
812 mode = AFTER|BEFORE;
813 refresh = 1;
814 break;
816 case 'h':
817 case KEY_LEFT:
818 if (start > 0) start--;
819 refresh = 1;
820 break;
821 case 'l':
822 case KEY_RIGHT:
823 if (start < cols) start++;
824 refresh = 1;
825 break;
830 struct mpos {
831 int m; /* merger index */
832 int s; /* stream 0,1,2 for a,b,c */
833 int o; /* offset in that stream */
836 int invalid(int s, enum mergetype type)
838 switch(type) {
839 case End: return 0;
840 case Unmatched: return s>0;
841 case Unchanged: return s>0;
842 case Extraneous: return s<2;
843 case Changed: return s==1;
844 case Conflict: return 0;
845 case AlreadyApplied: return 0;
847 return 0;
850 struct elmnt next_melmnt(struct mpos *pos,
851 struct file fm, struct file fb, struct file fa,
852 struct merge *m)
854 int l;
855 pos->o++;
856 while(1) {
857 switch(pos->s) {
858 case 0: l = m[pos->m].al; break;
859 case 1: l = m[pos->m].bl; break;
860 case 2: l = m[pos->m].cl; break;
862 if (pos->o >= l) {
863 pos->o = 0;
864 do {
865 pos->s++;
866 if (pos->s > 2) {
867 pos->s = 0;
868 pos->m++;
870 } while (invalid(pos->s, m[pos->m].type));
871 } else
872 break;
874 if (pos->m == -1 || m[pos->m].type == End) {
875 struct elmnt e;
876 e.start = NULL; e.len = 0;
877 return e;
879 switch(pos->s) {
880 default: /* keep compiler happy */
881 case 0: return fm.list[m[pos->m].a + pos->o];
882 case 1: return fb.list[m[pos->m].b + pos->o];
883 case 2: return fa.list[m[pos->m].c + pos->o];
887 struct elmnt prev_melmnt(struct mpos *pos,
888 struct file fm, struct file fb, struct file fa,
889 struct merge *m)
891 pos->o--;
892 while (pos->m >=0 && pos->o < 0) {
893 do {
894 pos->s--;
895 if (pos->s < 0) {
896 pos->s = 2;
897 pos->m--;
899 } while (pos->m >= 0 && invalid(pos->s, m[pos->m].type));
900 if (pos->m>=0) {
901 switch(pos->s) {
902 case 0: pos->o = m[pos->m].al-1; break;
903 case 1: pos->o = m[pos->m].bl-1; break;
904 case 2: pos->o = m[pos->m].cl-1; break;
908 if (pos->m < 0) {
909 struct elmnt e;
910 e.start = NULL; e.len = 0;
911 return e;
913 switch(pos->s) {
914 default: /* keep compiler happy */
915 case 0: return fm.list[m[pos->m].a + pos->o];
916 case 1: return fb.list[m[pos->m].b + pos->o];
917 case 2: return fa.list[m[pos->m].c + pos->o];
921 int visible(int mode, enum mergetype type, int stream)
923 #if 0
924 switch(side){
925 case 0: /* left - orig plus merge */
926 switch(type) {
927 case End: return A_NORMAL;
928 case Unmatched: return stream == 0 ? a_unmatched : -1;
929 case Unchanged: return stream == 0 ? a_common : -1;
930 case Extraneous: return -1;
931 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
932 case Conflict: return stream == 0 ? a_unmatched : -1;
933 case AlreadyApplied: return stream == 0 ? a_unmatched : -1;
935 break;
936 case 1: /* right - old plus new */
937 switch(type) {
938 case End: return A_NORMAL;
939 case Unmatched: return -1;
940 case Unchanged: return stream == 0 ? a_common : -1;
941 case Extraneous: return stream==2 ? a_extra : -1;
942 case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
943 case Conflict: return stream==0?-1:stream==1?(a_delete|A_UNDERLINE):a_added;
944 case AlreadyApplied: return stream==0?-1:stream==1?a_delete:a_added;
947 return 1;
948 #endif
949 if (mode == 0) return -1;
950 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
951 switch(type) {
952 case End: /* The END is always visible */
953 return A_NORMAL;
954 case Unmatched: /* Visible in ORIG and RESULT */
955 if (mode & (ORIG|RESULT))
956 return a_unmatched;
957 break;
958 case Unchanged: /* visible everywhere, but only show stream 0 */
959 if (stream == 0) return a_common;
960 break;
961 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
962 if ((mode & (BEFORE|AFTER))
963 && stream == 2)
964 return a_extra;
965 break;
966 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
967 if (stream == 0 &&
968 (mode & (ORIG|BEFORE)))
969 return a_delete;
970 if (stream == 2 &&
971 (mode & (RESULT|AFTER)))
972 return a_added;
973 break;
974 case Conflict:
975 switch(stream) {
976 case 0:
977 if (mode & (ORIG|RESULT))
978 return a_unmatched | A_REVERSE;
979 break;
980 case 1:
981 if (mode & BEFORE)
982 return a_extra | A_UNDERLINE;
983 break;
984 case 2:
985 if (mode & (AFTER|RESULT))
986 return a_added | A_UNDERLINE;
987 break;
989 break;
990 case AlreadyApplied:
991 switch(stream) {
992 case 0:
993 if (mode & (ORIG|RESULT))
994 return a_already;
995 break;
996 case 1:
997 if (mode & BEFORE)
998 return a_delete | A_UNDERLINE;
999 break;
1000 case 2:
1001 if (mode & AFTER)
1002 return a_added | A_UNDERLINE;
1003 break;
1005 break;
1007 return -1;
1012 void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1013 struct merge *m, int mode)
1015 if (pos->m < 0 || m[pos->m].type == End)
1016 return;
1017 while (1) {
1018 struct elmnt e = next_melmnt(pos, fm,fb,fa,m);
1019 if (e.start == NULL)
1020 return;
1021 if (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0)
1022 return;
1026 void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
1027 struct merge *m, int mode)
1029 if (pos->m < 0)
1030 return;
1031 while(1) {
1032 struct elmnt e = prev_melmnt(pos, fm,fb,fa,m);
1033 if (e.start == NULL ||
1034 (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0))
1035 return;
1040 void blank(int row, int start, int cols, int attr)
1042 attrset(attr);
1043 move(row,start);
1044 while (cols-- > 0)
1045 addch(' ');
1048 /* Checkline determines how many screen lines are needed to display
1049 * a file line.
1050 * Options are:
1051 * - one line that is before/original
1052 * - one line that is after/result
1053 * - one line that is unchanged/unmatched/extraneous
1054 * - two lines, but only one on the left.
1055 * - two lines, one before and one after.
1056 * CLARIFY/CORRECT this.
1058 int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa,
1059 struct merge *m, int mode)
1061 int rv = 0;
1062 struct elmnt e;
1064 if (visible(ORIG, m[pos.m].type, pos.s) >= 0)
1065 rv |= ORIG;
1066 if (visible(RESULT, m[pos.m].type, pos.s) >= 0)
1067 rv |= RESULT;
1068 if (visible(BEFORE, m[pos.m].type, pos.s) >= 0)
1069 rv |= BEFORE;
1070 if (visible(AFTER, m[pos.m].type, pos.s) >= 0)
1071 rv |= AFTER;
1073 do {
1074 if (m[pos.m].type == Changed)
1075 rv |= CHANGED | CHANGES;
1076 else if ((m[pos.m].type == AlreadyApplied ||
1077 m[pos.m].type == Conflict))
1078 rv |= CHANGES;
1079 e = prev_melmnt(&pos, fm,fb,fa,m);
1080 } while (e.start != NULL &&
1081 (!ends_mline(e) || visible(mode, m[pos.m].type, pos.s)==-1));
1083 return rv & (mode | CHANGED | CHANGES);
1086 void draw_mside(int mode, int row, int offset, int start, int cols,
1087 struct file fm, struct file fb, struct file fa, struct merge *m,
1088 struct mpos pos)
1090 /* find previous visible newline, or start of file */
1091 struct elmnt e;
1092 int col = 0;
1093 do {
1094 e = prev_melmnt(&pos, fm,fb,fa,m);
1095 } while (e.start != NULL &&
1096 (!ends_mline(e) || visible(mode, m[pos.m].type, pos.s)==-1));
1098 while (1) {
1099 unsigned char *c;
1100 int l;
1101 e = next_melmnt(&pos, fm,fb,fa,m);
1102 if (e.start == NULL ||
1103 (ends_mline(e) && visible(mode, m[pos.m].type, pos.s) != -1)) {
1104 if (col < start) col = start;
1105 if (e.start && e.start[0] == 0) {
1106 attrset(visible(mode, m[pos.m].type, pos.s));
1107 mvaddstr(row, col-start+offset, "SEP");
1108 col += 3;
1110 blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.m].type, pos.s):A_NORMAL );
1111 return;
1113 if (visible(mode, m[pos.m].type, pos.s) == -1) {
1114 continue;
1116 if (e.start[0] == 0)
1117 continue;
1118 attrset(visible(mode, m[pos.m].type, pos.s));
1119 c = (unsigned char *)e.start;
1120 l = e.len;
1121 while(l) {
1122 if (*c >= ' ' && *c != 0x7f) {
1123 if (col >= start && col < start+cols)
1124 mvaddch(row, col-start+offset, *c);
1125 col++;
1126 } else if (*c == '\t') {
1127 do {
1128 if (col >= start && col < start+cols)
1129 mvaddch(row, col-start+offset, ' ');
1130 col++;
1131 } while ((col&7)!= 0);
1132 } else {
1133 if (col >= start && col < start+cols)
1134 mvaddch(row, col-start+offset, '?');
1135 col++;
1137 c++;
1138 l--;
1143 void draw_mline(int row, struct mpos pos,
1144 struct file fm, struct file fb, struct file fa,
1145 struct merge *m,
1146 int start, int cols, int mode)
1149 * Draw the left and right images of this line
1150 * One side might be a_blank depending on the
1151 * visibility of this newline
1152 * If Changed is found, return ORIG|RESULT | BEFORE|AFTER,
1153 * If AlreadyApplied or Conflict, return BEFORE|AFTER
1155 int lcols, rcols;
1156 lcols = (cols-1)/2-1;
1157 rcols = cols - lcols - 3;
1159 attrset(A_STANDOUT);
1160 mvaddch(row, lcols+1, '|');
1162 attrset(A_NORMAL);
1163 if (!(mode & CHANGES)) {
1164 mvaddch(row, 0, ' ');
1165 mvaddch(row, lcols+2, ' ');
1166 } else if (mode & (ORIG|BEFORE)) {
1167 mvaddch(row, 0, '-');
1168 mvaddch(row, lcols+2, '-');
1169 } else {
1170 mvaddch(row, 0, '+');
1171 mvaddch(row, lcols+2, '+');
1174 if (visible(mode&(ORIG|RESULT), m[pos.m].type, pos.s) >= 0)
1175 /* visible on left */
1176 draw_mside(mode&(ORIG|RESULT), row, 1, start, lcols,
1177 fm,fb,fa,m, pos);
1178 else
1179 blank(row, 0, lcols+1, a_void);
1181 if (visible(mode&(BEFORE|AFTER), m[pos.m].type, pos.s) >= 0)
1182 /* visible on right */
1183 draw_mside(mode&(BEFORE|AFTER), row, lcols+3, start, rcols,
1184 fm,fb,fa,m, pos);
1185 else
1186 blank(row, lcols+2, rcols+1, a_void);
1189 extern void cleanlist(struct file a, struct file b, struct csl *list);
1191 void merge_window(struct plist *p, FILE *f, int reverse)
1193 /* display the merge in two side-by-side
1194 * panes.
1195 * left side shows diff between original and new
1196 * right side shows the requested patch
1198 * Unmatched: c_unmatched - left only
1199 * Unchanged: c_normal - left and right
1200 * Extraneous: c_extra - right only
1201 * Changed-a: c_changed - left and right
1202 * Changed-c: c_new - left and right
1203 * AlreadyApplied-b: c_old - right only
1204 * AlreadyApplied-c: c_applied - left and right
1205 * Conflict-a: ?? left only
1206 * Conflict-b: ?? left and right
1207 * Conflict-c: ??
1209 * A Conflict is displayed as the original in the
1210 * left side, and the highlighted diff in the right.
1212 * Newlines are the key to display.
1213 * 'pos' is always a newline (or eof).
1214 * For each side that this newline is visible on, we
1215 * rewind the previous newline visible on this side, and
1216 * the display the stuff inbetween
1218 * A 'position' is an offset in the merger, a stream
1219 * choice (a,b,c - some aren't relevant) and an offset in
1220 * that stream
1223 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1224 struct file fm, fb, fa;
1225 struct csl *csl1, *csl2;
1226 struct ci ci;
1227 char buf[100];
1228 int ch;
1229 int refresh = 2;
1230 int rows,cols;
1231 int i, c;
1232 int mode = ORIG|RESULT | BEFORE|AFTER;
1234 int row,start = 0;
1235 struct mpos pos, tpos;
1237 sp = load_segment(f, p->start, p->end);
1238 if (reverse)
1239 ch = split_patch(sp, &sa, &sb);
1240 else
1241 ch = split_patch(sp, &sb, &sa);
1243 sm = load_file(p->file);
1244 fm = split_stream(sm, ByWord, 0);
1245 fb = split_stream(sb, ByWord, 0);
1246 fa = split_stream(sa, ByWord, 0);
1248 csl1 = pdiff(fm, fb, ch);
1249 csl2 = diff(fb, fa);
1250 #if 0
1251 cleanlist(fm, fb, csl1);
1252 cleanlist(fb, fa, csl2);
1253 #endif
1255 ci = make_merger(fm, fb, fa, csl1, csl2, 0);
1257 row = 1;
1258 pos.m = 0; /* merge node */
1259 pos.s = 0; /* stream number */
1260 pos.o = -1; /* offset */
1261 next_mline(&pos, fm,fb,fa,ci.merger, mode);
1263 while(1) {
1264 if (refresh == 2) {
1265 clear();
1266 sprintf(buf, "File: %s%s\n", p->file,reverse?" - reversed":"");
1267 attrset(A_BOLD); mvaddstr(0,0,buf);
1268 clrtoeol();
1269 attrset(A_NORMAL);
1270 refresh = 1;
1272 if (row < 1 || rows >= rows)
1273 refresh = 1;
1274 if (refresh) {
1275 int mode2;
1277 refresh = 0;
1278 getmaxyx(stdscr, rows, cols);
1279 if (row < -3)
1280 row = (rows-1)/2+1;
1281 if (row < 1)
1282 row = 1;
1283 if (row > rows+3)
1284 row = (rows-1)/2+1;
1285 if (row >= rows)
1286 row = rows-1;
1287 tpos = pos;
1288 for (i=row-1; i>=1 && tpos.m >= 0; ) {
1289 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1290 mode2 = check_line(tpos, fm,fb,fa, ci.merger, mode);
1291 if ((mode2 & (RESULT|AFTER)) &&
1292 (mode2 & (CHANGED)))
1293 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(RESULT|AFTER|CHANGED|CHANGES));
1294 else if ((mode2 & (RESULT|AFTER)) &&
1295 (mode2 & (CHANGES)))
1296 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(AFTER|CHANGED|CHANGES));
1297 if (i >= 1 && (mode2 & (ORIG|BEFORE)))
1298 draw_mline(i--,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(ORIG|BEFORE|CHANGED|CHANGES));
1300 tpos = pos;
1301 for (i=row; i<rows && ci.merger[tpos.gm].type != End; ) {
1302 mode2 = check_line(tpos, fm,fb,fa,ci.merger,mode);
1303 if (mode2 & (ORIG|BEFORE))
1304 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(ORIG|BEFORE|CHANGED|CHANGES));
1305 if (i < rows && (mode2 & (RESULT|AFTER))) {
1306 if (mode2 & CHANGED)
1307 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(RESULT|AFTER|CHANGED|CHANGES));
1308 else if (mode2 & CHANGES)
1309 draw_mline(i++,tpos,fm,fb,fa,ci.merger,start,cols, mode2&(AFTER|CHANGED|CHANGES));
1311 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1314 move(row,0);
1315 c = getch();
1316 switch(c) {
1317 int mode2;
1318 case 27:
1319 case 'q':
1320 return;
1321 case 'L'-64:
1322 refresh = 2;
1323 break;
1326 case 'j':
1327 case 'n':
1328 case 'N'-64:
1329 case KEY_DOWN:
1330 tpos = pos;
1331 next_mline(&tpos, fm,fb,fa,ci.merger, mode);
1332 if (ci.merger[tpos.m].type != End) {
1333 row++;
1334 mode2 =check_line(pos, fm,fb,fa,ci.merger,mode);
1335 if ((mode2 & CHANGES) &&
1336 (mode2 & (BEFORE|ORIG)) &&
1337 (mode2 & (AFTER|RESULT))
1338 ) row++;
1339 pos = tpos;
1341 break;
1342 case 'N':
1343 /* Next 'patch' */
1344 while (pos.m >= 0 && ci.merger[pos.m].type == Unmatched)
1345 next_mline(&pos,fm,fb,fa,ci.merger, mode);
1346 row = -10;
1347 break;
1348 case 'k':
1349 case 'p':
1350 case 'P':
1351 case 'P'-64:
1352 case KEY_UP:
1353 tpos = pos;
1354 prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
1355 if (tpos.m >= 0) {
1356 pos = tpos;
1357 row--;
1358 mode2 = check_line(pos, fm,fb,fa,ci.merger,mode);
1359 if ((mode2 & CHANGES) &&
1360 (mode2 & (BEFORE|ORIG)) &&
1361 (mode2 & (AFTER|RESULT))
1362 ) row--;
1364 break;
1366 case 'a': /* 'after' view in patch window */
1367 if (mode & AFTER)
1368 mode &= ~BEFORE;
1369 else
1370 mode |= AFTER;
1371 refresh = 1;
1372 break;
1373 case 'b': /* 'before' view in patch window */
1374 if (mode & BEFORE)
1375 mode &= ~AFTER;
1376 else
1377 mode |= BEFORE;
1378 refresh = 1;
1379 break;
1380 case 'o': /* 'original' view in the merge window */
1381 if (mode & ORIG)
1382 mode &= ~RESULT;
1383 else
1384 mode |= ORIG;
1385 refresh = 1;
1386 break;
1387 case 'r': /* the 'result' view in the merge window */
1388 if (mode & RESULT)
1389 mode &= ~ORIG;
1390 else
1391 mode |= RESULT;
1392 refresh = 1;
1393 break;
1395 case 'h':
1396 case KEY_LEFT:
1397 if (start > 0) start--;
1398 refresh = 1;
1399 break;
1400 case 'l':
1401 case KEY_RIGHT:
1402 if (start < cols) start++;
1403 refresh = 1;
1404 break;
1409 void main_window(struct plist *pl, int n, FILE *f, int reverse)
1411 /* The main window lists all files together with summary information:
1412 * number of chunks, number of wiggles, number of conflicts.
1413 * The list is scrollable
1414 * When a entry is 'selected', we switch to the 'file' window
1415 * The list can be condensed by removing files with no conflict
1416 * or no wiggles, or removing subdirectories
1418 * We record which file in the list is 'current', and which
1419 * screen line it is on. We try to keep things stable while
1420 * moving.
1422 * Counts are printed before the name using at most 2 digits.
1423 * Numbers greater than 99 are XX
1424 * Ch Wi Co File
1425 * 27 5 1 drivers/md/md.c
1427 * A directory show the sum in all children.
1429 * Commands:
1430 * select: enter, space, mouseclick
1431 * on file, go to file window
1432 * on directory, toggle open
1433 * up: k, p, control-p uparrow
1434 * Move to previous open object
1435 * down: j, n, control-n, downarrow
1436 * Move to next open object
1439 int pos=0; /* position in file */
1440 int row=1; /* position on screen */
1441 int rows; /* size of screen in rows */
1442 int cols;
1443 int tpos, i;
1444 int refresh = 2;
1445 int c=0;
1447 while(1) {
1448 if (refresh == 2) {
1449 clear(); attrset(0);
1450 attron(A_BOLD);
1451 mvaddstr(0,0,"Ch Wi Co Patched Files");
1452 move(2,0);
1453 attroff(A_BOLD);
1454 refresh = 1;
1456 if (row <1 || row >= rows)
1457 refresh = 1;
1458 if (refresh) {
1459 refresh = 0;
1460 getmaxyx(stdscr, rows, cols);
1461 if (row >= rows +3)
1462 row = (rows+1)/2;
1463 if (row >= rows)
1464 row = rows-1;
1465 tpos = pos;
1466 for (i=row; i>1; i--) {
1467 tpos = get_prev(tpos, pl, n);
1468 if (tpos == -1) {
1469 row = row - i + 1;
1470 break;
1473 /* Ok, row and pos could be trustworthy now */
1474 tpos = pos;
1475 for (i=row; i>=1; i--) {
1476 draw_one(i, &pl[tpos], f, reverse);
1477 tpos = get_prev(tpos, pl, n);
1479 tpos = pos;
1480 for (i=row+1; i<rows; i++) {
1481 tpos = get_next(tpos, pl, n);
1482 if (tpos >= 0)
1483 draw_one(i, &pl[tpos], f, reverse);
1484 else
1485 draw_one(i, NULL, f, reverse);
1488 {char bb[20]; sprintf(bb,"%d", c); mvaddstr(0, 70, bb); clrtoeol();}
1489 move(row, 9);
1490 c = getch();
1491 switch(c) {
1492 case 'j':
1493 case 'n':
1494 case 'N':
1495 case 'N'-64:
1496 case KEY_DOWN:
1497 tpos = get_next(pos, pl, n);
1498 if (tpos >= 0) {
1499 pos = tpos;
1500 row++;
1502 break;
1503 case 'k':
1504 case 'p':
1505 case 'P':
1506 case 'P'-64:
1507 case KEY_UP:
1508 tpos = get_prev(pos, pl, n);
1509 if (tpos >= 0) {
1510 pos = tpos;
1511 row--;
1513 break;
1515 case ' ':
1516 case 13:
1517 if (pl[pos].end == 0) {
1518 pl[pos].open = ! pl[pos].open;
1519 refresh = 1;
1520 } else {
1521 /* diff_window(&pl[pos], f); */
1522 merge_window(&pl[pos],f,reverse);
1523 refresh = 2;
1525 break;
1526 case 27: /* escape */
1527 case 'q':
1528 return;
1530 case KEY_RESIZE:
1531 refresh = 2;
1532 break;
1538 int vpatch(int argc, char *argv[], int strip, int reverse, int replace)
1540 /* NOT argv[0] is first arg... */
1541 int n = 0;
1542 FILE *f = NULL;
1543 FILE *in = stdin;
1544 struct plist *pl;
1546 if (argc >=1) {
1547 in = fopen(argv[0], "r");
1548 if (!in) {
1549 printf("No %s\n", argv[0]);
1550 exit(1);
1552 } else {
1553 in = stdin;
1555 if (lseek(fileno(in),0L, 1) == -1) {
1556 f = tmpfile();
1557 if (!f) {
1558 fprintf(stderr, "Cannot create a temp file\n");
1559 exit(1);
1563 pl = parse_patch(in, f, &n);
1565 if (set_prefix(pl, n, strip) == 0) {
1566 fprintf(stderr, "%s: aborting\n", Cmd);
1567 exit(2);
1569 pl = sort_patches(pl, &n);
1571 if (f) {
1572 if (fileno(in) == 0) {
1573 close(0);
1574 dup(2);
1575 } else
1576 fclose(in);
1577 in = f;
1579 #if 0
1580 int i;
1581 for (i=0; i<n ; i++) {
1582 printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
1584 exit(0);
1585 #endif
1586 signal(SIGINT, catch);
1587 signal(SIGQUIT, catch);
1588 signal(SIGTERM, catch);
1589 signal(SIGBUS, catch);
1590 signal(SIGSEGV, catch);
1592 initscr(); cbreak(); noecho();
1593 start_color();
1594 use_default_colors();
1595 if (!has_colors()) {
1596 a_delete = A_UNDERLINE;
1597 a_added = A_BOLD;
1598 a_common = A_NORMAL;
1599 a_sep = A_STANDOUT;
1600 a_already = A_STANDOUT;
1601 } else {
1602 init_pair(1, COLOR_WHITE, COLOR_RED);
1603 a_delete = COLOR_PAIR(1);
1604 init_pair(2, COLOR_WHITE, COLOR_BLUE);
1605 a_added = COLOR_PAIR(2);
1606 a_common = A_NORMAL;
1607 init_pair(3, COLOR_WHITE, COLOR_GREEN);
1608 a_sep = COLOR_PAIR(3);
1609 init_pair(4, COLOR_WHITE, COLOR_YELLOW);
1610 a_void = COLOR_PAIR(4);
1611 init_pair(5, COLOR_BLUE, COLOR_WHITE);
1612 a_unmatched = COLOR_PAIR(5);
1613 init_pair(6, COLOR_CYAN, COLOR_WHITE);
1614 a_extra = COLOR_PAIR(6);
1616 init_pair(7, COLOR_BLACK, COLOR_CYAN);
1617 a_already = COLOR_PAIR(7);
1619 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
1620 mousemask(ALL_MOUSE_EVENTS, NULL);
1622 main_window(pl, n, in, reverse);
1624 nocbreak();nl();endwin();
1625 return 0;
1626 exit(0);
1628 #if 0
1629 WiggleVerbose=1 ~/work/wiggle/wiggle -mR fs/nfsd/nfs4callback.c .patches/removed/144NfsdV4-033 |less
1630 neilb@notabene:/home/src/mm$
1632 ~/work/wiggle/wiggle -BR .patches/removed/144NfsdV4-033
1635 #endif