diff options
Diffstat (limited to 'xdiff')
-rw-r--r-- | xdiff/xdiff.h | 9 | ||||
-rw-r--r-- | xdiff/xdiffi.c | 7 | ||||
-rw-r--r-- | xdiff/xdiffi.h | 2 | ||||
-rw-r--r-- | xdiff/xemit.c | 6 | ||||
-rw-r--r-- | xdiff/xemit.h | 3 | ||||
-rw-r--r-- | xdiff/xmerge.c | 237 | ||||
-rw-r--r-- | xdiff/xpatience.c | 381 | ||||
-rw-r--r-- | xdiff/xprepare.c | 18 | ||||
-rw-r--r-- | xdiff/xutils.c | 6 |
9 files changed, 588 insertions, 81 deletions
diff --git a/xdiff/xdiff.h b/xdiff/xdiff.h index 413082e1fd..4da052a3ff 100644 --- a/xdiff/xdiff.h +++ b/xdiff/xdiff.h @@ -32,6 +32,7 @@ extern "C" { #define XDF_IGNORE_WHITESPACE (1 << 2) #define XDF_IGNORE_WHITESPACE_CHANGE (1 << 3) #define XDF_IGNORE_WHITESPACE_AT_EOL (1 << 4) +#define XDF_PATIENCE_DIFF (1 << 5) #define XDF_WHITESPACE_FLAGS (XDF_IGNORE_WHITESPACE | XDF_IGNORE_WHITESPACE_CHANGE | XDF_IGNORE_WHITESPACE_AT_EOL) #define XDL_PATCH_NORMAL '-' @@ -50,10 +51,16 @@ extern "C" { #define XDL_BDOP_CPY 2 #define XDL_BDOP_INSB 3 +/* merge simplification levels */ #define XDL_MERGE_MINIMAL 0 #define XDL_MERGE_EAGER 1 #define XDL_MERGE_ZEALOUS 2 #define XDL_MERGE_ZEALOUS_ALNUM 3 +#define XDL_MERGE_LEVEL_MASK 0x0f + +/* merge output styles */ +#define XDL_MERGE_DIFF3 0x8000 +#define XDL_MERGE_STYLE_MASK 0x8000 typedef struct s_mmfile { char *ptr; @@ -78,9 +85,11 @@ typedef long (*find_func_t)(const char *line, long line_len, char *buffer, long typedef struct s_xdemitconf { long ctxlen; + long interhunkctxlen; unsigned long flags; find_func_t find_func; void *find_func_priv; + void (*emit_func)(); } xdemitconf_t; typedef struct s_bdiffparam { diff --git a/xdiff/xdiffi.c b/xdiff/xdiffi.c index 1bad8462fb..3e97462bdd 100644 --- a/xdiff/xdiffi.c +++ b/xdiff/xdiffi.c @@ -329,6 +329,9 @@ int xdl_do_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdalgoenv_t xenv; diffdata_t dd1, dd2; + if (xpp->flags & XDF_PATIENCE_DIFF) + return xdl_do_patience_diff(mf1, mf2, xpp, xe); + if (xdl_prepare_env(mf1, mf2, xpp, xe) < 0) { return -1; @@ -538,6 +541,8 @@ 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->emit_func ? + (emit_func_t)xecfg->emit_func : xdl_emit_diff; if (xdl_do_diff(mf1, mf2, xpp, &xe) < 0) { @@ -551,7 +556,7 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, return -1; } if (xscr) { - if (xdl_emit_diff(&xe, xscr, ecb, xecfg) < 0) { + if (ef(&xe, xscr, ecb, xecfg) < 0) { xdl_free_script(xscr); xdl_free_env(&xe); diff --git a/xdiff/xdiffi.h b/xdiff/xdiffi.h index 3e099dc445..ad033a8e6a 100644 --- a/xdiff/xdiffi.h +++ b/xdiff/xdiffi.h @@ -55,5 +55,7 @@ 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); #endif /* #if !defined(XDIFFI_H) */ diff --git a/xdiff/xemit.c b/xdiff/xemit.c index d3d9c845c6..05bfa41f10 100644 --- a/xdiff/xemit.c +++ b/xdiff/xemit.c @@ -27,7 +27,6 @@ static long xdl_get_rec(xdfile_t *xdf, long ri, char const **rec); static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t *ecb); -static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg); @@ -58,11 +57,12 @@ static int xdl_emit_record(xdfile_t *xdf, long ri, char const *pre, xdemitcb_t * * Starting at the passed change atom, find the latest change atom to be included * inside the differential hunk according to the specified configuration. */ -static xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { +xdchange_t *xdl_get_hunk(xdchange_t *xscr, xdemitconf_t const *xecfg) { xdchange_t *xch, *xchp; + long max_common = 2 * xecfg->ctxlen + xecfg->interhunkctxlen; for (xchp = xscr, xch = xscr->next; xch; xchp = xch, xch = xch->next) - if (xch->i1 - (xchp->i1 + xchp->chg1) > 2 * xecfg->ctxlen) + if (xch->i1 - (xchp->i1 + xchp->chg1) > max_common) break; return xchp; diff --git a/xdiff/xemit.h b/xdiff/xemit.h index 440a7390fa..c2e2e83027 100644 --- a/xdiff/xemit.h +++ b/xdiff/xemit.h @@ -24,7 +24,10 @@ #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); diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 82b3573e7a..d9737f04c2 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -30,17 +30,32 @@ typedef struct s_xdmerge { * 2 = no conflict, take second. */ int mode; + /* + * These point at the respective postimages. E.g. <i1,chg1> is + * how side #1 wants to change the common ancestor; if there is no + * overlap, lines before i1 in the postimage of side #1 appear + * in the merge result as a region touched by neither side. + */ long i1, i2; long chg1, chg2; + /* + * These point at the preimage; of course there is just one + * preimage, that is from the shared common ancestor. + */ + long i0; + long chg0; } xdmerge_t; static int xdl_append_merge(xdmerge_t **merge, int mode, - long i1, long chg1, long i2, long chg2) + long i0, long chg0, + long i1, long chg1, + long i2, long chg2) { xdmerge_t *m = *merge; if (m && (i1 <= m->i1 + m->chg1 || i2 <= m->i2 + m->chg2)) { if (mode != m->mode) m->mode = 0; + m->chg0 = i0 + chg0 - m->i0; m->chg1 = i1 + chg1 - m->i1; m->chg2 = i2 + chg2 - m->i2; } else { @@ -49,6 +64,8 @@ static int xdl_append_merge(xdmerge_t **merge, int mode, return -1; m->next = NULL; m->mode = mode; + m->i0 = i0; + m->chg0 = chg0; m->i1 = i1; m->chg1 = chg1; m->i2 = i2; @@ -91,11 +108,13 @@ static int xdl_merge_cmp_lines(xdfenv_t *xe1, int i1, xdfenv_t *xe2, int i2, return 0; } -static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +static int xdl_recs_copy_0(int use_orig, xdfenv_t *xe, int i, int count, int add_nl, char *dest) { - xrecord_t **recs = xe->xdf2.recs + i; + xrecord_t **recs; int size = 0; + recs = (use_orig ? xe->xdf1.recs : xe->xdf2.recs) + i; + if (count < 1) return 0; @@ -113,65 +132,109 @@ static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) return size; } -static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, - xdfenv_t *xe2, const char *name2, xdmerge_t *m, char *dest) +static int xdl_recs_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(0, xe, i, count, add_nl, dest); +} + +static int xdl_orig_copy(xdfenv_t *xe, int i, int count, int add_nl, char *dest) +{ + return xdl_recs_copy_0(1, xe, i, count, add_nl, dest); +} + +static int fill_conflict_hunk(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + int size, int i, int style, + xdmerge_t *m, char *dest) { const int marker_size = 7; int marker1_size = (name1 ? strlen(name1) + 1 : 0); int marker2_size = (name2 ? strlen(name2) + 1 : 0); - int conflict_marker_size = 3 * (marker_size + 1) - + marker1_size + marker2_size; - int size, i1, j; - - for (size = i1 = 0; m; m = m->next) { - if (m->mode == 0) { - size += xdl_recs_copy(xe1, i1, m->i1 - i1, 0, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '<'; - if (marker1_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name1, - marker1_size - 1); - size += marker1_size; - } - dest[size++] = '\n'; - } else - size += conflict_marker_size; - size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '='; - dest[size++] = '\n'; - } - size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, - dest ? dest + size : NULL); - if (dest) { - for (j = 0; j < marker_size; j++) - dest[size++] = '>'; - if (marker2_size) { - dest[size] = ' '; - memcpy(dest + size + 1, name2, - marker2_size - 1); - size += marker2_size; - } - dest[size++] = '\n'; - } - } else if (m->mode == 1) - size += xdl_recs_copy(xe1, i1, m->i1 + m->chg1 - i1, 0, - dest ? dest + size : NULL); + int j; + + /* Before conflicting part */ + size += xdl_recs_copy(xe1, i, m->i1 - i, 0, + dest ? dest + size : NULL); + + if (!dest) { + size += marker_size + 1 + marker1_size; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '<'; + if (marker1_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name1, marker1_size - 1); + size += marker1_size; + } + dest[size++] = '\n'; + } + + /* Postimage from side #1 */ + size += xdl_recs_copy(xe1, m->i1, m->chg1, 1, + dest ? dest + size : NULL); + + if (style == XDL_MERGE_DIFF3) { + /* Shared preimage */ + if (!dest) { + size += marker_size + 1; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '|'; + dest[size++] = '\n'; + } + size += xdl_orig_copy(xe1, m->i0, m->chg0, 1, + dest ? dest + size : NULL); + } + + if (!dest) { + size += marker_size + 1; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '='; + dest[size++] = '\n'; + } + + /* Postimage from side #2 */ + size += xdl_recs_copy(xe2, m->i2, m->chg2, 1, + dest ? dest + size : NULL); + if (!dest) { + size += marker_size + 1 + marker2_size; + } else { + for (j = 0; j < marker_size; j++) + dest[size++] = '>'; + if (marker2_size) { + dest[size] = ' '; + memcpy(dest + size + 1, name2, marker2_size - 1); + size += marker2_size; + } + dest[size++] = '\n'; + } + return size; +} + +static int xdl_fill_merge_buffer(xdfenv_t *xe1, const char *name1, + xdfenv_t *xe2, const char *name2, + xdmerge_t *m, char *dest, int style) +{ + int size, i; + + for (size = i = 0; m; m = m->next) { + if (m->mode == 0) + size = fill_conflict_hunk(xe1, name1, xe2, name2, + size, i, style, m, dest); + else if (m->mode == 1) + size += xdl_recs_copy(xe1, i, m->i1 + m->chg1 - i, 0, + dest ? dest + size : NULL); else if (m->mode == 2) - size += xdl_recs_copy(xe2, m->i2 - m->i1 + i1, - m->i1 + m->chg2 - i1, 0, - dest ? dest + size : NULL); + size += xdl_recs_copy(xe2, m->i2 - m->i1 + i, + m->i1 + m->chg2 - i, 0, + dest ? dest + size : NULL); else continue; - i1 = m->i1 + m->chg1; + i = m->i1 + m->chg1; } - size += xdl_recs_copy(xe1, i1, xe1->xdf2.nrec - i1, 0, - dest ? dest + size : NULL); + size += xdl_recs_copy(xe1, i, xe1->xdf2.nrec - i, 0, + dest ? dest + size : NULL); return size; } @@ -323,9 +386,20 @@ static int xdl_simplify_non_conflicts(xdfenv_t *xe1, xdmerge_t *m, */ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, xdfenv_t *xe2, xdchange_t *xscr2, const char *name2, - int level, xpparam_t const *xpp, mmbuffer_t *result) { + int flags, xpparam_t const *xpp, mmbuffer_t *result) { xdmerge_t *changes, *c; - int i1, i2, chg1, chg2; + int i0, i1, i2, chg0, chg1, chg2; + int level = flags & XDL_MERGE_LEVEL_MASK; + int style = flags & XDL_MERGE_STYLE_MASK; + + if (style == XDL_MERGE_DIFF3) { + /* + * "diff3 -m" output does not make sense for anything + * more aggressive than XDL_MERGE_EAGER. + */ + if (XDL_MERGE_EAGER < level) + level = XDL_MERGE_EAGER; + } c = changes = NULL; @@ -333,11 +407,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (!changes) changes = c; if (xscr1->i1 + xscr1->chg1 < xscr2->i1) { + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr2->i2 - xscr2->i1 + xscr1->i1; + chg0 = xscr1->chg1; chg1 = xscr1->chg2; chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -345,18 +422,21 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, continue; } if (xscr2->i1 + xscr2->chg1 < xscr1->i1) { + i0 = xscr2->i1; i1 = xscr1->i2 - xscr1->i1 + xscr2->i1; i2 = xscr2->i2; + chg0 = xscr2->chg1; chg1 = xscr2->chg1; chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } xscr2 = xscr2->next; continue; } - if (level < 1 || xscr1->i1 != xscr2->i1 || + if (level == XDL_MERGE_MINIMAL || xscr1->i1 != xscr2->i1 || xscr1->chg1 != xscr2->chg1 || xscr1->chg2 != xscr2->chg2 || xdl_merge_cmp_lines(xe1, xscr1->i2, @@ -366,19 +446,25 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, int off = xscr1->i1 - xscr2->i1; int ffo = off + xscr1->chg1 - xscr2->chg1; + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr2->i2; - if (off > 0) + if (off > 0) { + i0 -= off; i1 -= off; + } else i2 += off; + chg0 = xscr1->i1 + xscr1->chg1 - i0; chg1 = xscr1->i2 + xscr1->chg2 - i1; chg2 = xscr2->i2 + xscr2->chg2 - i2; - if (ffo > 0) - chg2 += ffo; - else + if (ffo < 0) { + chg0 -= ffo; chg1 -= ffo; - if (xdl_append_merge(&c, 0, i1, chg1, i2, chg2)) { + } else + chg2 += ffo; + if (xdl_append_merge(&c, 0, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -395,11 +481,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, while (xscr1) { if (!changes) changes = c; + i0 = xscr1->i1; i1 = xscr1->i2; i2 = xscr1->i1 + xe2->xdf2.nrec - xe2->xdf1.nrec; + chg0 = xscr1->chg1; chg1 = xscr1->chg2; chg2 = xscr1->chg1; - if (xdl_append_merge(&c, 1, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 1, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -408,11 +497,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, while (xscr2) { if (!changes) changes = c; + i0 = xscr2->i1; i1 = xscr2->i1 + xe1->xdf2.nrec - xe1->xdf1.nrec; i2 = xscr2->i2; + chg0 = xscr2->chg1; chg1 = xscr2->chg1; chg2 = xscr2->chg2; - if (xdl_append_merge(&c, 2, i1, chg1, i2, chg2)) { + if (xdl_append_merge(&c, 2, + i0, chg0, i1, chg1, i2, chg2)) { xdl_cleanup_merge(changes); return -1; } @@ -421,16 +513,17 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, if (!changes) changes = c; /* refine conflicts */ - if (level > 1 && + if (XDL_MERGE_ZEALOUS <= level && (xdl_refine_conflicts(xe1, xe2, changes, xpp) < 0 || - xdl_simplify_non_conflicts(xe1, changes, level > 2) < 0)) { + xdl_simplify_non_conflicts(xe1, changes, + XDL_MERGE_ZEALOUS < level) < 0)) { xdl_cleanup_merge(changes); return -1; } /* output */ if (result) { int size = xdl_fill_merge_buffer(xe1, name1, xe2, name2, - changes, NULL); + changes, NULL, style); result->ptr = xdl_malloc(size); if (!result->ptr) { xdl_cleanup_merge(changes); @@ -438,14 +531,14 @@ static int xdl_do_merge(xdfenv_t *xe1, xdchange_t *xscr1, const char *name1, } result->size = size; xdl_fill_merge_buffer(xe1, name1, xe2, name2, changes, - result->ptr); + result->ptr, style); } return xdl_cleanup_merge(changes); } int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, mmfile_t *mf2, const char *name2, - xpparam_t const *xpp, int level, mmbuffer_t *result) { + xpparam_t const *xpp, int flags, mmbuffer_t *result) { xdchange_t *xscr1, *xscr2; xdfenv_t xe1, xe2; int status; @@ -482,7 +575,7 @@ int xdl_merge(mmfile_t *orig, mmfile_t *mf1, const char *name1, } else { status = xdl_do_merge(&xe1, xscr1, name1, &xe2, xscr2, name2, - level, xpp, result); + flags, xpp, result); } xdl_free_script(xscr1); xdl_free_script(xscr2); diff --git a/xdiff/xpatience.c b/xdiff/xpatience.c new file mode 100644 index 0000000000..e42c16a807 --- /dev/null +++ b/xdiff/xpatience.c @@ -0,0 +1,381 @@ +/* + * LibXDiff by Davide Libenzi ( File Differential Library ) + * Copyright (C) 2003-2009 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, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * 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; + } *entries, *first, *last; + /* were common records found? */ + unsigned long has_matches; + mmfile_t *file1, *file2; + xdfenv_t *env; + xpparam_t const *xpp; +}; + +/* The argument "pass" is 1 for the first file, 2 for the second. */ +static void insert_record(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; + 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(line1++, result, 1); + + /* Then search for matches in the second file */ + while (count2--) + insert_record(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) / 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; + + 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]; + sequence[++i] = entry; + 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) +{ + /* + * 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; + xpparam_t xpp; + xdfenv_t env; + + subfile1.ptr = (char *)map->env->xdf1.recs[line1 - 1]->ptr; + subfile1.size = map->env->xdf1.recs[line1 + count1 - 2]->ptr + + map->env->xdf1.recs[line1 + count1 - 2]->size - subfile1.ptr; + subfile2.ptr = (char *)map->env->xdf2.recs[line2 - 1]->ptr; + subfile2.size = map->env->xdf2.recs[line2 + count2 - 2]->ptr + + map->env->xdf2.recs[line2 + count2 - 2]->size - subfile2.ptr; + xpp.flags = map->xpp->flags & ~XDF_PATIENCE_DIFF; + if (xdl_do_diff(&subfile1, &subfile2, &xpp, &env) < 0) + return -1; + + memcpy(map->env->xdf1.rchg + line1 - 1, env.xdf1.rchg, count1); + memcpy(map->env->xdf2.rchg + line2 - 1, env.xdf2.rchg, count2); + + xdl_free_env(&env); + + return 0; +} + +/* + * 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/xdiff/xprepare.c b/xdiff/xprepare.c index e87ab57c65..1689085235 100644 --- a/xdiff/xprepare.c +++ b/xdiff/xprepare.c @@ -23,10 +23,9 @@ #include "xinclude.h" - #define XDL_KPDIS_RUN 4 #define XDL_MAX_EQLIMIT 1024 - +#define XDL_SIMSCAN_WINDOW 100 typedef struct s_xdlclass { @@ -291,7 +290,8 @@ int xdl_prepare_env(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp, xdl_free_classifier(&cf); - if (xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) { + if (!(xpp->flags & XDF_PATIENCE_DIFF) && + xdl_optimize_ctxs(&xe->xdf1, &xe->xdf2) < 0) { xdl_free_ctx(&xe->xdf2); xdl_free_ctx(&xe->xdf1); @@ -313,6 +313,18 @@ 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 diff --git a/xdiff/xutils.c b/xdiff/xutils.c index d7974d1a3e..04ad468702 100644 --- a/xdiff/xutils.c +++ b/xdiff/xutils.c @@ -245,12 +245,14 @@ static unsigned long xdl_hash_record_with_whitespace(char const **data, while (ptr + 1 < top && isspace(ptr[1]) && ptr[1] != '\n') ptr++; - if (flags & XDF_IGNORE_WHITESPACE_CHANGE + if (flags & XDF_IGNORE_WHITESPACE) + ; /* already handled */ + else if (flags & XDF_IGNORE_WHITESPACE_CHANGE && ptr[1] != '\n') { ha += (ha << 5); ha ^= (unsigned long) ' '; } - if (flags & XDF_IGNORE_WHITESPACE_AT_EOL + else if (flags & XDF_IGNORE_WHITESPACE_AT_EOL && ptr[1] != '\n') { while (ptr2 != ptr + 1) { ha += (ha << 5); |