]> git.neil.brown.name Git - wiggle.git/commitdiff
Add new patch-browsing mode - needs a lot of work.
authorNeil Brown <neilb@suse.de>
Sun, 21 May 2006 23:46:32 +0000 (09:46 +1000)
committerNeil Brown <neilb@suse.de>
Mon, 22 May 2006 00:35:28 +0000 (10:35 +1000)
(cherry picked from 0b29f4153dbe34b164e2636066c19d2c383e17e4 commit)

ChangeLog [new file with mode: 0644]
Makefile
ReadMe.c
TODO
merge2.c [new file with mode: 0644]
vpatch.c [new file with mode: 0644]
wiggle.c
wiggle.h

diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..eddb566
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,4 @@
+
+       - Don't use --quite in dotest as it is a Debian specific extension
+         to /usr/bin/time
+       
\ No newline at end of file
index 388bf09116f1bad23476629b1996cf3265e621d4..957f3d2e2dc56fcb13d89cddec3f6f21d7b46e20 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -12,12 +12,14 @@ BINDIR  = /usr/bin
 MANDIR  = /usr/share/man
 MAN1DIR = $(MANDIR)/man1
 MAN5DIR = $(MANDIR)/man5
+LDLIBS = -lncurses
 
 all: wiggle wiggle.man test
 
-
-wiggle : wiggle.o load.o split.o extract.o diff.o bestmatch.o ReadMe.o merge.o
-wiggle.o load.o split.o extract.o diff.o bestmatch.o ReadMe.o merge.o : wiggle.h
+wiggle : wiggle.o load.o split.o extract.o diff.o bestmatch.o ReadMe.o \
+              merge.o merge2.o vpatch.o
+wiggle.o load.o split.o extract.o diff.o bestmatch.o ReadMe.o merge.o \
+               merge2.o vpatch.o : wiggle.h
 
 test: wiggle dotest
        sh dotest
@@ -48,3 +50,6 @@ dist : test clean version
 
 v : version
        cat version
+
+demo.patch:
+       diff -ru demo.orig demo.patched | sed 's/demo.patched/demo/' > demo.patch
index 6da1ab5fec78f52c430b73d11897f9c913fe79e6..92ceaa5879087d2a3c6ebf088fdd2939096b53fd 100644 (file)
--- a/ReadMe.c
+++ b/ReadMe.c
 
 char Version[] = "wiggle - v0.6 - 20 May 2003\n";
 
