Merge branch 'jc/bottomless'
authorJunio C Hamano <junkio@cox.net>
Mon, 17 Apr 2006 22:03:10 +0000 (15:03 -0700)
committerJunio C Hamano <junkio@cox.net>
Mon, 17 Apr 2006 22:03:10 +0000 (15:03 -0700)
* jc/bottomless:
  rev-list --bisect: limit list before bisecting.

15 files changed:
Documentation/diff-options.txt
GIT-VERSION-GEN
Makefile
commit.c
diff-tree.c
diff.c
diff.h
git-diff.sh
git.c
gitk
pager.c
t/Makefile
t/t5500-fetch-pack.sh
xdiff/xdiffi.c
xdiff/xmacros.h

index 338014c..c183dc9 100644 (file)
@@ -7,6 +7,12 @@
 --patch-with-raw::
        Generate patch but keep also the default raw diff output.
 
+--stat::
+       Generate a diffstat instead of a patch.
+
+--patch-with-stat::
+       Generate patch and prepend its diffstat.
+
 -z::
        \0 line termination on output
 
index 656f555..e88fe5a 100755 (executable)
@@ -1,7 +1,7 @@
 #!/bin/sh
 
 GVF=GIT-VERSION-FILE
-DEF_VER=v1.3-rc3.GIT
+DEF_VER=v1.3-rc4.GIT
 
 # First try git-describe, then see if there is a version file
 # (included in release tarballs), then default
index 1130af4..8371f7f 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -653,7 +653,7 @@ rpm: dist
 clean:
        rm -f *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o xdiff/*.o \
                $(LIB_FILE) $(XDIFF_LIB)
-       rm -f $(ALL_PROGRAMS) git$X
+       rm -f $(ALL_PROGRAMS) $(BUILT_INS) git$X
        rm -f *.spec *.pyc *.pyo */*.pyc */*.pyo common-cmds.h TAGS tags
        rm -rf $(GIT_TARNAME)
        rm -f $(GIT_TARNAME).tar.gz git-core_$(GIT_VERSION)-*.tar.gz
index ca25574..2717dd8 100644 (file)
--- a/commit.c
+++ b/commit.c
@@ -160,8 +160,8 @@ struct commit_graft *read_graft_line(char *buf, int len)
 
        if (buf[len-1] == '\n')
                buf[--len] = 0;
-       if (buf[0] == '#')
-               return 0;
+       if (buf[0] == '#' || buf[0] == '\0')
+               return NULL;
        if ((len + 1) % 41) {
        bad_graft_data:
                error("bad graft data: %s", buf);
@@ -192,6 +192,8 @@ int read_graft_file(const char *graft_file)
                /* The format is just "Commit Parent1 Parent2 ...\n" */
                int len = strlen(buf);
                struct commit_graft *graft = read_graft_line(buf, len);
+               if (!graft)
+                       continue;
                if (register_commit_graft(graft, 1))
                        error("duplicate graft data: %s", buf);
        }
index 2b79dd0..7015b06 100644 (file)
@@ -120,7 +120,7 @@ int main(int argc, const char **argv)
        if (opt->diffopt.output_format == DIFF_FORMAT_PATCH)
                opt->diffopt.recursive = 1;
 
-       diff_tree_setup_paths(get_pathspec(prefix, argv), opt);
+       diff_tree_setup_paths(get_pathspec(prefix, argv), &opt->diffopt);
        diff_setup_done(&opt->diffopt);
 
        switch (nr_sha1) {
diff --git a/diff.c b/diff.c
index a14e664..b54bbfa 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -8,7 +8,7 @@
 #include "quote.h"
 #include "diff.h"
 #include "diffcore.h"
-#include "xdiff/xdiff.h"
+#include "xdiff-interface.h"
 
 static int use_size_cache;
 
@@ -195,6 +195,143 @@ static int fn_out(void *priv, mmbuffer_t *mb, int nbuf)
        return 0;
 }
 
