]> granicus.if.org Git - vim/commitdiff
patch 8.1.0360: using an external diff program is slow and inflexible v8.1.0360
authorBram Moolenaar <Bram@vim.org>
Mon, 10 Sep 2018 15:51:58 +0000 (17:51 +0200)
committerBram Moolenaar <Bram@vim.org>
Mon, 10 Sep 2018 15:51:58 +0000 (17:51 +0200)
Problem:    Using an external diff program is slow and inflexible.
Solution:   Include the xdiff library. (Christian Brabandt, closes #2732)
            Use it by default.

42 files changed:
Filelist
runtime/doc/diff.txt
runtime/doc/options.txt
src/Make_cyg_ming.mak
src/Make_mvc.mak
src/Makefile
src/diff.c
src/structs.h
src/testdir/dumps/Test_diff_01.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_02.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_03.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_04.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_05.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_06.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_07.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_08.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_09.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_10.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_11.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_12.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_13.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_14.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_15.dump [new file with mode: 0644]
src/testdir/dumps/Test_diff_16.dump [new file with mode: 0644]
src/testdir/test_diffmode.vim
src/version.c
src/xdiff/COPYING [new file with mode: 0644]
src/xdiff/README.txt [new file with mode: 0644]
src/xdiff/xdiff.h [new file with mode: 0644]
src/xdiff/xdiffi.c [new file with mode: 0644]
src/xdiff/xdiffi.h [new file with mode: 0644]
src/xdiff/xemit.c [new file with mode: 0644]
src/xdiff/xemit.h [new file with mode: 0644]
src/xdiff/xhistogram.c [new file with mode: 0644]
src/xdiff/xinclude.h [new file with mode: 0644]
src/xdiff/xmacros.h [new file with mode: 0644]
src/xdiff/xpatience.c [new file with mode: 0644]
src/xdiff/xprepare.c [new file with mode: 0644]
src/xdiff/xprepare.h [new file with mode: 0644]
src/xdiff/xtypes.h [new file with mode: 0644]
src/xdiff/xutils.c [new file with mode: 0644]
src/xdiff/xutils.h [new file with mode: 0644]

index f488839740943e9f0a5e267e2cda5bdf5564ffbd..343a2bd5c4679ce73a1583a4d9f077b3a45af795 100644 (file)
--- a/Filelist
+++ b/Filelist
@@ -273,6 +273,20 @@ SRC_ALL =  \
                src/libvterm/t/92lp1640917.test \
                src/libvterm/t/harness.c \
                src/libvterm/t/run-test.pl \
+               src/xdiff/xdiff.h \
+               src/xdiff/xdiffi.c \
+               src/xdiff/xdiffi.h \
+               src/xdiff/xemit.c \
+               src/xdiff/xemit.h \
+               src/xdiff/xhistogram.c \
+               src/xdiff/xinclude.h \
+               src/xdiff/xmacros.h \
+               src/xdiff/xpatience.c \
+               src/xdiff/xprepare.c \
+               src/xdiff/xprepare.h \
+               src/xdiff/xtypes.h \
+               src/xdiff/xutils.c \
+               src/xdiff/xutils.h \
 
 
 # source files for Unix only
index d56aea72ad75d90c26168410fbed85612f1d5fc6..6670f45c6401e98d57d09a671979bf74a43b06d0 100644 (file)
@@ -39,7 +39,9 @@ The second and following arguments may also be a directory name.  Vim will
 then append the file name of the first argument to the directory name to find
 the file.
 
-This only works when a standard "diff" command is available.  See 'diffexpr'.
+By default an internal diff library will be used.  When 'diffopt' or
+'diffexpr' has been set an external "diff" command will be used.  This only
+works when such a diff program is available.
 
 Diffs are local to the current tab page |tab-page|.  You can't see diffs with
 a window in another tab page.  This does make it possible to have several
@@ -344,8 +346,9 @@ between file1 and file2: >
 
 The ">" is replaced with the value of 'shellredir'.
 
-The output of "diff" must be a normal "ed" style diff.  Do NOT use a context
-diff.  This example explains the format that Vim expects: >
+The output of "diff" must be a normal "ed" style diff or a unified diff.  Do
+NOT use a context diff.  This example explains the format that Vim expects for
+the "ed" style diff: >
 
        1a2
        > bbb
index fca2e4662eb127729df174a80279fb8ee18ad650..16aa422151954210fa8b45b30d4d82c0211183bd 100644 (file)
@@ -2609,8 +2609,8 @@ A jump table for the options with a short description can be found at |Q_op|.
                        {not in Vi}
                        {not available when compiled without the |+diff|
                        feature}
-       Expression which is evaluated to obtain an ed-style diff file from two
-       versions of a file.  See |diff-diffexpr|.
+       Expression which is evaluated to obtain a diff file (either ed-style
+       or unified-style) from two versions of a file.  See |diff-diffexpr|.
        This option cannot be set from a |modeline| or in the |sandbox|, for
        security reasons.
 
@@ -2657,11 +2657,31 @@ A jump table for the options with a short description can be found at |Q_op|.
                foldcolumn:{n}  Set the 'foldcolumn' option to {n} when
                                starting diff mode.  Without this 2 is used.
 
-       Examples: >
+               internal        Use the internal diff library.  This is
+                               ignored when 'diffexpr' is set.  *E960*
+                               When running out of memory when writing a
+                               buffer this item will be ignored for diffs
+                               involving that buffer.  Set the 'verbose'
+                               option to see when this happens.
+
+               indent-heuristic
+                                 Use the indent heuristic for the internal
+                                 diff library.
+
+                algorithm:{text} Use the specified diff algorithm with the
+                               internal diff engine. Currently supported 
+                               algorithms are:
+                               myers      the default algorithm
+                               minimal    spend extra time to generate the
+                                          smallest possible diff
+                               patience   patience diff algorithm
+                               histogram  histogram diff algorithm
 
-               :set diffopt=filler,context:4
+       Examples: >
+               :set diffopt=internal,filler,context:4
                :set diffopt=
-               :set diffopt=filler,foldcolumn:3
+               :set diffopt=internal,filler,foldcolumn:3
+               :set diffopt-=internal  " do NOT use the internal diff parser
 <
                                     *'digraph'* *'dg'* *'nodigraph'* *'nodg'*
 'digraph' 'dg'         boolean (default off)
index 16cd71259ce16282f79fbed4d27f079810d10e48..2308135b524b8cb217c6caae5d6b29716ec817b6 100644 (file)
@@ -166,6 +166,25 @@ else
 ifndef CROSS_COMPILE
 CROSS_COMPILE =
 endif
+
+# About the "sh.exe" condition, as explained by Ken Takata:
+#
+# If the makefile is executed with mingw32-make and sh.exe is not found in
+# $PATH, then $SHELL is set to "sh.exe" (without any path). In this case,
+# unix-like commands might not work and a dos-style path is needed.
+# 
+# If the makefile is executed with mingw32-make and sh.exe IS found in $PATH,
+# then $SHELL is set with the actual path of sh.exe (e.g.
+# "C:/msys64/usr/bin/sh.exe").  In this case, unix-like commands can be used.
+# 
+# If it is executed by the "make" command from cmd.exe, $SHELL is set to
+# "/bin/sh". If the "make" command is in the $PATH, other unix-like commands
+# might also work.
+# 
+# If it is executed by the "make" command from a unix-like shell,
+# $SHELL is set with the unix-style path (e.g. "/bin/bash").
+# In this case, unix-like commands can be used.
+#
 ifneq (sh.exe, $(SHELL))
 DEL = rm
 MKDIR = mkdir -p
@@ -810,6 +829,23 @@ OBJ += $(OUTDIR)/terminal.o \
        $(OUTDIR)/term_vterm.o
 endif
 
+# Include xdiff
+OBJ +=  $(OUTDIR)/xdiffi.o \
+       $(OUTDIR)/xemit.o \
+       $(OUTDIR)/xprepare.o \
+       $(OUTDIR)/xutils.o \
+       $(OUTDIR)/xhistogram.o \
+       $(OUTDIR)/xpatience.o
+
+XDIFF_DEPS = \
+       xdiff/xdiff.h \
+       xdiff/xdiffi.h \
+       xdiff/xemit.h \
+       xdiff/xinclude.h \
+       xdiff/xmacros.h \
+       xdiff/xprepare.h \
+       xdiff/xtypes.h \
+       xdiff/xutils.h
 
 ifdef MZSCHEME
 MZSCHEME_SUFFIX = Z
@@ -1055,6 +1091,23 @@ $(OUTDIR)/term_unicode.o: libvterm/src/unicode.c $(TERM_DEPS)
 $(OUTDIR)/term_vterm.o: libvterm/src/vterm.c $(TERM_DEPS)
        $(CCCTERM) libvterm/src/vterm.c -o $@
 
+$(OUTDIR)/xdiffi.o: xdiff/xdiffi.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xdiffi.c -o $(OUTDIR)/xdiffi.o
+
+$(OUTDIR)/xemit.o: xdiff/xemit.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xemit.c -o $(OUTDIR)/xemit.o
+
+$(OUTDIR)/xprepare.o: xdiff/xprepare.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xprepare.c -o $(OUTDIR)/xprepare.o
+
+$(OUTDIR)/xutils.o: xdiff/xutils.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xutils.c -o $(OUTDIR)/xutils.o
+
+$(OUTDIR)/xhistogram.o: xdiff/xhistogram.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xhistogram.c -o $(OUTDIR)/xhistogram.o
+
+$(OUTDIR)/xpatience.o: xdiff/xpatience.c $(XDIFF_DEPS)
+       $(CC) -c $(CFLAGS) xdiff/xpatience.c -o $(OUTDIR)/xpatience.o
 
 pathdef.c: $(INCL)
 ifneq (sh.exe, $(SHELL))
index f32903392ef79fbcf4c3bc9a7920b69abb42075e..1131f2b05f1fc889f55242f1c107703f7231ced9 100644 (file)
@@ -813,6 +813,24 @@ CUI_OBJ = $(OUTDIR)\iscygpty.obj
 !endif
 SUBSYSTEM_TOOLS = console
 
+XDIFF_OBJ = $(OBJDIR)/xdiffi.obj \
+       $(OBJDIR)/xemit.obj \
+       $(OBJDIR)/xprepare.obj \
+       $(OBJDIR)/xutils.obj \
+       $(OBJDIR)/xhistogram.obj \
+       $(OBJDIR)/xpatience.obj
+
+XDIFF_DEPS = \
+       xdiff/xdiff.h \
+       xdiff/xdiffi.h \
+       xdiff/xemit.h \
+       xdiff/xinclude.h \
+       xdiff/xmacros.h \
+       xdiff/xprepare.h \
+       xdiff/xtypes.h \
+       xdiff/xutils.h
+
+
 !if "$(SUBSYSTEM_VER)" != ""
 SUBSYSTEM = $(SUBSYSTEM),$(SUBSYSTEM_VER)
 SUBSYSTEM_TOOLS = $(SUBSYSTEM_TOOLS),$(SUBSYSTEM_VER)
@@ -1204,12 +1222,12 @@ all:    $(VIM).exe \
        tee/tee.exe \
        GvimExt/gvimext.dll
 
-$(VIM).exe: $(OUTDIR) $(OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) $(OLE_IDL) $(MZSCHEME_OBJ) \
+$(VIM).exe: $(OUTDIR) $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) $(OLE_IDL) $(MZSCHEME_OBJ) \
                $(LUA_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) $(TCL_OBJ) \
                $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) $(XPM_OBJ) \
                version.c version.h
        $(CC) $(CFLAGS_OUTDIR) version.c
-       $(link) $(LINKARGS1) -out:$(VIM).exe $(OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) \
+       $(link) $(LINKARGS1) -out:$(VIM).exe $(OBJ) $(XDIFF_OBJ) $(GUI_OBJ) $(CUI_OBJ) $(OLE_OBJ) \
                $(LUA_OBJ) $(MZSCHEME_OBJ) $(PERL_OBJ) $(PYTHON_OBJ) $(PYTHON3_OBJ) $(RUBY_OBJ) \
                $(TCL_OBJ) $(CSCOPE_OBJ) $(TERM_OBJ) $(NETBEANS_OBJ) $(CHANNEL_OBJ) \
                $(XPM_OBJ) $(OUTDIR)\version.obj $(LINKARGS2)
@@ -1304,6 +1322,7 @@ $(NEW_TESTS):
        $(MAKE) /NOLOGO -f Make_dos.mak nolog
        $(MAKE) /NOLOGO -f Make_dos.mak $@.res
        $(MAKE) /NOLOGO -f Make_dos.mak report
+       cat messages
        cd ..
 
 ###########################################################################
@@ -1344,6 +1363,24 @@ $(OUTDIR)/dict.obj:      $(OUTDIR) dict.c  $(INCL)
 
 $(OUTDIR)/diff.obj:    $(OUTDIR) diff.c  $(INCL)
 
+$(OUTDIR)/xdiffi.obj:  $(OUTDIR) xdiff/xdiffi.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xdiffi.c 
+
+$(OUTDIR)/xemit.obj:   $(OUTDIR) xdiff/xemit.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xemit.c 
+
+$(OUTDIR)/xprepare.obj:        $(OUTDIR) xdiff/xprepare.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xprepare.c 
+
+$(OUTDIR)/xutils.obj:  $(OUTDIR) xdiff/xutils.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xutils.c 
+
+$(OUTDIR)/xhistogram.obj:      $(OUTDIR) xdiff/xhistogram.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xhistogram.c 
+
+$(OUTDIR)/xpatience.obj:       $(OUTDIR) xdiff/xpatience.c  $(XDIFF_DEPS)
+       $(CC) $(CFLAGS_OUTDIR) xdiff/xpatience.c 
+
 $(OUTDIR)/digraph.obj: $(OUTDIR) digraph.c  $(INCL)
 
 $(OUTDIR)/edit.obj:    $(OUTDIR) edit.c  $(INCL)
index f2fafa4dcad95d6d980a9b1d6e5a6786325f10ca..d0cf956270847721a64abe69c5f4580678e9d9b3 100644 (file)
@@ -1400,6 +1400,32 @@ TERM_DEPS = \
 
 TERM_SRC = libvterm/src/*.c
 
+XDIFF_SRC = \
+       xdiff/xdiffi.c \
+       xdiff/xemit.c \
+       xdiff/xprepare.c \
+       xdiff/xutils.c \
+       xdiff/xhistogram.c \
+       xdiff/xpatience.c \
+
+XDIFF_OBJS = \
+       objects/xdiffi.o \
+       objects/xemit.o \
+       objects/xprepare.o \
+       objects/xutils.o \
+       objects/xhistogram.o \
+       objects/xpatience.o \
+
+XDIFF_INCL = \
+       xdiff/xdiff.h \
+       xdiff/xdiffi.h \
+       xdiff/xemit.h \
+       xdiff/xinclude.h \
+       xdiff/xmacros.h \
+       xdiff/xprepare.h \
+       xdiff/xtypes.h \
+       xdiff/xutils.h \
+
 ### Command to create dependencies based on #include "..."
 ### prototype headers are ignored due to -DPROTO, system
 ### headers #include <...> are ignored if we use the -MM option, as
@@ -1611,6 +1637,7 @@ BASIC_SRC = \
 SRC =  $(BASIC_SRC) \
        $(GUI_SRC) \
        $(TERM_SRC) \
+       $(XDIFF_SRC) \
        $(HANGULIN_SRC) \
        $(LUA_SRC) \
        $(MZSCHEME_SRC) \
@@ -1728,6 +1755,7 @@ OBJ_COMMON = \
        $(WORKSHOP_OBJ) \
        $(NETBEANS_OBJ) \
        $(CHANNEL_OBJ) \
+       $(XDIFF_OBJS) \
        $(WSDEBUG_OBJ)
 
 # The files included by tests are not in OBJ_COMMON.
@@ -2731,7 +2759,7 @@ SHADOWDIR = shadow
 
 shadow:        runtime pixmaps
        $(MKDIR_P) $(SHADOWDIR)
-       cd $(SHADOWDIR); ln -s ../*.[chm] ../*.in ../*.sh ../*.xs ../*.xbm ../gui_gtk_res.xml ../toolcheck ../proto ../libvterm ../vimtutor ../gvimtutor ../install-sh ../Make_all.mak .
+       cd $(SHADOWDIR); ln -s ../*.[chm] ../*.in ../*.sh ../*.xs ../*.xbm ../gui_gtk_res.xml ../toolcheck ../proto ../xdiff ../libvterm ../vimtutor ../gvimtutor ../install-sh ../Make_all.mak .
        mkdir $(SHADOWDIR)/auto
        cd $(SHADOWDIR)/auto; ln -s ../../auto/configure .
        $(MKDIR_P) $(SHADOWDIR)/po
@@ -2915,7 +2943,7 @@ objects/crypt_zip.o: crypt_zip.c
 objects/dict.o: dict.c
        $(CCC) -o $@ dict.c
 
-objects/diff.o: diff.c
+objects/diff.o: diff.c $(XDIFF_INCL)
        $(CCC) -o $@ diff.c
 
 objects/digraph.o: digraph.c
@@ -3229,6 +3257,27 @@ objects/term_unicode.o: libvterm/src/unicode.c $(TERM_DEPS)
 objects/term_vterm.o: libvterm/src/vterm.c $(TERM_DEPS)
        $(CCCTERM) -o $@ libvterm/src/vterm.c
 
+CCCDIFF = $(CCC_NF) $(ALL_CFLAGS)
+
+objects/xdiffi.o: xdiff/xdiffi.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xdiffi.c
+
+objects/xprepare.o: xdiff/xprepare.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xprepare.c
+
+objects/xutils.o: xdiff/xutils.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xutils.c
+
+objects/xemit.o: xdiff/xemit.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xemit.c
+
+objects/xhistogram.o: xdiff/xhistogram.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xhistogram.c
+
+objects/xpatience.o: xdiff/xpatience.c $(XDIFF_INCL)
+       $(CCCDIFF) -o $@ xdiff/xpatience.c
+
+
 ###############################################################################
 ### MacOS X installation
 ###
index c67654f621e296dbc5bee2e84b728b2f4bfe2e40..4c0792baae079c36a77f987c23a06b4a4412fab8 100644 (file)
@@ -9,23 +9,32 @@
 
 /*
  * diff.c: code for diff'ing two, three or four buffers.
+ *
+ * There are three ways to diff:
+ * - Shell out to an external diff program, using files.
+ * - Use the compiled-in xdiff library.
+ * - Let 'diffexpr' do the work, using files.
  */
 
 #include "vim.h"
+#include "xdiff/xdiff.h"
 
 #if defined(FEAT_DIFF) || defined(PROTO)
 
 static int     diff_busy = FALSE;      /* ex_diffgetput() is busy */
 
 /* flags obtained from the 'diffopt' option */
-#define DIFF_FILLER    1       /* display filler lines */
-#define DIFF_ICASE     2       /* ignore case */
-#define DIFF_IWHITE    4       /* ignore change in white space */
-#define DIFF_HORIZONTAL        8       /* horizontal splits */
-#define DIFF_VERTICAL  16      /* vertical splits */
-#define DIFF_HIDDEN_OFF        32      /* diffoff when hidden */
+#define DIFF_FILLER    1       // display filler lines
+#define DIFF_ICASE     2       // ignore case
+#define DIFF_IWHITE    4       // ignore change in white space
+#define DIFF_HORIZONTAL        8       // horizontal splits
+#define DIFF_VERTICAL  16      // vertical splits
+#define DIFF_HIDDEN_OFF        32      // diffoff when hidden
+#define DIFF_INTERNAL  64      // use internal xdiff algorithm
 static int     diff_flags = DIFF_FILLER;
 
+static long diff_algorithm = 0;
+
 #define LBUFLEN 50             /* length of line in diff file */
 
 static int diff_a_works = MAYBE; /* TRUE when "diff -a" works, FALSE when it
@@ -36,22 +45,45 @@ static int diff_bin_works = MAYBE; /* TRUE when "diff --binary" works, FALSE
                                      checked yet */
 #endif
 
+// used for diff input
+typedef struct {
+    char_u     *din_fname;  // used for external diff
+    mmfile_t   din_mmfile;  // used for internal diff
+} diffin_T;
+
+// used for diff result
+typedef struct {
+    char_u     *dout_fname;  // used for external diff
+    garray_T   dout_ga;      // used for internal diff
+} diffout_T;
+
+// two diff inputs and one result
+typedef struct {
+    diffin_T   dio_orig;     // original file input
+    diffin_T   dio_new;      // new file input
+    diffout_T  dio_diff;     // diff result
+    int                dio_internal; // using internal diff
+} diffio_T;
+
 static int diff_buf_idx(buf_T *buf);
 static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp);
 static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T line2, long amount, long amount_after);
 static void diff_check_unchanged(tabpage_T *tp, diff_T *dp);
 static int diff_check_sanity(tabpage_T *tp, diff_T *dp);
 static void diff_redraw(int dofold);
-static int diff_write(buf_T *buf, char_u *fname);
-static void diff_file(char_u *tmp_orig, char_u *tmp_new, char_u *tmp_diff);
+static int check_external_diff(diffio_T *diffio);
+static int diff_file(diffio_T *diffio);
 static int diff_equal_entry(diff_T *dp, int idx1, int idx2);
 static int diff_cmp(char_u *s1, char_u *s2);
 #ifdef FEAT_FOLDING
 static void diff_fold_update(diff_T *dp, int skip_idx);
 #endif
-static void diff_read(int idx_orig, int idx_new, char_u *fname);
+static void diff_read(int idx_orig, int idx_new, diffout_T *fname);
 static void diff_copy_entry(diff_T *dprev, diff_T *dp, int idx_orig, int idx_new);
 static diff_T *diff_alloc_new(tabpage_T *tp, diff_T *dprev, diff_T *dp);
+static int parse_diff_ed(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, long *count_new);
+static int parse_diff_unified(char_u *line, linenr_T *lnum_orig, long *count_orig, linenr_T *lnum_new, long *count_new);
+static int xdiff_out(void *priv, mmbuffer_t *mb, int nbuf);
 
 #ifndef USE_CR
 # define tag_fgets vim_fgets
@@ -631,26 +663,224 @@ diff_redraw(
        }
 }
 
+    static void
+clear_diffin(diffin_T *din)
+{
+    if (din->din_fname == NULL)
+    {
+       vim_free(din->din_mmfile.ptr);
+       din->din_mmfile.ptr = NULL;
+    }
+    else
+       mch_remove(din->din_fname);
+}
+
+    static void
+clear_diffout(diffout_T *dout)
+{
+    if (dout->dout_fname == NULL)
+       ga_clear_strings(&dout->dout_ga);
+    else
+       mch_remove(dout->dout_fname);
+}
+
+/*
+ * Write buffer "buf" to a memory buffer.
+ * Return FAIL for failure.
+ */
+    static int
+diff_write_buffer(buf_T *buf, diffin_T *din)
+{
+    linenr_T   lnum;
+    char_u     *s;
+    long       len = 0;
+    char_u     *ptr;
+
+    // xdiff requires one big block of memory with all the text.
+    for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
+       len += STRLEN(ml_get_buf(buf, lnum, FALSE)) + 1;
+    ptr = lalloc(len, TRUE);
+    if (ptr == NULL)
+    {
+       // Allocating memory failed.  This can happen, because we try to read
+       // the whole buffer text into memory.  Set the failed flag, the diff
+       // will be retried with external diff.  The flag is never reset.
+       buf->b_diff_failed = TRUE;
+       if (p_verbose > 0)
+       {
+           verbose_enter();
+           smsg((char_u *)
+                _("Not enough memory to use internal diff for buffer \"%s\""),
+                                                                buf->b_fname);
+           verbose_leave();
+       }
+       return FAIL;
+    }
+    din->din_mmfile.ptr = (char *)ptr;
+    din->din_mmfile.size = len;
+
+    len = 0;
+    for (lnum = 1; lnum <= buf->b_ml.ml_line_count; ++lnum)
+    {
+       for (s = ml_get_buf(buf, lnum, FALSE); *s != NUL; )
+       {
+           if (diff_flags & DIFF_ICASE)
+           {
+               int c;
+
+               // xdiff doesn't support ignoring case, fold-case the text.
+#ifdef FEAT_MBYTE
+               int     orig_len;
+               char_u  cbuf[MB_MAXBYTES + 1];
+
+               c = PTR2CHAR(s);
+               c = enc_utf8 ? utf_fold(c) : MB_TOLOWER(c);
+               orig_len = MB_PTR2LEN(s);
+               if (mb_char2bytes(c, cbuf) != orig_len)
+                   // TODO: handle byte length difference
+                   mch_memmove(ptr + len, s, orig_len);
+               else
+                   mch_memmove(ptr + len, cbuf, orig_len);
+
+               s += orig_len;
+               len += orig_len;
+#else
+               c = *s++;
+               ptr[len++] = TOLOWER_LOC(c);
+#endif
+           }
+           else
+               ptr[len++] = *s++;
+       }
+       ptr[len++] = NL;
+    }
+    return OK;
+}
+
 /*
- * Write buffer "buf" to file "name".
- * Always use 'fileformat' set to "unix".
- * Return FAIL for failure
+ * Write buffer "buf" to file or memory buffer.
+ * Return FAIL for failure.
  */
     static int
