vpatch cleanup
[wiggle/upstream.git] / vpatch.c
blobe5aa4e8dcc7b5ff9e01f25c8e4790163ddce3924
1 /*
2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2011 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; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
22 * Author: Neil Brown
23 * Email: <neilb@suse.de>
27 * vpatch - visual front end for wiggle - aka Browse mode.
29 * "files" display, lists all files with statistics
30 * - can hide various lines including subdirectories
31 * and files without wiggles or conflicts
32 * "merge" display shows various views of merged file with different
33 * parts in different colours.
35 * The window can be split horizontally to show the original and result
36 * beside the diff, and each different branch can be shown alone.
40 #include "wiggle.h"
41 #include <malloc.h>
42 #include <curses.h>
43 #include <unistd.h>
44 #include <stdlib.h>
45 #include <signal.h>
46 #include <fcntl.h>
48 static void term_init(void);
50 #define assert(x) do { if (!(x)) abort(); } while (0)
52 static struct stream load_segment(FILE *f,
53 unsigned int start, unsigned int end);
55 /* global attributes */
56 unsigned int a_delete, a_added, a_common, a_sep, a_void,
57 a_unmatched, a_extra, a_already;
59 /******************************************************************
60 * Help window
61 * We display help in an insert, leaving 5 columns left and right,
62 * and 2 rows top and bottom, but at most 58x15 plus border
63 * In help mode:
64 * SPC or RTN moves down or to next page
65 * BKSPC goes backwards
66 * 'q' returns to origin screen
67 * '?' show help on help
68 * left and right scroll help view
70 * A help text is an array of lines of text
73 char *help_help[] = {
74 " You are viewing the help page for the help viewer.",
75 "You normally get here by typing '?'",
76 "",
77 "The following keystrokes work in the help viewer:",
78 " ? display this help message",
79 " q return to previous view",
80 " SPC move forward through help document",
81 " RTN same as SPC",
82 " BKSP move backward through help document",
83 " RIGHT scroll help window so text on the right appears",
84 " LEFT scroll help window so text on the left appears",
85 NULL
88 /* We can give one or two pages to display in the help window.
89 * The first is specific to the current context. The second
90 * is optional and may provide help in a more broad context.
92 static void help_window(char *page1[], char *page2[])
94 int rows, cols;
95 int top, left;
96 int r, c;
97 int ch;
98 char **page = page1;
99 int line = 0;
100 int shift = 0;
102 getmaxyx(stdscr, rows, cols);
104 if (cols < 70) {
105 left = 6;
106 cols = cols-12;
107 } else {
108 left = (cols-58)/2 - 1;
109 cols = 58;
112 if (rows < 21) {
113 top = 3;
114 rows = rows - 6;
115 } else {
116 top = (rows-15)/2 - 1;
117 rows = 15;
120 /* Draw a bow around the 'help' area */
121 (void)attrset(A_STANDOUT);
122 for (c = left; c < left+cols; c++) {
123 mvaddch(top-1, c, '-');
124 mvaddch(top+rows, c, '-');
126 for (r = top; r < top + rows ; r++) {
127 mvaddch(r, left-1, '|');
128 mvaddch(r, left+cols, '|');
130 mvaddch(top-1, left-1, '/');
131 mvaddch(top-1, left+cols, '\\');
132 mvaddch(top+rows, left-1, '\\');
133 mvaddch(top+rows, left+cols, '/');
134 mvaddstr(top-1, left + cols/2 - 9, "HELP - 'q' to exit");
135 mvaddstr(top+rows, left+cols/2 - 17, "Press SPACE for more, '?' for help");
136 (void)attrset(A_NORMAL);
138 while (1) {
139 char **lnp = page + line;
141 /* Draw as much of the page at the current offset
142 * as fits.
144 for (r = 0; r < rows; r++) {
145 char *ln = *lnp;
146 int sh = shift;
147 if (ln)
148 lnp++;
149 else
150 ln = "";
152 while (*ln && sh > 0) {
153 ln++;
154 sh--;
156 for (c = 0; c < cols; c++) {
157 int chr = *ln;
158 if (chr)
159 ln++;
160 else
161 chr = ' ';
162 mvaddch(top+r, left+c, chr);
165 move(top+rows-1, left);
166 ch = getch();
168 switch (ch) {
169 case 'q':
170 return;
171 case '?':
172 if (page1 != help_help)
173 help_window(help_help, NULL);
174 break;
175 case ' ':
176 case '\r': /* page-down */
177 for (r = 0; r < rows-2; r++)
178 if (page[line])
179 line++;
180 if (!page[line]) {
181 line = 0;
182 if (page == page1)
183 page = page2;
184 else
185 page = NULL;
186 if (page == NULL)
187 return;
189 break;
191 case '\b': /* page up */
192 if (line > 0) {
193 line -= (rows-2);
194 if (line < 0)
195 line = 0;
196 } else {
197 if (page == page2)
198 page = page1;
199 else
200 page = page2;
201 if (page == NULL)
202 page = page1;
203 line = 0;
205 break;
207 case KEY_LEFT:
208 if (shift > 0)
209 shift--;
210 break;
211 case KEY_RIGHT:
212 shift++;
213 break;
215 case KEY_UP:
216 if (line > 0)
217 line--;
218 break;
219 case KEY_DOWN:
220 if (page[line])
221 line++;
222 break;
228 /* Type names are needed for tracing only. */
229 static char *typenames[] = {
230 [End] = "End",
231 [Unmatched] = "Unmatched",
232 [Unchanged] = "Unchanged",
233 [Extraneous] = "Extraneous",
234 [Changed] = "Changed",
235 [Conflict] = "Conflict",
236 [AlreadyApplied] = "AlreadyApplied",
239 /* When we merge the original and the diff together we need
240 * to keep track of where everything came from.
241 * When we display the different views, we need to be able to
242 * select certain portions of the whole document.
243 * These flags are used to identify what is present, and to
244 * request different parts be extracted. They also help
245 * guide choice of colour.
247 #define BEFORE 1
248 #define AFTER 2
249 #define ORIG 4
250 #define RESULT 8
251 #define CHANGED 16 /* The RESULT is different to ORIG */
252 #define CHANGES 32 /* AFTER is different to BEFORE */
253 #define WIGGLED 64 /* a conflict that was successfully resolved */
254 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
257 /* Displaying a Merge.
258 * The first step is to linearise the merge. The merge in inherently
259 * parallel with before/after streams. However much of the whole document
260 * is linear as normally much of the original in unchanged.
261 * All parallelism comes from the patch. This normally produces two
262 * parallel stream, but in the case of a conflict can produce three.
263 * For browsing the merge we only ever show two alternates in-line.
264 * When there are three we use two panes with 1 or 2 alternates in each.
265 * So to linearise the two streams we find lines that are completely
266 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
267 * a region where there are changes. We include everything between
268 * these twice, in two separate passes. The exact interpretation of the
269 * passes is handled at a higher level but will be one of:
270 * original and result
271 * before and after
272 * original and after (for a conflict)
273 * This is all encoded in the 'struct merge'. An array of these describes
274 * the whole document.
276 * At any position in the merge we can be in one of 3 states:
277 * 0: unchanged section
278 * 1: first pass
279 * 2: second pass
281 * So to walk a merge in display order we need a position in the merge,
282 * a current state, and when in a changed section, we need to know the
283 * bounds of that changed section.
284 * This is all encoded in 'struct mpos'.
286 * Each location may or may not be visible depending on certain
287 * display options.
289 * Also, some locations might be 'invalid' in that they don't need to be displayed.
290 * For example when the patch leaves a section of the original unchanged,
291 * we only need to see the original - the before/after sections are treated
292 * as invalid and are not displayed.
293 * The visibility of newlines is crucial and guides the display. One line
294 * of displayed text is all the visible sections between two visible newlines.
296 * Counting lines is a bit tricky. We only worry about line numbers in the
297 * original (stream 0) as these could compare with line numbers mentioned in
298 * patch chunks.
299 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
300 * That way we don't get a full counted line until we see the first char after the
301 * newline, so '+' lines are counted with the previous line.
304 struct mpos {
305 struct mp {
306 int m; /* merger index */
307 int s; /* stream 0,1,2 for a,b,c */
308 int o; /* offset in that stream */
309 int lineno; /* Counts newlines in stream 0
310 * set lsb when see newline.
311 * add one when not newline and lsb set
313 } p, /* the current point */
314 lo, /* eol for start of the current group */
315 hi; /* eol for end of the current group */
316 int state; /*
317 * 0 if on an unchanged (lo/hi not meaningful)
318 * 1 if on the '-' of a diff,
319 * 2 if on the '+' of a diff
323 /* used for checking location during search */
324 static int same_mpos(struct mpos a, struct mpos b)
326 return a.p.m == b.p.m &&
327 a.p.s == b.p.s &&
328 a.p.o == b.p.o &&
329 a.state == b.state;
332 /* Check if a particular stream is meaningful in a particular merge
333 * section. e.g. in an Unchanged section, only stream 0, the
334 * original, is meaningful. This is used to avoid walking down
335 * pointless paths.
337 static int stream_valid(int s, enum mergetype type)
339 switch (type) {
340 case End:
341 return 1;
342 case Unmatched:
343 return s == 0;
344 case Unchanged:
345 return s == 0;
346 case Extraneous:
347 return s == 2;
348 case Changed:
349 return s != 1;
350 case Conflict:
351 return 1;
352 case AlreadyApplied:
353 return 1;
355 return 0;
359 * Advance the 'pos' in the current mergepos returning the next
360 * element (word).
361 * This walks the merges in sequence, and the streams within
362 * each merge.
364 static struct elmnt next_melmnt(struct mp *pos,
365 struct file fm, struct file fb, struct file fa,
366 struct merge *m)
368 pos->o++;
369 while (1) {
370 int l = 0; /* Length remaining in current merge section */
371 if (pos->m >= 0)
372 switch (pos->s) {
373 case 0:
374 l = m[pos->m].al;
375 break;
376 case 1:
377 l = m[pos->m].bl;
378 break;
379 case 2:
380 l = m[pos->m].cl;
381 break;
383 if (pos->o >= l) {
384 /* Offset has reached length, choose new stream or
385 * new merge */
386 pos->o = 0;
387 do {
388 pos->s++;
389 if (pos->s > 2) {
390 pos->s = 0;
391 pos->m++;
393 } while (!stream_valid(pos->s, m[pos->m].type));
394 } else
395 break;
397 if (pos->m == -1 || m[pos->m].type == End) {
398 struct elmnt e;
399 e.start = NULL; e.len = 0;
400 return e;
402 switch (pos->s) {
403 default: /* keep compiler happy */
404 case 0:
405 if (pos->lineno & 1)
406 pos->lineno++;
407 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
408 pos->lineno++;
409 return fm.list[m[pos->m].a + pos->o];
410 case 1: return fb.list[m[pos->m].b + pos->o];
411 case 2: return fa.list[m[pos->m].c + pos->o];
415 /* step current position.p backwards */
416 static struct elmnt prev_melmnt(struct mp *pos,
417 struct file fm, struct file fb, struct file fa,
418 struct merge *m)
420 if (pos->s == 0) {
421 if (ends_mline(fm.list[m[pos->m].a + pos->o]))
422 pos->lineno--;
423 if (pos->lineno & 1)
424 pos->lineno--;
427 pos->o--;
428 while (pos->m >= 0 && pos->o < 0) {
429 do {
430 pos->s--;
431 if (pos->s < 0) {
432 pos->s = 2;
433 pos->m--;
435 } while (pos->m >= 0 &&
436 !stream_valid(pos->s, m[pos->m].type));
437 if (pos->m >= 0) {
438 switch (pos->s) {
439 case 0:
440 pos->o = m[pos->m].al-1;
441 break;
442 case 1:
443 pos->o = m[pos->m].bl-1;
444 break;
445 case 2:
446 pos->o = m[pos->m].cl-1;
447 break;
451 if (pos->m < 0) {
452 struct elmnt e;
453 e.start = NULL; e.len = 0;
454 return e;
456 switch (pos->s) {
457 default: /* keep compiler happy */
458 case 0: return fm.list[m[pos->m].a + pos->o];
459 case 1: return fb.list[m[pos->m].b + pos->o];
460 case 2: return fa.list[m[pos->m].c + pos->o];
464 /* 'visible' not only checks if this stream in this merge should be
465 * visible in this mode, but also chooses which colour/highlight to use
466 * to display it.
468 static int visible(int mode, enum mergetype type, int stream)
470 if (mode == 0)
471 return -1;
472 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
473 switch (type) {
474 case End: /* The END is always visible */
475 return A_NORMAL;
476 case Unmatched: /* Visible in ORIG and RESULT */
477 if (mode & (ORIG|RESULT))
478 return a_unmatched;
479 break;
480 case Unchanged: /* visible everywhere, but only show stream 0 */
481 if (stream == 0)
482 return a_common;
483 break;
484 case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
485 if ((mode & (BEFORE|AFTER))
486 && stream == 2)
487 return a_extra;
488 break;
489 case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
490 if (stream == 0 &&
491 (mode & (ORIG|BEFORE)))
492 return a_delete;
493 if (stream == 2 &&
494 (mode & (RESULT|AFTER)))
495 return a_added;
496 break;
497 case Conflict:
498 switch (stream) {
499 case 0:
500 if (mode & ORIG)
501 return a_unmatched | A_REVERSE;
502 break;
503 case 1:
504 if (mode & BEFORE)
505 return a_extra | A_UNDERLINE;
506 break;
507 case 2:
508 if (mode & (AFTER|RESULT))
509 return a_added | A_UNDERLINE;
510 break;
512 break;
513 case AlreadyApplied:
514 switch (stream) {
515 case 0:
516 if (mode & (ORIG|RESULT))
517 return a_already;
518 break;
519 case 1:
520 if (mode & BEFORE)
521 return a_delete | A_UNDERLINE;
522 break;
523 case 2:
524 if (mode & AFTER)
525 return a_added | A_UNDERLINE;
526 break;
528 break;
530 return -1;
533 /* checkline creates a summary of the sort of changes that
534 * are in a line, returning an "or" of
535 * CHANGED
536 * CHANGES
537 * WIGGLED
538 * CONFLICTED
540 static int check_line(struct mpos pos, struct file fm, struct file fb,
541 struct file fa,
542 struct merge *m, int mode)
544 int rv = 0;
545 struct elmnt e;
546 int unmatched = 0;
549 do {
550 if (m[pos.p.m].type == Changed)
551 rv |= CHANGED | CHANGES;
552 else if ((m[pos.p.m].type == AlreadyApplied ||
553 m[pos.p.m].type == Conflict))
554 rv |= CONFLICTED | CHANGES;
555 else if (m[pos.p.m].type == Extraneous)
556 rv |= WIGGLED;
557 else if (m[pos.p.m].type == Unmatched)
558 unmatched = 1;
559 e = prev_melmnt(&pos.p, fm, fb, fa, m);
560 } while (e.start != NULL &&
561 (!ends_mline(e)
562 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
564 if (unmatched && (rv & CHANGES))
565 rv |= WIGGLED;
566 return rv;
569 /* Find the next line in the merge which is visible.
570 * If we hit the end of a conflicted set during pass-1
571 * we rewind for pass-2.
572 * 'mode' tells which bits we want to see, possible one of
573 * the 4 parts (before/after/orig/result) or one of the pairs
574 * before+after or orig+result.
576 static void next_mline(struct mpos *pos, struct file fm, struct file fb,
577 struct file fa,
578 struct merge *m, int mode)
580 int mask;
581 do {
582 struct mp prv;
583 int mode2;
585 prv = pos->p;
586 while (1) {
587 struct elmnt e = next_melmnt(&pos->p, fm, fb, fa, m);
588 if (e.start == NULL)
589 break;
590 if (ends_mline(e) &&
591 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
592 break;
594 mode2 = check_line(*pos, fm, fb, fa, m, mode);
596 if ((mode2 & CHANGES) && pos->state == 0) {
597 /* Just entered a diff-set */
598 pos->lo = pos->p;
599 pos->state = 1;
600 } else if (!(mode2 & CHANGES) && pos->state) {
601 /* Come to the end of a diff-set */
602 switch (pos->state) {
603 case 1:
604 /* Need to record the end */
605 pos->hi = prv;
606 /* time for another pass */
607 pos->p = pos->lo;
608 pos->state++;
609 break;
610 case 2:
611 /* finished final pass */
612 pos->state = 0;
613 break;
616 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
617 switch (pos->state) {
618 case 1:
619 mask &= ~(RESULT|AFTER);
620 break;
621 case 2:
622 mask &= ~(ORIG|BEFORE);
623 break;
625 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
629 /* Move to previous line - simply the reverse of next_mline */
630 static void prev_mline(struct mpos *pos, struct file fm, struct file fb,
631 struct file fa,
632 struct merge *m, int mode)
634 int mask;
635 do {
636 struct mp prv;
637 int mode2;
639 prv = pos->p;
640 if (pos->p.m < 0)
641 return;
642 while (1) {
643 struct elmnt e = prev_melmnt(&pos->p, fm, fb, fa, m);
644 if (e.start == NULL)
645 break;
646 if (ends_mline(e) &&
647 visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
648 break;
650 mode2 = check_line(*pos, fm, fb, fa, m, mode);
652 if ((mode2 & CHANGES) && pos->state == 0) {
653 /* Just entered a diff-set */
654 pos->hi = pos->p;
655 pos->state = 2;
656 } else if (!(mode2 & CHANGES) && pos->state) {
657 /* Come to the end (start) of a diff-set */
658 switch (pos->state) {
659 case 1:
660 /* finished final pass */
661 pos->state = 0;
662 break;
663 case 2:
664 /* Need to record the start */
665 pos->lo = prv;
666 /* time for another pass */
667 pos->p = pos->hi;
668 pos->state--;
669 break;
672 mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
673 switch (pos->state) {
674 case 1:
675 mask &= ~(RESULT|AFTER);
676 break;
677 case 2:
678 mask &= ~(ORIG|BEFORE);
679 break;
681 } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
684 /* blank a whole row of display */
685 static void blank(int row, int start, int cols, unsigned int attr)
687 (void)attrset(attr);
688 move(row, start);
689 while (cols-- > 0)
690 addch(' ');
693 /* search of a string on one display line - just report if found, not where */
695 static int mcontains(struct mpos pos,
696 struct file fm, struct file fb, struct file fa,
697 struct merge *m,
698 int mode, char *search)
700 /* See if any of the files, between start of this line and here,
701 * contain the search string
703 struct elmnt e;
704 int len = strlen(search);
705 do {
706 e = prev_melmnt(&pos.p, fm, fb, fa, m);
707 if (e.start) {
708 int i;
709 for (i = 0; i < e.len; i++)
710 if (strncmp(e.start+i, search, len) == 0)
711 return 1;
713 } while (e.start != NULL &&
714 (!ends_mline(e)
715 || visible(mode, m[pos.p.m].type, pos.p.s) == -1));
716 return 0;
719 /* Drawing the display window.
720 * There are 7 different ways we can display the data, each
721 * of which can be configured by a keystroke:
722 * o original - just show the original file with no changes, but still
723 * with highlights of what is changed or unmatched
724 * r result - show just the result of the merge. Conflicts just show
725 * the original, not the before/after options
726 * b before - show the 'before' stream of the patch
727 * a after - show the 'after' stream of the patch
728 * d diff - show just the patch, both before and after
729 * m merge - show the full merge with -+ sections for changes.
730 * If point is in a wiggled or conflicted section the
731 * window is split horizontally and the diff is shown
732 * in the bottom window
733 * | sidebyside - two panes, left and right. Left holds the merge,
734 * right holds the diff. In the case of a conflict,
735 * left holds orig/after, right holds before/after
737 * The horizontal split for 'merge' mode is managed as follows.
738 * - The window is split when we first visit a line that contains
739 * a wiggle or a conflict, and the second pane is removed when
740 * we next visit a line that contains no changes (is fully Unchanged).
741 * - to display the second pane, we find a visible end-of-line in the
742 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
743 * the we centre that line.
744 * - We need to rewind to an unchanged section, and wind forward again
745 * to make sure that 'lo' and 'hi' are set properly.
746 * - every time we move, we redraw the second pane (see how that goes).
749 /* draw_mside draws one text line or, in the case of sidebyside, one side
750 * of a textline.
751 * The 'mode' tells us what to draw via the 'visible()' function.
752 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
753 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
754 * The desired cursor position is given in 'target' the actual end
755 * cursor position (allowing e.g. for tabs) is returned in *colp.
757 static void draw_mside(int mode, int row, int offset, int start, int cols,
758 struct file fm, struct file fb, struct file fa,
759 struct merge *m,
760 struct mpos pos,
761 int target, int *colp)
763 struct elmnt e;
764 int col = 0;
765 char tag;
767 switch (pos.state) {
768 case 0: /* unchanged line */
769 tag = ' ';
770 break;
771 case 1: /* 'before' text */
772 tag = '-';
773 if ((mode & ORIG) && (mode & CONFLICTED))
774 tag = '|';
775 mode &= (ORIG|BEFORE);
776 break;
777 case 2: /* the 'after' part */
778 tag = '+';
779 mode &= (AFTER|RESULT);
780 break;
783 if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
784 /* Not visible, just draw a blank */
785 blank(row, offset, cols, a_void);
786 if (colp)
787 *colp = 0;
788 return;
791 (void)attrset(A_NORMAL);
792 mvaddch(row, offset, tag);
793 offset++;
794 cols--;
796 /* find previous visible newline, or start of file */
798 e = prev_melmnt(&pos.p, fm, fb, fa, m);
799 while (e.start != NULL &&
800 (!ends_mline(e) ||
801 visible(mode, m[pos.p.m].type, pos.p.s) == -1));
803 while (1) {
804 unsigned char *c;
805 int l;
806 e = next_melmnt(&pos.p, fm, fb, fa, m);
807 if (e.start == NULL ||
808 (ends_mline(e)
809 && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
810 if (colp)
811 *colp = col;
812 if (col < start)
813 col = start;
814 if (e.start && e.start[0] == 0) {
815 char b[40];
816 struct elmnt e1;
817 if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
818 int A, B, C, D, E, F;
819 e1 = fb.list[m[pos.p.m].b + pos.p.o];
820 sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
821 sscanf(e.start+1, "%d %d %d", &D, &E, &F);
822 sprintf(b, "@@ -%d,%d +%d,%d @@\n", B, C, E, F);
823 (void)attrset(a_sep);
824 } else {
825 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
826 sprintf(b, "<%.17s>", e.start+1);
828 mvaddstr(row, col-start+offset, b);
829 col += strlen(b);
831 blank(row, col-start+offset, start+cols-col,
832 e.start
833 ? (unsigned)visible(mode, m[pos.p.m].type, pos.p.s)
834 : A_NORMAL);
835 return;
837 if (visible(mode, m[pos.p.m].type, pos.p.s) == -1)
838 continue;
839 if (e.start[0] == 0)
840 continue;
841 (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
842 c = (unsigned char *)e.start;
843 l = e.len;
844 while (l) {
845 if (*c >= ' ' && *c != 0x7f) {
846 if (col >= start && col < start+cols)
847 mvaddch(row, col-start+offset, *c);
848 col++;
849 } else if (*c == '\t') {
850 do {
851 if (col >= start && col < start+cols)
852 mvaddch(row, col-start+offset, ' ');
853 col++;
854 } while ((col&7) != 0);
855 } else {
856 if (col >= start && col < start+cols)
857 mvaddch(row, col-start+offset, '?');
858 col++;
860 c++;
861 if (colp && target <= col) {
862 if (col-start >= cols)
863 *colp = 10*col;
864 else
865 *colp = col;
866 colp = NULL;
868 l--;
873 /* Draw either 1 or 2 sides depending on the mode. */
875 static void draw_mline(int mode, int row, int start, int cols,
876 struct file fm, struct file fb, struct file fa,
877 struct merge *m,
878 struct mpos pos,
879 int target, int *colp)
882 * Draw the left and right images of this line
883 * One side might be a_blank depending on the
884 * visibility of this newline
886 int lcols, rcols;
888 mode |= check_line(pos, fm, fb, fa, m, mode);
890 if ((mode & (BEFORE|AFTER)) &&
891 (mode & (ORIG|RESULT))) {
893 lcols = (cols-1)/2;
894 rcols = cols - lcols - 1;
896 (void)attrset(A_STANDOUT);
897 mvaddch(row, lcols, '|');
899 draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
900 fm, fb, fa, m, pos, target, colp);
902 draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
903 fm, fb, fa, m, pos, 0, NULL);
904 } else
905 draw_mside(mode, row, 0, start, cols,
906 fm, fb, fa, m, pos, target, colp);
909 static char *merge_help[] = {
910 "This view shows the merge of the patch with the",
911 "original file. It is like a full-context diff showing",
912 "removed lines with a '-' prefix and added lines with a",
913 "'+' prefix.",
914 "In cases where a patch chunk could not be successfully",
915 "applied, the original text is prefixed with a '|', and",
916 "the text that the patch wanted to add is prefixed with",
917 "a '+'.",
918 "When the cursor is over such a conflict, or over a chunk",
919 "which required wiggling to apply (i.e. there was unmatched",
920 "text in the original, or extraneous unchanged text in",
921 "the patch), the terminal is split and the bottom pane is",
922 "use to display the part of the patch that applied to",
923 "this section of the original. This allows you to confirm",
924 "that a wiggled patch applied correctly, and to see",
925 "why there was a conflict",
926 NULL
928 static char *diff_help[] = {
929 "This is the 'diff' or 'patch' view. It shows",
930 "only the patch that is being applied without the",
931 "original to which it is being applied.",
932 "Underlined text indicates parts of the patch which",
933 "resulted in a conflict when applied to the",
934 "original.",
935 NULL
937 static char *orig_help[] = {
938 "This is the 'original' view which simply shows",
939 "the original file before applying the patch.",
940 "Sections of code that would be changed by the patch",
941 "are highlighted in red.",
942 NULL
944 static char *result_help[] = {
945 "This is the 'result' view which show just the",
946 "result of applying the patch. When a conflict",
947 "occurred this view does not show the full conflict",
948 "but only the 'after' part of the patch. To see",
949 "the full conflict, use the 'merge' or 'sidebyside'",
950 "views.",
951 NULL
953 static char *before_help[] = {
954 "This view shows the 'before' section of a patch.",
955 "It allows the expected match text to be seen uncluttered",
956 "by text that is meant to replaced it."
957 "Text with a red background is text that will be",
958 "removed by the patch",
959 NULL
961 static char *after_help[] = {
962 "This view shows the 'after' section of a patch.",
963 "It allows the intended result to be seen uncluttered",
964 "by text that was meant to be matched and replaced."
965 "Text with a blue background is text that was added",
966 "by the patch - it was not present in the 'before'",
967 "part of the patch",
968 NULL
970 static char *sidebyside_help[] = {
971 "This is the Side By Side view of a patched file.",
972 "The left side shows the original and the result.",
973 "The right side shows the patch which was applied",
974 "and lines up with the original/result as much as",
975 "possible.",
977 "Where one side has no line which matches the",
978 "other side it is displayed as a solid colour in the",
979 "yellow family (depending on your terminal window).",
980 NULL
982 static char *merge_window_help[] = {
983 " Highlight Colours and Keystroke commands",
985 "In all different views of a merge, highlight colours",
986 "are used to show which parts of lines were added,",
987 "removed, already changed, unchanged or in conflict.",
988 "Colours and their use are:",
989 " normal unchanged text",
990 " red background text that was removed or changed",
991 " blue background text that was added or the result",
992 " of a change",
993 " yellow background used in side-by-side for a line",
994 " which has no match on the other",
995 " side",
996 " blue foreground text in the original which did not",
997 " match anything in the patch",
998 " cyan foreground text in the patch which did not",
999 " match anything in the original",
1000 " cyan background already changed text: the result",
1001 " of the patch matches the original",
1002 " underline remove or added text can also be",
1003 " underlined indicating that it",
1004 " was involved in a conflict",
1006 "While viewing a merge various keystroke commands can",
1007 "be used to move around and change the view. Basic",
1008 "movement commands from both 'vi' and 'emacs' are",
1009 "available:",
1011 " p control-p k UP Move to previous line",
1012 " n control-n j DOWN Move to next line",
1013 " l LEFT Move one char to right",
1014 " h RIGHT Move one char to left",
1015 " / control-s Enter incremental search mode",
1016 " control-r Enter reverse-search mode",
1017 " control-g Search again",
1018 " ? Display help message",
1019 " ESC-< 0-G Go to start of file",
1020 " ESC-> G Go to end of file",
1021 " q Return to list of files or exit",
1022 " control-L recenter current line",
1023 " control-V page down",
1024 " ESC-v page up",
1025 " N go to next patch chunk",
1026 " P go to previous patch chunk",
1027 " ^ control-A go to start of line",
1028 " $ control-E go to end of line",
1030 " a display 'after' view",
1031 " b display 'before' view",
1032 " o display 'original' view",
1033 " r display 'result' view",
1034 " d display 'diff' or 'patch' view",
1035 " m display 'merge' view",
1036 " | display side-by-side view",
1037 NULL
1040 /* plist stores a list of patched files in an array
1041 * Each entry identifies a file, the range of the
1042 * original patch which applies to this file, some
1043 * statistics concerning how many conflicts etc, and
1044 * some linkage information so the list can be viewed
1045 * as a directory-tree.
1047 struct plist {
1048 char *file;
1049 unsigned int start, end;
1050 int parent;
1051 int next, prev, last;
1052 int open;
1053 int chunks, wiggles, conflicts;
1054 int calced;
1055 int is_merge;
1056 char *before, *after;
1059 static void merge_window(struct plist *p, FILE *f, int reverse)
1061 /* Display the merge window in one of the selectable modes,
1062 * starting with the 'merge' mode.
1064 * Newlines are the key to display.
1065 * 'pos' is always a visible newline (or eof).
1066 * In sidebyside mode it might only be visible on one side,
1067 * in which case the other side will be blank.
1068 * Where the newline is visible, we rewind the previous visible
1069 * newline visible and display the stuff in between
1071 * A 'position' is a struct mpos
1074 struct stream sm, sb, sa, sp; /* main, before, after, patch */
1075 struct file fm, fb, fa;
1076 struct csl *csl1, *csl2;
1077 struct ci ci;
1078 int ch; /* count of chunks */
1079 /* Always refresh the current line.
1080 * If refresh == 1, refresh all lines. If == 2, clear first
1082 int refresh = 2;
1083 int rows = 0, cols = 0;
1084 int splitrow = -1; /* screen row for split - diff appears below */
1085 int lastrow = 0; /* end of screen, or just above 'splitrow' */
1086 int i, c, cswitch;
1087 int mode = ORIG|RESULT;
1088 char *modename = "merge";
1089 char **modehelp = merge_help;
1091 int row, start = 0;
1092 int trow; /* screen-row while searching. If we cannot find,
1093 * we forget this number */
1094 int col = 0, target = 0;
1095 struct mpos pos; /* current point */
1096 struct mpos tpos, /* temp point while drawing lines above and below pos */
1097 toppos, /* pos at top of screen - for page-up */
1098 botpos; /* pos at bottom of screen - for page-down */
1099 struct mpos vpos, tvpos;
1100 int botrow = 0;
1101 int meta = 0, /* mode for multi-key commands- SEARCH or META */
1102 tmeta;
1103 int num = -1, /* numeric arg being typed. */
1104 tnum;
1105 char search[80]; /* string we are searching for */
1106 unsigned int searchlen = 0;
1107 int search_notfound = 0;
1108 int searchdir = 0;
1109 /* We record all the places we find so 'backspace'
1110 * can easily return to the previous one
1112 struct search_anchor {
1113 struct search_anchor *next;
1114 struct mpos pos;
1115 int notfound;
1116 int row, col;
1117 unsigned int searchlen;
1118 } *anchor = NULL;
1120 if (f == NULL) {
1121 /* three separate files */
1122 sm = load_file(p->file);
1123 sb = load_file(p->before);
1124 sa = load_file(p->after);
1125 ch = 0;
1126 } else {
1127 sp = load_segment(f, p->start, p->end);
1128 if (p->is_merge) {
1129 if (reverse)
1130 split_merge(sp, &sm, &sa, &sb);
1131 else
1132 split_merge(sp, &sm, &sb, &sa);
1133 ch = 0;
1134 } else {
1135 if (reverse)
1136 ch = split_patch(sp, &sa, &sb);
1137 else
1138 ch = split_patch(sp, &sb, &sa);
1140 sm = load_file(p->file);
1143 fm = split_stream(sm, ByWord);
1144 fb = split_stream(sb, ByWord);
1145 fa = split_stream(sa, ByWord);
1147 if (ch)
1148 csl1 = pdiff(fm, fb, ch);
1149 else
1150 csl1 = diff(fm, fb);
1151 csl2 = diff(fb, fa);
1153 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1);
1155 term_init();
1157 row = 1;
1158 pos.p.m = 0; /* merge node */
1159 pos.p.s = 0; /* stream number */
1160 pos.p.o = -1; /* offset */
1161 pos.p.lineno = 1;
1162 pos.state = 0;
1163 next_mline(&pos, fm, fb, fa, ci.merger, mode);
1164 vpos = pos;
1165 while (1) {
1166 if (refresh == 2) {
1167 char buf[100];
1168 clear();
1169 snprintf(buf, 100, "File: %s%s Mode: %s\n",
1170 p->file, reverse ? " - reversed" : "", modename);
1171 (void)attrset(A_BOLD);
1172 mvaddstr(0, 0, buf);
1173 clrtoeol();
1174 (void)attrset(A_NORMAL);
1175 refresh = 1;
1177 if (row < 1 || row >= lastrow)
1178 refresh = 1;
1180 if (mode == (ORIG|RESULT)) {
1181 int cmode = check_line(pos, fm, fb, fa, ci.merger, mode);
1182 if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) {
1183 splitrow = (rows+1)/2;
1184 lastrow = splitrow - 1;
1185 refresh = 1;
1187 if (splitrow >= 0 && !(cmode & CHANGES)) {
1188 splitrow = -1;
1189 lastrow = rows-1;
1190 refresh = 1;
1192 } else if (splitrow >= 0) {
1193 splitrow = -1;
1194 lastrow = rows-1;
1195 refresh = 1;
1198 if (refresh) {
1199 getmaxyx(stdscr, rows, cols);
1200 rows--; /* keep last row clear */
1201 if (splitrow >= 0) {
1202 splitrow = (rows+1)/2;
1203 lastrow = splitrow - 1;
1204 } else
1205 lastrow = rows - 1;
1207 if (row < -3)
1208 row = lastrow/2+1;
1209 if (row < 1)
1210 row = 1;
1211 if (row > lastrow+3)
1212 row = lastrow/2+1;
1213 if (row >= lastrow)
1214 row = lastrow-1;
1216 if (getenv("WIGGLE_VTRACE")) {
1217 char b[100];
1218 char *e, e2[7];
1219 int i;
1220 switch (vpos.p.s) {
1221 case 0:
1222 e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start;
1223 break;
1224 case 1:
1225 e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start;
1226 break;
1227 case 2:
1228 e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start;
1229 break;
1231 for (i = 0; i < 6; i++) {
1232 e2[i] = e[i];
1233 if (e2[i] < 32 || e2[i] >= 127)
1234 e2[i] = '?';
1236 sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
1237 vpos.p.s, vpos.p.o,
1238 vpos.p.m, typenames[ci.merger[vpos.p.m].type],
1239 ci.merger[vpos.p.m].al,
1240 ci.merger[vpos.p.m].bl,
1241 ci.merger[vpos.p.m].cl,
1242 ci.merger[vpos.p.m].in_conflict,
1245 (void)attrset(A_NORMAL);
1246 mvaddstr(0, 50, b);
1247 clrtoeol();
1250 char lbuf[20];
1251 (void)attrset(A_BOLD);
1252 snprintf(lbuf, 19, "ln:%d", (pos.p.lineno-1)/2);
1253 mvaddstr(0, cols - strlen(lbuf) - 4, " ");
1254 mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
1256 /* Always refresh the line */
1257 while (start > target) {
1258 start -= 8;
1259 refresh = 1;
1261 if (start < 0)
1262 start = 0;
1263 retry:
1264 draw_mline(mode, row, start, cols, fm, fb, fa, ci.merger,
1265 pos, target, &col);
1267 if (col > cols+start) {
1268 start += 8;
1269 refresh = 1;
1270 goto retry;
1272 if (col < start) {
1273 start -= 8;
1274 refresh = 1;
1275 if (start < 0)
1276 start = 0;
1277 goto retry;
1279 if (refresh) {
1280 refresh = 0;
1281 tpos = pos;
1283 for (i = row-1; i >= 1 && tpos.p.m >= 0; ) {
1284 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1285 draw_mline(mode, i--, start, cols,
1286 fm, fb, fa, ci.merger,
1287 tpos, 0, NULL);
1290 if (i) {
1291 row -= (i+1);
1292 refresh = 1;
1293 goto retry;
1295 toppos = tpos;
1296 while (i >= 1)
1297 blank(i--, 0, cols, a_void);
1298 tpos = pos;
1299 for (i = row; i <= lastrow && ci.merger[tpos.p.m].type != End; ) {
1300 draw_mline(mode, i++, start, cols,
1301 fm, fb, fa, ci.merger,
1302 tpos, 0, NULL);
1303 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1305 botpos = tpos; botrow = i;
1306 while (i <= lastrow)
1307 blank(i++, 0, cols, a_void);
1310 if (splitrow >= 0) {
1311 struct mpos spos = pos;
1312 int smode = BEFORE|AFTER;
1313 int srow = (rows + splitrow)/2;
1314 if (visible(smode, ci.merger[spos.p.m].type,
1315 spos.p.s) < 0)
1316 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1317 /* Now hi/lo might be wrong, so lets fix it. */
1318 tpos = spos;
1319 while (spos.p.m >= 0 && spos.state != 0)
1320 prev_mline(&spos, fm, fb, fa, ci.merger, smode);
1321 while (!same_mpos(spos, tpos))
1322 next_mline(&spos, fm, fb, fa, ci.merger, smode);
1325 (void)attrset(a_sep);
1326 for (i = 0; i < cols; i++)
1327 mvaddstr(splitrow, i, "-");
1329 tpos = spos;
1330 for (i = srow-1; i > splitrow; i--) {
1331 prev_mline(&tpos, fm, fb, fa, ci.merger, smode);
1332 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1333 tpos, 0, NULL);
1335 while (i > splitrow)
1336 blank(i--, 0, cols, a_void);
1337 tpos = spos;
1338 for (i = srow;
1339 i < rows && ci.merger[tpos.p.m].type != End;
1340 i++) {
1341 draw_mline(smode, i, start, cols, fm, fb, fa, ci.merger,
1342 tpos, 0, NULL);
1343 next_mline(&tpos, fm, fb, fa, ci.merger, smode);
1346 #define META(c) ((c)|0x1000)
1347 #define SEARCH(c) ((c)|0x2000)
1348 move(rows, 0);
1349 (void)attrset(A_NORMAL);
1350 if (num >= 0) {
1351 char buf[10];
1352 snprintf(buf, 10, "%d ", num);
1353 addstr(buf);
1355 if (meta & META(0))
1356 addstr("ESC...");
1357 if (meta & SEARCH(0)) {
1358 if (searchdir)
1359 addstr("Backwards ");
1360 addstr("Search: ");
1361 addstr(search);
1362 if (search_notfound)
1363 addstr(" - Not Found.");
1364 search_notfound = 0;
1366 clrtoeol();
1367 move(row, col-start);
1368 c = getch();
1369 tmeta = meta; meta = 0;
1370 tnum = num; num = -1;
1371 tvpos = vpos; vpos = pos;
1372 cswitch = c | tmeta;
1373 /* Handle some ranges */
1374 /* case '0' ... '9': */
1375 if (cswitch >= '0' && cswitch <= '9')
1376 cswitch = '0';
1377 /* case SEARCH(' ') ... SEARCH('~'): */
1378 if (cswitch >= SEARCH(' ') && cswitch <= SEARCH('~'))
1379 cswitch = SEARCH(' ');
1381 switch (cswitch) {
1382 case 27: /* escape */
1383 case META(27):
1384 meta = META(0);
1385 break;
1386 case META('<'): /* start of file */
1387 start:
1388 tpos = pos; row++;
1389 do {
1390 pos = tpos; row--;
1391 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1392 } while (tpos.p.m >= 0);
1393 if (row <= 0)
1394 row = 0;
1395 break;
1396 case META('>'): /* end of file */
1397 case 'G':
1398 if (tnum >= 0)
1399 goto start;
1400 tpos = pos; row--;
1401 do {
1402 pos = tpos; row++;
1403 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1404 } while (ci.merger[tpos.p.m].type != End);
1405 if (row >= lastrow)
1406 row = lastrow;
1407 break;
1408 case '0': /* actually '0'...'9' */
1409 if (tnum < 0)
1410 tnum = 0;
1411 num = tnum*10 + (c-'0');
1412 break;
1413 case 'q':
1414 return;
1416 case '/':
1417 case 'S'-64:
1418 /* incr search forward */
1419 meta = SEARCH(0);
1420 searchlen = 0;
1421 search[searchlen] = 0;
1422 searchdir = 0;
1423 break;
1424 case '\\':
1425 case 'R'-64:
1426 /* incr search backwards */
1427 meta = SEARCH(0);
1428 searchlen = 0;
1429 search[searchlen] = 0;
1430 searchdir = 1;
1431 break;
1432 case SEARCH('G'-64):
1433 case SEARCH('S'-64):
1434 case SEARCH('R'-64):
1435 /* search again */
1436 if ((c|tmeta) == SEARCH('R'-64))
1437 searchdir = 1;
1438 if ((c|tmeta) == SEARCH('S'-64))
1439 searchdir = 0;
1440 meta = SEARCH(0);
1441 tpos = pos; trow = row;
1442 if (searchdir) {
1443 trow--;
1444 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1445 } else {
1446 trow++;
1447 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1449 goto search_again;
1451 case SEARCH('H'-64):
1452 meta = SEARCH(0);
1453 if (anchor) {
1454 struct search_anchor *a;
1455 a = anchor;
1456 anchor = a->next;
1457 free(a);
1459 if (anchor) {
1460 struct search_anchor *a;
1461 a = anchor;
1462 anchor = a->next;
1463 pos = a->pos;
1464 row = a->row;
1465 col = a->col;
1466 search_notfound = a->notfound;
1467 searchlen = a->searchlen;
1468 search[searchlen] = 0;
1469 free(a);
1470 refresh = 1;
1472 break;
1473 case SEARCH(' '): /* actually ' '...'~' */
1474 case SEARCH('\t'):
1475 meta = SEARCH(0);
1476 if (searchlen < sizeof(search)-1)
1477 search[searchlen++] = c & (0x7f);
1478 search[searchlen] = 0;
1479 tpos = pos; trow = row;
1480 search_again:
1481 search_notfound = 1;
1482 do {
1483 if (mcontains(tpos, fm, fb, fa, ci.merger, mode, search)) {
1484 pos = tpos;
1485 row = trow;
1486 search_notfound = 0;
1487 break;
1489 if (searchdir) {
1490 trow--;
1491 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1492 } else {
1493 trow++;
1494 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1496 } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
1498 break;
1499 case 'L'-64:
1500 refresh = 2;
1501 row = lastrow / 2;
1502 break;
1504 case 'V'-64: /* page down */
1505 pos = botpos;
1506 if (botrow <= lastrow)
1507 row = botrow;
1508 else
1509 row = 2;
1510 refresh = 1;
1511 break;
1512 case META('v'): /* page up */
1513 pos = toppos;
1514 row = lastrow-1;
1515 refresh = 1;
1516 break;
1518 case 'j':
1519 case 'n':
1520 case 'N'-64:
1521 case KEY_DOWN:
1522 if (tnum < 0)
1523 tnum = 1;
1524 for (; tnum > 0 ; tnum--) {
1525 tpos = pos;
1526 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1527 if (ci.merger[tpos.p.m].type != End) {
1528 pos = tpos;
1529 row++;
1532 break;
1533 case 'N':
1534 /* Next diff */
1535 tpos = pos; row--;
1536 do {
1537 pos = tpos; row++;
1538 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1539 } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
1540 tpos = pos; row--;
1541 do {
1542 pos = tpos; row++;
1543 next_mline(&tpos, fm, fb, fa, ci.merger, mode);
1544 } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
1546 break;
1547 case 'P':
1548 /* Previous diff */
1549 tpos = pos; row++;
1550 do {
1551 pos = tpos; row--;
1552 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1553 } while (tpos.state == 0 && tpos.p.m >= 0);
1554 tpos = pos; row++;
1555 do {
1556 pos = tpos; row--;
1557 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1558 } while (tpos.state != 0 && tpos.p.m >= 0);
1559 break;
1561 case 'k':
1562 case 'p':
1563 case 'P'-64:
1564 case KEY_UP:
1565 if (tnum < 0)
1566 tnum = 1;
1567 for (; tnum > 0 ; tnum--) {
1568 tpos = pos;
1569 prev_mline(&tpos, fm, fb, fa, ci.merger, mode);
1570 if (tpos.p.m >= 0) {
1571 pos = tpos;
1572 row--;
1575 break;
1577 case KEY_LEFT:
1578 case 'h':
1579 /* left */
1580 target = col - 1;
1581 if (target < 0)
1582 target = 0;
1583 break;
1584 case KEY_RIGHT:
1585 case 'l':
1586 /* right */
1587 target = col + 1;
1588 break;
1590 case '^':
1591 case 'A'-64:
1592 /* Start of line */
1593 target = 0;
1594 break;
1595 case '$':
1596 case 'E'-64:
1597 /* End of line */
1598 target = 1000;
1599 break;
1601 case 'a': /* 'after' view in patch window */
1602 mode = AFTER; modename = "after"; modehelp = after_help;
1603 refresh = 2;
1604 break;
1605 case 'b': /* 'before' view in patch window */
1606 mode = BEFORE; modename = "before"; modehelp = before_help;
1607 refresh = 2;
1608 break;
1609 case 'o': /* 'original' view in the merge window */
1610 mode = ORIG; modename = "original"; modehelp = orig_help;
1611 refresh = 2;
1612 break;
1613 case 'r': /* the 'result' view in the merge window */
1614 mode = RESULT; modename = "result"; modehelp = result_help;
1615 refresh = 2;
1616 break;
1617 case 'd':
1618 mode = BEFORE|AFTER; modename = "diff"; modehelp = diff_help;
1619 refresh = 2;
1620 break;
1621 case 'm':
1622 mode = ORIG|RESULT; modename = "merge"; modehelp = merge_help;
1623 refresh = 2;
1624 break;
1626 case '|':
1627 mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside"; modehelp = sidebyside_help;
1628 refresh = 2;
1629 break;
1631 case 'H':
1632 if (start > 0)
1633 start--;
1634 target = start + 1;
1635 refresh = 1;
1636 break;
1637 case 'L':
1638 if (start < cols)
1639 start++;
1640 target = start + 1;
1641 refresh = 1;
1642 break;
1644 case '<':
1645 prev_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1646 if (tvpos.p.m >= 0)
1647 vpos = tvpos;
1648 break;
1649 case '>':
1650 next_melmnt(&tvpos.p, fm, fb, fa, ci.merger);
1651 if (ci.merger[tvpos.p.m].type != End)
1652 vpos = tvpos;
1653 break;
1655 case '?':
1656 help_window(modehelp, merge_window_help);
1657 refresh = 2;
1658 break;
1660 case KEY_RESIZE:
1661 refresh = 2;
1662 break;
1665 if (meta == SEARCH(0)) {
1666 if (anchor == NULL ||
1667 !same_mpos(anchor->pos, pos) ||
1668 anchor->searchlen != searchlen ||
1669 anchor->col != col) {
1670 struct search_anchor *a = malloc(sizeof(*a));
1671 a->pos = pos;
1672 a->row = row;
1673 a->col = col;
1674 a->searchlen = searchlen;
1675 a->notfound = search_notfound;
1676 a->next = anchor;
1677 anchor = a;
1679 } else {
1680 while (anchor) {
1681 struct search_anchor *a = anchor;
1682 anchor = a->next;
1683 free(a);
1689 static void show_merge(char *origname, FILE *patch, int reverse,
1690 int is_merge, char *before, char *after)
1692 struct plist p;
1694 p.file = origname;
1695 if (patch) {
1696 p.start = 0;
1697 fseek(patch, 0, SEEK_END);
1698 p.end = ftell(patch);
1699 fseek(patch, 0, SEEK_SET);
1701 p.calced = 0;
1702 p.is_merge = is_merge;
1703 p.before = before;
1704 p.after = after;
1706 merge_window(&p, patch, reverse);
1709 static struct plist *patch_add_file(struct plist *pl, int *np, char *file,
1710 unsigned int start, unsigned int end)
1712 /* size of pl is 0, 16, n^2 */
1713 int n = *np;
1714 int asize;
1716 while (*file == '/')
1717 /* leading '/' are bad... */
1718 file++;
1720 if (n == 0)
1721 asize = 0;
1722 else if (n <= 16)
1723 asize = 16;
1724 else if ((n&(n-1)) == 0)
1725 asize = n;
1726 else
1727 asize = n+1; /* not accurate, but not too large */
1728 if (asize <= n) {
1729 /* need to extend array */
1730 struct plist *npl;
1731 if (asize < 16)
1732 asize = 16;
1733 else
1734 asize += asize;
1735 npl = realloc(pl, asize * sizeof(struct plist));
1736 if (!npl) {
1737 fprintf(stderr, "malloc failed - skipping %s\n", file);
1738 return pl;
1740 pl = npl;
1742 pl[n].file = file;
1743 pl[n].start = start;
1744 pl[n].end = end;
1745 pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
1746 pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
1747 pl[n].open = 1;
1748 pl[n].calced = 0;
1749 pl[n].is_merge = 0;
1750 *np = n+1;
1751 return pl;
1754 static struct plist *parse_patch(FILE *f, FILE *of, int *np)
1756 /* read a multi-file patch from 'f' and record relevant
1757 * details in a plist.
1758 * if 'of' >= 0, fd might not be seekable so we write
1759 * to 'of' and use lseek on 'of' to determine position
1761 struct plist *plist = NULL;
1763 *np = 0;
1764 while (!feof(f)) {
1765 /* first, find the start of a patch: "\n+++ "
1766 * grab the file name and scan to the end of a line
1768 char *target = "\n+++ ";
1769 char *target2 = "\n--- ";
1770 char *pos = target;
1771 int c;
1772 char name[1024];
1773 unsigned start, end;
1775 while (*pos && (c = fgetc(f)) != EOF) {
1776 if (of)
1777 fputc(c, of);
1778 if (c == *pos)
1779 pos++;
1780 else
1781 pos = target;
1783 if (c == EOF)
1784 break;
1785 assert(c == ' ');
1786 /* now read a file name */
1787 pos = name;
1788 while ((c = fgetc(f)) != EOF
1789 && c != '\t' && c != '\n' && c != ' ' &&
1790 pos - name < 1023) {
1791 *pos++ = c;
1792 if (of)
1793 fputc(c, of);
1795 *pos = 0;
1796 if (c == EOF)
1797 break;
1798 if (of)
1799 fputc(c, of);
1800 while (c != '\n' && (c = fgetc(f)) != EOF)
1801 if (of)
1802 fputc(c, of);
1804 start = ftell(of ?: f);
1806 if (c == EOF)
1807 break;
1809 /* now skip to end - "\n--- " */
1810 pos = target2+1;
1812 while (*pos && (c = fgetc(f)) != EOF) {
1813 if (of)
1814 fputc(c, of);
1815 if (c == *pos)
1816 pos++;
1817 else
1818 pos = target2;
1820 end = ftell(of ?: f);
1821 if (pos > target2)
1822 end -= (pos - target2) - 1;
1823 plist = patch_add_file(plist, np,
1824 strdup(name), start, end);
1826 return plist;
1829 static struct stream load_segment(FILE *f,
1830 unsigned int start, unsigned int end)
1832 struct stream s;
1833 s.len = end - start;
1834 s.body = malloc(s.len);
1835 if (s.body) {
1836 fseek(f, start, 0);
1837 if (fread(s.body, 1, s.len, f) != (size_t)s.len) {
1838 free(s.body);
1839 s.body = NULL;
1841 } else
1842 die();
1843 return s;
1846 static int pl_cmp(const void *av, const void *bv)
1848 const struct plist *a = av;
1849 const struct plist *b = bv;
1850 return strcmp(a->file, b->file);
1853 static int common_depth(char *a, char *b)
1855 /* find number of path segments that these two have
1856 * in common
1858 int depth = 0;
1859 while (1) {
1860 char *c;
1861 int al, bl;
1862 c = strchr(a, '/');
1863 if (c)
1864 al = c-a;
1865 else
1866 al = strlen(a);
1867 c = strchr(b, '/');
1868 if (c)
1869 bl = c-b;
1870 else
1871 bl = strlen(b);
1872 if (al == 0 || al != bl || strncmp(a, b, al) != 0)
1873 return depth;
1874 a += al;
1875 while (*a == '/')
1876 a++;
1877 b += bl;
1878 while (*b == '/')
1879 b++;
1881 depth++;
1885 static struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
1887 /* any parent of file that is not a parent of curr
1888 * needs to be added to pl
1890 int d = common_depth(file, curr);
1891 char *buf = curr;
1892 while (d) {
1893 char *c = strchr(file, '/');
1894 int l;
1895 if (c)
1896 l = c-file;
1897 else
1898 l = strlen(file);
1899 file += l;
1900 curr += l;
1901 while (*file == '/')
1902 file++;
1903 while (*curr == '/')
1904 curr++;
1905 d--;
1907 while (*file) {
1908 if (curr > buf && curr[-1] != '/')
1909 *curr++ = '/';
1910 while (*file && *file != '/')
1911 *curr++ = *file++;
1912 while (*file == '/')
1913 file++;
1914 *curr = '\0';
1915 if (*file)
1916 pl = patch_add_file(pl, np, strdup(buf),
1917 0, 0);
1919 return pl;
1922 static struct plist *sort_patches(struct plist *pl, int *np)
1924 /* sort the patches, add directory names, and re-sort */
1925 char curr[1024];
1926 char *prev;
1927 int parents[100];
1928 int prevnode[100];
1929 int i, n;
1930 qsort(pl, *np, sizeof(struct plist), pl_cmp);
1931 curr[0] = 0;
1932 n = *np;
1933 for (i = 0; i < n; i++)
1934 pl = add_dir(pl, np, pl[i].file, curr);
1936 qsort(pl, *np, sizeof(struct plist), pl_cmp);
1938 /* array is now stable, so set up parent pointers */
1939 n = *np;
1940 curr[0] = 0;
1941 prevnode[0] = -1;
1942 prev = "";
1943 for (i = 0; i < n; i++) {
1944 int d = common_depth(prev, pl[i].file);
1945 if (d == 0)
1946 pl[i].parent = -1;
1947 else {
1948 pl[i].parent = parents[d-1];
1949 pl[pl[i].parent].last = i;
1951 pl[i].prev = prevnode[d];
1952 if (pl[i].prev > -1)
1953 pl[pl[i].prev].next = i;
1954 prev = pl[i].file;
1955 parents[d] = i;
1956 prevnode[d] = i;
1957 prevnode[d+1] = -1;
1959 return pl;
1962 /* determine how much we need to stripe of the front of
1963 * paths to find them from current directory. This is
1964 * used to guess correct '-p' value.
1966 static int get_strip(char *file)
1968 int fd;
1969 int strip = 0;
1971 while (file && *file) {
1972 fd = open(file, O_RDONLY);
1973 if (fd >= 0) {
1974 close(fd);
1975 return strip;
1977 strip++;
1978 file = strchr(file, '/');
1979 if (file)
1980 while (*file == '/')
1981 file++;
1983 return -1;
1987 static int set_prefix(struct plist *pl, int n, int strip)
1989 int i;
1990 for (i = 0; i < 4 && i < n && strip < 0; i++)
1991 strip = get_strip(pl[i].file);
1993 if (strip < 0) {
1994 fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
1995 Cmd);
1996 return 0;
1998 for (i = 0; i < n; i++) {
1999 char *p = pl[i].file;
2000 int j;
2001 for (j = 0; j < strip; j++) {
2002 if (p)
2003 p = strchr(p, '/');
2004 while (p && *p == '/')
2005 p++;
2007 if (p == NULL) {
2008 fprintf(stderr, "%s: cannot strip %d segments from %s\n",
2009 Cmd, strip, pl[i].file);
2010 return 0;
2012 pl[i].file = p;
2014 return 1;
2017 static void calc_one(struct plist *pl, FILE *f, int reverse)
2019 struct stream s1, s2;
2020 struct stream s = load_segment(f, pl->start, pl->end);
2021 struct stream sf;
2022 if (pl->is_merge) {
2023 if (reverse)
2024 split_merge(s, &sf, &s2, &s1);
2025 else
2026 split_merge(s, &sf, &s1, &s2);
2027 pl->chunks = 0;
2028 } else {
2029 sf = load_file(pl->file);
2030 if (reverse)
2031 pl->chunks = split_patch(s, &s2, &s1);
2032 else
2033 pl->chunks = split_patch(s, &s1, &s2);
2035 if (sf.body == NULL) {
2036 pl->wiggles = pl->conflicts = -1;
2037 } else {
2038 struct file ff, fp1, fp2;
2039 struct csl *csl1, *csl2;
2040 struct ci ci;
2041 ff = split_stream(sf, ByWord);
2042 fp1 = split_stream(s1, ByWord);
2043 fp2 = split_stream(s2, ByWord);
2044 if (pl->chunks)
2045 csl1 = pdiff(ff, fp1, pl->chunks);
2046 else
2047 csl1 = diff(ff, fp1);
2048 csl2 = diff(fp1, fp2);
2049 ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1);
2050 pl->wiggles = ci.wiggles;
2051 pl->conflicts = ci.conflicts;
2052 free(csl1);
2053 free(csl2);
2054 free(ff.list);
2055 free(fp1.list);
2056 free(fp2.list);
2059 free(s1.body);
2060 free(s2.body);
2061 free(s.body);
2062 free(sf.body);
2063 pl->calced = 1;
2066 static int get_prev(int pos, struct plist *pl, int n, int mode)
2068 int found = 0;
2069 if (pos == -1)
2070 return pos;
2071 do {
2072 if (pl[pos].prev == -1)
2073 return pl[pos].parent;
2074 pos = pl[pos].prev;
2075 while (pl[pos].open &&
2076 pl[pos].last >= 0)
2077 pos = pl[pos].last;
2078 if (pl[pos].last >= 0)
2079 /* always see directories */
2080 found = 1;
2081 else if (mode == 0)
2082 found = 1;
2083 else if (mode <= 1 && pl[pos].wiggles > 0)
2084 found = 1;
2085 else if (mode <= 2 && pl[pos].conflicts > 0)
2086 found = 1;
2087 } while (pos >= 0 && !found);
2088 return pos;
2091 static int get_next(int pos, struct plist *pl, int n, int mode,
2092 FILE *f, int reverse)
2094 int found = 0;
2095 if (pos == -1)
2096 return pos;
2097 do {
2098 if (pl[pos].open) {
2099 if (pos + 1 < n)
2100 pos = pos+1;
2101 else
2102 return -1;
2103 } else {
2104 while (pos >= 0 && pl[pos].next == -1)
2105 pos = pl[pos].parent;
2106 if (pos >= 0)
2107 pos = pl[pos].next;
2109 if (pl[pos].calced == 0 && pl[pos].end)
2110 calc_one(pl+pos, f, reverse);
2111 if (pl[pos].last >= 0)
2112 /* always see directories */
2113 found = 1;
2114 else if (mode == 0)
2115 found = 1;
2116 else if (mode <= 1 && pl[pos].wiggles > 0)
2117 found = 1;
2118 else if (mode <= 2 && pl[pos].conflicts > 0)
2119 found = 1;
2120 } while (pos >= 0 && !found);
2121 return pos;
2124 static void draw_one(int row, struct plist *pl, FILE *f, int reverse)
2126 char hdr[12];
2127 hdr[0] = 0;
2129 if (pl == NULL) {
2130 move(row, 0);
2131 clrtoeol();
2132 return;
2134 if (pl->calced == 0 && pl->end)
2135 /* better load the patch and count the chunks */
2136 calc_one(pl, f, reverse);
2137 if (pl->end == 0) {
2138 strcpy(hdr, " ");
2139 } else {
2140 if (pl->chunks > 99)
2141 strcpy(hdr, "XX");
2142 else
2143 sprintf(hdr, "%2d", pl->chunks);
2144 if (pl->wiggles > 99)
2145 strcpy(hdr+2, " XX");
2146 else
2147 sprintf(hdr+2, " %2d", pl->wiggles);
2148 if (pl->conflicts > 99)
2149 strcpy(hdr+5, " XX ");
2150 else
2151 sprintf(hdr+5, " %2d ", pl->conflicts);
2153 if (pl->end)
2154 strcpy(hdr+9, "= ");
2155 else if (pl->open)
2156 strcpy(hdr+9, "+ ");
2157 else
2158 strcpy(hdr+9, "- ");
2160 mvaddstr(row, 0, hdr);
2161 mvaddstr(row, 11, pl->file);
2162 clrtoeol();
2165 static char *main_help[] = {
2166 " You are using the \"browse\" mode of wiggle.",
2167 "This page shows a list of files in a patch together with",
2168 "the directories that contain them.",
2169 "A directory is indicated by a '+' if the contents are",
2170 "listed or a '-' if the contents are hidden. A file is",
2171 "indicated by an '='. Typing <space> or <return> will",
2172 "expose or hide a directory, and will visit a file.",
2174 "The three columns of numbers are:",
2175 " Ch The number of patch chunks which applied to",
2176 " this file",
2177 " Wi The number of chunks that needed to be wiggled",
2178 " in to place",
2179 " Co The number of chunks that created an unresolvable",
2180 " conflict",
2182 "Keystrokes recognised in this page are:",
2183 " ? Display this help",
2184 " SPC On a directory, toggle hiding of contents",
2185 " On file, visit the file",
2186 " RTN Same as SPC",
2187 " q Quit program",
2188 " n,j,DOWN Go to next line",
2189 " p,k,UP Go to previous line",
2191 " A list All files",
2192 " W only list files with a wiggle or a conflict",
2193 " C only list files with a conflict",
2194 NULL
2197 static void main_window(struct plist *pl, int n, FILE *f, int reverse)
2199 /* The main window lists all files together with summary information:
2200 * number of chunks, number of wiggles, number of conflicts.
2201 * The list is scrollable
2202 * When a entry is 'selected', we switch to the 'file' window
2203 * The list can be condensed by removing files with no conflict
2204 * or no wiggles, or removing subdirectories
2206 * We record which file in the list is 'current', and which
2207 * screen line it is on. We try to keep things stable while
2208 * moving.
2210 * Counts are printed before the name using at most 2 digits.
2211 * Numbers greater than 99 are XX
2212 * Ch Wi Co File
2213 * 27 5 1 drivers/md/md.c
2215 * A directory show the sum in all children.
2217 * Commands:
2218 * select: enter, space, mouseclick
2219 * on file, go to file window
2220 * on directory, toggle open
2221 * up: k, p, control-p uparrow
2222 * Move to previous open object
2223 * down: j, n, control-n, downarrow
2224 * Move to next open object
2226 * A W C: select All Wiggles or Conflicts
2227 * mode
2230 int pos = 0; /* position in file */
2231 int row = 1; /* position on screen */
2232 int rows = 0; /* size of screen in rows */
2233 int cols = 0;
2234 int tpos, i;
2235 int refresh = 2;
2236 int c = 0;
2237 int mode = 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2239 term_init();
2240 pl = sort_patches(pl, &n);
2242 while (1) {
2243 if (refresh == 2) {
2244 clear(); (void)attrset(0);
2245 attron(A_BOLD);
2246 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2247 move(2, 0);
2248 attroff(A_BOLD);
2249 refresh = 1;
2251 if (row < 1 || row >= rows)
2252 refresh = 1;
2253 if (refresh) {
2254 refresh = 0;
2255 getmaxyx(stdscr, rows, cols);
2256 if (row >= rows + 3)
2257 row = (rows+1)/2;
2258 if (row >= rows)
2259 row = rows-1;
2260 tpos = pos;
2261 for (i = row; i > 1; i--) {
2262 tpos = get_prev(tpos, pl, n, mode);
2263 if (tpos == -1) {
2264 row = row - i + 1;
2265 break;
2268 /* Ok, row and pos could be trustworthy now */
2269 tpos = pos;
2270 for (i = row; i >= 1; i--) {
2271 draw_one(i, &pl[tpos], f, reverse);
2272 tpos = get_prev(tpos, pl, n, mode);
2274 tpos = pos;
2275 for (i = row+1; i < rows; i++) {
2276 tpos = get_next(tpos, pl, n, mode, f, reverse);
2277 if (tpos >= 0)
2278 draw_one(i, &pl[tpos], f, reverse);
2279 else
2280 draw_one(i, NULL, f, reverse);
2283 {char bb[20];
2284 sprintf(bb, "%d", c);
2285 mvaddstr(0, 70, bb);
2286 clrtoeol();
2288 move(row, 9);
2289 c = getch();
2290 switch (c) {
2291 case 'j':
2292 case 'n':
2293 case 'N':
2294 case 'N'-64:
2295 case KEY_DOWN:
2296 tpos = get_next(pos, pl, n, mode, f, reverse);
2297 if (tpos >= 0) {
2298 pos = tpos;
2299 row++;
2301 break;
2302 case 'k':
2303 case 'p':
2304 case 'P':
2305 case 'P'-64:
2306 case KEY_UP:
2307 tpos = get_prev(pos, pl, n, mode);
2308 if (tpos >= 0) {
2309 pos = tpos;
2310 row--;
2312 break;
2314 case ' ':
2315 case 13:
2316 if (pl[pos].end == 0) {
2317 pl[pos].open = !pl[pos].open;
2318 refresh = 1;
2319 } else {
2320 /* diff_window(&pl[pos], f); */
2321 merge_window(&pl[pos], f, reverse);
2322 refresh = 2;
2324 break;
2325 case 27: /* escape */
2326 mvaddstr(0, 70, "ESC..."); clrtoeol();
2327 c = getch();
2328 switch (c) {
2330 break;
2331 case 'q':
2332 return;
2334 case 'A':
2335 mode = 0; refresh = 1;
2336 break;
2337 case 'W':
2338 mode = 1; refresh = 1;
2339 break;
2340 case 'C':
2341 mode = 2; refresh = 1;
2342 break;
2344 case '?':
2345 help_window(main_help, NULL);
2346 refresh = 2;
2347 break;
2349 case KEY_RESIZE:
2350 refresh = 2;
2351 break;
2356 static void catch(int sig)
2358 if (sig == SIGINT) {
2359 signal(sig, catch);
2360 return;
2362 nocbreak();
2363 nl();
2364 endwin();
2365 printf("Died on signal %d\n", sig);
2366 exit(2);
2369 static void term_init(void)
2372 static int init_done = 0;
2374 if (init_done)
2375 return;
2376 init_done = 1;
2378 signal(SIGINT, catch);
2379 signal(SIGQUIT, catch);
2380 signal(SIGTERM, catch);
2381 signal(SIGBUS, catch);
2382 signal(SIGSEGV, catch);
2384 initscr(); cbreak(); noecho();
2385 start_color();
2386 use_default_colors();
2387 if (!has_colors()) {
2388 a_delete = A_UNDERLINE;
2389 a_added = A_BOLD;
2390 a_common = A_NORMAL;
2391 a_sep = A_STANDOUT;
2392 a_already = A_STANDOUT;
2393 } else {
2394 init_pair(1, COLOR_WHITE, COLOR_RED);
2395 a_delete = COLOR_PAIR(1);
2396 init_pair(2, COLOR_WHITE, COLOR_BLUE);
2397 a_added = COLOR_PAIR(2);
2398 a_common = A_NORMAL;
2399 init_pair(3, COLOR_WHITE, COLOR_GREEN);
2400 a_sep = COLOR_PAIR(3); a_sep = A_STANDOUT;
2401 init_pair(4, -1, COLOR_YELLOW);
2402 a_void = COLOR_PAIR(4);
2403 init_pair(5, COLOR_BLUE, -1);
2404 a_unmatched = COLOR_PAIR(5);
2405 init_pair(6, COLOR_CYAN, -1);
2406 a_extra = COLOR_PAIR(6);
2408 init_pair(7, COLOR_BLACK, COLOR_CYAN);
2409 a_already = COLOR_PAIR(7);
2411 nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
2412 mousemask(ALL_MOUSE_EVENTS, NULL);
2415 int vpatch(int argc, char *argv[], int patch, int strip,
2416 int reverse, int replace)
2418 /* NOTE argv[0] is first arg...
2419 * Behaviour depends on number of args:
2420 * 0: A multi-file patch is read from stdin
2421 * 1: if 'patch', parse it as a multi-file patch and allow
2422 * the files to be browsed.
2423 * if filename ends '.rej', then treat it as a patch again
2424 * a file with the same basename
2425 * Else treat the file as a merge (with conflicts) and view it.
2426 * 2: First file is original, second is patch
2427 * 3: Files are: original previous new. The diff between 'previous' and
2428 * 'new' needs to be applied to 'original'.
2430 * If a multi-file patch is being read, 'strip' tells how many
2431 * path components to stripe. If it is -1, we guess based on
2432 * existing files.
2433 * If 'reverse' is given, when we invert any patch or diff
2434 * If 'replace' then we save the resulting merge.
2436 FILE *in;
2437 FILE *f;
2438 struct plist *pl;
2439 int num_patches;
2441 switch (argc) {
2442 default:
2443 fprintf(stderr, "%s: too many file names given.\n", Cmd);
2444 exit(1);
2446 case 0: /* stdin is a patch */
2447 if (lseek(fileno(stdin), 0L, 1) == -1) {
2448 /* cannot seek, so need to copy to a temp file */
2449 f = tmpfile();
2450 if (!f) {
2451 fprintf(stderr, "%s: Cannot create temp file\n", Cmd);
2452 exit(1);
2454 pl = parse_patch(stdin, f, &num_patches);
2455 in = f;
2456 } else {
2457 pl = parse_patch(stdin, NULL, &num_patches);
2458 in = fdopen(dup(0), "r");
2460 /* use stderr for keyboard input */
2461 dup2(2, 0);
2462 if (set_prefix(pl, num_patches, strip) == 0) {
2463 fprintf(stderr, "%s: aborting\n", Cmd);
2464 exit(2);
2466 main_window(pl, num_patches, in, reverse);
2467 break;
2469 case 1: /* a patch, a .rej, or a merge file */
2470 f = fopen(argv[0], "r");
2471 if (!f) {
2472 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2473 exit(1);
2475 if (patch) {
2476 pl = parse_patch(f, NULL, &num_patches);
2477 if (set_prefix(pl, num_patches, strip) == 0) {
2478 fprintf(stderr, "%s: aborting\n", Cmd);
2479 exit(2);
2481 main_window(pl, num_patches, f, reverse);
2482 } else if (strlen(argv[0]) > 4 &&
2483 strcmp(argv[0]+strlen(argv[0])-4, ".rej") == 0) {
2484 char *origname = strdup(argv[0]);
2485 origname[strlen(origname) - 4] = '\0';
2486 show_merge(origname, f, reverse, 0, NULL, NULL);
2487 } else
2488 show_merge(argv[0], f, reverse, 1, NULL, NULL);
2490 break;
2491 case 2: /* an orig and a diff/.ref */
2492 f = fopen(argv[1], "r");
2493 if (!f) {
2494 fprintf(stderr, "%s: cannot open %s\n", Cmd, argv[0]);
2495 exit(1);
2497 show_merge(argv[0], f, reverse, 0, NULL, NULL);
2498 break;
2499 case 3: /* orig, before, after */
2500 show_merge(argv[0], NULL, reverse, 1, argv[1], argv[2]);
2501 break;
2504 nocbreak();
2505 nl();
2506 endwin();
2507 exit(0);