2 * wiggle - apply rejected patches
4 * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
5 * Copyright (C) 2010-2011 Neil Brown <neilb@suse.de>
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation; either version 2 of the License, or
11 * (at your option) any later version.
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software Foundation, Inc.,
20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 * Email: <neilb@suse.de>
27 * vpatch - visual front end for wiggle - aka Browse mode.
29 * "files" display, lists all files with statistics
30 * - can hide various lines including subdirectories
31 * and files without wiggles or conflicts
32 * "merge" display shows various views of merged file with different
33 * parts in different colours.
35 * The window can be split horizontally to show the original and result
36 * beside the diff, and each different branch can be shown alone.
48 static void term_init(void);
50 #define assert(x) do { if (!(x)) abort(); } while (0)
52 static struct stream
load_segment(FILE *f
,
53 unsigned int start
, unsigned int end
);
55 /* global attributes */
56 unsigned int a_delete
, a_added
, a_common
, a_sep
, a_void
,
57 a_unmatched
, a_extra
, a_already
;
59 /******************************************************************
61 * We display help in an insert, leaving 5 columns left and right,
62 * and 2 rows top and bottom, but at most 58x15 plus border
64 * SPC or RTN moves down or to next page
65 * BKSPC goes backwards
66 * 'q' returns to origin screen
67 * '?' show help on help
68 * left and right scroll help view
70 * A help text is an array of lines of text
74 " You are viewing the help page for the help viewer.",
75 "You normally get here by typing '?'",
77 "The following keystrokes work in the help viewer:",
78 " ? display this help message",
79 " q return to previous view",
80 " SPC move forward through help document",
82 " BKSP move backward through help document",
83 " RIGHT scroll help window so text on the right appears",
84 " LEFT scroll help window so text on the left appears",
88 char *help_missing
[] = {
89 "The file that this patch applies to appears",
91 "Please type 'q' to continue",
95 char *help_corrupt
[] = {
96 "This patch appears to be corrupt",
97 "Please type 'q' to continue",
101 /* We can give one or two pages to display in the help window.
102 * The first is specific to the current context. The second
103 * is optional and may provide help in a more broad context.
105 static void help_window(char *page1
[], char *page2
[])
115 getmaxyx(stdscr
, rows
, cols
);
121 left
= (cols
-58)/2 - 1;
129 top
= (rows
-15)/2 - 1;
133 /* Draw a border around the 'help' area */
134 (void)attrset(A_STANDOUT
);
135 for (c
= left
; c
< left
+cols
; c
++) {
136 mvaddch(top
-1, c
, '-');
137 mvaddch(top
+rows
, c
, '-');
139 for (r
= top
; r
< top
+ rows
; r
++) {
140 mvaddch(r
, left
-1, '|');
141 mvaddch(r
, left
+cols
, '|');
143 mvaddch(top
-1, left
-1, '/');
144 mvaddch(top
-1, left
+cols
, '\\');
145 mvaddch(top
+rows
, left
-1, '\\');
146 mvaddch(top
+rows
, left
+cols
, '/');
147 mvaddstr(top
-1, left
+ cols
/2 - 9, "HELP - 'q' to exit");
148 mvaddstr(top
+rows
, left
+cols
/2 - 17, "Press SPACE for more, '?' for help");
149 (void)attrset(A_NORMAL
);
152 char **lnp
= page
+ line
;
154 /* Draw as much of the page at the current offset
157 for (r
= 0; r
< rows
; r
++) {
165 while (*ln
&& sh
> 0) {
169 for (c
= 0; c
< cols
; c
++) {
175 mvaddch(top
+r
, left
+c
, chr
);
178 move(top
+rows
-1, left
);
185 if (page1
!= help_help
)
186 help_window(help_help
, NULL
);
189 case '\r': /* page-down */
190 for (r
= 0; r
< rows
-2; r
++)
204 case '\b': /* page up */
240 static char *typenames
[] = {
242 [Unmatched
] = "Unmatched",
243 [Unchanged
] = "Unchanged",
244 [Extraneous
] = "Extraneous",
245 [Changed
] = "Changed",
246 [Conflict
] = "Conflict",
247 [AlreadyApplied
] = "AlreadyApplied",
250 /* When we merge the original and the diff together we need
251 * to keep track of where everything came from.
252 * When we display the different views, we need to be able to
253 * select certain portions of the whole document.
254 * These flags are used to identify what is present, and to
255 * request different parts be extracted. They also help
256 * guide choice of colour.
262 #define CHANGED 16 /* The RESULT is different to ORIG */
263 #define CHANGES 32 /* AFTER is different to BEFORE */
264 #define WIGGLED 64 /* a conflict that was successfully resolved */
265 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
267 /* Displaying a Merge.
268 * The first step is to linearise the merge. The merge in inherently
269 * parallel with before/after streams. However much of the whole document
270 * is linear as normally much of the original in unchanged.
271 * All parallelism comes from the patch. This normally produces two
272 * parallel stream, but in the case of a conflict can produce three.
273 * For browsing the merge we only ever show two alternates in-line.
274 * When there are three we use two panes with 1 or 2 alternates in each.
275 * So to linearise the two streams we find lines that are completely
276 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
277 * a region where there are changes. We include everything between
278 * these twice, in two separate passes. The exact interpretation of the
279 * passes is handled at a higher level but will be one of:
280 * original and result
282 * original and after (for a conflict)
283 * This is all encoded in the 'struct merge'. An array of these describes
284 * the whole document.
286 * At any position in the merge we can be in one of 3 states:
287 * 0: unchanged section
291 * So to walk a merge in display order we need a position in the merge,
292 * a current state, and when in a changed section, we need to know the
293 * bounds of that changed section.
294 * This is all encoded in 'struct mpos'.
296 * Each location may or may not be visible depending on certain
299 * Also, some locations might be 'invalid' in that they don't need to be displayed.
300 * For example when the patch leaves a section of the original unchanged,
301 * we only need to see the original - the before/after sections are treated
302 * as invalid and are not displayed.
303 * The visibility of newlines is crucial and guides the display. One line
304 * of displayed text is all the visible sections between two visible newlines.
306 * Counting lines is a bit tricky. We only worry about line numbers in the
307 * original (stream 0) as these could compare with line numbers mentioned in
309 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
310 * That way we don't get a full counted line until we see the first char after the
311 * newline, so '+' lines are counted with the previous line.
315 int m
; /* merger index */
316 int s
; /* stream 0,1,2 for a,b,c */
317 int o
; /* offset in that stream */
318 int lineno
; /* Counts newlines in stream 0
319 * set lsb when see newline.
320 * add one when not newline and lsb set
324 struct mp p
, /* the current point (end of a line) */
325 lo
, /* eol for start of the current group */
326 hi
; /* eol for end of the current group */
328 * 0 if on an unchanged (lo/hi not meaningful)
329 * 1 if on the '-' of a diff,
330 * 2 if on the '+' of a diff
335 struct mp pos
; /* where in the document we are (an element) */
336 int offset
; /* which char in that element */
337 int target
; /* display column - or -1 if we are looking for 'pos' */
338 int col
; /* where we found pos or target */
339 int width
; /* Size of char, for moving to the right */
340 int alt
; /* Cursor is in alternate window */
343 /* used for checking location during search */
344 static int same_mp(struct mp a
, struct mp b
)
350 static int same_mpos(struct mpos a
, struct mpos b
)
352 return same_mp(a
.p
, b
.p
) &&
353 (a
.state
== b
.state
|| a
.state
== 0 || b
.state
== 0);
356 /* Check if a particular stream is meaningful in a particular merge
357 * section. e.g. in an Unchanged section, only stream 0, the
358 * original, is meaningful. This is used to avoid walking down
361 static int stream_valid(int s
, enum mergetype type
)
383 * Advance the 'pos' in the current mergepos returning the next
385 * This walks the merges in sequence, and the streams within
388 static struct elmnt
next_melmnt(struct mp
*pos
,
389 struct file fm
, struct file fb
, struct file fa
,
394 int l
= 0; /* Length remaining in current merge section */
408 /* Offset has reached length, choose new stream or
417 } while (!stream_valid(pos
->s
, m
[pos
->m
].type
));
421 if (pos
->m
== -1 || m
[pos
->m
].type
== End
) {
423 e
.start
= NULL
; e
.len
= 0;
427 default: /* keep compiler happy */
431 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
433 return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
434 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
435 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
439 /* step current position.p backwards */
440 static struct elmnt
prev_melmnt(struct mp
*pos
,
441 struct file fm
, struct file fb
, struct file fa
,
445 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
452 while (pos
->m
>= 0 && pos
->o
< 0) {
459 } while (pos
->m
>= 0 &&
460 !stream_valid(pos
->s
, m
[pos
->m
].type
));
464 pos
->o
= m
[pos
->m
].al
-1;
467 pos
->o
= m
[pos
->m
].bl
-1;
470 pos
->o
= m
[pos
->m
].cl
-1;
477 e
.start
= NULL
; e
.len
= 0;
481 default: /* keep compiler happy */
482 case 0: return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
483 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
484 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
488 /* 'visible' not only checks if this stream in this merge should be
489 * visible in this mode, but also chooses which colour/highlight to use
492 static int visible(int mode
, enum mergetype type
, int stream
)
496 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
498 case End
: /* The END is always visible */
500 case Unmatched
: /* Visible in ORIG and RESULT */
501 if (mode
& (ORIG
|RESULT
))
504 case Unchanged
: /* visible everywhere, but only show stream 0 */
508 case Extraneous
: /* stream 2 is visible in BEFORE and AFTER */
509 if ((mode
& (BEFORE
|AFTER
))
513 case Changed
: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
515 (mode
& (ORIG
|BEFORE
)))
518 (mode
& (RESULT
|AFTER
)))
525 return a_unmatched
| A_REVERSE
;
529 return a_extra
| A_UNDERLINE
;
532 if (mode
& (AFTER
|RESULT
))
533 return a_added
| A_UNDERLINE
;
540 if (mode
& (ORIG
|RESULT
))
545 return a_delete
| A_UNDERLINE
;
549 return a_added
| A_UNDERLINE
;
557 /* checkline creates a summary of the sort of changes that
558 * are in a line, returning an "or" of
564 static int check_line(struct mpos pos
, struct file fm
, struct file fb
,
566 struct merge
*m
, int mode
)
573 if (m
[pos
.p
.m
].type
== Changed
)
574 rv
|= CHANGED
| CHANGES
;
575 else if ((m
[pos
.p
.m
].type
== AlreadyApplied
||
576 m
[pos
.p
.m
].type
== Conflict
))
577 rv
|= CONFLICTED
| CHANGES
;
578 else if (m
[pos
.p
.m
].type
== Extraneous
)
580 else if (m
[pos
.p
.m
].type
== Unmatched
)
582 if (m
[pos
.p
.m
].in_conflict
)
583 rv
|= CONFLICTED
| CHANGES
;
584 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
585 } while (e
.start
!= NULL
&&
587 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
589 if (unmatched
&& (rv
& CHANGES
))
594 /* Find the next line in the merge which is visible.
595 * If we hit the end of a conflicted set during pass-1
596 * we rewind for pass-2.
597 * 'mode' tells which bits we want to see, possible one of
598 * the 4 parts (before/after/orig/result) or one of the pairs
599 * before+after or orig+result.
601 static void next_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
603 struct merge
*m
, int mode
)
612 struct elmnt e
= next_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
616 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
619 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
621 if ((mode2
& CHANGES
) && pos
->state
== 0) {
622 /* Just entered a diff-set */
625 } else if (!(mode2
& CHANGES
) && pos
->state
) {
626 /* Come to the end of a diff-set */
627 switch (pos
->state
) {
629 /* Need to record the end */
631 /* time for another pass */
636 /* finished final pass */
641 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
642 switch (pos
->state
) {
644 mask
&= ~(RESULT
|AFTER
);
647 mask
&= ~(ORIG
|BEFORE
);
650 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
654 /* Move to previous line - simply the reverse of next_mline */
655 static void prev_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
657 struct merge
*m
, int mode
)
668 struct elmnt e
= prev_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
672 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
675 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
677 if ((mode2
& CHANGES
) && pos
->state
== 0) {
678 /* Just entered a diff-set */
681 } else if (!(mode2
& CHANGES
) && pos
->state
) {
682 /* Come to the end (start) of a diff-set */
683 switch (pos
->state
) {
685 /* finished final pass */
689 /* Need to record the start */
691 /* time for another pass */
697 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
698 switch (pos
->state
) {
700 mask
&= ~(RESULT
|AFTER
);
703 mask
&= ~(ORIG
|BEFORE
);
706 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
709 /* blank a whole row of display */
710 static void blank(int row
, int start
, int cols
, unsigned int attr
)
718 /* search of a string on one display line. If found, update the
722 static int mcontains(struct mpos pos
,
723 struct file fm
, struct file fb
, struct file fa
,
725 int mode
, char *search
, struct cursor
*curs
,
726 int dir
, int ignore_case
)
728 /* See if any of the files, between start of this line and here,
729 * contain the search string.
730 * However this is modified by dir:
731 * -2: find last match *before* curs
732 * -1: find last match at-or-before curs
733 * 1: find first match at-or-after curs
734 * 2: find first match *after* curs
736 * We only test for equality with curs, so if it is on a different
737 * line it will not be found and everything is before/after.
738 * As we search from end-of-line to start we find the last
740 * For a forward search, we stop when we find curs.
741 * For a backward search, we forget anything found when we find curs.
747 int len
= strlen(search
);
750 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
751 if (e
.start
&& e
.start
[0]) {
754 if (same_mp(pos
.p
, curs
->pos
))
755 curs_i
= curs
->offset
;
758 for (i
= e
.len
-1; i
>= 0; i
--) {
759 if (i
== curs_i
&& dir
== -1)
760 /* next match is the one we want */
762 if (i
== curs_i
&& dir
== 2)
763 /* future matches not accepted */
765 if ((!found
|| dir
> 0) &&
766 (ignore_case
? strncasecmp
: strncmp
)
767 (e
.start
+i
, search
, len
) == 0) {
772 if (i
== curs_i
&& dir
== -2)
773 /* next match is the one we want */
775 if (i
== curs_i
&& dir
== 1)
776 /* future matches not accepted */
780 } while (e
.start
!= NULL
&&
782 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
791 /* Drawing the display window.
792 * There are 7 different ways we can display the data, each
793 * of which can be configured by a keystroke:
794 * o original - just show the original file with no changes, but still
795 * with highlights of what is changed or unmatched
796 * r result - show just the result of the merge. Conflicts just show
797 * the original, not the before/after options
798 * b before - show the 'before' stream of the patch
799 * a after - show the 'after' stream of the patch
800 * d diff - show just the patch, both before and after
801 * m merge - show the full merge with -+ sections for changes.
802 * If point is in a wiggled or conflicted section the
803 * window is split horizontally and the diff is shown
804 * in the bottom window
805 * | sidebyside - two panes, left and right. Left holds the merge,
806 * right holds the diff. In the case of a conflict,
807 * left holds orig/after, right holds before/after
809 * The horizontal split for 'merge' mode is managed as follows.
810 * - The window is split when we first visit a line that contains
811 * a wiggle or a conflict, and the second pane is removed when
812 * we next visit a line that contains no changes (is fully Unchanged).
813 * - to display the second pane, we find a visible end-of-line in the
814 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
815 * the we centre that line.
816 * - We need to rewind to an unchanged section, and wind forward again
817 * to make sure that 'lo' and 'hi' are set properly.
818 * - every time we move, we redraw the second pane (see how that goes).
821 /* draw_mside draws one text line or, in the case of sidebyside, one side
823 * The 'mode' tells us what to draw via the 'visible()' function.
824 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
825 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
826 * The desired cursor position is given in 'target' the actual end
827 * cursor position (allowing e.g. for tabs) is returned in *colp.
829 static void draw_mside(int mode
, int row
, int offset
, int start
, int cols
,
830 struct file fm
, struct file fb
, struct file fa
,
838 unsigned int tag_attr
;
841 case 0: /* unchanged line */
845 case 1: /* 'before' text */
848 if ((mode
& ORIG
) && (mode
& CONFLICTED
)) {
852 mode
&= (ORIG
|BEFORE
);
854 case 2: /* the 'after' part */
857 mode
&= (AFTER
|RESULT
);
861 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) < 0) {
862 /* Not visible, just draw a blank */
863 blank(row
, offset
, cols
, a_void
);
873 (void)attrset(tag_attr
);
874 mvaddch(row
, offset
, tag
);
877 (void)attrset(A_NORMAL
);
879 /* find previous visible newline, or start of file */
881 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
882 while (e
.start
!= NULL
&&
884 visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
889 e
= next_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
890 if (e
.start
== NULL
||
892 && visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) != -1)) {
893 /* We have reached the end of visible line, or end of file */
896 if (col
>= start
+ cols
)
899 curs
->width
= -1; /* end of line */
900 if (curs
->target
>= 0) {
903 } else if (same_mp(pos
.p
, curs
->pos
))
908 if (e
.start
&& e
.start
[0] == 0) {
911 if (pos
.p
.s
== 2 && m
[pos
.p
.m
].type
== Extraneous
) {
912 int A
, B
, C
, D
, E
, F
;
913 e1
= fb
.list
[m
[pos
.p
.m
].b
+ pos
.p
.o
];
914 sscanf(e1
.start
+1, "%d %d %d", &A
, &B
, &C
);
915 sscanf(e
.start
+1, "%d %d %d", &D
, &E
, &F
);
916 sprintf(b
, "@@ -%d,%d +%d,%d @@\n", B
, C
, E
, F
);
917 (void)attrset(a_sep
);
919 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
920 sprintf(b
, "<%.17s>", e
.start
+1);
922 mvaddstr(row
, col
-start
+offset
, b
);
925 blank(row
, col
-start
+offset
, start
+cols
-col
,
927 ? (unsigned)visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
)
931 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1)
935 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
936 c
= (unsigned char *)e
.start
;
937 for (l
= 0; l
< e
.len
; l
++) {
939 if (*c
>= ' ' && *c
!= 0x7f) {
940 if (col
>= start
&& col
< start
+cols
)
941 mvaddch(row
, col
-start
+offset
, *c
);
943 } else if (*c
== '\t') {
945 if (col
>= start
&& col
< start
+cols
) {
946 mvaddch(row
, col
-start
+offset
, ' ');
948 } while ((col
&7) != 0);
950 if (col
>= start
&& col
< start
+cols
)
951 mvaddch(row
, col
-start
+offset
, '?');
955 if (curs
->target
>= 0) {
956 if (curs
->target
< col
) {
957 /* Found target column */
961 if (scol
>= start
+ cols
)
962 /* Didn't appear on screen */
965 curs
->width
= col
- scol
;
968 } else if (l
== curs
->offset
&&
969 same_mp(pos
.p
, curs
->pos
)) {
973 if (scol
>= start
+ cols
)
974 /* Didn't appear on screen */
977 curs
->width
= col
- scol
;
986 /* Draw either 1 or 2 sides depending on the mode. */
988 static void draw_mline(int mode
, int row
, int start
, int cols
,
989 struct file fm
, struct file fb
, struct file fa
,
995 * Draw the left and right images of this line
996 * One side might be a_blank depending on the
997 * visibility of this newline
1001 mode
|= check_line(pos
, fm
, fb
, fa
, m
, mode
);
1003 if ((mode
& (BEFORE
|AFTER
)) &&
1004 (mode
& (ORIG
|RESULT
))) {
1007 rcols
= cols
- lcols
- 1;
1009 (void)attrset(A_STANDOUT
);
1010 mvaddch(row
, lcols
, '|');
1012 draw_mside(mode
&~(BEFORE
|AFTER
), row
, 0, start
, lcols
,
1013 fm
, fb
, fa
, m
, pos
, curs
&& !curs
->alt
? curs
: NULL
);
1015 draw_mside(mode
&~(ORIG
|RESULT
), row
, lcols
+1, start
, rcols
,
1016 fm
, fb
, fa
, m
, pos
, curs
&& curs
->alt
? curs
: NULL
);
1018 draw_mside(mode
, row
, 0, start
, cols
,
1019 fm
, fb
, fa
, m
, pos
, curs
);
1022 static char *merge_help
[] = {
1023 "This view shows the merge of the patch with the",
1024 "original file. It is like a full-context diff showing",
1025 "removed lines with a '-' prefix and added lines with a",
1027 "In cases where a patch chunk could not be successfully",
1028 "applied, the original text is prefixed with a '|', and",
1029 "the text that the patch wanted to add is prefixed with",
1031 "When the cursor is over such a conflict, or over a chunk",
1032 "which required wiggling to apply (i.e. there was unmatched",
1033 "text in the original, or extraneous unchanged text in",
1034 "the patch), the terminal is split and the bottom pane is",
1035 "use to display the part of the patch that applied to",
1036 "this section of the original. This allows you to confirm",
1037 "that a wiggled patch applied correctly, and to see",
1038 "why there was a conflict",
1041 static char *diff_help
[] = {
1042 "This is the 'diff' or 'patch' view. It shows",
1043 "only the patch that is being applied without the",
1044 "original to which it is being applied.",
1045 "Underlined text indicates parts of the patch which",
1046 "resulted in a conflict when applied to the",
1050 static char *orig_help
[] = {
1051 "This is the 'original' view which simply shows",
1052 "the original file before applying the patch.",
1053 "Sections of code that would be changed by the patch",
1054 "are highlighted in red.",
1057 static char *result_help
[] = {
1058 "This is the 'result' view which shows just the",
1059 "result of applying the patch. When a conflict",
1060 "occurred this view does not show the full conflict",
1061 "but only the 'after' part of the patch. To see",
1062 "the full conflict, use the 'merge' or 'sidebyside'",
1066 static char *before_help
[] = {
1067 "This view shows the 'before' section of a patch.",
1068 "It allows the expected match text to be seen uncluttered",
1069 "by text that is meant to replaced it."
1070 "Red text is text that will be removed by the patch",
1073 static char *after_help
[] = {
1074 "This view shows the 'after' section of a patch.",
1075 "It allows the intended result to be seen uncluttered",
1076 "by text that was meant to be matched and replaced."
1077 "Green text is text that was added by the patch - it",
1078 "was not present in the 'before' part of the patch",
1081 static char *sidebyside_help
[] = {
1082 "This is the Side By Side view of a patched file.",
1083 "The left side shows the original and the result.",
1084 "The right side shows the patch which was applied",
1085 "and lines up with the original/result as much as",
1088 "Where one side has no line which matches the",
1089 "other side it is displayed as a solid colour in the",
1090 "yellow family (depending on your terminal window).",
1093 static char *merge_window_help
[] = {
1094 " Highlight Colours and Keystroke commands",
1096 "In all different views of a merge, highlight colours",
1097 "are used to show which parts of lines were added,",
1098 "removed, already changed, unchanged or in conflict.",
1099 "Colours and their use are:",
1100 " normal unchanged text",
1101 " red text that was removed or changed",
1102 " green text that was added or the result",
1104 " yellow background used in side-by-side for a line",
1105 " which has no match on the other",
1107 " blue text in the original which did not",
1108 " match anything in the patch",
1109 " cyan text in the patch which did not",
1110 " match anything in the original",
1111 " cyan background already changed text: the result",
1112 " of the patch matches the original",
1113 " underline remove or added text can also be",
1114 " underlined indicating that it",
1115 " was involved in a conflict",
1117 "While viewing a merge various keystroke commands can",
1118 "be used to move around and change the view. Basic",
1119 "movement commands from both 'vi' and 'emacs' are",
1122 " p control-p k UP Move to previous line",
1123 " n control-n j DOWN Move to next line",
1124 " l LEFT Move one char to right",
1125 " h RIGHT Move one char to left",
1126 " / control-s Enter incremental search mode",
1127 " control-r Enter reverse-search mode",
1128 " control-g Search again",
1129 " ? Display help message",
1130 " ESC-< 0-G Go to start of file",
1131 " ESC-> G Go to end of file",
1132 " q Return to list of files or exit",
1133 " control-L recenter current line",
1134 " control-V page down",
1136 " N go to next patch chunk",
1137 " P go to previous patch chunk",
1138 " O move cursor to alternate pane",
1139 " ^ control-A go to start of line",
1140 " $ control-E go to end of line",
1142 " a display 'after' view",
1143 " b display 'before' view",
1144 " o display 'original' view",
1145 " r display 'result' view",
1146 " d display 'diff' or 'patch' view",
1147 " m display 'merge' view",
1148 " | display side-by-side view",
1152 /* plist stores a list of patched files in an array
1153 * Each entry identifies a file, the range of the
1154 * original patch which applies to this file, some
1155 * statistics concerning how many conflicts etc, and
1156 * some linkage information so the list can be viewed
1157 * as a directory-tree.
1161 unsigned int start
, end
;
1163 int next
, prev
, last
;
1165 int chunks
, wiggles
, conflicts
;
1168 char *before
, *after
;
1171 static void merge_window(struct plist
*p
, FILE *f
, int reverse
)
1173 /* Display the merge window in one of the selectable modes,
1174 * starting with the 'merge' mode.
1176 * Newlines are the key to display.
1177 * 'pos' is always a visible newline (or eof).
1178 * In sidebyside mode it might only be visible on one side,
1179 * in which case the other side will be blank.
1180 * Where the newline is visible, we rewind the previous visible
1181 * newline visible and display the stuff in between
1183 * A 'position' is a struct mpos
1186 struct stream sm
, sb
, sa
, sp
; /* main, before, after, patch */
1187 struct file fm
, fb
, fa
;
1188 struct csl
*csl1
, *csl2
;
1190 int ch
; /* count of chunks */
1191 /* Always refresh the current line.
1192 * If refresh == 1, refresh all lines. If == 2, clear first
1195 int rows
= 0, cols
= 0;
1196 int splitrow
= -1; /* screen row for split - diff appears below */
1197 int lastrow
= 0; /* end of screen, or just above 'splitrow' */
1199 int mode
= ORIG
|RESULT
;
1200 int mmode
= mode
; /* Mode for moving - used when in 'other' pane */
1201 char *modename
= "merge";
1202 char **modehelp
= merge_help
;
1205 int trow
; /* screen-row while searching. If we cannot find,
1206 * we forget this number */
1208 struct mpos pos
; /* current point */
1209 struct mpos tpos
, /* temp point while drawing lines above and below pos */
1210 toppos
, /* pos at top of screen - for page-up */
1211 botpos
; /* pos at bottom of screen - for page-down */
1212 struct mpos vpos
, tvpos
;
1214 int meta
= 0, /* mode for multi-key commands- SEARCH or META */
1216 int num
= -1, /* numeric arg being typed. */
1218 char search
[80]; /* string we are searching for */
1219 unsigned int searchlen
= 0;
1220 int search_notfound
= 0;
1224 * 1 == no because there are upper-case chars
1225 * 2 == yes as there are no upper-case chars
1228 int ignore_case
= 2;
1229 /* We record all the places we find so 'backspace'
1230 * can easily return to the previous one
1232 struct search_anchor
{
1233 struct search_anchor
*next
;
1238 unsigned int searchlen
;
1242 /* three separate files */
1243 sm
= load_file(p
->file
);
1244 sb
= load_file(p
->before
);
1245 sa
= load_file(p
->after
);
1248 sp
= load_segment(f
, p
->start
, p
->end
);
1251 split_merge(sp
, &sm
, &sa
, &sb
);
1253 split_merge(sp
, &sm
, &sb
, &sa
);
1257 ch
= split_patch(sp
, &sa
, &sb
);
1259 ch
= split_patch(sp
, &sb
, &sa
);
1261 sm
= load_file(p
->file
);
1264 if (!sm
.body
|| !sb
.body
|| !sa
.body
) {
1267 help_window(help_missing
, NULL
);
1269 help_window(help_corrupt
, NULL
);
1272 /* FIXME check for errors in the stream */
1273 fm
= split_stream(sm
, ByWord
);
1274 fb
= split_stream(sb
, ByWord
);
1275 fa
= split_stream(sa
, ByWord
);
1278 csl1
= pdiff(fm
, fb
, ch
);
1280 csl1
= diff(fm
, fb
);
1281 csl2
= diff(fb
, fa
);
1283 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1, 0);
1288 pos
.p
.m
= 0; /* merge node */
1289 pos
.p
.s
= 0; /* stream number */
1290 pos
.p
.o
= -1; /* offset */
1293 next_mline(&pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1294 memset(&curs
, 0, sizeof(curs
));
1300 snprintf(buf
, 100, "File: %s%s Mode: %s\n",
1301 p
->file
, reverse
? " - reversed" : "", modename
);
1302 (void)attrset(A_BOLD
);
1303 mvaddstr(0, 0, buf
);
1305 (void)attrset(A_NORMAL
);
1308 if (row
< 1 || row
>= lastrow
)
1313 if (mode
== (ORIG
|RESULT
)) {
1314 int cmode
= check_line(pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1315 if (splitrow
< 0 && (cmode
& (WIGGLED
|CONFLICTED
))) {
1316 splitrow
= (rows
+1)/2;
1317 lastrow
= splitrow
- 1;
1320 if (!curs
.alt
&& splitrow
>= 0 && !(cmode
& CHANGES
)) {
1325 } else if (splitrow
>= 0) {
1332 getmaxyx(stdscr
, rows
, cols
);
1333 rows
--; /* keep last row clear */
1334 if (splitrow
>= 0) {
1335 splitrow
= (rows
+1)/2;
1336 lastrow
= splitrow
- 1;
1344 if (row
> lastrow
+3)
1349 if (getenv("WIGGLE_VTRACE")) {
1355 e
= fm
.list
[ci
.merger
[vpos
.p
.m
].a
+ vpos
.p
.o
].start
;
1358 e
= fb
.list
[ci
.merger
[vpos
.p
.m
].b
+ vpos
.p
.o
].start
;
1361 e
= fa
.list
[ci
.merger
[vpos
.p
.m
].c
+ vpos
.p
.o
].start
;
1364 for (i
= 0; i
< 6; i
++) {
1366 if (e2
[i
] < 32 || e2
[i
] >= 127)
1369 sprintf(b
, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos
.state
,
1371 vpos
.p
.m
, typenames
[ci
.merger
[vpos
.p
.m
].type
],
1372 ci
.merger
[vpos
.p
.m
].al
,
1373 ci
.merger
[vpos
.p
.m
].bl
,
1374 ci
.merger
[vpos
.p
.m
].cl
,
1375 ci
.merger
[vpos
.p
.m
].in_conflict
,
1378 (void)attrset(A_NORMAL
);
1383 /* Always refresh the line */
1384 while (start
> curs
.target
) {
1391 draw_mline(mode
, row
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1392 pos
, (splitrow
>= 0 && curs
.alt
) ? NULL
: &curs
);
1393 if (curs
.width
== 0 && start
< curs
.col
) {
1394 /* width == 0 implies it appear after end-of-screen */
1399 if (curs
.col
< start
) {
1410 for (i
= row
-1; i
>= 1 && tpos
.p
.m
>= 0; ) {
1411 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1412 draw_mline(mode
, i
--, start
, cols
,
1413 fm
, fb
, fa
, ci
.merger
,
1424 blank(i
--, 0, cols
, a_void
);
1426 for (i
= row
; i
<= lastrow
&& ci
.merger
[tpos
.p
.m
].type
!= End
; ) {
1427 draw_mline(mode
, i
++, start
, cols
,
1428 fm
, fb
, fa
, ci
.merger
,
1430 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1432 botpos
= tpos
; botrow
= i
;
1433 while (i
<= lastrow
)
1434 blank(i
++, 0, cols
, a_void
);
1437 if (splitrow
>= 0) {
1438 struct mpos spos
= pos
;
1439 int smode
= BEFORE
|AFTER
;
1440 int srow
= (rows
+ splitrow
)/2;
1441 if (visible(smode
, ci
.merger
[spos
.p
.m
].type
,
1443 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1444 /* Now hi/lo might be wrong, so lets fix it. */
1446 while (spos
.p
.m
>= 0 && spos
.state
!= 0)
1447 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1448 while (!same_mpos(spos
, tpos
))
1449 next_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1451 (void)attrset(a_sep
);
1452 for (i
= 0; i
< cols
; i
++)
1453 mvaddstr(splitrow
, i
, "-");
1456 for (i
= srow
-1; i
> splitrow
; i
--) {
1457 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1458 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1461 while (i
> splitrow
)
1462 blank(i
--, 0, cols
, a_void
);
1465 i
< rows
&& ci
.merger
[tpos
.p
.m
].type
!= End
;
1467 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1469 (i
== srow
&& curs
.alt
) ? &curs
: NULL
);
1470 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1473 blank(i
++, 0, cols
, a_void
);
1475 /* Now that curs is accurate, report the type */
1478 (void)attrset(A_BOLD
);
1479 snprintf(lbuf
, 29, "%s ln:%d",
1480 typenames
[ci
.merger
[curs
.pos
.m
].type
],
1481 (pos
.p
.lineno
-1)/2);
1482 mvaddstr(0, cols
- strlen(lbuf
) - 4, " ");
1483 mvaddstr(0, cols
- strlen(lbuf
) - 1, lbuf
);
1485 #define META(c) ((c)|0x1000)
1486 #define SEARCH(c) ((c)|0x2000)
1488 (void)attrset(A_NORMAL
);
1491 snprintf(buf
, 10, "%d ", num
);
1496 if (meta
& SEARCH(0)) {
1498 addstr("Backwards ");
1501 if (search_notfound
)
1502 addstr(" - Not Found.");
1503 search_notfound
= 0;
1506 /* '+1' to skip over the leading +/-/| char */
1507 if (curs
.alt
&& splitrow
> 0)
1508 move((rows
+ splitrow
)/2, curs
.col
- start
+ 1);
1509 else if (curs
.alt
&& ((mode
& (BEFORE
|AFTER
)) &&
1510 (mode
& (ORIG
|RESULT
))))
1511 move(row
, curs
.col
-start
+ (cols
-1)/2+2);
1513 move(row
, curs
.col
-start
+1);
1515 tmeta
= meta
; meta
= 0;
1516 tnum
= num
; num
= -1;
1517 tvpos
= vpos
; vpos
= pos
;
1518 cswitch
= c
| tmeta
;
1519 /* Handle some ranges */
1520 /* case '0' ... '9': */
1521 if (cswitch
>= '0' && cswitch
<= '9')
1523 /* case SEARCH(' ') ... SEARCH('~'): */
1524 if (cswitch
>= SEARCH(' ') && cswitch
<= SEARCH('~'))
1525 cswitch
= SEARCH(' ');
1528 case 27: /* escape */
1532 case META('<'): /* start of file */
1537 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1538 } while (tpos
.p
.m
>= 0);
1542 case META('>'): /* end of file */
1549 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1550 } while (ci
.merger
[tpos
.p
.m
].type
!= End
);
1554 case '0': /* actually '0'...'9' */
1557 num
= tnum
*10 + (c
-'0');
1564 /* incr search forward */
1567 search
[searchlen
] = 0;
1572 /* incr search backwards */
1575 search
[searchlen
] = 0;
1578 case SEARCH('G'-64):
1579 case SEARCH('S'-64):
1580 case SEARCH('R'-64):
1582 if ((c
|tmeta
) == SEARCH('R'-64))
1584 else if ((c
|tmeta
) == SEARCH('S'-64))
1589 tpos
= pos
; trow
= row
;
1592 case SEARCH('H'-64):
1593 case SEARCH(KEY_BACKSPACE
):
1596 struct search_anchor
*a
;
1602 struct search_anchor
*a
;
1610 search_notfound
= a
->notfound
;
1611 searchlen
= a
->searchlen
;
1612 search
[searchlen
] = 0;
1617 case SEARCH(' '): /* actually ' '...'~' */
1620 if (searchlen
< sizeof(search
)-1)
1621 search
[searchlen
++] = c
& (0x7f);
1622 search
[searchlen
] = 0;
1623 tpos
= pos
; trow
= row
;
1625 search_notfound
= 1;
1626 if (ignore_case
== 1 || ignore_case
== 2) {
1629 for (i
=0; i
< searchlen
; i
++)
1630 if (isupper(search
[i
])) {
1636 if (mcontains(tpos
, fm
, fb
, fa
, ci
.merger
,
1637 mmode
, search
, &curs
, searchdir
,
1638 ignore_case
>= 2)) {
1642 search_notfound
= 0;
1645 if (searchdir
< 0) {
1647 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1650 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1652 } while (tpos
.p
.m
>= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1653 searchdir
/= abs(searchdir
);
1661 case 'V'-64: /* page down */
1663 if (botrow
<= lastrow
)
1669 case META('v'): /* page up */
1681 for (; tnum
> 0 ; tnum
--) {
1683 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1684 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
1695 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1696 } while (pos
.state
!= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1700 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1701 } while (pos
.state
== 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1709 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1710 } while (tpos
.state
== 0 && tpos
.p
.m
>= 0);
1714 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1715 } while (tpos
.state
!= 0 && tpos
.p
.m
>= 0);
1724 for (; tnum
> 0 ; tnum
--) {
1726 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1727 if (tpos
.p
.m
>= 0) {
1737 curs
.target
= curs
.col
- 1;
1738 if (curs
.target
< 0) {
1739 /* Try to go to end of previous line */
1741 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1742 if (tpos
.p
.m
>= 0) {
1754 if (curs
.width
>= 0)
1755 curs
.target
= curs
.col
+ curs
.width
;
1757 /* end of line, go to next */
1759 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1760 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
1781 curs
.alt
= !curs
.alt
;
1782 if (curs
.alt
&& mode
== (ORIG
|RESULT
))
1783 mmode
= (BEFORE
|AFTER
);
1788 case 'a': /* 'after' view in patch window */
1789 mode
= AFTER
; modename
= "after"; modehelp
= after_help
;
1790 mmode
= mode
; curs
.alt
= 0;
1793 case 'b': /* 'before' view in patch window */
1794 mode
= BEFORE
; modename
= "before"; modehelp
= before_help
;
1795 mmode
= mode
; curs
.alt
= 0;
1798 case 'o': /* 'original' view in the merge window */
1799 mode
= ORIG
; modename
= "original"; modehelp
= orig_help
;
1800 mmode
= mode
; curs
.alt
= 0;
1803 case 'r': /* the 'result' view in the merge window */
1804 mode
= RESULT
; modename
= "result"; modehelp
= result_help
;
1805 mmode
= mode
; curs
.alt
= 0;
1809 mode
= BEFORE
|AFTER
; modename
= "diff"; modehelp
= diff_help
;
1810 mmode
= mode
; curs
.alt
= 0;
1814 mode
= ORIG
|RESULT
; modename
= "merge"; modehelp
= merge_help
;
1815 mmode
= mode
; curs
.alt
= 0;
1820 mode
= ORIG
|RESULT
|BEFORE
|AFTER
; modename
= "sidebyside"; modehelp
= sidebyside_help
;
1821 mmode
= mode
; curs
.alt
= 0;
1825 case 'H': /* scroll window to the right */
1828 curs
.target
= start
+ 1;
1831 case 'L': /* scroll window to the left */
1834 curs
.target
= start
+ 1;
1839 prev_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1844 next_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1845 if (ci
.merger
[tvpos
.p
.m
].type
!= End
)
1850 help_window(modehelp
, merge_window_help
);
1859 if (meta
== SEARCH(0)) {
1860 if (anchor
== NULL
||
1861 !same_mpos(anchor
->pos
, pos
) ||
1862 anchor
->searchlen
!= searchlen
||
1863 !same_mp(anchor
->curs
.pos
, curs
.pos
)) {
1864 struct search_anchor
*a
= xmalloc(sizeof(*a
));
1869 a
->searchlen
= searchlen
;
1870 a
->notfound
= search_notfound
;
1876 struct search_anchor
*a
= anchor
;
1882 /* move backward and forward to make sure we
1883 * are on a visible line
1886 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1890 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mmode
);
1891 if (ci
.merger
[tpos
.p
.m
].type
!= End
)
1897 static void show_merge(char *origname
, FILE *patch
, int reverse
,
1898 int is_merge
, char *before
, char *after
)
1905 fseek(patch
, 0, SEEK_END
);
1906 p
.end
= ftell(patch
);
1907 fseek(patch
, 0, SEEK_SET
);
1910 p
.is_merge
= is_merge
;
1914 freopen("/dev/null","w",stderr
);
1915 merge_window(&p
, patch
, reverse
);
1918 static struct plist
*patch_add_file(struct plist
*pl
, int *np
, char *file
,
1919 unsigned int start
, unsigned int end
)
1921 /* size of pl is 0, 16, n^2 */
1925 while (*file
== '/')
1926 /* leading '/' are bad... */
1933 else if ((n
&(n
-1)) == 0)
1936 asize
= n
+1; /* not accurate, but not too large */
1938 /* need to extend array */
1944 npl
= realloc(pl
, asize
* sizeof(struct plist
));
1946 fprintf(stderr
, "realloc failed - skipping %s\n", file
);
1952 pl
[n
].start
= start
;
1954 pl
[n
].last
= pl
[n
].next
= pl
[n
].prev
= pl
[n
].parent
= -1;
1955 pl
[n
].chunks
= pl
[n
].wiggles
= 0; pl
[n
].conflicts
= 100;
1963 static struct plist
*parse_patch(FILE *f
, FILE *of
, int *np
)
1965 /* read a multi-file patch from 'f' and record relevant
1966 * details in a plist.
1967 * if 'of' >= 0, fd might not be seekable so we write
1968 * to 'of' and use lseek on 'of' to determine position
1970 struct plist
*plist
= NULL
;
1974 /* first, find the start of a patch: "\n+++ "
1975 * grab the file name and scan to the end of a line
1977 char *target
= "\n+++ ";
1978 char *target2
= "\n--- ";
1982 unsigned start
, end
;
1984 while (*pos
&& (c
= fgetc(f
)) != EOF
) {
1995 /* now read a file name */
1997 while ((c
= fgetc(f
)) != EOF
1998 && c
!= '\t' && c
!= '\n' && c
!= ' ' &&
1999 pos
- name
< 1023) {
2009 while (c
!= '\n' && (c
= fgetc(f
)) != EOF
)
2013 start
= ftell(of
?: f
);
2018 /* now skip to end - "\n--- " */
2021 while (*pos
&& (c
= fgetc(f
)) != EOF
) {
2029 end
= ftell(of
?: f
);
2031 end
-= (pos
- target2
) - 1;
2032 plist
= patch_add_file(plist
, np
,
2033 strdup(name
), start
, end
);
2038 static struct stream
load_segment(FILE *f
,
2039 unsigned int start
, unsigned int end
)
2042 s
.len
= end
- start
;
2043 s
.body
= xmalloc(s
.len
);
2045 if (fread(s
.body
, 1, s
.len
, f
) != (size_t)s
.len
)
2050 static int pl_cmp(const void *av
, const void *bv
)
2052 const struct plist
*a
= av
;
2053 const struct plist
*b
= bv
;
2054 return strcmp(a
->file
, b
->file
);
2057 static int common_depth(char *a
, char *b
)
2059 /* find number of path segments that these two have
2076 if (al
== 0 || al
!= bl
|| strncmp(a
, b
, al
) != 0)
2089 static struct plist
*add_dir(struct plist
*pl
, int *np
, char *file
, char *curr
)
2091 /* any parent of file that is not a parent of curr
2092 * needs to be added to pl
2094 int d
= common_depth(file
, curr
);
2097 char *c
= strchr(file
, '/');
2105 while (*file
== '/')
2107 while (*curr
== '/')
2112 if (curr
> buf
&& curr
[-1] != '/')
2114 while (*file
&& *file
!= '/')
2116 while (*file
== '/')
2120 pl
= patch_add_file(pl
, np
, strdup(buf
),
2126 static struct plist
*sort_patches(struct plist
*pl
, int *np
)
2128 /* sort the patches, add directory names, and re-sort */
2134 qsort(pl
, *np
, sizeof(struct plist
), pl_cmp
);
2137 for (i
= 0; i
< n
; i
++)
2138 pl
= add_dir(pl
, np
, pl
[i
].file
, curr
);
2140 qsort(pl
, *np
, sizeof(struct plist
), pl_cmp
);
2142 /* array is now stable, so set up parent pointers */
2147 for (i
= 0; i
< n
; i
++) {
2148 int d
= common_depth(prev
, pl
[i
].file
);
2152 pl
[i
].parent
= parents
[d
-1];
2153 pl
[pl
[i
].parent
].last
= i
;
2155 pl
[i
].prev
= prevnode
[d
];
2156 if (pl
[i
].prev
> -1)
2157 pl
[pl
[i
].prev
].next
= i
;
2166 /* determine how much we need to stripe of the front of
2167 * paths to find them from current directory. This is
2168 * used to guess correct '-p' value.
2170 static int get_strip(char *file
)
2175 while (file
&& *file
) {
2176 fd
= open(file
, O_RDONLY
);
2182 file
= strchr(file
, '/');
2184 while (*file
== '/')
2191 static int set_prefix(struct plist
*pl
, int n
, int strip
)
2194 for (i
= 0; i
< 4 && i
< n
&& strip
< 0; i
++)
2195 strip
= get_strip(pl
[i
].file
);
2198 fprintf(stderr
, "%s: Cannot find files to patch: please specify --strip\n",
2202 for (i
= 0; i
< n
; i
++) {
2203 char *p
= pl
[i
].file
;
2205 for (j
= 0; j
< strip
; j
++) {
2208 while (p
&& *p
== '/')
2212 fprintf(stderr
, "%s: cannot strip %d segments from %s\n",
2213 Cmd
, strip
, pl
[i
].file
);
2221 static void calc_one(struct plist
*pl
, FILE *f
, int reverse
)
2223 struct stream s1
, s2
;
2224 struct stream s
= load_segment(f
, pl
->start
, pl
->end
);
2228 split_merge(s
, &sf
, &s2
, &s1
);
2230 split_merge(s
, &sf
, &s1
, &s2
);
2233 sf
= load_file(pl
->file
);
2235 pl
->chunks
= split_patch(s
, &s2
, &s1
);
2237 pl
->chunks
= split_patch(s
, &s1
, &s2
);
2239 if (sf
.body
== NULL
|| s1
.body
== NULL
|| s1
.body
== NULL
) {
2240 pl
->wiggles
= pl
->conflicts
= -1;
2242 struct file ff
, fp1
, fp2
;
2243 struct csl
*csl1
, *csl2
;
2245 ff
= split_stream(sf
, ByWord
);
2246 fp1
= split_stream(s1
, ByWord
);
2247 fp2
= split_stream(s2
, ByWord
);
2249 csl1
= pdiff(ff
, fp1
, pl
->chunks
);
2251 csl1
= diff(ff
, fp1
);
2252 csl2
= diff(fp1
, fp2
);
2253 ci
= make_merger(ff
, fp1
, fp2
, csl1
, csl2
, 0, 1, 0);
2254 pl
->wiggles
= ci
.wiggles
;
2255 pl
->conflicts
= ci
.conflicts
;
2270 static int get_prev(int pos
, struct plist
*pl
, int n
, int mode
)
2276 if (pl
[pos
].prev
== -1)
2277 return pl
[pos
].parent
;
2279 while (pl
[pos
].open
&&
2282 if (pl
[pos
].last
>= 0)
2283 /* always see directories */
2287 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2289 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2291 } while (pos
>= 0 && !found
);
2295 static int get_next(int pos
, struct plist
*pl
, int n
, int mode
,
2296 FILE *f
, int reverse
)
2308 while (pos
>= 0 && pl
[pos
].next
== -1)
2309 pos
= pl
[pos
].parent
;
2315 if (pl
[pos
].calced
== 0 && pl
[pos
].end
)
2316 calc_one(pl
+pos
, f
, reverse
);
2317 if (pl
[pos
].last
>= 0)
2318 /* always see directories */
2322 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2324 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2326 } while (pos
>= 0 && !found
);
2330 static void draw_one(int row
, struct plist
*pl
, FILE *f
, int reverse
)
2340 if (pl
->calced
== 0 && pl
->end
)
2341 /* better load the patch and count the chunks */
2342 calc_one(pl
, f
, reverse
);
2346 if (pl
->chunks
> 99)
2349 sprintf(hdr
, "%2d", pl
->chunks
);
2350 if (pl
->wiggles
> 99)
2351 strcpy(hdr
+2, " XX");
2353 sprintf(hdr
+2, " %2d", pl
->wiggles
);
2354 if (pl
->conflicts
> 99)
2355 strcpy(hdr
+5, " XX ");
2357 sprintf(hdr
+5, " %2d ", pl
->conflicts
);
2360 strcpy(hdr
+9, "= ");
2362 strcpy(hdr
+9, "+ ");
2364 strcpy(hdr
+9, "- ");
2366 mvaddstr(row
, 0, hdr
);
2367 mvaddstr(row
, 11, pl
->file
);
2371 static char *main_help
[] = {
2372 " You are using the \"browse\" mode of wiggle.",
2373 "This page shows a list of files in a patch together with",
2374 "the directories that contain them.",
2375 "A directory is indicated by a '+' if the contents are",
2376 "listed or a '-' if the contents are hidden. A file is",
2377 "indicated by an '='. Typing <space> or <return> will",
2378 "expose or hide a directory, and will visit a file.",
2380 "The three columns of numbers are:",
2381 " Ch The number of patch chunks which applied to",
2383 " Wi The number of chunks that needed to be wiggled",
2385 " Co The number of chunks that created an unresolvable",
2388 "Keystrokes recognised in this page are:",
2389 " ? Display this help",
2390 " SPC On a directory, toggle hiding of contents",
2391 " On file, visit the file",
2394 " n,j,DOWN Go to next line",
2395 " p,k,UP Go to previous line",
2397 " A list All files",
2398 " W only list files with a wiggle or a conflict",
2399 " C only list files with a conflict",
2403 static void main_window(struct plist
*pl
, int n
, FILE *f
, int reverse
)
2405 /* The main window lists all files together with summary information:
2406 * number of chunks, number of wiggles, number of conflicts.
2407 * The list is scrollable
2408 * When a entry is 'selected', we switch to the 'file' window
2409 * The list can be condensed by removing files with no conflict
2410 * or no wiggles, or removing subdirectories
2412 * We record which file in the list is 'current', and which
2413 * screen line it is on. We try to keep things stable while
2416 * Counts are printed before the name using at most 2 digits.
2417 * Numbers greater than 99 are XX
2419 * 27 5 1 drivers/md/md.c
2421 * A directory show the sum in all children.
2424 * select: enter, space, mouseclick
2425 * on file, go to file window
2426 * on directory, toggle open
2427 * up: k, p, control-p uparrow
2428 * Move to previous open object
2429 * down: j, n, control-n, downarrow
2430 * Move to next open object
2432 * A W C: select All Wiggles or Conflicts
2436 int pos
= 0; /* position in file */
2437 int row
= 1; /* position on screen */
2438 int rows
= 0; /* size of screen in rows */
2443 int mode
= 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2445 freopen("/dev/null","w",stderr
);
2447 pl
= sort_patches(pl
, &n
);
2451 clear(); (void)attrset(0);
2453 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2458 if (row
< 1 || row
>= rows
)
2462 getmaxyx(stdscr
, rows
, cols
);
2463 cols
= cols
; /* Silence warning that 'cols' isn't used */
2464 if (row
>= rows
+ 3)
2469 for (i
= row
; i
> 1; i
--) {
2470 tpos
= get_prev(tpos
, pl
, n
, mode
);
2476 /* Ok, row and pos could be trustworthy now */
2478 for (i
= row
; i
>= 1; i
--) {
2479 draw_one(i
, &pl
[tpos
], f
, reverse
);
2480 tpos
= get_prev(tpos
, pl
, n
, mode
);
2483 for (i
= row
+1; i
< rows
; i
++) {
2484 tpos
= get_next(tpos
, pl
, n
, mode
, f
, reverse
);
2486 draw_one(i
, &pl
[tpos
], f
, reverse
);
2488 draw_one(i
, NULL
, f
, reverse
);
2492 sprintf(bb
, "%d", c
);
2493 mvaddstr(0, 70, bb
);
2504 tpos
= get_next(pos
, pl
, n
, mode
, f
, reverse
);
2515 tpos
= get_prev(pos
, pl
, n
, mode
);
2524 if (pl
[pos
].end
== 0) {
2525 pl
[pos
].open
= !pl
[pos
].open
;
2528 /* diff_window(&pl[pos], f); */
2529 merge_window(&pl
[pos
], f
, reverse
);
2533 case 27: /* escape */
2534 mvaddstr(0, 70, "ESC..."); clrtoeol();
2543 mode
= 0; refresh
= 1;
2546 mode
= 1; refresh
= 1;
2549 mode
= 2; refresh
= 1;
2553 help_window(main_help
, NULL
);
2564 static void catch(int sig
)
2566 if (sig
== SIGINT
) {
2573 printf("Died on signal %d\n", sig
);
2577 static void term_init(void)
2580 static int init_done
= 0;
2586 signal(SIGINT
, catch);
2587 signal(SIGQUIT
, catch);
2588 signal(SIGTERM
, catch);
2589 signal(SIGBUS
, catch);
2590 signal(SIGSEGV
, catch);
2592 initscr(); cbreak(); noecho();
2594 use_default_colors();
2595 if (!has_colors()) {
2596 a_delete
= A_UNDERLINE
;
2598 a_common
= A_NORMAL
;
2600 a_already
= A_STANDOUT
;
2602 init_pair(1, COLOR_RED
, -1);
2603 a_delete
= COLOR_PAIR(1);
2604 init_pair(2, COLOR_GREEN
, -1);
2605 a_added
= COLOR_PAIR(2);
2606 a_common
= A_NORMAL
;
2607 init_pair(3, COLOR_WHITE
, COLOR_GREEN
);
2608 a_sep
= COLOR_PAIR(3); a_sep
= A_STANDOUT
;
2609 init_pair(4, -1, COLOR_YELLOW
);
2610 a_void
= COLOR_PAIR(4);
2611 init_pair(5, COLOR_BLUE
, -1);
2612 a_unmatched
= COLOR_PAIR(5);
2613 init_pair(6, COLOR_CYAN
, -1);
2614 a_extra
= COLOR_PAIR(6);
2616 init_pair(7, COLOR_BLACK
, COLOR_CYAN
);
2617 a_already
= COLOR_PAIR(7);
2619 nonl(); intrflush(stdscr
, FALSE
); keypad(stdscr
, TRUE
);
2620 mousemask(ALL_MOUSE_EVENTS
, NULL
);
2623 int vpatch(int argc
, char *argv
[], int patch
, int strip
,
2624 int reverse
, int replace
)
2626 /* NOTE argv[0] is first arg...
2627 * Behaviour depends on number of args:
2628 * 0: A multi-file patch is read from stdin
2629 * 1: if 'patch', parse it as a multi-file patch and allow
2630 * the files to be browsed.
2631 * if filename ends '.rej', then treat it as a patch again
2632 * a file with the same basename
2633 * Else treat the file as a merge (with conflicts) and view it.
2634 * 2: First file is original, second is patch
2635 * 3: Files are: original previous new. The diff between 'previous' and
2636 * 'new' needs to be applied to 'original'.
2638 * If a multi-file patch is being read, 'strip' tells how many
2639 * path components to strip. If it is -1, we guess based on
2641 * If 'reverse' is given, when we invert any patch or diff
2642 * If 'replace' then we save the resulting merge.
2651 fprintf(stderr
, "%s: too many file names given.\n", Cmd
);
2654 case 0: /* stdin is a patch */
2655 if (lseek(fileno(stdin
), 0L, 1) == -1) {
2656 /* cannot seek, so need to copy to a temp file */
2659 fprintf(stderr
, "%s: Cannot create temp file\n", Cmd
);
2662 pl
= parse_patch(stdin
, f
, &num_patches
);
2665 pl
= parse_patch(stdin
, NULL
, &num_patches
);
2666 in
= fdopen(dup(0), "r");
2668 /* use stderr for keyboard input */
2670 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2671 fprintf(stderr
, "%s: aborting\n", Cmd
);
2674 main_window(pl
, num_patches
, in
, reverse
);
2677 case 1: /* a patch, a .rej, or a merge file */
2678 f
= fopen(argv
[0], "r");
2680 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2684 pl
= parse_patch(f
, NULL
, &num_patches
);
2685 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2686 fprintf(stderr
, "%s: aborting\n", Cmd
);
2689 main_window(pl
, num_patches
, f
, reverse
);
2690 } else if (strlen(argv
[0]) > 4 &&
2691 strcmp(argv
[0]+strlen(argv
[0])-4, ".rej") == 0) {
2692 char *origname
= strdup(argv
[0]);
2693 origname
[strlen(origname
) - 4] = '\0';
2694 show_merge(origname
, f
, reverse
, 0, NULL
, NULL
);
2696 show_merge(argv
[0], f
, reverse
, 1, NULL
, NULL
);
2699 case 2: /* an orig and a diff/.ref */
2700 f
= fopen(argv
[1], "r");
2702 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2705 show_merge(argv
[0], f
, reverse
, 0, NULL
, NULL
);
2707 case 3: /* orig, before, after */
2708 show_merge(argv
[0], NULL
, reverse
, 1, argv
[1], argv
[2]);