-diff_write(buf_T *buf, char_u *fname)
+diff_write(buf_T *buf, diffin_T *din)
 {
     int                r;
     char_u     *save_ff;
 
+    if (din->din_fname == NULL)
+       return diff_write_buffer(buf, din);
+
+    // Always use 'fileformat' set to "unix".
     save_ff = buf->b_p_ff;
     buf->b_p_ff = vim_strsave((char_u *)FF_UNIX);
-    r = buf_write(buf, fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count,
-                                            NULL, FALSE, FALSE, FALSE, TRUE);
+    r = buf_write(buf, din->din_fname, NULL,
+                       (linenr_T)1, buf->b_ml.ml_line_count,
+                       NULL, FALSE, FALSE, FALSE, TRUE);
     free_string_option(buf->b_p_ff);
     buf->b_p_ff = save_ff;
     return r;
 }
 
+/*
+ * Update the diffs for all buffers involved.
+ */
+    static void
+diff_try_update(
+       diffio_T    *dio,
+       int         idx_orig,
+       exarg_T     *eap)       // "eap" can be NULL
+{
+    buf_T      *buf;
+    int                idx_new;
+
+    if (dio->dio_internal)
+    {
+       ga_init2(&dio->dio_diff.dout_ga, sizeof(char *), 1000);
+    }
+    else
+    {
+       // We need three temp file names.
+       dio->dio_orig.din_fname = vim_tempname('o', TRUE);
+       dio->dio_new.din_fname = vim_tempname('n', TRUE);
+       dio->dio_diff.dout_fname = vim_tempname('d', TRUE);
+       if (dio->dio_orig.din_fname == NULL
+               || dio->dio_new.din_fname == NULL
+               || dio->dio_diff.dout_fname == NULL)
+           goto theend;
+    }
+
+    // Check external diff is actually working.
+    if (!dio->dio_internal && check_external_diff(dio) == FAIL)
+       goto theend;
+
+    // :diffupdate!
+    if (eap != NULL && eap->forceit)
+       for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
+       {
+           buf = curtab->tp_diffbuf[idx_new];
+           if (buf_valid(buf))
+               buf_check_timestamp(buf, FALSE);
+       }
+
+    // Write the first buffer to a tempfile or mmfile_t.
+    buf = curtab->tp_diffbuf[idx_orig];
+    if (diff_write(buf, &dio->dio_orig) == FAIL)
+       goto theend;
+
+    // Make a difference between the first buffer and every other.
+    for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
+    {
+       buf = curtab->tp_diffbuf[idx_new];
+       if (buf == NULL || buf->b_ml.ml_mfp == NULL)
+           continue; // skip buffer that isn't loaded
+
+       // Write the other buffer and diff with the first one.
+       if (diff_write(buf, &dio->dio_new) == FAIL)
+           continue;
+       if (diff_file(dio) == FAIL)
+           continue;
+
+       // Read the diff output and add each entry to the diff list.
+       diff_read(idx_orig, idx_new, &dio->dio_diff);
+
+       clear_diffin(&dio->dio_new);
+       clear_diffout(&dio->dio_diff);
+    }
+    clear_diffin(&dio->dio_orig);
+
+theend:
+    vim_free(dio->dio_orig.din_fname);
+    vim_free(dio->dio_new.din_fname);
+    vim_free(dio->dio_diff.dout_fname);
+}
+
+/*
+ * Return TRUE if the options are set to use the internal diff library.
+ * Note that if the internal diff failed for one of the buffers, the external
+ * diff will be used anyway.
+ */
+    static int
+diff_internal(void)
+{
+    return (diff_flags & DIFF_INTERNAL) != 0 && *p_dex == NUL;
+}
+
+/*
+ * Return TRUE if the internal diff failed for one of the diff buffers.
+ */
+    static int
+diff_internal_failed(void)
+{
+    int idx;
+
+    // Only need to do something when there is another buffer.
+    for (idx = 0; idx < DB_COUNT; ++idx)
+       if (curtab->tp_diffbuf[idx] != NULL
+               && curtab->tp_diffbuf[idx]->b_diff_failed)
+           return TRUE;
+    return FALSE;
+}
+
 /*
  * Completely update the diffs for the buffers involved.
  * This uses the ordinary "diff" command.
@@ -658,54 +888,65 @@ diff_write(buf_T *buf, char_u *fname)
  * could have been produced by autocommands, e.g. the netrw plugin).
  */
     void
-ex_diffupdate(
-    exarg_T    *eap)       /* can be NULL */
+ex_diffupdate(exarg_T *eap)    // "eap" can be NULL
 {
-    buf_T      *buf;
     int                idx_orig;
     int                idx_new;
-    char_u     *tmp_orig;
-    char_u     *tmp_new;
-    char_u     *tmp_diff;
-    FILE       *fd;
-    int                ok;
-    int                io_error = FALSE;
+    diffio_T   diffio;
 
-    /* Delete all diffblocks. */
+    // Delete all diffblocks.
     diff_clear(curtab);
     curtab->tp_diff_invalid = FALSE;
 
-    /* Use the first buffer as the original text. */
+    // Use the first buffer as the original text.
     for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig)
        if (curtab->tp_diffbuf[idx_orig] != NULL)
            break;
     if (idx_orig == DB_COUNT)
        return;
 
-    /* Only need to do something when there is another buffer. */
+    // Only need to do something when there is another buffer.
     for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
        if (curtab->tp_diffbuf[idx_new] != NULL)
            break;
     if (idx_new == DB_COUNT)
        return;
 
-    /* We need three temp file names. */
-    tmp_orig = vim_tempname('o', TRUE);
-    tmp_new = vim_tempname('n', TRUE);
-    tmp_diff = vim_tempname('d', TRUE);
-    if (tmp_orig == NULL || tmp_new == NULL || tmp_diff == NULL)
-       goto theend;
+    // Only use the internal method if it did not fail for one of the buffers.
+    vim_memset(&diffio, 0, sizeof(diffio));
+    diffio.dio_internal = diff_internal() && !diff_internal_failed();
+
+    diff_try_update(&diffio, idx_orig, eap);
+    if (diffio.dio_internal && diff_internal_failed())
+    {
+       // Internal diff failed, use external diff instead.
+       vim_memset(&diffio, 0, sizeof(diffio));
+       diff_try_update(&diffio, idx_orig, eap);
+    }
+
+    // force updating cursor position on screen
+    curwin->w_valid_cursor.lnum = 0;
+
+    diff_redraw(TRUE);
+}
+
+/*
+ * Do a quick test if "diff" really works.  Otherwise it looks like there
+ * are no differences.  Can't use the return value, it's non-zero when
+ * there are differences.
+ */
+    static int
+check_external_diff(diffio_T *diffio)
+{
+    FILE       *fd;
+    int                ok;
+    int                io_error = FALSE;
 
-    /*
-     * Do a quick test if "diff" really works.  Otherwise it looks like there
-     * are no differences.  Can't use the return value, it's non-zero when
-     * there are differences.
-     * May try twice, first with "-a" and then without.
-     */
+    // May try twice, first with "-a" and then without.
     for (;;)
     {
        ok = FALSE;
-       fd = mch_fopen((char *)tmp_orig, "w");
+       fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w");
        if (fd == NULL)
            io_error = TRUE;
        else
@@ -713,7 +954,7 @@ ex_diffupdate(
            if (fwrite("line1\n", (size_t)6, (size_t)1, fd) != 1)
                io_error = TRUE;
            fclose(fd);
-           fd = mch_fopen((char *)tmp_new, "w");
+           fd = mch_fopen((char *)diffio->dio_new.din_fname, "w");
            if (fd == NULL)
                io_error = TRUE;
            else
@@ -721,8 +962,9 @@ ex_diffupdate(
                if (fwrite("line2\n", (size_t)6, (size_t)1, fd) != 1)
                    io_error = TRUE;
                fclose(fd);
-               diff_file(tmp_orig, tmp_new, tmp_diff);
-               fd = mch_fopen((char *)tmp_diff, "r");
+               fd = NULL;
+               if (diff_file(diffio) == OK)
+                   fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r");
                if (fd == NULL)
                    io_error = TRUE;
                else
@@ -739,10 +981,10 @@ ex_diffupdate(
                    }
                    fclose(fd);
                }
-               mch_remove(tmp_diff);
-               mch_remove(tmp_new);
+               mch_remove(diffio->dio_diff.dout_fname);
+               mch_remove(diffio->dio_new.din_fname);
            }
-           mch_remove(tmp_orig);
+           mch_remove(diffio->dio_orig.din_fname);
        }
 
 #ifdef FEAT_EVAL
@@ -785,98 +1027,101 @@ ex_diffupdate(
 #if defined(MSWIN)
        diff_bin_works = MAYBE;
 #endif
-       goto theend;
+       return FAIL;
     }
+    return OK;
+}
 
-    /* :diffupdate! */
-    if (eap != NULL && eap->forceit)
-       for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new)
-       {
-           buf = curtab->tp_diffbuf[idx_new];
-           if (buf_valid(buf))
-               buf_check_timestamp(buf, FALSE);
-       }
-
-    /* Write the first buffer to a tempfile. */
-    buf = curtab->tp_diffbuf[idx_orig];
-    if (diff_write(buf, tmp_orig) == FAIL)
-       goto theend;
-
-    /* Make a difference between the first buffer and every other. */
-    for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new)
-    {
-       buf = curtab->tp_diffbuf[idx_new];
-       if (buf == NULL || buf->b_ml.ml_mfp == NULL)
-           continue; /* skip buffer that isn't loaded */
-       if (diff_write(buf, tmp_new) == FAIL)
-           continue;
-       diff_file(tmp_orig, tmp_new, tmp_diff);
+/*
+ * Invoke the xdiff function.
+ */
+    static int
+diff_file_internal(diffio_T *diffio)
+{
+    xpparam_t      param;
+    xdemitconf_t    emit_cfg;
+    xdemitcb_t     emit_cb;
 
-       /* Read the diff output and add each entry to the diff list. */
-       diff_read(idx_orig, idx_new, tmp_diff);
-       mch_remove(tmp_diff);
-       mch_remove(tmp_new);
-    }
-    mch_remove(tmp_orig);
+    vim_memset(&param, 0, sizeof(param));
+    vim_memset(&emit_cfg, 0, sizeof(emit_cfg));
+    vim_memset(&emit_cb, 0, sizeof(emit_cb));
 
-    /* force updating cursor position on screen */
-    curwin->w_valid_cursor.lnum = 0;
+    param.flags = diff_algorithm;
 
-    diff_redraw(TRUE);
+    if (diff_flags & DIFF_IWHITE)
+       param.flags |= XDF_IGNORE_WHITESPACE_CHANGE;
 
-theend:
-    vim_free(tmp_orig);
-    vim_free(tmp_new);
-    vim_free(tmp_diff);
+    emit_cfg.ctxlen = 0; // don't need any diff_context here
+    emit_cb.priv = &diffio->dio_diff;
+    emit_cb.outf = xdiff_out;
+    if (xdl_diff(&diffio->dio_orig.din_mmfile,
+               &diffio->dio_new.din_mmfile,
+               &param, &emit_cfg, &emit_cb) < 0)
+    {
+       EMSG(_("E960: Problem creating the internal diff"));
+       return FAIL;
+    }
+    return OK;
 }
 
 /*
  * Make a diff between files "tmp_orig" and "tmp_new", results in "tmp_diff".
+ * return OK or FAIL;
  */
-    static void
-diff_file(
-    char_u     *tmp_orig,
-    char_u     *tmp_new,
-    char_u     *tmp_diff)
+    static int
+diff_file(diffio_T *dio)
 {
     char_u     *cmd;
     size_t     len;
+    char_u     *tmp_orig = dio->dio_orig.din_fname;
+    char_u     *tmp_new = dio->dio_new.din_fname;
+    char_u     *tmp_diff = dio->dio_diff.dout_fname;
 
 #ifdef FEAT_EVAL
     if (*p_dex != NUL)
-       /* Use 'diffexpr' to generate the diff file. */
+    {
+       // Use 'diffexpr' to generate the diff file.
        eval_diff(tmp_orig, tmp_new, tmp_diff);
+       return OK;
+    }
     else
 #endif
+    // Use xdiff for generating the diff.
+    if (dio->dio_internal)
+    {
+       return diff_file_internal(dio);
+    }
+    else
     {
        len = STRLEN(tmp_orig) + STRLEN(tmp_new)
                                      + STRLEN(tmp_diff) + STRLEN(p_srr) + 27;
        cmd = alloc((unsigned)len);
-       if (cmd != NULL)
-       {
-           /* We don't want $DIFF_OPTIONS to get in the way. */
-           if (getenv("DIFF_OPTIONS"))
-               vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)"");
-
-           /* Build the diff command and execute it.  Always use -a, binary
-            * differences are of no use.  Ignore errors, diff returns
-            * non-zero when differences have been found. */
-           vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s",
-                   diff_a_works == FALSE ? "" : "-a ",
+       if (cmd == NULL)
+           return FAIL;
+
+       // We don't want $DIFF_OPTIONS to get in the way.
+       if (getenv("DIFF_OPTIONS"))
+           vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)"");
+
+       // Build the diff command and execute it.  Always use -a, binary
+       // differences are of no use.  Ignore errors, diff returns
+       // non-zero when differences have been found.
+       vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s",
+               diff_a_works == FALSE ? "" : "-a ",
 #if defined(MSWIN)
-                   diff_bin_works == TRUE ? "--binary " : "",
+               diff_bin_works == TRUE ? "--binary " : "",
 #else
-                   "",
+               "",
 #endif
-                   (diff_flags & DIFF_IWHITE) ? "-b " : "",
-                   (diff_flags & DIFF_ICASE) ? "-i " : "",
-                   tmp_orig, tmp_new);
-           append_redir(cmd, (int)len, p_srr, tmp_diff);
-           block_autocmds();   /* Avoid ShellCmdPost stuff */
-           (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT);
-           unblock_autocmds();
-           vim_free(cmd);
-       }
+               (diff_flags & DIFF_IWHITE) ? "-b " : "",
+               (diff_flags & DIFF_ICASE) ? "-i " : "",
+               tmp_orig, tmp_new);
+       append_redir(cmd, (int)len, p_srr, tmp_diff);
+       block_autocmds();       // avoid ShellCmdPost stuff
+       (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT);
+       unblock_autocmds();
+       vim_free(cmd);
+       return OK;
     }
 }
 
@@ -1282,89 +1527,105 @@ ex_diffoff(exarg_T *eap)
  */
     static void
 diff_read(
-    int                idx_orig,       /* idx of original file */
-    int                idx_new,        /* idx of new file */
-    char_u     *fname)         /* name of diff output file */
+    int                idx_orig,       // idx of original file
+    int                idx_new,        // idx of new file
+    diffout_T  *dout)          // diff output
 {
-    FILE       *fd;
+    FILE       *fd = NULL;
+    int                line_idx = 0;
     diff_T     *dprev = NULL;
     diff_T     *dp = curtab->tp_first_diff;
     diff_T     *dn, *dpl;
-    long       f1, l1, f2, l2;
     char_u     linebuf[LBUFLEN];   /* only need to hold the diff line */
-    int                difftype;
-    char_u     *p;
+    char_u     *line;
     long       off;
     int                i;
     linenr_T   lnum_orig, lnum_new;
     long       count_orig, count_new;
     int                notset = TRUE;      /* block "*dp" not set yet */
+    enum {
+       DIFF_ED,
+       DIFF_UNIFIED,
+       DIFF_NONE
+    } diffstyle = DIFF_NONE;
 
-    fd = mch_fopen((char *)fname, "r");
-    if (fd == NULL)
+    if (dout->dout_fname == NULL)
     {
-       EMSG(_("E98: Cannot read diff output"));
-       return;
+       diffstyle = DIFF_UNIFIED;
+    }
+    else
+    {
+       fd = mch_fopen((char *)dout->dout_fname, "r");
+       if (fd == NULL)
+       {
+           EMSG(_("E98: Cannot read diff output"));
+           return;
+       }
     }
 
     for (;;)
     {
-       if (tag_fgets(linebuf, LBUFLEN, fd))
-           break;              /* end of file */
-       if (!isdigit(*linebuf))
-           continue;           /* not the start of a diff block */
-
-       /* This line must be one of three formats:
-        * {first}[,{last}]c{first}[,{last}]
-        * {first}a{first}[,{last}]
-        * {first}[,{last}]d{first}
-        */
-       p = linebuf;
-       f1 = getdigits(&p);
-       if (*p == ',')
+       if (fd == NULL)
        {
-           ++p;
-           l1 = getdigits(&p);
+           if (line_idx >= dout->dout_ga.ga_len)
+               break;      // did last line
+           line = ((char_u **)dout->dout_ga.ga_data)[line_idx++];
        }
        else
-           l1 = f1;
-       if (*p != 'a' && *p != 'c' && *p != 'd')
-           continue;           /* invalid diff format */
-       difftype = *p++;
-       f2 = getdigits(&p);
-       if (*p == ',')
        {
-           ++p;
-           l2 = getdigits(&p);
+           if (tag_fgets(linebuf, LBUFLEN, fd))
+               break;          // end of file
+           line = linebuf;
        }
-       else
-           l2 = f2;
-       if (l1 < f1 || l2 < f2)
-           continue;           /* invalid line range */
 
-       if (difftype == 'a')
+       if (diffstyle == DIFF_NONE)
        {
-           lnum_orig = f1 + 1;
-           count_orig = 0;
+           // Determine diff style.
+           // ed like diff looks like this:
+           // {first}[,{last}]c{first}[,{last}]
+           // {first}a{first}[,{last}]
+           // {first}[,{last}]d{first}
+           //
+           // unified diff looks like this:
+           // --- file1       2018-03-20 13:23:35.783153140 +0100
+           // +++ file2       2018-03-20 13:23:41.183156066 +0100
+           // @@ -1,3 +1,5 @@
+           if (isdigit(*line))
+               diffstyle = DIFF_ED;
+           else if ((STRNCMP(line, "@@ ", 3) == 0))
+              diffstyle = DIFF_UNIFIED;
+           else if ((STRNCMP(line, "--- ", 4) == 0)
+                   && (tag_fgets(linebuf, LBUFLEN, fd) == 0)
+                   && (STRNCMP(line, "+++ ", 4) == 0)
+                   && (tag_fgets(linebuf, LBUFLEN, fd) == 0)
+                   && (STRNCMP(line, "@@ ", 3) == 0))
+               diffstyle = DIFF_UNIFIED;
        }
-       else
+
+       if (diffstyle == DIFF_ED)
        {
-           lnum_orig = f1;
-           count_orig = l1 - f1 + 1;
+           if (!isdigit(*line))
+               continue;       // not the start of a diff block
+           if (parse_diff_ed(line, &lnum_orig, &count_orig,
+                                               &lnum_new, &count_new) == FAIL)
+               continue;
        }
-       if (difftype == 'd')
+       else if (diffstyle == DIFF_UNIFIED)
        {
-           lnum_new = f2 + 1;
-           count_new = 0;
+           if (STRNCMP(line, "@@ ", 3)  != 0)
+               continue;       // not the start of a diff block
+           if (parse_diff_unified(line, &lnum_orig, &count_orig,
+                                               &lnum_new, &count_new) == FAIL)
+               continue;
        }
        else
        {
-           lnum_new = f2;
-           count_new = l2 - f2 + 1;
+           EMSG(_("E959: Invalid diff format."));
+           break;
        }
 
-       /* Go over blocks before the change, for which orig and new are equal.
-        * Copy blocks from orig to new. */
+       // Go over blocks before the change, for which orig and new are equal.
+       // Copy blocks from orig to new.
        while (dp != NULL
                && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig])
        {
@@ -1379,14 +1640,14 @@ diff_read(
                && lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig]
                && lnum_orig + count_orig >= dp->df_lnum[idx_orig])
        {
-           /* New block overlaps with existing block(s).
-            * First find last block that overlaps. */
+           // New block overlaps with existing block(s).
+           // First find last block that overlaps.
            for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next)
                if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig])
                    break;
 
-           /* If the newly found block starts before the old one, set the
-            * start back a number of lines. */
+           // If the newly found block starts before the old one, set the
+           // start back a number of lines.
            off = dp->df_lnum[idx_orig] - lnum_orig;
            if (off > 0)
            {
@@ -1398,24 +1659,24 @@ diff_read(
            }
            else if (notset)
            {
-               /* new block inside existing one, adjust new block */
+               // new block inside existing one, adjust new block
                dp->df_lnum[idx_new] = lnum_new + off;
                dp->df_count[idx_new] = count_new - off;
            }
            else
-               /* second overlap of new block with existing block */
+               // second overlap of new block with existing block
                dp->df_count[idx_new] += count_new - count_orig
                    + dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]
                    - (dp->df_lnum[idx_orig] + dp->df_count[idx_orig]);
 
-           /* Adjust the size of the block to include all the lines to the
-            * end of the existing block or the new diff, whatever ends last. */
+           // Adjust the size of the block to include all the lines to the
+           // end of the existing block or the new diff, whatever ends last.
            off = (lnum_orig + count_orig)
                         - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
            if (off < 0)
            {
-               /* new change ends in existing block, adjust the end if not
-                * done already */
+               // new change ends in existing block, adjust the end if not
+               // done already
                if (notset)
                    dp->df_count[idx_new] += -off;
                off = 0;
@@ -1425,7 +1686,7 @@ diff_read(
                    dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i]
                                                       - dp->df_lnum[i] + off;
 
-           /* Delete the diff blocks that have been merged into one. */
+           // Delete the diff blocks that have been merged into one.
            dn = dp->df_next;
            dp->df_next = dpl->df_next;
            while (dn != dp->df_next)
@@ -1437,7 +1698,7 @@ diff_read(
        }
        else
        {
-           /* Allocate a new diffblock. */
+           // Allocate a new diffblock.
            dp = diff_alloc_new(curtab, dprev, dp);
            if (dp == NULL)
                goto done;
@@ -1447,17 +1708,17 @@ diff_read(
            dp->df_lnum[idx_new] = lnum_new;
            dp->df_count[idx_new] = count_new;
 
-           /* Set values for other buffers, these must be equal to the
-            * original buffer, otherwise there would have been a change
-            * already. */
+           // Set values for other buffers, these must be equal to the
+           // original buffer, otherwise there would have been a change
+           // already.
            for (i = idx_orig + 1; i < idx_new; ++i)
                if (curtab->tp_diffbuf[i] != NULL)
                    diff_copy_entry(dprev, dp, idx_orig, i);
        }
-       notset = FALSE;         /* "*dp" has been set */
+       notset = FALSE;         // "*dp" has been set
     }
 
-    /* for remaining diff blocks orig and new are equal */
+    // for remaining diff blocks orig and new are equal
     while (dp != NULL)
     {
        if (notset)
@@ -1468,7 +1729,8 @@ diff_read(
     }
 
 done:
-    fclose(fd);
+    if (fd != NULL)
+       fclose(fd);
 }
 
 /*
@@ -1860,6 +2122,7 @@ diffopt_changed(void)
     int                diff_context_new = 6;
     int                diff_flags_new = 0;
     int                diff_foldcolumn_new = 2;
+    long       diff_algorithm_new = 0;
     tabpage_T  *tp;
 
     p = p_dip;
@@ -1905,6 +2168,41 @@ diffopt_changed(void)
            p += 9;
            diff_flags_new |= DIFF_HIDDEN_OFF;
        }
+       else if (STRNCMP(p, "indent-heuristic", 16) == 0)
+       {
+           p += 16;
+           diff_algorithm_new |= XDF_INDENT_HEURISTIC;
+       }
+       else if (STRNCMP(p, "internal", 8) == 0)
+       {
+           p += 8;
+           diff_flags_new |= DIFF_INTERNAL;
+       }
+       else if (STRNCMP(p, "algorithm:", 10) == 0)
+       {
+           p += 10;
+           if (STRNCMP(p, "myers", 5) == 0)
+           {
+               p += 5;
+               diff_algorithm_new = 0;
+           }
+           else if (STRNCMP(p, "minimal", 7) == 0)
+           {
+               p += 7;
+               diff_algorithm_new = XDF_NEED_MINIMAL;
+           }
+           else if (STRNCMP(p, "patience", 8) == 0)
+           {
+               p += 8;
+               diff_algorithm_new = XDF_PATIENCE_DIFF;
+           }
+           else if (STRNCMP(p, "histogram", 9) == 0)
+           {
+               p += 9;
+               diff_algorithm_new = XDF_HISTOGRAM_DIFF;
+           }
+       }
+
        if (*p != ',' && *p != NUL)
            return FAIL;
        if (*p == ',')
@@ -1916,13 +2214,14 @@ diffopt_changed(void)
        return FAIL;
 
     /* If "icase" or "iwhite" was added or removed, need to update the diff. */
-    if (diff_flags != diff_flags_new)
+    if (diff_flags != diff_flags_new || diff_algorithm != diff_algorithm_new)
        FOR_ALL_TABPAGES(tp)
            tp->tp_diff_invalid = TRUE;
 
     diff_flags = diff_flags_new;
     diff_context = diff_context_new;
     diff_foldcolumn = diff_foldcolumn_new;
+    diff_algorithm = diff_algorithm_new;
 
     diff_redraw(TRUE);
 
@@ -2690,4 +2989,156 @@ diff_lnum_win(linenr_T lnum, win_T *wp)
     return n;
 }
 
