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
, int backup
)
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 #define free_stuff(none) \
1382 #define find_line(ln) \
1384 pos.p.m = 0; /* merge node */ \
1385 pos.p.s = 0; /* stream number */ \
1386 pos.p.o = -1; /* offset */ \
1389 memset(&curs, 0, sizeof(curs)); \
1391 next_mline(&pos, fm, fb, fa, ci.merger, mode); \
1392 while (pos.p.lineno < ln && ci.merger[pos.p.m].type != End); \
1395 #define prepare_merge(ch) \
1397 /* FIXME check for errors in the stream */ \
1398 fm = split_stream(sm, ByWord | ignore_blanks); \
1399 fb = split_stream(sb, ByWord | ignore_blanks); \
1400 fa = split_stream(sa, ByWord | ignore_blanks); \
1402 if (ch && !just_diff) \
1403 csl1 = pdiff(fm, fb, ch); \
1405 csl1 = diff(fm, fb, 1); \
1406 csl2 = diff_patch(fb, fa, 1); \
1408 ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1, 0); \
1409 for (i = 0; ci.merger[i].type != End; i++) \
1410 ci.merger[i].oldtype = ci.merger[i].type; \
1420 /* three separate files */
1421 sb
= load_file(p
->before
);
1422 sa
= load_file(p
->after
);
1426 sm
= load_file(p
->file
);
1428 /* One merge file */
1429 sp
= load_file(p
->file
);
1431 split_merge(sp
, &sm
, &sa
, &sb
);
1433 split_merge(sp
, &sm
, &sb
, &sa
);
1438 sp
= load_segment(f
, p
->start
, p
->end
);
1441 split_merge(sp
, &sm
, &sa
, &sb
);
1443 split_merge(sp
, &sm
, &sb
, &sa
);
1447 ch
= split_patch(sp
, &sa
, &sb
);
1449 ch
= split_patch(sp
, &sb
, &sa
);
1453 sm
= load_file(p
->file
);
1457 if (!sm
.body
|| !sb
.body
|| !sa
.body
) {
1464 help_window(help_missing
, NULL
, 0);
1466 help_window(help_corrupt
, NULL
, 0);
1471 term_init(!selftest
);
1482 if (row
< 1 || row
>= lastrow
)
1487 if (mode
== (ORIG
|RESULT
)) {
1488 int cmode
= check_line(pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1489 if (cmode
& (WIGGLED
| CONFLICTED
)) {
1491 splitrow
= (rows
+1)/2;
1492 lastrow
= splitrow
- 1;
1495 } else if (!curs
.alt
&& splitrow
>= 0) {
1500 } else if (splitrow
>= 0) {
1507 getmaxyx(stdscr
, rows
, cols
);
1508 rows
--; /* keep last row clear */
1509 if (splitrow
>= 0) {
1510 splitrow
= (rows
+1)/2;
1511 lastrow
= splitrow
- 1;
1519 if (row
> lastrow
+3)
1525 /* Always refresh the line */
1526 while (start
> curs
.target
) {
1532 vispos
= pos
; /* visible position - if cursor is in
1533 * alternate pane, pos might not be visible
1535 if (check_line(vispos
, fm
, fb
, fa
, ci
.merger
, mode
)
1537 if (vispos
.state
== 0) {
1539 vispos
.lo
= vispos
.p
;
1540 vispos
.hi
= vispos
.p
;
1546 if (visible(mode
, ci
.merger
, &vispos
) < 0)
1547 prev_mline(&vispos
, fm
, fb
, fa
, ci
.merger
, mode
);
1551 draw_mline(mode
, row
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1552 vispos
, (splitrow
>= 0 && curs
.alt
) ? NULL
: &curs
);
1553 if (curs
.width
== 0 && start
< curs
.col
) {
1554 /* width == 0 implies it appear after end-of-screen */
1559 if (curs
.col
< start
) {
1571 for (i
= row
-1; i
>= 1 && tpos
.p
.m
>= 0; ) {
1572 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1573 draw_mline(mode
, i
--, start
, cols
,
1574 fm
, fb
, fa
, ci
.merger
,
1585 blank(i
--, 0, cols
, a_void
);
1587 for (i
= row
; i
<= lastrow
&& ci
.merger
[tpos
.p
.m
].type
!= End
; ) {
1588 draw_mline(mode
, i
++, start
, cols
,
1589 fm
, fb
, fa
, ci
.merger
,
1591 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1593 botpos
= tpos
; botrow
= i
;
1594 while (i
<= lastrow
)
1595 blank(i
++, 0, cols
, a_void
);
1598 if (splitrow
>= 0) {
1599 struct mpos spos
= pos
;
1600 int smode
= BEFORE
|AFTER
;
1601 int srow
= (rows
+ splitrow
)/2;
1602 if (check_line(spos
, fm
, fb
, fa
, ci
.merger
, smode
)
1604 if (spos
.state
== 0)
1609 if (visible(smode
, ci
.merger
, &spos
) < 0)
1610 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1611 /* Now hi/lo might be wrong, so lets fix it. */
1614 /* 'hi' might be wrong so we mustn't depend
1615 * on it while walking back. So set state
1616 * to 1 to avoid ever testing it.
1619 while (spos
.p
.m
>= 0 && spos
.state
!= 0)
1620 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1621 while (!same_mpos(spos
, tpos
) &&
1623 ci
.merger
[spos
.p
.m
].type
!= End
)
1624 next_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1626 (void)attrset(a_sep
);
1627 for (i
= 0; i
< cols
; i
++)
1628 mvaddstr(splitrow
, i
, "-");
1631 for (i
= srow
-1; i
> splitrow
; i
--) {
1632 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1633 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1636 while (i
> splitrow
)
1637 blank(i
--, 0, cols
, a_void
);
1640 i
< rows
&& tpos
.p
.m
>= 0 &&
1641 ci
.merger
[tpos
.p
.m
].type
!= End
;
1643 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1645 (i
== srow
&& curs
.alt
) ? &curs
: NULL
);
1646 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1649 blank(i
++, 0, cols
, a_void
);
1651 /* Now that curs is accurate, report the type */
1656 l
= snprintf(buf
, 100, "File: %s%s ",
1657 p
->file
, reverse
? " - reversed" : "");
1658 snprintf(buf
+l
, 100-l
, "Mode: %s", modename
);
1659 (void)attrset(A_BOLD
);
1660 mvaddstr(0, 0, buf
);
1661 (void)attrset(A_NORMAL
);
1663 addstr(" (ignoring blanks)");
1665 (void)attrset(A_BOLD
);
1666 if (ci
.merger
[curs
.pos
.m
].type
!= ci
.merger
[curs
.pos
.m
].oldtype
)
1667 l
= snprintf(buf
, sizeof(buf
), "%s->", typenames
[ci
.merger
[curs
.pos
.m
].oldtype
]);
1668 snprintf(buf
+l
, sizeof(buf
)-l
, "%s ln:%d",
1669 typenames
[ci
.merger
[curs
.pos
.m
].type
],
1670 (pos
.p
.lineno
-1)/2);
1671 mvaddstr(0, cols
- strlen(buf
) - 1, buf
);
1673 #define META(c) ((c)|0x1000)
1674 #define SEARCH(c) ((c)|0x2000)
1675 #define CTRLX(c) ((c)|0x4000)
1677 (void)attrset(A_NORMAL
);
1686 snprintf(buf
, sizeof(buf
), "%d ", num
);
1691 if (meta
& CTRLX(0))
1693 if (meta
& SEARCH(0)) {
1695 addstr("Backwards ");
1698 if (search_notfound
)
1699 addstr(" - Not Found.");
1700 search_notfound
= 0;
1703 /* '+1' to skip over the leading +/-/| char */
1704 if (curs
.alt
&& splitrow
> 0)
1705 move((rows
+ splitrow
)/2, curs
.col
- start
+ 1);
1706 else if (curs
.alt
&& ((mode
& (BEFORE
|AFTER
)) &&
1707 (mode
& (ORIG
|RESULT
))))
1708 move(row
, curs
.col
-start
+ (cols
-1)/2+2);
1710 move(row
, curs
.col
-start
+1);
1719 tmeta
= meta
; meta
= 0;
1720 tnum
= num
; num
= -1;
1721 cswitch
= c
| tmeta
;
1722 /* Handle some ranges */
1723 /* case '0' ... '9': */
1724 if (cswitch
>= '0' && cswitch
<= '9')
1726 /* case SEARCH(' ') ... SEARCH('~'): */
1727 if (cswitch
>= SEARCH(' ') && cswitch
<= SEARCH('~'))
1728 cswitch
= SEARCH(' ');
1731 case 27: /* escape */
1741 case META('<'): /* start of file */
1746 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1747 } while (tpos
.p
.m
>= 0);
1751 case META('>'): /* end of file */
1758 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1759 } while (ci
.merger
[tpos
.p
.m
].type
!= End
);
1763 case '0': /* actually '0'...'9' */
1766 num
= tnum
*10 + (c
-'0');
1770 mesg
= "Autosave disabled";
1772 mesg
= "Use 'q' to quit";
1776 mesg
= "Will auto-save on exit, using Ctrl-C to cancel";
1785 answer
= help_window(save_query
, NULL
, 1);
1790 p
->conflicts
= isolate_conflicts(
1791 fm
, fb
, fa
, csl1
, csl2
, 0,
1792 ci
.merger
, 0, &p
->wiggles
);
1793 p
->chunks
= p
->conflicts
;
1794 save_merge(fm
, fb
, fa
, ci
.merger
,
1795 p
->outfile
? p
->outfile
: p
->file
,
1796 backup
&& (p
->outfile
? 0 : !p
->is_merge
));
1806 case 'I': /* Toggle ignoring of spaces */
1809 answer
= help_window(toggle_ignore
, NULL
, 1);
1815 ignore_blanks
= ignore_blanks
? 0 : IgnoreBlanks
;
1817 find_line(pos
.p
.lineno
);
1823 if (!p
->file
|| just_diff
) {
1824 mesg
= "Cannot run editor when diffing";
1828 lineno
= save_tmp_merge(fm
, fb
, fa
, ci
.merger
,
1830 ci
.merger
+ pos
.p
.m
,
1835 do_edit(tempname
, lineno
);
1836 sp
= load_file(tempname
);
1838 split_merge(sp
, &sm
, &sb
, &sa
);
1839 if (sp
.len
== sm
.len
&&
1840 memcmp(sp
.body
, sm
.body
, sm
.len
) == 0 &&
1842 /* no conflicts left, so display diff */
1844 sm
= load_file(p
->file
);
1847 sb
.body
= memdup(sm
.body
, sm
.len
);
1855 find_line(pos
.p
.lineno
);
1862 /* incr search forward */
1865 search
[searchlen
] = 0;
1870 /* incr search backwards */
1873 search
[searchlen
] = 0;
1876 case SEARCH('G'-64):
1877 case SEARCH('S'-64):
1878 case SEARCH('R'-64):
1880 if ((c
|tmeta
) == SEARCH('R'-64))
1885 tpos
= pos
; trow
= row
;
1888 case SEARCH('H'-64):
1889 case SEARCH(KEY_BACKSPACE
):
1892 struct search_anchor
*a
;
1898 struct search_anchor
*a
;
1906 search_notfound
= a
->notfound
;
1907 searchlen
= a
->searchlen
;
1908 search
[searchlen
] = 0;
1913 case SEARCH(' '): /* actually ' '...'~' */
1916 if (searchlen
< sizeof(search
)-1)
1917 search
[searchlen
++] = c
& (0x7f);
1918 search
[searchlen
] = 0;
1919 tpos
= pos
; trow
= row
;
1921 search_notfound
= 1;
1922 if (ignore_case
== 1 || ignore_case
== 2) {
1925 for (i
=0; i
< searchlen
; i
++)
1926 if (isupper(search
[i
])) {
1932 if (mcontains(tpos
, fm
, fb
, fa
, ci
.merger
,
1933 mmode
, search
, &curs
, searchdir
,
1934 ignore_case
>= 2)) {
1938 search_notfound
= 0;
1941 if (searchdir
< 0) {
1943 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1946 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1948 } while (tpos
.p
.m
>= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1949 searchdir
/= abs(searchdir
);
1959 case 'V'-64: /* page down */
1961 if (botrow
<= lastrow
) {
1971 case META('v'): /* page up */
1978 if (getmouse(&mevent
) != OK
)
1980 /* First see if this is on the 'other' pane */
1982 /* merge mode, top and bottom */
1983 if ((curs
.alt
&& mevent
.y
< splitrow
) ||
1984 (!curs
.alt
&& mevent
.y
> splitrow
)) {
1987 } else if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
)) {
1988 /* side-by-side mode */
1989 if ((curs
.alt
&& mevent
.x
< cols
/2) ||
1990 (!curs
.alt
&& mevent
.x
> cols
/2)) {
1994 /* Now try to find the right line */
1995 if (splitrow
< 0 || !curs
.alt
)
1998 trow
= (rows
+ splitrow
)/2;
1999 while (trow
> mevent
.y
) {
2001 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2002 if (tpos
.p
.m
>= 0) {
2008 while (trow
< mevent
.y
) {
2010 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2011 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2017 if (splitrow
< 0 || !curs
.alt
)
2018 /* it is OK to change the row */
2021 /* Now set the target column */
2022 if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
) &&
2024 curs
.target
= start
+ mevent
.x
- cols
/ 2 - 1;
2026 curs
.target
= start
+ mevent
.x
- 1;
2034 for (; tnum
> 0 ; tnum
--) {
2036 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2037 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2052 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2053 } while (!(pos
.state
== 0
2054 && (check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2055 & (CONFLICTED
|WIGGLED
)) == 0)
2056 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2060 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2061 } while (pos
.state
== 0
2062 && (check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2063 & (CONFLICTED
|WIGGLED
)) == 0
2064 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2072 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2073 } while ((check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2075 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2079 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2080 } while ((check_line(pos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2082 && ci
.merger
[tpos
.p
.m
].type
!= End
);
2091 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2092 } while (tpos
.state
== 0
2093 && (check_line(tpos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2094 & (CONFLICTED
|WIGGLED
)) == 0
2099 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2100 } while (!(tpos
.state
== 0
2101 && (check_line(tpos
, fm
, fb
, fa
, ci
.merger
, mmode
)
2102 & (CONFLICTED
|WIGGLED
)) == 0)
2112 for (; tnum
> 0 ; tnum
--) {
2114 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2115 if (tpos
.p
.m
>= 0) {
2126 curs
.target
= curs
.col
- 1;
2127 if (curs
.target
< 0) {
2128 /* Try to go to end of previous line */
2130 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2131 if (tpos
.p
.m
>= 0) {
2143 if (curs
.width
>= 0)
2144 curs
.target
= curs
.col
+ curs
.width
;
2146 /* end of line, go to next */
2148 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2149 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
2172 curs
.alt
= !curs
.alt
;
2173 if (curs
.alt
&& mode
== (ORIG
|RESULT
))
2174 mmode
= (BEFORE
|AFTER
);
2179 case 'a': /* 'after' view in patch window */
2182 mode
= AFTER
; modename
= "after"; modehelp
= after_help
;
2183 mmode
= mode
; curs
.alt
= 0;
2186 case 'b': /* 'before' view in patch window */
2189 mode
= BEFORE
; modename
= "before"; modehelp
= before_help
;
2190 mmode
= mode
; curs
.alt
= 0;
2193 case 'o': /* 'original' view in the merge window */
2196 mode
= ORIG
; modename
= "original"; modehelp
= orig_help
;
2197 mmode
= mode
; curs
.alt
= 0;
2200 case 'r': /* the 'result' view in the merge window */
2203 mode
= RESULT
; modename
= "result"; modehelp
= result_help
;
2204 mmode
= mode
; curs
.alt
= 0;
2208 if (mode
== (BEFORE
|AFTER
))
2210 mode
= BEFORE
|AFTER
; modename
= "diff"; modehelp
= diff_help
;
2211 mmode
= mode
; curs
.alt
= 0;
2216 mode
= ORIG
|RESULT
; modename
= "merge"; modehelp
= merge_help
;
2217 mmode
= mode
; curs
.alt
= 0;
2222 if (mode
== (ORIG
|RESULT
|BEFORE
|AFTER
))
2224 mode
= ORIG
|RESULT
|BEFORE
|AFTER
; modename
= "sidebyside"; modehelp
= sidebyside_help
;
2225 mmode
= mode
; curs
.alt
= 0;
2229 case 'H': /* scroll window to the right */
2232 curs
.target
= start
+ 1;
2235 case 'L': /* scroll window to the left */
2238 curs
.target
= start
+ 1;
2242 case 'x': /* Toggle rejecting of conflict.
2243 * A 'Conflict' or 'Changed' becomes 'Unchanged'
2244 * 'Unmatched' becomes 'Changed'
2246 if (ci
.merger
[curs
.pos
.m
].oldtype
== Conflict
||
2247 ci
.merger
[curs
.pos
.m
].oldtype
== Changed
)
2249 else if (ci
.merger
[curs
.pos
.m
].oldtype
== Unmatched
)
2254 if (ci
.merger
[curs
.pos
.m
].type
== next
)
2255 ci
.merger
[curs
.pos
.m
].type
= ci
.merger
[curs
.pos
.m
].oldtype
;
2257 ci
.merger
[curs
.pos
.m
].type
= next
;
2258 p
->conflicts
= isolate_conflicts(
2259 fm
, fb
, fa
, csl1
, csl2
, 0,
2260 ci
.merger
, 0, &p
->wiggles
);
2265 case 'c': /* Toggle accepting of conflict.
2266 * A 'Conflict' or 'Extraneous' becomes 'Changed'
2268 if (ci
.merger
[curs
.pos
.m
].oldtype
!= Conflict
&&
2269 ci
.merger
[curs
.pos
.m
].oldtype
!= Extraneous
)
2272 if (ci
.merger
[curs
.pos
.m
].type
== Changed
)
2273 ci
.merger
[curs
.pos
.m
].type
= ci
.merger
[curs
.pos
.m
].oldtype
;
2275 ci
.merger
[curs
.pos
.m
].type
= Changed
;
2276 p
->conflicts
= isolate_conflicts(
2277 fm
, fb
, fa
, csl1
, csl2
, 0,
2278 ci
.merger
, 0, &p
->wiggles
);
2283 case 'X': /* Reset all changes on the current line */
2286 ci
.merger
[tpos
.p
.m
].type
=
2287 ci
.merger
[tpos
.p
.m
].oldtype
;
2288 e
= prev_melmnt(&tpos
.p
, fm
, fb
, fa
, ci
.merger
);
2291 } while (!ends_line(e
) ||
2292 visible(mode
& (RESULT
|AFTER
), ci
.merger
, &tpos
) < 0);
2293 p
->conflicts
= isolate_conflicts(
2294 fm
, fb
, fa
, csl1
, csl2
, 0,
2295 ci
.merger
, 0, &p
->wiggles
);
2301 help_window(modehelp
, merge_window_help
, 0);
2310 if (meta
== SEARCH(0)) {
2311 if (anchor
== NULL
||
2312 !same_mpos(anchor
->pos
, pos
) ||
2313 anchor
->searchlen
!= searchlen
||
2314 !same_mp(anchor
->curs
.pos
, curs
.pos
)) {
2315 struct search_anchor
*a
= xmalloc(sizeof(*a
));
2320 a
->searchlen
= searchlen
;
2321 a
->notfound
= search_notfound
;
2327 struct search_anchor
*a
= anchor
;
2333 /* move backward and forward to make sure we
2334 * are on a visible line
2337 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2341 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
2342 if (ci
.merger
[tpos
.p
.m
].type
!= End
)
2348 static int show_merge(char *origname
, FILE *patch
, int reverse
,
2349 int is_merge
, char *before
, char *after
,
2350 int replace
, char *outfile
,
2351 int selftest
, int ignore_blanks
,
2352 int just_diff
, int backup
)
2354 struct plist p
= {0};
2357 p
.outfile
= replace
? outfile
: NULL
;
2360 fseek(patch
, 0, SEEK_END
);
2361 p
.end
= ftell(patch
);
2362 fseek(patch
, 0, SEEK_SET
);
2365 p
.is_merge
= is_merge
;
2369 freopen("/dev/null","w",stderr
);
2370 return merge_window(&p
, patch
, reverse
, replace
, selftest
,
2371 ignore_blanks
, just_diff
, backup
);
2374 static void calc_one(struct plist
*pl
, FILE *f
, int reverse
,
2375 int ignore_blanks
, int just_diff
)
2377 struct stream s1
, s2
;
2378 struct stream s
= load_segment(f
, pl
->start
, pl
->end
);
2382 split_merge(s
, &sf
, &s2
, &s1
);
2384 split_merge(s
, &sf
, &s1
, &s2
);
2388 pl
->chunks
= split_patch(s
, &s2
, &s1
);
2390 pl
->chunks
= split_patch(s
, &s1
, &s2
);
2394 sf
= load_file(pl
->file
);
2396 if (sf
.body
== NULL
|| s1
.body
== NULL
|| s1
.body
== NULL
) {
2397 pl
->wiggles
= pl
->conflicts
= -1;
2399 struct file ff
, fp1
, fp2
;
2400 struct csl
*csl1
, *csl2
;
2402 ff
= split_stream(sf
, ByWord
| ignore_blanks
);
2403 fp1
= split_stream(s1
, ByWord
| ignore_blanks
);
2404 fp2
= split_stream(s2
, ByWord
| ignore_blanks
);
2405 if (pl
->chunks
&& !just_diff
)
2406 csl1
= pdiff(ff
, fp1
, pl
->chunks
);
2408 csl1
= diff(ff
, fp1
, 1);
2409 csl2
= diff_patch(fp1
, fp2
, 1);
2410 ci
= make_merger(ff
, fp1
, fp2
, csl1
, csl2
, 0, 1, 0);
2411 pl
->wiggles
= ci
.wiggles
;
2412 pl
->conflicts
= ci
.conflicts
;
2429 static int get_prev(int pos
, struct plist
*pl
, int n
, int mode
)
2432 if (pos
== -1 || pl
== NULL
)
2435 if (pl
[pos
].prev
== -1)
2436 return pl
[pos
].parent
;
2438 while (pl
[pos
].open
&&
2441 if (pl
[pos
].last
>= 0)
2442 /* always see directories */
2446 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2448 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2450 } while (pos
>= 0 && !found
);
2454 static int get_next(int pos
, struct plist
*pl
, int n
, int mode
,
2455 FILE *f
, int reverse
, int ignore_blanks
, int just_diff
)
2467 while (pos
>= 0 && pl
[pos
].next
== -1)
2468 pos
= pl
[pos
].parent
;
2474 if (pl
[pos
].calced
== 0 && pl
[pos
].end
)
2475 calc_one(pl
+pos
, f
, reverse
, ignore_blanks
, just_diff
);
2476 if (pl
[pos
].last
>= 0)
2477 /* always see directories */
2481 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2483 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2485 } while (pos
>= 0 && !found
);
2489 static void draw_one(int row
, struct plist
*pl
, FILE *f
, int reverse
,
2490 int ignore_blanks
, int just_diff
)
2500 if (pl
->calced
== 0 && pl
->end
)
2501 /* better load the patch and count the chunks */
2502 calc_one(pl
, f
, reverse
, ignore_blanks
, just_diff
);
2506 if (pl
->chunks
> 99)
2509 sprintf(hdr
, "%2d", pl
->chunks
);
2510 if (pl
->wiggles
> 99)
2511 strcpy(hdr
+2, " XX");
2513 sprintf(hdr
+2, " %2d", pl
->wiggles
);
2514 if (pl
->conflicts
> 99)
2515 strcpy(hdr
+5, " XX ");
2517 sprintf(hdr
+5, " %2d ", pl
->conflicts
);
2520 strcpy(hdr
+9, "= ");
2522 strcpy(hdr
+9, "+ ");
2524 strcpy(hdr
+9, "- ");
2528 else if (pl
->is_merge
)
2530 else if (pl
->conflicts
)
2531 attrset(a_has_conflicts
);
2532 else if (pl
->wiggles
)
2533 attrset(a_has_wiggles
);
2535 attrset(a_no_wiggles
);
2537 mvaddstr(row
, 0, hdr
);
2538 mvaddstr(row
, 11, pl
->file
);
2542 static int save_one(FILE *f
, struct plist
*pl
, int reverse
,
2543 int ignore_blanks
, int backup
)
2545 struct stream sp
, sa
, sb
, sm
;
2546 struct file fa
, fb
, fm
;
2547 struct csl
*csl1
, *csl2
;
2550 sp
= load_segment(f
, pl
->start
,
2553 chunks
= split_patch(sp
, &sa
, &sb
);
2555 chunks
= split_patch(sp
, &sb
, &sa
);
2556 fb
= split_stream(sb
, ByWord
| ignore_blanks
);
2557 fa
= split_stream(sa
, ByWord
| ignore_blanks
);
2558 sm
= load_file(pl
->file
);
2559 fm
= split_stream(sm
, ByWord
| ignore_blanks
);
2560 csl1
= pdiff(fm
, fb
, chunks
);
2561 csl2
= diff_patch(fb
, fa
, 1);
2562 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1, 0);
2563 return save_merge(fm
, fb
, fa
, ci
.merger
,
2567 static char *main_help
[] = {
2568 " You are using the \"browse\" mode of wiggle.",
2569 "This page shows a list of files in a patch together with",
2570 "the directories that contain them.",
2571 "A directory is indicated by a '+' if the contents are",
2572 "listed or a '-' if the contents are hidden. A file is",
2573 "indicated by an '='. Typing <space> or <return> will",
2574 "expose or hide a directory, and will visit a file.",
2576 "The three columns of numbers are:",
2577 " Ch The number of patch chunks which applied to",
2579 " Wi The number of chunks that needed to be wiggled",
2581 " Co The number of chunks that created an unresolvable",
2584 "Keystrokes recognised in this page are:",
2585 " ? Display this help",
2586 " SPC On a directory, toggle hiding of contents",
2587 " On file, visit the file",
2590 " control-C Disable auto-save-on-exit",
2591 " n,j,DOWN Go to next line",
2592 " p,k,UP Go to previous line",
2594 " A list All files",
2595 " W only list files with a wiggle or a conflict",
2596 " C only list files with a conflict",
2598 " S Save this file with changes applied. If",
2599 " some but not all files are saved, wiggle will",
2600 " prompt on exit to save the rest.",
2601 " R Revert the current saved file to its original",
2603 " I toggle whether spaces are ignored",
2604 " when matching text.",
2607 static char *saveall_msg
= " %d file%s (of %d) have not been saved.";
2608 static char saveall_buf
[200];
2609 static char *saveall_query
[] = {
2612 " Would you like to save them?",
2613 " Y = yes, save them all",
2614 " N = no, exit without saving anything else",
2615 " Q = Don't quit just yet",
2618 static void main_window(struct plist
*pl
, int np
, FILE *f
, int reverse
,
2619 int replace
, int ignore_blanks
, int just_diff
, int backup
)
2621 /* The main window lists all files together with summary information:
2622 * number of chunks, number of wiggles, number of conflicts.
2623 * The list is scrollable
2624 * When a entry is 'selected', we switch to the 'file' window
2625 * The list can be condensed by removing files with no conflict
2626 * or no wiggles, or removing subdirectories
2628 * We record which file in the list is 'current', and which
2629 * screen line it is on. We try to keep things stable while
2632 * Counts are printed before the name using at most 2 digits.
2633 * Numbers greater than 99 are XX
2635 * 27 5 1 drivers/md/md.c
2637 * A directory show the sum in all children.
2640 * select: enter, space, mouseclick
2641 * on file, go to file window
2642 * on directory, toggle open
2643 * up: k, p, control-p uparrow
2644 * Move to previous open object
2645 * down: j, n, control-n, downarrow
2646 * Move to next open object
2648 * A W C: select All Wiggles or Conflicts
2653 char mesg_buf
[1024];
2654 int last_mesg_len
= 0;
2655 int pos
= 0; /* position in file */
2656 int row
= 1; /* position on screen */
2657 int rows
= 0; /* size of screen in rows */
2662 int mode
= 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2663 int cnt
; /* count of files that need saving */
2664 int any
; /* count of files that have been save*/
2667 char *debug
= getenv("WIGGLE_DEBUG");
2669 if (debug
&& !*debug
)
2672 freopen("/dev/null","w",stderr
);
2677 clear(); (void)attrset(0);
2679 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2682 addstr(" (ignoring blanks)");
2686 if (row
< 1 || row
>= rows
)
2690 getmaxyx(stdscr
, rows
, cols
);
2692 if (row
>= rows
+ 3)
2697 for (i
= row
; i
> 1; i
--) {
2698 tpos
= get_prev(tpos
, pl
, np
, mode
);
2704 /* Ok, row and pos could be trustworthy now */
2706 for (i
= row
; i
>= 1; i
--) {
2707 draw_one(i
, &pl
[tpos
], f
, reverse
, ignore_blanks
, just_diff
);
2708 tpos
= get_prev(tpos
, pl
, np
, mode
);
2711 for (i
= row
+1; i
< rows
; i
++) {
2712 tpos
= get_next(tpos
, pl
, np
, mode
, f
, reverse
,ignore_blanks
, just_diff
);
2714 draw_one(i
, &pl
[tpos
], f
, reverse
, ignore_blanks
, just_diff
);
2716 draw_one(i
, NULL
, f
, reverse
, ignore_blanks
, just_diff
);
2720 if (last_mesg_len
) {
2721 move(0, cols
- last_mesg_len
);
2726 last_mesg_len
= strlen(mesg
);
2727 move(0, cols
- last_mesg_len
);
2731 /* debugging help: report last keystroke */
2733 sprintf(bb
, "last-key = 0%o", c
);
2735 last_mesg_len
= strlen(bb
);
2736 mvaddstr(0, cols
- last_mesg_len
, bb
);
2746 tpos
= get_next(pos
, pl
, np
, mode
, f
, reverse
, ignore_blanks
, just_diff
);
2757 tpos
= get_prev(pos
, pl
, np
, mode
);
2765 if (getmouse(&mevent
) != OK
)
2767 while (row
< mevent
.y
&&
2768 (tpos
= get_next(pos
, pl
, np
, mode
, f
, reverse
, ignore_blanks
, just_diff
))
2773 while (row
> mevent
.y
&&
2774 (tpos
= get_prev(pos
, pl
, np
, mode
)) >= 0) {
2778 if (row
!= mevent
.y
)
2779 /* couldn't find the line */
2784 if (pl
[pos
].end
== 0) {
2785 pl
[pos
].open
= !pl
[pos
].open
;
2788 mesg
= "Opened folder";
2790 mesg
= "Closed folder";
2793 if (pl
[pos
].is_merge
)
2794 c
= merge_window(&pl
[pos
], NULL
, reverse
, 0, 0,
2795 ignore_blanks
, just_diff
, backup
);
2797 c
= merge_window(&pl
[pos
], f
, reverse
, 0, 0,
2798 ignore_blanks
, just_diff
, backup
);
2801 pl
[pos
].is_merge
= 1;
2802 snprintf(mesg_buf
, cols
,
2809 case 27: /* escape */
2811 mvaddstr(0, cols
-10, "ESC..."); clrtoeol();
2815 move(0, cols
-10); clrtoeol();
2819 mesg
= "Save-on-exit disabled. Use 'q' to quit.";
2821 mesg
= "Use 'q' to quit.";
2828 for (i
= 0; i
< np
; i
++)
2829 if (pl
[i
].end
&& !pl
[i
].is_merge
)
2841 sprintf(saveall_buf
, saveall_msg
,
2842 cnt
, cnt
== 1 ? "" : "s", cnt
+any
);
2843 ans
= help_window(saveall_query
, NULL
, 1);
2849 for (i
= 0; i
< np
; i
++) {
2854 ignore_blanks
, backup
);
2860 printf("%d file%s saved\n", cnt
,
2861 cnt
== 1 ? "" : "s");
2865 mode
= 0; refresh
= 1;
2866 mesg
= "Showing ALL files";
2869 mode
= 1; refresh
= 1;
2870 mesg
= "Showing Wiggled files";
2873 mode
= 2; refresh
= 1;
2874 mesg
= "Showing Conflicted files";
2877 case 'S': /* Save updated file */
2878 if (pl
[pos
].end
== 0) {
2880 mesg
= "Cannot save a folder.";
2881 } else if (pl
[pos
].is_merge
) {
2883 mesg
= "File is already saved.";
2885 if (save_one(f
, &pl
[pos
], reverse
, ignore_blanks
, backup
) == 0) {
2886 pl
[pos
].is_merge
= 1;
2887 snprintf(mesg_buf
, cols
,
2890 pl
[pos
].chunks
= pl
[pos
].conflicts
;
2891 pl
[pos
].wiggles
= 0;
2893 snprintf(mesg_buf
, cols
,
2894 "Failed to save file %s.",
2901 case 'R': /* Restore updated file */
2902 if (pl
[pos
].end
== 0)
2903 mesg
= "Cannot restore a folder.";
2904 else if (!pl
[pos
].is_merge
)
2905 mesg
= "File has not been saved, cannot restore.";
2907 mesg
= "Backups are disabled, nothing to restore!";
2909 /* rename foo.porig to foo, and clear is_merge */
2910 char *file
= pl
[pos
].file
;
2911 char *orignew
= xmalloc(strlen(file
) + 20);
2912 strcpy(orignew
, file
);
2913 strcat(orignew
, ".porig");
2914 if (rename(orignew
, file
) == 0) {
2915 mesg
= "File has been restored.";
2916 pl
[pos
].is_merge
= 0;
2918 calc_one(&pl
[pos
], f
, reverse
, ignore_blanks
, just_diff
);
2920 mesg
= "Could not restore file!";
2924 case 'I': /* Toggle ignoring blanks */
2925 ignore_blanks
= ignore_blanks
? 0 : IgnoreBlanks
;
2927 for (i
= 0; i
< np
; i
++)
2932 help_window(main_help
, NULL
, 0);
2943 static void catch(int sig
)
2945 if (sig
== SIGINT
&& !intr_kills
) {
2952 printf("Died on signal %d\n", sig
);
2954 if (sig
!= SIGBUS
&& sig
!= SIGSEGV
)
2957 /* Otherwise return and die */
2961 static void term_init(int doraw
)
2964 static int init_done
= 0;
2970 signal(SIGINT
, catch);
2971 signal(SIGQUIT
, catch);
2972 signal(SIGTERM
, catch);
2973 signal(SIGBUS
, catch);
2974 signal(SIGSEGV
, catch);
2983 use_default_colors();
2984 if (!has_colors()) {
2985 a_delete
= A_UNDERLINE
;
2987 a_common
= A_NORMAL
;
2989 a_already
= A_STANDOUT
;
2990 a_has_conflicts
= A_UNDERLINE
;
2991 a_has_wiggles
= A_BOLD
;
2992 a_no_wiggles
= A_NORMAL
;
2994 init_pair(1, COLOR_RED
, -1);
2995 a_delete
= COLOR_PAIR(1);
2996 init_pair(2, COLOR_GREEN
, -1);
2997 a_added
= COLOR_PAIR(2);
2998 a_common
= A_NORMAL
;
2999 init_pair(3, COLOR_WHITE
, COLOR_GREEN
);
3000 a_sep
= COLOR_PAIR(3); a_sep
= A_STANDOUT
;
3001 init_pair(4, -1, COLOR_YELLOW
);
3002 a_void
= COLOR_PAIR(4);
3003 init_pair(5, COLOR_BLUE
, -1);
3004 a_unmatched
= COLOR_PAIR(5);
3005 init_pair(6, COLOR_CYAN
, -1);
3006 a_extra
= COLOR_PAIR(6);
3008 init_pair(7, COLOR_BLACK
, COLOR_CYAN
);
3009 a_already
= COLOR_PAIR(7);
3011 a_has_conflicts
= a_delete
;
3012 a_has_wiggles
= a_added
;
3013 a_no_wiggles
= a_unmatched
;
3016 nonl(); intrflush(stdscr
, FALSE
); keypad(stdscr
, TRUE
);
3017 mousemask(ALL_MOUSE_EVENTS
, NULL
);
3020 int vpatch(int argc
, char *argv
[], int patch
, int strip
,
3021 int reverse
, int replace
, char *outfilename
,
3022 int selftest
, int ignore_blanks
, int backup
)
3024 /* NOTE argv[0] is first arg...
3025 * Behaviour depends on number of args and 'patch'.
3026 * If 'patch' is '1', assume a patch. if '2', assume a diff.
3027 * 0: A multi-file patch or diff is read from stdin.
3028 * A 'patch' is applies to relevant files. A 'diff' is just
3030 * 1: if 'patch', parse it as a multi-file patch/diff and allow
3031 * the files to be browsed.
3032 * if filename ends '.rej', then treat it as a patch/diff again
3033 * a file with the same basename
3034 * Else treat the file as a merge (with conflicts) and view it.
3036 * 2: First file is original, second is patch unless patch==2,
3037 * then two files need to be diffed.
3038 * 3: Files are: original previous new. The diff between 'previous' and
3039 * 'new' needs to be applied to 'original'.
3041 * If a multi-file patch is being read, 'strip' tells how many
3042 * path components to strip. If it is -1, we guess based on
3044 * If 'reverse' is given, when we invert any patch or diff
3045 * If 'replace' then we save the resulting merge.
3051 int just_diff
= (patch
== 2);
3055 fprintf(stderr
, "%s: too many file names given.\n", Cmd
);
3058 case 0: /* stdin is a patch or diff */
3059 if (lseek(fileno(stdin
), 0L, 1) == -1) {
3060 /* cannot seek, so need to copy to a temp file */
3063 fprintf(stderr
, "%s: Cannot create temp file\n", Cmd
);
3066 pl
= parse_patch(stdin
, f
, &num_patches
);
3069 pl
= parse_patch(stdin
, NULL
, &num_patches
);
3070 in
= fdopen(dup(0), "r");
3072 /* use stderr for keyboard input */
3075 set_prefix(pl
, num_patches
, strip
) == 0) {
3076 fprintf(stderr
, "%s: aborting\n", Cmd
);
3079 pl
= sort_patches(pl
, &num_patches
);
3080 main_window(pl
, num_patches
, in
, reverse
, replace
, ignore_blanks
,
3082 plist_free(pl
, num_patches
);
3086 case 1: /* a patch/diff, a .rej, or a merge file */
3087 f
= fopen(argv
[0], "r");
3089 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
3092 check_dir(argv
[0], fileno(f
));
3094 pl
= parse_patch(f
, NULL
, &num_patches
);
3095 if (!just_diff
&& set_prefix(pl
, num_patches
, strip
) == 0) {
3096 fprintf(stderr
, "%s: aborting\n", Cmd
);
3099 pl
= sort_patches(pl
, &num_patches
);
3100 main_window(pl
, num_patches
, f
, reverse
, replace
,
3101 ignore_blanks
, just_diff
, backup
);
3102 plist_free(pl
, num_patches
);
3103 } else if (strlen(argv
[0]) > 4 &&
3104 strcmp(argv
[0]+strlen(argv
[0])-4, ".rej") == 0) {
3105 char *origname
= strdup(argv
[0]);
3106 origname
[strlen(origname
) - 4] = '\0';
3107 show_merge(origname
, f
, reverse
, 0, NULL
, NULL
,
3108 replace
, outfilename
,
3109 selftest
, ignore_blanks
, just_diff
, backup
);
3111 show_merge(argv
[0], f
, reverse
, 1, NULL
, NULL
,
3112 replace
, outfilename
,
3113 selftest
, ignore_blanks
, just_diff
, backup
);
3116 case 2: /* an orig and a diff/.rej or two files */
3118 show_merge(NULL
, NULL
, reverse
, 0, argv
[0], argv
[1],
3119 replace
, outfilename
,
3120 selftest
, ignore_blanks
, just_diff
, backup
);
3123 f
= fopen(argv
[1], "r");
3125 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
3128 check_dir(argv
[1], fileno(f
));
3129 show_merge(argv
[0], f
, reverse
, 0, NULL
, NULL
,
3130 replace
, outfilename
,
3131 selftest
, ignore_blanks
, just_diff
, backup
);
3133 case 3: /* orig, before, after */
3134 show_merge(argv
[0], NULL
, reverse
, 0, argv
[1], argv
[2],
3135 replace
, outfilename
,
3136 selftest
, ignore_blanks
, just_diff
, backup
);