-char short_options[]="xdmwlrh123pVRvq";
+char short_options1[]="xdmwlrh123pVRvqB"; /* not mode B */
+char short_options2[]="xdmwlrh123p:VRvqB"; /* mode B */
+
+       
 struct option long_options[] = {
+       {"browse",      0, 0, 'B'},
        {"extract",     0, 0, 'x'},
        {"diff",        0, 0, 'd'},
        {"merge",       0, 0, 'm'},
@@ -49,17 +53,18 @@ struct option long_options[] = {
        {"reverse",     0, 0, 'R'},
        {"verbose",     0, 0, 'v'},
        {"quiet",       0, 0, 'q'},
+       {"strip",       1, 0, 'p'},
        {0, 0, 0, 0}
 };
 
 char Usage[] =
-"Usage: wiggle --diff|--extract|--merge --lines|--words [--replace] files...\n";
+"Usage: wiggle --diff|--extract|--merge|--browse --lines|--words [--replace] files...\n";
 
 char Help[] =  "\n"
 "Wiggle - apply patches that 'patch' rejects.\n"
 "\n"
 "Wiggle provides three distinct but related functions:\n"
-"merge, diff, and extract.\n"
+"merge, diff, extract, and browse.\n"
 "To get more detailed help on a function, select the function\n"
 "before requesting help.  e.g.\n"
 "    wiggle --diff --help\n"
@@ -68,6 +73,7 @@ char Help[] =  "\n"
 "   --extract   -x    : select 'extract' function.\n"
 "   --diff      -d    : select 'diff' function.\n"
 "   --merge     -m    : select 'merge' function (default).\n"
+"   --browse    -B    : select 'browse' function - default if program is 'vpatch'.\n"
 "\n"
 "   --words     -w    : word-wise diff and merge.\n"
 "   --lines     -l    : line-wise diff and merge.\n"
@@ -83,6 +89,8 @@ char Help[] =  "\n"
 "\n"
 "   --replace   -r    : replace first file with result of merger.\n"
 "\n"
+"   --strip=    -p    : number of path components to strip from file names.\n"
+"\n"
 "Wiggle needs to be given 1, 2, or 3 files.  Any one of these can\n"
 "be given as '-' to signify standard input.\n"
 "\n";
@@ -141,3 +149,13 @@ char HelpMerge[] = "\n"
 "and differences between the second and third are merged into the first.\n"
 "This usage is much like 'merge'.\n"
 "\n";
+
+char HelpBrowse[] = "\n"
+"wiggle --browse [-R] [--strip=n] multi-file-patch\n"
+"vpatch  [-R] [--strip=n] multi-file-patch\n"
+"\n"
+"The 'browse' function provides an interactive mode for browsing a\n"
+"set of patches.  It allows the application of a patch to each file \n"
+"to be inspected and allows limited editting to correct mis-application\n"
+"of patches where wiggling was required, and where conflicts occurred.\n"
+"\n";
diff --git a/TODO b/TODO
index d0f8bd2a72ff1aa66a79b2e4a7c2fb0c12f14ff5..cdd9e66ab7d905b993ef193a0a0897e00bc5c247 100644 (file)
--- a/TODO
+++ b/TODO
 - Design viewer.
    Maybe:
     3 windows: before, patch, after
+
+-----------------------------------
+p - md.c - wait_event_interruptible
+  The preceeding tabs aren't noticed as being the same...
+
+
+-----------------------------------
+31 March 2005
+Some possible targets:
+
+ - check new marge code on all tests
+ - output merge as a diff from original
+ - handle multi-file patchs, producing new patch or updating files
+ - improve diff3 markers
+ - modified
+ - preserve permissions in created file
+ - allow output to have just one stream selected of conflicts.
+ - allow 'output' to include .rej files
+ - fix "produced this was" -> "produced this way" in man page
+
+other things
+ - Read a series of patches and determine dependancies
+   Then push a given patch forward or backward in the list.
+   Possibly full determination of dependancies isn't needed.
+   Just pust the target patch until it hits a wall, then
+   push the wall as far as it goes.
+   A potential 'wall' happens when inserted text is deleted.
+   We refine A -> B -> C and see if it can be broken up with
+   common a->a->a sections and between them,
+    x->x->y or p->q->q
+   There can then become x->y->y and p->p->q
+   But suppose we find x->x->y and p->q->q with no
+   a->a->a in between.  Are we allowed to handle that?
+
+   This is a sentence
+    (is -> was)
+   This was a sentence
+    (a -> my)
+   This was my sentence
+   Commutine the patches give
+   This is a sentence
+    (a -> my)
+   This is my sentence
+    (is -> was)
+   This was my sentence
+
+   That seems safe enough.  How about insertions and deletions?
+    This a sentence 
+     (add is)
+    This is a sentence
+      (remove a)
+    This is sentence 
+
+    This a sentence
+      (remove a)
+    This setence
+      (add is)
+    This is sentence
+
+    Seems ok... Maybe the fact that we have perfect matches (no extraneous stuff)
+    make it easier....
+
+
+    So: first sort the blocks (well, the files) and see if there is any overlap
+    of after-A with before-B.
+    If not, we update offsets.  Maybe store each chunk as offset-from-end-of-last.
+    If so, we extend both blocks, possibly including other blocks, to get two blocks
+    that start and end at the same place.
+    Then run a word-wise merge-like thing.  If there are no conflicts, extract the new
+    intermediate file and create the new diff from that.
+
+    So: each patch is a list of files with hunks
+      the hunks may grow extra context as it is found in other hunks
+       and may even merge.
+     To commute two patches:
+        If a chunk doesn't match any chunk in other file, just retain it.
+        If it does, expand both chunks with common data from the other
+         then run the diff code, then extract the new middle
+
diff --git a/merge2.c b/merge2.c
new file mode 100644 (file)
index 0000000..aec9ef2
--- /dev/null
+++ b/merge2.c
@@ -0,0 +1,433 @@
+/*
+ * wiggle - apply rejected patches
+ *
+ * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
+ *
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *    Author: Neil Brown
+ *    Email: <neilb@cse.unsw.edu.au>
+ *    Paper: Neil Brown
+ *           School of Computer Science and Engineering
+ *           The University of New South Wales
+ *           Sydney, 2052
+ *           Australia
+ */
+
+#include "wiggle.h"
+#include <stdlib.h>
+#include <malloc.h>
+
+/*
+ * Second attempt at merging....
+ *
+ * We want to create a mergelist which identifies 'orig' and 'after'
+ * sections (from a and c) and conflicts (which are ranges of a,b,c which
+ * all don't match).
+ * It is also helpful to differentiate 'orig' sections that aren't
+ * matched in 'b' with orig sections that are.
+ * To help with highlighting, it will be useful to know where
+ * the conflicts match the csl lists.
+ *
+ * This can all be achieved with a list of (a,b,c,c1,c1) 5-tuples.
+ * If two consecutive differ in more than one of a,b,c, it is a 
+ * conflict.
+ * If only 'a' differ, it is un-matched original.
+ * If only 'b' differ, it is matched, unchanged original
+ * If only 'c' differ, it is 1
+ */
+
+static inline int min(int a, int b)
+{
+       return a<b?a:b;
+}
+static inline void assert(int a)
+{
+       if (!a) abort();
+}
+
+int check_alreadyapplied(struct file af, struct file cf,
+                         struct merge *m)
+{
+       int i;
+       if (m->al != m->cl)
+               return 0;
+       for (i=0; i<m->al; i++) {
+               if (af.list[m->a+i].len != cf.list[m->c+i].len)
+                       return 0;
+               if (strncmp(af.list[m->a+i].start,
+                           cf.list[m->c+i].start,
+                           af.list[m->a+i].len)!= 0)
+                       return 0;
+       }
+#if 0
+       printf("already applied %d,%d,%d - %d,%d,%d\n",
+              m->a,m->b,m->c,m->al,m->bl,m->cl);
+       printf(" %.10s - %.10s\n", af.list[m->a].start,
+              cf.list[m->c].start);
+#endif
+       m->type = AlreadyApplied;
+       return 1;
+}
+
+inline int isolate_conflicts(struct file af, struct file bf, struct file cf,
+                             struct csl *csl1, struct csl *csl2, int words,
+                             struct merge *m)
+{
+       /* A conflict indicates that something is definitely wrong
+        * and so we need to be a bit suspicious of nearby apparent matches.
+        * To display a conflict effectively we expands it's effect to 
+        * include any Extraneous, Unmatched or Changed text.
+        * Also, unless 'words', we need to include any partial lines
+        * in the Unchanged text that forms the border of a conflict.
+        *
+        */
+       int i,j,k;
+       int cnt = 0;
+
+       for (i=0; m[i].type != End; i++) 
+               if (m[i].type == Conflict) {
+                       /* We have a conflict here.
+                        * First search backwards for an Unchanged marking
+                        * things as in_conflict.  Then find the 
+                        * cut-point in the Unchanged.  If there isn't one, 
+                        * keep looking. 
+                        *
+                        * Then search forward doing the same thing.
+                        */
+                       cnt++;
+                       m[i].in_conflict = 1;
+                       j = i;
+                       while (--j >= 0) {
+                               if (!m[j].in_conflict) {
+                                       m[j].in_conflict = 1;
+                                       m[j].lo = 0;
+                               }
+                               if (m[j].type == Unchanged || m[j].type == Changed) {
+                                       if (words) {
+                                               m[j].hi = m[j].al;
+                                               break;
+                                       }
+                                       /* need to find the last line-break, which
+                                        * might be after the last newline, if there
+                                        * is one, or might be at the start
+                                        */
+                                       for (k=m[j].al; k>0; k--) 
+                                               if (ends_line(af.list[m[j].a+k-1]))
+                                                       break;
+                                       if (k>0) {
+                                               m[j].hi = k;
+                                               break;
+                                       }
+                                       if ((m[j].a == 0 || ends_line(af.list[m[j].a-1])) &&
+                                           (m[j].b == 0 || ends_line(bf.list[m[j].b-1])) &&
+                                           (m[j].c == 0 || ends_line(cf.list[m[j].c-1]))){
+                                               m[j].hi = 0;
+                                               break;
+                                       }
+                                       /* no start-of-line found... */
+                                       m[j].hi = -1;
+                               }
+                       }
+                       /* now the forward search */
+                       for (j=i+1; m[j].type != End; j++) {
+                               m[j].in_conflict = 1;
+                               if (m[j].type == Unchanged || m[j].type == Changed) {
+                                       m[j].hi = m[j].al;
+                                       if (words) {
+                                               m[j].lo = 0;
+                                               break;
+                                       }
+                                       /* need to find a line-break, which might be at
+                                        * the very beginning, or might be after the
+                                        * first newline - if there is one
+                                        */
+                                       if ((m[j].a == 0 || ends_line(af.list[m[j].a-1])) &&
+                                           (m[j].b == 0 || ends_line(bf.list[m[j].b-1])) &&
+                                           (m[j].c == 0 || ends_line(cf.list[m[j].c-1]))){
+                                               m[j].lo = 0;
+                                               break;
+                                       }
+                                       for (k=0; k<m[j].al; k++)
+                                               if (ends_line(af.list[m[j].a+k]))
+                                                       break;
+                                       if (k<m[j].al) {
+                                               m[j].lo = k+1;
+                                               break;
+                                       }
+                                       /* no start-of-line found */
+                                       m[j].lo = m[j].al+1;
+                               }
+                       }
+                       i = j;
+               }
+       return cnt;
+}
+
+
+struct ci make_merger(struct file af, struct file bf, struct file cf,
+                     struct csl *csl1, struct csl *csl2, int words)
+{
+       /* find the wiggles and conflicts between csl1 and csl2
+        */
+       struct ci rv;
+       int i,l;
+       int a,b,c,c1,c2;
+       int wiggle_found = 0;
+
+       rv.conflicts = rv.wiggles = rv.ignored = 0;
+
+       for (i=0; csl1[i].len; i++);
+       l = i;
+       for (i=0; csl2[i].len; i++);
+       l += i;
+       /* maybe a bit of slack at each end */
+       l = l* 4 + 10;
+
+       rv.merger = malloc(sizeof(struct merge)*l);
+       if (!rv.merger)
+               return rv;
+
+       a=b=c=c1=c2 = 0;
+       i = 0;
+       while (1) {
+               int match1, match2;
+               match1 = (a>=csl1[c1].a && b >= csl1[c1].b); /* c1 doesn't match */
+               match2 = (b>=csl2[c2].a && c >= csl2[c2].b);
+
+               rv.merger[i].a = a;
+               rv.merger[i].b = b;
+               rv.merger[i].c = c;
+               rv.merger[i].c1 = c1;
+               rv.merger[i].c2 = c2;
+               rv.merger[i].in_conflict = 0;
+
+               if (!match1 && match2) {
+                       if (a < csl1[c1].a) {
+                               /* some unmatched text */
+                               rv.merger[i].type = Unmatched;
+                               rv.merger[i].al = csl1[c1].a - a;
+                               rv.merger[i].bl = 0;
+                               rv.merger[i].cl = 0;
+                               wiggle_found ++;
+                       } else {
+                               int newb;
+                               int j;
+                               assert(b<csl1[c1].b);
+                               /* some Extraneous text */
+                               /* length is min of unmatched on left
+                                * and matched on right
+                                */
+                               rv.merger[i].type = Extraneous;
+                               rv.merger[i].al = 0;
+                               rv.merger[i].cl = 
+                                       rv.merger[i].bl =
+                                       min(csl1[c1].b - b,
+                                           csl2[c2].len - (b-csl2[c2].a));
+                               newb = b+rv.merger[i].bl;
+                               for (j=b; j<newb; j++) {
+                                       if (bf.list[j].start[0] == '\0') {
+                                               if (wiggle_found > 1)
+                                                       rv.wiggles++;
+                                               wiggle_found = 0;
+                                       } else
+                                               wiggle_found ++;
+                               }
+                       }
+               } else if (match1 && !match2) {
+                       /* some changed text
+                        * if 'c' is currently at a suitable cut-point, then
+                        * we can look for a triple-cut-point for start.
+                        * Also, if csl2[c2].b isn't in a conflict, and is
+                        * a suitable cut-point, then we could make a 
+                        * triple-cut-point for end of a conflict.
+                        */
+
+                       rv.merger[i].type = Changed;
+                       rv.merger[i].bl = min(csl1[c1].b+csl1[c1].len, csl2[c2].a) - b;
+                       rv.merger[i].al = rv.merger[i].bl;
+                       rv.merger[i].cl = csl2[c2].b - c;
+               } else if (match1 && match2) {
+                       /* Some unchanged text 
+                        */
+                       rv.merger[i].type = Unchanged;
+                       rv.merger[i].bl = 
+                               min(csl1[c1].len - (b-csl1[c1].b),
+                                   csl2[c2].len - (b-csl2[c2].a));
+                       rv.merger[i].al = rv.merger[i].cl =
+                               rv.merger[i].bl;
+               } else {
+                       /* must be a conflict. 
+                        * Move a and c to next match, and b to closest of the two
+                        */
+                       rv.merger[i].type = Conflict;
+                       rv.merger[i].al = csl1[c1].a - a;
+                       rv.merger[i].cl = csl2[c2].b - c;
+                       rv.merger[i].bl = min(csl1[c1].b, csl2[c2].a) - b;
+                       if (check_alreadyapplied(af,cf,&rv.merger[i]))
+                               rv.ignored ++;
+               }
+               a += rv.merger[i].al;
+               b += rv.merger[i].bl;
+               c += rv.merger[i].cl;
+               i++;
+
+               while (csl1[c1].a + csl1[c1].len <= a && csl1[c1].len) c1++;
+               assert(csl1[c1].b + csl1[c1].len >= b);
+               while (csl2[c2].b + csl2[c2].len <= c && csl2[c2].len) c2++;
+               assert(csl2[c2].a + csl2[c2].len >= b);
+               if (csl1[c1].len == 0 && csl2[c2].len == 0 &&
+                   a == csl1[c1].a && b == csl1[c1].b &&
+                   b == csl2[c2].a && c == csl2[c2].b)
+                       break;
+       }
+       rv.merger[i].type = End;
+       rv.merger[i].in_conflict = 0;
+       rv.conflicts = isolate_conflicts(af,bf,cf,csl1,csl2,words,rv.merger);
+       if (wiggle_found)
+               rv.wiggles++;
+       return rv;
+}
+
+void printrange(FILE *out, struct file *f, int start, int len)
+{
+       while (len> 0) {
+               printword(out, f->list[start]);
+               start++;
+               len--;
+       }
+}
+
+struct ci print_merge2(FILE *out, struct file *a, struct file *b, struct file *c,
+                struct csl *c1, struct csl *c2,
+                int words)
+{
+       struct ci rv = make_merger(*a, *b, *c, c1, c2, words);
+       struct merge *m;
+
+       for (m=rv.merger; m->type != End ; m++) {
+               struct merge *cm;
+#if 1
+               static int v= -1;
+               if (v == -1) {
+                       if (getenv("WiggleVerbose"))
+                               v = 1;
+                       else
+                               v = 0;
+               }
+               if (v)
+               printf("[%s: %d-%d,%d-%d,%d-%d%s(%d,%d)]\n",
+                      m->type==Unmatched?"Unmatched":
+                      m->type==Unchanged?"Unchanged":
+                      m->type==Extraneous?"Extraneous":
+                      m->type==Changed?"Changed":
+                      m->type==AlreadyApplied?"AlreadyApplied":
+                      m->type==Conflict?"Conflict":"unknown",
+                      m->a, m->a+m->al-1,
+                      m->b, m->b+m->bl-1,
+                      m->c, m->c+m->cl-1, m->in_conflict?" in_conflict":"",
+                      m->lo, m->hi);
+#endif
+               while (m->in_conflict) {
+                       /* need to print from 'hi' to 'lo' of next
+                        * Unchanged which is < it's hi
+                        */
+                       int st = m->hi;
+
+                       if (m->type == Unchanged)
+                               printrange(out, a, m->a+m->lo, m->hi - m->lo);
+                       else
+                               printrange(out, c, m->c, m->cl);
+
+#if 1
+               if (v)
+                       for (cm=m; cm->in_conflict; cm++) {
+                               printf("{%s: %d-%d,%d-%d,%d-%d%s(%d,%d)}\n",
+                                      cm->type==Unmatched?"Unmatched":
+                                      cm->type==Unchanged?"Unchanged":
+                                      cm->type==Extraneous?"Extraneous":
+                                      cm->type==Changed?"Changed":
+                                      cm->type==AlreadyApplied?"AlreadyApplied":
+                                      cm->type==Conflict?"Conflict":"unknown",
+                                      cm->a, cm->a+cm->al-1,
+                                      cm->b, cm->b+cm->bl-1,
+                                      cm->c, cm->c+cm->cl-1, cm->in_conflict?" in_conflict":"",
+                                      cm->lo, cm->hi);
+                               if (cm->type == Unchanged && cm != m && cm->lo < cm->hi)
+                                       break;
+                       }
+#endif
+
+                       fputs(words?"<<<---":"<<<<<<<\n", out);
+                       for (cm=m; cm->in_conflict; cm++) {
+                               if ((cm->type == Unchanged || cm->type == Changed) && cm != m && cm->lo < cm->hi) {
+                                       printrange(out, a, cm->a, cm->lo);
+                                       break;
+                               }
+                               printrange(out, a, cm->a+st, cm->al-st);
+                               st = 0;
+                       }
+                       fputs(words?"|||":"|||||||\n", out);
+                       st = m->hi;
+                       for (cm=m; cm->in_conflict; cm++) {
+                               if ((cm->type == Unchanged || cm->type == Changed) && cm != m && cm->lo < cm->hi) {
+                                       printrange(out, b, cm->b, cm->lo);
+                                       break;
+                               }
+                               printrange(out, b, cm->b+st, cm->bl-st);
+                               st = 0;
+                       }
+                       fputs(words?"===":"=======\n", out);
+                       st = m->hi;
+                       for (cm=m; cm->in_conflict; cm++) {
+                               if ((cm->type == Unchanged || cm->type == Changed) && cm != m && cm->lo < cm->hi) {
+                                       if (cm->type == Unchanged)
+                                               printrange(out, c, cm->c, cm->lo);
+                                       break;
+                               }
+                               printrange(out, c, cm->c+st, cm->cl-st);
+                               st = 0;
+                       }
+                       fputs(words?"--->>>":">>>>>>>\n", out);
+                       m = cm;
+                       if (m->type == Unchanged && m->in_conflict && m->hi >= m->al) {
+                               printrange(out, a, m->a+m->lo, m->hi-m->lo);
+                               m++;
+                       }
+               }
+
+               /* there is always some non-conflict after a conflict,
+                * unless we hit the end
+                */
+               if (m->type == End)
+                       break;
+               switch(m->type) {
+               case Unchanged:
+               case AlreadyApplied:
+               case Unmatched:
+                       printrange(out, a, m->a, m->al);
+                       break;
+               case Extraneous:
+                       break;
+               case Changed:
+                       printrange(out, c, m->c, m->cl);
+                       break;
+               case Conflict:
+               case End: assert(0);
+               }
+       }
+       return rv;
+}
diff --git a/vpatch.c b/vpatch.c
new file mode 100644 (file)
index 0000000..4fa31cf
--- /dev/null
+++ b/vpatch.c
@@ -0,0 +1,1549 @@
+/*
+ * wiggle - apply rejected patches
+ *
+ * Copyright (C) 2005 Neil Brown <neilb@cse.unsw.edu.au>
+ *
+ *
+ *    This program is free software; you can redistribute it and/or modify
+ *    it under the terms of the GNU General Public License as published by
+ *    the Free Software Foundation; either version 2 of the License, or
+ *    (at your option) any later version.
+ *
+ *    This program is distributed in the hope that it will be useful,
+ *    but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *    GNU General Public License for more details.
+ *
+ *    You should have received a copy of the GNU General Public License
+ *    along with this program; if not, write to the Free Software
+ *    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ *
+ *    Author: Neil Brown
+ *    Email: <neilb@cse.unsw.edu.au>
+ *    Paper: Neil Brown
+ *           School of Computer Science and Engineering
+ *           The University of New South Wales
+ *           Sydney, 2052
+ *           Australia
+ */
+
+/*
+ * vpatch - visual front end for wiggle
+ *
+ * "files" display, lists all files with statistics
+ *    - can hide various lines including subdirectories
+ *      and files without wiggles or conflicts
+ * "diff" display shows merged file with different parts
+ *      in different colours
+ *    - untouched are pale  A_DIM
+ *    - matched/remaining are regular A_NORMAL
+ *    - matched/removed are red/underlined A_UNDERLINE
+ *    - unmatched in file are A_STANDOUT 
+ *    - unmatched in patch are A_STANDOUT|A_UNDERLINE ???
+ *    - inserted are inverse/green ?? A_REVERSE
+ *
+ *  The window can be split horiz or vert and two different
+ *  views displayed.  They will have different parts missing
+ *
+ *  So a display of NORMAL, underline, standout|underline reverse
+ *   should show a normal patch.
+ *
+ */
+
+#include "wiggle.h"
+#include <malloc.h>
+#include <string.h>
+#include <curses.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <fcntl.h>
+
+#define assert(x) do { if (!(x)) abort(); } while (0)
+
+struct plist {
+       char *file;
+       unsigned int start, end;
+       int parent;
+       int next, prev, last;
+       int open;
+       int chunks, wiggles, conflicts;
+       int calced;
+};
+
+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 == '/') file++; /* leading '/' are bad... */
+
+/*     printf("adding %s at %d: %u %u\n", file, n, start, end); */
+       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);
+               }
+               start = of ? ftell(of) : ftell(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;
+               }
+               end = of ? ftell(of) : ftell(f);
+               if (pos > target2)
+                       end -= (pos - target2) - 1;
+               plist = patch_add_file(plist, np,
+                                      strdup(name), start, end);
+       }
+       return plist;
+}
+#if 0
+void die()
+{
+       fprintf(stderr,"vpatch: fatal error\n");
+       abort();
+       exit(3);
+}
+#endif
+
+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;
+               }
+       } else
+               die();
+       return s;
+}
+
+
+void catch(int sig)
+{
+       if (sig == SIGINT) {
+               signal(sig, catch);
+               return;
+       }
+       nocbreak();nl();endwin();
+       printf("Died on signal %d\n", sig);
+       exit(2);
+}
+
+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);
+}
+
+int common_depth(char *a, char *b)
+{
+       /* find number of patch 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++;
+
+               depth++;
+       }
+}
+
+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;
+}
+
+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);
+
+       qsort(pl, *np, sizeof(struct plist), pl_cmp);
+
+       /* 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;
+}
+
+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;
+
+}
+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);
+
+       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;
+}
+
+
+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;
+}
+
+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;
+}
+
+/* global attributes */
+int a_delete, a_added, a_common, a_sep, a_void, a_unmatched, a_extra, a_already;
+
+void draw_one(int row, struct plist *pl, FILE *f, int reverse)
+{
+       char hdr[10];
+       hdr[0] = 0;
+
+       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 (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);
+                       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 addword(struct elmnt e)
+{
+       addnstr(e.start, e.len);
+}
+void addsep(struct elmnt e1, struct elmnt e2)
+{
+       int a,b,c,d,e,f;
+       char buf[50];
+       attron(a_sep);
+       sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
+       sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
+       sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c,e,f);
+       addstr(buf);
+       attroff(a_sep);
+}
+#define BEFORE 1
+#define AFTER  2
+#define        ORIG    4
+#define        RESULT  8
+
+struct pos {
+       int a,b,c;
+};
+struct elmnt prev_elmnt(struct pos *pos, int mode,
+                    struct file f1, struct file f2, struct csl *csl)
+{
+       while(1) {
+               int a1, b1;
+               if (pos->a > csl[pos->c].a) {
+                       assert(pos->b > csl[pos->c].b);
+                       pos->a--;
+                       pos->b--;
+                       return f1.list[pos->a];
+               }
+               b1=0;
+               if (pos->c) b1 = csl[pos->c-1].b+csl[pos->c-1].len;
+               if (pos->b > b1) {
+                       pos->b--;
+                       if (!(mode&AFTER))
+                               continue;
+                       return f2.list[pos->b];
+               }
+               a1=0;
+               if (pos->c) a1 = csl[pos->c-1].a+csl[pos->c-1].len;
+               if (pos->a > a1) {
+                       pos->a--;
+                       if (!(mode&BEFORE))
+                               continue;
+                       return f1.list[pos->a];
+               }
+               /* must be time to go to previous common segment */
+               pos->c--;
+               if (pos->c < 0) {
+                       struct elmnt e;
+                       e.start = NULL; e.len = 0;
+                       return e;
+               }
+       }
+}
+
+struct elmnt next_elmnt(struct pos *pos, int mode, int *type,
+                    struct file f1, struct file f2, struct csl *csl)
+{
+       if (pos->c < 0) {
+               struct elmnt e;
+               e.start = NULL; e.len = 0;
+               return e;
+       }
+       while(1) {
+               int a1;
+               *type = a_delete;
+               if (pos->a < csl[pos->c].a) {
+                       if (mode & BEFORE)
+                               return f1.list[pos->a++];
+                       pos->a++;
+                       continue;
+               }
+               *type = a_added;
+               if (pos->b < csl[pos->c].b) {
+                       if (mode & AFTER)
+                               return f2.list[pos->b++];
+                       pos->b++;
+                       continue;
+               }
+               *type = a_common;
+               a1 = csl[pos->c].a + csl[pos->c].len;
+               if (pos->a < a1) {
+                       pos->b++;
+                       return f1.list[pos->a++];
+               }
+               if (csl[pos->c].len == 0) {
+                       struct elmnt e;
+                       e.start = NULL; e.len = 0;
+                       pos->c = -1;
+                       return e;
+               }
+               pos->c++;
+       }
+}
+
+void pos_sol(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
+{
+       /* find the start-of-line before 'pos' considering 'mode' only.
+        */
+       if (pos->c < 0) return;
+       while(1) {
+               struct pos tpos = *pos;
+               struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
+               if (e.start == NULL) {
+                       return;
+               }
+               if (e.start[0] == '\n' || e.start[0] == 0)
+                       return;
+               *pos = tpos;
+       }
+}
+
+void prev_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
+{
+       /* find the start-of-line before 'pos' considering 'mode' only.
+        */
+       int eol=0;
+       if (pos->c < 0) return;
+       while(1) {
+               struct pos tpos = *pos;
+               struct elmnt e = prev_elmnt(&tpos, mode, f1, f2, csl);
+               if (e.start == NULL) {
+                       pos->c = -1;
+                       return;
+               }
+               if (e.start[0] == '\n' || e.start[0] == 0) {
+                       if (eol == 1)
+                               return;
+                       eol=1;
+               }
+               *pos = tpos;
+               if (e.start[0] == 0) return;
+               
+       }
+}
+
+void next_pos(struct pos *pos, int mode, struct file f1, struct file f2, struct csl *csl)
+{
+       /* find the start-of-line after 'pos' considering 'mode' only.
+        */
+       int type;
+       if (pos->c < 0) return;
+       while(1) {
+               struct pos tpos = *pos;
+               struct elmnt e = next_elmnt(&tpos, mode, &type, f1, f2, csl);
+               if (e.start == NULL) {
+                       pos->c = -1;
+                       return;
+               }
+               *pos = tpos;
+               if (e.start[0] == '\n') {
+                       return;
+               }
+               if (e.start[0] == 0)
+                       return;
+       }
+}
+
+void draw_line(int i, struct pos pos, int mode, 
+              struct file f1, struct file f2, struct csl *csl, int start, int len)
+{
+       int col = 0;
+       int attr;
+       struct elmnt e1;
+       move(i, col);
+       while (1) {
+               e1 = next_elmnt(&pos, mode, &attr, f1, f2, csl);
+               if (e1.start == NULL) {
+                       attr = a_void;
+                       break;
+               }
+               if (e1.start[0] == '\0') {
+                       int a,b,c;
+                       int d,e,f;
+                       struct elmnt e2 = f2.list[pos.b-1];
+                       char buf[50];
+                       attrset(a_sep);
+                       sscanf(e1.start+1, "%d %d %d", &a, &b, &c);
+                       sscanf(e2.start+1, "%d %d %d", &d, &e, &f);
+                       sprintf(buf, "@@ -%d,%d +%d,%d @@\n", b,c, e,f);
+                       addstr(buf);
+                       break;
+               } else {
+                       unsigned char *c;
+                       int l;
+                       attrset(attr);
+                       if (e1.start[0] == '\n') {
+                               break;
+                       }
+                       c = e1.start;
+                       l = e1.len;
+                       while (l) {
+                               if (*c >= ' ' && *c != 0x7f) {
+                                       /* normal width char */
+                                       if (col >= start && col < len+start)
+                                               mvaddch(i,col-start, *c);
+                                       col++;
+                               } else if (*c == '\t') {
+                                       do {
+                                               if (col >= start && col < len+start)
+                                                       mvaddch(i, col-start, ' ');
+                                               col++;
+                                       } while ((col&7)!= 0);
+                               } else {
+                                       if (col>=start && col < len+start)
+                                               mvaddch(i, col-start, *c=='\n'?'@':'?');
+                                       col++;
+                               }
+                               c++;
+                               l--;
+                       }
+               }
+       }
+       bkgdset(attr);
+       clrtoeol();
+       bkgdset(A_NORMAL);
+}
+
+void diff_window(struct plist *p, FILE *f)
+{
+       /* display just the diff, either 'before' or 'after' or all.
+        *
+        * In any case highlighting is the same
+        *
+        * Current pos is given as a,b,csl where a and b are
+        * before or in the common segment, and one is  immediately
+        * after a newline.
+        * The current row is given by 'row'.
+        * Lines do not wrap, be horizontal scrolling is supported.
+        *
+        * We don't insist that the top line lies at or above the top
+        * of the screen, as that allows alignment of different views
+        * more easily.
+        */
+       struct stream s;
+       struct stream  s1, s2;
+       struct file f1, f2;
+       struct csl *csl;
+       char buf[100];
+       int ch;
+       int row, rows, cols;
+       int start = 0;
+       int i;
+       int c;
+       int refresh = 2;
+       int mode = BEFORE|AFTER;
+
+       struct pos pos, tpos;
+
+       s = load_segment(f, p->start, p->end);
+       ch = split_patch(s, &s1, &s2);
+
+
+       f1 = split_stream(s1, ByWord, 0);
+       f2 = split_stream(s2, ByWord, 0);
+
+       csl = diff(f1, f2);
+
+       pos.a=0; pos.b=0; pos.c=0;
+       row = 1;
+
+       while(1) {
+               if (refresh == 2) {
+                       clear();
+                       sprintf(buf, "File: %s\n", p->file);
+                       attrset(A_BOLD); mvaddstr(0,0,buf); clrtoeol(); attrset(A_NORMAL);
+                       refresh = 1;
+               }
+               if (row < 1 || row >= rows)
+                       refresh = 1;
+               if (refresh) {
+                       refresh = 0;
+                       getmaxyx(stdscr,rows,cols);
+                       if (row < -3)
+                               row = (rows+1)/2;
+                       if (row < 1)
+                               row = 1;
+                       if (row >= rows+3)
+                               row = (rows+1)/2;
+                       if (row >= rows)
+                               row = rows-1;
+                       tpos = pos;
+                       pos_sol(&tpos, mode, f1, f2, csl);
+                       draw_line(row, tpos, mode, f1, f2, csl, start, cols);
+                       for (i=row-1; i>=1; i--) {
+                               prev_pos(&tpos, mode, f1,f2,csl);
+                               draw_line(i, tpos, mode, f1, f2,csl, start,cols);
+                       }
+                       tpos = pos;
+                       for (i=row+1; i<rows; i++) {
+                               next_pos(&tpos, mode, f1, f2, csl);
+                               draw_line(i, tpos, mode, f1, f2, csl, start,cols);
+                       }
+               }
+               move(row,0);
+               c = getch();
+               switch(c) {
+               case 27:
+               case 'q':
+                       return;
+               case 'L'-64:
+                       refresh = 2;
+                       break;
+
+               case 'j':
+               case 'n':
+               case 'N':
+               case 'N'-64:
+               case KEY_DOWN:
+                       tpos = pos;
+                       next_pos(&tpos, mode, f1, f2, csl);
+                       if (tpos.c >= 0) {
+                               pos = tpos;
+                               row++;
+                       }
+                       break;
+               case 'k':
+               case 'p':
+               case 'P':
+               case 'P'-64:
+               case KEY_UP:
+                       tpos = pos;
+                       prev_pos(&tpos, mode, f1, f2, csl);
+                       if (tpos.c >= 0) {
+                               pos = tpos;
+                               row--;
+                       }
+                       break;
+
+               case 'a':
+                       mode = AFTER;
+                       refresh = 1;
+                       break;
+               case 'b':
+                       mode = BEFORE;
+                       refresh = 1;
+                       break;
+               case 'x':
+                       mode = AFTER|BEFORE;
+                       refresh = 1;
+                       break;
+
+               case 'h':
+               case KEY_LEFT:
+                       if (start > 0) start--;
+                       refresh = 1;
+                       break;
+               case 'l':
+               case KEY_RIGHT:
+                       if (start < cols) start++;
+                       refresh = 1;
+                       break;
+               }
+       }
+}
+
+struct mpos {
+       int m; /* merger index */
+       int s; /* stream 0,1,2 for a,b,c */
+       int o; /* offset in that stream */
+};
+
+int invalid(int s, enum mergetype type)
+{
+       switch(type) {
+       case End: return 0;
+       case Unmatched: return s>0;
+       case Unchanged: return s>0;
+       case Extraneous: return s<2;
+       case Changed: return s==1;
+       case Conflict: return 0;
+       case AlreadyApplied: return 0;
+       }
+       return 0;
+}
+
+struct elmnt next_melmnt(struct mpos *pos,
+                        struct file fm, struct file fb, struct file fa,
+                        struct merge *m)
+{
+       int l;
+       pos->o++;
+       while(1) {
+               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) {
+                       pos->o = 0;
+                       do {
+                               pos->s++;
+                               if (pos->s > 2) {
+                                       pos->s = 0;
+                                       pos->m++;
+                               }
+                       } while (invalid(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: 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];
+       }
+}
+
+struct elmnt prev_melmnt(struct mpos *pos,
+                        struct file fm, struct file fb, struct file fa,
+                        struct merge *m)
+{
+       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 && invalid(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 visible(int mode, enum mergetype type, int stream)
+{
+#if 0
+       switch(side){
+       case 0: /* left - orig plus merge */
+               switch(type) {
+               case End: return A_NORMAL;
+               case Unmatched: return stream == 0 ? a_unmatched : -1;
+               case Unchanged: return stream == 0 ? a_common : -1;
+               case Extraneous: return -1;
+               case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
+               case Conflict: return stream == 0 ? a_unmatched : -1;
+               case AlreadyApplied: return stream == 0 ? a_unmatched : -1;
+               }
+               break;
+       case 1: /* right - old plus new */
+               switch(type) {
+               case End: return A_NORMAL;
+               case Unmatched: return -1;
+               case Unchanged: return stream == 0 ? a_common : -1;
+               case Extraneous: return stream==2 ? a_extra : -1;
+               case Changed: return stream == 0? a_delete:stream==1?-1:a_added;
+               case Conflict: return stream==0?-1:stream==1?(a_delete|A_UNDERLINE):a_added;
+               case AlreadyApplied: return stream==0?-1:stream==1?a_delete:a_added;
+               }
+       }
+       return 1;
+#endif
+       /* 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|RESULT))
+                               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;
+}
+
+
+
+void next_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
+             struct merge *m, int mode)
+{
+       if (pos->m < 0 || m[pos->m].type == End)
+               return;
+       while (1) {
+               struct elmnt e = next_melmnt(pos, fm,fb,fa,m);
+               if (e.start == NULL)
+                       return;
+               if (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0)
+                       return;
+       }
+}
+
+void prev_mline(struct mpos *pos, struct file fm, struct file fb, struct file fa,
+               struct merge *m, int mode)
+{
+       if (pos->m < 0)
+               return;
+       while(1) {
+               struct elmnt e = prev_melmnt(pos, fm,fb,fa,m);
+               if (e.start == NULL || 
+                   (ends_mline(e) && visible(mode, m[pos->m].type, pos->s) >= 0))
+                       return;
+       }
+}
+
+
+void blank(int row, int start, int cols, int attr)
+{
+       attrset(attr);
+       move(row,start);
+       while (cols-- > 0)
+               addch(' ');
+}
+
+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)
+{
+       /* find previous visible newline, or start of file */
+       struct elmnt e;
+       int col = 0;
+       do {
+               e = prev_melmnt(&pos, fm,fb,fa,m);
+       } while (e.start != NULL &&
+                (!ends_mline(e) || visible(mode, m[pos.m].type, pos.s)==-1));
+
+       while (1) {
+               unsigned char *c;
+               int l;
+               e = next_melmnt(&pos, fm,fb,fa,m);
+               if (e.start == NULL || 
+                   (ends_mline(e) && visible(mode, m[pos.m].type, pos.s) != -1)) {
+                       if (col < start) col = start;
+                       if (e.start && e.start[0] == 0) {
+                               attrset(visible(mode, m[pos.m].type, pos.s));
+                               mvaddstr(row, col-start+offset, "SEP");
+                               col += 3;
+                       }
+                       blank(row, col-start+offset, start+cols-col, e.start?visible(mode, m[pos.m].type, pos.s):A_NORMAL );
+                       return;
+               }
+               if (visible(mode, m[pos.m].type, pos.s) == -1) {
+                       continue;
+               }
+               if (e.start[0] == 0)
+                       continue;
+               attrset(visible(mode, m[pos.m].type, pos.s));
+               c = 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++;
+                       l--;
+               }
+       }
+}
+
+void draw_mline(int row, struct mpos pos, 
+               struct file fm, struct file fb, struct file fa,
+               struct merge *m, 
+               int start, int cols, int mode)
+{
+       /*
+        * 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;
+       lcols = (cols-1)/2;
+       rcols = cols - lcols - 1;
+
+       attrset(A_STANDOUT);
+       mvaddch(row, lcols, '|');
+       if (visible(mode&(ORIG|RESULT), m[pos.m].type, pos.s) >= 0)
+               /* visible on left */
+               draw_mside(mode&(ORIG|RESULT), row, 0, start, lcols, 
+                          fm,fb,fa,m, pos);
+       else
+               blank(row, 0, lcols, a_void);
+
+       if (visible(mode&(BEFORE|AFTER), m[pos.m].type, pos.s) >= 0)
+               /* visible on right */
+               draw_mside(mode&(BEFORE|AFTER), row, lcols+1, start, rcols,
+                          fm,fb,fa,m, pos);
+       else
+               blank(row, lcols+1, rcols, a_void);
+}
+
+extern void cleanlist(struct file a, struct file b, struct csl *list);
+
+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 one, we
+        * rewind the previous newline visible on this side, and
+        * the display the stuff inbetween
+        *
+        * A 'position' is an offset in the merger, a stream
+        * choice (a,b,c - some aren't relevant) and an offset in
+        * that stream
+        */
+
+       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,cols;
+       int i, c;
+       int mode = ORIG|RESULT | BEFORE|AFTER;
+
+       int row,start = 0;
+       struct mpos pos, tpos;
+
+       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);
+       cleanlist(fm, fb, csl1);
+       cleanlist(fb, fa, csl2);
+
+       ci = make_merger(fm, fb, fa, csl1, csl2, 0);
+
+       row = 1;
+       pos.m = 0; /* merge node */
+       pos.s = 0; /* stream number */
+       pos.o = -1; /* offset */
+       next_mline(&pos, fm,fb,fa,ci.merger, mode);
+
+       while(1) {
+               if (refresh == 2) {
+                       clear();
+                       sprintf(buf, "File: %s%s\n", p->file,reverse?" - reversed":"");
+                       attrset(A_BOLD); mvaddstr(0,0,buf);
+                       clrtoeol();
+                       attrset(A_NORMAL);
+                       refresh = 1;
+               }
+               if (row < 1 || rows >= rows)
+                       refresh = 1;
+               if (refresh) {
+                       refresh = 0;
+                       getmaxyx(stdscr, rows, cols);
+                       if (row < -3)
+                               row = (rows-1)/2+1;
+                       if (row < 1)
+                               row = 1;
+                       if (row > rows+3)
+                               row = (rows-1)/2+1;
+                       if (row >= rows)
+                               row = rows-1;
+                       tpos = pos;
+                       draw_mline(row,tpos,fm,fb,fa,ci.merger,start,cols, mode);
+                       for (i=row-1; i>=1; i--) {
+                               prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+                               draw_mline(i,tpos,fm,fb,fa,ci.merger,start,cols, mode);
+                       }
+                       tpos = pos;
+                       for (i=row+1; i<rows; i++) {
+                               next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+                               draw_mline(i,tpos,fm,fb,fa,ci.merger,start,cols, mode);
+                       }
+               }
+               move(row,0);
+               c = getch();
+               switch(c) {
+               case 27:
+               case 'q':
+                       return;
+               case 'L'-64:
+                       refresh = 2;
+                       break;
+
+
+               case 'j':
+               case 'n':
+               case 'N'-64:
+               case KEY_DOWN:
+                       tpos = pos;
+                       next_mline(&tpos, fm,fb,fa,ci.merger, mode);
+                       if (ci.merger[tpos.m].type != End) {
+                               pos = tpos;
+                               row++;
+                       }
+                       break;
+               case 'N':
+                       /* Next 'patch' */
+                       while (pos.m >= 0 && ci.merger[pos.m].type == Unmatched)
+                               next_mline(&pos,fm,fb,fa,ci.merger, mode);
+                       row = -10;
+                       break;
+               case 'k':
+               case 'p':
+               case 'P':
+               case 'P'-64:
+               case KEY_UP:
+                       tpos = pos;
+                       prev_mline(&tpos, fm,fb,fa,ci.merger, mode);
+                       if (tpos.m >= 0) {
+                               pos = tpos;
+                               row--;
+                       }
+                       break;
+
+               case 'a': /* 'after' view in patch window */
+                       if (mode & AFTER)
+                               mode &= ~BEFORE;
+                       else
+                               mode |= AFTER;
+                       refresh = 1;
+                       break;
+               case 'b': /* 'before' view in patch window */
+                       if (mode & BEFORE)
+                               mode &= ~AFTER;
+                       else
+                               mode |= BEFORE;
+                       refresh = 1;
+                       break;
+               case 'o': /* 'original' view in the merge window */
+                       if (mode & ORIG)
+                               mode &= ~RESULT;
+                       else
+                               mode |= ORIG;
+                       refresh = 1;
+                       break;
+               case 'r': /* the 'result' view in the merge window */
+                       if (mode & RESULT)
+                               mode &= ~ORIG;
+                       else
+                               mode |= RESULT;
+                       refresh = 1;
+                       break;
+
+               case 'h':
+               case KEY_LEFT:
+                       if (start > 0) start--;
+                       refresh = 1;
+                       break;
+               case 'l':
+               case KEY_RIGHT:
+                       if (start < cols) start++;
+                       refresh = 1;
+                       break;
+               }
+       }
+}
+
+void main_window(struct plist *pl, int n, FILE *f, int reverse)
+{
+       /* The main window lists all files together with summary information:
+        * number of chunks, number of wiggles, number of conflicts.
+        * The list is scrollable
+        * When a entry is 'selected', we switch to the 'file' window
+        * The list can be condensed by removing files with no conflict
+        * or no wiggles, or removing subdirectories
+        *
+        * We record which file in the list is 'current', and which
+        * screen line it is on.  We try to keep things stable while
+        * moving.
+        *
+        * Counts are printed before the name using at most 2 digits. 
+        * Numbers greater than 99 are XX
+        * Ch Wi Co File
+        * 27 5   1 drivers/md/md.c
+        *
+        * A directory show the sum in all children.
+        *
+        * Commands:
+        *  select:  enter, space, mouseclick
+        *      on file, go to file window
+        *      on directory, toggle open
+        *  up:  k, p, control-p uparrow
+        *      Move to previous open object
+        *  down: j, n, control-n, downarrow
+        *      Move to next open object
+        *  
+        */
+       int pos=0; /* position in file */
+       int row=1; /* position on screen */
+       int rows; /* size of screen in rows */
+       int cols;
+       int tpos, i;
+       int refresh = 2;
+       int c=0;
+
+       while(1) {
+               if (refresh == 2) {
+                       clear(); attrset(0);
+                       attron(A_BOLD);
+                       mvaddstr(0,0,"Ch Wi Co Patched Files");
+                       move(2,0);
+                       attroff(A_BOLD);
+                       refresh = 1;
+               }
+               if (row <1  || row >= rows)
+                       refresh = 1;
+               if (refresh) {
+                       refresh = 0;
+                       getmaxyx(stdscr, rows, cols);
+                       if (row >= rows +3)
+                               row = (rows+1)/2;
+                       if (row >= rows)
+                               row = rows-1;
+                       tpos = pos;
+                       for (i=row; i>1; i--) {
+                               tpos = get_prev(tpos, pl, n);
+                               if (tpos == -1) {
+                                       row = row - i + 1;
+                                       break;
+                               }
+                       }
+                       /* Ok, row and pos could be trustworthy now */
+                       tpos = pos;
+                       for (i=row; i>=1; i--) {
+                               draw_one(i, &pl[tpos], f, reverse);
+                               tpos = get_prev(tpos, pl, n);
+                       }
+                       tpos = pos;
+                       for (i=row+1; i<rows; i++) {
+                               tpos = get_next(tpos, pl, n);
+                               if (tpos >= 0)
+                                       draw_one(i, &pl[tpos], f, reverse);
+                               else
+                                       draw_one(i, NULL, f, reverse);
+                       }
+               }
+               {char bb[20]; sprintf(bb,"%d", c); mvaddstr(0, 70, bb); clrtoeol();}
+               move(row, 9);
+               c = getch();
+               switch(c) {
+               case 'j':
+               case 'n':
+               case 'N':
+               case 'N'-64:
+               case KEY_DOWN:
+                       tpos = get_next(pos, pl, n);
+                       if (tpos >= 0) {
+                               pos = tpos;
+                               row++;
+                       }
+                       break;
+               case 'k':
+               case 'p':
+               case 'P':
+               case 'P'-64:
+               case KEY_UP:
+                       tpos = get_prev(pos, pl, n);
+                       if (tpos >= 0) {
+                               pos = tpos;
+                               row--;
+                       }
+                       break;
+
+               case ' ':
+               case 13:
+                       if (pl[pos].end == 0) {
+                               pl[pos].open = ! pl[pos].open;
+                               refresh = 1;
+                       } else {
+                               /* diff_window(&pl[pos], f); */
+                               merge_window(&pl[pos],f,reverse);
+                               refresh = 2;
+                       }
+                       break;
+               case 27: /* escape */
+               case 'q':
+                       return;
+
+               case KEY_RESIZE:
+                       refresh = 2;
+                       break;
+               }
+       }
+}
+
+
+int vpatch(int argc, char *argv[], int strip, int reverse, int replace)
+{
+       /* NOT argv[0] is first arg... */
+       int n = 0;
+       FILE *f = NULL;
+       FILE *in = stdin;
+       struct plist *pl;
+
+       if (argc >=1) {
+               in = fopen(argv[0], "r");
+               if (!in) {
+                       printf("No %s\n", argv[0]);
+                       exit(1);
+               }
+       } else {
+               in = stdin;
+       }
+       if (lseek(fileno(in),0L, 1) == -1) {
+               f = tmpfile();
+               if (!f) {
+                       fprintf(stderr, "Cannot create a temp file\n");
+                       exit(1);
+               }
+       }
+
+       pl = parse_patch(in, f, &n);
+
+       if (set_prefix(pl, n, strip) == 0) {
+               fprintf(stderr, "%s: aborting\n", Cmd);
+               exit(2);
+       }
+       pl = sort_patches(pl, &n);
+
+       if (f) {
+               if (fileno(in) == 0) {
+                       close(0);
+                       dup(2);
+               } else
+                       fclose(in);
+               in = f;
+       }
+#if 0
+       int i;
+       for (i=0; i<n ; i++) {
+               printf("%3d: %3d %2d/%2d %s\n", i, pl[i].parent, pl[i].prev, pl[i].next, pl[i].file);
+       }
+       exit(0);
+#endif
+       signal(SIGINT, catch);
+       signal(SIGQUIT, catch);
+       signal(SIGTERM, catch);
+       signal(SIGBUS, catch);
+       signal(SIGSEGV, catch);
+
+       initscr(); cbreak(); noecho();
+       start_color();
+       use_default_colors();
+       if (!has_colors()) {
+               a_delete = A_UNDERLINE;
+               a_added = A_BOLD;
+               a_common = A_NORMAL;
+               a_sep = A_STANDOUT;
+               a_already = A_STANDOUT;
+       } else {
+               init_pair(1, COLOR_WHITE, COLOR_RED);
+               a_delete = COLOR_PAIR(1);
+               init_pair(2, COLOR_WHITE, COLOR_BLUE);
+               a_added = COLOR_PAIR(2);
+               a_common = A_NORMAL;
+               init_pair(3, COLOR_WHITE, COLOR_GREEN);
+               a_sep = COLOR_PAIR(3);
+               init_pair(4, COLOR_WHITE, COLOR_YELLOW);
+               a_void = COLOR_PAIR(4);
+               init_pair(5, COLOR_BLUE, COLOR_WHITE);
+               a_unmatched = COLOR_PAIR(5);
+               init_pair(6, COLOR_CYAN, COLOR_WHITE);
+               a_extra = COLOR_PAIR(6);
+
+               init_pair(7, COLOR_BLACK, COLOR_CYAN);
+               a_already = COLOR_PAIR(7);
+       }
+       nonl(); intrflush(stdscr, FALSE); keypad(stdscr, TRUE);
+       mousemask(ALL_MOUSE_EVENTS, NULL);
+
+       main_window(pl, n, in, reverse);
+
+       nocbreak();nl();endwin();
+       return 0;
+       exit(0);
+}
+#if 0
+ WiggleVerbose=1 ~/work/wiggle/wiggle -mR fs/nfsd/nfs4callback.c .patches/removed/144NfsdV4-033 |less
+neilb@notabene:/home/src/mm$ 
+
+~/work/wiggle/wiggle -BR .patches/removed/144NfsdV4-033 
+
+
+#endif
index 2bbb90fac5c77aae57ec72f8670dbc8f29eabd57..b2e019e0026ab644eff9a559d1f53f9712d35ad7 100644 (file)
--- a/wiggle.c
+++ b/wiggle.c
@@ -85,6 +85,8 @@
 #include       <stdlib.h>
 #include       <ctype.h>
 
+char *Cmd = "wiggle";
+
 void die()
 {
        fprintf(stderr,"wiggle: fatal error\n");
@@ -152,17 +154,26 @@ int main(int argc, char *argv[])
        int reverse = 0;
        int verbose=0, quiet=0;
        int i;
+       int strip = -1;
        int chunks1=0, chunks2=0, chunks3=0;
        int exit_status = 0;
        FILE *outfile = stdout;
        char *helpmsg;
+       char *base0;
 
        struct stream f, flist[3];
        struct file fl[3];
        struct csl *csl1, *csl2;
 
+       base0 = strrchr(argv[0], '/');
+       if (base0) base0++; else base0=argv[0];
+       if (strcmp(base0, "vpatch")==0) {
+               Cmd = base0;
+               mode = 'B';
+       }
+
        while ((opt = getopt_long(argc, argv,
-                                 short_options, long_options,
+                                 short_options(mode), long_options,
                                  &option_index)) != -1)
                switch(opt) {
                case 'h':
@@ -171,6 +182,7 @@ int main(int argc, char *argv[])
                        case 'x': helpmsg = HelpExtract; break;
                        case 'd': helpmsg = HelpDiff; break;
                        case 'm': helpmsg = HelpMerge; break;
+                       case 'B': helpmsg = HelpBrowse; break;
                        }
                        fputs(helpmsg, stderr);
                        exit(0);
@@ -184,6 +196,7 @@ int main(int argc, char *argv[])
                        fputs(Usage, stderr);
                        exit(2);
 
+               case 'B':
                case 'x':
                case 'd':
                case 'm':
@@ -222,7 +235,13 @@ int main(int argc, char *argv[])
                        exit(2);
 
                case 'p':
-                       ispatch = 1;
+                       if (mode == 'B')
+                               strip = atol(optarg?optarg:"0");
+                       else if (optarg) {
+                               fprintf(stderr, "wiggle: SORRY, PARSE ERROR\n");
+                               exit(2);
+                       } else
+                               ispatch = 1;
                        continue;
 
                case 'v': verbose++; continue;
@@ -231,6 +250,12 @@ int main(int argc, char *argv[])
        if (!mode)
                mode = 'm';
 
+       if (mode == 'B') {
+               vpatch(argc-optind, argv+optind, strip, reverse, replace);
+               /* should not return */
+               exit(1);
+       }
+
        if (obj && mode == 'x') {
                fprintf(stderr,"wiggle: cannot specify --line or --word with --extract\n");
                exit(2);
@@ -618,7 +643,7 @@ int main(int argc, char *argv[])
                {
                        struct ci ci;
 
-                       ci = print_merge(outfile, &fl[0], &fl[1], &fl[2], 
+                       ci = print_merge2(outfile, &fl[0], &fl[1], &fl[2], 
                                                   csl1, csl2, obj=='w');
                        if (!quiet && ci.conflicts)
                                fprintf(stderr, "%d unresolved conflict%s found\n", ci.conflicts, ci.conflicts==1?"":"s");
index 124be296d7dc80382bb78bd73219163150809de8..9cb2155985214877ef2a2122573c10f6de4cb811 100644 (file)
--- a/wiggle.h
+++ b/wiggle.h
@@ -56,6 +56,11 @@ static inline int ends_line(struct elmnt e)
        return e.len &&  e.start[e.len-1] == '\n';
 }
 
+static inline int ends_mline(struct elmnt e)
+{
+       return e.len &&  e.start[0] == '\n';
+}
+
 struct csl {
        int a,b;
        int len;
@@ -66,6 +71,14 @@ struct file {
        int elcnt;
 };
 
+struct merge {
+       enum mergetype {End, Unmatched, Unchanged, Extraneous, Changed, Conflict, AlreadyApplied} type;
+       int a,b,c; /* start of ranges */
+       int al, bl, cl; /* length of ranges */
+       int c1, c2; /* this or next commonsequence */
+       int in_conflict; 
+       int lo,hi; /* region of an Unchanged that is not involved in a conflict */
+};
 extern struct stream load_file(char *name);
 extern int split_patch(struct stream, struct stream*, struct stream*);
 extern int split_merge(struct stream, struct stream*, struct stream*, struct stream*);
@@ -77,22 +90,39 @@ extern struct csl *diff_partial(struct file a, struct file b,
 extern struct csl *worddiff(struct stream f1, struct stream f2,
                            struct file *fl1p, struct file *fl2p);
 
-struct ci { int conflicts; int ignored; };
+struct ci { 
+       int conflicts, wiggles, ignored; 
+       struct merge *merger;
+};
 extern struct ci print_merge(FILE *out, struct file *a, struct file *b, struct file *c,
                       struct csl *c1, struct csl *c2,
                       int words);
+extern struct ci print_merge2(FILE *out, struct file *a, struct file *b, struct file *c,
+                      struct csl *c1, struct csl *c2,
+                      int words);
 extern void printword(FILE *f, struct elmnt e);
 
+extern struct ci make_merger(struct file a, struct file b, struct file c,
+                              struct csl *c1, struct csl *c2, int words);
+
 extern void die(void);
 
+extern int vpatch(int argc, char *argv[], int strip, int reverse, int replace);
+
+extern char *Cmd;
 extern char Version[];
-extern char short_options[];
+extern char short_options1[], short_options2[];
+static inline char *short_options(char mode)
+{
+       return mode == 'B' ? short_options2 : short_options1;
+}
 extern struct option long_options[];
 extern char Usage[];
 extern char Help[];
 extern char HelpExtract[];
 extern char HelpDiff[];
 extern char HelpMerge[];
+extern char HelpBrowse[];
 
 
 #define        ByLine  0