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 /* We can give one or two pages to display in the help window.
89 * The first is specific to the current context. The second
90 * is optional and may provide help in a more broad context.
92 static void help_window(char *page1
[], char *page2
[])
102 getmaxyx(stdscr
, rows
, cols
);
108 left
= (cols
-58)/2 - 1;
116 top
= (rows
-15)/2 - 1;
120 /* Draw a bow around the 'help' area */
121 (void)attrset(A_STANDOUT
);
122 for (c
= left
; c
< left
+cols
; c
++) {
123 mvaddch(top
-1, c
, '-');
124 mvaddch(top
+rows
, c
, '-');
126 for (r
= top
; r
< top
+ rows
; r
++) {
127 mvaddch(r
, left
-1, '|');
128 mvaddch(r
, left
+cols
, '|');
130 mvaddch(top
-1, left
-1, '/');
131 mvaddch(top
-1, left
+cols
, '\\');
132 mvaddch(top
+rows
, left
-1, '\\');
133 mvaddch(top
+rows
, left
+cols
, '/');
134 mvaddstr(top
-1, left
+ cols
/2 - 9, "HELP - 'q' to exit");
135 mvaddstr(top
+rows
, left
+cols
/2 - 17, "Press SPACE for more, '?' for help");
136 (void)attrset(A_NORMAL
);
139 char **lnp
= page
+ line
;
141 /* Draw as much of the page at the current offset
144 for (r
= 0; r
< rows
; r
++) {
152 while (*ln
&& sh
> 0) {
156 for (c
= 0; c
< cols
; c
++) {
162 mvaddch(top
+r
, left
+c
, chr
);
165 move(top
+rows
-1, left
);
172 if (page1
!= help_help
)
173 help_window(help_help
, NULL
);
176 case '\r': /* page-down */
177 for (r
= 0; r
< rows
-2; r
++)
191 case '\b': /* page up */
228 /* Type names are needed for tracing only. */
229 static char *typenames
[] = {
231 [Unmatched
] = "Unmatched",
232 [Unchanged
] = "Unchanged",
233 [Extraneous
] = "Extraneous",
234 [Changed
] = "Changed",
235 [Conflict
] = "Conflict",
236 [AlreadyApplied
] = "AlreadyApplied",
239 /* When we merge the original and the diff together we need
240 * to keep track of where everything came from.
241 * When we display the different views, we need to be able to
242 * select certain portions of the whole document.
243 * These flags are used to identify what is present, and to
244 * request different parts be extracted. They also help
245 * guide choice of colour.
251 #define CHANGED 16 /* The RESULT is different to ORIG */
252 #define CHANGES 32 /* AFTER is different to BEFORE */
253 #define WIGGLED 64 /* a conflict that was successfully resolved */
254 #define CONFLICTED 128 /* a conflict that was not successfully resolved */
257 /* Displaying a Merge.
258 * The first step is to linearise the merge. The merge in inherently
259 * parallel with before/after streams. However much of the whole document
260 * is linear as normally much of the original in unchanged.
261 * All parallelism comes from the patch. This normally produces two
262 * parallel stream, but in the case of a conflict can produce three.
263 * For browsing the merge we only ever show two alternates in-line.
264 * When there are three we use two panes with 1 or 2 alternates in each.
265 * So to linearise the two streams we find lines that are completely
266 * unchanged (same for all 3 streams, or missing in 2nd and 3rd) which bound
267 * a region where there are changes. We include everything between
268 * these twice, in two separate passes. The exact interpretation of the
269 * passes is handled at a higher level but will be one of:
270 * original and result
272 * original and after (for a conflict)
273 * This is all encoded in the 'struct merge'. An array of these describes
274 * the whole document.
276 * At any position in the merge we can be in one of 3 states:
277 * 0: unchanged section
281 * So to walk a merge in display order we need a position in the merge,
282 * a current state, and when in a changed section, we need to know the
283 * bounds of that changed section.
284 * This is all encoded in 'struct mpos'.
286 * Each location may or may not be visible depending on certain
289 * Also, some locations might be 'invalid' in that they don't need to be displayed.
290 * For example when the patch leaves a section of the original unchanged,
291 * we only need to see the original - the before/after sections are treated
292 * as invalid and are not displayed.
293 * The visibility of newlines is crucial and guides the display. One line
294 * of displayed text is all the visible sections between two visible newlines.
296 * Counting lines is a bit tricky. We only worry about line numbers in the
297 * original (stream 0) as these could compare with line numbers mentioned in
299 * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
300 * That way we don't get a full counted line until we see the first char after the
301 * newline, so '+' lines are counted with the previous line.
306 int m
; /* merger index */
307 int s
; /* stream 0,1,2 for a,b,c */
308 int o
; /* offset in that stream */
309 int lineno
; /* Counts newlines in stream 0
310 * set lsb when see newline.
311 * add one when not newline and lsb set
313 } p
, /* the current point */
314 lo
, /* eol for start of the current group */
315 hi
; /* eol for end of the current group */
317 * 0 if on an unchanged (lo/hi not meaningful)
318 * 1 if on the '-' of a diff,
319 * 2 if on the '+' of a diff
323 /* used for checking location during search */
324 static int same_mpos(struct mpos a
, struct mpos b
)
326 return a
.p
.m
== b
.p
.m
&&
332 /* Check if a particular stream is meaningful in a particular merge
333 * section. e.g. in an Unchanged section, only stream 0, the
334 * original, is meaningful. This is used to avoid walking down
337 static int stream_valid(int s
, enum mergetype type
)
359 * Advance the 'pos' in the current mergepos returning the next
361 * This walks the merges in sequence, and the streams within
364 static struct elmnt
next_melmnt(struct mp
*pos
,
365 struct file fm
, struct file fb
, struct file fa
,
370 int l
= 0; /* Length remaining in current merge section */
384 /* Offset has reached length, choose new stream or
393 } while (!stream_valid(pos
->s
, m
[pos
->m
].type
));
397 if (pos
->m
== -1 || m
[pos
->m
].type
== End
) {
399 e
.start
= NULL
; e
.len
= 0;
403 default: /* keep compiler happy */
407 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
409 return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
410 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
411 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
415 /* step current position.p backwards */
416 static struct elmnt
prev_melmnt(struct mp
*pos
,
417 struct file fm
, struct file fb
, struct file fa
,
421 if (ends_mline(fm
.list
[m
[pos
->m
].a
+ pos
->o
]))
428 while (pos
->m
>= 0 && pos
->o
< 0) {
435 } while (pos
->m
>= 0 &&
436 !stream_valid(pos
->s
, m
[pos
->m
].type
));
440 pos
->o
= m
[pos
->m
].al
-1;
443 pos
->o
= m
[pos
->m
].bl
-1;
446 pos
->o
= m
[pos
->m
].cl
-1;
453 e
.start
= NULL
; e
.len
= 0;
457 default: /* keep compiler happy */
458 case 0: return fm
.list
[m
[pos
->m
].a
+ pos
->o
];
459 case 1: return fb
.list
[m
[pos
->m
].b
+ pos
->o
];
460 case 2: return fa
.list
[m
[pos
->m
].c
+ pos
->o
];
464 /* 'visible' not only checks if this stream in this merge should be
465 * visible in this mode, but also chooses which colour/highlight to use
468 static int visible(int mode
, enum mergetype type
, int stream
)
472 /* mode can be any combination of ORIG RESULT BEFORE AFTER */
474 case End
: /* The END is always visible */
476 case Unmatched
: /* Visible in ORIG and RESULT */
477 if (mode
& (ORIG
|RESULT
))
480 case Unchanged
: /* visible everywhere, but only show stream 0 */
484 case Extraneous
: /* stream 2 is visible in BEFORE and AFTER */
485 if ((mode
& (BEFORE
|AFTER
))
489 case Changed
: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
491 (mode
& (ORIG
|BEFORE
)))
494 (mode
& (RESULT
|AFTER
)))
501 return a_unmatched
| A_REVERSE
;
505 return a_extra
| A_UNDERLINE
;
508 if (mode
& (AFTER
|RESULT
))
509 return a_added
| A_UNDERLINE
;
516 if (mode
& (ORIG
|RESULT
))
521 return a_delete
| A_UNDERLINE
;
525 return a_added
| A_UNDERLINE
;
533 /* checkline creates a summary of the sort of changes that
534 * are in a line, returning an "or" of
540 static int check_line(struct mpos pos
, struct file fm
, struct file fb
,
542 struct merge
*m
, int mode
)
550 if (m
[pos
.p
.m
].type
== Changed
)
551 rv
|= CHANGED
| CHANGES
;
552 else if ((m
[pos
.p
.m
].type
== AlreadyApplied
||
553 m
[pos
.p
.m
].type
== Conflict
))
554 rv
|= CONFLICTED
| CHANGES
;
555 else if (m
[pos
.p
.m
].type
== Extraneous
)
557 else if (m
[pos
.p
.m
].type
== Unmatched
)
559 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
560 } while (e
.start
!= NULL
&&
562 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
564 if (unmatched
&& (rv
& CHANGES
))
569 /* Find the next line in the merge which is visible.
570 * If we hit the end of a conflicted set during pass-1
571 * we rewind for pass-2.
572 * 'mode' tells which bits we want to see, possible one of
573 * the 4 parts (before/after/orig/result) or one of the pairs
574 * before+after or orig+result.
576 static void next_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
578 struct merge
*m
, int mode
)
587 struct elmnt e
= next_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
591 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
594 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
596 if ((mode2
& CHANGES
) && pos
->state
== 0) {
597 /* Just entered a diff-set */
600 } else if (!(mode2
& CHANGES
) && pos
->state
) {
601 /* Come to the end of a diff-set */
602 switch (pos
->state
) {
604 /* Need to record the end */
606 /* time for another pass */
611 /* finished final pass */
616 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
617 switch (pos
->state
) {
619 mask
&= ~(RESULT
|AFTER
);
622 mask
&= ~(ORIG
|BEFORE
);
625 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
629 /* Move to previous line - simply the reverse of next_mline */
630 static void prev_mline(struct mpos
*pos
, struct file fm
, struct file fb
,
632 struct merge
*m
, int mode
)
643 struct elmnt e
= prev_melmnt(&pos
->p
, fm
, fb
, fa
, m
);
647 visible(mode
, m
[pos
->p
.m
].type
, pos
->p
.s
) >= 0)
650 mode2
= check_line(*pos
, fm
, fb
, fa
, m
, mode
);
652 if ((mode2
& CHANGES
) && pos
->state
== 0) {
653 /* Just entered a diff-set */
656 } else if (!(mode2
& CHANGES
) && pos
->state
) {
657 /* Come to the end (start) of a diff-set */
658 switch (pos
->state
) {
660 /* finished final pass */
664 /* Need to record the start */
666 /* time for another pass */
672 mask
= ORIG
|RESULT
|BEFORE
|AFTER
|CHANGES
|CHANGED
;
673 switch (pos
->state
) {
675 mask
&= ~(RESULT
|AFTER
);
678 mask
&= ~(ORIG
|BEFORE
);
681 } while (visible(mode
&mask
, m
[pos
->p
.m
].type
, pos
->p
.s
) < 0);
684 /* blank a whole row of display */
685 static void blank(int row
, int start
, int cols
, unsigned int attr
)
693 /* search of a string on one display line - just report if found, not where */
695 static int mcontains(struct mpos pos
,
696 struct file fm
, struct file fb
, struct file fa
,
698 int mode
, char *search
)
700 /* See if any of the files, between start of this line and here,
701 * contain the search string
704 int len
= strlen(search
);
706 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
709 for (i
= 0; i
< e
.len
; i
++)
710 if (strncmp(e
.start
+i
, search
, len
) == 0)
713 } while (e
.start
!= NULL
&&
715 || visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
719 /* Drawing the display window.
720 * There are 7 different ways we can display the data, each
721 * of which can be configured by a keystroke:
722 * o original - just show the original file with no changes, but still
723 * with highlights of what is changed or unmatched
724 * r result - show just the result of the merge. Conflicts just show
725 * the original, not the before/after options
726 * b before - show the 'before' stream of the patch
727 * a after - show the 'after' stream of the patch
728 * d diff - show just the patch, both before and after
729 * m merge - show the full merge with -+ sections for changes.
730 * If point is in a wiggled or conflicted section the
731 * window is split horizontally and the diff is shown
732 * in the bottom window
733 * | sidebyside - two panes, left and right. Left holds the merge,
734 * right holds the diff. In the case of a conflict,
735 * left holds orig/after, right holds before/after
737 * The horizontal split for 'merge' mode is managed as follows.
738 * - The window is split when we first visit a line that contains
739 * a wiggle or a conflict, and the second pane is removed when
740 * we next visit a line that contains no changes (is fully Unchanged).
741 * - to display the second pane, we find a visible end-of-line in the
742 * (BEFORE|AFTER) mode at-or-before the current end-of-line and
743 * the we centre that line.
744 * - We need to rewind to an unchanged section, and wind forward again
745 * to make sure that 'lo' and 'hi' are set properly.
746 * - every time we move, we redraw the second pane (see how that goes).
749 /* draw_mside draws one text line or, in the case of sidebyside, one side
751 * The 'mode' tells us what to draw via the 'visible()' function.
752 * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
753 * It may also have WIGGLED or CONFLICTED ored in to trigger extra highlights.
754 * The desired cursor position is given in 'target' the actual end
755 * cursor position (allowing e.g. for tabs) is returned in *colp.
757 static void draw_mside(int mode
, int row
, int offset
, int start
, int cols
,
758 struct file fm
, struct file fb
, struct file fa
,
761 int target
, int *colp
)
768 case 0: /* unchanged line */
771 case 1: /* 'before' text */
773 if ((mode
& ORIG
) && (mode
& CONFLICTED
))
775 mode
&= (ORIG
|BEFORE
);
777 case 2: /* the 'after' part */
779 mode
&= (AFTER
|RESULT
);
783 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) < 0) {
784 /* Not visible, just draw a blank */
785 blank(row
, offset
, cols
, a_void
);
791 (void)attrset(A_NORMAL
);
792 mvaddch(row
, offset
, tag
);
796 /* find previous visible newline, or start of file */
798 e
= prev_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
799 while (e
.start
!= NULL
&&
801 visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1));
806 e
= next_melmnt(&pos
.p
, fm
, fb
, fa
, m
);
807 if (e
.start
== NULL
||
809 && visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) != -1)) {
814 if (e
.start
&& e
.start
[0] == 0) {
817 if (pos
.p
.s
== 2 && m
[pos
.p
.m
].type
== Extraneous
) {
818 int A
, B
, C
, D
, E
, F
;
819 e1
= fb
.list
[m
[pos
.p
.m
].b
+ pos
.p
.o
];
820 sscanf(e1
.start
+1, "%d %d %d", &A
, &B
, &C
);
821 sscanf(e
.start
+1, "%d %d %d", &D
, &E
, &F
);
822 sprintf(b
, "@@ -%d,%d +%d,%d @@\n", B
, C
, E
, F
);
823 (void)attrset(a_sep
);
825 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
826 sprintf(b
, "<%.17s>", e
.start
+1);
828 mvaddstr(row
, col
-start
+offset
, b
);
831 blank(row
, col
-start
+offset
, start
+cols
-col
,
833 ? (unsigned)visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
)
837 if (visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
) == -1)
841 (void)attrset(visible(mode
, m
[pos
.p
.m
].type
, pos
.p
.s
));
842 c
= (unsigned char *)e
.start
;
845 if (*c
>= ' ' && *c
!= 0x7f) {
846 if (col
>= start
&& col
< start
+cols
)
847 mvaddch(row
, col
-start
+offset
, *c
);
849 } else if (*c
== '\t') {
851 if (col
>= start
&& col
< start
+cols
)
852 mvaddch(row
, col
-start
+offset
, ' ');
854 } while ((col
&7) != 0);
856 if (col
>= start
&& col
< start
+cols
)
857 mvaddch(row
, col
-start
+offset
, '?');
861 if (colp
&& target
<= col
) {
862 if (col
-start
>= cols
)
873 /* Draw either 1 or 2 sides depending on the mode. */
875 static void draw_mline(int mode
, int row
, int start
, int cols
,
876 struct file fm
, struct file fb
, struct file fa
,
879 int target
, int *colp
)
882 * Draw the left and right images of this line
883 * One side might be a_blank depending on the
884 * visibility of this newline
888 mode
|= check_line(pos
, fm
, fb
, fa
, m
, mode
);
890 if ((mode
& (BEFORE
|AFTER
)) &&
891 (mode
& (ORIG
|RESULT
))) {
894 rcols
= cols
- lcols
- 1;
896 (void)attrset(A_STANDOUT
);
897 mvaddch(row
, lcols
, '|');
899 draw_mside(mode
&~(BEFORE
|AFTER
), row
, 0, start
, lcols
,
900 fm
, fb
, fa
, m
, pos
, target
, colp
);
902 draw_mside(mode
&~(ORIG
|RESULT
), row
, lcols
+1, start
, rcols
,
903 fm
, fb
, fa
, m
, pos
, 0, NULL
);
905 draw_mside(mode
, row
, 0, start
, cols
,
906 fm
, fb
, fa
, m
, pos
, target
, colp
);
909 static char *merge_help
[] = {
910 "This view shows the merge of the patch with the",
911 "original file. It is like a full-context diff showing",
912 "removed lines with a '-' prefix and added lines with a",
914 "In cases where a patch chunk could not be successfully",
915 "applied, the original text is prefixed with a '|', and",
916 "the text that the patch wanted to add is prefixed with",
918 "When the cursor is over such a conflict, or over a chunk",
919 "which required wiggling to apply (i.e. there was unmatched",
920 "text in the original, or extraneous unchanged text in",
921 "the patch), the terminal is split and the bottom pane is",
922 "use to display the part of the patch that applied to",
923 "this section of the original. This allows you to confirm",
924 "that a wiggled patch applied correctly, and to see",
925 "why there was a conflict",
928 static char *diff_help
[] = {
929 "This is the 'diff' or 'patch' view. It shows",
930 "only the patch that is being applied without the",
931 "original to which it is being applied.",
932 "Underlined text indicates parts of the patch which",
933 "resulted in a conflict when applied to the",
937 static char *orig_help
[] = {
938 "This is the 'original' view which simply shows",
939 "the original file before applying the patch.",
940 "Sections of code that would be changed by the patch",
941 "are highlighted in red.",
944 static char *result_help
[] = {
945 "This is the 'result' view which show just the",
946 "result of applying the patch. When a conflict",
947 "occurred this view does not show the full conflict",
948 "but only the 'after' part of the patch. To see",
949 "the full conflict, use the 'merge' or 'sidebyside'",
953 static char *before_help
[] = {
954 "This view shows the 'before' section of a patch.",
955 "It allows the expected match text to be seen uncluttered",
956 "by text that is meant to replaced it."
957 "Text with a red background is text that will be",
958 "removed by the patch",
961 static char *after_help
[] = {
962 "This view shows the 'after' section of a patch.",
963 "It allows the intended result to be seen uncluttered",
964 "by text that was meant to be matched and replaced."
965 "Text with a blue background is text that was added",
966 "by the patch - it was not present in the 'before'",
970 static char *sidebyside_help
[] = {
971 "This is the Side By Side view of a patched file.",
972 "The left side shows the original and the result.",
973 "The right side shows the patch which was applied",
974 "and lines up with the original/result as much as",
977 "Where one side has no line which matches the",
978 "other side it is displayed as a solid colour in the",
979 "yellow family (depending on your terminal window).",
982 static char *merge_window_help
[] = {
983 " Highlight Colours and Keystroke commands",
985 "In all different views of a merge, highlight colours",
986 "are used to show which parts of lines were added,",
987 "removed, already changed, unchanged or in conflict.",
988 "Colours and their use are:",
989 " normal unchanged text",
990 " red background text that was removed or changed",
991 " blue background text that was added or the result",
993 " yellow background used in side-by-side for a line",
994 " which has no match on the other",
996 " blue foreground text in the original which did not",
997 " match anything in the patch",
998 " cyan foreground text in the patch which did not",
999 " match anything in the original",
1000 " cyan background already changed text: the result",
1001 " of the patch matches the original",
1002 " underline remove or added text can also be",
1003 " underlined indicating that it",
1004 " was involved in a conflict",
1006 "While viewing a merge various keystroke commands can",
1007 "be used to move around and change the view. Basic",
1008 "movement commands from both 'vi' and 'emacs' are",
1011 " p control-p k UP Move to previous line",
1012 " n control-n j DOWN Move to next line",
1013 " l LEFT Move one char to right",
1014 " h RIGHT Move one char to left",
1015 " / control-s Enter incremental search mode",
1016 " control-r Enter reverse-search mode",
1017 " control-g Search again",
1018 " ? Display help message",
1019 " ESC-< 0-G Go to start of file",
1020 " ESC-> G Go to end of file",
1021 " q Return to list of files or exit",
1022 " control-L recenter current line",
1023 " control-V page down",
1025 " N go to next patch chunk",
1026 " P go to previous patch chunk",
1027 " ^ control-A go to start of line",
1028 " $ control-E go to end of line",
1030 " a display 'after' view",
1031 " b display 'before' view",
1032 " o display 'original' view",
1033 " r display 'result' view",
1034 " d display 'diff' or 'patch' view",
1035 " m display 'merge' view",
1036 " | display side-by-side view",
1040 /* plist stores a list of patched files in an array
1041 * Each entry identifies a file, the range of the
1042 * original patch which applies to this file, some
1043 * statistics concerning how many conflicts etc, and
1044 * some linkage information so the list can be viewed
1045 * as a directory-tree.
1049 unsigned int start
, end
;
1051 int next
, prev
, last
;
1053 int chunks
, wiggles
, conflicts
;
1056 char *before
, *after
;
1059 static void merge_window(struct plist
*p
, FILE *f
, int reverse
)
1061 /* Display the merge window in one of the selectable modes,
1062 * starting with the 'merge' mode.
1064 * Newlines are the key to display.
1065 * 'pos' is always a visible newline (or eof).
1066 * In sidebyside mode it might only be visible on one side,
1067 * in which case the other side will be blank.
1068 * Where the newline is visible, we rewind the previous visible
1069 * newline visible and display the stuff in between
1071 * A 'position' is a struct mpos
1074 struct stream sm
, sb
, sa
, sp
; /* main, before, after, patch */
1075 struct file fm
, fb
, fa
;
1076 struct csl
*csl1
, *csl2
;
1078 int ch
; /* count of chunks */
1079 /* Always refresh the current line.
1080 * If refresh == 1, refresh all lines. If == 2, clear first
1083 int rows
= 0, cols
= 0;
1084 int splitrow
= -1; /* screen row for split - diff appears below */
1085 int lastrow
= 0; /* end of screen, or just above 'splitrow' */
1087 int mode
= ORIG
|RESULT
;
1088 char *modename
= "merge";
1089 char **modehelp
= merge_help
;
1092 int trow
; /* screen-row while searching. If we cannot find,
1093 * we forget this number */
1094 int col
= 0, target
= 0;
1095 struct mpos pos
; /* current point */
1096 struct mpos tpos
, /* temp point while drawing lines above and below pos */
1097 toppos
, /* pos at top of screen - for page-up */
1098 botpos
; /* pos at bottom of screen - for page-down */
1099 struct mpos vpos
, tvpos
;
1101 int meta
= 0, /* mode for multi-key commands- SEARCH or META */
1103 int num
= -1, /* numeric arg being typed. */
1105 char search
[80]; /* string we are searching for */
1106 unsigned int searchlen
= 0;
1107 int search_notfound
= 0;
1109 /* We record all the places we find so 'backspace'
1110 * can easily return to the previous one
1112 struct search_anchor
{
1113 struct search_anchor
*next
;
1117 unsigned int searchlen
;
1121 /* three separate files */
1122 sm
= load_file(p
->file
);
1123 sb
= load_file(p
->before
);
1124 sa
= load_file(p
->after
);
1127 sp
= load_segment(f
, p
->start
, p
->end
);
1130 split_merge(sp
, &sm
, &sa
, &sb
);
1132 split_merge(sp
, &sm
, &sb
, &sa
);
1136 ch
= split_patch(sp
, &sa
, &sb
);
1138 ch
= split_patch(sp
, &sb
, &sa
);
1140 sm
= load_file(p
->file
);
1143 fm
= split_stream(sm
, ByWord
);
1144 fb
= split_stream(sb
, ByWord
);
1145 fa
= split_stream(sa
, ByWord
);
1148 csl1
= pdiff(fm
, fb
, ch
);
1150 csl1
= diff(fm
, fb
);
1151 csl2
= diff(fb
, fa
);
1153 ci
= make_merger(fm
, fb
, fa
, csl1
, csl2
, 0, 1);
1158 pos
.p
.m
= 0; /* merge node */
1159 pos
.p
.s
= 0; /* stream number */
1160 pos
.p
.o
= -1; /* offset */
1163 next_mline(&pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1169 snprintf(buf
, 100, "File: %s%s Mode: %s\n",
1170 p
->file
, reverse
? " - reversed" : "", modename
);
1171 (void)attrset(A_BOLD
);
1172 mvaddstr(0, 0, buf
);
1174 (void)attrset(A_NORMAL
);
1177 if (row
< 1 || row
>= lastrow
)
1180 if (mode
== (ORIG
|RESULT
)) {
1181 int cmode
= check_line(pos
, fm
, fb
, fa
, ci
.merger
, mode
);
1182 if (splitrow
< 0 && (cmode
& (WIGGLED
|CONFLICTED
))) {
1183 splitrow
= (rows
+1)/2;
1184 lastrow
= splitrow
- 1;
1187 if (splitrow
>= 0 && !(cmode
& CHANGES
)) {
1192 } else if (splitrow
>= 0) {
1199 getmaxyx(stdscr
, rows
, cols
);
1200 rows
--; /* keep last row clear */
1201 if (splitrow
>= 0) {
1202 splitrow
= (rows
+1)/2;
1203 lastrow
= splitrow
- 1;
1211 if (row
> lastrow
+3)
1216 if (getenv("WIGGLE_VTRACE")) {
1222 e
= fm
.list
[ci
.merger
[vpos
.p
.m
].a
+ vpos
.p
.o
].start
;
1225 e
= fb
.list
[ci
.merger
[vpos
.p
.m
].b
+ vpos
.p
.o
].start
;
1228 e
= fa
.list
[ci
.merger
[vpos
.p
.m
].c
+ vpos
.p
.o
].start
;
1231 for (i
= 0; i
< 6; i
++) {
1233 if (e2
[i
] < 32 || e2
[i
] >= 127)
1236 sprintf(b
, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos
.state
,
1238 vpos
.p
.m
, typenames
[ci
.merger
[vpos
.p
.m
].type
],
1239 ci
.merger
[vpos
.p
.m
].al
,
1240 ci
.merger
[vpos
.p
.m
].bl
,
1241 ci
.merger
[vpos
.p
.m
].cl
,
1242 ci
.merger
[vpos
.p
.m
].in_conflict
,
1245 (void)attrset(A_NORMAL
);
1251 (void)attrset(A_BOLD
);
1252 snprintf(lbuf
, 19, "ln:%d", (pos
.p
.lineno
-1)/2);
1253 mvaddstr(0, cols
- strlen(lbuf
) - 4, " ");
1254 mvaddstr(0, cols
- strlen(lbuf
) - 1, lbuf
);
1256 /* Always refresh the line */
1257 while (start
> target
) {
1264 draw_mline(mode
, row
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1267 if (col
> cols
+start
) {
1283 for (i
= row
-1; i
>= 1 && tpos
.p
.m
>= 0; ) {
1284 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1285 draw_mline(mode
, i
--, start
, cols
,
1286 fm
, fb
, fa
, ci
.merger
,
1297 blank(i
--, 0, cols
, a_void
);
1299 for (i
= row
; i
<= lastrow
&& ci
.merger
[tpos
.p
.m
].type
!= End
; ) {
1300 draw_mline(mode
, i
++, start
, cols
,
1301 fm
, fb
, fa
, ci
.merger
,
1303 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1305 botpos
= tpos
; botrow
= i
;
1306 while (i
<= lastrow
)
1307 blank(i
++, 0, cols
, a_void
);
1310 if (splitrow
>= 0) {
1311 struct mpos spos
= pos
;
1312 int smode
= BEFORE
|AFTER
;
1313 int srow
= (rows
+ splitrow
)/2;
1314 if (visible(smode
, ci
.merger
[spos
.p
.m
].type
,
1316 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1317 /* Now hi/lo might be wrong, so lets fix it. */
1319 while (spos
.p
.m
>= 0 && spos
.state
!= 0)
1320 prev_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1321 while (!same_mpos(spos
, tpos
))
1322 next_mline(&spos
, fm
, fb
, fa
, ci
.merger
, smode
);
1325 (void)attrset(a_sep
);
1326 for (i
= 0; i
< cols
; i
++)
1327 mvaddstr(splitrow
, i
, "-");
1330 for (i
= srow
-1; i
> splitrow
; i
--) {
1331 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1332 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1335 while (i
> splitrow
)
1336 blank(i
--, 0, cols
, a_void
);
1339 i
< rows
&& ci
.merger
[tpos
.p
.m
].type
!= End
;
1341 draw_mline(smode
, i
, start
, cols
, fm
, fb
, fa
, ci
.merger
,
1343 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, smode
);
1346 #define META(c) ((c)|0x1000)
1347 #define SEARCH(c) ((c)|0x2000)
1349 (void)attrset(A_NORMAL
);
1352 snprintf(buf
, 10, "%d ", num
);
1357 if (meta
& SEARCH(0)) {
1359 addstr("Backwards ");
1362 if (search_notfound
)
1363 addstr(" - Not Found.");
1364 search_notfound
= 0;
1367 move(row
, col
-start
);
1369 tmeta
= meta
; meta
= 0;
1370 tnum
= num
; num
= -1;
1371 tvpos
= vpos
; vpos
= pos
;
1372 cswitch
= c
| tmeta
;
1373 /* Handle some ranges */
1374 /* case '0' ... '9': */
1375 if (cswitch
>= '0' && cswitch
<= '9')
1377 /* case SEARCH(' ') ... SEARCH('~'): */
1378 if (cswitch
>= SEARCH(' ') && cswitch
<= SEARCH('~'))
1379 cswitch
= SEARCH(' ');
1382 case 27: /* escape */
1386 case META('<'): /* start of file */
1391 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1392 } while (tpos
.p
.m
>= 0);
1396 case META('>'): /* end of file */
1403 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1404 } while (ci
.merger
[tpos
.p
.m
].type
!= End
);
1408 case '0': /* actually '0'...'9' */
1411 num
= tnum
*10 + (c
-'0');
1418 /* incr search forward */
1421 search
[searchlen
] = 0;
1426 /* incr search backwards */
1429 search
[searchlen
] = 0;
1432 case SEARCH('G'-64):
1433 case SEARCH('S'-64):
1434 case SEARCH('R'-64):
1436 if ((c
|tmeta
) == SEARCH('R'-64))
1438 if ((c
|tmeta
) == SEARCH('S'-64))
1441 tpos
= pos
; trow
= row
;
1444 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1447 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1451 case SEARCH('H'-64):
1454 struct search_anchor
*a
;
1460 struct search_anchor
*a
;
1466 search_notfound
= a
->notfound
;
1467 searchlen
= a
->searchlen
;
1468 search
[searchlen
] = 0;
1473 case SEARCH(' '): /* actually ' '...'~' */
1476 if (searchlen
< sizeof(search
)-1)
1477 search
[searchlen
++] = c
& (0x7f);
1478 search
[searchlen
] = 0;
1479 tpos
= pos
; trow
= row
;
1481 search_notfound
= 1;
1483 if (mcontains(tpos
, fm
, fb
, fa
, ci
.merger
, mode
, search
)) {
1486 search_notfound
= 0;
1491 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1494 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1496 } while (tpos
.p
.m
>= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1504 case 'V'-64: /* page down */
1506 if (botrow
<= lastrow
)
1512 case META('v'): /* page up */
1524 for (; tnum
> 0 ; tnum
--) {
1526 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1527 if (ci
.merger
[tpos
.p
.m
].type
!= End
) {
1538 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1539 } while (pos
.state
!= 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1543 next_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1544 } while (pos
.state
== 0 && ci
.merger
[tpos
.p
.m
].type
!= End
);
1552 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1553 } while (tpos
.state
== 0 && tpos
.p
.m
>= 0);
1557 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1558 } while (tpos
.state
!= 0 && tpos
.p
.m
>= 0);
1567 for (; tnum
> 0 ; tnum
--) {
1569 prev_mline(&tpos
, fm
, fb
, fa
, ci
.merger
, mode
);
1570 if (tpos
.p
.m
>= 0) {
1601 case 'a': /* 'after' view in patch window */
1602 mode
= AFTER
; modename
= "after"; modehelp
= after_help
;
1605 case 'b': /* 'before' view in patch window */
1606 mode
= BEFORE
; modename
= "before"; modehelp
= before_help
;
1609 case 'o': /* 'original' view in the merge window */
1610 mode
= ORIG
; modename
= "original"; modehelp
= orig_help
;
1613 case 'r': /* the 'result' view in the merge window */
1614 mode
= RESULT
; modename
= "result"; modehelp
= result_help
;
1618 mode
= BEFORE
|AFTER
; modename
= "diff"; modehelp
= diff_help
;
1622 mode
= ORIG
|RESULT
; modename
= "merge"; modehelp
= merge_help
;
1627 mode
= ORIG
|RESULT
|BEFORE
|AFTER
; modename
= "sidebyside"; modehelp
= sidebyside_help
;
1645 prev_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1650 next_melmnt(&tvpos
.p
, fm
, fb
, fa
, ci
.merger
);
1651 if (ci
.merger
[tvpos
.p
.m
].type
!= End
)
1656 help_window(modehelp
, merge_window_help
);
1665 if (meta
== SEARCH(0)) {
1666 if (anchor
== NULL
||
1667 !same_mpos(anchor
->pos
, pos
) ||
1668 anchor
->searchlen
!= searchlen
||
1669 anchor
->col
!= col
) {
1670 struct search_anchor
*a
= malloc(sizeof(*a
));
1674 a
->searchlen
= searchlen
;
1675 a
->notfound
= search_notfound
;
1681 struct search_anchor
*a
= anchor
;
1689 static void show_merge(char *origname
, FILE *patch
, int reverse
,
1690 int is_merge
, char *before
, char *after
)
1697 fseek(patch
, 0, SEEK_END
);
1698 p
.end
= ftell(patch
);
1699 fseek(patch
, 0, SEEK_SET
);
1702 p
.is_merge
= is_merge
;
1706 merge_window(&p
, patch
, reverse
);
1709 static struct plist
*patch_add_file(struct plist
*pl
, int *np
, char *file
,
1710 unsigned int start
, unsigned int end
)
1712 /* size of pl is 0, 16, n^2 */
1716 while (*file
== '/')
1717 /* leading '/' are bad... */
1724 else if ((n
&(n
-1)) == 0)
1727 asize
= n
+1; /* not accurate, but not too large */
1729 /* need to extend array */
1735 npl
= realloc(pl
, asize
* sizeof(struct plist
));
1737 fprintf(stderr
, "malloc failed - skipping %s\n", file
);
1743 pl
[n
].start
= start
;
1745 pl
[n
].last
= pl
[n
].next
= pl
[n
].prev
= pl
[n
].parent
= -1;
1746 pl
[n
].chunks
= pl
[n
].wiggles
= 0; pl
[n
].conflicts
= 100;
1754 static struct plist
*parse_patch(FILE *f
, FILE *of
, int *np
)
1756 /* read a multi-file patch from 'f' and record relevant
1757 * details in a plist.
1758 * if 'of' >= 0, fd might not be seekable so we write
1759 * to 'of' and use lseek on 'of' to determine position
1761 struct plist
*plist
= NULL
;
1765 /* first, find the start of a patch: "\n+++ "
1766 * grab the file name and scan to the end of a line
1768 char *target
= "\n+++ ";
1769 char *target2
= "\n--- ";
1773 unsigned start
, end
;
1775 while (*pos
&& (c
= fgetc(f
)) != EOF
) {
1786 /* now read a file name */
1788 while ((c
= fgetc(f
)) != EOF
1789 && c
!= '\t' && c
!= '\n' && c
!= ' ' &&
1790 pos
- name
< 1023) {
1800 while (c
!= '\n' && (c
= fgetc(f
)) != EOF
)
1804 start
= ftell(of
?: f
);
1809 /* now skip to end - "\n--- " */
1812 while (*pos
&& (c
= fgetc(f
)) != EOF
) {
1820 end
= ftell(of
?: f
);
1822 end
-= (pos
- target2
) - 1;
1823 plist
= patch_add_file(plist
, np
,
1824 strdup(name
), start
, end
);
1829 static struct stream
load_segment(FILE *f
,
1830 unsigned int start
, unsigned int end
)
1833 s
.len
= end
- start
;
1834 s
.body
= malloc(s
.len
);
1837 if (fread(s
.body
, 1, s
.len
, f
) != (size_t)s
.len
) {
1846 static int pl_cmp(const void *av
, const void *bv
)
1848 const struct plist
*a
= av
;
1849 const struct plist
*b
= bv
;
1850 return strcmp(a
->file
, b
->file
);
1853 static int common_depth(char *a
, char *b
)
1855 /* find number of path segments that these two have
1872 if (al
== 0 || al
!= bl
|| strncmp(a
, b
, al
) != 0)
1885 static struct plist
*add_dir(struct plist
*pl
, int *np
, char *file
, char *curr
)
1887 /* any parent of file that is not a parent of curr
1888 * needs to be added to pl
1890 int d
= common_depth(file
, curr
);
1893 char *c
= strchr(file
, '/');
1901 while (*file
== '/')
1903 while (*curr
== '/')
1908 if (curr
> buf
&& curr
[-1] != '/')
1910 while (*file
&& *file
!= '/')
1912 while (*file
== '/')
1916 pl
= patch_add_file(pl
, np
, strdup(buf
),
1922 static struct plist
*sort_patches(struct plist
*pl
, int *np
)
1924 /* sort the patches, add directory names, and re-sort */
1930 qsort(pl
, *np
, sizeof(struct plist
), pl_cmp
);
1933 for (i
= 0; i
< n
; i
++)
1934 pl
= add_dir(pl
, np
, pl
[i
].file
, curr
);
1936 qsort(pl
, *np
, sizeof(struct plist
), pl_cmp
);
1938 /* array is now stable, so set up parent pointers */
1943 for (i
= 0; i
< n
; i
++) {
1944 int d
= common_depth(prev
, pl
[i
].file
);
1948 pl
[i
].parent
= parents
[d
-1];
1949 pl
[pl
[i
].parent
].last
= i
;
1951 pl
[i
].prev
= prevnode
[d
];
1952 if (pl
[i
].prev
> -1)
1953 pl
[pl
[i
].prev
].next
= i
;
1962 /* determine how much we need to stripe of the front of
1963 * paths to find them from current directory. This is
1964 * used to guess correct '-p' value.
1966 static int get_strip(char *file
)
1971 while (file
&& *file
) {
1972 fd
= open(file
, O_RDONLY
);
1978 file
= strchr(file
, '/');
1980 while (*file
== '/')
1987 static int set_prefix(struct plist
*pl
, int n
, int strip
)
1990 for (i
= 0; i
< 4 && i
< n
&& strip
< 0; i
++)
1991 strip
= get_strip(pl
[i
].file
);
1994 fprintf(stderr
, "%s: Cannot file files to patch: please specify --strip\n",
1998 for (i
= 0; i
< n
; i
++) {
1999 char *p
= pl
[i
].file
;
2001 for (j
= 0; j
< strip
; j
++) {
2004 while (p
&& *p
== '/')
2008 fprintf(stderr
, "%s: cannot strip %d segments from %s\n",
2009 Cmd
, strip
, pl
[i
].file
);
2017 static void calc_one(struct plist
*pl
, FILE *f
, int reverse
)
2019 struct stream s1
, s2
;
2020 struct stream s
= load_segment(f
, pl
->start
, pl
->end
);
2024 split_merge(s
, &sf
, &s2
, &s1
);
2026 split_merge(s
, &sf
, &s1
, &s2
);
2029 sf
= load_file(pl
->file
);
2031 pl
->chunks
= split_patch(s
, &s2
, &s1
);
2033 pl
->chunks
= split_patch(s
, &s1
, &s2
);
2035 if (sf
.body
== NULL
) {
2036 pl
->wiggles
= pl
->conflicts
= -1;
2038 struct file ff
, fp1
, fp2
;
2039 struct csl
*csl1
, *csl2
;
2041 ff
= split_stream(sf
, ByWord
);
2042 fp1
= split_stream(s1
, ByWord
);
2043 fp2
= split_stream(s2
, ByWord
);
2045 csl1
= pdiff(ff
, fp1
, pl
->chunks
);
2047 csl1
= diff(ff
, fp1
);
2048 csl2
= diff(fp1
, fp2
);
2049 ci
= make_merger(ff
, fp1
, fp2
, csl1
, csl2
, 0, 1);
2050 pl
->wiggles
= ci
.wiggles
;
2051 pl
->conflicts
= ci
.conflicts
;
2066 static int get_prev(int pos
, struct plist
*pl
, int n
, int mode
)
2072 if (pl
[pos
].prev
== -1)
2073 return pl
[pos
].parent
;
2075 while (pl
[pos
].open
&&
2078 if (pl
[pos
].last
>= 0)
2079 /* always see directories */
2083 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2085 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2087 } while (pos
>= 0 && !found
);
2091 static int get_next(int pos
, struct plist
*pl
, int n
, int mode
,
2092 FILE *f
, int reverse
)
2104 while (pos
>= 0 && pl
[pos
].next
== -1)
2105 pos
= pl
[pos
].parent
;
2109 if (pl
[pos
].calced
== 0 && pl
[pos
].end
)
2110 calc_one(pl
+pos
, f
, reverse
);
2111 if (pl
[pos
].last
>= 0)
2112 /* always see directories */
2116 else if (mode
<= 1 && pl
[pos
].wiggles
> 0)
2118 else if (mode
<= 2 && pl
[pos
].conflicts
> 0)
2120 } while (pos
>= 0 && !found
);
2124 static void draw_one(int row
, struct plist
*pl
, FILE *f
, int reverse
)
2134 if (pl
->calced
== 0 && pl
->end
)
2135 /* better load the patch and count the chunks */
2136 calc_one(pl
, f
, reverse
);
2140 if (pl
->chunks
> 99)
2143 sprintf(hdr
, "%2d", pl
->chunks
);
2144 if (pl
->wiggles
> 99)
2145 strcpy(hdr
+2, " XX");
2147 sprintf(hdr
+2, " %2d", pl
->wiggles
);
2148 if (pl
->conflicts
> 99)
2149 strcpy(hdr
+5, " XX ");
2151 sprintf(hdr
+5, " %2d ", pl
->conflicts
);
2154 strcpy(hdr
+9, "= ");
2156 strcpy(hdr
+9, "+ ");
2158 strcpy(hdr
+9, "- ");
2160 mvaddstr(row
, 0, hdr
);
2161 mvaddstr(row
, 11, pl
->file
);
2165 static char *main_help
[] = {
2166 " You are using the \"browse\" mode of wiggle.",
2167 "This page shows a list of files in a patch together with",
2168 "the directories that contain them.",
2169 "A directory is indicated by a '+' if the contents are",
2170 "listed or a '-' if the contents are hidden. A file is",
2171 "indicated by an '='. Typing <space> or <return> will",
2172 "expose or hide a directory, and will visit a file.",
2174 "The three columns of numbers are:",
2175 " Ch The number of patch chunks which applied to",
2177 " Wi The number of chunks that needed to be wiggled",
2179 " Co The number of chunks that created an unresolvable",
2182 "Keystrokes recognised in this page are:",
2183 " ? Display this help",
2184 " SPC On a directory, toggle hiding of contents",
2185 " On file, visit the file",
2188 " n,j,DOWN Go to next line",
2189 " p,k,UP Go to previous line",
2191 " A list All files",
2192 " W only list files with a wiggle or a conflict",
2193 " C only list files with a conflict",
2197 static void main_window(struct plist
*pl
, int n
, FILE *f
, int reverse
)
2199 /* The main window lists all files together with summary information:
2200 * number of chunks, number of wiggles, number of conflicts.
2201 * The list is scrollable
2202 * When a entry is 'selected', we switch to the 'file' window
2203 * The list can be condensed by removing files with no conflict
2204 * or no wiggles, or removing subdirectories
2206 * We record which file in the list is 'current', and which
2207 * screen line it is on. We try to keep things stable while
2210 * Counts are printed before the name using at most 2 digits.
2211 * Numbers greater than 99 are XX
2213 * 27 5 1 drivers/md/md.c
2215 * A directory show the sum in all children.
2218 * select: enter, space, mouseclick
2219 * on file, go to file window
2220 * on directory, toggle open
2221 * up: k, p, control-p uparrow
2222 * Move to previous open object
2223 * down: j, n, control-n, downarrow
2224 * Move to next open object
2226 * A W C: select All Wiggles or Conflicts
2230 int pos
= 0; /* position in file */
2231 int row
= 1; /* position on screen */
2232 int rows
= 0; /* size of screen in rows */
2237 int mode
= 0; /* 0=all, 1= only wiggled, 2=only conflicted */
2240 pl
= sort_patches(pl
, &n
);
2244 clear(); (void)attrset(0);
2246 mvaddstr(0, 0, "Ch Wi Co Patched Files");
2251 if (row
< 1 || row
>= rows
)
2255 getmaxyx(stdscr
, rows
, cols
);
2256 if (row
>= rows
+ 3)
2261 for (i
= row
; i
> 1; i
--) {
2262 tpos
= get_prev(tpos
, pl
, n
, mode
);
2268 /* Ok, row and pos could be trustworthy now */
2270 for (i
= row
; i
>= 1; i
--) {
2271 draw_one(i
, &pl
[tpos
], f
, reverse
);
2272 tpos
= get_prev(tpos
, pl
, n
, mode
);
2275 for (i
= row
+1; i
< rows
; i
++) {
2276 tpos
= get_next(tpos
, pl
, n
, mode
, f
, reverse
);
2278 draw_one(i
, &pl
[tpos
], f
, reverse
);
2280 draw_one(i
, NULL
, f
, reverse
);
2284 sprintf(bb
, "%d", c
);
2285 mvaddstr(0, 70, bb
);
2296 tpos
= get_next(pos
, pl
, n
, mode
, f
, reverse
);
2307 tpos
= get_prev(pos
, pl
, n
, mode
);
2316 if (pl
[pos
].end
== 0) {
2317 pl
[pos
].open
= !pl
[pos
].open
;
2320 /* diff_window(&pl[pos], f); */
2321 merge_window(&pl
[pos
], f
, reverse
);
2325 case 27: /* escape */
2326 mvaddstr(0, 70, "ESC..."); clrtoeol();
2335 mode
= 0; refresh
= 1;
2338 mode
= 1; refresh
= 1;
2341 mode
= 2; refresh
= 1;
2345 help_window(main_help
, NULL
);
2356 static void catch(int sig
)
2358 if (sig
== SIGINT
) {
2365 printf("Died on signal %d\n", sig
);
2369 static void term_init(void)
2372 static int init_done
= 0;
2378 signal(SIGINT
, catch);
2379 signal(SIGQUIT
, catch);
2380 signal(SIGTERM
, catch);
2381 signal(SIGBUS
, catch);
2382 signal(SIGSEGV
, catch);
2384 initscr(); cbreak(); noecho();
2386 use_default_colors();
2387 if (!has_colors()) {
2388 a_delete
= A_UNDERLINE
;
2390 a_common
= A_NORMAL
;
2392 a_already
= A_STANDOUT
;
2394 init_pair(1, COLOR_WHITE
, COLOR_RED
);
2395 a_delete
= COLOR_PAIR(1);
2396 init_pair(2, COLOR_WHITE
, COLOR_BLUE
);
2397 a_added
= COLOR_PAIR(2);
2398 a_common
= A_NORMAL
;
2399 init_pair(3, COLOR_WHITE
, COLOR_GREEN
);
2400 a_sep
= COLOR_PAIR(3); a_sep
= A_STANDOUT
;
2401 init_pair(4, -1, COLOR_YELLOW
);
2402 a_void
= COLOR_PAIR(4);
2403 init_pair(5, COLOR_BLUE
, -1);
2404 a_unmatched
= COLOR_PAIR(5);
2405 init_pair(6, COLOR_CYAN
, -1);
2406 a_extra
= COLOR_PAIR(6);
2408 init_pair(7, COLOR_BLACK
, COLOR_CYAN
);
2409 a_already
= COLOR_PAIR(7);
2411 nonl(); intrflush(stdscr
, FALSE
); keypad(stdscr
, TRUE
);
2412 mousemask(ALL_MOUSE_EVENTS
, NULL
);
2415 int vpatch(int argc
, char *argv
[], int patch
, int strip
,
2416 int reverse
, int replace
)
2418 /* NOTE argv[0] is first arg...
2419 * Behaviour depends on number of args:
2420 * 0: A multi-file patch is read from stdin
2421 * 1: if 'patch', parse it as a multi-file patch and allow
2422 * the files to be browsed.
2423 * if filename ends '.rej', then treat it as a patch again
2424 * a file with the same basename
2425 * Else treat the file as a merge (with conflicts) and view it.
2426 * 2: First file is original, second is patch
2427 * 3: Files are: original previous new. The diff between 'previous' and
2428 * 'new' needs to be applied to 'original'.
2430 * If a multi-file patch is being read, 'strip' tells how many
2431 * path components to stripe. If it is -1, we guess based on
2433 * If 'reverse' is given, when we invert any patch or diff
2434 * If 'replace' then we save the resulting merge.
2443 fprintf(stderr
, "%s: too many file names given.\n", Cmd
);
2446 case 0: /* stdin is a patch */
2447 if (lseek(fileno(stdin
), 0L, 1) == -1) {
2448 /* cannot seek, so need to copy to a temp file */
2451 fprintf(stderr
, "%s: Cannot create temp file\n", Cmd
);
2454 pl
= parse_patch(stdin
, f
, &num_patches
);
2457 pl
= parse_patch(stdin
, NULL
, &num_patches
);
2458 in
= fdopen(dup(0), "r");
2460 /* use stderr for keyboard input */
2462 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2463 fprintf(stderr
, "%s: aborting\n", Cmd
);
2466 main_window(pl
, num_patches
, in
, reverse
);
2469 case 1: /* a patch, a .rej, or a merge file */
2470 f
= fopen(argv
[0], "r");
2472 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2476 pl
= parse_patch(f
, NULL
, &num_patches
);
2477 if (set_prefix(pl
, num_patches
, strip
) == 0) {
2478 fprintf(stderr
, "%s: aborting\n", Cmd
);
2481 main_window(pl
, num_patches
, f
, reverse
);
2482 } else if (strlen(argv
[0]) > 4 &&
2483 strcmp(argv
[0]+strlen(argv
[0])-4, ".rej") == 0) {
2484 char *origname
= strdup(argv
[0]);
2485 origname
[strlen(origname
) - 4] = '\0';
2486 show_merge(origname
, f
, reverse
, 0, NULL
, NULL
);
2488 show_merge(argv
[0], f
, reverse
, 1, NULL
, NULL
);
2491 case 2: /* an orig and a diff/.ref */
2492 f
= fopen(argv
[1], "r");
2494 fprintf(stderr
, "%s: cannot open %s\n", Cmd
, argv
[0]);
2497 show_merge(argv
[0], f
, reverse
, 0, NULL
, NULL
);
2499 case 3: /* orig, before, after */
2500 show_merge(argv
[0], NULL
, reverse
, 1, argv
[1], argv
[2]);