+/*
+ * Handle an ED style diff line.
+ * Return FAIL if the line does not contain diff info.
+ */
+    static int
+parse_diff_ed(
+       char_u      *line,
+       linenr_T    *lnum_orig,
+       long        *count_orig,
+       linenr_T    *lnum_new,
+       long        *count_new)
+{
+    char_u *p;
+    long    f1, l1, f2, l2;
+    int            difftype;
+
+    // The line must be one of three formats:
+    // change: {first}[,{last}]c{first}[,{last}]
+    // append: {first}a{first}[,{last}]
+    // delete: {first}[,{last}]d{first}
+    p = line;
+    f1 = getdigits(&p);
+    if (*p == ',')
+    {
+       ++p;
+       l1 = getdigits(&p);
+    }
+    else
+       l1 = f1;
+    if (*p != 'a' && *p != 'c' && *p != 'd')
+       return FAIL;            // invalid diff format
+    difftype = *p++;
+    f2 = getdigits(&p);
+    if (*p == ',')
+    {
+       ++p;
+       l2 = getdigits(&p);
+    }
+    else
+       l2 = f2;
+    if (l1 < f1 || l2 < f2)
+       return FAIL;
+
+    if (difftype == 'a')
+    {
+       *lnum_orig = f1 + 1;
+       *count_orig = 0;
+    }
+    else
+    {
+       *lnum_orig = f1;
+       *count_orig = l1 - f1 + 1;
+    }
+    if (difftype == 'd')
+    {
+       *lnum_new = f2 + 1;
+       *count_new = 0;
+    }
+    else
+    {
+       *lnum_new = f2;
+       *count_new = l2 - f2 + 1;
+    }
+    return OK;
+}
+
+/*
+ * Parses unified diff with zero(!) context lines.
+ * Return FAIL if there is no diff information in "line".
+ */
+    static int
+parse_diff_unified(
+       char_u      *line,
+       linenr_T    *lnum_orig,
+       long        *count_orig,
+       linenr_T    *lnum_new,
+       long        *count_new)
+{
+    char_u *p;
+    long    oldline, oldcount, newline, newcount;
+
+    // Parse unified diff hunk header:
+    // @@ -oldline,oldcount +newline,newcount @@
+    p = line;
+    if (*p++ == '@' && *p++ == '@' && *p++ == ' ' && *p++ == '-')
+    {
+       oldline = getdigits(&p);
+       if (*p == ',')
+       {
+           ++p;
+           oldcount = getdigits(&p);
+       }
+       else
+           oldcount = 1;
+       if (*p++ == ' ' && *p++ == '+')
+       {
+           newline = getdigits(&p);
+           if (*p == ',')
+           {
+               ++p;
+               newcount = getdigits(&p);
+           }
+           else
+               newcount = 1;
+       }
+       else
+           return FAIL;        // invalid diff format
+
+       if (oldcount == 0)
+           oldline += 1;
+       if (newcount == 0)
+           newline += 1;
+       if (newline == 0)
+           newline = 1;
+
+       *lnum_orig = oldline;
+       *count_orig = oldcount;
+       *lnum_new = newline;
+       *count_new = newcount;
+
+       return OK;
+    }
+
+    return FAIL;
+}
+
+/*
+ * Callback function for the xdl_diff() function.
+ * Stores the diff output in a grow array.
+ */
+    static int
+xdiff_out(void *priv, mmbuffer_t *mb, int nbuf)
+{
+    diffout_T  *dout = (diffout_T *)priv;
+    int                i;
+    char_u     *p;
+
+    for (i = 0; i < nbuf; i++)
+    {
+       // We are only interested in the header lines, skip text lines.
+       if (STRNCMP(mb[i].ptr, "@@ ", 3)  != 0)
+           continue;
+       if (ga_grow(&dout->dout_ga, 1) == FAIL)
+           return -1;
+       p = vim_strnsave((char_u *)mb[i].ptr, mb[i].size);
+       if (p == NULL)
+           return -1;
+       ((char_u **)dout->dout_ga.ga_data)[dout->dout_ga.ga_len++] = p;
+    }
+    return 0;
+}
+
 #endif /* FEAT_DIFF */
index 253f774abc2b1ed599cf208a60c867fce3a7cc24..ee56bd3e40b7bd4fde19c0ddc3458b9cb3362f6c 100644 (file)
@@ -2433,7 +2433,9 @@ struct file_buffer
     term_T     *b_term;        /* When not NULL this buffer is for a terminal
                                 * window. */
 #endif
-
+#ifdef FEAT_DIFF
+    int                b_diff_failed;  // internal diff failed for this buffer
+#endif
 }; /* file_buffer */
 
 
