#define assert(x) do { if (!(x)) abort(); } while (0)
-char *typenames[] = {
- [End] = "End",
- [Unmatched] = "Unmatched",
- [Unchanged] = "Unchanged",
- [Extraneous] = "Extraneous",
- [Changed] = "Changed",
- [Conflict] = "Conflict",
- [AlreadyApplied] = "AlreadyApplied",
-};
/* plist stores a list of patched files in an array
* Each entry identifies a file, the range of the
int chunks, wiggles, conflicts;
int calced;
};
+static struct stream load_segment(FILE *f,
+ unsigned int start, unsigned int end);
-struct plist *patch_add_file(struct plist *pl, int *np, char *file,
- unsigned int start, unsigned int end)
-{
- /* size of pl is 0, 16, n^2 */
- int n = *np;
- int asize;
-
- while (*file == '/')
- /* leading '/' are bad... */
- file++;
-
- if (n==0)
- asize = 0;
- else if (n<=16)
- asize = 16;
- else if ((n&(n-1))==0)
- asize = n;
- else
- asize = n+1; /* not accurate, but not too large */
- if (asize <= n) {
- /* need to extend array */
- struct plist *npl;
- if (asize < 16) asize = 16;
- else asize += asize;
- npl = realloc(pl, asize * sizeof(struct plist));
- if (!npl) {
- fprintf(stderr, "malloc failed - skipping %s\n", file);
- return pl;
- }
- pl = npl;
- }
- pl[n].file = file;
- pl[n].start = start;
- pl[n].end = end;
- pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
- pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
- pl[n].open = 1;
- pl[n].calced = 0;
- *np = n+1;
- return pl;
-}
-
-struct plist *parse_patch(FILE *f, FILE *of, int *np)
-{
- /* read a multi-file patch from 'f' and record relevant
- * details in a plist.
- * if 'of' >= 0, fd might not be seekable so we write
- * to 'of' and use lseek on 'of' to determine position
- */
- struct plist *plist = NULL;
-
- while (!feof(f)) {
- /* first, find the start of a patch: "\n+++ "
- * grab the file name and scan to the end of a line
- */
- char *target="\n+++ ";
- char *target2="\n--- ";
- char *pos = target;
- int c;
- char name[1024];
- unsigned start, end;
-
- while (*pos && (c=fgetc(f)) != EOF ) {
- if (of) fputc(c, of);
- if (c == *pos)
- pos++;
- else pos = target;
- }
- if (c == EOF)
- break;
- assert(c == ' ');
- /* now read a file name */
- pos = name;
- while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
- pos - name < 1023) {
- *pos++ = c;
- if (of)
- fputc(c, of);
- }
- *pos = 0;
- if (c == EOF)
- break;
- if (of) fputc(c, of);
- while (c != '\n' && (c=fgetc(f)) != EOF)
- if (of)
- fputc(c, of);
+/* global attributes */
+int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
- start = ftell(of ?: f);
+char *typenames[] = {
+ [End] = "End",
+ [Unmatched] = "Unmatched",
+ [Unchanged] = "Unchanged",
+ [Extraneous] = "Extraneous",
+ [Changed] = "Changed",
+ [Conflict] = "Conflict",
+ [AlreadyApplied] = "AlreadyApplied",
+};
- if (c == EOF)
- break;
+#define BEFORE 1
+#define AFTER 2
+#define ORIG 4
+#define RESULT 8
+#define CHANGED 16 /* The RESULT is different to ORIG */
+#define CHANGES 32 /* AFTER is different to BEFORE */
+#define WIGGLED 64 /* a conflict that was successfully resolved */
+#define CONFLICTED 128 /* a conflict that was not successfully resolved */
- /* now skip to end - "\n--- " */
- pos = target2+1;
- while (*pos && (c=fgetc(f)) != EOF) {
- if (of)
- fputc(c, of);
- if (c == *pos)
- pos++;
- else
- pos = target2;
- }
- end = ftell(of ?: f);
- if (pos > target2)
- end -= (pos - target2) - 1;
- plist = patch_add_file(plist, np,
- strdup(name), start, end);
- }
- return plist;
-}
+/* Displaying a Merge.
+ * The first step is to linearise the merge. The merge in inherently
+ * parallel with before/after streams. However much of the whole document
+ * is linear as normally much of the original in unchanged.
+ * All parallelism comes from the patch. This normally produces two
+ * parallel stream, but in the case of a conflict can produce three.
+ * For browsing the merge we only ever show two alternates in-line.
+ * When there are three we use two panes with 1 or 2 alternates in each.
+ * So to linearise the two streams we find lines that are completely
+ * unchanged (same or all 3 streams, or missing in 2nd and 3rd) which bound
+ * a region where there are changes. We include everything between
+ * these twice, in two separate passes. The exact interpretation of the
+ * passes is handled at a higher level but will be one of:
+ * original and result
+ * before and after
+ * original and after (for a conflict)
+ *
+ * At any position in the merge we can be in one of 3 states:
+ * 0: unchanged section
+ * 1: first pass
+ * 2: second pass
+ *
+ * So to walk a merge in display order we need a position in the merge,
+ * a current state, and when in a changed section, we need to know the
+ * bounds of that changed section.
+ * This is all encoded in 'struct mpos'.
+ *
+ * Each location may or may not be visible depending on certain
+ * display options.
+ *
+ * Also, some locations might be 'invalid' in that they don't need to be displayed.
+ * For example when the patch leaves a section of the original unchanged,
+ * we only need to see the original - the before/after sections are treated
+ * as invalid and are not displayed.
+ * The visibility of newlines is crucial and guides the display. One line
+ * of displayed text is all the visible sections between two visible newlines.
+ *
+ * Counting lines is a bit tricky. We only worry about line numbers in the
+ * original (stream 0) as these could compare with line numbers mentioned in
+ * patch chunks.
+ * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
+ * That way we don't get a full counted line until we see the first char after the
+ * newline, so '+' lines are counted with the previous line.
+ *
+ */
+struct mpos {
+ struct mp {
+ int m; /* merger index */
+ int s; /* stream 0,1,2 for a,b,c */
+ int o; /* offset in that stream */
+ int lineno; /* Counts newlines in stream 0
+ * set lsb when see newline.
+ * add one when not newline and lsb set
+ */
+ } p, /* the current point */
+ lo, /* eol for start of the current group */
+ hi; /* eol for end of the current group */
+ int state; /*
+ * 0 if on an unchanged (lo/hi not meaningful)
+ * 1 if on the '-' of a diff,
+ * 2 if on the '+' of a diff
+ */
+};
-static struct stream load_segment(FILE *f,
- unsigned int start, unsigned int end)
+/* used for checking location during search */
+int same_mpos(struct mpos a, struct mpos b)
{
- struct stream s;
- s.len = end - start;
- s.body = malloc(s.len);
- if (s.body) {
- fseek(f, start, 0);
- if (fread(s.body, 1, s.len, f) != s.len) {
- free(s.body);
- s.body = NULL;
- }
- } else
- die();
- return s;
+ return a.p.m == b.p.m &&
+ a.p.s == b.p.s &&
+ a.p.o == b.p.o &&
+ a.state == b.state;
}
-
-void catch(int sig)
+/* Check if a particular stream is meaningful in a particular merge
+ * section. e.g. in an Unchanged section, only stream 0, the
+ * original, is meaningful. This is used to avoid walking down
+ * pointless paths.
+ */
+int stream_valid(int s, enum mergetype type)
{
- if (sig == SIGINT) {
- signal(sig, catch);
- return;
+ switch(type) {
+ case End: return 1;
+ case Unmatched: return s == 0;
+ case Unchanged: return s == 0;
+ case Extraneous: return s == 2;
+ case Changed: return s != 1;
+ case Conflict: return 1;
+ case AlreadyApplied: return 1;
}
- nocbreak();nl();endwin();
- printf("Died on signal %d\n", sig);
- exit(2);
+ return 0;
}
-int pl_cmp(const void *av, const void *bv)
+/*
+ * Advance the 'pos' in the current mergepos returning the next
+ * element (word).
+ * This walks the merges in sequence, and the streams within
+ * each merge.
+ */
+struct elmnt next_melmnt(struct mp *pos,
+ struct file fm, struct file fb, struct file fa,
+ struct merge *m)
{
- const struct plist *a = av;
- const struct plist *b = bv;
- return strcmp(a->file, b->file);
-}
-
-int common_depth(char *a, char *b)
-{
- /* find number of path segments that these two have
- * in common
- */
- int depth = 0;
+ pos->o++;
while(1) {
- char *c;
- int al, bl;
- c = strchr(a, '/');
- if (c) al = c-a; else al = strlen(a);
- c = strchr(b, '/');
- if (c) bl = c-b; else bl = strlen(b);
- if (al == 0 || al != bl || strncmp(a,b,al) != 0)
- return depth;
- a+= al;
- while (*a=='/') a++;
- b+= bl;
- while(*b=='/') b++;
-
- depth++;
+ int l=0; /* Length remaining in current merge section */
+ if (pos->m >= 0)
+ switch(pos->s) {
+ case 0: l = m[pos->m].al; break;
+ case 1: l = m[pos->m].bl; break;
+ case 2: l = m[pos->m].cl; break;
+ }
+ if (pos->o >= l) {
+ /* Offset has reached length, choose new stream or
+ * new merge */
+ pos->o = 0;
+ do {
+ pos->s++;
+ if (pos->s > 2) {
+ pos->s = 0;
+ pos->m++;
+ }
+ } while (!stream_valid(pos->s, m[pos->m].type));
+ } else
+ break;
}
-}
-
-struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
-{
- /* any parent of file that is not a parent of curr
- * needs to be added to pl
- */
- int d = common_depth(file, curr);
- char *buf = curr;
- while (d) {
- char *c = strchr(file, '/');
- int l;
- if (c) l = c-file; else l = strlen(file);
- file += l;
- curr += l;
- while (*file == '/') file++;
- while (*curr == '/') curr++;
- d--;
+ if (pos->m == -1 || m[pos->m].type == End) {
+ struct elmnt e;
+ e.start = NULL; e.len = 0;
+ return e;
}
- while (*file) {
- if (curr > buf && curr[-1] != '/')
- *curr++ = '/';
- while (*file && *file != '/')
- *curr++ = *file++;
- while (*file == '/') file++;
- *curr = '\0';
- if (*file)
- pl = patch_add_file(pl, np, strdup(buf),
- 0, 0);
+ switch(pos->s) {
+ default: /* keep compiler happy */
+ case 0:
+ if (pos->lineno & 1)
+ pos->lineno ++;
+ if (ends_mline(fm.list[m[pos->m].a + pos->o]))
+ pos->lineno ++;
+
+ return fm.list[m[pos->m].a + pos->o];
+ case 1: return fb.list[m[pos->m].b + pos->o];
+ case 2: return fa.list[m[pos->m].c + pos->o];
}
- return pl;
}
-struct plist *sort_patches(struct plist *pl, int *np)
+/* step current position.p backwards */
+struct elmnt prev_melmnt(struct mp *pos,
+ struct file fm, struct file fb, struct file fa,
+ struct merge *m)
{
- /* sort the patches, add directory names, and re-sort */
- char curr[1024];
- char *prev;
- int parents[100];
- int prevnode[100];
- int i, n;
- qsort(pl, *np, sizeof(struct plist), pl_cmp);
- curr[0] = 0;
- n = *np;
- for (i=0; i<n; i++)
- pl = add_dir(pl, np, pl[i].file, curr);
-
- qsort(pl, *np, sizeof(struct plist), pl_cmp);
+ if (pos->s == 0) {
+ if (ends_mline(fm.list[m[pos->m].a + pos->o]))
+ pos->lineno--;
+ if (pos->lineno & 1)
+ pos->lineno--;
+ }
- /* array is now stable, so set up parent pointers */
- n = *np;
- curr[0] = 0;
- prevnode[0] = -1;
- prev = "";
- for (i=0; i<n; i++) {
- int d = common_depth(prev, pl[i].file);
- if (d == 0)
- pl[i].parent = -1;
- else {
- pl[i].parent = parents[d-1];
- pl[pl[i].parent].last = i;
+ pos->o--;
+ while (pos->m >=0 && pos->o < 0) {
+ do {
+ pos->s--;
+ if (pos->s < 0) {
+ pos->s = 2;
+ pos->m--;
+ }
+ } while (pos->m >= 0 &&
+ !stream_valid(pos->s, m[pos->m].type));
+ if (pos->m>=0) {
+ switch(pos->s) {
+ case 0: pos->o = m[pos->m].al-1; break;
+ case 1: pos->o = m[pos->m].bl-1; break;
+ case 2: pos->o = m[pos->m].cl-1; break;
+ }
}
- pl[i].prev = prevnode[d];
- if (pl[i].prev > -1)
- pl[pl[i].prev].next = i;
- prev = pl[i].file;
- parents[d] = i;
- prevnode[d] = i;
- prevnode[d+1] = -1;
}
- return pl;
+ if (pos->m < 0) {
+ struct elmnt e;
+ e.start = NULL; e.len = 0;
+ return e;
+ }
+ switch(pos->s) {
+ default: /* keep compiler happy */
+ case 0: return fm.list[m[pos->m].a + pos->o];
+ case 1: return fb.list[m[pos->m].b + pos->o];
+ case 2: return fa.list[m[pos->m].c + pos->o];
+ }
}
-/* determine how much we need to stripe of the front of
- * paths to find them from current directory. This is
- * used to guess correct '-p' value.
+/* 'visible' not only checks if this stream in this merge should be
+ * visible in this mode, but also chooses which colour/highlight to use
+ * to display it.
*/
-int get_strip(char *file)
+int visible(int mode, enum mergetype type, int stream)
{
- int fd;
- int strip = 0;
-
- while (file && *file) {
- fd = open(file, O_RDONLY);
- if (fd >= 0) {
- close(fd);
- return strip;
+ if (mode == 0) return -1;
+ /* mode can be any combination of ORIG RESULT BEFORE AFTER */
+ switch(type) {
+ case End: /* The END is always visible */
+ return A_NORMAL;
+ case Unmatched: /* Visible in ORIG and RESULT */
+ if (mode & (ORIG|RESULT))
+ return a_unmatched;
+ break;
+ case Unchanged: /* visible everywhere, but only show stream 0 */
+ if (stream == 0) return a_common;
+ break;
+ case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
+ if ((mode & (BEFORE|AFTER))
+ && stream == 2)
+ return a_extra;
+ break;
+ case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
+ if (stream == 0 &&
+ (mode & (ORIG|BEFORE)))
+ return a_delete;
+ if (stream == 2 &&
+ (mode & (RESULT|AFTER)))
+ return a_added;
+ break;
+ case Conflict:
+ switch(stream) {
+ case 0:
+ if (mode & ORIG)
+ return a_unmatched | A_REVERSE;
+ break;
+ case 1:
+ if (mode & BEFORE)
+ return a_extra | A_UNDERLINE;
+ break;
+ case 2:
+ if (mode & (AFTER|RESULT))
+ return a_added | A_UNDERLINE;
+ break;
}
- strip++;
- file = strchr(file, '/');
- if (file)
- while(*file == '/')
- file++;
+ break;
+ case AlreadyApplied:
+ switch(stream) {
+ case 0:
+ if (mode & (ORIG|RESULT))
+ return a_already;
+ break;
+ case 1:
+ if (mode & BEFORE)
+ return a_delete | A_UNDERLINE;
+ break;
+ case 2:
+ if (mode & AFTER)
+ return a_added | A_UNDERLINE;
+ break;
+ }
+ break;
}
return -1;
-
}
-int set_prefix(struct plist *pl, int n, int strip)
+
+
+/* checkline creates a summary of the sort of changes that
+ * are in a line, returning an or of
+ * CHANGED
+ * CHANGES
+ * WIGGLED
+ * CONFLICTED
+ */
+int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa,
+ struct merge *m, int mode)
{
- int i;
- for(i=0; i<4 && i<n && strip < 0; i++)
- strip = get_strip(pl[i].file);
+ int rv = 0;
+ struct elmnt e;
+ int unmatched = 0;
- if (strip < 0) {
- fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
- Cmd);
- return 0;
- }
- for (i=0; i<n; i++) {
- char *p = pl[i].file;
- int j;
- for (j=0; j<strip; j++) {
- if (p) p = strchr(p,'/');
- while (p && *p == '/') p++;
+
+ do {
+ if (m[pos.p.m].type == Changed)
+ rv |= CHANGED | CHANGES;
+ else if ((m[pos.p.m].type == AlreadyApplied ||
+ m[pos.p.m].type == Conflict))
+ rv |= CONFLICTED | CHANGES;
+ else if (m[pos.p.m].type == Extraneous)
+ rv |= WIGGLED;
+ else if (m[pos.p.m].type == Unmatched)
+ unmatched = 1;
+ e = prev_melmnt(&pos.p, fm,fb,fa,m);
+ } while (e.start != NULL &&
+ (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
+
+ if (unmatched && (rv & CHANGES))
+ rv |= WIGGLED;
+ return rv;
+}
+
+void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
+ struct merge *m, int mode)
+{
+ int mask;
+ do {
+ struct mp prv;
+ int mode2;
+
+ prv = pos->p;
+ while (1) {
+ struct elmnt e = next_melmnt(&pos->p, fm,fb,fa,m);
+ if (e.start == NULL)
+ break;
+ if (ends_mline(e) &&
+ visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
+ break;
}
- if (p == NULL) {
- fprintf(stderr, "%s: cannot strip %d segments from %s\n",
- Cmd, strip, pl[i].file);
- return 0;
+ mode2 = check_line(*pos, fm,fb,fa,m,mode);
+
+ if ((mode2 & CHANGES) && pos->state == 0) {
+ /* Just entered a diff-set */
+ pos->lo = pos->p;
+ pos->state = 1;
+ } else if (!(mode2 & CHANGES) && pos->state) {
+ /* Come to the end of a diff-set */
+ if (pos->state == 1)
+ /* Need to record the end */
+ pos->hi = prv;
+ if (pos->state == 2) {
+ /* finished final pass */
+ pos->state = 0;
+ } else {
+ /* time for another pass */
+ pos->p = pos->lo;
+ pos->state ++;
+ }
}
- pl[i].file = p;
- }
- return 1;
+ mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
+ switch(pos->state) {
+ case 1: mask &= ~(RESULT|AFTER); break;
+ case 2: mask &= ~(ORIG|BEFORE); break;
+ }
+ } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
+
}
+void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
+ struct merge *m, int mode)
+{
+ int mask;
+ do {
+ struct mp prv;
+ int mode2;
-int get_prev(int pos, struct plist *pl, int n)
+ prv = pos->p;
+ if (pos->p.m < 0)
+ return;
+ while(1) {
+ struct elmnt e = prev_melmnt(&pos->p, fm,fb,fa,m);
+ if (e.start == NULL)
+ break;
+ if (ends_mline(e) &&
+ visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
+ break;
+ }
+ mode2 = check_line(*pos, fm,fb,fa,m,mode);
+
+ if ((mode2 & CHANGES) && pos->state == 0) {
+ /* Just entered a diff-set */
+ pos->hi = pos->p;
+ pos->state = 2;
+ } else if (!(mode2 & CHANGES) && pos->state) {
+ /* Come to the end (start) of a diff-set */
+ if (pos->state == 2)
+ /* Need to record the start */
+ pos->lo = prv;
+ if (pos->state == 1) {
+ /* finished final pass */
+ pos->state = 0;
+ } else {
+ /* time for another pass */
+ pos->p = pos->hi;
+ pos->state --;
+ }
+ }
+ mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
+ switch(pos->state) {
+ case 1: mask &= ~(RESULT|AFTER); break;
+ case 2: mask &= ~(ORIG|BEFORE); break;
+ }
+ } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
+}
+
+/* blank a whole row of display */
+void blank(int row, int start, int cols, int attr)
{
- if (pos == -1) return pos;
- if (pl[pos].prev == -1)
- return pl[pos].parent;
- pos = pl[pos].prev;
- while (pl[pos].open &&
- pl[pos].last >= 0)
- pos = pl[pos].last;
- return pos;
+ (void)attrset(attr);
+ move(row,start);
+ while (cols-- > 0)
+ addch(' ');
}
-int get_next(int pos, struct plist *pl, int n)
+/* search of a string on one display line - just report if found, not where */
+
+int mcontains(struct mpos pos,
+ struct file fm, struct file fb, struct file fa, struct merge *m,
+ int mode, char *search)
{
- if (pos == -1) return pos;
- if (pl[pos].open) {
- if (pos +1 < n)
- return pos+1;
- else
- return -1;
- }
- while (pos >= 0 && pl[pos].next == -1)
- pos = pl[pos].parent;
- if (pos >= 0)
- pos = pl[pos].next;
- return pos;
+ /* See if any of the files, between start of this line and here,
+ * contain the search string
+ */
+ struct elmnt e;
+ int len = strlen(search);
+ do {
+ e = prev_melmnt(&pos.p, fm,fb,fa,m);
+ if (e.start) {
+ int i;
+ for (i=0; i<e.len; i++)
+ if (strncmp(e.start+i, search, len)==0)
+ return 1;
+ }
+ } while (e.start != NULL &&
+ (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
+ return 0;
}
-/* global attributes */
-int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
+/* Drawing the display window.
+ * There are 7 different ways we can display the data, each
+ * of which can be configured by a keystroke:
+ * o original - just show the original file with no changes, but still
+ * which highlights of what is changed or unmatched
+ * r result - show just the result of the merge. Conflicts just show
+ * the original, not the before/after options
+ * b before - show the 'before' stream of the patch
+ * a after - show the 'after' stream of the patch
+ * d diff - show just the patch, both before and after
+ * m merge - show the full merge with -+ sections for changes.
+ * If point is in a wiggled or conflicted section the
+ * window is split horizontally and the diff is shown
+ * in the bottom window
+ * | sidebyside - two panes, left and right. Left holds the merge,
+ * right holds the diff. In the case of a conflict,
+ * left holds orig/after, right holds before/after
+ *
+ * The horizontal split for 'merge' mode is managed as follows.
+ * - The window is split when we first visit a line that contains
+ * a wiggle or a conflict, and the second pane is removed when
+ * we next visit a line that contains no changes (is fully Unchanged).
+ * - to display the second pane, we go do the previous mline for that
+ * alternate mode (BEFORE|AFTER) same point is not visible. Then
+ * we centre that line
+ * - We need to rewind to an unchanged section, and wind forward again
+ * to make sure that 'lo' and 'hi' are set properly.
+ * - every time we move, we redraw the second pane (see how that goes).
+ */
-void draw_one(int row, struct plist *pl, FILE *f, int reverse)
+
+
+/* draw_mside draws one text line or, in the case of sidebyside, one side
+ * of a textline.
+ * The 'mode' tells us what to draw via the 'visible()' function.
+ * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
+ * It may also have WIGGLED or CONFLICTED ored in
+ */
+void draw_mside(int mode, int row, int offset, int start, int cols,
+ struct file fm, struct file fb, struct file fa, struct merge *m,
+ struct mpos pos,
+ int target, int *colp)
{
- char hdr[12];
- hdr[0] = 0;
+ struct elmnt e;
+ int col = 0;
+ char tag;
- if (pl == NULL) {
- move(row,0);
- clrtoeol();
+ switch(pos.state) {
+ case 0: /* unchanged line */
+ tag = ' ';
+ break;
+ case 1: /* 'before' text */
+ tag = '-';
+ if ((mode & ORIG) && (mode & CONFLICTED)) {
+ tag = '|';
+ }
+ mode &= (ORIG|BEFORE);
+ break;
+ case 2: /* the 'after' part */
+ tag = '+';
+ mode &= (AFTER|RESULT);
+ break;
+ }
+
+ if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
+ /* Not visible, just draw a blank */
+ blank(row, offset, cols, a_void);
+ if (colp)
+ *colp = 0;
return;
}
- if (pl->calced == 0 && pl->end) {
- /* better load the patch and count the chunks */
- struct stream s1, s2;
- struct stream s = load_segment(f, pl->start, pl->end);
- struct stream sf = load_file(pl->file);
- if (reverse)
- pl->chunks = split_patch(s, &s2, &s1);
- else
- pl->chunks = split_patch(s, &s1, &s2);
- if (sf.body == NULL) {
- pl->wiggles = pl->conflicts = -1;
- } else {
- struct file ff, fp1, fp2;
- struct csl *csl1, *csl2;
- struct ci ci;
- ff = split_stream(sf, ByWord, 0);
- fp1 = split_stream(s1, ByWord, 0);
- fp2 = split_stream(s2, ByWord, 0);
- csl1 = pdiff(ff, fp1, pl->chunks);
- csl2 = diff(fp1,fp2);
- ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1);
- pl->wiggles = ci.wiggles;
- pl->conflicts = ci.conflicts;
- free(csl1);
- free(csl2);
- free(ff.list);
- free(fp1.list);
- free(fp2.list);
- }
+ (void)attrset(A_NORMAL);
+ mvaddch(row, offset, tag);
+ offset++;
+ cols--;
- free(s1.body);
- free(s2.body);
- free(s.body);
- pl->calced = 1;
- }
- if (pl->end == 0) {
- strcpy(hdr, " ");
- } else {
- if (pl->chunks > 99)
- strcpy(hdr, "XX");
- else sprintf(hdr, "%2d", pl->chunks);
- if (pl->wiggles > 99)
- strcpy(hdr+2, " XX");
- else sprintf(hdr+2, " %2d", pl->wiggles);
- if (pl->conflicts > 99)
- strcpy(hdr+5, " XX ");
- else sprintf(hdr+5, " %2d ", pl->conflicts);
- }
- if (pl->end)
- strcpy(hdr+9, "= ");
- else if (pl->open)
- strcpy(hdr+9, "+ ");
- else strcpy(hdr+9, "- ");
+ /* find previous visible newline, or start of file */
+ do
+ e = prev_melmnt(&pos.p, fm,fb,fa,m);
+ while (e.start != NULL &&
+ (!ends_mline(e) ||
+ visible(mode, m[pos.p.m].type, pos.p.s)==-1));
- mvaddstr(row, 0, hdr);
- mvaddstr(row, 11, pl->file);
- clrtoeol();
+ while (1) {
+ unsigned char *c;
+ int l;
+ e = next_melmnt(&pos.p, fm,fb,fa,m);
+ if (e.start == NULL ||
+ (ends_mline(e) && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
+ if (colp) *colp = col;
+ if (col < start) col = start;
+ if (e.start && e.start[0] == 0) {
+ char b[40];
+ struct elmnt e1;
+ if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
+ int A,B,C,D,E,F;
+ e1 = fb.list[m[pos.p.m].b + pos.p.o];
+ sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
+ sscanf(e.start+1, "%d %d %d", &D, &E, &F);
+ sprintf(b, "@@ -%d,%d +%d,%d @@\n", B,C,E,F);
+ (void)attrset(a_sep);
+ } else {
+ (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
+ sprintf(b, "<%.17s>", e.start+1);
+ }
+ mvaddstr(row, col-start+offset, b);
+ col += strlen(b);
+ }
+ blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.p.m].type, pos.p.s):A_NORMAL );
+ return;
+ }
+ if (visible(mode, m[pos.p.m].type, pos.p.s) == -1) {
+ continue;
+ }
+ if (e.start[0] == 0)
+ continue;
+ (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
+ c = (unsigned char *)e.start;
+ l = e.len;
+ while(l) {
+ if (*c >= ' ' && *c != 0x7f) {
+ if (col >= start && col < start+cols)
+ mvaddch(row, col-start+offset, *c);
+ col++;
+ } else if (*c == '\t') {
+ do {
+ if (col >= start && col < start+cols)
+ mvaddch(row, col-start+offset, ' ');
+ col++;
+ } while ((col&7)!= 0);
+ } else {
+ if (col >= start && col < start+cols)
+ mvaddch(row, col-start+offset, '?');
+ col++;
+ }
+ c++;
+ if (colp && target <= col) {
+ if (col-start >= cols)
+ *colp = 10*col;
+ else
+ *colp = col;
+ colp = NULL;
+ }
+ l--;
+ }
+ }
}
-#define BEFORE 1
-#define AFTER 2
-#define ORIG 4
-#define RESULT 8
-#define CHANGED 16 /* The RESULT is different to ORIG */
-#define CHANGES 32 /* AFTER is different to BEFORE */
-#define WIGGLED 64 /* a conflict that was successfully resolved */
-#define CONFLICTED 128 /* a conflict that was not successfully resolved */
+void draw_mline(int mode, int row, int start, int cols,
+ struct file fm, struct file fb, struct file fa,
+ struct merge *m,
+ struct mpos pos,
+ int target, int *colp)
+{
+ /*
+ * Draw the left and right images of this line
+ * One side might be a_blank depending on the
+ * visibility of this newline
+ */
+ int lcols, rcols;
+ mode |= check_line(pos, fm,fb,fa,m,mode);
-/* Displaying a Merge.
- * The first step is to linearise the merge. The merge in inherently
- * parallel with before/after streams. However much of the whole document
- * is linear as normally much of the original in unchanged.
- * All parallelism comes from the patch. This normally produces two
- * parallel stream, but in the case of a conflict can produce three.
- * For browsing the merge we only ever show two alternates in-line.
- * When there are three we use two panes with 1 or 2 alternates in each.
- * So to linearise the two streams we find lines that are completely
- * unchanged (same or all 3 streams, or missing in 2nd and 3rd) which bound
- * a region where there are changes. We include everything between
- * these twice, in two separate passes. The exact interpretation of the
- * passes is handled at a higher level but will be one of:
- * original and result
- * before and after
- * original and after (for a conflict)
- *
- * At any position in the merge we can be in one of 3 states:
- * 0: unchanged section
- * 1: first pass
- * 2: second pass
- *
- * So to walk a merge in display order we need a position in the merge,
- * a current state, and when in a changed section, we need to know the
- * bounds of that changed section.
- * This is all encoded in 'struct mpos'.
- *
- * Each location may or may not be visible depending on certain
- * display options.
- *
- * Also, some locations might be 'invalid' in that they don't need to be displayed.
- * For example when the patch leaves a section of the original unchanged,
- * we only need to see the original - the before/after sections are treated
- * as invalid and are not displayed.
- * The visibility of newlines is crucial and guides the display. One line
- * of displayed text is all the visible sections between two visible newlines.
- *
- * Counting lines is a bit tricky. We only worry about line numbers in the
- * original (stream 0) as these could compare with line numbers mentioned in
- * patch chunks.
- * We count 2 for every line: 1 for everything before the newline and 1 for the newline.
- * That way we don't get a full counted line until we see the first char after the
- * newline, so '+' lines are counted with the previous line.
- *
- */
-struct mpos {
- struct mp {
- int m; /* merger index */
- int s; /* stream 0,1,2 for a,b,c */
- int o; /* offset in that stream */
- int lineno; /* Counts newlines in stream 0
- * set lsb when see newline.
- * add one when not newline and lsb set
- */
- } p, /* the current point */
- lo, /* eol for start of the current group */
- hi; /* eol for end of the current group */
- int state; /*
- * 0 if on an unchanged (lo/hi not meaningful)
- * 1 if on the '-' of a diff,
- * 2 if on the '+' of a diff
- */
-};
+ if ( (mode & (BEFORE|AFTER)) &&
+ (mode & (ORIG|RESULT))) {
-/* used for checking location during search */
-int same_mpos(struct mpos a, struct mpos b)
-{
- return a.p.m == b.p.m &&
- a.p.s == b.p.s &&
- a.p.o == b.p.o &&
- a.state == b.state;
-}
+ lcols = (cols-1)/2;
+ rcols = cols - lcols - 1;
-/* Check if a particular stream is meaningful in a particular merge
- * section. e.g. in an Unchanged section, only stream 0, the
- * original, is meaningful. This is used to avoid walking down
- * pointless paths.
- */
-int stream_valid(int s, enum mergetype type)
-{
- switch(type) {
- case End: return 1;
- case Unmatched: return s == 0;
- case Unchanged: return s == 0;
- case Extraneous: return s == 2;
- case Changed: return s != 1;
- case Conflict: return 1;
- case AlreadyApplied: return 1;
- }
- return 0;
+ (void)attrset(A_STANDOUT);
+ mvaddch(row, lcols, '|');
+
+ draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
+ fm,fb,fa,m, pos, target, colp);
+
+ draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
+ fm,fb,fa,m, pos, 0, NULL);
+ } else
+ draw_mside(mode, row, 0, start, cols,
+ fm,fb,fa,m, pos, target, colp);
}
-/*
- * Advance the 'pos' in the current mergepos returning the next
- * element (word).
- * This walks the merges in sequence, and the streams within
- * each merge.
- */
-struct elmnt next_melmnt(struct mp *pos,
- struct file fm, struct file fb, struct file fa,
- struct merge *m)
+extern void cleanlist(struct file a, struct file b, struct csl *list);
+
+void merge_window(struct plist *p, FILE *f, int reverse)
{
- pos->o++;
- while(1) {
- int l=0; /* Length remaining in current merge section */
- if (pos->m >= 0)
- switch(pos->s) {
- case 0: l = m[pos->m].al; break;
- case 1: l = m[pos->m].bl; break;
- case 2: l = m[pos->m].cl; break;
- }
- if (pos->o >= l) {
- /* Offset has reached length, choose new stream or
- * new merge */
- pos->o = 0;
- do {
- pos->s++;
- if (pos->s > 2) {
- pos->s = 0;
- pos->m++;
- }
- } while (!stream_valid(pos->s, m[pos->m].type));
- } else
- break;
- }
- if (pos->m == -1 || m[pos->m].type == End) {
- struct elmnt e;
- e.start = NULL; e.len = 0;
- return e;
- }
- switch(pos->s) {
- default: /* keep compiler happy */
- case 0:
- if (pos->lineno & 1)
- pos->lineno ++;
- if (ends_mline(fm.list[m[pos->m].a + pos->o]))
- pos->lineno ++;
-
- return fm.list[m[pos->m].a + pos->o];
- case 1: return fb.list[m[pos->m].b + pos->o];
- case 2: return fa.list[m[pos->m].c + pos->o];
- }
-}
+ /* display the merge in two side-by-side
+ * panes.
+ * left side shows diff between original and new
+ * right side shows the requested patch
+ *
+ * Unmatched: c_unmatched - left only
+ * Unchanged: c_normal - left and right
+ * Extraneous: c_extra - right only
+ * Changed-a: c_changed - left and right
+ * Changed-c: c_new - left and right
+ * AlreadyApplied-b: c_old - right only
+ * AlreadyApplied-c: c_applied - left and right
+ * Conflict-a: ?? left only
+ * Conflict-b: ?? left and right
+ * Conflict-c: ??
+ *
+ * A Conflict is displayed as the original in the
+ * left side, and the highlighted diff in the right.
+ *
+ * Newlines are the key to display.
+ * 'pos' is always a newline (or eof).
+ * For each side that this newline is visible on, we
+ * rewind the previous newline visible on this side, and
+ * the display the stuff in between
+ *
+ * A 'position' is an offset in the merger, a stream
+ * choice (a,b,c - some aren't relevant) and an offset in
+ * that stream
+ */
-/* step current position.p backwards */
-struct elmnt prev_melmnt(struct mp *pos,
- struct file fm, struct file fb, struct file fa,
- struct merge *m)
-{
- if (pos->s == 0) {
- if (ends_mline(fm.list[m[pos->m].a + pos->o]))
- pos->lineno--;
- if (pos->lineno & 1)
- pos->lineno--;
- }
+ struct stream sm, sb, sa, sp; /* main, before, after, patch */
+ struct file fm, fb, fa;
+ struct csl *csl1, *csl2;
+ struct ci ci;
+ char buf[100];
+ int ch;
+ int refresh = 2;
+ int rows = 0, cols = 0;
+ int splitrow = -1;
+ int lastrow = 0;
+ int i, c;
+ int mode = ORIG|RESULT | BEFORE|AFTER;
+ char *modename = "sidebyside";
- pos->o--;
- while (pos->m >=0 && pos->o < 0) {
- do {
- pos->s--;
- if (pos->s < 0) {
- pos->s = 2;
- pos->m--;
- }
- } while (pos->m >= 0 &&
- !stream_valid(pos->s, m[pos->m].type));
- if (pos->m>=0) {
- switch(pos->s) {
- case 0: pos->o = m[pos->m].al-1; break;
- case 1: pos->o = m[pos->m].bl-1; break;
- case 2: pos->o = m[pos->m].cl-1; break;
- }
- }
- }
- if (pos->m < 0) {
- struct elmnt e;
- e.start = NULL; e.len = 0;
- return e;
- }
- switch(pos->s) {
- default: /* keep compiler happy */
- case 0: return fm.list[m[pos->m].a + pos->o];
- case 1: return fb.list[m[pos->m].b + pos->o];
- case 2: return fa.list[m[pos->m].c + pos->o];
- }
-}
+ int row,start = 0;
+ int trow;
+ int col=0, target=0;
+ struct mpos pos;
+ struct mpos tpos, toppos, botpos;
+ struct mpos vpos, tvpos;
+ int botrow = 0;
+ int meta = 0, tmeta;
+ int num= -1, tnum;
+ char search[80];
+ int searchlen = 0;
+ int search_notfound = 0;
+ int searchdir = 0;
+ struct search_anchor {
+ struct search_anchor *next;
+ struct mpos pos;
+ int notfound;
+ int row, col, searchlen;
+ } *anchor = NULL;
-/* 'visible' not only checks if this stream in this merge should be
- * visible in this mode, but also chooses which colour/highlight to use
- * to display it.
- */
-int visible(int mode, enum mergetype type, int stream)
-{
- if (mode == 0) return -1;
- /* mode can be any combination of ORIG RESULT BEFORE AFTER */
- switch(type) {
- case End: /* The END is always visible */
- return A_NORMAL;
- case Unmatched: /* Visible in ORIG and RESULT */
- if (mode & (ORIG|RESULT))
- return a_unmatched;
- break;
- case Unchanged: /* visible everywhere, but only show stream 0 */
- if (stream == 0) return a_common;
- break;
- case Extraneous: /* stream 2 is visible in BEFORE and AFTER */
- if ((mode & (BEFORE|AFTER))
- && stream == 2)
- return a_extra;
- break;
- case Changed: /* stream zero visible ORIG and BEFORE, stream 2 elsewhere */
- if (stream == 0 &&
- (mode & (ORIG|BEFORE)))
- return a_delete;
- if (stream == 2 &&
- (mode & (RESULT|AFTER)))
- return a_added;
- break;
- case Conflict:
- switch(stream) {
- case 0:
- if (mode & ORIG)
- return a_unmatched | A_REVERSE;
- break;
- case 1:
- if (mode & BEFORE)
- return a_extra | A_UNDERLINE;
- break;
- case 2:
- if (mode & (AFTER|RESULT))
- return a_added | A_UNDERLINE;
- break;
- }
- break;
- case AlreadyApplied:
- switch(stream) {
- case 0:
- if (mode & (ORIG|RESULT))
- return a_already;
- break;
- case 1:
- if (mode & BEFORE)
- return a_delete | A_UNDERLINE;
- break;
- case 2:
- if (mode & AFTER)
- return a_added | A_UNDERLINE;
- break;
- }
- break;
- }
- return -1;
-}
+ sp = load_segment(f, p->start, p->end);
+ if (reverse)
+ ch = split_patch(sp, &sa, &sb);
+ else
+ ch = split_patch(sp, &sb, &sa);
+ sm = load_file(p->file);
+ fm = split_stream(sm, ByWord, 0);
+ fb = split_stream(sb, ByWord, 0);
+ fa = split_stream(sa, ByWord, 0);
+ csl1 = pdiff(fm, fb, ch);
+ csl2 = diff(fb, fa);
-/* checkline creates a summary of the sort of changes that
- * are in a line, returning an or of
- * CHANGED
- * CHANGES
- * WIGGLED
- * CONFLICTED
- */
-int check_line(struct mpos pos, struct file fm, struct file fb, struct file fa,
- struct merge *m, int mode)
-{
- int rv = 0;
- struct elmnt e;
- int unmatched = 0;
+ ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1);
+ row = 1;
+ pos.p.m = 0; /* merge node */
+ pos.p.s = 0; /* stream number */
+ pos.p.o = -1; /* offset */
+ pos.p.lineno = 1;
+ pos.state = 0;
+ next_mline(&pos, fm,fb,fa,ci.merger, mode);
+ vpos = pos;
+ while(1) {
+ if (refresh == 2) {
+ clear();
+ sprintf(buf, "File: %s%s Mode: %s\n",
+ p->file,reverse?" - reversed":"", modename);
+ (void)attrset(A_BOLD); mvaddstr(0,0,buf);
+ clrtoeol();
+ (void)attrset(A_NORMAL);
+ refresh = 1;
+ }
+ if (row < 1 || row >= lastrow)
+ refresh = 1;
- do {
- if (m[pos.p.m].type == Changed)
- rv |= CHANGED | CHANGES;
- else if ((m[pos.p.m].type == AlreadyApplied ||
- m[pos.p.m].type == Conflict))
- rv |= CONFLICTED | CHANGES;
- else if (m[pos.p.m].type == Extraneous)
- rv |= WIGGLED;
- else if (m[pos.p.m].type == Unmatched)
- unmatched = 1;
- e = prev_melmnt(&pos.p, fm,fb,fa,m);
- } while (e.start != NULL &&
- (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
-
- if (unmatched && (rv & CHANGES))
- rv |= WIGGLED;
- return rv;
-}
-
-void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
- struct merge *m, int mode)
-{
- int mask;
- do {
- struct mp prv;
- int mode2;
- prv = pos->p;
- while (1) {
- struct elmnt e = next_melmnt(&pos->p, fm,fb,fa,m);
- if (e.start == NULL)
- break;
- if (ends_mline(e) &&
- visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
- break;
- }
- mode2 = check_line(*pos, fm,fb,fa,m,mode);
- if ((mode2 & CHANGES) && pos->state == 0) {
- /* Just entered a diff-set */
- pos->lo = pos->p;
- pos->state = 1;
- } else if (!(mode2 & CHANGES) && pos->state) {
- /* Come to the end of a diff-set */
- if (pos->state == 1)
- /* Need to record the end */
- pos->hi = prv;
- if (pos->state == 2) {
- /* finished final pass */
- pos->state = 0;
- } else {
- /* time for another pass */
- pos->p = pos->lo;
- pos->state ++;
+ if (mode == (ORIG|RESULT)) {
+ int cmode = check_line(pos, fm,fb,fa,ci.merger, mode);
+ if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) {
+ splitrow = (rows+1)/2;
+ lastrow = splitrow - 1;
+ refresh = 1;
}
+ if (splitrow >= 0 && !(cmode & CHANGES)) {
+ splitrow = -1;
+ lastrow = rows-1;
+ refresh = 1;
+ }
+ } else if (splitrow >= 0) {
+ splitrow = -1;
+ lastrow = rows-1;
+ refresh = 1;
}
- mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
- switch(pos->state) {
- case 1: mask &= ~(RESULT|AFTER); break;
- case 2: mask &= ~(ORIG|BEFORE); break;
- }
- } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
-
-}
-void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
- struct merge *m, int mode)
-{
- int mask;
- do {
- struct mp prv;
- int mode2;
+ if (refresh) {
+ getmaxyx(stdscr, rows, cols);
+ rows--; /* keep last row clear */
+ if (splitrow >= 0) {
+ splitrow = (rows+1)/2;
+ lastrow = splitrow - 1;
+ } else
+ lastrow = rows - 1;
- prv = pos->p;
- if (pos->p.m < 0)
- return;
- while(1) {
- struct elmnt e = prev_melmnt(&pos->p, fm,fb,fa,m);
- if (e.start == NULL)
- break;
- if (ends_mline(e) &&
- visible(mode, m[pos->p.m].type, pos->p.s) >= 0)
- break;
+ if (row < -3)
+ row = lastrow/2+1;
+ if (row < 1)
+ row = 1;
+ if (row > lastrow+3)
+ row = lastrow/2+1;
+ if (row >= lastrow)
+ row = lastrow-1;
}
- mode2 = check_line(*pos, fm,fb,fa,m,mode);
-
- if ((mode2 & CHANGES) && pos->state == 0) {
- /* Just entered a diff-set */
- pos->hi = pos->p;
- pos->state = 2;
- } else if (!(mode2 & CHANGES) && pos->state) {
- /* Come to the end (start) of a diff-set */
- if (pos->state == 2)
- /* Need to record the start */
- pos->lo = prv;
- if (pos->state == 1) {
- /* finished final pass */
- pos->state = 0;
- } else {
- /* time for another pass */
- pos->p = pos->hi;
- pos->state --;
+ if (getenv("WIGGLE_VTRACE")) {
+ char b[100];
+ char *e, e2[7];
+ int i;
+ switch (vpos.p.s) {
+ case 0: e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start; break;
+ case 1: e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start; break;
+ case 2: e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start; break;
+ }
+ for (i=0; i<6; i++) {
+ e2[i] = e[i];
+ if (e2[i] < 32 || e2[i] >= 127) e2[i] = '?';
}
+ sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
+ vpos.p.s, vpos.p.o,
+ vpos.p.m, typenames[ci.merger[vpos.p.m].type],
+ ci.merger[vpos.p.m].al,
+ ci.merger[vpos.p.m].bl,
+ ci.merger[vpos.p.m].cl,
+ ci.merger[vpos.p.m].in_conflict,
+ e2
+ );
+ (void)attrset(A_NORMAL);
+ mvaddstr(0, 50, b);
+ clrtoeol();
}
- mask = ORIG|RESULT|BEFORE|AFTER|CHANGES|CHANGED;
- switch(pos->state) {
- case 1: mask &= ~(RESULT|AFTER); break;
- case 2: mask &= ~(ORIG|BEFORE); break;
+ {
+ char lbuf[20];
+ (void)attrset(A_BOLD);
+ sprintf(lbuf, "ln:%d", (pos.p.lineno-1)/2);
+ mvaddstr(0, cols - strlen(lbuf) - 4, " ");
+ mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
}
- } while (visible(mode&mask, m[pos->p.m].type, pos->p.s) < 0);
-}
+ /* Always refresh the line */
+ while (start > target) { start -= 8; refresh = 1;}
+ if (start < 0) start = 0;
+ retry:
+ draw_mline(mode, row, start, cols, fm,fb,fa,ci.merger,
+ pos, target, &col);
-/* blank a whole row of display */
-void blank(int row, int start, int cols, int attr)
-{
- (void)attrset(attr);
- move(row,start);
- while (cols-- > 0)
- addch(' ');
-}
+ if (col > cols+start) {
+ start += 8;
+ refresh = 1;
+ goto retry;
+ }
+ if (col < start) {
+ start -= 8;
+ refresh = 1;
+ if (start < 0) start = 0;
+ goto retry;
+ }
+ if (refresh) {
+ refresh = 0;
+ tpos = pos;
-/* search of a string on one display line - just report if found, not where */
+ for (i=row-1; i>=1 && tpos.p.m >= 0; ) {
+ prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ draw_mline(mode, i--, start, cols, fm,fb,fa,ci.merger,
+ tpos, 0, NULL);
-int mcontains(struct mpos pos,
- struct file fm, struct file fb, struct file fa, struct merge *m,
- int mode, char *search)
-{
- /* See if any of the files, between start of this line and here,
- * contain the search string
- */
- struct elmnt e;
- int len = strlen(search);
- do {
- e = prev_melmnt(&pos.p, fm,fb,fa,m);
- if (e.start) {
- int i;
- for (i=0; i<e.len; i++)
- if (strncmp(e.start+i, search, len)==0)
- return 1;
+ }
+ if (i) {
+ row -= (i+1);
+ refresh = 1;
+ goto retry;
+ }
+ toppos = tpos;
+ while (i >= 1)
+ blank(i--, 0, cols, a_void);
+ tpos = pos;
+ for (i=row; i<= lastrow && ci.merger[tpos.p.m].type != End; ) {
+ draw_mline(mode, i++, start, cols, fm,fb,fa,ci.merger,
+ tpos, 0, NULL);
+ next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ }
+ botpos = tpos; botrow = i;
+ while (i<=lastrow)
+ blank(i++, 0, cols, a_void);
}
- } while (e.start != NULL &&
- (!ends_mline(e) || visible(mode, m[pos.p.m].type, pos.p.s)==-1));
- return 0;
-}
+
+ if (splitrow >= 0) {
+ struct mpos spos = pos;
+ int smode = BEFORE|AFTER;
+ int srow = (rows + splitrow)/2;
+ if (visible(smode, ci.merger[spos.p.m].type,
+ spos.p.s) < 0)
+ prev_mline(&spos, fm,fb,fa,ci.merger, smode);
+ /* Now hi/lo might be wrong, so lets fix it. */
+ tpos = spos;
+ while (spos.p.m >= 0 && spos.state != 0)
+ prev_mline(&spos, fm,fb,fa,ci.merger, smode);
+ while (!same_mpos(spos, tpos))
+ next_mline(&spos, fm,fb,fa,ci.merger, smode);
+
-/* Drawing the display window.
- * There are 7 different ways we can display the data, each
- * of which can be configured by a keystroke:
- * o original - just show the original file with no changes, but still
- * which highlights of what is changed or unmatched
- * r result - show just the result of the merge. Conflicts just show
- * the original, not the before/after options
- * b before - show the 'before' stream of the patch
- * a after - show the 'after' stream of the patch
- * d diff - show just the patch, both before and after
- * m merge - show the full merge with -+ sections for changes.
- * If point is in a wiggled or conflicted section the
- * window is split horizontally and the diff is shown
- * in the bottom window
- * | sidebyside - two panes, left and right. Left holds the merge,
- * right holds the diff. In the case of a conflict,
- * left holds orig/after, right holds before/after
- *
- * The horizontal split for 'merge' mode is managed as follows.
- * - The window is split when we first visit a line that contains
- * a wiggle or a conflict, and the second pane is removed when
- * we next visit a line that contains no changes (is fully Unchanged).
- * - to display the second pane, we go do the previous mline for that
- * alternate mode (BEFORE|AFTER) same point is not visible. Then
- * we centre that line
- * - We need to rewind to an unchanged section, and wind forward again
- * to make sure that 'lo' and 'hi' are set properly.
- * - every time we move, we redraw the second pane (see how that goes).
- */
-
-
-
-/* draw_mside draws one text line or, in the case of sidebyside, one side
- * of a textline.
- * The 'mode' tells us what to draw via the 'visible()' function.
- * It is one of ORIG RESULT BEFORE AFTER or ORIG|RESULT or BEFORE|AFTER
- * It may also have WIGGLED or CONFLICTED ored in
- */
-void draw_mside(int mode, int row, int offset, int start, int cols,
- struct file fm, struct file fb, struct file fa, struct merge *m,
- struct mpos pos,
- int target, int *colp)
-{
- struct elmnt e;
- int col = 0;
- char tag;
-
- switch(pos.state) {
- case 0: /* unchanged line */
- tag = ' ';
- break;
- case 1: /* 'before' text */
- tag = '-';
- if ((mode & ORIG) && (mode & CONFLICTED)) {
- tag = '|';
- }
- mode &= (ORIG|BEFORE);
- break;
- case 2: /* the 'after' part */
- tag = '+';
- mode &= (AFTER|RESULT);
- break;
- }
-
- if (visible(mode, m[pos.p.m].type, pos.p.s) < 0) {
- /* Not visible, just draw a blank */
- blank(row, offset, cols, a_void);
- if (colp)
- *colp = 0;
- return;
- }
-
- (void)attrset(A_NORMAL);
- mvaddch(row, offset, tag);
- offset++;
- cols--;
-
- /* find previous visible newline, or start of file */
- do
- e = prev_melmnt(&pos.p, fm,fb,fa,m);
- while (e.start != NULL &&
- (!ends_mline(e) ||
- visible(mode, m[pos.p.m].type, pos.p.s)==-1));
+ (void)attrset(a_sep);
+ for (i=0; i<cols; i++)
+ mvaddstr(splitrow, i, "-");
- while (1) {
- unsigned char *c;
- int l;
- e = next_melmnt(&pos.p, fm,fb,fa,m);
- if (e.start == NULL ||
- (ends_mline(e) && visible(mode, m[pos.p.m].type, pos.p.s) != -1)) {
- if (colp) *colp = col;
- if (col < start) col = start;
- if (e.start && e.start[0] == 0) {
- char b[40];
- struct elmnt e1;
- if (pos.p.s == 2 && m[pos.p.m].type == Extraneous) {
- int A,B,C,D,E,F;
- e1 = fb.list[m[pos.p.m].b + pos.p.o];
- sscanf(e1.start+1, "%d %d %d", &A, &B, &C);
- sscanf(e.start+1, "%d %d %d", &D, &E, &F);
- sprintf(b, "@@ -%d,%d +%d,%d @@\n", B,C,E,F);
- (void)attrset(a_sep);
- } else {
- (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
- sprintf(b, "<%.17s>", e.start+1);
- }
- mvaddstr(row, col-start+offset, b);
- col += strlen(b);
+ tpos = spos;
+ for (i=srow-1; i>splitrow; i-- ) {
+ prev_mline(&tpos, fm,fb,fa,ci.merger, smode);
+ draw_mline(smode, i, start, cols, fm,fb,fa,ci.merger,
+ tpos, 0, NULL);
+ }
+ while (i > splitrow)
+ blank(i--, 0, cols, a_void);
+ tpos = spos;
+ for (i=srow; i<rows && ci.merger[tpos.p.m].type != End; i++) {
+ draw_mline(smode, i, start, cols, fm, fb,fa,ci.merger,
+ tpos, 0, NULL);
+ next_mline(&tpos, fm,fb,fa,ci.merger, smode);
}
- blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.p.m].type, pos.p.s):A_NORMAL );
- return;
}
- if (visible(mode, m[pos.p.m].type, pos.p.s) == -1) {
- continue;
+#define META(c) ((c)|0x1000)
+#define SEARCH(c) ((c)|0x2000)
+ move(rows,0);
+ (void)attrset(A_NORMAL);
+ if (num>=0) { char buf[10]; sprintf(buf, "%d ", num); addstr(buf);}
+ if (meta & META(0)) addstr("ESC...");
+ if (meta & SEARCH(0)) {
+ if (searchdir) addstr("Backwards ");
+ addstr("Search: ");
+ addstr(search);
+ if (search_notfound)
+ addstr(" - Not Found.");
+ search_notfound = 0;
}
- if (e.start[0] == 0)
- continue;
- (void)attrset(visible(mode, m[pos.p.m].type, pos.p.s));
- c = (unsigned char *)e.start;
- l = e.len;
- while(l) {
- if (*c >= ' ' && *c != 0x7f) {
- if (col >= start && col < start+cols)
- mvaddch(row, col-start+offset, *c);
- col++;
- } else if (*c == '\t') {
- do {
- if (col >= start && col < start+cols)
- mvaddch(row, col-start+offset, ' ');
- col++;
- } while ((col&7)!= 0);
+ clrtoeol();
+ move(row,col-start);
+ c = getch();
+ tmeta = meta; meta = 0;
+ tnum = num; num = -1;
+ tvpos = vpos; vpos = pos;
+ switch(c | tmeta) {
+ case 27: /* escape */
+ case META(27):
+ meta = META(0);
+ break;
+ case META('<'): /* start of file */
+ start:
+ tpos = pos; row++;
+ do {
+ pos = tpos; row--;
+ prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
+ } while (tpos.p.m >= 0);
+ if (row <= 0)
+ row = 0;
+ break;
+ case META('>'): /* end of file */
+ case 'G':
+ if (tnum >=0) goto start;
+ tpos = pos; row--;
+ do {
+ pos = tpos; row++;
+ next_mline(&tpos, fm,fb,fa,ci.merger,mode);
+ } while (ci.merger[tpos.p.m].type != End);
+ if (row >= lastrow)
+ row = lastrow;
+ break;
+ case '0' ... '9':
+ if (tnum < 0) tnum = 0;
+ num = tnum*10 + (c-'0');
+ break;
+ case 'q':
+ return;
+
+ case '/':
+ case 'S'-64:
+ /* incr search forward */
+ meta = SEARCH(0);
+ searchlen = 0;
+ search[searchlen] = 0;
+ searchdir = 0;
+ break;
+ case '\\':
+ case 'R'-64:
+ /* incr search backwards */
+ meta = SEARCH(0);
+ searchlen = 0;
+ search[searchlen] = 0;
+ searchdir = 1;
+ break;
+ case SEARCH('G'-64):
+ case SEARCH('S'-64):
+ case SEARCH('R'-64):
+ /* search again */
+ if ((c|tmeta) == SEARCH('R'-64))
+ searchdir = 1;
+ if ((c|tmeta) == SEARCH('S'-64))
+ searchdir = 0;
+ meta = SEARCH(0);
+ tpos = pos; trow = row;
+ if (searchdir) {
+ trow--;
+ prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
} else {
- if (col >= start && col < start+cols)
- mvaddch(row, col-start+offset, '?');
- col++;
- }
- c++;
- if (colp && target <= col) {
- if (col-start >= cols)
- *colp = 10*col;
- else
- *colp = col;
- colp = NULL;
+ trow++;
+ next_mline(&tpos, fm,fb,fa,ci.merger,mode);
}
- l--;
- }
- }
-}
-
-void draw_mline(int mode, int row, int start, int cols,
- struct file fm, struct file fb, struct file fa,
- struct merge *m,
- struct mpos pos,
- int target, int *colp)
-{
- /*
- * Draw the left and right images of this line
- * One side might be a_blank depending on the
- * visibility of this newline
- */
- int lcols, rcols;
-
- mode |= check_line(pos, fm,fb,fa,m,mode);
-
- if ( (mode & (BEFORE|AFTER)) &&
- (mode & (ORIG|RESULT))) {
-
- lcols = (cols-1)/2;
- rcols = cols - lcols - 1;
-
- (void)attrset(A_STANDOUT);
- mvaddch(row, lcols, '|');
+ goto search_again;
- draw_mside(mode&~(BEFORE|AFTER), row, 0, start, lcols,
- fm,fb,fa,m, pos, target, colp);
+ case SEARCH('H'-64):
+ meta = SEARCH(0);
+ if (anchor) {
+ struct search_anchor *a;
+ a = anchor;
+ anchor = a->next;
+ free(a);
+ }
+ if (anchor) {
+ struct search_anchor *a;
+ a = anchor;
+ anchor = a->next;
+ pos = a->pos;
+ row = a->row;
+ col = a->col;
+ search_notfound = a->notfound;
+ searchlen = a->searchlen;
+ search[searchlen] = 0;
+ free(a);
+ refresh = 1;
+ }
+ break;
+ case SEARCH(' ') ... SEARCH('~'):
+ case SEARCH('\t'):
+ meta = SEARCH(0);
+ if (searchlen < sizeof(search)-1)
+ search[searchlen++] = c & (0x7f);
+ search[searchlen] = 0;
+ tpos = pos; trow = row;
+ search_again:
+ search_notfound = 1;
+ do {
+ if (mcontains(tpos, fm,fb,fa,ci.merger,mode,search)) {
+ pos = tpos;
+ row = trow;
+ search_notfound = 0;
+ break;
+ }
+ if (searchdir) {
+ trow--;
+ prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
+ } else {
+ trow++;
+ next_mline(&tpos, fm,fb,fa,ci.merger,mode);
+ }
+ } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
- draw_mside(mode&~(ORIG|RESULT), row, lcols+1, start, rcols,
- fm,fb,fa,m, pos, 0, NULL);
- } else
- draw_mside(mode, row, 0, start, cols,
- fm,fb,fa,m, pos, target, colp);
-}
+ break;
+ case 'L'-64:
+ refresh = 2;
+ row = lastrow / 2;
+ break;
-extern void cleanlist(struct file a, struct file b, struct csl *list);
+ case 'V'-64: /* page down */
+ pos = botpos;
+ if (botrow <= lastrow)
+ row = botrow;
+ else
+ row = 2;
+ refresh = 1;
+ break;
+ case META('v'): /* page up */
+ pos = toppos;
+ row = lastrow-1;
+ refresh = 1;
+ break;
-void merge_window(struct plist *p, FILE *f, int reverse)
-{
- /* display the merge in two side-by-side
- * panes.
- * left side shows diff between original and new
- * right side shows the requested patch
- *
- * Unmatched: c_unmatched - left only
- * Unchanged: c_normal - left and right
- * Extraneous: c_extra - right only
- * Changed-a: c_changed - left and right
- * Changed-c: c_new - left and right
- * AlreadyApplied-b: c_old - right only
- * AlreadyApplied-c: c_applied - left and right
- * Conflict-a: ?? left only
- * Conflict-b: ?? left and right
- * Conflict-c: ??
- *
- * A Conflict is displayed as the original in the
- * left side, and the highlighted diff in the right.
- *
- * Newlines are the key to display.
- * 'pos' is always a newline (or eof).
- * For each side that this newline is visible on, we
- * rewind the previous newline visible on this side, and
- * the display the stuff in between
- *
- * A 'position' is an offset in the merger, a stream
- * choice (a,b,c - some aren't relevant) and an offset in
- * that stream
- */
+ case 'j':
+ case 'n':
+ case 'N'-64:
+ case KEY_DOWN:
+ if (tnum < 0) tnum = 1;
+ for (; tnum > 0 ; tnum--) {
+ tpos = pos;
+ next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ if (ci.merger[tpos.p.m].type != End) {
+ pos = tpos;
+ row++;
+ }
+ }
+ break;
+ case 'N':
+ /* Next diff */
+ tpos = pos; row--;
+ do {
+ pos = tpos; row++;
+ next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
+ tpos = pos; row--;
+ do {
+ pos = tpos; row++;
+ next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
- struct stream sm, sb, sa, sp; /* main, before, after, patch */
- struct file fm, fb, fa;
- struct csl *csl1, *csl2;
- struct ci ci;
- char buf[100];
- int ch;
- int refresh = 2;
- int rows = 0, cols = 0;
- int splitrow = -1;
- int lastrow = 0;
- int i, c;
- int mode = ORIG|RESULT | BEFORE|AFTER;
- char *modename = "sidebyside";
+ break;
+ case 'P':
+ /* Previous diff */
+ tpos = pos; row++;
+ do {
+ pos = tpos; row--;
+ prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ } while (tpos.state == 0 && tpos.p.m >= 0);
+ tpos = pos; row++;
+ do {
+ pos = tpos; row--;
+ prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ } while (tpos.state != 0 && tpos.p.m >= 0);
+ break;
- int row,start = 0;
- int trow;
- int col=0, target=0;
- struct mpos pos;
- struct mpos tpos, toppos, botpos;
- struct mpos vpos, tvpos;
- int botrow = 0;
- int meta = 0, tmeta;
- int num= -1, tnum;
- char search[80];
- int searchlen = 0;
- int search_notfound = 0;
- int searchdir = 0;
- struct search_anchor {
- struct search_anchor *next;
- struct mpos pos;
- int notfound;
- int row, col, searchlen;
- } *anchor = NULL;
+ case 'k':
+ case 'p':
+ case 'P'-64:
+ case KEY_UP:
+ if (tnum < 0) tnum = 1;
+ for (; tnum > 0 ; tnum--) {
+ tpos = pos;
+ prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+ if (tpos.p.m >= 0) {
+ pos = tpos;
+ row--;
+ }
+ }
+ break;
- sp = load_segment(f, p->start, p->end);
- if (reverse)
- ch = split_patch(sp, &sa, &sb);
- else
- ch = split_patch(sp, &sb, &sa);
+ case KEY_LEFT:
+ case 'h':
+ /* left */
+ target = col - 1;
+ if (target < 0) target = 0;
+ break;
+ case KEY_RIGHT:
+ case 'l':
+ /* right */
+ target = col + 1;
+ break;
- sm = load_file(p->file);
- fm = split_stream(sm, ByWord, 0);
- fb = split_stream(sb, ByWord, 0);
- fa = split_stream(sa, ByWord, 0);
+ case '^':
+ case 'A'-64:
+ /* Start of line */
+ target = 0;
+ break;
+ case '$':
+ case 'E'-64:
+ /* End of line */
+ target = 1000;
+ break;
- csl1 = pdiff(fm, fb, ch);
- csl2 = diff(fb, fa);
+ case 'a': /* 'after' view in patch window */
+ mode = AFTER; modename = "after";
+ refresh = 2;
+ break;
+ case 'b': /* 'before' view in patch window */
+ mode = BEFORE; modename = "before";
+ refresh = 2;
+ break;
+ case 'o': /* 'original' view in the merge window */
+ mode = ORIG; modename = "original";
+ refresh = 2;
+ break;
+ case 'r': /* the 'result' view in the merge window */
+ mode = RESULT; modename = "result";
+ refresh = 2;
+ break;
+ case 'd':
+ mode = BEFORE|AFTER; modename = "diff";
+ refresh = 2;
+ break;
+ case 'm':
+ mode = ORIG|RESULT; modename = "merge";
+ refresh = 2;
+ break;
- ci = make_merger(fm, fb, fa, csl1, csl2, 0, 1);
+ case '|':
+ mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside";
+ refresh = 1;
+ break;
- row = 1;
- pos.p.m = 0; /* merge node */
- pos.p.s = 0; /* stream number */
- pos.p.o = -1; /* offset */
- pos.p.lineno = 1;
- pos.state = 0;
- next_mline(&pos, fm,fb,fa,ci.merger, mode);
- vpos = pos;
- while(1) {
- if (refresh == 2) {
- clear();
- sprintf(buf, "File: %s%s Mode: %s\n",
- p->file,reverse?" - reversed":"", modename);
- (void)attrset(A_BOLD); mvaddstr(0,0,buf);
- clrtoeol();
- (void)attrset(A_NORMAL);
+ case 'H':
+ if (start > 0) start--;
+ target = start + 1;
refresh = 1;
- }
- if (row < 1 || row >= lastrow)
+ break;
+ case 'L':
+ if (start < cols) start++;
+ target = start + 1;
refresh = 1;
+ break;
+ case '<':
+ prev_melmnt(&tvpos.p, fm,fb,fa,ci.merger);
+ if (tvpos.p.m >= 0)
+ vpos = tvpos;
+ break;
+ case '>':
+ next_melmnt(&tvpos.p, fm,fb,fa,ci.merger);
+ if (ci.merger[tvpos.p.m].type != End)
+ vpos = tvpos;
+ break;
+ }
-
- if (mode == (ORIG|RESULT)) {
- int cmode = check_line(pos, fm,fb,fa,ci.merger, mode);
- if (splitrow < 0 && (cmode & (WIGGLED|CONFLICTED))) {
- splitrow = (rows+1)/2;
- lastrow = splitrow - 1;
- refresh = 1;
+ if (meta == SEARCH(0)) {
+ if (anchor == NULL ||
+ !same_mpos(anchor->pos, pos) ||
+ anchor->searchlen != searchlen ||
+ anchor->col != col) {
+ struct search_anchor *a = malloc(sizeof(*a));
+ a->pos = pos;
+ a->row = row;
+ a->col = col;
+ a->searchlen = searchlen;
+ a->notfound = search_notfound;
+ a->next = anchor;
+ anchor = a;
}
- if (splitrow >= 0 && !(cmode & CHANGES)) {
- splitrow = -1;
- lastrow = rows-1;
- refresh = 1;
+ } else {
+ while (anchor) {
+ struct search_anchor *a = anchor;
+ anchor = a->next;
+ free(a);
}
- } else if (splitrow >= 0) {
- splitrow = -1;
- lastrow = rows-1;
- refresh = 1;
}
+ }
+}
- if (refresh) {
- getmaxyx(stdscr, rows, cols);
- rows--; /* keep last row clear */
- if (splitrow >= 0) {
- splitrow = (rows+1)/2;
- lastrow = splitrow - 1;
- } else
- lastrow = rows - 1;
+struct plist *patch_add_file(struct plist *pl, int *np, char *file,
+ unsigned int start, unsigned int end)
+{
+ /* size of pl is 0, 16, n^2 */
+ int n = *np;
+ int asize;
- if (row < -3)
- row = lastrow/2+1;
- if (row < 1)
- row = 1;
- if (row > lastrow+3)
- row = lastrow/2+1;
- if (row >= lastrow)
- row = lastrow-1;
+ while (*file == '/')
+ /* leading '/' are bad... */
+ file++;
+
+ if (n==0)
+ asize = 0;
+ else if (n<=16)
+ asize = 16;
+ else if ((n&(n-1))==0)
+ asize = n;
+ else
+ asize = n+1; /* not accurate, but not too large */
+ if (asize <= n) {
+ /* need to extend array */
+ struct plist *npl;
+ if (asize < 16) asize = 16;
+ else asize += asize;
+ npl = realloc(pl, asize * sizeof(struct plist));
+ if (!npl) {
+ fprintf(stderr, "malloc failed - skipping %s\n", file);
+ return pl;
}
- if (getenv("WIGGLE_VTRACE")) {
- char b[100];
- char *e, e2[7];
- int i;
- switch (vpos.p.s) {
- case 0: e = fm.list[ci.merger[vpos.p.m].a + vpos.p.o].start; break;
- case 1: e = fb.list[ci.merger[vpos.p.m].b + vpos.p.o].start; break;
- case 2: e = fa.list[ci.merger[vpos.p.m].c + vpos.p.o].start; break;
- }
- for (i=0; i<6; i++) {
- e2[i] = e[i];
- if (e2[i] < 32 || e2[i] >= 127) e2[i] = '?';
- }
- sprintf(b, "st=%d str=%d o=%d m=%d mt=%s(%d,%d,%d) ic=%d <%.3s>", vpos.state,
- vpos.p.s, vpos.p.o,
- vpos.p.m, typenames[ci.merger[vpos.p.m].type],
- ci.merger[vpos.p.m].al,
- ci.merger[vpos.p.m].bl,
- ci.merger[vpos.p.m].cl,
- ci.merger[vpos.p.m].in_conflict,
- e2
- );
- (void)attrset(A_NORMAL);
- mvaddstr(0, 50, b);
- clrtoeol();
+ pl = npl;
+ }
+ pl[n].file = file;
+ pl[n].start = start;
+ pl[n].end = end;
+ pl[n].last = pl[n].next = pl[n].prev = pl[n].parent = -1;
+ pl[n].chunks = pl[n].wiggles = 0; pl[n].conflicts = 100;
+ pl[n].open = 1;
+ pl[n].calced = 0;
+ *np = n+1;
+ return pl;
+}
+
+struct plist *parse_patch(FILE *f, FILE *of, int *np)
+{
+ /* read a multi-file patch from 'f' and record relevant
+ * details in a plist.
+ * if 'of' >= 0, fd might not be seekable so we write
+ * to 'of' and use lseek on 'of' to determine position
+ */
+ struct plist *plist = NULL;
+
+ while (!feof(f)) {
+ /* first, find the start of a patch: "\n+++ "
+ * grab the file name and scan to the end of a line
+ */
+ char *target="\n+++ ";
+ char *target2="\n--- ";
+ char *pos = target;
+ int c;
+ char name[1024];
+ unsigned start, end;
+
+ while (*pos && (c=fgetc(f)) != EOF ) {
+ if (of) fputc(c, of);
+ if (c == *pos)
+ pos++;
+ else pos = target;
}
- {
- char lbuf[20];
- (void)attrset(A_BOLD);
- sprintf(lbuf, "ln:%d", (pos.p.lineno-1)/2);
- mvaddstr(0, cols - strlen(lbuf) - 4, " ");
- mvaddstr(0, cols - strlen(lbuf) - 1, lbuf);
+ if (c == EOF)
+ break;
+ assert(c == ' ');
+ /* now read a file name */
+ pos = name;
+ while ((c=fgetc(f)) != EOF && c != '\t' && c != '\n' && c != ' ' &&
+ pos - name < 1023) {
+ *pos++ = c;
+ if (of)
+ fputc(c, of);
}
- /* Always refresh the line */
- while (start > target) { start -= 8; refresh = 1;}
- if (start < 0) start = 0;
- retry:
- draw_mline(mode, row, start, cols, fm,fb,fa,ci.merger,
- pos, target, &col);
+ *pos = 0;
+ if (c == EOF)
+ break;
+ if (of) fputc(c, of);
+ while (c != '\n' && (c=fgetc(f)) != EOF)
+ if (of)
+ fputc(c, of);
- if (col > cols+start) {
- start += 8;
- refresh = 1;
- goto retry;
+ start = ftell(of ?: f);
+
+ if (c == EOF)
+ break;
+
+ /* now skip to end - "\n--- " */
+ pos = target2+1;
+
+ while (*pos && (c=fgetc(f)) != EOF) {
+ if (of)
+ fputc(c, of);
+ if (c == *pos)
+ pos++;
+ else
+ pos = target2;
}
- if (col < start) {
- start -= 8;
- refresh = 1;
- if (start < 0) start = 0;
- goto retry;
+ end = ftell(of ?: f);
+ if (pos > target2)
+ end -= (pos - target2) - 1;
+ plist = patch_add_file(plist, np,
+ strdup(name), start, end);
+ }
+ return plist;
+}
+
+static struct stream load_segment(FILE *f,
+ unsigned int start, unsigned int end)
+{
+ struct stream s;
+ s.len = end - start;
+ s.body = malloc(s.len);
+ if (s.body) {
+ fseek(f, start, 0);
+ if (fread(s.body, 1, s.len, f) != s.len) {
+ free(s.body);
+ s.body = NULL;
}
- if (refresh) {
- refresh = 0;
- tpos = pos;
+ } else
+ die();
+ return s;
+}
- for (i=row-1; i>=1 && tpos.p.m >= 0; ) {
- prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
- draw_mline(mode, i--, start, cols, fm,fb,fa,ci.merger,
- tpos, 0, NULL);
- }
- if (i) {
- row -= (i+1);
- refresh = 1;
- goto retry;
- }
- toppos = tpos;
- while (i >= 1)
- blank(i--, 0, cols, a_void);
- tpos = pos;
- for (i=row; i<= lastrow && ci.merger[tpos.p.m].type != End; ) {
- draw_mline(mode, i++, start, cols, fm,fb,fa,ci.merger,
- tpos, 0, NULL);
- next_mline(&tpos, fm,fb,fa,ci.merger, mode);
- }
- botpos = tpos; botrow = i;
- while (i<=lastrow)
- blank(i++, 0, cols, a_void);
- }
-
- if (splitrow >= 0) {
- struct mpos spos = pos;
- int smode = BEFORE|AFTER;
- int srow = (rows + splitrow)/2;
- if (visible(smode, ci.merger[spos.p.m].type,
- spos.p.s) < 0)
- prev_mline(&spos, fm,fb,fa,ci.merger, smode);
- /* Now hi/lo might be wrong, so lets fix it. */
- tpos = spos;
- while (spos.p.m >= 0 && spos.state != 0)
- prev_mline(&spos, fm,fb,fa,ci.merger, smode);
- while (!same_mpos(spos, tpos))
- next_mline(&spos, fm,fb,fa,ci.merger, smode);
-
+int pl_cmp(const void *av, const void *bv)
+{
+ const struct plist *a = av;
+ const struct plist *b = bv;
+ return strcmp(a->file, b->file);
+}
- (void)attrset(a_sep);
- for (i=0; i<cols; i++)
- mvaddstr(splitrow, i, "-");
+int common_depth(char *a, char *b)
+{
+ /* find number of path segments that these two have
+ * in common
+ */
+ int depth = 0;
+ while(1) {
+ char *c;
+ int al, bl;
+ c = strchr(a, '/');
+ if (c) al = c-a; else al = strlen(a);
+ c = strchr(b, '/');
+ if (c) bl = c-b; else bl = strlen(b);
+ if (al == 0 || al != bl || strncmp(a,b,al) != 0)
+ return depth;
+ a+= al;
+ while (*a=='/') a++;
+ b+= bl;
+ while(*b=='/') b++;
- tpos = spos;
- for (i=srow-1; i>splitrow; i-- ) {
- prev_mline(&tpos, fm,fb,fa,ci.merger, smode);
- draw_mline(smode, i, start, cols, fm,fb,fa,ci.merger,
- tpos, 0, NULL);
- }
- while (i > splitrow)
- blank(i--, 0, cols, a_void);
- tpos = spos;
- for (i=srow; i<rows && ci.merger[tpos.p.m].type != End; i++) {
- draw_mline(smode, i, start, cols, fm, fb,fa,ci.merger,
- tpos, 0, NULL);
- next_mline(&tpos, fm,fb,fa,ci.merger, smode);
- }
- }
-#define META(c) ((c)|0x1000)
-#define SEARCH(c) ((c)|0x2000)
- move(rows,0);
- (void)attrset(A_NORMAL);
- if (num>=0) { char buf[10]; sprintf(buf, "%d ", num); addstr(buf);}
- if (meta & META(0)) addstr("ESC...");
- if (meta & SEARCH(0)) {
- if (searchdir) addstr("Backwards ");
- addstr("Search: ");
- addstr(search);
- if (search_notfound)
- addstr(" - Not Found.");
- search_notfound = 0;
- }
- clrtoeol();
- move(row,col-start);
- c = getch();
- tmeta = meta; meta = 0;
- tnum = num; num = -1;
- tvpos = vpos; vpos = pos;
- switch(c | tmeta) {
- case 27: /* escape */
- case META(27):
- meta = META(0);
- break;
- case META('<'): /* start of file */
- start:
- tpos = pos; row++;
- do {
- pos = tpos; row--;
- prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
- } while (tpos.p.m >= 0);
- if (row <= 0)
- row = 0;
- break;
- case META('>'): /* end of file */
- case 'G':
- if (tnum >=0) goto start;
- tpos = pos; row--;
- do {
- pos = tpos; row++;
- next_mline(&tpos, fm,fb,fa,ci.merger,mode);
- } while (ci.merger[tpos.p.m].type != End);
- if (row >= lastrow)
- row = lastrow;
- break;
- case '0' ... '9':
- if (tnum < 0) tnum = 0;
- num = tnum*10 + (c-'0');
- break;
- case 'q':
- return;
+ depth++;
+ }
+}
- case '/':
- case 'S'-64:
- /* incr search forward */
- meta = SEARCH(0);
- searchlen = 0;
- search[searchlen] = 0;
- searchdir = 0;
- break;
- case '\\':
- case 'R'-64:
- /* incr search backwards */
- meta = SEARCH(0);
- searchlen = 0;
- search[searchlen] = 0;
- searchdir = 1;
- break;
- case SEARCH('G'-64):
- case SEARCH('S'-64):
- case SEARCH('R'-64):
- /* search again */
- if ((c|tmeta) == SEARCH('R'-64))
- searchdir = 1;
- if ((c|tmeta) == SEARCH('S'-64))
- searchdir = 0;
- meta = SEARCH(0);
- tpos = pos; trow = row;
- if (searchdir) {
- trow--;
- prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
- } else {
- trow++;
- next_mline(&tpos, fm,fb,fa,ci.merger,mode);
- }
- goto search_again;
+struct plist *add_dir(struct plist *pl, int *np, char *file, char *curr)
+{
+ /* any parent of file that is not a parent of curr
+ * needs to be added to pl
+ */
+ int d = common_depth(file, curr);
+ char *buf = curr;
+ while (d) {
+ char *c = strchr(file, '/');
+ int l;
+ if (c) l = c-file; else l = strlen(file);
+ file += l;
+ curr += l;
+ while (*file == '/') file++;
+ while (*curr == '/') curr++;
+ d--;
+ }
+ while (*file) {
+ if (curr > buf && curr[-1] != '/')
+ *curr++ = '/';
+ while (*file && *file != '/')
+ *curr++ = *file++;
+ while (*file == '/') file++;
+ *curr = '\0';
+ if (*file)
+ pl = patch_add_file(pl, np, strdup(buf),
+ 0, 0);
+ }
+ return pl;
+}
- case SEARCH('H'-64):
- meta = SEARCH(0);
- if (anchor) {
- struct search_anchor *a;
- a = anchor;
- anchor = a->next;
- free(a);
- }
- if (anchor) {
- struct search_anchor *a;
- a = anchor;
- anchor = a->next;
- pos = a->pos;
- row = a->row;
- col = a->col;
- search_notfound = a->notfound;
- searchlen = a->searchlen;
- search[searchlen] = 0;
- free(a);
- refresh = 1;
- }
- break;
- case SEARCH(' ') ... SEARCH('~'):
- case SEARCH('\t'):
- meta = SEARCH(0);
- if (searchlen < sizeof(search)-1)
- search[searchlen++] = c & (0x7f);
- search[searchlen] = 0;
- tpos = pos; trow = row;
- search_again:
- search_notfound = 1;
- do {
- if (mcontains(tpos, fm,fb,fa,ci.merger,mode,search)) {
- pos = tpos;
- row = trow;
- search_notfound = 0;
- break;
- }
- if (searchdir) {
- trow--;
- prev_mline(&tpos, fm,fb,fa,ci.merger,mode);
- } else {
- trow++;
- next_mline(&tpos, fm,fb,fa,ci.merger,mode);
- }
- } while (tpos.p.m >= 0 && ci.merger[tpos.p.m].type != End);
+struct plist *sort_patches(struct plist *pl, int *np)
+{
+ /* sort the patches, add directory names, and re-sort */
+ char curr[1024];
+ char *prev;
+ int parents[100];
+ int prevnode[100];
+ int i, n;
+ qsort(pl, *np, sizeof(struct plist), pl_cmp);
+ curr[0] = 0;
+ n = *np;
+ for (i=0; i<n; i++)
+ pl = add_dir(pl, np, pl[i].file, curr);
- break;
- case 'L'-64:
- refresh = 2;
- row = lastrow / 2;
- break;
+ qsort(pl, *np, sizeof(struct plist), pl_cmp);
- case 'V'-64: /* page down */
- pos = botpos;
- if (botrow <= lastrow)
- row = botrow;
- else
- row = 2;
- refresh = 1;
- break;
- case META('v'): /* page up */
- pos = toppos;
- row = lastrow-1;
- refresh = 1;
- break;
+ /* array is now stable, so set up parent pointers */
+ n = *np;
+ curr[0] = 0;
+ prevnode[0] = -1;
+ prev = "";
+ for (i=0; i<n; i++) {
+ int d = common_depth(prev, pl[i].file);
+ if (d == 0)
+ pl[i].parent = -1;
+ else {
+ pl[i].parent = parents[d-1];
+ pl[pl[i].parent].last = i;
+ }
+ pl[i].prev = prevnode[d];
+ if (pl[i].prev > -1)
+ pl[pl[i].prev].next = i;
+ prev = pl[i].file;
+ parents[d] = i;
+ prevnode[d] = i;
+ prevnode[d+1] = -1;
+ }
+ return pl;
+}
- case 'j':
- case 'n':
- case 'N'-64:
- case KEY_DOWN:
- if (tnum < 0) tnum = 1;
- for (; tnum > 0 ; tnum--) {
- tpos = pos;
- next_mline(&tpos, fm,fb,fa,ci.merger, mode);
- if (ci.merger[tpos.p.m].type != End) {
- pos = tpos;
- row++;
- }
- }
- break;
- case 'N':
- /* Next diff */
- tpos = pos; row--;
- do {
- pos = tpos; row++;
- next_mline(&tpos, fm,fb,fa,ci.merger, mode);
- } while (pos.state != 0 && ci.merger[tpos.p.m].type != End);
- tpos = pos; row--;
- do {
- pos = tpos; row++;
- next_mline(&tpos, fm,fb,fa,ci.merger, mode);
- } while (pos.state == 0 && ci.merger[tpos.p.m].type != End);
+/* determine how much we need to stripe of the front of
+ * paths to find them from current directory. This is
+ * used to guess correct '-p' value.
+ */
+int get_strip(char *file)
+{
+ int fd;
+ int strip = 0;
+
+ while (file && *file) {
+ fd = open(file, O_RDONLY);
+ if (fd >= 0) {
+ close(fd);
+ return strip;
+ }
+ strip++;
+ file = strchr(file, '/');
+ if (file)
+ while(*file == '/')
+ file++;
+ }
+ return -1;
- break;
- case 'P':
- /* Previous diff */
- tpos = pos; row++;
- do {
- pos = tpos; row--;
- prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
- } while (tpos.state == 0 && tpos.p.m >= 0);
- tpos = pos; row++;
- do {
- pos = tpos; row--;
- prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
- } while (tpos.state != 0 && tpos.p.m >= 0);
- break;
+}
- case 'k':
- case 'p':
- case 'P'-64:
- case KEY_UP:
- if (tnum < 0) tnum = 1;
- for (; tnum > 0 ; tnum--) {
- tpos = pos;
- prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
- if (tpos.p.m >= 0) {
- pos = tpos;
- row--;
- }
- }
- break;
+int set_prefix(struct plist *pl, int n, int strip)
+{
+ int i;
+ for(i=0; i<4 && i<n && strip < 0; i++)
+ strip = get_strip(pl[i].file);
- case KEY_LEFT:
- case 'h':
- /* left */
- target = col - 1;
- if (target < 0) target = 0;
- break;
- case KEY_RIGHT:
- case 'l':
- /* right */
- target = col + 1;
- break;
+ if (strip < 0) {
+ fprintf(stderr, "%s: Cannot file files to patch: please specify --strip\n",
+ Cmd);
+ return 0;
+ }
+ for (i=0; i<n; i++) {
+ char *p = pl[i].file;
+ int j;
+ for (j=0; j<strip; j++) {
+ if (p) p = strchr(p,'/');
+ while (p && *p == '/') p++;
+ }
+ if (p == NULL) {
+ fprintf(stderr, "%s: cannot strip %d segments from %s\n",
+ Cmd, strip, pl[i].file);
+ return 0;
+ }
+ pl[i].file = p;
+ }
+ return 1;
+}
- case '^':
- case 'A'-64:
- /* Start of line */
- target = 0;
- break;
- case '$':
- case 'E'-64:
- /* End of line */
- target = 1000;
- break;
- case 'a': /* 'after' view in patch window */
- mode = AFTER; modename = "after";
- refresh = 2;
- break;
- case 'b': /* 'before' view in patch window */
- mode = BEFORE; modename = "before";
- refresh = 2;
- break;
- case 'o': /* 'original' view in the merge window */
- mode = ORIG; modename = "original";
- refresh = 2;
- break;
- case 'r': /* the 'result' view in the merge window */
- mode = RESULT; modename = "result";
- refresh = 2;
- break;
- case 'd':
- mode = BEFORE|AFTER; modename = "diff";
- refresh = 2;
- break;
- case 'm':
- mode = ORIG|RESULT; modename = "merge";
- refresh = 2;
- break;
+int get_prev(int pos, struct plist *pl, int n)
+{
+ if (pos == -1) return pos;
+ if (pl[pos].prev == -1)
+ return pl[pos].parent;
+ pos = pl[pos].prev;
+ while (pl[pos].open &&
+ pl[pos].last >= 0)
+ pos = pl[pos].last;
+ return pos;
+}
- case '|':
- mode = ORIG|RESULT|BEFORE|AFTER; modename = "sidebyside";
- refresh = 1;
- break;
+int get_next(int pos, struct plist *pl, int n)
+{
+ if (pos == -1) return pos;
+ if (pl[pos].open) {
+ if (pos +1 < n)
+ return pos+1;
+ else
+ return -1;
+ }
+ while (pos >= 0 && pl[pos].next == -1)
+ pos = pl[pos].parent;
+ if (pos >= 0)
+ pos = pl[pos].next;
+ return pos;
+}
- case 'H':
- if (start > 0) start--;
- target = start + 1;
- refresh = 1;
- break;
- case 'L':
- if (start < cols) start++;
- target = start + 1;
- refresh = 1;
- break;
+void draw_one(int row, struct plist *pl, FILE *f, int reverse)
+{
+ char hdr[12];
+ hdr[0] = 0;
- case '<':
- prev_melmnt(&tvpos.p, fm,fb,fa,ci.merger);
- if (tvpos.p.m >= 0)
- vpos = tvpos;
- break;
- case '>':
- next_melmnt(&tvpos.p, fm,fb,fa,ci.merger);
- if (ci.merger[tvpos.p.m].type != End)
- vpos = tvpos;
- break;
- }
+ if (pl == NULL) {
+ move(row,0);
+ clrtoeol();
+ return;
+ }
+ if (pl->calced == 0 && pl->end) {
+ /* better load the patch and count the chunks */
+ struct stream s1, s2;
+ struct stream s = load_segment(f, pl->start, pl->end);
+ struct stream sf = load_file(pl->file);
+ if (reverse)
+ pl->chunks = split_patch(s, &s2, &s1);
+ else
+ pl->chunks = split_patch(s, &s1, &s2);
- if (meta == SEARCH(0)) {
- if (anchor == NULL ||
- !same_mpos(anchor->pos, pos) ||
- anchor->searchlen != searchlen ||
- anchor->col != col) {
- struct search_anchor *a = malloc(sizeof(*a));
- a->pos = pos;
- a->row = row;
- a->col = col;
- a->searchlen = searchlen;
- a->notfound = search_notfound;
- a->next = anchor;
- anchor = a;
- }
+ if (sf.body == NULL) {
+ pl->wiggles = pl->conflicts = -1;
} else {
- while (anchor) {
- struct search_anchor *a = anchor;
- anchor = a->next;
- free(a);
- }
+ struct file ff, fp1, fp2;
+ struct csl *csl1, *csl2;
+ struct ci ci;
+ ff = split_stream(sf, ByWord, 0);
+ fp1 = split_stream(s1, ByWord, 0);
+ fp2 = split_stream(s2, ByWord, 0);
+ csl1 = pdiff(ff, fp1, pl->chunks);
+ csl2 = diff(fp1,fp2);
+ ci = make_merger(ff, fp1, fp2, csl1, csl2, 0, 1);
+ pl->wiggles = ci.wiggles;
+ pl->conflicts = ci.conflicts;
+ free(csl1);
+ free(csl2);
+ free(ff.list);
+ free(fp1.list);
+ free(fp2.list);
}
+
+ free(s1.body);
+ free(s2.body);
+ free(s.body);
+ pl->calced = 1;
+ }
+ if (pl->end == 0) {
+ strcpy(hdr, " ");
+ } else {
+ if (pl->chunks > 99)
+ strcpy(hdr, "XX");
+ else sprintf(hdr, "%2d", pl->chunks);
+ if (pl->wiggles > 99)
+ strcpy(hdr+2, " XX");
+ else sprintf(hdr+2, " %2d", pl->wiggles);
+ if (pl->conflicts > 99)
+ strcpy(hdr+5, " XX ");
+ else sprintf(hdr+5, " %2d ", pl->conflicts);
}
+ if (pl->end)
+ strcpy(hdr+9, "= ");
+ else if (pl->open)
+ strcpy(hdr+9, "+ ");
+ else strcpy(hdr+9, "- ");
+
+ mvaddstr(row, 0, hdr);
+ mvaddstr(row, 11, pl->file);
+ clrtoeol();
}
void main_window(struct plist *pl, int n, FILE *f, int reverse)
}
+
+void catch(int sig)
+{
+ if (sig == SIGINT) {
+ signal(sig, catch);
+ return;
+ }
+ nocbreak();nl();endwin();
+ printf("Died on signal %d\n", sig);
+ exit(2);
+}
+
int vpatch(int argc, char *argv[], int strip, int reverse, int replace)
{
/* NOTE argv[0] is first arg... */