+struct diffstat_t {
+       struct xdiff_emit_state xm;
+
+       int nr;
+       int alloc;
+       struct diffstat_file {
+               char *name;
+               unsigned is_unmerged:1;
+               unsigned is_binary:1;
+               unsigned int added, deleted;
+       } **files;
+};
+
+static struct diffstat_file *diffstat_add(struct diffstat_t *diffstat,
+               const char *name)
+{
+       struct diffstat_file *x;
+       x = xcalloc(sizeof (*x), 1);
+       if (diffstat->nr == diffstat->alloc) {
+               diffstat->alloc = alloc_nr(diffstat->alloc);
+               diffstat->files = xrealloc(diffstat->files,
+                               diffstat->alloc * sizeof(x));
+       }
+       diffstat->files[diffstat->nr++] = x;
+       x->name = strdup(name);
+       return x;
+}
+
+static void diffstat_consume(void *priv, char *line, unsigned long len)
+{
+       struct diffstat_t *diffstat = priv;
+       struct diffstat_file *x = diffstat->files[diffstat->nr - 1];
+
+       if (line[0] == '+')
+               x->added++;
+       else if (line[0] == '-')
+               x->deleted++;
+}
+
+static const char pluses[] = "++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++";
+static const char minuses[]= "----------------------------------------------------------------------";
+
+static void show_stats(struct diffstat_t* data)
+{
+       char *prefix = "";
+       int i, len, add, del, total, adds = 0, dels = 0;
+       int max, max_change = 0, max_len = 0;
+       int total_files = data->nr;
+
+       if (data->nr == 0)
+               return;
+
+       for (i = 0; i < data->nr; i++) {
+               struct diffstat_file *file = data->files[i];
+
+               if (file->is_binary || file->is_unmerged)
+                       continue;
+               if (max_change < file->added + file->deleted)
+                       max_change = file->added + file->deleted;
+               len = strlen(file->name);
+               if (max_len < len)
+                       max_len = len;
+       }
+
+       for (i = 0; i < data->nr; i++) {
+               char *name = data->files[i]->name;
+               int added = data->files[i]->added;
+               int deleted = data->files[i]->deleted;
+
+               if (0 < (len = quote_c_style(name, NULL, NULL, 0))) {
+                       char *qname = xmalloc(len + 1);
+                       quote_c_style(name, qname, NULL, 0);
+                       free(name);
+                       data->files[i]->name = name = qname;
+               }
+
+               /*
+                * "scale" the filename
+                */
+               len = strlen(name);
+               max = max_len;
+               if (max > 50)
+                       max = 50;
+               if (len > max) {
+                       char *slash;
+                       prefix = "...";
+                       max -= 3;
+                       name += len - max;
+                       slash = strchr(name, '/');
+                       if (slash)
+                               name = slash;
+               }
+               len = max;
+
+               /*
+                * scale the add/delete
+                */
+               max = max_change;
+               if (max + len > 70)
+                       max = 70 - len;
+
+               if (data->files[i]->is_binary) {
+                       printf(" %s%-*s |  Bin\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (data->files[i]->is_unmerged) {
+                       printf(" %s%-*s |  Unmerged\n", prefix, len, name);
+                       goto free_diffstat_file;
+               }
+               else if (added + deleted == 0) {
+                       total_files--;
+                       goto free_diffstat_file;
+               }
+
+               add = added;
+               del = deleted;
+               total = add + del;
+               adds += add;
+               dels += del;
+
+               if (max_change > 0) {
+                       total = (total * max + max_change / 2) / max_change;
+                       add = (add * max + max_change / 2) / max_change;
+                       del = total - add;
+               }
+               printf(" %s%-*s |%5d %.*s%.*s\n", prefix,
+                               len, name, added + deleted,
+                               add, pluses, del, minuses);
+       free_diffstat_file:
+               free(data->files[i]->name);
+               free(data->files[i]);
+       }
+       free(data->files);
+       printf(" %d files changed, %d insertions(+), %d deletions(-)\n",
+                       total_files, adds, dels);
+}
+
 #define FIRST_FEW_BYTES 8000
 static int mmfile_is_binary(mmfile_t *mf)
 {
@@ -286,6 +423,40 @@ static void builtin_diff(const char *name_a,
        return;
 }
 
+static void builtin_diffstat(const char *name_a, const char *name_b,
+               struct diff_filespec *one, struct diff_filespec *two,
+               struct diffstat_t *diffstat)
+{
+       mmfile_t mf1, mf2;
+       struct diffstat_file *data;
+
+       data = diffstat_add(diffstat, name_a ? name_a : name_b);
+
+       if (!one || !two) {
+               data->is_unmerged = 1;
+               return;
+       }
+
+       if (fill_mmfile(&mf1, one) < 0 || fill_mmfile(&mf2, two) < 0)
+               die("unable to read files to diff");
+
+       if (mmfile_is_binary(&mf1) || mmfile_is_binary(&mf2))
+               data->is_binary = 1;
+       else {
+               /* Crazy xdl interfaces.. */
+               xpparam_t xpp;
+               xdemitconf_t xecfg;
+               xdemitcb_t ecb;
+
+               xpp.flags = XDF_NEED_MINIMAL;
+               xecfg.ctxlen = 0;
+               xecfg.flags = 0;
+               ecb.outf = xdiff_outf;
+               ecb.priv = diffstat;
+               xdl_diff(&mf1, &mf2, &xpp, &xecfg, &ecb);
+       }
+}
+
 struct diff_filespec *alloc_filespec(const char *path)
 {
        int namelen = strlen(path);
@@ -819,6 +990,27 @@ static void run_diff(struct diff_filepair *p, struct diff_options *o)
        free(other_munged);
 }
 
+static void run_diffstat(struct diff_filepair *p, struct diff_options *o,
+               struct diffstat_t *diffstat)
+{
+       const char *name;
+       const char *other;
+
+       if (DIFF_PAIR_UNMERGED(p)) {
+               /* unmerged */
+               builtin_diffstat(p->one->path, NULL, NULL, NULL, diffstat);
+               return;
+       }
+
+       name = p->one->path;
+       other = (strcmp(name, p->two->path) ? p->two->path : NULL);
+
+       diff_fill_sha1_info(p->one);
+       diff_fill_sha1_info(p->two);
+
+       builtin_diffstat(name, other, p->one, p->two, diffstat);
+}
+
 void diff_setup(struct diff_options *options)
 {
        memset(options, 0, sizeof(*options));
@@ -866,6 +1058,12 @@ int diff_opt_parse(struct diff_options *options, const char **av, int ac)
                options->output_format = DIFF_FORMAT_PATCH;
                options->with_raw = 1;
        }
+       else if (!strcmp(arg, "--stat"))
+               options->output_format = DIFF_FORMAT_DIFFSTAT;
+       else if (!strcmp(arg, "--patch-with-stat")) {
+               options->output_format = DIFF_FORMAT_PATCH;
+               options->with_stat = 1;
+       }
        else if (!strcmp(arg, "-z"))
                options->line_termination = 0;
        else if (!strncmp(arg, "-l", 2))
@@ -1160,11 +1358,24 @@ static void diff_flush_patch(struct diff_filepair *p, struct diff_options *o)
 
        if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
            (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
-               return; /* no tree diffs in patch format */ 
+               return; /* no tree diffs in patch format */
 
        run_diff(p, o);
 }
 
+static void diff_flush_stat(struct diff_filepair *p, struct diff_options *o,
+               struct diffstat_t *diffstat)
+{
+       if (diff_unmodified_pair(p))
+               return;
+
+       if ((DIFF_FILE_VALID(p->one) && S_ISDIR(p->one->mode)) ||
+           (DIFF_FILE_VALID(p->two) && S_ISDIR(p->two->mode)))
+               return; /* no tree diffs in patch format */
+
+       run_diffstat(p, o, diffstat);
+}
+
 int diff_queue_is_empty(void)
 {
        struct diff_queue_struct *q = &diff_queued_diff;
@@ -1276,7 +1487,8 @@ static void diff_resolve_rename_copy(void)
 
 static void flush_one_pair(struct diff_filepair *p,
                           int diff_output_format,
-                          struct diff_options *options)
+                          struct diff_options *options,
+                          struct diffstat_t *diffstat)
 {
        int inter_name_termination = '\t';
        int line_termination = options->line_termination;
@@ -1291,6 +1503,9 @@ static void flush_one_pair(struct diff_filepair *p,
                break;
        default:
                switch (diff_output_format) {
+               case DIFF_FORMAT_DIFFSTAT:
+                       diff_flush_stat(p, options, diffstat);
+                       break;
                case DIFF_FORMAT_PATCH:
                        diff_flush_patch(p, options);
                        break;
@@ -1316,19 +1531,42 @@ void diff_flush(struct diff_options *options)
        struct diff_queue_struct *q = &diff_queued_diff;
        int i;
        int diff_output_format = options->output_format;
+       struct diffstat_t *diffstat = NULL;
+
+       if (diff_output_format == DIFF_FORMAT_DIFFSTAT || options->with_stat) {
+               diffstat = xcalloc(sizeof (struct diffstat_t), 1);
+               diffstat->xm.consume = diffstat_consume;
+       }
 
        if (options->with_raw) {
                for (i = 0; i < q->nr; i++) {
                        struct diff_filepair *p = q->queue[i];
-                       flush_one_pair(p, DIFF_FORMAT_RAW, options);
+                       flush_one_pair(p, DIFF_FORMAT_RAW, options, NULL);
+               }
+               putchar(options->line_termination);
+       }
+       if (options->with_stat) {
+               for (i = 0; i < q->nr; i++) {
+                       struct diff_filepair *p = q->queue[i];
+                       flush_one_pair(p, DIFF_FORMAT_DIFFSTAT, options,
+                                       diffstat);
                }
+               show_stats(diffstat);
+               free(diffstat);
+               diffstat = NULL;
                putchar(options->line_termination);
        }
        for (i = 0; i < q->nr; i++) {
                struct diff_filepair *p = q->queue[i];
-               flush_one_pair(p, diff_output_format, options);
+               flush_one_pair(p, diff_output_format, options, diffstat);
                diff_free_filepair(p);
        }
+
+       if (diffstat) {
+               show_stats(diffstat);
+               free(diffstat);
+       }
+
        free(q->queue);
        q->queue = NULL;
        q->nr = q->alloc = 0;
diff --git a/diff.h b/diff.h
index 236095f..f783bae 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -25,6 +25,7 @@ struct diff_options {
        const char *pickaxe;
        unsigned recursive:1,
                 with_raw:1,
+                with_stat:1,
                 tree_in_recursive:1,
                 full_index:1;
        int break_opt;
@@ -119,6 +120,9 @@ extern void diffcore_std_no_resolve(struct diff_options *);
 "  -u            synonym for -p.\n" \
 "  --patch-with-raw\n" \
 "                output both a patch and the diff-raw format.\n" \
+"  --stat        show diffstat instead of patch.\n" \
+"  --patch-with-stat\n" \
+"                output a patch and prepend its diffstat.\n" \
 "  --name-only   show only names of changed files.\n" \
 "  --name-status show names and status of changed files.\n" \
 "  --full-index  show full object name on index lines.\n" \
@@ -142,6 +146,7 @@ extern int diff_queue_is_empty(void);
 #define DIFF_FORMAT_NO_OUTPUT  3
 #define DIFF_FORMAT_NAME       4
 #define DIFF_FORMAT_NAME_STATUS        5
+#define DIFF_FORMAT_DIFFSTAT   6
 
 extern void diff_flush(struct diff_options*);
 
index dc0dd31..0fe6770 100755 (executable)
@@ -30,9 +30,11 @@ case " $flags " in
        cc_or_p=--cc ;;
 esac
 
-# If we do not have --name-status, --name-only, -r, or -c default to --cc.
+# If we do not have --name-status, --name-only, -r, -c or --stat,
+# default to --cc.
 case " $flags " in
-*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* )
+*" '--name-status' "* | *" '--name-only' "* | *" '-r' "* | *" '-c' "* | \
+*" '--stat' "*)
        ;;
 *)
        flags="$flags'$cc_or_p' " ;;
diff --git a/git.c b/git.c
index 78ed403..140ed18 100644 (file)
--- a/git.c
+++ b/git.c
@@ -388,8 +388,10 @@ static int cmd_log(int argc, const char **argv, char **envp)
                pretty_print_commit(commit_format, commit, ~0, buf,
                                    LOGSIZE, abbrev);
                printf("%s\n", buf);
-               if (do_diff)
+               if (do_diff) {
+                       printf("---\n");
                        log_tree_commit(&opt, commit);
+               }
                shown = 1;
                free(commit->buffer);
                commit->buffer = NULL;
@@ -411,6 +413,12 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
        };
        int i;
 
+       /* Turn "git cmd --help" into "git help cmd" */
+       if (argc > 1 && !strcmp(argv[1], "--help")) {
+               argv[1] = argv[0];
+               argv[0] = cmd = "help";
+       }
+
        for (i = 0; i < ARRAY_SIZE(commands); i++) {
                struct cmd_struct *p = commands+i;
                if (strcmp(p->cmd, cmd))
diff --git a/gitk b/gitk
index f88c06e..87e7162 100755 (executable)
--- a/gitk
+++ b/gitk
@@ -1116,11 +1116,12 @@ proc layoutrows {row endrow last} {
 
 proc addextraid {id row} {
     global displayorder commitrow commitinfo
-    global commitidx
+    global commitidx commitlisted
     global parentlist childlist children
 
     incr commitidx
     lappend displayorder $id
+    lappend commitlisted 0
     lappend parentlist {}
     set commitrow($id) $row
     readcommit $id
@@ -1500,7 +1501,7 @@ proc drawcmittext {id row col rmx} {
 proc drawcmitrow {row} {
     global displayorder rowidlist
     global idrowranges idrangedrawn iddrawn
-    global commitinfo commitlisted parentlist numcommits
+    global commitinfo parentlist numcommits
 
     if {$row >= $numcommits} return
     foreach id [lindex $rowidlist $row] {
diff --git a/pager.c b/pager.c
index 1364e15..e5ba273 100644 (file)
--- a/pager.c
+++ b/pager.c
@@ -5,22 +5,24 @@
  * something different on Windows, for example.
  */
 
-static void run_pager(void)
+static void run_pager(const char *pager)
 {
-       const char *prog = getenv("PAGER");
-       if (!prog)
-               prog = "less";
-       setenv("LESS", "-S", 0);
-       execlp(prog, prog, NULL);
+       execlp(pager, pager, NULL);
 }
 
 void setup_pager(void)
 {
        pid_t pid;
        int fd[2];
+       const char *pager = getenv("PAGER");
 
        if (!isatty(1))
                return;
+       if (!pager)
+               pager = "less";
+       else if (!*pager)
+               return;
+
        if (pipe(fd) < 0)
                return;
        pid = fork();
@@ -43,6 +45,7 @@ void setup_pager(void)
        close(fd[0]);
        close(fd[1]);
 
-       run_pager();
+       setenv("LESS", "-S", 0);
+       run_pager(pager);
        exit(255);
 }
index fe65f53..5495985 100644 (file)
@@ -25,5 +25,5 @@ clean:
        rm -fr trash
 
 .PHONY: $(T) clean
-.NOPARALLEL:
+.NOTPARALLEL:
 
index e15e14f..92f12d9 100755 (executable)
@@ -12,11 +12,6 @@ test_description='Testing multi_ack pack fetching
 
 # Some convenience functions
 
-function show_count () {
-       commit_count=$(($commit_count+1))
-       printf "      %d\r" $commit_count
-}
-
 function add () {
        local name=$1
        local text="$@"
@@ -55,13 +50,6 @@ function test_expect_object_count () {
                "test $count = $output"
 }
 
-function test_repack () {
-       local rep=$1
-
-       test_expect_success "repack && prune-packed in $rep" \
-               '(git-repack && git-prune-packed)2>>log.txt'
-}
-
 function pull_to_client () {
        local number=$1
        local heads=$2
@@ -70,13 +58,23 @@ function pull_to_client () {
 
        cd client
        test_expect_success "$number pull" \
-               "git-fetch-pack -v .. $heads > log.txt 2>&1"
+               "git-fetch-pack -k -v .. $heads"
        case "$heads" in *A*) echo $ATIP > .git/refs/heads/A;; esac
        case "$heads" in *B*) echo $BTIP > .git/refs/heads/B;; esac
        git-symbolic-ref HEAD refs/heads/${heads:0:1}
+
        test_expect_success "fsck" 'git-fsck-objects --full > fsck.txt 2>&1'
-       test_expect_object_count "after $number pull" $count
-       pack_count=$(grep Unpacking log.txt|tr -dc "0-9")
+
+       test_expect_success 'check downloaded results' \
+       'mv .git/objects/pack/pack-* . &&
+        p=`ls -1 pack-*.pack` &&
+        git-unpack-objects <$p &&
+        git-fsck-objects --full'
+
+       test_expect_success "new object count after $number pull" \
+       'idx=`echo pack-*.idx` &&
+        pack_count=`git-show-index <$idx | wc -l` &&
+        test $pack_count = $count'
        test -z "$pack_count" && pack_count=0
        if [ -z "$no_strict_count_check" ]; then
                test_expect_success "minimal count" "test $count = $pack_count"
@@ -84,6 +82,7 @@ function pull_to_client () {
                test $count != $pack_count && \
                        echo "WARNING: $pack_count objects transmitted, only $count of which were needed"
        fi
+       rm -f pack-*
        cd ..
 }
 
@@ -117,8 +116,6 @@ git-symbolic-ref HEAD refs/heads/B
 
 pull_to_client 1st "B A" $((11*3))
 
-(cd client; test_repack client)
-
 add A11 $A10
 
 prev=1; cur=2; while [ $cur -le 65 ]; do
@@ -129,8 +126,6 @@ done
 
 pull_to_client 2nd "B" $((64*3))
 
-(cd client; test_repack client)
-
 pull_to_client 3rd "A" $((1*3)) # old fails
 
 test_done
index 641362d..b95ade2 100644 (file)
@@ -45,6 +45,8 @@ static long xdl_split(unsigned long const *ha1, long off1, long lim1,
                      long *kvdf, long *kvdb, int need_min, xdpsplit_t *spl,
                      xdalgoenv_t *xenv);
 static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1, long chg2);
+static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo);
+
 
 
 
@@ -395,6 +397,110 @@ static xdchange_t *xdl_add_change(xdchange_t *xscr, long i1, long i2, long chg1,
 }
 
 
+static int xdl_change_compact(xdfile_t *xdf, xdfile_t *xdfo) {
+       long ix, ixo, ixs, ixref, grpsiz, nrec = xdf->nrec;
+       char *rchg = xdf->rchg, *rchgo = xdfo->rchg;
+       xrecord_t **recs = xdf->recs;
+
+       /*
+        * This is the same of what GNU diff does. Move back and forward
+        * change groups for a consistent and pretty diff output. This also
+        * helps in finding joineable change groups and reduce the diff size.
+        */
+       for (ix = ixo = 0;;) {
+               /*
+                * Find the first changed line in the to-be-compacted file.
+                * We need to keep track of both indexes, so if we find a
+                * changed lines group on the other file, while scanning the
+                * to-be-compacted file, we need to skip it properly. Note
+                * that loops that are testing for changed lines on rchg* do
+                * not need index bounding since the array is prepared with
+                * a zero at position -1 and N.
+                */
+               for (; ix < nrec && !rchg[ix]; ix++)
+                       while (rchgo[ixo++]);
+               if (ix == nrec)
+                       break;
+
+               /*
+                * Record the start of a changed-group in the to-be-compacted file
+                * and find the end of it, on both to-be-compacted and other file
+                * indexes (ix and ixo).
+                */
+               ixs = ix;
+               for (ix++; rchg[ix]; ix++);
+               for (; rchgo[ixo]; ixo++);
+
+               do {
+                       grpsiz = ix - ixs;
+
+                       /*
+                        * If the line before the current change group, is equal to
+                        * the last line of the current change group, shift backward
+                        * the group.
+                        */
+                       while (ixs > 0 && recs[ixs - 1]->ha == recs[ix - 1]->ha &&
+                              XDL_RECMATCH(recs[ixs - 1], recs[ix - 1])) {
+                               rchg[--ixs] = 1;
+                               rchg[--ix] = 0;
+
+                               /*
+                                * This change might have joined two change groups,
+                                * so we try to take this scenario in account by moving
+                                * the start index accordingly (and so the other-file
+                                * end-of-group index).
+                                */
+                               for (; rchg[ixs - 1]; ixs--);
+                               while (rchgo[--ixo]);
+                       }
+
+                       /*
+                        * Record the end-of-group position in case we are matched
+                        * with a group of changes in the other file (that is, the
+                        * change record before the enf-of-group index in the other
+                        * file is set).
+                        */
+                       ixref = rchgo[ixo - 1] ? ix: nrec;
+
+                       /*
+                        * If the first line of the current change group, is equal to
+                        * the line next of the current change group, shift forward
+                        * the group.
+                        */
+                       while (ix < nrec && recs[ixs]->ha == recs[ix]->ha &&
+                              XDL_RECMATCH(recs[ixs], recs[ix])) {
+                               rchg[ixs++] = 0;
+                               rchg[ix++] = 1;
+
+                               /*
+                                * This change might have joined two change groups,
+                                * so we try to take this scenario in account by moving
+                                * the start index accordingly (and so the other-file
+                                * end-of-group index). Keep tracking the reference
+                                * index in case we are shifting together with a
+                                * corresponding group of changes in the other file.
+                                */
+                               for (; rchg[ix]; ix++);
+                               while (rchgo[++ixo])
+                                       ixref = ix;
+                       }
+               } while (grpsiz != ix - ixs);
+
+               /*
+                * Try to move back the possibly merged group of changes, to match
+                * the recorded postion in the other file.
+                */
+               while (ixref < ix) {
+                       rchg[--ixs] = 1;
+                       rchg[--ix] = 0;
+                       while (rchgo[--ixo]);
+               }
+       }
+
+       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;
@@ -440,13 +546,13 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
 
                return -1;
        }
-
-       if (xdl_build_script(&xe, &xscr) < 0) {
+       if (xdl_change_compact(&xe.xdf1, &xe.xdf2) < 0 ||
+           xdl_change_compact(&xe.xdf2, &xe.xdf1) < 0 ||
+           xdl_build_script(&xe, &xscr) < 0) {
 
                xdl_free_env(&xe);
                return -1;
        }
-
        if (xscr) {
                if (xdl_emit_diff(&xe, xscr, ecb, xecfg) < 0) {
 
@@ -454,10 +560,8 @@ int xdl_diff(mmfile_t *mf1, mmfile_t *mf2, xpparam_t const *xpp,
                        xdl_free_env(&xe);
                        return -1;
                }
-
                xdl_free_script(xscr);
        }
-
        xdl_free_env(&xe);
 
        return 0;
index 4c2fde8..78f0260 100644 (file)
@@ -33,6 +33,7 @@
 #define XDL_ISDIGIT(c) ((c) >= '0' && (c) <= '9')
 #define XDL_HASHLONG(v, b) (((unsigned long)(v) * GR_PRIME) >> ((CHAR_BIT * sizeof(unsigned long)) - (b)))
 #define XDL_PTRFREE(p) do { if (p) { xdl_free(p); (p) = NULL; } } while (0)
+#define XDL_RECMATCH(r1, r2) ((r1)->size == (r2)->size && memcmp((r1)->ptr, (r2)->ptr, (r1)->size) == 0)
 #define XDL_LE32_PUT(p, v) \
 do { \
        unsigned char *__p = (unsigned char *) (p); \