2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2013 Neil Brown <neilb@suse.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program.
22 * Email: <neilb@suse.de>
26 * vpatch - visual front end for wiggle - aka Browse mode.
28 * "files" display, lists all files with statistics
29 * - can hide various lines including subdirectories
30 * and files without wiggles or conflicts
31 * "merge" display shows various views of merged file with different
32 * parts in different colours.
34 * The window can be split horizontally to show the original and result
35 * beside the diff, and each different branch can be shown alone.
48 static void term_init(int raw
);
50 static int intr_kills
= 0;
52 /* global attributes */
53 unsigned int a_delete
, a_added
, a_common
, a_sep
, a_void
,
54 a_unmatched
, a_extra
, a_already
;
55 unsigned int a_has_conflicts
, a_has_wiggles
, a_no_wiggles
, a_saved
;
57 /******************************************************************
59 * We display help in an insert, leaving 5 columns left and right,
60 * and 2 rows top and bottom, but at most 58x15 plus border
62 * SPC or RTN moves down or to next page
63 * BKSPC goes backwards
64 * 'q' returns to origin screen
65 * '?' show help on help
66 * left and right scroll help view
68 * A help text is an array of lines of text
72 " You are viewing the help page for the help viewer.",
73 "You normally get here by typing '?'",
75 "The following keystrokes work in the help viewer:",
76 " ? display this help message",
77 " q return to previous view",
78 " SPC move forward through help document",
80 " BKSP move backward through help document",
81 " RIGHT scroll help window so text on the right appears",
82 " LEFT scroll help window so text on the left appears",
86 char *help_missing
[] = {
87 "The file that this patch applies to appears",
89 "Please type 'q' to continue",
93 char *help_corrupt
[] = {
94 "This patch appears to be corrupt",
95 "Please type 'q' to continue",
99 /* We can give one or two pages to display in the help window.
100 * The first is specific to the current context. The second
101 * is optional and may provide help in a more broad context.
103 static int help_window(char *page1
[], char *page2
[], int query
)
113 getmaxyx(stdscr
, rows
, cols
);
119 left
= (cols
-58)/2 - 1;
127 top
= (rows
-15)/2 - 1;
131 /* Draw a border around the 'help' area */
132 (void)attrset(A_STANDOUT
);
133 for (c
= left
; c
< left
+cols
; c
++) {
134 mvaddch(top
-1, c
, '-');
135 mvaddch(top
+rows
, c
, '-');
137 for (r
= top
; r
< top
+ rows
; r
++) {
138 mvaddch(r
, left
-1, '|');
139 mvaddch(r
, left
+cols
, '|');
141 mvaddch(top
-1, left
-1, '/');
142 mvaddch(top
-1, left
+cols
, '\\');
143 mvaddch(top
+rows
, left
-1, '\\');
144 mvaddch(top
+rows
, left
+cols
, '/');
146 mvaddstr(top
-1, left
+ cols
/2 - 4, "Question");
147 mvaddstr(top
+rows
, left
+ cols
/2 - 9,
148 "Answer Y, N, or Q.");
150 mvaddstr(top
-1, left
+ cols
/2 - 9,
151 "HELP - 'q' to exit");
152 mvaddstr(top
+rows
, left
+cols
/2 - 17,
153 "Press SPACE for more, '?' for help");
155 (void)attrset(A_NORMAL
);
158 char **lnp
= page
+ line
;
160 /* Draw as much of the page at the current offset
163 for (r
= 0; r
< rows
; r
++) {
171 while (*ln
&& sh
> 0) {
175 for (c
= 0; c
< cols
; c
++) {
181 mvaddch(top
+r
, left
+c
, chr
);
184 move(top
+rows
-1, left
);
205 if (page1
!= help_help
)
206 help_window(help_help
, NULL
, 0);
209 case '\r': /* page-down */
210 for (r
= 0; r
< rows
-2; r
++)
213 if (!page
[line
] && !query
) {
224 case '\b': /* page up */
260 static char *typenames
[] = {
262 [Unmatched
] = "Unmatched",
263 [Unchanged
] = "Unchanged",
264 [Extraneous
] = "Extraneous",
265 [Changed
] = "Changed",
266 [Conflict
] = "Conflict",
267 [AlreadyApplied
] = "AlreadyApplied",
270 /* When we merge the original and the diff together we need
271 * to keep track of where everything came from.
272 * When we display the different views, we need to be able to
273 * select certain portions of the whole document.
274 * These flags are used to identify what is present, and to
275 * request different parts be extracted. They also help
276 * guide choice of colour.
282 #define CHANGES 16 /* A change is visible here,
283 * so 2 streams need to be shown */
284 #define WIGGLED 32 /* a conflict that was successfully resolved */
285 #define CONFLICTED 64 /* a conflict that was not successfully resolved */
287 /* Displaying a Merge.
288 * The first step is to linearise the merge. The merge in inherently
289 * parallel with before/after streams. However much of the whole document
290 * is linear as normally much of the original in unchanged.
291 * All parallelism comes from the patch. This normally produces two
292 * parallel stream, but in the case of a conflict can produce three.
293 * For browsing the merge we only ever show two alternates in-line.
294 * When there are three we use two panes with 1 or 2 alternates in each.
295 * So to linearise the two streams we find lines that are completely
296 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
297 * a region where there are changes. We include everything between
298 * these twice, in two separate passes. The exact interpretation of the
299 * passes is handled at a higher level but will be one of:
300 * original and result
302 * original and after (for a conflict)
303 * This is all encoded in the 'struct merge'. An array of these describes
304 * the whole document.
306 * At any position in the merge we can be in one of 3 states:
307 * 0: unchanged section
311 * So to walk a merge in display order we need a position in the merge,
312 * a current state, and when in a changed section, we need to know the
313 * bounds of that changed section.
314 * This is all encoded in 'struct mpos'.
316 * Each location may or may not be visible depending on certain
319 * Also, some locations might be 'invalid' in that they don't need to be displayed.
320 * For example when the patch leaves a section of the original unchanged,
321 * we only need to see the original - the before/after sections are treated
322 * as invalid and are not displayed.
323 * The visibility of newlines is crucial and guides the display. One line
324 * of displayed text is all the visible sections between two visible newlines.
326 * Counting lines is a bit tricky. We only worry about line numbers in the
327 * original (stream 0) as these could compare with line numbers mentioned in
329 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
330 * That way we don't get a full counted line until we see the first char after the
331 * newline, so '+' lines are counted with the previous line.
335 int m
; /* merger index */
336 int s
; /* stream 0,1,2 for a,b,c */
337 int o
; /* offset in that stream */
338 int lineno
; /* Counts newlines in stream 0
339 * set lsb when see newline.
340 * add one when not newline and lsb set
344 struct mp p
, /* the current point (end of a line) */
345 lo
, /* eol for start of the current group */
346 hi
; /* eol for end of the current group */
348 * 0 if on an unchanged (lo/hi not meaningful)
349 * 1 if on the '-' of a diff,
350 * 2 if on the '+' of a diff
355 struct mp pos
; /* where in the document we are (an element) */
356 int offset
; /* which char in that element */
357 int target
; /* display column - or -1 if we are looking for 'pos' */
358 int col
; /* where we found pos or target */
359 int width
; /* Size of char, for moving to the right */
360 int alt
; /* Cursor is in alternate window */
363 /* used for checking location during search */
364 static int same_mp(struct mp a
, struct mp b
)
370 static int same_mpos(struct mpos a
, struct mpos b
)
372 return same_mp(a
.p
, b
.p
) &&
373 (a
.state
== b
.state
|| a
.state
== 0 || b
.state
== 0);
376 /* Check if a particular stream is meaningful in a particular merge
377 * section. e.g. in an Unchanged section, only stream 0, the
378 * original, is meaningful. This is used to avoid walking down
381 static int stream_valid(int s
, enum mergetype type
)
403 * Advance the 'pos' in the current mergepos returning the next
405 * This walks the merges in sequence, and the streams within
408 static struct elmnt
next_melmnt(struct mp
*pos
,
409 struct file fm
, struct file fb
, struct file fa
,
413 while (pos
->m
< 0 || m
[pos
->m
].type
!= End
) {
414 int l
= 0; /* Length remaining in current merge section */
428 /* Offset has reached length, choose new stream or
437 } while (!stream_valid(pos
->s
, m
[pos
->m
].oldtype
));
441 if (pos
->m
== -1 || m
[pos
->m
].type
== End
) {
443 e
.start
= NULL
; e
.hash
= 0; e
.len
= 0;
447 default: /* keep compiler happy */
451 if (ends_line(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
453 return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
454 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
455 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
459 /* step current position.p backwards */
460 static struct elmnt
prev_melmnt(struct mp
*pos
,
461 struct file fm
, struct file fb
, struct file fa
,
465 if (m
[pos
->m
].a
+ pos
->o
< fm
.elcnt
&&
466 ends_line(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
473 while (pos
->m
>= 0 && pos
->o
< 0) {
480 } while (pos
->m
>= 0 &&
481 !stream_valid(pos
->s
, m
[pos
->m
].oldtype
));
485 pos
->o
= m
[pos
->m
].al
-1;
488 pos
->o
= m
[pos
->m
].bl
-1;
491 pos
->o
= m
[pos
->m
].cl
-1;
496 if (pos
->m
< 0 || m
[pos
->m
].type
== End
) {
498 e
.start
= NULL
; e
.hash
= 0; e
.len
= 0;
502 default: /* keep compiler happy */
503 case 0: return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
504 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
505 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
509 /* 'visible' not only checks if this stream in this merge should be
510 * visible in this mode, but also chooses which colour/highlight to use
513 static int visible(int mode
, struct merge
*m
, struct mpos
*pos
)
516 int stream
= pos
->p
.s
;
522 else if (mode
& RESULT
)
523 type
= m
[pos
->p
.m
].type
;
525 type
= m
[pos
->p
.m
].oldtype
;
526 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
528 case End
: /* The END is always visible */
530 case Unmatched
: /* Visible in ORIG and RESULT */
531 if (mode
& (ORIG
|RESULT
))
534 case Unchanged
: /* visible everywhere, but only show stream 0 */
538 case Extraneous
: /* stream 2 is visible in BEFORE and AFTER */
539 if ((mode
& (BEFORE
|AFTER
))
543 case Changed
: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
545 (mode
& (ORIG
|BEFORE
)))
548 (mode
& (RESULT
|AFTER
)))
555 return a_unmatched
| A_REVERSE
;
559 return a_extra
| A_UNDERLINE
;
562 if (mode
& (AFTER
|RESULT
))
563 return a_added
| A_UNDERLINE
;
570 if (mode
& (ORIG
|RESULT
))
575 return a_delete
| A_UNDERLINE
;
579 return a_added
| A_UNDERLINE
;
587 /* checkline creates a summary of the sort of changes that
588 * are in a line, returning an "or" of
593 static int check_line(struct mpos pos
, struct file fm
, struct file fb
,
595 struct merge
*m
, int mode
)
604 int type
= m
[pos
.p
.m
].oldtype
;
606 type
= m
[pos
.p
.m
].type
;
609 else if (type
== Conflict
) {
610 rv
|= CONFLICTED
| CHANGES
;
611 } else if (type
== AlreadyApplied
) {
613 if (mode
& (BEFORE
|AFTER
))
615 } else if (type
== Extraneous
) {
616 if (fb
.list
[m
[pos
.p
.m
].b
].start
[0] == '\0') {
617 /* hunk headers don't count as wiggles
618 * and nothing before a hunk header
619 * can possibly be part of this 'line' */
624 } else if (type
== Unmatched
)
627 if (m
[pos
.p
.m
].in_conflict
> 1)
628 rv
|= CONFLICTED
| CHANGES
;
629 if (m
[pos
.p
.m
].in_conflict
== 1 &&
630 (pos
.p
.o
< m
[pos
.p
.m
].lo
||
631 pos
.p
.o
> m
[pos
.p
.m
].hi
))
632 rv
|= CONFLICTED
| CHANGES
;
633 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
634 } while (e
.start
!= NULL
&&
636 || visible(mode
, m
, &pos
) == -1));
637 /* This is a bit of a hack... If the end-of-line just
638 * before this line was changed, then quite possibly this
639 * line is part of a change too. This is particularly important
640 * when --ignore-blanks is in effect as newlines are not separate
641 * from other words. It could be that this test needs to be
642 * strengthened when I have examined more cases.
644 if (e
.start
&& m
[pos
.p
.m
].oldtype
== Changed
)
647 if (unmatched
&& (rv
& CHANGES
))
652 /* Find the next line in the merge which is visible.
653 * If we hit the end of a conflicted set during pass-1
654 * we rewind for pass-2.
655 * 'mode' tells which bits we want to see, possible one of
656 * the 4 parts (before/after/orig/result) or one of the pairs
657 * before+after or orig+result.
659 static void next_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
661 struct merge
*m
, int mode
)
670 struct elmnt e
= next_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
674 visible(mode
, m
, pos
) >= 0)
677 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
679 if ((mode2
& CHANGES
) && pos
->state
== 0) {
680 /* Just entered a diff-set */
683 } else if (!(mode2
& CHANGES
) && pos
->state
) {
684 /* Come to the end of a diff-set */
685 switch (pos
->state
) {
687 /* Need to record the end */
689 /* time for another pass */
694 /* finished final pass */
699 mask
= ORIG
|RESULT
|BEFORE
|AFTER
;
700 switch (pos
->state
) {
702 mask
&= ~(RESULT
|AFTER
);
705 mask
&= ~(ORIG
|BEFORE
);
708 } while (visible(mode
&mask
, m
, pos
) < 0);
712 /* Move to previous line - simply the reverse of next_mline */
713 static void prev_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
715 struct merge
*m
, int mode
)
726 struct elmnt e
= prev_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
730 visible(mode
, m
, pos
) >= 0)
733 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
735 if ((mode2
& CHANGES
) && pos
->state
== 0) {
736 /* Just entered a diff-set */
739 } else if (!(mode2
& CHANGES
) && pos
->state
) {
740 /* Come to the end (start) of a diff-set */
741 switch (pos
->state
) {
743 /* finished final pass */
747 /* Need to record the start */
749 /* time for another pass */
755 mask
= ORIG
|RESULT
|BEFORE
|AFTER
;
756 switch (pos
->state
) {
758 mask
&= ~(RESULT
|AFTER
);
761 mask
&= ~(ORIG
|BEFORE
);
764 } while (visible(mode
&mask
, m
, pos
) < 0);
767 /* blank a whole row of display */
768 static void blank(int row
, int start
, int cols
, unsigned int attr
)
776 /* search of a string on one display line. If found, update the
780 static int mcontains(struct mpos pos
,
781 struct file fm
, struct file fb
, struct file fa
,
783 int mode
, char *search
, struct cursor
*curs
,
784 int dir
, int ignore_case
)
786 /* See if any of the files, between start of this line and here,
787 * contain the search string.
788 * However this is modified by dir:
789 * -2: find last match *before* curs
790 * -1: find last match at-or-before curs
791 * 1: find first match at-or-after curs
792 * 2: find first match *after* curs
794 * We only test for equality with curs, so if it is on a different
795 * line it will not be found and everything is before/after.
796 * As we search from end-of-line to start we find the last
798 * For a forward search, we stop when we find curs.
799 * For a backward search, we forget anything found when we find curs.
805 int len
= strlen(search
);
808 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
809 if (e
.start
&& e
.start
[0]) {
812 if (same_mp(pos
.p
, curs
->pos
))
813 curs_i
= curs
->offset
;
816 for (i
= e
.len
-1; i
>= 0; i
--) {
817 if (i
== curs_i
&& dir
== -1)
818 /* next match is the one we want */
820 if (i
== curs_i
&& dir
== 2)
821 /* future matches not accepted */
823 if ((!found
|| dir
> 0) &&
824 (ignore_case
? strncasecmp
: strncmp
)
825 (e
.start
+i
, search
, len
) == 0) {
830 if (i
== curs_i
&& dir
== -2)
831 /* next match is the one we want */
833 if (i
== curs_i
&& dir
== 1)
834 /* future matches not accepted */
838 } while (e
.start
!= NULL
&&
840 || visible(mode
, m
, &pos
) == -1));
849 /* Drawing the display window.
850 * There are 7 different ways we can display the data, each
851 * of which can be configured by a keystroke:
852 * o original - just show the original file with no changes, but still
853 * with highlights of what is changed or unmatched
854 * r result - show just the result of the merge. Conflicts just show
855 * the original, not the before/after options
856 * b before - show the 'before' stream of the patch
857 * a after - show the 'after' stream of the patch
858 * d diff - show just the patch, both before and after
859 * m merge - show the full merge with -+ sections for changes.
860 * If point is in a wiggled or conflicted section the
861 * window is split horizontally and the diff is shown
862 * in the bottom window
863 * | sidebyside - two panes, left and right. Left holds the merge,
864 * right holds the diff. In the case of a conflict,
865 * left holds orig/after, right holds before/after
867 * The horizontal split for 'merge' mode is managed as follows.
868 * - The window is split when we first visit a line that contains
869 * a wiggle or a conflict, and the second pane is removed when
870 * we next visit a line that contains no changes (is fully Unchanged).
871 * - to display the second pane, we find a visible end-of-line in the
872 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
873 * the we centre that line.
874 * - We need to rewind to an unchanged section, and wind forward again
875 * to make sure that 'lo' and 'hi' are set properly.
876 * - every time we move, we redraw the second pane (see how that goes).
879 /* draw_mside draws one text line or, in the case of sidebyside, one side
881 * The 'mode' tells us what to draw via the 'visible()' function.
882 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
883 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
884 * The desired cursor position is given in 'target' the actual end
885 * cursor position (allowing e.g. for tabs) is returned in *colp.
887 static void draw_mside(int mode
, int row
, int offset
, int start
, int cols
,
888 struct file fm
, struct file fb
, struct file fa
,
896 unsigned int tag_attr
;
900 default: /* keep compiler happy */
901 case 0: /* unchanged line */
905 case 1: /* 'before' text */
908 if ((mode
& ORIG
) && (mode
& CONFLICTED
)) {
910 tag_attr
= a_delete
| A_REVERSE
;
912 mode
&= (ORIG
|BEFORE
);
914 case 2: /* the 'after' part */
917 mode
&= (AFTER
|RESULT
);
921 if (visible(mode
, m
, &pos
) < 0) {
922 /* Not visible, just draw a blank */
923 blank(row
, offset
, cols
, a_void
);
933 (void)attrset(tag_attr
);
934 mvaddch(row
, offset
, tag
);
937 (void)attrset(A_NORMAL
);
939 if (check_line(pos
, fm
, fb
, fa
, m
, mode
))
942 /* find previous visible newline, or start of file */
944 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
945 while (e
.start
!= NULL
&&
947 visible(mode
, m
, &pos
) == -1));
954 e
= next_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
958 if (visible(mode
, m
, &pos
) == -1)
962 c
= (unsigned char *)e
.start
- e
.prefix
;
964 attr
= visible(mode
, m
, &pos
);
965 if ((attr
== a_unmatched
|| attr
== a_extra
) &&
967 /* Only highlight spaces if there is a tab nearby */
968 for (l
= 0; l
< e
.plen
+ e
.prefix
; l
++)
971 if (!highlight_space
&& (c
[0] == ' ' || c
[0] == '\t')) {
972 /* always highlight space/tab at end-of-line */
973 struct mp nxt
= pos
.p
;
974 struct elmnt nxte
= next_melmnt(&nxt
, fm
, fb
, fa
, m
);
975 if (nxte
.start
[0] == '\n')
978 for (l
= 0; l
< e
.plen
+ e
.prefix
; l
++) {
983 if (*c
>= ' ' && *c
!= 0x7f) {
985 (void)attrset(attr
|A_REVERSE
);
986 if (col
>= start
&& col
< start
+cols
)
987 mvaddch(row
, col
-start
+offset
, *c
);
989 } else if (*c
== '\t') {
991 (void)attrset(attr
|A_UNDERLINE
);
993 if (col
>= start
&& col
< start
+cols
) {
994 mvaddch(row
, col
-start
+offset
, ' ');
996 } while ((col
&7) != 0);
998 if (col
>= start
&& col
< start
+cols
)
999 mvaddch(row
, col
-start
+offset
, '?');
1003 if (curs
->target
>= 0) {
1004 if (curs
->target
< col
) {
1005 /* Found target column */
1009 if (scol
>= start
+ cols
)
1010 /* Didn't appear on screen */
1013 curs
->width
= col
- scol
;
1016 } else if (l
== curs
->offset
&&
1017 same_mp(pos
.p
, curs
->pos
)) {
1019 curs
->target
= scol
;
1021 if (scol
>= start
+ cols
)
1022 /* Didn't appear on screen */
1025 curs
->width
= col
- scol
;
1032 && visible(mode
, m
, &pos
) != -1))
1036 /* We have reached the end of visible line, or end of file */
1039 if (col
>= start
+ cols
)
1042 curs
->width
= -1; /* end of line */
1043 if (curs
->target
>= 0) {
1046 } else if (same_mp(pos
.p
, curs
->pos
))
1051 if (e
.start
&& e
.start
[0] == 0) {
1054 int A
, B
, C
, D
, E
, F
;
1055 e1
= fb
.list
[m
[pos
.p
.m
].b
+ pos
.p
.o
];
1056 sscanf(e1
.start
+1, "%d %d %d", &A
, &B
, &C
);
1057 sscanf(e
.start
+1, "%d %d %d", &D
, &E
, &F
);
1058 snprintf(b
, sizeof(b
), "@@ -%d,%d +%d,%d @@%s", B
, C
, E
, F
, e1
.start
+18);
1059 (void)attrset(a_sep
);
1061 mvaddstr(row
, col
-start
+offset
, b
);
1064 blank(row
, col
-start
+offset
, start
+cols
-col
,
1066 ? (unsigned)visible(mode
, m
, &pos
)
1070 /* Draw either 1 or 2 sides depending on the mode. */
1072 static void draw_mline(int mode
, int row
, int start
, int cols
,
1073 struct file fm
, struct file fb
, struct file fa
,
1076 struct cursor
*curs
)
1079 * Draw the left and right images of this line
1080 * One side might be a_blank depending on the
1081 * visibility of this newline
1085 mode
|= check_line(pos
, fm
, fb
, fa
, m
, mode
);
1087 if ((mode
& (BEFORE
|AFTER
)) &&
1088 (mode
& (ORIG
|RESULT
))) {
1091 rcols
= cols
- lcols
- 1;
1093 (void)attrset(A_STANDOUT
);
1094 mvaddch(row
, lcols
, '|');
1096 draw_mside(mode
&~(BEFORE
|AFTER
), row
, 0, start
, lcols
,
1097 fm
, fb
, fa
, m
, pos
, curs
&& !curs
->alt
? curs
: NULL
);
1099 draw_mside(mode
&~(ORIG
|RESULT
), row
, lcols
+1, start
, rcols
,
1100 fm
, fb
, fa
, m
, pos
, curs
&& curs
->alt
? curs
: NULL
);
1102 draw_mside(mode
, row
, 0, start
, cols
,
1103 fm
, fb
, fa
, m
, pos
, curs
);
1106 static char *merge_help
[] = {
1107 "This view shows the merge of the patch with the",
1108 "original file. It is like a full-context diff showing",
1109 "removed lines with a '-' prefix and added lines with a",
1111 "In cases where a patch chunk could not be successfully",
1112 "applied, the original text is prefixed with a '|', and",
1113 "the text that the patch wanted to add is prefixed with",
1115 "When the cursor is over such a conflict, or over a chunk",
1116 "which required wiggling to apply (i.e. there was unmatched",
1117 "text in the original, or extraneous unchanged text in",
1118 "the patch), the terminal is split and the bottom pane is",
1119 "use to display the part of the patch that applied to",
1120 "this section of the original. This allows you to confirm",
1121 "that a wiggled patch applied correctly, and to see",
1122 "why there was a conflict",
1125 static char *diff_help
[] = {
1126 "This is the 'diff' or 'patch' view. It shows",
1127 "only the patch that is being applied without the",
1128 "original to which it is being applied.",
1129 "Underlined text indicates parts of the patch which",
1130 "resulted in a conflict when applied to the",
1134 static char *orig_help
[] = {
1135 "This is the 'original' view which simply shows",
1136 "the original file before applying the patch.",
1137 "Sections of code that would be changed by the patch",
1138 "are highlighted in red.",
1141 static char *result_help
[] = {
1142 "This is the 'result' view which shows just the",
1143 "result of applying the patch. When a conflict",
1144 "occurred this view does not show the full conflict",
1145 "but only the 'after' part of the patch. To see",
1146 "the full conflict, use the 'merge' or 'sidebyside'",
1150 static char *before_help
[] = {
1151 "This view shows the 'before' section of a patch.",
1152 "It allows the expected match text to be seen uncluttered",
1153 "by text that is meant to replaced it.",
1154 "Red text is text that will be removed by the patch",
1157 static char *after_help
[] = {
1158 "This view shows the 'after' section of a patch.",
1159 "It allows the intended result to be seen uncluttered",
1160 "by text that was meant to be matched and replaced.",
1161 "Green text is text that was added by the patch - it",
1162 "was not present in the 'before' part of the patch",
1165 static char *sidebyside_help
[] = {
1166 "This is the Side By Side view of a patched file.",
1167 "The left side shows the original and the result.",
1168 "The right side shows the patch which was applied",
1169 "and lines up with the original/result as much as",
1172 "Where one side has no line which matches the",
1173 "other side it is displayed as a solid colour in the",
1174 "yellow family (depending on your terminal window).",
1177 static char *merge_window_help
[] = {
1178 " Highlight Colours and Keystroke commands",
1180 "In all different views of a merge, highlight colours",
1181 "are used to show which parts of lines were added,",
1182 "removed, already changed, unchanged or in conflict.",
1183 "Colours and their use are:",
1184 " normal unchanged text",
1185 " red text that was removed or changed",
1186 " green text that was added or the result",
1188 " yellow background used in side-by-side for a line",
1189 " which has no match on the other",
1191 " blue text in the original which did not",
1192 " match anything in the patch",
1193 " cyan text in the patch which did not",
1194 " match anything in the original",
1195 " cyan background already changed text: the result",
1196 " of the patch matches the original",
1197 " underline remove or added text can also be",
1198 " underlined indicating that it",
1199 " was involved in a conflict",
1201 "While viewing a merge various keystroke commands can",
1202 "be used to move around and change the view. Basic",
1203 "movement commands from both 'vi' and 'emacs' are",
1206 " p control-p k UP Move to previous line",
1207 " n control-n j DOWN Move to next line",
1208 " l LEFT Move one char to right",
1209 " h RIGHT Move one char to left",
1210 " / control-s Enter incremental search mode",
1211 " control-r Enter reverse-search mode",
1212 " control-g Search again",
1213 " ? Display help message",
1214 " ESC-< 0-G Go to start of file",
1215 " ESC-> G Go to end of file",
1216 " q Return to list of files or exit",
1217 " S Arrange for merge to be saved on exit",
1218 " control-C Disable auto-save-on-exit",
1219 " control-L recenter current line",
1220 " control-V SPACE page down",
1221 " ESC-v BACKSPC page up",
1222 " N go to next patch chunk",
1223 " P go to previous patch chunk",
1224 " C go to next conflicted chunk",
1225 " C-X-o O move cursor to alternate pane",
1226 " ^ control-A go to start of line",
1227 " $ control-E go to end of line",
1229 " a display 'after' view",
1230 " b display 'before' view",
1231 " o display 'original' view",
1232 " r display 'result' view",
1233 " d display 'diff' or 'patch' view",
1234 " m display 'merge' view",
1235 " | display side-by-side view",
1237 " I toggle whether spaces are ignored",
1238 " when matching text.",
1239 " x toggle ignoring of current Changed,",
1240 " Conflict, or Unmatched item",
1241 " c toggle accepting of result of conflict",
1242 " X Revert 'c' and 'x' changes on this line",
1243 " v Save the current merge and run the",
1244 " default editor on the file.",
1247 static char *save_query
[] = {
1249 "You have modified the merge.",
1250 "Would you like to save it?",
1251 " Y = save the modified merge",
1252 " N = discard modifications, don't save",
1253 " Q = return to viewing modified merge",
1257 static char *toggle_ignore
[] = {
1259 "You have modified the merge.",
1260 "Toggling ignoring of spaces will discard changes.",
1261 "Do you want to proceed?",
1262 " Y = discard changes and toggle ignoring of spaces",
1263 " N = keep changes, don't toggle",
1267 static void do_edit(char *file
, int line
)
1269 char *ed
= getenv("VISUAL");
1272 ed
= getenv("EDITOR");
1274 ed
= "/usr/bin/edit";
1275 snprintf(linebuf
, sizeof(linebuf
), "+%d", line
);
1278 execlp(ed
, ed
, linebuf
, file
, NULL
);
1287 static void *memdup(void *a
, int len
)
1289 char *r
= malloc(len
);
1294 static int merge_window(struct plist
*p
, FILE *f
, int reverse
, int replace
,
1295 int selftest
, int ignore_blanks
, int just_diff
)
1297 /* Display the merge window in one of the selectable modes,
1298 * starting with the 'merge' mode.
1300 * Newlines are the key to display.
1301 * 'pos' is always a visible newline (or eof).
1302 * In sidebyside mode it might only be visible on one side,
1303 * in which case the other side will be blank.
1304 * Where the newline is visible, we rewind the previous visible
1305 * newline visible and display the stuff in between
1307 * A 'position' is a struct mpos
1310 struct stream sm
, sb
, sa
, sp
; /* main, before, after, patch */
1311 struct file fm
, fb
, fa
;
1312 struct csl
*csl1
, *csl2
;
1314 int ch
; /* count of chunks */
1315 /* Always refresh the current line.
1316 * If refresh == 1, refresh all lines. If == 2, clear first
1319 int rows
= 0, cols
= 0;
1320 int splitrow
= -1; /* screen row for split - diff appears below */
1321 int lastrow
= 0; /* end of screen, or just above 'splitrow' */
1324 int mode
= just_diff
? (BEFORE
|AFTER
) : (ORIG
|RESULT
);
1325 int mmode
= mode
; /* Mode for moving - used when in 'other' pane */
1326 char *modename
= just_diff
? "diff" : "merge";
1327 char **modehelp
= just_diff
? diff_help
: merge_help
;
1331 int trow
; /* screen-row while searching. If we cannot find,
1332 * we forget this number */
1334 struct mpos pos
; /* current point */
1335 struct mpos tpos
, /* temp point while drawing lines above and below pos */
1336 toppos
, /* pos at top of screen - for page-up */
1337 botpos
; /* pos at bottom of screen - for page-down */
1340 int meta
= 0, /* mode for multi-key commands- SEARCH or META */
1342 int num
= -1, /* numeric arg being typed. */
1345 int changes
= 0; /* If any edits have been made to the merge */
1346 int answer
; /* answer to 'save changes?' question */
1349 char search
[80]; /* string we are searching for */
1350 unsigned int searchlen
= 0;
1351 int search_notfound
= 0;
1355 * 1 == no because there are upper-case chars
1356 * 2 == yes as there are no upper-case chars
1359 int ignore_case
= 2;
1360 /* We record all the places we find so 'backspace'
1361 * can easily return to the previous one
1363 struct search_anchor
{
1364 struct search_anchor
*next
;
1369 unsigned int searchlen
;
1372 void free_stuff(void)
1381 void find_line(int ln
)
1383 pos
.p
.m
= 0; /* merge node */
1384 pos
.p
.s
= 0; /* stream number */
1385 pos
.p
.o
= -1; /* offset */
1388 memset(&curs
, 0, sizeof(curs
));
1390 next_mline(&pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1391 while (pos
.p
.lineno
< ln
&& ci
.merger
[pos
.p
.m
].type
!= End
);
1393 void prepare_merge(int ch
)
1395 /* FIXME check for errors in the stream */
1396 fm
= split_stream(sm
, ByWord
| ignore_blanks
);
1397 fb
= split_stream(sb
, ByWord
| ignore_blanks
);
1398 fa
= split_stream(sa
, ByWord
| ignore_blanks
);
1400 if (ch
&& !just_diff
)
1401 csl1
= pdiff(fm
, fb
, ch
);
1403 csl1
= diff(fm
, fb
);
1404 csl2
= diff_patch(fb
, fa
);
1406 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1, 0);
1407 for (i
= 0; ci
.merger
[i
].type
!= End
; i
++)
1408 ci
.merger
[i
].oldtype
= ci
.merger
[i
].type
;
1418 /* three separate files */
1419 sb
= load_file(p
->before
);
1420 sa
= load_file(p
->after
);
1424 sm
= load_file(p
->file
);
1426 /* One merge file */
1427 sp
= load_file(p
->file
);
1429 split_merge(sp
, &sm
, &sa
, &sb
);
1431 split_merge(sp
, &sm
, &sb
, &sa
);
1436 sp
= load_segment(f
, p
->start
, p
->end
);
1439 split_merge(sp
, &sm
, &sa
, &sb
);
1441 split_merge(sp
, &sm
, &sb
, &sa
);
1445 ch
= split_patch(sp
, &sa
, &sb
);
1447 ch
= split_patch(sp
, &sb
, &sa
);
1451 sm
= load_file(p
->file
);
1455 if (!sm
.body
|| !sb
.body
|| !sa
.body
) {
1462 help_window(help_missing
, NULL
, 0);
1464 help_window(help_corrupt
, NULL
, 0);
1469 term_init(!selftest
);
1480 if (row
< 1 || row
>= lastrow
)
1485 if (mode
== (ORIG
|RESULT
)) {
1486 int cmode
= check_line(pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1487 if (cmode
& (WIGGLED
| CONFLICTED
)) {
1489 splitrow
= (rows
+1)/2;
1490 lastrow
= splitrow
- 1;
1493 } else if (!curs
.alt
&& splitrow
>= 0) {
1498 } else if (splitrow
>= 0) {
1505 getmaxyx(stdscr
, rows
, cols
);
1506 rows
--; /* keep last row clear */
1507 if (splitrow
>= 0) {
1508 splitrow
= (rows
+1)/2;
1509 lastrow
= splitrow
- 1;
1517 if (row
> lastrow
+3)
1523 /* Always refresh the line */
1524 while (start
> curs
.target
) {
1530 vispos
= pos
; /* visible position - if cursor is in
1531 * alternate pane, pos might not be visible
1533 if (check_line(vispos
, fm
, fb
, fa
, ci
.merger
, mode
)
1535 if (vispos
.state
== 0) {
1537 vispos
.lo
= vispos
.p
;
1538 vispos
.hi
= vispos
.p
;
1544 if (visible(mode
, ci
.merger
, &vispos
) < 0)
1545 prev_mline(&vispos
, fm
, fb
, fa
, ci
.merger
, mode
);
1549 draw_mline(mode
, row
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1550 vispos
, (splitrow
>= 0 && curs
.alt
) ? NULL
: &curs
);
1551 if (curs
.width
== 0 && start
< curs
.col
) {
1552 /* width == 0 implies it appear after end-of-screen */
1557 if (curs
.col
< start
) {
1569 for (i
= row
-1; i
>= 1 && tpos
.p
.m
>= 0; ) {
1570 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1571 draw_mline(mode
, i
--, start
, cols
,
1572 fm
, fb
, fa
, ci
.merger
,
1583 blank(i
--, 0, cols
, a_void
);
1585 for (i
= row
; i
<= lastrow
&& ci
.merger
[tpos
.p
.m
].type
!= End
; ) {
1586 draw_mline(mode
, i
++, start
, cols
,
1587 fm
, fb
, fa
, ci
.merger
,
1589 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1591 botpos
= tpos
; botrow
= i
;
1592 while (i
<= lastrow
)
1593 blank(i
++, 0, cols
, a_void
);
1596 if (splitrow
>= 0) {
1597 struct mpos spos
= pos
;
1598 int smode
= BEFORE
|AFTER
;
1599 int srow
= (rows
+ splitrow
)/2;
1600 if (check_line(spos
, fm
, fb
, fa
, ci
.merger
, smode
)
1602 if (spos
.state
== 0)
1607 if (visible(smode
, ci
.merger
, &spos
) < 0)
1608 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1609 /* Now hi/lo might be wrong, so lets fix it. */
1612 /* 'hi' might be wrong so we mustn't depend
1613 * on it while walking back. So set state
1614 * to 1 to avoid ever testing it.
1617 while (spos
.p
.m
>= 0 && spos
.state
!= 0)
1618 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1619 while (!same_mpos(spos
, tpos
) &&
1620 ci
.merger
[spos
.p
.m
].type
!= End
)
1621 next_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1623 (void)attrset(a_sep
);
1624 for (i
= 0; i
< cols
; i
++)
1625 mvaddstr(splitrow
, i
, "-");
1628 for (i
= srow
-1; i
> splitrow
; i
--) {
1629 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1630 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1633 while (i
> splitrow
)
1634 blank(i
--, 0, cols
, a_void
);
1637 i
< rows
&& ci
.merger
[tpos
.p
.m
].type
!= End
;
1639 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1641 (i
== srow
&& curs
.alt
) ? &curs
: NULL
);
1642 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1645 blank(i
++, 0, cols
, a_void
);
1647 /* Now that curs is accurate, report the type */
1652 l
= snprintf(buf
, 100, "File: %s%s ",
1653 p
->file
, reverse
? " - reversed" : "");
1654 snprintf(buf
+l
, 100-l
, "Mode: %s", modename
);
1655 (void)attrset(A_BOLD
);
1656 mvaddstr(0, 0, buf
);
1657 (void)attrset(A_NORMAL
);
1659 addstr(" (ignoring blanks)");
1661 (void)attrset(A_BOLD
);
1662 if (ci
.merger
[curs
.pos
.m
].type
!= ci
.merger
[curs
.pos
.m
].oldtype
)
1663 l
= snprintf(buf
, sizeof(buf
), "%s->", typenames
[ci
.merger
[curs
.pos
.m
].oldtype
]);
1664 snprintf(buf
+l
, sizeof(buf
)-l
, "%s ln:%d",
1665 typenames
[ci
.merger
[curs
.pos
.m
].type
],
1666 (pos
.p
.lineno
-1)/2);
1667 mvaddstr(0, cols
- strlen(buf
) - 1, buf
);
1669 #define META(c) ((c)|0x1000)
1670 #define SEARCH(c) ((c)|0x2000)
1671 #define CTRLX(c) ((c)|0x4000)
1673 (void)attrset(A_NORMAL
);
1682 snprintf(buf
, 10, "%d ", num
);
1687 if (meta
& CTRLX(0))
1689 if (meta
& SEARCH(0)) {
1691 addstr("Backwards ");
1694 if (search_notfound
)
1695 addstr(" - Not Found.");
1696 search_notfound
= 0;
1699 /* '+1' to skip over the leading +/-/| char */
1700 if (curs
.alt
&& splitrow
> 0)
1701 move((rows
+ splitrow
)/2, curs
.col
- start
+ 1);
1702 else if (curs
.alt
&& ((mode
& (BEFORE
|AFTER
)) &&
1703 (mode
& (ORIG
|RESULT
))))
1704 move(row
, curs
.col
-start
+ (cols
-1)/2+2);
1706 move(row
, curs
.col
-start
+1);
1715 tmeta
= meta
; meta
= 0;
1716 tnum
= num
; num
= -1;
1717 cswitch
= c
| tmeta
;
1718 /* Handle some ranges */
1719 /* case '0' ... '9': */
1720 if (cswitch
>= '0' && cswitch
<= '9')
1722 /* case SEARCH(' ') ... SEARCH('~'): */
1723 if (cswitch
>= SEARCH(' ') && cswitch
<= SEARCH('~'))
1724 cswitch
= SEARCH(' ');
1727 case 27: /* escape */
1737 case META('<'): /* start of file */
1742 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1743 } while (tpos
.p
.m
>= 0);
1747 case META('>'): /* end of file */
1754 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1755 } while (ci
.merger
[tpos
.p
.m
].type
!= End
);
1759 case '0': /* actually '0'...'9' */
1762 num
= tnum
*10 + (c
-'0');
1766 mesg
= "Autosave disabled";
1768 mesg
= "Use 'q' to quit";
1772 mesg
= "Will auto-save on exit, using Ctrl-C to cancel";
1781 answer
= help_window(save_query
, NULL
, 1);
1786 p
->conflicts
= isolate_conflicts(
1787 fm
, fb
, fa
, csl1
, csl2
, 0,
1788 ci
.merger
, 0, &p
->wiggles
);
1789 p
->chunks
= p
->conflicts
;
1790 save_merge(fm
, fb
, fa
, ci
.merger
,
1791 p
->outfile
?: p
->file
,
1792 p
->outfile
? 0 : !p
->is_merge
);
1802 case 'I': /* Toggle ignoring of spaces */
1805 answer
= help_window(toggle_ignore
, NULL
, 1);
1811 ignore_blanks
= ignore_blanks
? 0 : IgnoreBlanks
;
1813 find_line(pos
.p
.lineno
);
1819 if (!p
->file
|| just_diff
) {
1820 mesg
= "Cannot run editor when diffing";
1824 lineno
= save_tmp_merge(fm
, fb
, fa
, ci
.merger
,
1826 ci
.merger
+ pos
.p
.m
,
1831 do_edit(tempname
, lineno
);
1832 sp
= load_file(tempname
);
1834 split_merge(sp
, &sm
, &sb
, &sa
);
1835 if (sp
.len
== sm
.len
&&
1836 memcmp(sp
.body
, sm
.body
, sm
.len
) == 0 &&
1838 /* no conflicts left, so display diff */
1840 sm
= load_file(p
->file
);
1843 sb
.body
= memdup(sm
.body
, sm
.len
);
1851 find_line(pos
.p
.lineno
);
1858 /* incr search forward */
1861 search
[searchlen
] = 0;
1866 /* incr search backwards */
1869 search
[searchlen
] = 0;
1872 case SEARCH('G'-64):
1873 case SEARCH('S'-64):
1874 case SEARCH('R'-64):
1876 if ((c
|tmeta
) == SEARCH('R'-64))
1881 tpos
= pos
; trow
= row
;
1884 case SEARCH('H'-64):
1885 case SEARCH(KEY_BACKSPACE
):
1888 struct search_anchor
*a
;
1894 struct search_anchor
*a
;
1902 search_notfound
= a
->notfound
;
1903 searchlen
= a
->searchlen
;
1904 search
[searchlen
] = 0;
1909 case SEARCH(' '): /* actually ' '...'~' */
1912 if (searchlen
< sizeof(search
)-1)
1913 search
[searchlen
++] = c
& (0x7f);
1914 search
[searchlen
] = 0;
1915 tpos
= pos
; trow
= row
;
1917 search_notfound
= 1;
1918 if (ignore_case
== 1 || ignore_case
== 2) {
1921 for (i
=0; i
< searchlen
; i
++)
1922 if (isupper(search
[i
])) {
1928 if (mcontains(tpos
, fm
, fb
, fa
, ci
.merger
,
1929 mmode
, search
, &curs
, searchdir
,
1930 ignore_case
>= 2)) {
1934 search_notfound
= 0;
1937 if (searchdir
< 0) {
1939 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1942 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1944 } while (tpos
.p
.m
>= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1945 searchdir
/= abs(searchdir
);
1955 case 'V'-64: /* page down */
1957 if (botrow
<= lastrow
) {
1967 case META('v'): /* page up */
1974 if (getmouse(&mevent
) != OK
)
1976 /* First see if this is on the 'other' pane */
1978 /* merge mode, top and bottom */
1979 if ((curs
.alt
&& mevent
.y
< splitrow
) ||
1980 (!curs
.alt
&& mevent
.y
> splitrow
)) {
1983 } else if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
)) {
1984 /* side-by-side mode */
1985 if ((curs
.alt
&& mevent
.x
< cols
/2) ||
1986 (!curs
.alt
&& mevent
.x
> cols
/2)) {
1990 /* Now try to find the right line */
1991 if (splitrow
< 0 || !curs
.alt
)
1994 trow
= (rows
+ splitrow
)/2;
1995 while (trow
> mevent
.y
) {
1997 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1998 if (tpos
.p
.m
>= 0) {
2004 while (trow
< mevent
.y
) {
2006 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2007 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2013 if (splitrow
< 0 || !curs
.alt
)
2014 /* it is OK to change the row */
2017 /* Now set the target column */
2018 if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
) &&
2020 curs
.target
= start
+ mevent
.x
- cols
/ 2 - 1;
2022 curs
.target
= start
+ mevent
.x
- 1;
2030 for (; tnum
> 0 ; tnum
--) {
2032 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2033 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2048 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2049 } while (!(pos
.state
== 0
2050 && (check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2051 & (CONFLICTED
|WIGGLED
)) == 0)
2052 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2056 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2057 } while (pos
.state
== 0
2058 && (check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2059 & (CONFLICTED
|WIGGLED
)) == 0
2060 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2068 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2069 } while (!(check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2071 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2075 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2076 } while ((check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2078 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2087 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2088 } while (tpos
.state
== 0
2089 && (check_line(tpos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2090 & (CONFLICTED
|WIGGLED
)) == 0
2095 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2096 } while (!(tpos
.state
== 0
2097 && (check_line(tpos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2098 & (CONFLICTED
|WIGGLED
)) == 0)
2108 for (; tnum
> 0 ; tnum
--) {
2110 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2111 if (tpos
.p
.m
>= 0) {
2122 curs
.target
= curs
.col
- 1;
2123 if (curs
.target
< 0) {
2124 /* Try to go to end of previous line */
2126 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2127 if (tpos
.p
.m
>= 0) {
2139 if (curs
.width
>= 0)
2140 curs
.target
= curs
.col
+ curs
.width
;
2142 /* end of line, go to next */
2144 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2145 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2168 curs
.alt
= !curs
.alt
;
2169 if (curs
.alt
&& mode
== (ORIG
|RESULT
))
2170 mmode
= (BEFORE
|AFTER
);
2175 case 'a': /* 'after' view in patch window */
2178 mode
= AFTER
; modename
= "after"; modehelp
= after_help
;
2179 mmode
= mode
; curs
.alt
= 0;
2182 case 'b': /* 'before' view in patch window */
2185 mode
= BEFORE
; modename
= "before"; modehelp
= before_help
;
2186 mmode
= mode
; curs
.alt
= 0;
2189 case 'o': /* 'original' view in the merge window */
2192 mode
= ORIG
; modename
= "original"; modehelp
= orig_help
;
2193 mmode
= mode
; curs
.alt
= 0;
2196 case 'r': /* the 'result' view in the merge window */
2199 mode
= RESULT
; modename
= "result"; modehelp
= result_help
;
2200 mmode
= mode
; curs
.alt
= 0;
2204 if (mode
== (BEFORE
|AFTER
))
2206 mode
= BEFORE
|AFTER
; modename
= "diff"; modehelp
= diff_help
;
2207 mmode
= mode
; curs
.alt
= 0;
2212 mode
= ORIG
|RESULT
; modename
= "merge"; modehelp
= merge_help
;
2213 mmode
= mode
; curs
.alt
= 0;
2218 if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
))
2220 mode
= ORIG
|RESULT
|BEFORE
|AFTER
; modename
= "sidebyside"; modehelp
= sidebyside_help
;
2221 mmode
= mode
; curs
.alt
= 0;
2225 case 'H': /* scroll window to the right */
2228 curs
.target
= start
+ 1;
2231 case 'L': /* scroll window to the left */
2234 curs
.target
= start
+ 1;
2238 case 'x': /* Toggle rejecting of conflict.
2239 * A 'Conflict' or 'Changed' becomes 'Unchanged'
2240 * 'Unmatched' becomes 'Changed'
2242 if (ci
.merger
[curs
.pos
.m
].oldtype
== Conflict
||
2243 ci
.merger
[curs
.pos
.m
].oldtype
== Changed
)
2245 else if (ci
.merger
[curs
.pos
.m
].oldtype
== Unmatched
)
2250 if (ci
.merger
[curs
.pos
.m
].type
== next
)
2251 ci
.merger
[curs
.pos
.m
].type
= ci
.merger
[curs
.pos
.m
].oldtype
;
2253 ci
.merger
[curs
.pos
.m
].type
= next
;
2254 p
->conflicts
= isolate_conflicts(
2255 fm
, fb
, fa
, csl1
, csl2
, 0,
2256 ci
.merger
, 0, &p
->wiggles
);
2261 case 'c': /* Toggle accepting of conflict.
2262 * A 'Conflict' or 'Extraneous' becomes 'Changed'
2264 if (ci
.merger
[curs
.pos
.m
].oldtype
!= Conflict
&&
2265 ci
.merger
[curs
.pos
.m
].oldtype
!= Extraneous
)
2268 if (ci
.merger
[curs
.pos
.m
].type
== Changed
)
2269 ci
.merger
[curs
.pos
.m
].type
= ci
.merger
[curs
.pos
.m
].oldtype
;
2271 ci
.merger
[curs
.pos
.m
].type
= Changed
;
2272 p
->conflicts
= isolate_conflicts(
2273 fm
, fb
, fa
, csl1
, csl2
, 0,
2274 ci
.merger
, 0, &p
->wiggles
);
2279 case 'X': /* Reset all changes on the current line */
2282 ci
.merger
[tpos
.p
.m
].type
=
2283 ci
.merger
[tpos
.p
.m
].oldtype
;
2284 e
= prev_melmnt(&tpos
.p
, fm
, fb
, fa
, ci
.merger
);
2287 } while (!ends_line(e
) ||
2288 visible(mode
& (RESULT
|AFTER
), ci
.merger
, &tpos
) < 0);
2289 p
->conflicts
= isolate_conflicts(
2290 fm
, fb
, fa
, csl1
, csl2
, 0,
2291 ci
.merger
, 0, &p
->wiggles
);
2297 help_window(modehelp
, merge_window_help
, 0);
2306 if (meta
== SEARCH(0)) {
2307 if (anchor
== NULL
||
2308 !same_mpos(anchor
->pos
, pos
) ||
2309 anchor
->searchlen
!= searchlen
||
2310 !same_mp(anchor
->curs
.pos
, curs
.pos
)) {
2311 struct search_anchor
*a
= xmalloc(sizeof(*a
));
2316 a
->searchlen
= searchlen
;
2317 a
->notfound
= search_notfound
;
2323 struct search_anchor
*a
= anchor
;
2329 /* move backward and forward to make sure we
2330 * are on a visible line
2333 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2337 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2338 if (ci
.merger
[tpos
.p
.m
].type
!= End
)
2344 static int show_merge(char *origname
, FILE *patch
, int reverse
,
2345 int is_merge
, char *before
, char *after
,
2346 int replace
, char *outfile
,
2347 int selftest
, int ignore_blanks
,
2350 struct plist p
= {0};
2354 p
.outfile
= outfile
;
2357 fseek(patch
, 0, SEEK_END
);
2358 p
.end
= ftell(patch
);
2359 fseek(patch
, 0, SEEK_SET
);
2362 p
.is_merge
= is_merge
;
2366 freopen("/dev/null","w",stderr
);
2367 return merge_window(&p
, patch
, reverse
, replace
, selftest
,
2368 ignore_blanks
, just_diff
);
2371 static void calc_one(struct plist
*pl
, FILE *f
, int reverse
,
2372 int ignore_blanks
, int just_diff
)
2374 struct stream s1
, s2
;
2375 struct stream s
= load_segment(f
, pl
->start
, pl
->end
);
2379 split_merge(s
, &sf
, &s2
, &s1
);
2381 split_merge(s
, &sf
, &s1
, &s2
);
2385 pl
->chunks
= split_patch(s
, &s2
, &s1
);
2387 pl
->chunks
= split_patch(s
, &s1
, &s2
);
2391 sf
= load_file(pl
->file
);
2393 if (sf
.body
== NULL
|| s1
.body
== NULL
|| s1
.body
== NULL
) {
2394 pl
->wiggles
= pl
->conflicts
= -1;
2396 struct file ff
, fp1
, fp2
;
2397 struct csl
*csl1
, *csl2
;
2399 ff
= split_stream(sf
, ByWord
| ignore_blanks
);
2400 fp1
= split_stream(s1
, ByWord
| ignore_blanks
);
2401 fp2
= split_stream(s2
, ByWord
| ignore_blanks
);
2402 if (pl
->chunks
&& !just_diff
)
2403 csl1
= pdiff(ff
, fp1
, pl
->chunks
);
2405 csl1
= diff(ff
, fp1
);
2406 csl2
= diff_patch(fp1
, fp2
);
2407 ci
= make_merger(ff
, fp1
, fp2
, csl1
, csl2
, 0, 1, 0);
2408 pl
->wiggles
= ci
.wiggles
;
2409 pl
->conflicts
= ci
.conflicts
;
2426 static int get_prev(int pos
, struct plist
*pl
, int n
, int mode
)
2432 if (pl
[pos
].prev
== -1)
2433 return pl
[pos
].parent
;
2435 while (pl
[pos
].open
&&
2438 if (pl
[pos
].last
>= 0)
2439 /* always see directories */
2443 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2445 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2447 } while (pos
>= 0 && !found
);
2451 static int get_next(int pos
, struct plist
*pl
, int n
, int mode
,
2452 FILE *f
, int reverse
, int ignore_blanks
, int just_diff
)
2464 while (pos
>= 0 && pl
[pos
].next
== -1)
2465 pos
= pl
[pos
].parent
;
2471 if (pl
[pos
].calced
== 0 && pl
[pos
].end
)
2472 calc_one(pl
+pos
, f
, reverse
, ignore_blanks
, just_diff
);
2473 if (pl
[pos
].last
>= 0)
2474 /* always see directories */
2478 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2480 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2482 } while (pos
>= 0 && !found
);
2486 static void draw_one(int row
, struct plist
*pl
, FILE *f
, int reverse
,
2487 int ignore_blanks
, int just_diff
)
2497 if (pl
->calced
== 0 && pl
->end
)
2498 /* better load the patch and count the chunks */
2499 calc_one(pl
, f
, reverse
, ignore_blanks
, just_diff
);
2503 if (pl
->chunks
> 99)
2506 sprintf(hdr
, "%2d", pl
->chunks
);
2507 if (pl
->wiggles
> 99)
2508 strcpy(hdr
+2, " XX");
2510 sprintf(hdr
+2, " %2d", pl
->wiggles
);
2511 if (pl
->conflicts
> 99)
2512 strcpy(hdr
+5, " XX ");
2514 sprintf(hdr
+5, " %2d ", pl
->conflicts
);
2517 strcpy(hdr
+9, "= ");
2519 strcpy(hdr
+9, "+ ");
2521 strcpy(hdr
+9, "- ");
2525 else if (pl
->is_merge
)
2527 else if (pl
->conflicts
)
2528 attrset(a_has_conflicts
);
2529 else if (pl
->wiggles
)
2530 attrset(a_has_wiggles
);
2532 attrset(a_no_wiggles
);
2534 mvaddstr(row
, 0, hdr
);
2535 mvaddstr(row
, 11, pl
->file
);
2539 static int save_one(FILE *f
, struct plist
*pl
, int reverse
,
2542 struct stream sp
, sa
, sb
, sm
;
2543 struct file fa
, fb
, fm
;
2544 struct csl
*csl1
, *csl2
;
2547 sp
= load_segment(f
, pl
->start
,
2550 chunks
= split_patch(sp
, &sa
, &sb
);
2552 chunks
= split_patch(sp
, &sb
, &sa
);
2553 fb
= split_stream(sb
, ByWord
| ignore_blanks
);
2554 fa
= split_stream(sa
, ByWord
| ignore_blanks
);
2555 sm
= load_file(pl
->file
);
2556 fm
= split_stream(sm
, ByWord
| ignore_blanks
);
2557 csl1
= pdiff(fm
, fb
, chunks
);
2558 csl2
= diff_patch(fb
, fa
);
2559 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1, 0);
2560 return save_merge(fm
, fb
, fa
, ci
.merger
,
2564 static char *main_help
[] = {
2565 " You are using the \"browse\" mode of wiggle.",
2566 "This page shows a list of files in a patch together with",
2567 "the directories that contain them.",
2568 "A directory is indicated by a '+' if the contents are",
2569 "listed or a '-' if the contents are hidden. A file is",
2570 "indicated by an '='. Typing <space> or <return> will",
2571 "expose or hide a directory, and will visit a file.",
2573 "The three columns of numbers are:",
2574 " Ch The number of patch chunks which applied to",
2576 " Wi The number of chunks that needed to be wiggled",
2578 " Co The number of chunks that created an unresolvable",
2581 "Keystrokes recognised in this page are:",
2582 " ? Display this help",
2583 " SPC On a directory, toggle hiding of contents",
2584 " On file, visit the file",
2587 " control-C Disable auto-save-on-exit",
2588 " n,j,DOWN Go to next line",
2589 " p,k,UP Go to previous line",
2591 " A list All files",
2592 " W only list files with a wiggle or a conflict",
2593 " C only list files with a conflict",
2595 " S Save this file with changes applied. If",
2596 " some but not all files are saved, wiggle will",
2597 " prompt on exit to save the rest.",
2598 " R Revert the current saved file to its original",
2600 " I toggle whether spaces are ignored",
2601 " when matching text.",
2604 static char *saveall_msg
= " %d file%s (of %d) have not been saved.";
2605 static char saveall_buf
[200];
2606 static char *saveall_query
[] = {
2609 " Would you like to save them?",
2610 " Y = yes, save them all",
2611 " N = no, exit without saving anything else",
2612 " Q = Don't quit just yet",
2615 static void main_window(struct plist
*pl
, int *np
, FILE *f
, int reverse
,
2616 int replace
, int ignore_blanks
, int just_diff
)
2618 /* The main window lists all files together with summary information:
2619 * number of chunks, number of wiggles, number of conflicts.
2620 * The list is scrollable
2621 * When a entry is 'selected', we switch to the 'file' window
2622 * The list can be condensed by removing files with no conflict
2623 * or no wiggles, or removing subdirectories
2625 * We record which file in the list is 'current', and which
2626 * screen line it is on. We try to keep things stable while
2629 * Counts are printed before the name using at most 2 digits.
2630 * Numbers greater than 99 are XX
2632 * 27 5 1 drivers/md/md.c
2634 * A directory show the sum in all children.
2637 * select: enter, space, mouseclick
2638 * on file, go to file window
2639 * on directory, toggle open
2640 * up: k, p, control-p uparrow
2641 * Move to previous open object
2642 * down: j, n, control-n, downarrow
2643 * Move to next open object
2645 * A W C: select All Wiggles or Conflicts
2650 char mesg_buf
[1024];
2651 int last_mesg_len
= 0;
2652 int pos
= 0; /* position in file */
2653 int row
= 1; /* position on screen */
2654 int rows
= 0; /* size of screen in rows */
2659 int mode
= 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2660 int cnt
; /* count of files that need saving */
2661 int any
; /* count of files that have been save*/
2664 char *debug
= getenv("WIGGLE_DEBUG");
2666 if (debug
&& !*debug
)
2669 freopen("/dev/null","w",stderr
);
2671 pl
= sort_patches(pl
, np
);
2675 clear(); (void)attrset(0);
2677 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2680 addstr(" (ignoring blanks)");
2684 if (row
< 1 || row
>= rows
)
2688 getmaxyx(stdscr
, rows
, cols
);
2690 if (row
>= rows
+ 3)
2695 for (i
= row
; i
> 1; i
--) {
2696 tpos
= get_prev(tpos
, pl
, *np
, mode
);
2702 /* Ok, row and pos could be trustworthy now */
2704 for (i
= row
; i
>= 1; i
--) {
2705 draw_one(i
, &pl
[tpos
], f
, reverse
, ignore_blanks
, just_diff
);
2706 tpos
= get_prev(tpos
, pl
, *np
, mode
);
2709 for (i
= row
+1; i
< rows
; i
++) {
2710 tpos
= get_next(tpos
, pl
, *np
, mode
, f
, reverse
,ignore_blanks
, just_diff
);
2712 draw_one(i
, &pl
[tpos
], f
, reverse
, ignore_blanks
, just_diff
);
2714 draw_one(i
, NULL
, f
, reverse
, ignore_blanks
, just_diff
);
2718 if (last_mesg_len
) {
2719 move(0, cols
- last_mesg_len
);
2724 last_mesg_len
= strlen(mesg
);
2725 move(0, cols
- last_mesg_len
);
2729 /* debugging help: report last keystroke */
2731 sprintf(bb
, "last-key = 0%o", c
);
2733 last_mesg_len
= strlen(bb
);
2734 mvaddstr(0, cols
- last_mesg_len
, bb
);
2744 tpos
= get_next(pos
, pl
, *np
, mode
, f
, reverse
, ignore_blanks
, just_diff
);
2755 tpos
= get_prev(pos
, pl
, *np
, mode
);
2763 if (getmouse(&mevent
) != OK
)
2765 while (row
< mevent
.y
&&
2766 (tpos
= get_next(pos
, pl
, *np
, mode
, f
, reverse
, ignore_blanks
, just_diff
))
2771 while (row
> mevent
.y
&&
2772 (tpos
= get_prev(pos
, pl
, *np
, mode
)) >= 0) {
2776 if (row
!= mevent
.y
)
2777 /* couldn't find the line */
2782 if (pl
[pos
].end
== 0) {
2783 pl
[pos
].open
= !pl
[pos
].open
;
2786 mesg
= "Opened folder";
2788 mesg
= "Closed folder";
2791 if (pl
[pos
].is_merge
)
2792 c
= merge_window(&pl
[pos
], NULL
, reverse
, 0, 0, ignore_blanks
, just_diff
);
2794 c
= merge_window(&pl
[pos
], f
, reverse
, 0, 0, ignore_blanks
, just_diff
);
2797 pl
[pos
].is_merge
= 1;
2798 snprintf(mesg_buf
, cols
,
2805 case 27: /* escape */
2807 mvaddstr(0, cols
-10, "ESC..."); clrtoeol();
2811 move(0, cols
-10); clrtoeol();
2815 mesg
= "Save-on-exit disabled. Use 'q' to quit.";
2817 mesg
= "Use 'q' to quit.";
2824 for (i
= 0; i
< *np
; i
++)
2825 if (pl
[i
].end
&& !pl
[i
].is_merge
)
2837 sprintf(saveall_buf
, saveall_msg
,
2838 cnt
, cnt
== 1 ? "" : "s", cnt
+any
);
2839 ans
= help_window(saveall_query
, NULL
, 1);
2845 for (i
= 0; i
< *np
; i
++) {
2856 printf("%d file%s saved\n", cnt
,
2857 cnt
== 1 ? "" : "s");
2861 mode
= 0; refresh
= 1;
2862 mesg
= "Showing ALL files";
2865 mode
= 1; refresh
= 1;
2866 mesg
= "Showing Wiggled files";
2869 mode
= 2; refresh
= 1;
2870 mesg
= "Showing Conflicted files";
2873 case 'S': /* Save updated file */
2874 if (pl
[pos
].end
== 0) {
2876 mesg
= "Cannot save a folder.";
2877 } else if (pl
[pos
].is_merge
) {
2879 mesg
= "File is already saved.";
2881 if (save_one(f
, &pl
[pos
], reverse
, ignore_blanks
) == 0) {
2882 pl
[pos
].is_merge
= 1;
2883 snprintf(mesg_buf
, cols
,
2886 pl
[pos
].chunks
= pl
[pos
].conflicts
;
2887 pl
[pos
].wiggles
= 0;
2889 snprintf(mesg_buf
, cols
,
2890 "Failed to save file %s.",
2897 case 'R': /* Restore updated file */
2898 if (pl
[pos
].end
== 0)
2899 mesg
= "Cannot restore a folder.";
2900 else if (!pl
[pos
].is_merge
)
2901 mesg
= "File has not been saved, cannot restore.";
2903 /* rename foo.porig to foo, and clear is_merge */
2904 char *file
= pl
[pos
].file
;
2905 char *orignew
= xmalloc(strlen(file
) + 20);
2906 strcpy(orignew
, file
);
2907 strcat(orignew
, ".porig");
2908 if (rename(orignew
, file
) == 0) {
2909 mesg
= "File has been restored.";
2910 pl
[pos
].is_merge
= 0;
2912 calc_one(&pl
[pos
], f
, reverse
, ignore_blanks
, just_diff
);
2914 mesg
= "Could not restore file!";
2918 case 'I': /* Toggle ignoring blanks */
2919 ignore_blanks
= ignore_blanks
? 0 : IgnoreBlanks
;
2921 for (i
= 0; i
< *np
; i
++)
2926 help_window(main_help
, NULL
, 0);
2937 static void catch(int sig
)
2939 if (sig
== SIGINT
&& !intr_kills
) {
2946 printf("Died on signal %d\n", sig
);
2948 if (sig
!= SIGBUS
&& sig
!= SIGSEGV
)
2951 /* Otherwise return and die */
2955 static void term_init(int doraw
)
2958 static int init_done
= 0;
2964 signal(SIGINT
, catch);
2965 signal(SIGQUIT
, catch);
2966 signal(SIGTERM
, catch);
2967 signal(SIGBUS
, catch);
2968 signal(SIGSEGV
, catch);
2977 use_default_colors();
2978 if (!has_colors()) {
2979 a_delete
= A_UNDERLINE
;
2981 a_common
= A_NORMAL
;
2983 a_already
= A_STANDOUT
;
2984 a_has_conflicts
= A_UNDERLINE
;
2985 a_has_wiggles
= A_BOLD
;
2986 a_no_wiggles
= A_NORMAL
;
2988 init_pair(1, COLOR_RED
, -1);
2989 a_delete
= COLOR_PAIR(1);
2990 init_pair(2, COLOR_GREEN
, -1);
2991 a_added
= COLOR_PAIR(2);
2992 a_common
= A_NORMAL
;
2993 init_pair(3, COLOR_WHITE
, COLOR_GREEN
);
2994 a_sep
= COLOR_PAIR(3); a_sep
= A_STANDOUT
;
2995 init_pair(4, -1, COLOR_YELLOW
);
2996 a_void
= COLOR_PAIR(4);
2997 init_pair(5, COLOR_BLUE
, -1);
2998 a_unmatched
= COLOR_PAIR(5);
2999 init_pair(6, COLOR_CYAN
, -1);
3000 a_extra
= COLOR_PAIR(6);
3002 init_pair(7, COLOR_BLACK
, COLOR_CYAN
);
3003 a_already
= COLOR_PAIR(7);
3005 a_has_conflicts
= a_delete
;
3006 a_has_wiggles
= a_added
;
3007 a_no_wiggles
= a_unmatched
;
3010 nonl(); intrflush(stdscr
, FALSE
); keypad(stdscr
, TRUE
);
3011 mousemask(ALL_MOUSE_EVENTS
, NULL
);
3014 int vpatch(int argc
, char *argv
[], int patch
, int strip
,
3015 int reverse
, int replace
, char *outfilename
,
3016 int selftest
, int ignore_blanks
)
3018 /* NOTE argv[0] is first arg...
3019 * Behaviour depends on number of args and 'patch'.
3020 * If 'patch' is '1', assume a patch. if '2', assume a diff.
3021 * 0: A multi-file patch or diff is read from stdin.
3022 * A 'patch' is applies to relevant files. A 'diff' is just
3024 * 1: if 'patch', parse it as a multi-file patch/diff and allow
3025 * the files to be browsed.
3026 * if filename ends '.rej', then treat it as a patch/diff again
3027 * a file with the same basename
3028 * Else treat the file as a merge (with conflicts) and view it.
3030 * 2: First file is original, second is patch unless patch==2,
3031 * then two files need to be diffed.
3032 * 3: Files are: original previous new. The diff between 'previous' and
3033 * 'new' needs to be applied to 'original'.
3035 * If a multi-file patch is being read, 'strip' tells how many
3036 * path components to strip. If it is -1, we guess based on
3038 * If 'reverse' is given, when we invert any patch or diff
3039 * If 'replace' then we save the resulting merge.
3045 int just_diff
= (patch
== 2);
3049 fprintf(stderr
, "%s: too many file names given.\n", Cmd
);
3052 case 0: /* stdin is a patch or diff */
3053 if (lseek(fileno(stdin
), 0L, 1) == -1) {
3054 /* cannot seek, so need to copy to a temp file */
3057 fprintf(stderr
, "%s: Cannot create temp file\n", Cmd
);
3060 pl
= parse_patch(stdin
, f
, &num_patches
);
3063 pl
= parse_patch(stdin
, NULL
, &num_patches
);
3064 in
= fdopen(dup(0), "r");
3066 /* use stderr for keyboard input */
3069 set_prefix(pl
, num_patches
, strip
) == 0) {
3070 fprintf(stderr
, "%s: aborting\n", Cmd
);
3073 main_window(pl
, &num_patches
, in
, reverse
, replace
, ignore_blanks
, just_diff
);
3074 plist_free(pl
, num_patches
);
3078 case 1: /* a patch/diff, a .rej, or a merge file */
3079 f
= fopen(argv
[0], "r");
3081 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
3084 check_dir(argv
[0], fileno(f
));
3086 pl
= parse_patch(f
, NULL
, &num_patches
);
3087 if (!just_diff
&& set_prefix(pl
, num_patches
, strip
) == 0) {
3088 fprintf(stderr
, "%s: aborting\n", Cmd
);
3091 main_window(pl
, &num_patches
, f
, reverse
, replace
,ignore_blanks
, just_diff
);
3092 plist_free(pl
, num_patches
);
3093 } else if (strlen(argv
[0]) > 4 &&
3094 strcmp(argv
[0]+strlen(argv
[0])-4, ".rej") == 0) {
3095 char *origname
= strdup(argv
[0]);
3096 origname
[strlen(origname
) - 4] = '\0';
3097 show_merge(origname
, f
, reverse
, 0, NULL
, NULL
,
3098 replace
, outfilename
,
3099 selftest
, ignore_blanks
, just_diff
);
3101 show_merge(argv
[0], f
, reverse
, 1, NULL
, NULL
,
3102 replace
, outfilename
,
3103 selftest
, ignore_blanks
, just_diff
);
3106 case 2: /* an orig and a diff/.rej or two files */
3108 show_merge(NULL
, NULL
, reverse
, 0, argv
[0], argv
[1],
3109 replace
, outfilename
,
3110 selftest
, ignore_blanks
, just_diff
);
3113 f
= fopen(argv
[1], "r");
3114 check_dir(argv
[1], fileno(f
));
3116 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
3119 show_merge(argv
[0], f
, reverse
, 0, NULL
, NULL
,
3120 replace
, outfilename
,
3121 selftest
, ignore_blanks
, just_diff
);
3123 case 3: /* orig, before, after */
3124 show_merge(argv
[0], NULL
, reverse
, 0, argv
[1], argv
[2],
3125 replace
, outfilename
,
3126 selftest
, ignore_blanks
, just_diff
);