diff --git a/src/testdir/dumps/Test_diff_01.dump b/src/testdir/dumps/Test_diff_01.dump
new file mode 100644 (file)
index 0000000..0dbce1f
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|0+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19
+| @1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_02.dump b/src/testdir/dumps/Test_diff_02.dump
new file mode 100644 (file)
index 0000000..78f7978
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|0+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |7|-@19
+| @1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_03.dump b/src/testdir/dumps/Test_diff_03.dump
new file mode 100644 (file)
index 0000000..60916a8
--- /dev/null
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19
+| @1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_04.dump b/src/testdir/dumps/Test_diff_04.dump
new file mode 100644 (file)
index 0000000..ba9cdd8
--- /dev/null
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| @1|4| |l|i|n|e|s|:| |1|-@19
+| @1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_05.dump b/src/testdir/dumps/Test_diff_05.dump
new file mode 100644 (file)
index 0000000..7a5d540
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_06.dump b/src/testdir/dumps/Test_diff_06.dump
new file mode 100644 (file)
index 0000000..8cd68fd
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|2+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|3+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|4+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|4+0#0000000#5fd7ff255| @33||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|5+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|6+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|7+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|8+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|9+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32||+1&&| +0#0000e05#a8a8a8255@1|1+0#0000000#ffffff0|0| @32
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|1+0#0000000#5fd7ff255@1| @32
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_07.dump b/src/testdir/dumps/Test_diff_07.dump
new file mode 100644 (file)
index 0000000..97864c6
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|/+2#0000000#ff404010@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| +0&#ffd7ff255@13||+1&#ffffff0| +0#0000e05#a8a8a8255@1|i+2#0000000#ff404010|n|t| |f|i|b|(|i|n|t| |n|)| +0&#ffd7ff255@20
+| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|n+2&#ff404010|t| |i|;| +0&#ffd7ff255@24||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|f+2&#ff404010|(|n| |>| |2|)| +0&#ffd7ff255@21
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@7|p+2&#ff404010|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)+0&#ffd7ff255|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@7|r+2&#ff404010|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)+0&#ffd7ff255|;
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|%|d|\|n|"|,| |f|o@1|)|;| @6||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|}| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|i+2#0000000#ff404010|n|t| |f|a|c|t|(|i|n|t| |n|)| +0&#ffd7ff255@19||+1&#ffffff0| +0#0000e05#a8a8a8255@1|/+2#0000000#ff404010@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| +0&#ffd7ff255@13
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|f+2&#ff404010|(|n| |>| |1|)| +0&#ffd7ff255@21||+1&#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#ffd7ff255@3|i|n+2&#ff404010|t| |i|;| +0&#ffd7ff255@24
+|X+3&#ffffff0|f|i|l|e|1| @12|1|,|1| @11|T|o|p| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
diff --git a/src/testdir/dumps/Test_diff_08.dump b/src/testdir/dumps/Test_diff_08.dump
new file mode 100644 (file)
index 0000000..6445a57
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|i|b|(|i|n|t| |n|)| @20
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|{+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|i|f|(|n| |>| |2|)| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|{| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|r|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)|;
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|}+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13||+1&&| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|X+3#0000000#ffffff0|f|i|l|e|1| @12|1|,|1| @11|T|o|p| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|a|l|g|o|r|i|t|h|m|:|p|a|t|i|e|n|c|e| @42
diff --git a/src/testdir/dumps/Test_diff_09.dump b/src/testdir/dumps/Test_diff_09.dump
new file mode 100644 (file)
index 0000000..6445a57
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16||+1&&| +0#0000e05#a8a8a8255@1|#+0#0000000#ffffff0|i|n|c|l|u|d|e| |<|s|t|d|i|o|.|h|>| @16
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|i+0#0000000#5fd7ff255|n|t| |f|i|b|(|i|n|t| |n|)| @20
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|{+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|i|f|(|n| |>| |2|)| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|{| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|r|e|t|u|r|n| |f|i|b|(|n|-|1|)| |+| |f|i|b|(|n|-|2|)|;
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|}| @29
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|r|e|t|u|r|n| |1|;| @21
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1|}+0#0000000#5fd7ff255| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13||+1&&| +0#0000e05#a8a8a8255@1|/+0#0000000#ffffff0@1| |F|r|o|b|s| |f|o@1| |h|e|a|r|t|i|l|y| @13
+| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |f|r|o|b|n|i|t|z|(|i|n|t| |f|o@1|)| @13
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|i|n|t| |i|;| @24
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|f|o|r|(|i| |=| |0|;| |i| |<| |1|0|;| |i|+@1|)| @7
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|{| @29
+| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@7|p|r|i|n|t|f|(|"|Y|o|u|r| |a|n|s|w|e|r| |i|s|:| |"|)|;||+1&#ffffff0| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34
+|X+3#0000000#ffffff0|f|i|l|e|1| @12|1|,|1| @11|T|o|p| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|T|o|p
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|a|l|g|o|r|i|t|h|m|:|p|a|t|i|e|n|c|e| @42
diff --git a/src/testdir/dumps/Test_diff_10.dump b/src/testdir/dumps/Test_diff_10.dump
new file mode 100644 (file)
index 0000000..70b9c82
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1> +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@5|v|.|p|r|e|p|a|r|e| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|0|-|1| @9|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|0|-|1| @9|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
diff --git a/src/testdir/dumps/Test_diff_11.dump b/src/testdir/dumps/Test_diff_11.dump
new file mode 100644 (file)
index 0000000..0e4268e
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1> +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@1|d|e|f| |f|i|n|a|l|i|z|e|(|v|a|l|u|e|s|)| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@5|v|.|p|r|e|p|a|r|e| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@34
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|v|a|l|u|e|s|.|e|a|c|h| |d|o| |||v||| @12
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|v|.|f|i|n|a|l|i|z|e| @18
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@3|e|n|d| @27
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|0|-|1| @9|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|0|-|1| @9|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|d|e|n|t|-|h|e|u|r|i|s|t|i|c| @44
diff --git a/src/testdir/dumps/Test_diff_12.dump b/src/testdir/dumps/Test_diff_12.dump
new file mode 100644 (file)
index 0000000..0ed4f91
--- /dev/null
@@ -0,0 +1,20 @@
+|++0#0000e05#a8a8a8255| |+|-@1| |1|0| |l|i|n|e|s|:| |1|-@19||+1#0000000#ffffff0|++0#0000e05#a8a8a8255| |+|-@1| |1|0| |l|i|n|e|s|:| |1|-@19
+| @1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_13.dump b/src/testdir/dumps/Test_diff_13.dump
new file mode 100644 (file)
index 0000000..cc1f1ad
--- /dev/null
@@ -0,0 +1,20 @@
+|-+0#0000e05#a8a8a8255| | +0#0000000#ffffff0@34||+1&&|-+0#0000e05#a8a8a8255| | +0#0000000#ffffff0@34
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|0|,|0|-|1| @9|A|l@1| |X+1&&|f|i|l|e|2| @12|0|,|0|-|1| @9|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_14.dump b/src/testdir/dumps/Test_diff_14.dump
new file mode 100644 (file)
index 0000000..5f9b75f
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1|a+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|A+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|b+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|d| @32||+1&#ffffff0| +0#0000e05#a8a8a8255@1|c+0#0000000#ffd7ff255|D|e+2&#ff404010| +0&#ffd7ff255@31
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&> @73
diff --git a/src/testdir/dumps/Test_diff_15.dump b/src/testdir/dumps/Test_diff_15.dump
new file mode 100644 (file)
index 0000000..0eb1813
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|i|f| |(|0|)| @25
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|{| @30
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @4
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r|e|t|u|r|n| |0|;| @22||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|r|e|t|u|r|n| |0|;| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|}| @30
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|&|v|i|m| |d|i|f@1|o|p|t|+|=|f|i|l@1|e|r| |d|i|f@1|o|p|t|+|=|i|w|h|i|t|e| @26
diff --git a/src/testdir/dumps/Test_diff_16.dump b/src/testdir/dumps/Test_diff_16.dump
new file mode 100644 (file)
index 0000000..74ad5b6
--- /dev/null
@@ -0,0 +1,20 @@
+| +0#0000e05#a8a8a8255@1>i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24||+1&&| +0#0000e05#a8a8a8255@1|i+0#0000000#ffffff0|n|t| |m|a|i|n|(|)| @24
+| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|{+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|i|f| |(|0|)| @25
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|{| @30
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @7||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|p|r|i|n|t|f|(|"|H|e|l@1|o|,| |W|o|r|l|d|!|"|)|;| @4
+| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@2|r|e|t|u|r|n| |0|;| @22||+1&&| +0#0000e05#a8a8a8255@1| +0#0000000#ffffff0@5|r|e|t|u|r|n| |0|;| @19
+| +0#0000e05#a8a8a8255@1|-+0#4040ff13#afffff255@34||+1#0000000#ffffff0| +0#0000e05#a8a8a8255@1| +0#0000000#5fd7ff255@2|}| @30
+| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33||+1&&| +0#0000e05#a8a8a8255@1|}+0#0000000#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33||+1#0000000&| +0#0000e05#a8a8a8255@1|~+0#4040ff13#ffffff0| @33
+|X+3#0000000&|f|i|l|e|1| @12|1|,|1| @11|A|l@1| |X+1&&|f|i|l|e|2| @12|1|,|1| @11|A|l@1
+|:+0&&|s|e|t| |d|i|f@1|o|p|t|+|=|i|n|t|e|r|n|a|l| @52
index d6c5c83fe566e5b17caa48efd47827fb32e3a323..c3c1eaf4a55a5a336d162ff4ab2753740d71a1e7 100644 (file)
@@ -1,4 +1,6 @@
 " Tests for diff mode
+source shared.vim
+source screendump.vim
 
 func Test_diff_fold_sync()
   enew!
@@ -33,6 +35,18 @@ func Test_diff_fold_sync()
 endfunc
 
 func Test_vert_split()
+  set diffopt=filler
+  call Common_vert_split()
+  set diffopt&
+endfunc
+
+func Test_vert_split_internal()
+  set diffopt=internal,filler
+  call Common_vert_split()
+  set diffopt&
+endfunc
+
+func Common_vert_split()
   " Disable the title to avoid xterm keeping the wrong one.
   set notitle noicon
   new
@@ -275,10 +289,8 @@ func Test_diffoff()
   bwipe!
 endfunc
 
-func Test_diffopt_icase()
-  set diffopt=icase,foldcolumn:0
-
-  e one
+func Common_icase_test()
+  edit one
   call setline(1, ['One', 'Two', 'Three', 'Four', 'Fi#ve'])
   redraw
   let normattr = screenattr(1, 1)
@@ -300,32 +312,54 @@ func Test_diffopt_icase()
 
   diffoff!
   %bwipe!
+endfunc
+
+func Test_diffopt_icase()
+  set diffopt=icase,foldcolumn:0
+  call Common_icase_test()
   set diffopt&
 endfunc
 
-func Test_diffopt_iwhite()
-  set diffopt=iwhite,foldcolumn:0
+func Test_diffopt_icase_internal()
+  set diffopt=icase,foldcolumn:0,internal
+  call Common_icase_test()
+  set diffopt&
+endfunc
 
-  e one
-  " Difference in trailing spaces should be ignored,
+func Common_iwhite_test()
+  edit one
+  " Difference in trailing spaces and amount of spaces should be ignored,
   " but not other space differences.
-  call setline(1, ["One \t", 'Two', 'Three', 'Four'])
+  call setline(1, ["One \t", 'Two', 'Three', 'one two', 'one two', 'Four'])
   redraw
   let normattr = screenattr(1, 1)
   diffthis
 
   botright vert new two
-  call setline(1, ["One\t ", "Two\t ", 'Three', ' Four'])
+  call setline(1, ["One\t ", "Two\t ", 'Three', 'one   two', 'onetwo', ' Four'])
   diffthis
 
   redraw
   call assert_equal(normattr, screenattr(1, 1))
   call assert_equal(normattr, screenattr(2, 1))
   call assert_equal(normattr, screenattr(3, 1))
-  call assert_notequal(normattr, screenattr(4, 1))
+  call assert_equal(normattr, screenattr(4, 1))
+  call assert_notequal(normattr, screenattr(5, 1))
+  call assert_notequal(normattr, screenattr(6, 1))
 
   diffoff!
   %bwipe!
+endfunc
+
+func Test_diffopt_iwhite()
+  set diffopt=iwhite,foldcolumn:0
+  call Common_iwhite_test()
+  set diffopt&
+endfunc
+
+func Test_diffopt_iwhite_internal()
+  set diffopt=internal,iwhite,foldcolumn:0
+  call Common_iwhite_test()
   set diffopt&
 endfunc
 
@@ -339,8 +373,13 @@ func Test_diffopt_context()
 
   set diffopt=context:2
   call assert_equal('+--  2 lines: 1', foldtextresult(1))
+  set diffopt=internal,context:2
+  call assert_equal('+--  2 lines: 1', foldtextresult(1))
+
   set diffopt=context:1
   call assert_equal('+--  3 lines: 1', foldtextresult(1))
+  set diffopt=internal,context:1
+  call assert_equal('+--  3 lines: 1', foldtextresult(1))
 
   diffoff!
   %bwipe!
@@ -348,7 +387,7 @@ func Test_diffopt_context()
 endfunc
 
 func Test_diffopt_horizontal()
-  set diffopt=horizontal
+  set diffopt=internal,horizontal
   diffsplit
 
   call assert_equal(&columns, winwidth(1))
@@ -362,7 +401,7 @@ func Test_diffopt_horizontal()
 endfunc
 
 func Test_diffopt_vertical()
-  set diffopt=vertical
+  set diffopt=internal,vertical
   diffsplit
 
   call assert_equal(&lines - 2, winheight(1))
@@ -376,7 +415,7 @@ func Test_diffopt_vertical()
 endfunc
 
 func Test_diffopt_hiddenoff()
-  set diffopt=filler,foldcolumn:0,hiddenoff
+  set diffopt=internal,filler,foldcolumn:0,hiddenoff
   e! one
   call setline(1, ['Two', 'Three'])
   redraw
@@ -399,7 +438,7 @@ func Test_diffopt_hiddenoff()
 endfunc
 
 func Test_diffoff_hidden()
-  set diffopt=filler,foldcolumn:0
+  set diffopt=internal,filler,foldcolumn:0
   e! one
   call setline(1, ['Two', 'Three'])
   redraw
@@ -629,3 +668,118 @@ func Test_diff_lastline()
   bwipe!
   bwipe!
 endfunc
+
+func WriteDiffFiles(list1, list2)
+  call writefile(a:list1, 'Xfile1')
+  call writefile(a:list2, 'Xfile2')
+endfunc
+
+" Verify a screendump with both the external and external diff.
+func VerifyBoth(buf, dumpfile, extra)
+  call term_sendkeys(a:buf, ":diffupdate!\<cr>")
+  " trailing : for leaving the cursor on the command line
+  for cmd in [":set diffopt=filler" . a:extra . "\<cr>:", ":set diffopt+=internal\<cr>:"]
+    call term_sendkeys(a:buf, cmd)
+    if VerifyScreenDump(a:buf, a:dumpfile, {}, cmd =~ 'internal' ? 'internal' : 'external')
+      break " don't let the next iteration overwrite the "failed" file.
+    endif
+  endfor
+endfunc
+
+func Test_diff_screen()
+  if !CanRunVimInTerminal() || !has('menu')
+    return
+  endif
+  " clean up already existing swap files, just in case
+  call delete('.Xfile1.swp')
+  call delete('.Xfile2.swp')
+
+  " Test 1: Add a line in beginning of file 2
+  call WriteDiffFiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+  let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+  " Set autoread mode, ,so that Vim won't complain once we re-write the test
+  " files
+  call term_sendkeys(buf, ":set autoread\<cr>\<c-w>w:set autoread\<cr>\<c-w>w")
+
+  call VerifyBoth(buf, 'Test_diff_01', '')
+
+  " Test 2: Add a line in beginning of file 1
+  call WriteDiffFiles([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+  call VerifyBoth(buf, 'Test_diff_02', '')
+
+  " Test 3: Add a line at the end of file 2
+  call WriteDiffFiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+  call VerifyBoth(buf, 'Test_diff_03', '')
+
+  " Test 4: Add a line at the end of file 1
+  call WriteDiffFiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+  call VerifyBoth(buf, 'Test_diff_04', '')
+
+  " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
+  call WriteDiffFiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
+  call VerifyBoth(buf, 'Test_diff_05', '')
+
+  " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
+  call WriteDiffFiles([1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+  call VerifyBoth(buf, 'Test_diff_06', '')
+
+  " Test 7 - 9: Test normal/patience/histogram diff algorithm
+  call WriteDiffFiles(['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
+      \ '    int i;', '    for(i = 0; i < 10; i++)', '    {', '        printf("Your answer is: ");',
+      \ '        printf("%d\n", foo);', '    }', '}', '', 'int fact(int n)', '{', '    if(n > 1)', '    {',
+      \ '        return fact(n-1) * n;', '    }', '    return 1;', '}', '', 'int main(int argc, char **argv)',
+      \ '{', '    frobnitz(fact(10));', '}'],
+      \ ['#include <stdio.h>', '', 'int fib(int n)', '{', '    if(n > 2)', '    {',
+      \ '        return fib(n-1) + fib(n-2);', '    }', '    return 1;', '}', '', '// Frobs foo heartily',
+      \ 'int frobnitz(int foo)', '{', '    int i;', '    for(i = 0; i < 10; i++)', '    {',
+      \ '        printf("%d\n", foo);', '    }', '}', '',
+      \ 'int main(int argc, char **argv)', '{', '    frobnitz(fib(10));', '}'])
+  call term_sendkeys(buf, ":diffupdate!\<cr>")
+  call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_07', {})
+
+  call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_08', {})
+
+  call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_09', {})
+
+  " Test 10-11: normal/indent-heuristic
+  call term_sendkeys(buf, ":set diffopt&vim\<cr>")
+  call WriteDiffFiles(['', '  def finalize(values)', '', '    values.each do |v|', '      v.finalize', '    end'],
+      \ ['', '  def finalize(values)', '', '    values.each do |v|', '      v.prepare', '    end', '',
+      \ '    values.each do |v|', '      v.finalize', '    end'])
+  call term_sendkeys(buf, ":diffupdate!\<cr>")
+  call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_10', {})
+
+  call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_11', {})
+
+  " Test 12: diff the same file
+  call WriteDiffFiles([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+  call VerifyBoth(buf, 'Test_diff_12', '')
+
+  " Test 13: diff an empty file
+  call WriteDiffFiles([], [])
+  call VerifyBoth(buf, 'Test_diff_13', '')
+
+  " Test 14: test diffopt+=icase
+  call WriteDiffFiles(['a', 'b', 'cd'], ['A', 'b', 'cDe'])
+  call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
+
+  " Test 15-16: test diffopt+=iwhite
+  call WriteDiffFiles(['int main()', '{', '   printf("Hello, World!");', '   return 0;', '}'],
+      \ ['int main()', '{', '   if (0)', '   {', '      printf("Hello, World!");', '      return 0;', '   }', '}'])
+  call term_sendkeys(buf, ":diffupdate!\<cr>")
+  call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_15', {})
+  call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+  call VerifyScreenDump(buf, 'Test_diff_16', {})
+
+  " clean up
+  call StopVimInTerminal(buf)
+  call delete('Xfile1')
+  call delete('Xfile2')
+endfunc
+
index 73bcab10941cefd262d48cd6716b497e7aa9ec90..9ba63af29e8abe5b5b4fb355db7224f06e8dbc68 100644 (file)
@@ -794,6 +794,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    360,
 /**/
     359,
 /**/
diff --git a/src/xdiff/COPYING b/src/xdiff/COPYING
new file mode 100644 (file)
index 0000000..f3f1b3b
--- /dev/null
@@ -0,0 +1,504 @@
+                 GNU LESSER GENERAL PUBLIC LICENSE
+                      Version 2.1, February 1999
+
+ Copyright (C) 1991, 1999 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+[This is the first released version of the Lesser GPL.  It also counts
+ as the successor of the GNU Library Public License, version 2, hence
+ the version number 2.1.]
+
+                           Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+Licenses are intended to guarantee your freedom to share and change
+free software--to make sure the software is free for all its users.
+
+  This license, the Lesser General Public License, applies to some
+specially designated software packages--typically libraries--of the
+Free Software Foundation and other authors who decide to use it.  You
+can use it too, but we suggest you first think carefully about whether
+this license or the ordinary General Public License is the better
+strategy to use in any particular case, based on the explanations below.
+
+  When we speak of free software, we are referring to freedom of use,
+not price.  Our General Public Licenses are designed to make sure that
+you have the freedom to distribute copies of free software (and charge
+for this service if you wish); that you receive source code or can get
+it if you want it; that you can change the software and use pieces of
+it in new free programs; and that you are informed that you can do
+these things.
+
+  To protect your rights, we need to make restrictions that forbid
+distributors to deny you these rights or to ask you to surrender these
+rights.  These restrictions translate to certain responsibilities for
+you if you distribute copies of the library or if you modify it.
+
+  For example, if you distribute copies of the library, whether gratis
+or for a fee, you must give the recipients all the rights that we gave
+you.  You must make sure that they, too, receive or can get the source
+code.  If you link other code with the library, you must provide
+complete object files to the recipients, so that they can relink them
+with the library after making changes to the library and recompiling
+it.  And you must show them these terms so they know their rights.
+
+  We protect your rights with a two-step method: (1) we copyright the
+library, and (2) we offer you this license, which gives you legal
+permission to copy, distribute and/or modify the library.
+
+  To protect each distributor, we want to make it very clear that
+there is no warranty for the free library.  Also, if the library is
+modified by someone else and passed on, the recipients should know
+that what they have is not the original version, so that the original
+author's reputation will not be affected by problems that might be
+introduced by others.
+
+  Finally, software patents pose a constant threat to the existence of
+any free program.  We wish to make sure that a company cannot
+effectively restrict the users of a free program by obtaining a
+restrictive license from a patent holder.  Therefore, we insist that
+any patent license obtained for a version of the library must be
+consistent with the full freedom of use specified in this license.
+
+  Most GNU software, including some libraries, is covered by the
+ordinary GNU General Public License.  This license, the GNU Lesser
+General Public License, applies to certain designated libraries, and
+is quite different from the ordinary General Public License.  We use
+this license for certain libraries in order to permit linking those
+libraries into non-free programs.
+
+  When a program is linked with a library, whether statically or using
+a shared library, the combination of the two is legally speaking a
+combined work, a derivative of the original library.  The ordinary
+General Public License therefore permits such linking only if the
+entire combination fits its criteria of freedom.  The Lesser General
+Public License permits more lax criteria for linking other code with
+the library.
+
+  We call this license the "Lesser" General Public License because it
+does Less to protect the user's freedom than the ordinary General
+Public License.  It also provides other free software developers Less
+of an advantage over competing non-free programs.  These disadvantages
+are the reason we use the ordinary General Public License for many
+libraries.  However, the Lesser license provides advantages in certain
+special circumstances.
+
+  For example, on rare occasions, there may be a special need to
+encourage the widest possible use of a certain library, so that it becomes
+a de-facto standard.  To achieve this, non-free programs must be
+allowed to use the library.  A more frequent case is that a free
+library does the same job as widely used non-free libraries.  In this
+case, there is little to gain by limiting the free library to free
+software only, so we use the Lesser General Public License.
+
+  In other cases, permission to use a particular library in non-free
+programs enables a greater number of people to use a large body of
+free software.  For example, permission to use the GNU C Library in
+non-free programs enables many more people to use the whole GNU
+operating system, as well as its variant, the GNU/Linux operating
+system.
+
+  Although the Lesser General Public License is Less protective of the
+users' freedom, it does ensure that the user of a program that is
+linked with the Library has the freedom and the wherewithal to run
+that program using a modified version of the Library.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.  Pay close attention to the difference between a
+"work based on the library" and a "work that uses the library".  The
+former contains code derived from the library, whereas the latter must
+be combined with the library in order to run.
+
+                 GNU LESSER GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License Agreement applies to any software library or other
+program which contains a notice placed by the copyright holder or
+other authorized party saying it may be distributed under the terms of
+this Lesser General Public License (also called "this License").
+Each licensee is addressed as "you".
+
+  A "library" means a collection of software functions and/or data
+prepared so as to be conveniently linked with application programs
+(which use some of those functions and data) to form executables.
+
+  The "Library", below, refers to any such software library or work
+which has been distributed under these terms.  A "work based on the
+Library" means either the Library or any derivative work under
+copyright law: that is to say, a work containing the Library or a
+portion of it, either verbatim or with modifications and/or translated
+straightforwardly into another language.  (Hereinafter, translation is
+included without limitation in the term "modification".)
+
+  "Source code" for a work means the preferred form of the work for
+making modifications to it.  For a library, complete source code means
+all the source code for all modules it contains, plus any associated
+interface definition files, plus the scripts used to control compilation
+and installation of the library.
+
+  Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running a program using the Library is not restricted, and output from
+such a program is covered only if its contents constitute a work based
+on the Library (independent of the use of the Library in a tool for
+writing it).  Whether that is true depends on what the Library does
+and what the program that uses the Library does.
+  
+  1. You may copy and distribute verbatim copies of the Library's
+complete source code as you receive it, in any medium, provided that
+you conspicuously and appropriately publish on each copy an
+appropriate copyright notice and disclaimer of warranty; keep intact
+all the notices that refer to this License and to the absence of any
+warranty; and distribute a copy of this License along with the
+Library.
+
+  You may charge a fee for the physical act of transferring a copy,
+and you may at your option offer warranty protection in exchange for a
+fee.
+
+  2. You may modify your copy or copies of the Library or any portion
+of it, thus forming a work based on the Library, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) The modified work must itself be a software library.
+
+    b) You must cause the files modified to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    c) You must cause the whole of the work to be licensed at no
+    charge to all third parties under the terms of this License.
+
+    d) If a facility in the modified Library refers to a function or a
+    table of data to be supplied by an application program that uses
+    the facility, other than as an argument passed when the facility
+    is invoked, then you must make a good faith effort to ensure that,
+    in the event an application does not supply such function or
+    table, the facility still operates, and performs whatever part of
+    its purpose remains meaningful.
+
+    (For example, a function in a library to compute square roots has
+    a purpose that is entirely well-defined independent of the
+    application.  Therefore, Subsection 2d requires that any
+    application-supplied function or table used by this function must
+    be optional: if the application does not supply it, the square
+    root function must still compute square roots.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Library,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Library, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote
+it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Library.
+
+In addition, mere aggregation of another work not based on the Library
+with the Library (or with a work based on the Library) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may opt to apply the terms of the ordinary GNU General Public
+License instead of this License to a given copy of the Library.  To do
+this, you must alter all the notices that refer to this License, so
+that they refer to the ordinary GNU General Public License, version 2,
+instead of to this License.  (If a newer version than version 2 of the
+ordinary GNU General Public License has appeared, then you can specify
+that version instead if you wish.)  Do not make any other change in
+these notices.
+
+  Once this change is made in a given copy, it is irreversible for
+that copy, so the ordinary GNU General Public License applies to all
+subsequent copies and derivative works made from that copy.
+
+  This option is useful when you wish to copy part of the code of
+the Library into a program that is not a library.
+
+  4. You may copy and distribute the Library (or a portion or
+derivative of it, under Section 2) in object code or executable form
+under the terms of Sections 1 and 2 above provided that you accompany
+it with the complete corresponding machine-readable source code, which
+must be distributed under the terms of Sections 1 and 2 above on a
+medium customarily used for software interchange.
+
+  If distribution of object code is made by offering access to copy
+from a designated place, then offering equivalent access to copy the
+source code from the same place satisfies the requirement to
+distribute the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  5. A program that contains no derivative of any portion of the
+Library, but is designed to work with the Library by being compiled or
+linked with it, is called a "work that uses the Library".  Such a
+work, in isolation, is not a derivative work of the Library, and
+therefore falls outside the scope of this License.
+
+  However, linking a "work that uses the Library" with the Library
+creates an executable that is a derivative of the Library (because it
+contains portions of the Library), rather than a "work that uses the
+library".  The executable is therefore covered by this License.
+Section 6 states terms for distribution of such executables.
+
+  When a "work that uses the Library" uses material from a header file
+that is part of the Library, the object code for the work may be a
+derivative work of the Library even though the source code is not.
+Whether this is true is especially significant if the work can be
+linked without the Library, or if the work is itself a library.  The
+threshold for this to be true is not precisely defined by law.
+
+  If such an object file uses only numerical parameters, data
+structure layouts and accessors, and small macros and small inline
+functions (ten lines or less in length), then the use of the object
+file is unrestricted, regardless of whether it is legally a derivative
+work.  (Executables containing this object code plus portions of the
+Library will still fall under Section 6.)
+
+  Otherwise, if the work is a derivative of the Library, you may
+distribute the object code for the work under the terms of Section 6.
+Any executables containing that work also fall under Section 6,
+whether or not they are linked directly with the Library itself.
+
+  6. As an exception to the Sections above, you may also combine or
+link a "work that uses the Library" with the Library to produce a
+work containing portions of the Library, and distribute that work
+under terms of your choice, provided that the terms permit
+modification of the work for the customer's own use and reverse
+engineering for debugging such modifications.
+
+  You must give prominent notice with each copy of the work that the
+Library is used in it and that the Library and its use are covered by
+this License.  You must supply a copy of this License.  If the work
+during execution displays copyright notices, you must include the
+copyright notice for the Library among them, as well as a reference
+directing the user to the copy of this License.  Also, you must do one
+of these things:
+
+    a) Accompany the work with the complete corresponding
+    machine-readable source code for the Library including whatever
+    changes were used in the work (which must be distributed under
+    Sections 1 and 2 above); and, if the work is an executable linked
+    with the Library, with the complete machine-readable "work that
+    uses the Library", as object code and/or source code, so that the
+    user can modify the Library and then relink to produce a modified
+    executable containing the modified Library.  (It is understood
+    that the user who changes the contents of definitions files in the
+    Library will not necessarily be able to recompile the application
+    to use the modified definitions.)
+
+    b) Use a suitable shared library mechanism for linking with the
+    Library.  A suitable mechanism is one that (1) uses at run time a
+    copy of the library already present on the user's computer system,
+    rather than copying library functions into the executable, and (2)
+    will operate properly with a modified version of the library, if
+    the user installs one, as long as the modified version is
+    interface-compatible with the version that the work was made with.
+
+    c) Accompany the work with a written offer, valid for at
+    least three years, to give the same user the materials
+    specified in Subsection 6a, above, for a charge no more
+    than the cost of performing this distribution.
+
+    d) If distribution of the work is made by offering access to copy
+    from a designated place, offer equivalent access to copy the above
+    specified materials from the same place.
+
+    e) Verify that the user has already received a copy of these
+    materials or that you have already sent this user a copy.
+
+  For an executable, the required form of the "work that uses the
+Library" must include any data and utility programs needed for
+reproducing the executable from it.  However, as a special exception,
+the materials to be distributed need not include anything that is
+normally distributed (in either source or binary form) with the major
+components (compiler, kernel, and so on) of the operating system on
+which the executable runs, unless that component itself accompanies
+the executable.
+
+  It may happen that this requirement contradicts the license
+restrictions of other proprietary libraries that do not normally
+accompany the operating system.  Such a contradiction means you cannot
+use both them and the Library together in an executable that you
+distribute.
+
+  7. You may place library facilities that are a work based on the
+Library side-by-side in a single library together with other library
+facilities not covered by this License, and distribute such a combined
+library, provided that the separate distribution of the work based on
+the Library and of the other library facilities is otherwise
+permitted, and provided that you do these two things:
+
+    a) Accompany the combined library with a copy of the same work
+    based on the Library, uncombined with any other library
+    facilities.  This must be distributed under the terms of the
+    Sections above.
+
+    b) Give prominent notice with the combined library of the fact
+    that part of it is a work based on the Library, and explaining
+    where to find the accompanying uncombined form of the same work.
+
+  8. You may not copy, modify, sublicense, link with, or distribute
+the Library except as expressly provided under this License.  Any
+attempt otherwise to copy, modify, sublicense, link with, or
+distribute the Library is void, and will automatically terminate your
+rights under this License.  However, parties who have received copies,
+or rights, from you under this License will not have their licenses
+terminated so long as such parties remain in full compliance.
+
+  9. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Library or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Library (or any work based on the
+Library), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Library or works based on it.
+
+  10. Each time you redistribute the Library (or any work based on the
+Library), the recipient automatically receives a license from the
+original licensor to copy, distribute, link with or modify the Library
+subject to these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties with
+this License.
+
+  11. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Library at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Library by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Library.
+
+If any portion of this section is held invalid or unenforceable under any
+particular circumstance, the balance of the section is intended to apply,
+and the section as a whole is intended to apply in other circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  12. If the distribution and/or use of the Library is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Library under this License may add
+an explicit geographical distribution limitation excluding those countries,
+so that distribution is permitted only in or among countries not thus
+excluded.  In such case, this License incorporates the limitation as if
+written in the body of this License.
+
+  13. The Free Software Foundation may publish revised and/or new
+versions of the Lesser General Public License from time to time.
+Such new versions will be similar in spirit to the present version,
+but may differ in detail to address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Library
+specifies a version number of this License which applies to it and
+"any later version", you have the option of following the terms and
+conditions either of that version or of any later version published by
+the Free Software Foundation.  If the Library does not specify a
+license version number, you may choose any version ever published by
+the Free Software Foundation.
+
+  14. If you wish to incorporate parts of the Library into other free
+programs whose distribution conditions are incompatible with these,
+write to the author to ask for permission.  For software which is
+copyrighted by the Free Software Foundation, write to the Free
+Software Foundation; we sometimes make exceptions for this.  Our
+decision will be guided by the two goals of preserving the free status
+of all derivatives of our free software and of promoting the sharing
+and reuse of software generally.
+
+                           NO WARRANTY
+
+  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
+KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
+LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
+WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
+AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
+FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
+CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
+LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
+RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
+FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
+SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGES.
+
+                    END OF TERMS AND CONDITIONS
+
+           How to Apply These Terms to Your New Libraries
+
+  If you develop a new library, and you want it to be of the greatest
+possible use to the public, we recommend making it free software that
+everyone can redistribute and change.  You can do so by permitting
+redistribution under these terms (or, alternatively, under the terms of the
+ordinary General Public License).
+
+  To apply these terms, attach the following notices to the library.  It is
+safest to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least the
+"copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the library's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This library is free software; you can redistribute it and/or
+    modify it under the terms of the GNU Lesser General Public
+    License as published by the Free Software Foundation; either
+    version 2.1 of the License, or (at your option) any later version.
+
+    This library 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
+    Lesser General Public License for more details.
+
+    You should have received a copy of the GNU Lesser General Public
+    License along with this library; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+Also add information on how to contact you by electronic and paper mail.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the library, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the
+  library `Frob' (a library for tweaking knobs) written by James Random Hacker.
+
+  <signature of Ty Coon>, 1 April 1990
+  Ty Coon, President of Vice
+
+That's all there is to it!
+
+
diff --git a/src/xdiff/README.txt b/src/xdiff/README.txt
new file mode 100644 (file)
index 0000000..f77893d
--- /dev/null
@@ -0,0 +1,14 @@
+The files in this directory come from the xdiff implementation in git.
+You can find it here: https://github.com/git/git/tree/master/xdiff
+The files were last updated 2018 September 10.
+
+This is originally based on libxdiff, which can be found here:
+http://www.xmailserver.org/xdiff-lib.html
+
+The git version was used because it has been maintained and improved.
+And since it's part of git it is expected to be reliable.
+
+The code is distributed under the GNU LGPL license.  It is included in the
+COPYING file.
+
+The first work for including xdiff in Vim was done by Christian Brabandt.
diff --git a/src/xdiff/xdiff.h b/src/xdiff/xdiff.h
new file mode 100644 (file)
index 0000000..2356da5
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XDIFF_H)
+#define XDIFF_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* #ifdef __cplusplus */
+
+/* xpparm_t.flags */
+#define XDF_NEED_MINIMAL (1 << 0)
+
+#define XDF_IGNORE_WHITESPACE (1 << 1)
+#define XDF_IGNORE_WHITESPACE_CHANGE (1 << 2)
+#define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 3)
+#define XDF_IGNORE_CR_AT_EOL (1 << 4)
+#define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | \
+                             XDF_IGNORE_WHITESPACE_CHANGE | \
+                             XDF_IGNORE_WHITESPACE_AT_EOL | \
+                             XDF_IGNORE_CR_AT_EOL)
+
+#define XDF_IGNORE_BLANK_LINES (1 << 7)
+
+#define XDF_PATIENCE_DIFF (1 << 14)
+#define XDF_HISTOGRAM_DIFF (1 << 15)
+#define XDF_DIFF_ALGORITHM_MASK (XDF_PATIENCE_DIFF | XDF_HISTOGRAM_DIFF)
+#define XDF_DIFF_ALG(x) ((x) & XDF_DIFF_ALGORITHM_MASK)
+
+#define XDF_INDENT_HEURISTIC (1 << 23)
+
+/* xdemitconf_t.flags */
+#define XDL_EMIT_FUNCNAMES (1 << 0)
+#define XDL_EMIT_FUNCCONTEXT (1 << 2)
+
+/* merge simplification levels */
+#define XDL_MERGE_MINIMAL 0
+#define XDL_MERGE_EAGER 1
+#define XDL_MERGE_ZEALOUS 2
+#define XDL_MERGE_ZEALOUS_ALNUM 3
+
+/* merge favor modes */
+#define XDL_MERGE_FAVOR_OURS 1
+#define XDL_MERGE_FAVOR_THEIRS 2
+#define XDL_MERGE_FAVOR_UNION 3
+
+/* merge output styles */
+#define XDL_MERGE_DIFF3 1
+
+typedef struct s_mmfile {
+       char *ptr;
+       long size;
+} mmfile_t;
+
+typedef struct s_mmbuffer {
+       char *ptr;
+       long size;
+} mmbuffer_t;
+
+typedef struct s_xpparam {
+       unsigned long flags;
+
+       /* See Documentation/diff-options.txt. */
+       char **anchors;
+       size_t anchors_nr;
+} xpparam_t;
+
+typedef struct s_xdemitcb {
+       void *priv;
+       int (*outf)(void *, mmbuffer_t *, int);
+} xdemitcb_t;
+
+typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long buffer_size, void *priv);
+
+typedef int (*xdl_emit_hunk_consume_func_t)(long start_a, long count_a,
+                                           long start_b, long count_b,
+                                           void *cb_data);
+
+typedef struct s_xdemitconf {
+       long ctxlen;
+       long interhunkctxlen;
+       unsigned long flags;
+       find_func_t find_func;
+       void *find_func_priv;
+       xdl_emit_hunk_consume_func_t hunk_func;
+} xdemitconf_t;
+
+typedef struct s_bdiffparam {
+       long bsize;
+} bdiffparam_t;
+
+
+#define xdl_malloc(x) malloc(x)
+#define xdl_free(ptr) free(ptr)
+#define xdl_realloc(ptr,x) realloc(ptr,x)
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size);
+long xdl_mmfile_size(mmfile_t *mmf);
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+            xdemitconf_t const *xecfg, xdemitcb_t *ecb);
+
+typedef struct s_xmparam {
+       xpparam_t xpp;
+       int marker_size;
+       int level;
+       int favor;
+       int style;
+       const char *ancestor;   /* label for orig */
+       const char *file1;      /* label for mf1 */
+       const char *file2;      /* label for mf2 */
+} xmparam_t;
+
+#define DEFAULT_CONFLICT_MARKER_SIZE 7
+
+int xdl_merge(mmfile_t *orig, mmfile_t *mf1, mmfile_t *mf2,
+               xmparam_t const *xmp, mmbuffer_t *result);
+
+#ifdef __cplusplus
+}
+#endif /* #ifdef __cplusplus */
+
+#endif /* #if !defined(XDIFF_H) */
diff --git a/src/xdiff/xdiffi.c b/src/xdiff/xdiffi.c
new file mode 100644 (file)
index 0000000..3dc90ae
--- /dev/null
@@ -0,0 +1,1043 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+#define XDL_MAX_COST_MIN 256
+#define XDL_HEUR_MIN_COST 256
+#define XDL_LINE_MAX (long)((1UL << (CHAR_BIT * sizeof(long) - 1)) - 1)
+#define XDL_SNAKE_CNT 20
+#define XDL_K_HEUR 4
+
+typedef struct s_xdpsplit {
+       long i1, i2;
+       int min_lo, min_hi;
+} xdpsplit_t;
+
+/*
+ * See "An O(ND) Difference Algorithm and its Variations", by Eugene Myers.
+ * Basically considers a "box" (off1, off2, lim1, lim2) and scan from both
+ * the forward diagonal starting from (off1, off2) and the backward diagonal
+ * starting from (lim1, lim2). If the K values on the same diagonal crosses
+ * returns the furthest point of reach. We might end up having to expensive
+ * cases using this algorithm is full, so a little bit of heuristic is needed
+ * to cut the search and to return a suboptimal point.
+ */
+static long xdl_split(unsigned long const *ha1, long off1, long lim1,
+                     unsigned long const *ha2, long off2, long lim2,
+                     long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
+                     xdalgoenv_t *xenv) {
+       long dmin = off1 - lim2, dmax = lim1 - off2;
+       long fmid = off1 - off2, bmid = lim1 - lim2;
+       long odd = (fmid - bmid) & 1;
+       long fmin = fmid, fmax = fmid;
+       long bmin = bmid, bmax = bmid;
+       long ec, d, i1, i2, prev1, best, dd, v, k;
+
+       /*
+        * Set initial diagonal values for both forward and backward path.
+        */
+       kvdf[fmid] = off1;
+       kvdb[bmid] = lim1;
+
+       for (ec = 1;; ec++) {
+               int got_snake = 0;
+
+               /*
+                * We need to extent the diagonal "domain" by one. If the next
+                * values exits the box boundaries we need to change it in the
+                * opposite direction because (max - min) must be a power of two.
+                * Also we initialize the external K value to -1 so that we can
+                * avoid extra conditions check inside the core loop.
+                */
+               if (fmin > dmin)
+                       kvdf[--fmin - 1] = -1;
+               else
+                       ++fmin;
+               if (fmax < dmax)
+                       kvdf[++fmax + 1] = -1;
+               else
+                       --fmax;
+
+               for (d = fmax; d >= fmin; d -= 2) {
+                       if (kvdf[d - 1] >= kvdf[d + 1])
+                               i1 = kvdf[d - 1] + 1;
+                       else
+                               i1 = kvdf[d + 1];
+                       prev1 = i1;
+                       i2 = i1 - d;
+                       for (; i1 < lim1 && i2 < lim2 && ha1[i1] == ha2[i2]; i1++, i2++);
+                       if (i1 - prev1 > xenv->snake_cnt)
+                               got_snake = 1;
+                       kvdf[d] = i1;
+                       if (odd && bmin <= d && d <= bmax && kvdb[d] <= i1) {
+                               spl->i1 = i1;
+                               spl->i2 = i2;
+                               spl->min_lo = spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               /*
+                * We need to extent the diagonal "domain" by one. If the next
+                * values exits the box boundaries we need to change it in the
+                * opposite direction because (max - min) must be a power of two.
+                * Also we initialize the external K value to -1 so that we can
+                * avoid extra conditions check inside the core loop.
+                */
+               if (bmin > dmin)
+                       kvdb[--bmin - 1] = XDL_LINE_MAX;
+               else
+                       ++bmin;
+               if (bmax < dmax)
+                       kvdb[++bmax + 1] = XDL_LINE_MAX;
+               else
+                       --bmax;
+
+               for (d = bmax; d >= bmin; d -= 2) {
+                       if (kvdb[d - 1] < kvdb[d + 1])
+                               i1 = kvdb[d - 1];
+                       else
+                               i1 = kvdb[d + 1] - 1;
+                       prev1 = i1;
+                       i2 = i1 - d;
+                       for (; i1 > off1 && i2 > off2 && ha1[i1 - 1] == ha2[i2 - 1]; i1--, i2--);
+                       if (prev1 - i1 > xenv->snake_cnt)
+                               got_snake = 1;
+                       kvdb[d] = i1;
+                       if (!odd && fmin <= d && d <= fmax && i1 <= kvdf[d]) {
+                               spl->i1 = i1;
+                               spl->i2 = i2;
+                               spl->min_lo = spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               if (need_min)
+                       continue;
+
+               /*
+                * If the edit cost is above the heuristic trigger and if
+                * we got a good snake, we sample current diagonals to see
+                * if some of the, have reached an "interesting" path. Our
+                * measure is a function of the distance from the diagonal
+                * corner (i1 + i2) penalized with the distance from the
+                * mid diagonal itself. If this value is above the current
+                * edit cost times a magic factor (XDL_K_HEUR) we consider
+                * it interesting.
+                */
+               if (got_snake && ec > xenv->heur_min) {
+                       for (best = 0, d = fmax; d >= fmin; d -= 2) {
+                               dd = d > fmid ? d - fmid: fmid - d;
+                               i1 = kvdf[d];
+                               i2 = i1 - d;
+                               v = (i1 - off1) + (i2 - off2) - dd;
+
+                               if (v > XDL_K_HEUR * ec && v > best &&
+                                   off1 + xenv->snake_cnt <= i1 && i1 < lim1 &&
+                                   off2 + xenv->snake_cnt <= i2 && i2 < lim2) {
+                                       for (k = 1; ha1[i1 - k] == ha2[i2 - k]; k++)
+                                               if (k == xenv->snake_cnt) {
+                                                       best = v;
+                                                       spl->i1 = i1;
+                                                       spl->i2 = i2;
+                                                       break;
+                                               }
+                               }
+                       }
+                       if (best > 0) {
+                               spl->min_lo = 1;
+                               spl->min_hi = 0;
+                               return ec;
+                       }
+
+                       for (best = 0, d = bmax; d >= bmin; d -= 2) {
+                               dd = d > bmid ? d - bmid: bmid - d;
+                               i1 = kvdb[d];
+                               i2 = i1 - d;
+                               v = (lim1 - i1) + (lim2 - i2) - dd;
+
+                               if (v > XDL_K_HEUR * ec && v > best &&
+                                   off1 < i1 && i1 <= lim1 - xenv->snake_cnt &&
+                                   off2 < i2 && i2 <= lim2 - xenv->snake_cnt) {
+                                       for (k = 0; ha1[i1 + k] == ha2[i2 + k]; k++)
+                                               if (k == xenv->snake_cnt - 1) {
+                                                       best = v;
+                                                       spl->i1 = i1;
+                                                       spl->i2 = i2;
+                                                       break;
+                                               }
+                               }
+                       }
+                       if (best > 0) {
+                               spl->min_lo = 0;
+                               spl->min_hi = 1;
+                               return ec;
+                       }
+               }
+
+               /*
+                * Enough is enough. We spent too much time here and now we collect
+                * the furthest reaching path using the (i1 + i2) measure.
+                */
+               if (ec >= xenv->mxcost) {
+                       long fbest, fbest1, bbest, bbest1;
+
+                       fbest = fbest1 = -1;
+                       for (d = fmax; d >= fmin; d -= 2) {
+                               i1 = XDL_MIN(kvdf[d], lim1);
+                               i2 = i1 - d;
+                               if (lim2 < i2)
+                                       i1 = lim2 + d, i2 = lim2;
+                               if (fbest < i1 + i2) {
+                                       fbest = i1 + i2;
+                                       fbest1 = i1;
+                               }
+                       }
+
+                       bbest = bbest1 = XDL_LINE_MAX;
+                       for (d = bmax; d >= bmin; d -= 2) {
+                               i1 = XDL_MAX(off1, kvdb[d]);
+                               i2 = i1 - d;
+                               if (i2 < off2)
+                                       i1 = off2 + d, i2 = off2;
+                               if (i1 + i2 < bbest) {
+                                       bbest = i1 + i2;
+                                       bbest1 = i1;
+                               }
+                       }
+
+                       if ((lim1 + lim2) - bbest < fbest - (off1 + off2)) {
+                               spl->i1 = fbest1;
+                               spl->i2 = fbest - fbest1;
+                               spl->min_lo = 1;
+                               spl->min_hi = 0;
+                       } else {
+                               spl->i1 = bbest1;
+                               spl->i2 = bbest - bbest1;
+                               spl->min_lo = 0;
+                               spl->min_hi = 1;
+                       }
+                       return ec;
+               }
+       }
+}
+
+
+/*
+ * Rule: "Divide et Impera". Recursively split the box in sub-boxes by calling
+ * the box splitting function. Note that the real job (marking changed lines)
+ * is done in the two boundary reaching checks.
+ */
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+                diffdata_t *dd2, long off2, long lim2,
+                long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv) {
+       unsigned long const *ha1 = dd1->ha, *ha2 = dd2->ha;
+
+       /*
+        * Shrink the box by walking through each diagonal snake (SW and NE).
+        */
+       for (; off1 < lim1 && off2 < lim2 && ha1[off1] == ha2[off2]; off1++, off2++);
+       for (; off1 < lim1 && off2 < lim2 && ha1[lim1 - 1] == ha2[lim2 - 1]; lim1--, lim2--);
+
+       /*
+        * If one dimension is empty, then all records on the other one must
+        * be obviously changed.
+        */
+       if (off1 == lim1) {
+               char *rchg2 = dd2->rchg;
+               long *rindex2 = dd2->rindex;
+
+               for (; off2 < lim2; off2++)
+                       rchg2[rindex2[off2]] = 1;
+       } else if (off2 == lim2) {
+               char *rchg1 = dd1->rchg;
+               long *rindex1 = dd1->rindex;
+
+               for (; off1 < lim1; off1++)
+                       rchg1[rindex1[off1]] = 1;
+       } else {
+               xdpsplit_t spl;
+               spl.i1 = spl.i2 = 0;
+
+               /*
+                * Divide ...
+                */
+               if (xdl_split(ha1, off1, lim1, ha2, off2, lim2, kvdf, kvdb,
+                             need_min, &spl, xenv) < 0) {
+
+                       return -1;
+               }
+
+               /*
+                * ... et Impera.
+                */
+               if (xdl_recs_cmp(dd1, off1, spl.i1, dd2, off2, spl.i2,
+                                kvdf, kvdb, spl.min_lo, xenv) < 0 ||
+                   xdl_recs_cmp(dd1, spl.i1, lim1, dd2, spl.i2, lim2,
+                                kvdf, kvdb, spl.min_hi, xenv) < 0) {
+
+                       return -1;
+               }
+       }
+
+       return 0;
+}
+
+
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *xe) {
+       long ndiags;
+       long *kvd, *kvdf, *kvdb;
+       xdalgoenv_t xenv;
+       diffdata_t dd1, dd2;
+
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_PATIENCE_DIFF)
+               return xdl_do_patience_diff(mf1, mf2, xpp, xe);
+
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+               return xdl_do_histogram_diff(mf1, mf2, xpp, xe);
+
+       if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) {
+
+               return -1;
+       }
+
+       /*
+        * Allocate and setup K vectors to be used by the differential algorithm.
+        * One is to store the forward path and one to store the backward path.
+        */
+       ndiags = xe->xdf1.nreff + xe->xdf2.nreff + 3;
+       if (!(kvd = (long *) xdl_malloc((2 * ndiags + 2) * sizeof(long)))) {
+
+               xdl_free_env(xe);
+               return -1;
+       }
+       kvdf = kvd;
+       kvdb = kvdf + ndiags;
+       kvdf += xe->xdf2.nreff + 1;
+       kvdb += xe->xdf2.nreff + 1;
+
+       xenv.mxcost = xdl_bogosqrt(ndiags);
+       if (xenv.mxcost < XDL_MAX_COST_MIN)
+               xenv.mxcost = XDL_MAX_COST_MIN;
+       xenv.snake_cnt = XDL_SNAKE_CNT;
+       xenv.heur_min = XDL_HEUR_MIN_COST;
+
+       dd1.nrec = xe->xdf1.nreff;
+       dd1.ha = xe->xdf1.ha;
+       dd1.rchg = xe->xdf1.rchg;
+       dd1.rindex = xe->xdf1.rindex;
+       dd2.nrec = xe->xdf2.nreff;
+       dd2.ha = xe->xdf2.ha;
+       dd2.rchg = xe->xdf2.rchg;
+       dd2.rindex = xe->xdf2.rindex;
+
+       if (xdl_recs_cmp(&dd1, 0, dd1.nrec, &dd2, 0, dd2.nrec,
+                        kvdf, kvdb, (xpp->flags & XDF_NEED_MINIMAL) != 0, &xenv) < 0) {
+
+               xdl_free(kvd);
+               xdl_free_env(xe);
+               return -1;
+       }
+
+       xdl_free(kvd);
+
+       return 0;
+}
+
+
+static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2) {
+       xdchange_t *xch;
+
+       if (!(xch = (xdchange_t *) xdl_malloc(sizeof(xdchange_t))))
+               return NULL;
+
+       xch->next = xscr;
+       xch->i1 = i1;
+       xch->i2 = i2;
+       xch->chg1 = chg1;
+       xch->chg2 = chg2;
+       xch->ignore = 0;
+
+       return xch;
+}
+
+
+static int recs_match(xrecord_t *rec1, xrecord_t *rec2, long flags)
+{
+       return (rec1->ha == rec2->ha &&
+               xdl_recmatch(rec1->ptr, rec1->size,
+                            rec2->ptr, rec2->size,
+                            flags));
+}
+
+/*
+ * If a line is indented more than this, get_indent() just returns this value.
+ * This avoids having to do absurd amounts of work for data that are not
+ * human-readable text, and also ensures that the output of get_indent fits within
+ * an int.
+ */
+#define MAX_INDENT 200
+
+/*
+ * Return the amount of indentation of the specified line, treating TAB as 8
+ * columns. Return -1 if line is empty or contains only whitespace. Clamp the
+ * output value at MAX_INDENT.
+ */
+static int get_indent(xrecord_t *rec)
+{
+       long i;
+       int ret = 0;
+
+       for (i = 0; i < rec->size; i++) {
+               char c = rec->ptr[i];
+
+               if (!XDL_ISSPACE(c))
+                       return ret;
+               else if (c == ' ')
+                       ret += 1;
+               else if (c == '\t')
+                       ret += 8 - ret % 8;
+               /* ignore other whitespace characters */
+
+               if (ret >= MAX_INDENT)
+                       return MAX_INDENT;
+       }
+
+       /* The line contains only whitespace. */
+       return -1;
+}
+
+/*
+ * If more than this number of consecutive blank rows are found, just return this
+ * value. This avoids requiring O(N^2) work for pathological cases, and also
+ * ensures that the output of score_split fits in an int.
+ */
+#define MAX_BLANKS 20
+
+/* Characteristics measured about a hypothetical split position. */
+struct split_measurement {
+       /*
+        * Is the split at the end of the file (aside from any blank lines)?
+        */
+       int end_of_file;
+
+       /*
+        * How much is the line immediately following the split indented (or -1 if
+        * the line is blank):
+        */
+       int indent;
+
+       /*
+        * How many consecutive lines above the split are blank?
+        */
+       int pre_blank;
+
+       /*
+        * How much is the nearest non-blank line above the split indented (or -1
+        * if there is no such line)?
+        */
+       int pre_indent;
+
+       /*
+        * How many lines after the line following the split are blank?
+        */
+       int post_blank;
+
+       /*
+        * How much is the nearest non-blank line after the line following the
+        * split indented (or -1 if there is no such line)?
+        */
+       int post_indent;
+};
+
+struct split_score {
+       /* The effective indent of this split (smaller is preferred). */
+       int effective_indent;
+
+       /* Penalty for this split (smaller is preferred). */
+       int penalty;
+};
+
+/*
+ * Fill m with information about a hypothetical split of xdf above line split.
+ */
+static void measure_split(const xdfile_t *xdf, long split,
+                         struct split_measurement *m)
+{
+       long i;
+
+       if (split >= xdf->nrec) {
+               m->end_of_file = 1;
+               m->indent = -1;
+       } else {
+               m->end_of_file = 0;
+               m->indent = get_indent(xdf->recs[split]);
+       }
+
+       m->pre_blank = 0;
+       m->pre_indent = -1;
+       for (i = split - 1; i >= 0; i--) {
+               m->pre_indent = get_indent(xdf->recs[i]);
+               if (m->pre_indent != -1)
+                       break;
+               m->pre_blank += 1;
+               if (m->pre_blank == MAX_BLANKS) {
+                       m->pre_indent = 0;
+                       break;
+               }
+       }
+
+       m->post_blank = 0;
+       m->post_indent = -1;
+       for (i = split + 1; i < xdf->nrec; i++) {
+               m->post_indent = get_indent(xdf->recs[i]);
+               if (m->post_indent != -1)
+                       break;
+               m->post_blank += 1;
+               if (m->post_blank == MAX_BLANKS) {
+                       m->post_indent = 0;
+                       break;
+               }
+       }
+}
+
+/*
+ * The empirically-determined weight factors used by score_split() below.
+ * Larger values means that the position is a less favorable place to split.
+ *
+ * Note that scores are only ever compared against each other, so multiplying
+ * all of these weight/penalty values by the same factor wouldn't change the
+ * heuristic's behavior. Still, we need to set that arbitrary scale *somehow*.
+ * In practice, these numbers are chosen to be large enough that they can be
+ * adjusted relative to each other with sufficient precision despite using
+ * integer math.
+ */
+
+/* Penalty if there are no non-blank lines before the split */
+#define START_OF_FILE_PENALTY 1
+
+/* Penalty if there are no non-blank lines after the split */
+#define END_OF_FILE_PENALTY 21
+
+/* Multiplier for the number of blank lines around the split */
+#define TOTAL_BLANK_WEIGHT (-30)
+
+/* Multiplier for the number of blank lines after the split */
+#define POST_BLANK_WEIGHT 6
+
+/*
+ * Penalties applied if the line is indented more than its predecessor
+ */
+#define RELATIVE_INDENT_PENALTY (-4)
+#define RELATIVE_INDENT_WITH_BLANK_PENALTY 10
+
+/*
+ * Penalties applied if the line is indented less than both its predecessor and
+ * its successor
+ */
+#define RELATIVE_OUTDENT_PENALTY 24
+#define RELATIVE_OUTDENT_WITH_BLANK_PENALTY 17
+
+/*
+ * Penalties applied if the line is indented less than its predecessor but not
+ * less than its successor
+ */
+#define RELATIVE_DEDENT_PENALTY 23
+#define RELATIVE_DEDENT_WITH_BLANK_PENALTY 17
+
+/*
+ * We only consider whether the sum of the effective indents for splits are
+ * less than (-1), equal to (0), or greater than (+1) each other. The resulting
+ * value is multiplied by the following weight and combined with the penalty to
+ * determine the better of two scores.
+ */
+#define INDENT_WEIGHT 60
+
+/*
+ * How far do we slide a hunk at most?
+ */
+#define INDENT_HEURISTIC_MAX_SLIDING 100
+
+/*
+ * Compute a badness score for the hypothetical split whose measurements are
+ * stored in m. The weight factors were determined empirically using the tools and
+ * corpus described in
+ *
+ *     https://github.com/mhagger/diff-slider-tools
+ *
+ * Also see that project if you want to improve the weights based on, for example,
+ * a larger or more diverse corpus.
+ */
+static void score_add_split(const struct split_measurement *m, struct split_score *s)
+{
+       /*
+        * A place to accumulate penalty factors (positive makes this index more
+        * favored):
+        */
+       int post_blank, total_blank, indent, any_blanks;
+
+       if (m->pre_indent == -1 && m->pre_blank == 0)
+               s->penalty += START_OF_FILE_PENALTY;
+
+       if (m->end_of_file)
+               s->penalty += END_OF_FILE_PENALTY;
+
+       /*
+        * Set post_blank to the number of blank lines following the split,
+        * including the line immediately after the split:
+        */
+       post_blank = (m->indent == -1) ? 1 + m->post_blank : 0;
+       total_blank = m->pre_blank + post_blank;
+
+       /* Penalties based on nearby blank lines: */
+       s->penalty += TOTAL_BLANK_WEIGHT * total_blank;
+       s->penalty += POST_BLANK_WEIGHT * post_blank;
+
+       if (m->indent != -1)
+               indent = m->indent;
+       else
+               indent = m->post_indent;
+
+       any_blanks = (total_blank != 0);
+
+       /* Note that the effective indent is -1 at the end of the file: */
+       s->effective_indent += indent;
+
+       if (indent == -1) {
+               /* No additional adjustments needed. */
+       } else if (m->pre_indent == -1) {
+               /* No additional adjustments needed. */
+       } else if (indent > m->pre_indent) {
+               /*
+                * The line is indented more than its predecessor.
+                */
+               s->penalty += any_blanks ?
+                       RELATIVE_INDENT_WITH_BLANK_PENALTY :
+                       RELATIVE_INDENT_PENALTY;
+       } else if (indent == m->pre_indent) {
+               /*
+                * The line has the same indentation level as its predecessor.
+                * No additional adjustments needed.
+                */
+       } else {
+               /*
+                * The line is indented less than its predecessor. It could be
+                * the block terminator of the previous block, but it could
+                * also be the start of a new block (e.g., an "else" block, or
+                * maybe the previous block didn't have a block terminator).
+                * Try to distinguish those cases based on what comes next:
+                */
+               if (m->post_indent != -1 && m->post_indent > indent) {
+                       /*
+                        * The following line is indented more. So it is likely
+                        * that this line is the start of a block.
+                        */
+                       s->penalty += any_blanks ?
+                               RELATIVE_OUTDENT_WITH_BLANK_PENALTY :
+                               RELATIVE_OUTDENT_PENALTY;
+               } else {
+                       /*
+                        * That was probably the end of a block.
+                        */
+                       s->penalty += any_blanks ?
+                               RELATIVE_DEDENT_WITH_BLANK_PENALTY :
+                               RELATIVE_DEDENT_PENALTY;
+               }
+       }
+}
+
+static int score_cmp(struct split_score *s1, struct split_score *s2)
+{
+       /* -1 if s1.effective_indent < s2->effective_indent, etc. */
+       int cmp_indents = ((s1->effective_indent > s2->effective_indent) -
+                          (s1->effective_indent < s2->effective_indent));
+
+       return INDENT_WEIGHT * cmp_indents + (s1->penalty - s2->penalty);
+}
+
+/*
+ * Represent a group of changed lines in an xdfile_t (i.e., a contiguous group
+ * of lines that was inserted or deleted from the corresponding version of the
+ * file). We consider there to be such a group at the beginning of the file, at
+ * the end of the file, and between any two unchanged lines, though most such
+ * groups will usually be empty.
+ *
+ * If the first line in a group is equal to the line following the group, then
+ * the group can be slid down. Similarly, if the last line in a group is equal
+ * to the line preceding the group, then the group can be slid up. See
+ * group_slide_down() and group_slide_up().
+ *
+ * Note that loops that are testing for changed lines in xdf->rchg do not need
+ * index bounding since the array is prepared with a zero at position -1 and N.
+ */
+struct xdlgroup {
+       /*
+        * The index of the first changed line in the group, or the index of
+        * the unchanged line above which the (empty) group is located.
+        */
+       long start;
+
+       /*
+        * The index of the first unchanged line after the group. For an empty
+        * group, end is equal to start.
+        */
+       long end;
+};
+
+/*
+ * Initialize g to point at the first group in xdf.
+ */
+static void group_init(xdfile_t *xdf, struct xdlgroup *g)
+{
+       g->start = g->end = 0;
+       while (xdf->rchg[g->end])
+               g->end++;
+}
+
+/*
+ * Move g to describe the next (possibly empty) group in xdf and return 0. If g
+ * is already at the end of the file, do nothing and return -1.
+ */
+static inline int group_next(xdfile_t *xdf, struct xdlgroup *g)
+{
+       if (g->end == xdf->nrec)
+               return -1;
+
+       g->start = g->end + 1;
+       for (g->end = g->start; xdf->rchg[g->end]; g->end++)
+               ;
+
+       return 0;
+}
+
+/*
+ * Move g to describe the previous (possibly empty) group in xdf and return 0.
+ * If g is already at the beginning of the file, do nothing and return -1.
+ */
+static inline int group_previous(xdfile_t *xdf, struct xdlgroup *g)
+{
+       if (g->start == 0)
+               return -1;
+
+       g->end = g->start - 1;
+       for (g->start = g->end; xdf->rchg[g->start - 1]; g->start--)
+               ;
+
+       return 0;
+}
+
+/*
+ * If g can be slid toward the end of the file, do so, and if it bumps into a
+ * following group, expand this group to include it. Return 0 on success or -1
+ * if g cannot be slid down.
+ */
+static int group_slide_down(xdfile_t *xdf, struct xdlgroup *g, long flags)
+{
+       if (g->end < xdf->nrec &&
+           recs_match(xdf->recs[g->start], xdf->recs[g->end], flags)) {
+               xdf->rchg[g->start++] = 0;
+               xdf->rchg[g->end++] = 1;
+
+               while (xdf->rchg[g->end])
+                       g->end++;
+
+               return 0;
+       } else {
+               return -1;
+       }
+}
+
+/*
+ * If g can be slid toward the beginning of the file, do so, and if it bumps
+ * into a previous group, expand this group to include it. Return 0 on success
+ * or -1 if g cannot be slid up.
+ */
+static int group_slide_up(xdfile_t *xdf, struct xdlgroup *g, long flags)
+{
+       if (g->start > 0 &&
+           recs_match(xdf->recs[g->start - 1], xdf->recs[g->end - 1], flags)) {
+               xdf->rchg[--g->start] = 1;
+               xdf->rchg[--g->end] = 0;
+
+               while (xdf->rchg[g->start - 1])
+                       g->start--;
+
+               return 0;
+       } else {
+               return -1;
+       }
+}
+
+static void xdl_bug(const char *msg)
+{
+       fprintf(stderr, "BUG: %s\n", msg);
+       exit(1);
+}
+
+/*
+ * Move back and forward change groups for a consistent and pretty diff output.
+ * This also helps in finding joinable change groups and reducing the diff
+ * size.
+ */
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags) {
+       struct xdlgroup g, go;
+       long earliest_end, end_matching_other;
+       long groupsize;
+
+       group_init(xdf, &g);
+       group_init(xdfo, &go);
+
+       while (1) {
+               /* If the group is empty in the to-be-compacted file, skip it: */
+               if (g.end == g.start)
+                       goto next;
+
+               /*
+                * Now shift the change up and then down as far as possible in
+                * each direction. If it bumps into any other changes, merge them.
+                */
+               do {
+                       groupsize = g.end - g.start;
+
+                       /*
+                        * Keep track of the last "end" index that causes this
+                        * group to align with a group of changed lines in the
+                        * other file. -1 indicates that we haven't found such
+                        * a match yet:
+                        */
+                       end_matching_other = -1;
+
+                       /* Shift the group backward as much as possible: */
+                       while (!group_slide_up(xdf, &g, flags))
+                               if (group_previous(xdfo, &go))
+                                       xdl_bug("group sync broken sliding up");
+
+                       /*
+                        * This is this highest that this group can be shifted.
+                        * Record its end index:
+                        */
+                       earliest_end = g.end;
+
+                       if (go.end > go.start)
+                               end_matching_other = g.end;
+
+                       /* Now shift the group forward as far as possible: */
+                       while (1) {
+                               if (group_slide_down(xdf, &g, flags))
+                                       break;
+                               if (group_next(xdfo, &go))
+                                       xdl_bug("group sync broken sliding down");
+
+                               if (go.end > go.start)
+                                       end_matching_other = g.end;
+                       }
+               } while (groupsize != g.end - g.start);
+
+               /*
+                * If the group can be shifted, then we can possibly use this
+                * freedom to produce a more intuitive diff.
+                *
+                * The group is currently shifted as far down as possible, so the
+                * heuristics below only have to handle upwards shifts.
+                */
+
+               if (g.end == earliest_end) {
+                       /* no shifting was possible */
+               } else if (end_matching_other != -1) {
+                       /*
+                        * Move the possibly merged group of changes back to line
+                        * up with the last group of changes from the other file
+                        * that it can align with.
+                        */
+                       while (go.end == go.start) {
+                               if (group_slide_up(xdf, &g, flags))
+                                       xdl_bug("match disappeared");
+                               if (group_previous(xdfo, &go))
+                                       xdl_bug("group sync broken sliding to match");
+                       }
+               } else if (flags & XDF_INDENT_HEURISTIC) {
+                       /*
+                        * Indent heuristic: a group of pure add/delete lines
+                        * implies two splits, one between the end of the "before"
+                        * context and the start of the group, and another between
+                        * the end of the group and the beginning of the "after"
+                        * context. Some splits are aesthetically better and some
+                        * are worse. We compute a badness "score" for each split,
+                        * and add the scores for the two splits to define a
+                        * "score" for each position that the group can be shifted
+                        * to. Then we pick the shift with the lowest score.
+                        */
+                       long shift, best_shift = -1;
+                       struct split_score best_score;
+
+                       shift = earliest_end;
+                       if (g.end - groupsize - 1 > shift)
+                               shift = g.end - groupsize - 1;
+                       if (g.end - INDENT_HEURISTIC_MAX_SLIDING > shift)
+                               shift = g.end - INDENT_HEURISTIC_MAX_SLIDING;
+                       for (; shift <= g.end; shift++) {
+                               struct split_measurement m;
+                               struct split_score score = {0, 0};
+
+                               measure_split(xdf, shift, &m);
+                               score_add_split(&m, &score);
+                               measure_split(xdf, shift - groupsize, &m);
+                               score_add_split(&m, &score);
+                               if (best_shift == -1 ||
+                                   score_cmp(&score, &best_score) <= 0) {
+                                       best_score.effective_indent = score.effective_indent;
+                                       best_score.penalty = score.penalty;
+                                       best_shift = shift;
+                               }
+                       }
+
+                       while (g.end > best_shift) {
+                               if (group_slide_up(xdf, &g, flags))
+                                       xdl_bug("best shift unreached");
+                               if (group_previous(xdfo, &go))
+                                       xdl_bug("group sync broken sliding to blank line");
+                       }
+               }
+
+       next:
+               /* Move past the just-processed group: */
+               if (group_next(xdf, &g))
+                       break;
+               if (group_next(xdfo, &go))
+                       xdl_bug("group sync broken moving to next group");
+       }
+
+       if (!group_next(xdfo, &go))
+               xdl_bug("group sync broken at end of file");
+
+       return 0;
+}
+
+
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr) {
+       xdchange_t *cscr = NULL, *xch;
+       char *rchg1 = xe->xdf1.rchg, *rchg2 = xe->xdf2.rchg;
+       long i1, i2, l1, l2;
+
+       /*
+        * Trivial. Collects "groups" of changes and creates an edit script.
+        */
+       for (i1 = xe->xdf1.nrec, i2 = xe->xdf2.nrec; i1 >= 0 || i2 >= 0; i1--, i2--)
+               if (rchg1[i1 - 1] || rchg2[i2 - 1]) {
+                       for (l1 = i1; rchg1[i1 - 1]; i1--);
+                       for (l2 = i2; rchg2[i2 - 1]; i2--);
+
+                       if (!(xch = xdl_add_change(cscr, i1, i2, l1 - i1, l2 - i2))) {
+                               xdl_free_script(cscr);
+                               return -1;
+                       }
+                       cscr = xch;
+               }
+
+       *xscr = cscr;
+
+       return 0;
+}
+
+
+void xdl_free_script(xdchange_t *xscr) {
+       xdchange_t *xch;
+
+       while ((xch = xscr) != NULL) {
+               xscr = xscr->next;
+               xdl_free(xch);
+       }
+}
+
+static int xdl_call_hunk_func(xdfenv_t *xe UNUSED, xdchange_t *xscr, xdemitcb_t *ecb,
+                             xdemitconf_t const *xecfg)
+{
+       xdchange_t *xch, *xche;
+
+       for (xch = xscr; xch; xch = xche->next) {
+               xche = xdl_get_hunk(&xch, xecfg);
+               if (!xch)
+                       break;
+               if (xecfg->hunk_func(xch->i1, xche->i1 + xche->chg1 - xch->i1,
+                                    xch->i2, xche->i2 + xche->chg2 - xch->i2,
+                                    ecb->priv) < 0)
+                       return -1;
+       }
+       return 0;
+}
+
+static void xdl_mark_ignorable(xdchange_t *xscr, xdfenv_t *xe, long flags)
+{
+       xdchange_t *xch;
+
+       for (xch = xscr; xch; xch = xch->next) {
+               int ignore = 1;
+               xrecord_t **rec;
+               long i;
+
+               rec = &xe->xdf1.recs[xch->i1];
+               for (i = 0; i < xch->chg1 && ignore; i++)
+                       ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+               rec = &xe->xdf2.recs[xch->i2];
+               for (i = 0; i < xch->chg2 && ignore; i++)
+                       ignore = xdl_blankline(rec[i]->ptr, rec[i]->size, flags);
+
+               xch->ignore = ignore;
+       }
+}
+
+int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+            xdemitconf_t const *xecfg, xdemitcb_t *ecb) {
+       xdchange_t *xscr;
+       xdfenv_t xe;
+       emit_func_t ef = xecfg->hunk_func ? xdl_call_hunk_func : xdl_emit_diff;
+
+       if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) {
+
+               return -1;
+       }
+       if (xdl_change_compact(&xe.xdf1, &xe.xdf2, xpp->flags) < 0 ||
+           xdl_change_compact(&xe.xdf2, &xe.xdf1, xpp->flags) < 0 ||
+           xdl_build_script(&xe, &xscr) < 0) {
+
+               xdl_free_env(&xe);
+               return -1;
+       }
+       if (xscr) {
+               if (xpp->flags & XDF_IGNORE_BLANK_LINES)
+                       xdl_mark_ignorable(xscr, &xe, xpp->flags);
+
+               if (ef(&xe, xscr, ecb, xecfg) < 0) {
+
+                       xdl_free_script(xscr);
+                       xdl_free_env(&xe);
+                       return -1;
+               }
+               xdl_free_script(xscr);
+       }
+       xdl_free_env(&xe);
+
+       return 0;
+}
diff --git a/src/xdiff/xdiffi.h b/src/xdiff/xdiffi.h
new file mode 100644 (file)
index 0000000..8f1c7c8
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XDIFFI_H)
+#define XDIFFI_H
+
+
+typedef struct s_diffdata {
+       long nrec;
+       unsigned long const *ha;
+       long *rindex;
+       char *rchg;
+} diffdata_t;
+
+typedef struct s_xdalgoenv {
+       long mxcost;
+       long snake_cnt;
+       long heur_min;
+} xdalgoenv_t;
+
+typedef struct s_xdchange {
+       struct s_xdchange *next;
+       long i1, i2;
+       long chg1, chg2;
+       int ignore;
+} xdchange_t;
+
+
+
+int xdl_recs_cmp(diffdata_t *dd1, long off1, long lim1,
+                diffdata_t *dd2, long off2, long lim2,
+                long *kvdf, long *kvdb, int need_min, xdalgoenv_t *xenv);
+int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *xe);
+int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo, long flags);
+int xdl_build_script(xdfenv_t *xe, xdchange_t **xscr);
+void xdl_free_script(xdchange_t *xscr);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                 xdemitconf_t const *xecfg);
+int xdl_do_patience_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *env);
+int xdl_do_histogram_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+               xdfenv_t *env);
+
+#endif /* #if !defined(XDIFFI_H) */
diff --git a/src/xdiff/xemit.c b/src/xdiff/xemit.c
new file mode 100644 (file)
index 0000000..718b3ae
--- /dev/null
@@ -0,0 +1,312 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec) {
+
+       *rec = xdf->recs[ri]->ptr;
+
+       return xdf->recs[ri]->size;
+}
+
+
+static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb) {
+       long size, psize = strlen(pre);
+       char const *rec;
+
+       size = xdl_get_rec(xdf, ri, &rec);
+       if (xdl_emit_diffrec(rec, size, pre, psize, ecb) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
+
+
+/*
+ * Starting at the passed change atom, find the latest change atom to be included
+ * inside the differential hunk according to the specified configuration.
+ * Also advance xscr if the first changes must be discarded.
+ */
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg)
+{
+       xdchange_t *xch, *xchp, *lxch;
+       long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen;
+       long max_ignorable = xecfg->ctxlen;
+       unsigned long ignored = 0; /* number of ignored blank lines */
+
+       /* remove ignorable changes that are too far before other changes */
+       for (xchp = *xscr; xchp && xchp->ignore; xchp = xchp->next) {
+               xch = xchp->next;
+
+               if (xch == NULL ||
+                   xch->i1 - (xchp->i1 + xchp->chg1) >= max_ignorable)
+                       *xscr = xch;
+       }
+
+       if (*xscr == NULL)
+               return NULL;
+
+       lxch = *xscr;
+
+       for (xchp = *xscr, xch = xchp->next; xch; xchp = xch, xch = xch->next) {
+               long distance = xch->i1 - (xchp->i1 + xchp->chg1);
+               if (distance > max_common)
+                       break;
+
+               if (distance < max_ignorable && (!xch->ignore || lxch == xchp)) {
+                       lxch = xch;
+                       ignored = 0;
+               } else if (distance < max_ignorable && xch->ignore) {
+                       ignored += xch->chg2;
+               } else if (lxch != xchp &&
+                          xch->i1 + (long)ignored - (lxch->i1 + lxch->chg1) > max_common) {
+                       break;
+               } else if (!xch->ignore) {
+                       lxch = xch;
+                       ignored = 0;
+               } else {
+                       ignored += xch->chg2;
+               }
+       }
+
+       return lxch;
+}
+
+
+static long def_ff(const char *rec, long len, char *buf, long sz, void *priv UNUSED)
+{
+       if (len > 0 &&
+                       (isalpha((unsigned char)*rec) || /* identifier? */
+                        *rec == '_' || /* also identifier? */
+                        *rec == '$')) { /* identifiers from VMS and other esoterico */
+               if (len > sz)
+                       len = sz;
+               while (0 < len && isspace((unsigned char)rec[len - 1]))
+                       len--;
+               memcpy(buf, rec, len);
+               return len;
+       }
+       return -1;
+}
+
+static long match_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri,
+                          char *buf, long sz)
+{
+       const char *rec;
+       long len = xdl_get_rec(xdf, ri, &rec);
+       if (!xecfg->find_func)
+               return def_ff(rec, len, buf, sz, xecfg->find_func_priv);
+       return xecfg->find_func(rec, len, buf, sz, xecfg->find_func_priv);
+}
+
+static int is_func_rec(xdfile_t *xdf, xdemitconf_t const *xecfg, long ri)
+{
+       char dummy[1];
+       return match_func_rec(xdf, xecfg, ri, dummy, sizeof(dummy)) >= 0;
+}
+
+struct func_line {
+       long len;
+       char buf[80];
+};
+
+static long get_func_line(xdfenv_t *xe, xdemitconf_t const *xecfg,
+                         struct func_line *func_line, long start, long limit)
+{
+       long l, size, step = (start > limit) ? -1 : 1;
+       char *buf, dummy[1];
+
+       buf = func_line ? func_line->buf : dummy;
+       size = func_line ? sizeof(func_line->buf) : sizeof(dummy);
+
+       for (l = start; l != limit && 0 <= l && l < xe->xdf1.nrec; l += step) {
+               long len = match_func_rec(&xe->xdf1, xecfg, l, buf, size);
+               if (len >= 0) {
+                       if (func_line)
+                               func_line->len = len;
+                       return l;
+               }
+       }
+       return -1;
+}
+
+static int is_empty_rec(xdfile_t *xdf, long ri)
+{
+       const char *rec;
+       long len = xdl_get_rec(xdf, ri, &rec);
+
+       while (len > 0 && XDL_ISSPACE(*rec)) {
+               rec++;
+               len--;
+       }
+       return !len;
+}
+
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                 xdemitconf_t const *xecfg) {
+       long s1, s2, e1, e2, lctx;
+       xdchange_t *xch, *xche;
+       long funclineprev = -1;
+       struct func_line func_line = { 0 };
+
+       for (xch = xscr; xch; xch = xche->next) {
+               xche = xdl_get_hunk(&xch, xecfg);
+               if (!xch)
+                       break;
+
+               s1 = XDL_MAX(xch->i1 - xecfg->ctxlen, 0);
+               s2 = XDL_MAX(xch->i2 - xecfg->ctxlen, 0);
+
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fs1, i1 = xch->i1;
+
+                       /* Appended chunk? */
+                       if (i1 >= xe->xdf1.nrec) {
+                               long i2 = xch->i2;
+
+                               /*
+                                * We don't need additional context if
+                                * a whole function was added.
+                                */
+                               while (i2 < xe->xdf2.nrec) {
+                                       if (is_func_rec(&xe->xdf2, xecfg, i2))
+                                               goto post_context_calculation;
+                                       i2++;
+                               }
+
+                               /*
+                                * Otherwise get more context from the
+                                * pre-image.
+                                */
+                               i1 = xe->xdf1.nrec - 1;
+                       }
+
+                       fs1 = get_func_line(xe, xecfg, NULL, i1, -1);
+                       while (fs1 > 0 && !is_empty_rec(&xe->xdf1, fs1 - 1) &&
+                              !is_func_rec(&xe->xdf1, xecfg, fs1 - 1))
+                               fs1--;
+                       if (fs1 < 0)
+                               fs1 = 0;
+                       if (fs1 < s1) {
+                               s2 -= s1 - fs1;
+                               s1 = fs1;
+                       }
+               }
+
+ post_context_calculation:
+               lctx = xecfg->ctxlen;
+               lctx = XDL_MIN(lctx, xe->xdf1.nrec - (xche->i1 + xche->chg1));
+               lctx = XDL_MIN(lctx, xe->xdf2.nrec - (xche->i2 + xche->chg2));
+
+               e1 = xche->i1 + xche->chg1 + lctx;
+               e2 = xche->i2 + xche->chg2 + lctx;
+
+               if (xecfg->flags & XDL_EMIT_FUNCCONTEXT) {
+                       long fe1 = get_func_line(xe, xecfg, NULL,
+                                                xche->i1 + xche->chg1,
+                                                xe->xdf1.nrec);
+                       while (fe1 > 0 && is_empty_rec(&xe->xdf1, fe1 - 1))
+                               fe1--;
+                       if (fe1 < 0)
+                               fe1 = xe->xdf1.nrec;
+                       if (fe1 > e1) {
+                               e2 += fe1 - e1;
+                               e1 = fe1;
+                       }
+
+                       /*
+                        * Overlap with next change?  Then include it
+                        * in the current hunk and start over to find
+                        * its new end.
+                        */
+                       if (xche->next) {
+                               long l = XDL_MIN(xche->next->i1,
+                                                xe->xdf1.nrec - 1);
+                               if (l - xecfg->ctxlen <= e1 ||
+                                   get_func_line(xe, xecfg, NULL, l, e1) < 0) {
+                                       xche = xche->next;
+                                       goto post_context_calculation;
+                               }
+                       }
+               }
+
+               /*
+                * Emit current hunk header.
+                */
+
+               if (xecfg->flags & XDL_EMIT_FUNCNAMES) {
+                       get_func_line(xe, xecfg, &func_line,
+                                     s1 - 1, funclineprev);
+                       funclineprev = s1 - 1;
+               }
+               if (xdl_emit_hunk_hdr(s1 + 1, e1 - s1, s2 + 1, e2 - s2,
+                                     func_line.buf, func_line.len, ecb) < 0)
+                       return -1;
+
+               /*
+                * Emit pre-context.
+                */
+               for (; s2 < xch->i2; s2++)
+                       if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+                               return -1;
+
+               for (s1 = xch->i1, s2 = xch->i2;; xch = xch->next) {
+                       /*
+                        * Merge previous with current change atom.
+                        */
+                       for (; s1 < xch->i1 && s2 < xch->i2; s1++, s2++)
+                               if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+                                       return -1;
+
+                       /*
+                        * Removes lines from the first file.
+                        */
+                       for (s1 = xch->i1; s1 < xch->i1 + xch->chg1; s1++)
+                               if (xdl_emit_record(&xe->xdf1, s1, "-", ecb) < 0)
+                                       return -1;
+
+                       /*
+                        * Adds lines from the second file.
+                        */
+                       for (s2 = xch->i2; s2 < xch->i2 + xch->chg2; s2++)
+                               if (xdl_emit_record(&xe->xdf2, s2, "+", ecb) < 0)
+                                       return -1;
+
+                       if (xch == xche)
+                               break;
+                       s1 = xch->i1 + xch->chg1;
+                       s2 = xch->i2 + xch->chg2;
+               }
+
+               /*
+                * Emit post-context.
+                */
+               for (s2 = xche->i2 + xche->chg2; s2 < e2; s2++)
+                       if (xdl_emit_record(&xe->xdf2, s2, " ", ecb) < 0)
+                               return -1;
+       }
+
+       return 0;
+}
diff --git a/src/xdiff/xemit.h b/src/xdiff/xemit.h
new file mode 100644 (file)
index 0000000..1b9887e
--- /dev/null
@@ -0,0 +1,36 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XEMIT_H)
+#define XEMIT_H
+
+
+typedef int (*emit_func_t)(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                          xdemitconf_t const *xecfg);
+
+xdchange_t *xdl_get_hunk(xdchange_t **xscr, xdemitconf_t const *xecfg);
+int xdl_emit_diff(xdfenv_t *xe, xdchange_t *xscr, xdemitcb_t *ecb,
+                 xdemitconf_t const *xecfg);
+
+
+
+#endif /* #if !defined(XEMIT_H) */
diff --git a/src/xdiff/xhistogram.c b/src/xdiff/xhistogram.c
new file mode 100644 (file)
index 0000000..3fb8974
--- /dev/null
@@ -0,0 +1,386 @@
+/*
+ * Copyright (C) 2010, Google Inc.
+ * and other copyright owners as documented in JGit's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+#define MAX_PTR        INT_MAX
+#define MAX_CNT        INT_MAX
+
+#define LINE_END(n) (line##n + count##n - 1)
+#define LINE_END_PTR(n) (*line##n + *count##n - 1)
+
+struct histindex {
+       struct record {
+               unsigned int ptr, cnt;
+               struct record *next;
+       } **records, /* an occurrence */
+         **line_map; /* map of line to record chain */
+       chastore_t rcha;
+       unsigned int *next_ptrs;
+       unsigned int table_bits,
+                    records_size,
+                    line_map_size;
+
+       unsigned int max_chain_length,
+                    key_shift,
+                    ptr_shift;
+
+       unsigned int cnt,
+                    has_common;
+
+       xdfenv_t *env;
+       xpparam_t const *xpp;
+};
+
+struct region {
+       unsigned int begin1, end1;
+       unsigned int begin2, end2;
+};
+
+#define LINE_MAP(i, a) (i->line_map[(a) - i->ptr_shift])
+
+#define NEXT_PTR(index, ptr) \
+       (index->next_ptrs[(ptr) - index->ptr_shift])
+
+#define CNT(index, ptr) \
+       ((LINE_MAP(index, ptr))->cnt)
+
+#define REC(env, s, l) \
+       (env->xdf##s.recs[l - 1])
+
+static int cmp_recs(xpparam_t const *xpp,
+       xrecord_t *r1, xrecord_t *r2)
+{
+       return r1->ha == r2->ha &&
+               xdl_recmatch(r1->ptr, r1->size, r2->ptr, r2->size,
+                           xpp->flags);
+}
+
+#define CMP_ENV(xpp, env, s1, l1, s2, l2) \
+       (cmp_recs(xpp, REC(env, s1, l1), REC(env, s2, l2)))
+
+#define CMP(i, s1, l1, s2, l2) \
+       (cmp_recs(i->xpp, REC(i->env, s1, l1), REC(i->env, s2, l2)))
+
+#define TABLE_HASH(index, side, line) \
+       XDL_HASHLONG((REC(index->env, side, line))->ha, index->table_bits)
+
+static int scanA(struct histindex *index, int line1, int count1)
+{
+       int ptr, tbl_idx;
+       unsigned int chain_len;
+       struct record **rec_chain, *rec;
+
+       for (ptr = LINE_END(1); line1 <= ptr; ptr--) {
+               tbl_idx = TABLE_HASH(index, 1, ptr);
+               rec_chain = index->records + tbl_idx;
+               rec = *rec_chain;
+
+               chain_len = 0;
+               while (rec) {
+                       if (CMP(index, 1, rec->ptr, 1, ptr)) {
+                               /*
+                                * ptr is identical to another element. Insert
+                                * it onto the front of the existing element
+                                * chain.
+                                */
+                               NEXT_PTR(index, ptr) = rec->ptr;
+                               rec->ptr = ptr;
+                               /* cap rec->cnt at MAX_CNT */
+                               rec->cnt = XDL_MIN(MAX_CNT, rec->cnt + 1);
+                               LINE_MAP(index, ptr) = rec;
+                               goto continue_scan;
+                       }
+
+                       rec = rec->next;
+                       chain_len++;
+               }
+
+               if (chain_len == index->max_chain_length)
+                       return -1;
+
+               /*
+                * This is the first time we have ever seen this particular
+                * element in the sequence. Construct a new chain for it.
+                */
+               if (!(rec = xdl_cha_alloc(&index->rcha)))
+                       return -1;
+               rec->ptr = ptr;
+               rec->cnt = 1;
+               rec->next = *rec_chain;
+               *rec_chain = rec;
+               LINE_MAP(index, ptr) = rec;
+
+continue_scan:
+               ; /* no op */
+       }
+
+       return 0;
+}
+
+static int try_lcs(struct histindex *index, struct region *lcs, int b_ptr,
+       int line1, int count1, int line2, int count2)
+{
+       unsigned int b_next = b_ptr + 1;
+       struct record *rec = index->records[TABLE_HASH(index, 2, b_ptr)];
+       unsigned int as, ae, bs, be, np, rc;
+       int should_break;
+
+       for (; rec; rec = rec->next) {
+               if (rec->cnt > index->cnt) {
+                       if (!index->has_common)
+                               index->has_common = CMP(index, 1, rec->ptr, 2, b_ptr);
+                       continue;
+               }
+
+               as = rec->ptr;
+               if (!CMP(index, 1, as, 2, b_ptr))
+                       continue;
+
+               index->has_common = 1;
+               for (;;) {
+                       should_break = 0;
+                       np = NEXT_PTR(index, as);
+                       bs = b_ptr;
+                       ae = as;
+                       be = bs;
+                       rc = rec->cnt;
+
+                       while (line1 < (int)as && line2 < (int)bs
+                               && CMP(index, 1, as - 1, 2, bs - 1)) {
+                               as--;
+                               bs--;
+                               if (1 < rc)
+                                       rc = XDL_MIN(rc, CNT(index, as));
+                       }
+                       while ((int)ae < LINE_END(1) && (int)be < LINE_END(2)
+                               && CMP(index, 1, ae + 1, 2, be + 1)) {
+                               ae++;
+                               be++;
+                               if (1 < rc)
+                                       rc = XDL_MIN(rc, CNT(index, ae));
+                       }
+
+                       if (b_next <= be)
+                               b_next = be + 1;
+                       if (lcs->end1 - lcs->begin1 < ae - as || rc < index->cnt) {
+                               lcs->begin1 = as;
+                               lcs->begin2 = bs;
+                               lcs->end1 = ae;
+                               lcs->end2 = be;
+                               index->cnt = rc;
+                       }
+
+                       if (np == 0)
+                               break;
+
+                       while (np <= ae) {
+                               np = NEXT_PTR(index, np);
+                               if (np == 0) {
+                                       should_break = 1;
+                                       break;
+                               }
+                       }
+
+                       if (should_break)
+                               break;
+
+                       as = np;
+               }
+       }
+       return b_next;
+}
+
+static int fall_back_to_classic_diff(xpparam_t const *xpp, xdfenv_t *env,
+               int line1, int count1, int line2, int count2)
+{
+       xpparam_t xpparam;
+       xpparam.flags = xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
+
+       return xdl_fall_back_diff(env, &xpparam,
+                                 line1, count1, line2, count2);
+}
+
+static inline void free_index(struct histindex *index)
+{
+       xdl_free(index->records);
+       xdl_free(index->line_map);
+       xdl_free(index->next_ptrs);
+       xdl_cha_free(&index->rcha);
+}
+
+static int find_lcs(xpparam_t const *xpp, xdfenv_t *env,
+                   struct region *lcs,
+                   int line1, int count1, int line2, int count2)
+{
+       int b_ptr;
+       int sz, ret = -1;
+       struct histindex index;
+
+       memset(&index, 0, sizeof(index));
+
+       index.env = env;
+       index.xpp = xpp;
+
+       index.records = NULL;
+       index.line_map = NULL;
+       /* in case of early xdl_cha_free() */
+       index.rcha.head = NULL;
+
+       index.table_bits = xdl_hashbits(count1);
+       sz = index.records_size = 1 << index.table_bits;
+       sz *= sizeof(struct record *);
+       if (!(index.records = (struct record **) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.records, 0, sz);
+
+       sz = index.line_map_size = count1;
+       sz *= sizeof(struct record *);
+       if (!(index.line_map = (struct record **) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.line_map, 0, sz);
+
+       sz = index.line_map_size;
+       sz *= sizeof(unsigned int);
+       if (!(index.next_ptrs = (unsigned int *) xdl_malloc(sz)))
+               goto cleanup;
+       memset(index.next_ptrs, 0, sz);
+
+       /* lines / 4 + 1 comes from xprepare.c:xdl_prepare_ctx() */
+       if (xdl_cha_init(&index.rcha, sizeof(struct record), count1 / 4 + 1) < 0)
+               goto cleanup;
+
+       index.ptr_shift = line1;
+       index.max_chain_length = 64;
+
+       if (scanA(&index, line1, count1))
+               goto cleanup;
+
+       index.cnt = index.max_chain_length + 1;
+
+       for (b_ptr = line2; b_ptr <= LINE_END(2); )
+               b_ptr = try_lcs(&index, lcs, b_ptr, line1, count1, line2, count2);
+
+       if (index.has_common && index.max_chain_length < index.cnt)
+               ret = 1;
+       else
+               ret = 0;
+
+cleanup:
+       free_index(&index);
+       return ret;
+}
+
+static int histogram_diff(xpparam_t const *xpp, xdfenv_t *env,
+       int line1, int count1, int line2, int count2)
+{
+       struct region lcs;
+       int lcs_found;
+       int result;
+redo:
+       result = -1;
+
+       if (count1 <= 0 && count2 <= 0)
+               return 0;
+
+       if (LINE_END(1) >= MAX_PTR)
+               return -1;
+
+       if (!count1) {
+               while(count2--)
+                       env->xdf2.rchg[line2++ - 1] = 1;
+               return 0;
+       } else if (!count2) {
+               while(count1--)
+                       env->xdf1.rchg[line1++ - 1] = 1;
+               return 0;
+       }
+
+       memset(&lcs, 0, sizeof(lcs));
+       lcs_found = find_lcs(xpp, env, &lcs, line1, count1, line2, count2);
+       if (lcs_found < 0)
+               goto out;
+       else if (lcs_found)
+               result = fall_back_to_classic_diff(xpp, env, line1, count1, line2, count2);
+       else {
+               if (lcs.begin1 == 0 && lcs.begin2 == 0) {
+                       while (count1--)
+                               env->xdf1.rchg[line1++ - 1] = 1;
+                       while (count2--)
+                               env->xdf2.rchg[line2++ - 1] = 1;
+                       result = 0;
+               } else {
+                       result = histogram_diff(xpp, env,
+                                               line1, lcs.begin1 - line1,
+                                               line2, lcs.begin2 - line2);
+                       if (result)
+                               goto out;
+                       /*
+                        * result = histogram_diff(xpp, env,
+                        *            lcs.end1 + 1, LINE_END(1) - lcs.end1,
+                        *            lcs.end2 + 1, LINE_END(2) - lcs.end2);
+                        * but let's optimize tail recursion ourself:
+                       */
+                       count1 = LINE_END(1) - lcs.end1;
+                       line1 = lcs.end1 + 1;
+                       count2 = LINE_END(2) - lcs.end2;
+                       line2 = lcs.end2 + 1;
+                       goto redo;
+               }
+       }
+out:
+       return result;
+}
+
+int xdl_do_histogram_diff(mmfile_t *file1, mmfile_t *file2,
+       xpparam_t const *xpp, xdfenv_t *env)
+{
+       if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+               return -1;
+
+       return histogram_diff(xpp, env,
+               env->xdf1.dstart + 1, env->xdf1.dend - env->xdf1.dstart + 1,
+               env->xdf2.dstart + 1, env->xdf2.dend - env->xdf2.dstart + 1);
+}
diff --git a/src/xdiff/xinclude.h b/src/xdiff/xinclude.h
new file mode 100644 (file)
index 0000000..46b8608
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+/* defines HAVE_ATTRIBUTE_UNUSED */
+#ifdef HAVE_CONFIG_H
+# include "../auto/config.h"
+#endif
+
+/* Mark unused function arguments with UNUSED, so that gcc -Wunused-parameter
+ * can be used to check for mistakes. */
+#ifdef HAVE_ATTRIBUTE_UNUSED
+# define UNUSED __attribute__((unused))
+#else
+# define UNUSED
+#endif
+
+#if defined(_MSC_VER)
+# define inline __inline
+#endif
+
+#if !defined(XINCLUDE_H)
+#define XINCLUDE_H
+
+#include <ctype.h>
+#include <stdio.h>
+#include <stdlib.h>
+#if !defined(_WIN32)
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <limits.h>
+
+#include "xmacros.h"
+#include "xdiff.h"
+#include "xtypes.h"
+#include "xutils.h"
+#include "xprepare.h"
+#include "xdiffi.h"
+#include "xemit.h"
+
+
+#endif /* #if !defined(XINCLUDE_H) */
diff --git a/src/xdiff/xmacros.h b/src/xdiff/xmacros.h
new file mode 100644 (file)
index 0000000..2809a28
--- /dev/null
@@ -0,0 +1,54 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XMACROS_H)
+#define XMACROS_H
+
+
+
+
+#define XDL_MIN(a, b) ((a) < (b) ? (a): (b))
+#define XDL_MAX(a, b) ((a) > (b) ? (a): (b))
+#define XDL_ABS(v) ((v) >= 0 ? (v): -(v))
+#define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
+#define XDL_ISSPACE(c) (isspace((unsigned char)(c)))
+#define XDL_ADDBITS(v,b)       ((v) + ((v) >> (b)))
+#define XDL_MASKBITS(b)                ((1UL << (b)) - 1)
+#define XDL_HASHLONG(v,b)      (XDL_ADDBITS((unsigned long)(v), b) & XDL_MASKBITS(b))
+#define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
+#define XDL_LE32_PUT(p, v) \
+do { \
+       unsigned char *__p = (unsigned char *) (p); \
+       *__p++ = (unsigned char) (v); \
+       *__p++ = (unsigned char) ((v) >> 8); \
+       *__p++ = (unsigned char) ((v) >> 16); \
+       *__p = (unsigned char) ((v) >> 24); \
+} while (0)
+#define XDL_LE32_GET(p, v) \
+do { \
+       unsigned char const *__p = (unsigned char const *) (p); \
+       (v) = (unsigned long) __p[0] | ((unsigned long) __p[1]) << 8 | \
+               ((unsigned long) __p[2]) << 16 | ((unsigned long) __p[3]) << 24; \
+} while (0)
+
+
+#endif /* #if !defined(XMACROS_H) */
diff --git a/src/xdiff/xpatience.c b/src/xdiff/xpatience.c
new file mode 100644 (file)
index 0000000..1052d10
--- /dev/null
@@ -0,0 +1,390 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003-2016 Davide Libenzi, Johannes E. Schindelin
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+#include "xinclude.h"
+#include "xtypes.h"
+#include "xdiff.h"
+
+/*
+ * The basic idea of patience diff is to find lines that are unique in
+ * both files.  These are intuitively the ones that we want to see as
+ * common lines.
+ *
+ * The maximal ordered sequence of such line pairs (where ordered means
+ * that the order in the sequence agrees with the order of the lines in
+ * both files) naturally defines an initial set of common lines.
+ *
+ * Now, the algorithm tries to extend the set of common lines by growing
+ * the line ranges where the files have identical lines.
+ *
+ * Between those common lines, the patience diff algorithm is applied
+ * recursively, until no unique line pairs can be found; these line ranges
+ * are handled by the well-known Myers algorithm.
+ */
+
+#define NON_UNIQUE ULONG_MAX
+
+/*
+ * This is a hash mapping from line hash to line numbers in the first and
+ * second file.
+ */
+struct hashmap {
+       int nr, alloc;
+       struct entry {
+               unsigned long hash;
+               /*
+                * 0 = unused entry, 1 = first line, 2 = second, etc.
+                * line2 is NON_UNIQUE if the line is not unique
+                * in either the first or the second file.
+                */
+               unsigned long line1, line2;
+               /*
+                * "next" & "previous" are used for the longest common
+                * sequence;
+                * initially, "next" reflects only the order in file1.
+                */
+               struct entry *next, *previous;
+
+               /*
+                * If 1, this entry can serve as an anchor. See
+                * Documentation/diff-options.txt for more information.
+                */
+               unsigned anchor : 1;
+       } *entries, *first, *last;
+       /* were common records found? */
+       unsigned long has_matches;
+       mmfile_t *file1, *file2;
+       xdfenv_t *env;
+       xpparam_t const *xpp;
+};
+
+static int is_anchor(xpparam_t const *xpp, const char *line)
+{
+       size_t i;
+       for (i = 0; i < xpp->anchors_nr; i++) {
+               if (!strncmp(line, xpp->anchors[i], strlen(xpp->anchors[i])))
+                       return 1;
+       }
+       return 0;
+}
+
+/* The argument "pass" is 1 for the first file, 2 for the second. */
+static void insert_record(xpparam_t const *xpp, int line, struct hashmap *map,
+                         int pass)
+{
+       xrecord_t **records = pass == 1 ?
+               map->env->xdf1.recs : map->env->xdf2.recs;
+       xrecord_t *record = records[line - 1], *other;
+       /*
+        * After xdl_prepare_env() (or more precisely, due to
+        * xdl_classify_record()), the "ha" member of the records (AKA lines)
+        * is _not_ the hash anymore, but a linearized version of it.  In
+        * other words, the "ha" member is guaranteed to start with 0 and
+        * the second record's ha can only be 0 or 1, etc.
+        *
+        * So we multiply ha by 2 in the hope that the hashing was
+        * "unique enough".
+        */
+       int index = (int)((record->ha << 1) % map->alloc);
+
+       while (map->entries[index].line1) {
+               other = map->env->xdf1.recs[map->entries[index].line1 - 1];
+               if (map->entries[index].hash != record->ha ||
+                               !xdl_recmatch(record->ptr, record->size,
+                                       other->ptr, other->size,
+                                       map->xpp->flags)) {
+                       if (++index >= map->alloc)
+                               index = 0;
+                       continue;
+               }
+               if (pass == 2)
+                       map->has_matches = 1;
+               if (pass == 1 || map->entries[index].line2)
+                       map->entries[index].line2 = NON_UNIQUE;
+               else
+                       map->entries[index].line2 = line;
+               return;
+       }
+       if (pass == 2)
+               return;
+       map->entries[index].line1 = line;
+       map->entries[index].hash = record->ha;
+       map->entries[index].anchor = is_anchor(xpp, map->env->xdf1.recs[line - 1]->ptr);
+       if (!map->first)
+               map->first = map->entries + index;
+       if (map->last) {
+               map->last->next = map->entries + index;
+               map->entries[index].previous = map->last;
+       }
+       map->last = map->entries + index;
+       map->nr++;
+}
+
+/*
+ * This function has to be called for each recursion into the inter-hunk
+ * parts, as previously non-unique lines can become unique when being
+ * restricted to a smaller part of the files.
+ *
+ * It is assumed that env has been prepared using xdl_prepare().
+ */
+static int fill_hashmap(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               struct hashmap *result,
+               int line1, int count1, int line2, int count2)
+{
+       result->file1 = file1;
+       result->file2 = file2;
+       result->xpp = xpp;
+       result->env = env;
+
+       /* We know exactly how large we want the hash map */
+       result->alloc = count1 * 2;
+       result->entries = (struct entry *)
+               xdl_malloc(result->alloc * sizeof(struct entry));
+       if (!result->entries)
+               return -1;
+       memset(result->entries, 0, result->alloc * sizeof(struct entry));
+
+       /* First, fill with entries from the first file */
+       while (count1--)
+               insert_record(xpp, line1++, result, 1);
+
+       /* Then search for matches in the second file */
+       while (count2--)
+               insert_record(xpp, line2++, result, 2);
+
+       return 0;
+}
+
+/*
+ * Find the longest sequence with a smaller last element (meaning a smaller
+ * line2, as we construct the sequence with entries ordered by line1).
+ */
+static int binary_search(struct entry **sequence, int longest,
+               struct entry *entry)
+{
+       int left = -1, right = longest;
+
+       while (left + 1 < right) {
+               int middle = left + (right - left) / 2;
+               /* by construction, no two entries can be equal */
+               if (sequence[middle]->line2 > entry->line2)
+                       right = middle;
+               else
+                       left = middle;
+       }
+       /* return the index in "sequence", _not_ the sequence length */
+       return left;
+}
+
+/*
+ * The idea is to start with the list of common unique lines sorted by
+ * the order in file1.  For each of these pairs, the longest (partial)
+ * sequence whose last element's line2 is smaller is determined.
+ *
+ * For efficiency, the sequences are kept in a list containing exactly one
+ * item per sequence length: the sequence with the smallest last
+ * element (in terms of line2).
+ */
+static struct entry *find_longest_common_sequence(struct hashmap *map)
+{
+       struct entry **sequence = xdl_malloc(map->nr * sizeof(struct entry *));
+       int longest = 0, i;
+       struct entry *entry;
+
+       /*
+        * If not -1, this entry in sequence must never be overridden.
+        * Therefore, overriding entries before this has no effect, so
+        * do not do that either.
+        */
+       int anchor_i = -1;
+
+       for (entry = map->first; entry; entry = entry->next) {
+               if (!entry->line2 || entry->line2 == NON_UNIQUE)
+                       continue;
+               i = binary_search(sequence, longest, entry);
+               entry->previous = i < 0 ? NULL : sequence[i];
+               ++i;
+               if (i <= anchor_i)
+                       continue;
+               sequence[i] = entry;
+               if (entry->anchor) {
+                       anchor_i = i;
+                       longest = anchor_i + 1;
+               } else if (i == longest) {
+                       longest++;
+               }
+       }
+
+       /* No common unique lines were found */
+       if (!longest) {
+               xdl_free(sequence);
+               return NULL;
+       }
+
+       /* Iterate starting at the last element, adjusting the "next" members */
+       entry = sequence[longest - 1];
+       entry->next = NULL;
+       while (entry->previous) {
+               entry->previous->next = entry;
+               entry = entry->previous;
+       }
+       xdl_free(sequence);
+       return entry;
+}
+
+static int match(struct hashmap *map, int line1, int line2)
+{
+       xrecord_t *record1 = map->env->xdf1.recs[line1 - 1];
+       xrecord_t *record2 = map->env->xdf2.recs[line2 - 1];
+       return xdl_recmatch(record1->ptr, record1->size,
+               record2->ptr, record2->size, map->xpp->flags);
+}
+
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               int line1, int count1, int line2, int count2);
+
+static int walk_common_sequence(struct hashmap *map, struct entry *first,
+               int line1, int count1, int line2, int count2)
+{
+       int end1 = line1 + count1, end2 = line2 + count2;
+       int next1, next2;
+
+       for (;;) {
+               /* Try to grow the line ranges of common lines */
+               if (first) {
+                       next1 = first->line1;
+                       next2 = first->line2;
+                       while (next1 > line1 && next2 > line2 &&
+                                       match(map, next1 - 1, next2 - 1)) {
+                               next1--;
+                               next2--;
+                       }
+               } else {
+                       next1 = end1;
+                       next2 = end2;
+               }
+               while (line1 < next1 && line2 < next2 &&
+                               match(map, line1, line2)) {
+                       line1++;
+                       line2++;
+               }
+
+               /* Recurse */
+               if (next1 > line1 || next2 > line2) {
+                       struct hashmap submap;
+
+                       memset(&submap, 0, sizeof(submap));
+                       if (patience_diff(map->file1, map->file2,
+                                       map->xpp, map->env,
+                                       line1, next1 - line1,
+                                       line2, next2 - line2))
+                               return -1;
+               }
+
+               if (!first)
+                       return 0;
+
+               while (first->next &&
+                               first->next->line1 == first->line1 + 1 &&
+                               first->next->line2 == first->line2 + 1)
+                       first = first->next;
+
+               line1 = first->line1 + 1;
+               line2 = first->line2 + 1;
+
+               first = first->next;
+       }
+}
+
+static int fall_back_to_classic_diff(struct hashmap *map,
+               int line1, int count1, int line2, int count2)
+{
+       xpparam_t xpp;
+       xpp.flags = map->xpp->flags & ~XDF_DIFF_ALGORITHM_MASK;
+
+       return xdl_fall_back_diff(map->env, &xpp,
+                                 line1, count1, line2, count2);
+}
+
+/*
+ * Recursively find the longest common sequence of unique lines,
+ * and if none was found, ask xdl_do_diff() to do the job.
+ *
+ * This function assumes that env was prepared with xdl_prepare_env().
+ */
+static int patience_diff(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env,
+               int line1, int count1, int line2, int count2)
+{
+       struct hashmap map;
+       struct entry *first;
+       int result = 0;
+
+       /* trivial case: one side is empty */
+       if (!count1) {
+               while(count2--)
+                       env->xdf2.rchg[line2++ - 1] = 1;
+               return 0;
+       } else if (!count2) {
+               while(count1--)
+                       env->xdf1.rchg[line1++ - 1] = 1;
+               return 0;
+       }
+
+       memset(&map, 0, sizeof(map));
+       if (fill_hashmap(file1, file2, xpp, env, &map,
+                       line1, count1, line2, count2))
+               return -1;
+
+       /* are there any matching lines at all? */
+       if (!map.has_matches) {
+               while(count1--)
+                       env->xdf1.rchg[line1++ - 1] = 1;
+               while(count2--)
+                       env->xdf2.rchg[line2++ - 1] = 1;
+               xdl_free(map.entries);
+               return 0;
+       }
+
+       first = find_longest_common_sequence(&map);
+       if (first)
+               result = walk_common_sequence(&map, first,
+                       line1, count1, line2, count2);
+       else
+               result = fall_back_to_classic_diff(&map,
+                       line1, count1, line2, count2);
+
+       xdl_free(map.entries);
+       return result;
+}
+
+int xdl_do_patience_diff(mmfile_t *file1, mmfile_t *file2,
+               xpparam_t const *xpp, xdfenv_t *env)
+{
+       if (xdl_prepare_env(file1, file2, xpp, env) < 0)
+               return -1;
+
+       /* environment is cleaned up in xdl_diff() */
+       return patience_diff(file1, file2, xpp, env,
+                       1, env->xdf1.nrec, 1, env->xdf2.nrec);
+}
diff --git a/src/xdiff/xprepare.c b/src/xdiff/xprepare.c
new file mode 100644 (file)
index 0000000..abeb8fb
--- /dev/null
@@ -0,0 +1,483 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include "xinclude.h"
+
+
+#define XDL_KPDIS_RUN 4
+#define XDL_MAX_EQLIMIT 1024
+#define XDL_SIMSCAN_WINDOW 100
+#define XDL_GUESS_NLINES1 256
+#define XDL_GUESS_NLINES2 20
+
+
+typedef struct s_xdlclass {
+       struct s_xdlclass *next;
+       unsigned long ha;
+       char const *line;
+       long size;
+       long idx;
+       long len1, len2;
+} xdlclass_t;
+
+typedef struct s_xdlclassifier {
+       unsigned int hbits;
+       long hsize;
+       xdlclass_t **rchash;
+       chastore_t ncha;
+       xdlclass_t **rcrecs;
+       long alloc;
+       long count;
+       long flags;
+} xdlclassifier_t;
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags);
+static void xdl_free_classifier(xdlclassifier_t *cf);
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec);
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+                          xdlclassifier_t *cf, xdfile_t *xdf);
+static void xdl_free_ctx(xdfile_t *xdf);
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e);
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2);
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2);
+
+
+
+
+static int xdl_init_classifier(xdlclassifier_t *cf, long size, long flags) {
+       cf->flags = flags;
+
+       cf->hbits = xdl_hashbits((unsigned int) size);
+       cf->hsize = 1 << cf->hbits;
+
+       if (xdl_cha_init(&cf->ncha, sizeof(xdlclass_t), size / 4 + 1) < 0) {
+
+               return -1;
+       }
+       if (!(cf->rchash = (xdlclass_t **) xdl_malloc(cf->hsize * sizeof(xdlclass_t *)))) {
+
+               xdl_cha_free(&cf->ncha);
+               return -1;
+       }
+       memset(cf->rchash, 0, cf->hsize * sizeof(xdlclass_t *));
+
+       cf->alloc = size;
+       if (!(cf->rcrecs = (xdlclass_t **) xdl_malloc(cf->alloc * sizeof(xdlclass_t *)))) {
+
+               xdl_free(cf->rchash);
+               xdl_cha_free(&cf->ncha);
+               return -1;
+       }
+
+       cf->count = 0;
+
+       return 0;
+}
+
+
+static void xdl_free_classifier(xdlclassifier_t *cf) {
+
+       xdl_free(cf->rcrecs);
+       xdl_free(cf->rchash);
+       xdl_cha_free(&cf->ncha);
+}
+
+
+static int xdl_classify_record(unsigned int pass, xdlclassifier_t *cf, xrecord_t **rhash,
+                              unsigned int hbits, xrecord_t *rec) {
+       long hi;
+       char const *line;
+       xdlclass_t *rcrec;
+       xdlclass_t **rcrecs;
+
+       line = rec->ptr;
+       hi = (long) XDL_HASHLONG(rec->ha, cf->hbits);
+       for (rcrec = cf->rchash[hi]; rcrec; rcrec = rcrec->next)
+               if (rcrec->ha == rec->ha &&
+                               xdl_recmatch(rcrec->line, rcrec->size,
+                                       rec->ptr, rec->size, cf->flags))
+                       break;
+
+       if (!rcrec) {
+               if (!(rcrec = xdl_cha_alloc(&cf->ncha))) {
+
+                       return -1;
+               }
+               rcrec->idx = cf->count++;
+               if (cf->count > cf->alloc) {
+                       cf->alloc *= 2;
+                       if (!(rcrecs = (xdlclass_t **) xdl_realloc(cf->rcrecs, cf->alloc * sizeof(xdlclass_t *)))) {
+
+                               return -1;
+                       }
+                       cf->rcrecs = rcrecs;
+               }
+               cf->rcrecs[rcrec->idx] = rcrec;
+               rcrec->line = line;
+               rcrec->size = rec->size;
+               rcrec->ha = rec->ha;
+               rcrec->len1 = rcrec->len2 = 0;
+               rcrec->next = cf->rchash[hi];
+               cf->rchash[hi] = rcrec;
+       }
+
+       (pass == 1) ? rcrec->len1++ : rcrec->len2++;
+
+       rec->ha = (unsigned long) rcrec->idx;
+
+       hi = (long) XDL_HASHLONG(rec->ha, hbits);
+       rec->next = rhash[hi];
+       rhash[hi] = rec;
+
+       return 0;
+}
+
+
+static int xdl_prepare_ctx(unsigned int pass, mmfile_t *mf, long narec, xpparam_t const *xpp,
+                          xdlclassifier_t *cf, xdfile_t *xdf) {
+       unsigned int hbits;
+       long nrec, hsize, bsize;
+       unsigned long hav;
+       char const *blk, *cur, *top, *prev;
+       xrecord_t *crec;
+       xrecord_t **recs, **rrecs;
+       xrecord_t **rhash;
+       unsigned long *ha;
+       char *rchg;
+       long *rindex;
+
+       ha = NULL;
+       rindex = NULL;
+       rchg = NULL;
+       rhash = NULL;
+       recs = NULL;
+
+       if (xdl_cha_init(&xdf->rcha, sizeof(xrecord_t), narec / 4 + 1) < 0)
+               goto abort;
+       if (!(recs = (xrecord_t **) xdl_malloc(narec * sizeof(xrecord_t *))))
+               goto abort;
+
+       if (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF)
+               hbits = hsize = 0;
+       else {
+               hbits = xdl_hashbits((unsigned int) narec);
+               hsize = 1 << hbits;
+               if (!(rhash = (xrecord_t **) xdl_malloc(hsize * sizeof(xrecord_t *))))
+                       goto abort;
+               memset(rhash, 0, hsize * sizeof(xrecord_t *));
+       }
+
+       nrec = 0;
+       if ((cur = blk = xdl_mmfile_first(mf, &bsize)) != NULL) {
+               for (top = blk + bsize; cur < top; ) {
+                       prev = cur;
+                       hav = xdl_hash_record(&cur, top, xpp->flags);
+                       if (nrec >= narec) {
+                               narec *= 2;
+                               if (!(rrecs = (xrecord_t **) xdl_realloc(recs, narec * sizeof(xrecord_t *))))
+                                       goto abort;
+                               recs = rrecs;
+                       }
+                       if (!(crec = xdl_cha_alloc(&xdf->rcha)))
+                               goto abort;
+                       crec->ptr = prev;
+                       crec->size = (long) (cur - prev);
+                       crec->ha = hav;
+                       recs[nrec++] = crec;
+
+                       if ((XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+                           xdl_classify_record(pass, cf, rhash, hbits, crec) < 0)
+                               goto abort;
+               }
+       }
+
+       if (!(rchg = (char *) xdl_malloc((nrec + 2) * sizeof(char))))
+               goto abort;
+       memset(rchg, 0, (nrec + 2) * sizeof(char));
+
+       if (!(rindex = (long *) xdl_malloc((nrec + 1) * sizeof(long))))
+               goto abort;
+       if (!(ha = (unsigned long *) xdl_malloc((nrec + 1) * sizeof(unsigned long))))
+               goto abort;
+
+       xdf->nrec = nrec;
+       xdf->recs = recs;
+       xdf->hbits = hbits;
+       xdf->rhash = rhash;
+       xdf->rchg = rchg + 1;
+       xdf->rindex = rindex;
+       xdf->nreff = 0;
+       xdf->ha = ha;
+       xdf->dstart = 0;
+       xdf->dend = nrec - 1;
+
+       return 0;
+
+abort:
+       xdl_free(ha);
+       xdl_free(rindex);
+       xdl_free(rchg);
+       xdl_free(rhash);
+       xdl_free(recs);
+       xdl_cha_free(&xdf->rcha);
+       return -1;
+}
+
+
+static void xdl_free_ctx(xdfile_t *xdf) {
+
+       xdl_free(xdf->rhash);
+       xdl_free(xdf->rindex);
+       xdl_free(xdf->rchg - 1);
+       xdl_free(xdf->ha);
+       xdl_free(xdf->recs);
+       xdl_cha_free(&xdf->rcha);
+}
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+                   xdfenv_t *xe) {
+       long enl1, enl2, sample;
+       xdlclassifier_t cf;
+
+       memset(&cf, 0, sizeof(cf));
+
+       /*
+        * For histogram diff, we can afford a smaller sample size and
+        * thus a poorer estimate of the number of lines, as the hash
+        * table (rhash) won't be filled up/grown. The number of lines
+        * (nrecs) will be updated correctly anyway by
+        * xdl_prepare_ctx().
+        */
+       sample = (XDF_DIFF_ALG(xpp->flags) == XDF_HISTOGRAM_DIFF
+                 ? XDL_GUESS_NLINES2 : XDL_GUESS_NLINES1);
+
+       enl1 = xdl_guess_lines(mf1, sample) + 1;
+       enl2 = xdl_guess_lines(mf2, sample) + 1;
+
+       if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF &&
+           xdl_init_classifier(&cf, enl1 + enl2 + 1, xpp->flags) < 0)
+               return -1;
+
+       if (xdl_prepare_ctx(1, mf1, enl1, xpp, &cf, &xe->xdf1) < 0) {
+
+               xdl_free_classifier(&cf);
+               return -1;
+       }
+       if (xdl_prepare_ctx(2, mf2, enl2, xpp, &cf, &xe->xdf2) < 0) {
+
+               xdl_free_ctx(&xe->xdf1);
+               xdl_free_classifier(&cf);
+               return -1;
+       }
+
+       if ((XDF_DIFF_ALG(xpp->flags) != XDF_PATIENCE_DIFF) &&
+           (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF) &&
+           xdl_optimize_ctxs(&cf, &xe->xdf1, &xe->xdf2) < 0) {
+
+               xdl_free_ctx(&xe->xdf2);
+               xdl_free_ctx(&xe->xdf1);
+               xdl_free_classifier(&cf);
+               return -1;
+       }
+
+       if (XDF_DIFF_ALG(xpp->flags) != XDF_HISTOGRAM_DIFF)
+               xdl_free_classifier(&cf);
+
+       return 0;
+}
+
+
+void xdl_free_env(xdfenv_t *xe) {
+
+       xdl_free_ctx(&xe->xdf2);
+       xdl_free_ctx(&xe->xdf1);
+}
+
+
+static int xdl_clean_mmatch(char const *dis, long i, long s, long e) {
+       long r, rdis0, rpdis0, rdis1, rpdis1;
+
+       /*
+        * Limits the window the is examined during the similar-lines
+        * scan. The loops below stops when dis[i - r] == 1 (line that
+        * has no match), but there are corner cases where the loop
+        * proceed all the way to the extremities by causing huge
+        * performance penalties in case of big files.
+        */
+       if (i - s > XDL_SIMSCAN_WINDOW)
+               s = i - XDL_SIMSCAN_WINDOW;
+       if (e - i > XDL_SIMSCAN_WINDOW)
+               e = i + XDL_SIMSCAN_WINDOW;
+
+       /*
+        * Scans the lines before 'i' to find a run of lines that either
+        * have no match (dis[j] == 0) or have multiple matches (dis[j] > 1).
+        * Note that we always call this function with dis[i] > 1, so the
+        * current line (i) is already a multimatch line.
+        */
+       for (r = 1, rdis0 = 0, rpdis0 = 1; (i - r) >= s; r++) {
+               if (!dis[i - r])
+                       rdis0++;
+               else if (dis[i - r] == 2)
+                       rpdis0++;
+               else
+                       break;
+       }
+       /*
+        * If the run before the line 'i' found only multimatch lines, we
+        * return 0 and hence we don't make the current line (i) discarded.
+        * We want to discard multimatch lines only when they appear in the
+        * middle of runs with nomatch lines (dis[j] == 0).
+        */
+       if (rdis0 == 0)
+               return 0;
+       for (r = 1, rdis1 = 0, rpdis1 = 1; (i + r) <= e; r++) {
+               if (!dis[i + r])
+                       rdis1++;
+               else if (dis[i + r] == 2)
+                       rpdis1++;
+               else
+                       break;
+       }
+       /*
+        * If the run after the line 'i' found only multimatch lines, we
+        * return 0 and hence we don't make the current line (i) discarded.
+        */
+       if (rdis1 == 0)
+               return 0;
+       rdis1 += rdis0;
+       rpdis1 += rpdis0;
+
+       return rpdis1 * XDL_KPDIS_RUN < (rpdis1 + rdis1);
+}
+
+
+/*
+ * Try to reduce the problem complexity, discard records that have no
+ * matches on the other file. Also, lines that have multiple matches
+ * might be potentially discarded if they happear in a run of discardable.
+ */
+static int xdl_cleanup_records(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, nm, nreff, mlim;
+       xrecord_t **recs;
+       xdlclass_t *rcrec;
+       char *dis, *dis1, *dis2;
+
+       if (!(dis = (char *) xdl_malloc(xdf1->nrec + xdf2->nrec + 2))) {
+
+               return -1;
+       }
+       memset(dis, 0, xdf1->nrec + xdf2->nrec + 2);
+       dis1 = dis;
+       dis2 = dis1 + xdf1->nrec + 1;
+
+       if ((mlim = xdl_bogosqrt(xdf1->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
+       for (i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart]; i <= xdf1->dend; i++, recs++) {
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len2 : 0;
+               dis1[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+       }
+
+       if ((mlim = xdl_bogosqrt(xdf2->nrec)) > XDL_MAX_EQLIMIT)
+               mlim = XDL_MAX_EQLIMIT;
+       for (i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart]; i <= xdf2->dend; i++, recs++) {
+               rcrec = cf->rcrecs[(*recs)->ha];
+               nm = rcrec ? rcrec->len1 : 0;
+               dis2[i] = (nm == 0) ? 0: (nm >= mlim) ? 2: 1;
+       }
+
+       for (nreff = 0, i = xdf1->dstart, recs = &xdf1->recs[xdf1->dstart];
+            i <= xdf1->dend; i++, recs++) {
+               if (dis1[i] == 1 ||
+                   (dis1[i] == 2 && !xdl_clean_mmatch(dis1, i, xdf1->dstart, xdf1->dend))) {
+                       xdf1->rindex[nreff] = i;
+                       xdf1->ha[nreff] = (*recs)->ha;
+                       nreff++;
+               } else
+                       xdf1->rchg[i] = 1;
+       }
+       xdf1->nreff = nreff;
+
+       for (nreff = 0, i = xdf2->dstart, recs = &xdf2->recs[xdf2->dstart];
+            i <= xdf2->dend; i++, recs++) {
+               if (dis2[i] == 1 ||
+                   (dis2[i] == 2 && !xdl_clean_mmatch(dis2, i, xdf2->dstart, xdf2->dend))) {
+                       xdf2->rindex[nreff] = i;
+                       xdf2->ha[nreff] = (*recs)->ha;
+                       nreff++;
+               } else
+                       xdf2->rchg[i] = 1;
+       }
+       xdf2->nreff = nreff;
+
+       xdl_free(dis);
+
+       return 0;
+}
+
+
+/*
+ * Early trim initial and terminal matching records.
+ */
+static int xdl_trim_ends(xdfile_t *xdf1, xdfile_t *xdf2) {
+       long i, lim;
+       xrecord_t **recs1, **recs2;
+
+       recs1 = xdf1->recs;
+       recs2 = xdf2->recs;
+       for (i = 0, lim = XDL_MIN(xdf1->nrec, xdf2->nrec); i < lim;
+            i++, recs1++, recs2++)
+               if ((*recs1)->ha != (*recs2)->ha)
+                       break;
+
+       xdf1->dstart = xdf2->dstart = i;
+
+       recs1 = xdf1->recs + xdf1->nrec - 1;
+       recs2 = xdf2->recs + xdf2->nrec - 1;
+       for (lim -= i, i = 0; i < lim; i++, recs1--, recs2--)
+               if ((*recs1)->ha != (*recs2)->ha)
+                       break;
+
+       xdf1->dend = xdf1->nrec - i - 1;
+       xdf2->dend = xdf2->nrec - i - 1;
+
+       return 0;
+}
+
+
+static int xdl_optimize_ctxs(xdlclassifier_t *cf, xdfile_t *xdf1, xdfile_t *xdf2) {
+
+       if (xdl_trim_ends(xdf1, xdf2) < 0 ||
+           xdl_cleanup_records(cf, xdf1, xdf2) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
diff --git a/src/xdiff/xprepare.h b/src/xdiff/xprepare.h
new file mode 100644 (file)
index 0000000..947d9fc
--- /dev/null
@@ -0,0 +1,34 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XPREPARE_H)
+#define XPREPARE_H
+
+
+
+int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
+                   xdfenv_t *xe);
+void xdl_free_env(xdfenv_t *xe);
+
+
+
+#endif /* #if !defined(XPREPARE_H) */
diff --git a/src/xdiff/xtypes.h b/src/xdiff/xtypes.h
new file mode 100644 (file)
index 0000000..8442bd4
--- /dev/null
@@ -0,0 +1,67 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XTYPES_H)
+#define XTYPES_H
+
+
+
+typedef struct s_chanode {
+       struct s_chanode *next;
+       long icurr;
+} chanode_t;
+
+typedef struct s_chastore {
+       chanode_t *head, *tail;
+       long isize, nsize;
+       chanode_t *ancur;
+       chanode_t *sncur;
+       long scurr;
+} chastore_t;
+
+typedef struct s_xrecord {
+       struct s_xrecord *next;
+       char const *ptr;
+       long size;
+       unsigned long ha;
+} xrecord_t;
+
+typedef struct s_xdfile {
+       chastore_t rcha;
+       long nrec;
+       unsigned int hbits;
+       xrecord_t **rhash;
+       long dstart, dend;
+       xrecord_t **recs;
+       char *rchg;
+       long *rindex;
+       long nreff;
+       unsigned long *ha;
+} xdfile_t;
+
+typedef struct s_xdfenv {
+       xdfile_t xdf1, xdf2;
+} xdfenv_t;
+
+
+
+#endif /* #if !defined(XTYPES_H) */
diff --git a/src/xdiff/xutils.c b/src/xdiff/xutils.c
new file mode 100644 (file)
index 0000000..4ab6ca8
--- /dev/null
@@ -0,0 +1,425 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003 Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#include <limits.h>
+#include <assert.h>
+#include "xinclude.h"
+
+
+
+
+long xdl_bogosqrt(long n) {
+       long i;
+
+       /*
+        * Classical integer square root approximation using shifts.
+        */
+       for (i = 1; n > 0; n >>= 2)
+               i <<= 1;
+
+       return i;
+}
+
+
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+                    xdemitcb_t *ecb) {
+       int i = 2;
+       mmbuffer_t mb[3];
+
+       mb[0].ptr = (char *) pre;
+       mb[0].size = psize;
+       mb[1].ptr = (char *) rec;
+       mb[1].size = size;
+       if (size > 0 && rec[size - 1] != '\n') {
+               mb[2].ptr = (char *) "\n\\ No newline at end of file\n";
+               mb[2].size = strlen(mb[2].ptr);
+               i++;
+       }
+       if (ecb->outf(ecb->priv, mb, i) < 0) {
+
+               return -1;
+       }
+
+       return 0;
+}
+
+void *xdl_mmfile_first(mmfile_t *mmf, long *size)
+{
+       *size = mmf->size;
+       return mmf->ptr;
+}
+
+
+long xdl_mmfile_size(mmfile_t *mmf)
+{
+       return mmf->size;
+}
+
+
+int xdl_cha_init(chastore_t *cha, long isize, long icount) {
+
+       cha->head = cha->tail = NULL;
+       cha->isize = isize;
+       cha->nsize = icount * isize;
+       cha->ancur = cha->sncur = NULL;
+       cha->scurr = 0;
+
+       return 0;
+}
+
+
+void xdl_cha_free(chastore_t *cha) {
+       chanode_t *cur, *tmp;
+
+       for (cur = cha->head; (tmp = cur) != NULL;) {
+               cur = cur->next;
+               xdl_free(tmp);
+       }
+}
+
+
+void *xdl_cha_alloc(chastore_t *cha) {
+       chanode_t *ancur;
+       void *data;
+
+       if (!(ancur = cha->ancur) || ancur->icurr == cha->nsize) {
+               if (!(ancur = (chanode_t *) xdl_malloc(sizeof(chanode_t) + cha->nsize))) {
+
+                       return NULL;
+               }
+               ancur->icurr = 0;
+               ancur->next = NULL;
+               if (cha->tail)
+                       cha->tail->next = ancur;
+               if (!cha->head)
+                       cha->head = ancur;
+               cha->tail = ancur;
+               cha->ancur = ancur;
+       }
+
+       data = (char *) ancur + sizeof(chanode_t) + ancur->icurr;
+       ancur->icurr += cha->isize;
+
+       return data;
+}
+
+long xdl_guess_lines(mmfile_t *mf, long sample) {
+       long nl = 0, size, tsize = 0;
+       char const *data, *cur, *top;
+
+       if ((cur = data = xdl_mmfile_first(mf, &size)) != NULL) {
+               for (top = data + size; nl < sample && cur < top; ) {
+                       nl++;
+                       if (!(cur = memchr(cur, '\n', top - cur)))
+                               cur = top;
+                       else
+                               cur++;
+               }
+               tsize += (long) (cur - data);
+       }
+
+       if (nl && tsize)
+               nl = xdl_mmfile_size(mf) / (tsize / nl);
+
+       return nl + 1;
+}
+
+int xdl_blankline(const char *line, long size, long flags)
+{
+       long i;
+
+       if (!(flags & XDF_WHITESPACE_FLAGS))
+               return (size <= 1);
+
+       for (i = 0; i < size && XDL_ISSPACE(line[i]); i++)
+               ;
+
+       return (i == size);
+}
+
+/*
+ * Have we eaten everything on the line, except for an optional
+ * CR at the very end?
+ */
+static int ends_with_optional_cr(const char *l, long s, long i)
+{
+       int complete = s && l[s-1] == '\n';
+
+       if (complete)
+               s--;
+       if (s == i)
+               return 1;
+       /* do not ignore CR at the end of an incomplete line */
+       if (complete && s == i + 1 && l[i] == '\r')
+               return 1;
+       return 0;
+}
+
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags)
+{
+       int i1, i2;
+
+       if (s1 == s2 && !memcmp(l1, l2, s1))
+               return 1;
+       if (!(flags & XDF_WHITESPACE_FLAGS))
+               return 0;
+
+       i1 = 0;
+       i2 = 0;
+
+       /*
+        * -w matches everything that matches with -b, and -b in turn
+        * matches everything that matches with --ignore-space-at-eol,
+        * which in turn matches everything that matches with --ignore-cr-at-eol.
+        *
+        * Each flavor of ignoring needs different logic to skip whitespaces
+        * while we have both sides to compare.
+        */
+       if (flags & XDF_IGNORE_WHITESPACE) {
+               goto skip_ws;
+               while (i1 < s1 && i2 < s2) {
+                       if (l1[i1++] != l2[i2++])
+                               return 0;
+               skip_ws:
+                       while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+                               i1++;
+                       while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+                               i2++;
+               }
+       } else if (flags & XDF_IGNORE_WHITESPACE_CHANGE) {
+               while (i1 < s1 && i2 < s2) {
+                       if (XDL_ISSPACE(l1[i1]) && XDL_ISSPACE(l2[i2])) {
+                               /* Skip matching spaces and try again */
+                               while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+                                       i1++;
+                               while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+                                       i2++;
+                               continue;
+                       }
+                       if (l1[i1++] != l2[i2++])
+                               return 0;
+               }
+       } else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL) {
+               while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+                       i1++;
+                       i2++;
+               }
+       } else if (flags & XDF_IGNORE_CR_AT_EOL) {
+               /* Find the first difference and see how the line ends */
+               while (i1 < s1 && i2 < s2 && l1[i1] == l2[i2]) {
+                       i1++;
+                       i2++;
+               }
+               return (ends_with_optional_cr(l1, s1, i1) &&
+                       ends_with_optional_cr(l2, s2, i2));
+       }
+
+       /*
+        * After running out of one side, the remaining side must have
+        * nothing but whitespace for the lines to match.  Note that
+        * ignore-whitespace-at-eol case may break out of the loop
+        * while there still are characters remaining on both lines.
+        */
+       if (i1 < s1) {
+               while (i1 < s1 && XDL_ISSPACE(l1[i1]))
+                       i1++;
+               if (s1 != i1)
+                       return 0;
+       }
+       if (i2 < s2) {
+               while (i2 < s2 && XDL_ISSPACE(l2[i2]))
+                       i2++;
+               return (s2 == i2);
+       }
+       return 1;
+}
+
+static unsigned long xdl_hash_record_with_whitespace(char const **data,
+               char const *top, long flags) {
+       unsigned long ha = 5381;
+       char const *ptr = *data;
+       int cr_at_eol_only = (flags & XDF_WHITESPACE_FLAGS) == XDF_IGNORE_CR_AT_EOL;
+
+       for (; ptr < top && *ptr != '\n'; ptr++) {
+               if (cr_at_eol_only) {
+                       /* do not ignore CR at the end of an incomplete line */
+                       if (*ptr == '\r' &&
+                           (ptr + 1 < top && ptr[1] == '\n'))
+                               continue;
+               }
+               else if (XDL_ISSPACE(*ptr)) {
+                       const char *ptr2 = ptr;
+                       int at_eol;
+                       while (ptr + 1 < top && XDL_ISSPACE(ptr[1])
+                                       && ptr[1] != '\n')
+                               ptr++;
+                       at_eol = (top <= ptr + 1 || ptr[1] == '\n');
+                       if (flags & XDF_IGNORE_WHITESPACE)
+                               ; /* already handled */
+                       else if (flags & XDF_IGNORE_WHITESPACE_CHANGE
+                                && !at_eol) {
+                               ha += (ha << 5);
+                               ha ^= (unsigned long) ' ';
+                       }
+                       else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL
+                                && !at_eol) {
+                               while (ptr2 != ptr + 1) {
+                                       ha += (ha << 5);
+                                       ha ^= (unsigned long) *ptr2;
+                                       ptr2++;
+                               }
+                       }
+                       continue;
+               }
+               ha += (ha << 5);
+               ha ^= (unsigned long) *ptr;
+       }
+       *data = ptr < top ? ptr + 1: ptr;
+
+       return ha;
+}
+
+unsigned long xdl_hash_record(char const **data, char const *top, long flags) {
+       unsigned long ha = 5381;
+       char const *ptr = *data;
+
+       if (flags & XDF_WHITESPACE_FLAGS)
+               return xdl_hash_record_with_whitespace(data, top, flags);
+
+       for (; ptr < top && *ptr != '\n'; ptr++) {
+               ha += (ha << 5);
+               ha ^= (unsigned long) *ptr;
+       }
+       *data = ptr < top ? ptr + 1: ptr;
+
+       return ha;
+}
+
+unsigned int xdl_hashbits(unsigned int size) {
+       unsigned int val = 1, bits = 0;
+
+       for (; val < size && bits < CHAR_BIT * sizeof(unsigned int); val <<= 1, bits++);
+       return bits ? bits: 1;
+}
+
+
+int xdl_num_out(char *out, long val) {
+       char *ptr, *str = out;
+       char buf[32];
+
+       ptr = buf + sizeof(buf) - 1;
+       *ptr = '\0';
+       if (val < 0) {
+               *--ptr = '-';
+               val = -val;
+       }
+       for (; val && ptr > buf; val /= 10)
+               *--ptr = "0123456789"[val % 10];
+       if (*ptr)
+               for (; *ptr; ptr++, str++)
+                       *str = *ptr;
+       else
+               *str++ = '0';
+       *str = '\0';
+
+       return str - out;
+}
+
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+                     const char *func, long funclen, xdemitcb_t *ecb) {
+       int nb = 0;
+       mmbuffer_t mb;
+       char buf[128];
+
+       memcpy(buf, "@@ -", 4);
+       nb += 4;
+
+       nb += xdl_num_out(buf + nb, c1 ? s1: s1 - 1);
+
+       if (c1 != 1) {
+               memcpy(buf + nb, ",", 1);
+               nb += 1;
+
+               nb += xdl_num_out(buf + nb, c1);
+       }
+
+       memcpy(buf + nb, " +", 2);
+       nb += 2;
+
+       nb += xdl_num_out(buf + nb, c2 ? s2: s2 - 1);
+
+       if (c2 != 1) {
+               memcpy(buf + nb, ",", 1);
+               nb += 1;
+
+               nb += xdl_num_out(buf + nb, c2);
+       }
+
+       memcpy(buf + nb, " @@", 3);
+       nb += 3;
+       if (func && funclen) {
+               buf[nb++] = ' ';
+               if (funclen > (long)sizeof(buf) - nb - 1)
+                       funclen = sizeof(buf) - nb - 1;
+               memcpy(buf + nb, func, funclen);
+               nb += funclen;
+       }
+       buf[nb++] = '\n';
+
+       mb.ptr = buf;
+       mb.size = nb;
+       if (ecb->outf(ecb->priv, &mb, 1) < 0)
+               return -1;
+
+       return 0;
+}
+
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+               int line1, int count1, int line2, int count2)
+{
+       /*
+        * This probably does not work outside Git, since
+        * we have a very simple mmfile structure.
+        *
+        * Note: ideally, we would reuse the prepared environment, but
+        * the libxdiff interface does not (yet) allow for diffing only
+        * ranges of lines instead of the whole files.
+        */
+       mmfile_t subfile1, subfile2;
+       xdfenv_t env;
+
+       subfile1.ptr = (char *)diff_env->xdf1.recs[line1 - 1]->ptr;
+       subfile1.size = diff_env->xdf1.recs[line1 + count1 - 2]->ptr +
+               diff_env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr;
+       subfile2.ptr = (char *)diff_env->xdf2.recs[line2 - 1]->ptr;
+       subfile2.size = diff_env->xdf2.recs[line2 + count2 - 2]->ptr +
+               diff_env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr;
+       if (xdl_do_diff(&subfile1, &subfile2, xpp, &env) < 0)
+               return -1;
+
+       memcpy(diff_env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1);
+       memcpy(diff_env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2);
+
+       xdl_free_env(&env);
+
+       return 0;
+}
diff --git a/src/xdiff/xutils.h b/src/xdiff/xutils.h
new file mode 100644 (file)
index 0000000..fba7bae
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ *  LibXDiff by Davide Libenzi ( File Differential Library )
+ *  Copyright (C) 2003  Davide Libenzi
+ *
+ *  This library is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public
+ *  License as published by the Free Software Foundation; either
+ *  version 2.1 of the License, or (at your option) any later version.
+ *
+ *  This library 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
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this library; if not, see
+ *  <http://www.gnu.org/licenses/>.
+ *
+ *  Davide Libenzi <davidel@xmailserver.org>
+ *
+ */
+
+#if !defined(XUTILS_H)
+#define XUTILS_H
+
+
+
+long xdl_bogosqrt(long n);
+int xdl_emit_diffrec(char const *rec, long size, char const *pre, long psize,
+                    xdemitcb_t *ecb);
+int xdl_cha_init(chastore_t *cha, long isize, long icount);
+void xdl_cha_free(chastore_t *cha);
+void *xdl_cha_alloc(chastore_t *cha);
+long xdl_guess_lines(mmfile_t *mf, long sample);
+int xdl_blankline(const char *line, long size, long flags);
+int xdl_recmatch(const char *l1, long s1, const char *l2, long s2, long flags);
+unsigned long xdl_hash_record(char const **data, char const *top, long flags);
+unsigned int xdl_hashbits(unsigned int size);
+int xdl_num_out(char *out, long val);
+int xdl_emit_hunk_hdr(long s1, long c1, long s2, long c2,
+                     const char *func, long funclen, xdemitcb_t *ecb);
+int xdl_fall_back_diff(xdfenv_t *diff_env, xpparam_t const *xpp,
+                      int line1, int count1, int line2, int count2);
+
+
+
+#endif /* #if !defined(XUTILS_H) */