unpack-trees.c: look ahead in the index
authorJunio C Hamano <gitster@pobox.com>
Sun, 20 Sep 2009 07:03:39 +0000 (00:03 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 7 Jan 2010 23:00:14 +0000 (15:00 -0800)
This makes the traversal of index be in sync with the tree traversal.
When unpack_callback() is fed a set of tree entries from trees, it
inspects the name of the entry and checks if the an index entry with
the same name could be hiding behind the current index entry, and

(1) if the name appears in the index as a leaf node, it is also
fed to the n_way_merge() callback function;

(2) if the name is a directory in the index, i.e. there are entries in
that are underneath it, then nothing is fed to the n_way_merge()
callback function;

(3) otherwise, if the name comes before the first eligible entry in the
index, the index entry is first unpacked alone.

When traverse_trees_recursive() descends into a subdirectory, the
cache_bottom pointer is moved to walk index entries within that directory.

All of these are omitted for diff-index, which does not even want to be
fed an index entry and a tree entry with D/F conflicts.

This fixes 3-way read-tree and exposes a bug in other parts of the system
in t6035, test #5. The test prepares these three trees:

O = HEAD^
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b/c/d
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/x

A = HEAD
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b/c/d
100644 blob 587be6b4c3f93f93c489c0111bba5596147a26cb a/x

B = master
120000 blob a36b77384451ea1de7bd340ffca868249626bc52 a/b
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/b-2/c/d
100644 blob e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 a/x

With a clean index that matches HEAD, running

git read-tree -m -u --aggressive $O $A $B

now yields

120000 a36b77384451ea1de7bd340ffca868249626bc52 3 a/b
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 0 a/b-2/c/d
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 1 a/b/c/d
100644 e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 2 a/b/c/d
100644 587be6b4c3f93f93c489c0111bba5596147a26cb 0 a/x

which is correct. "master" created "a/b" symlink that did not exist,
and removed "a/b/c/d" while HEAD did not do touch either path.

Before this series, read-tree did not notice the situation and resolved
addition of "a/b" and removal of "a/b/c/d" independently. If A = HEAD had
another path "a/b/c/e" added, this merge should conflict but instead it
silently resolved "a/b" and then immediately overwrote it to add
"a/b/c/e", which was quite bogus.

Tests in t1012 start to work with this.

Signed-off-by: Junio C Hamano <gitster@pobox.com>
diff-lib.c
diff.c
diff.h
t/t1012-read-tree-df.sh
unpack-trees.c
index f759917d33ea174800b57e34c5f192ad43977f49..c9998f4c91087271c36703b6dbe8a67d872624c5 100644 (file)
@@ -425,6 +425,7 @@ int run_diff_index(struct rev_info *revs, int cached)
                exit(128);
 
        diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
+       diffcore_fix_diff_index(&revs->diffopt);
        diffcore_std(&revs->diffopt);
        diff_flush(&revs->diffopt);
        return 0;
diff --git a/diff.c b/diff.c
index 08bbd3e9070996b38f4d34cedf7640d93aa5808d..3bfb4a19d2cc758f5937199cc37b05307c87d85d 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -3628,6 +3628,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
        *q = outq;
 }
 
+static int diffnamecmp(const void *a_, const void *b_)
+{
+       const struct diff_filepair *a = *((const struct diff_filepair **)a_);
+       const struct diff_filepair *b = *((const struct diff_filepair **)b_);
+       const char *name_a, *name_b;
+
+       name_a = a->one ? a->one->path : a->two->path;
+       name_b = b->one ? b->one->path : b->two->path;
+       return strcmp(name_a, name_b);
+}
+
+void diffcore_fix_diff_index(struct diff_options *options)
+{
+       struct diff_queue_struct *q = &diff_queued_diff;
+       qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
+}
+
 void diffcore_std(struct diff_options *options)
 {
        if (options->skip_stat_unmatch)
diff --git a/diff.h b/diff.h
index 15fcecdecd9b033700902de44138eafdb66932eb..471f606a926242ab48957746dcc33f7ff0a41418 100644 (file)
--- a/diff.h
+++ b/diff.h
@@ -208,6 +208,7 @@ extern int diff_setup_done(struct diff_options *);
 #define DIFF_PICKAXE_REGEX     2
 
 extern void diffcore_std(struct diff_options *);
+extern void diffcore_fix_diff_index(struct diff_options *);
 
 #define COMMON_DIFF_OPTIONS_HELP \
 "\ncommon diff options:\n" \
index f1e650ac39c59dfed692a8c373ed9ffef83f45f6..9811d467da5a54eefd68ecfad4c68b67e5b3734f 100755 (executable)
@@ -51,7 +51,7 @@ test_expect_success setup '
        :
 '
 
-test_expect_failure '3-way (1)' '
+test_expect_success '3-way (1)' '
        settree A-000 &&
        git read-tree -m -u O-000 A-000 B-000 &&
        checkindex <<-EOF
@@ -63,7 +63,7 @@ test_expect_failure '3-way (1)' '
        EOF
 '
 
-test_expect_failure '3-way (2)' '
+test_expect_success '3-way (2)' '
        settree A-001 &&
        git read-tree -m -u O-000 A-001 B-000 &&
        checkindex <<-EOF
@@ -76,7 +76,7 @@ test_expect_failure '3-way (2)' '
        EOF
 '
 
-test_expect_failure '3-way (3)' '
+test_expect_success '3-way (3)' '
        settree A-010 &&
        git read-tree -m -u O-010 A-010 B-010 &&
        checkindex <<-EOF
@@ -90,7 +90,7 @@ test_expect_failure '3-way (3)' '
        EOF
 '
 
-test_expect_failure '2-way (1)' '
+test_expect_success '2-way (1)' '
        settree O-020 &&
        git read-tree -m -u O-020 A-020 &&
        checkindex <<-EOF
index 685adb4b7723a510fc90f09c1a65a1382bff8c74..74cabc36ff09255819558c38b25a38a4e6871418 100644 (file)
@@ -231,9 +231,37 @@ static int unpack_index_entry(struct cache_entry *ce,
        return ret;
 }
 
+static int find_cache_pos(struct traverse_info *, const struct name_entry *);
+
+static void restore_cache_bottom(struct traverse_info *info, int bottom)
+{
+       struct unpack_trees_options *o = info->data;
+
+       if (o->diff_index_cached)
+               return;
+       o->cache_bottom = bottom;
+}
+
+static int switch_cache_bottom(struct traverse_info *info)
+{
+       struct unpack_trees_options *o = info->data;
+       int ret, pos;
+
+       if (o->diff_index_cached)
+               return 0;
+       ret = o->cache_bottom;
+       pos = find_cache_pos(info->prev, &info->name);
+
+       if (pos < -1)
+               o->cache_bottom = -2 - pos;
+       else if (pos < 0)
+               o->cache_bottom = o->src_index->cache_nr;
+       return ret;
+}
+
 static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
 {
-       int i;
+       int i, ret, bottom;
        struct tree_desc t[MAX_UNPACK_TREES];
        struct traverse_info newinfo;
        struct name_entry *p;
@@ -254,7 +282,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long
                        sha1 = names[i].sha1;
                fill_tree_descriptor(t+i, sha1);
        }
-       return traverse_trees(n, t, &newinfo);
+
+       bottom = switch_cache_bottom(&newinfo);
+       ret = traverse_trees(n, t, &newinfo);
+       restore_cache_bottom(&newinfo, bottom);
+       return ret;
 }
 
 /*
@@ -393,6 +425,82 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message)
        return -1;
 }
 
+/* NEEDSWORK: give this a better name and share with tree-walk.c */
+static int name_compare(const char *a, int a_len,
+                       const char *b, int b_len)
+{
+       int len = (a_len < b_len) ? a_len : b_len;
+       int cmp = memcmp(a, b, len);
+       if (cmp)
+               return cmp;
+       return (a_len - b_len);
+}
+
+/*
+ * The tree traversal is looking at name p.  If we have a matching entry,
+ * return it.  If name p is a directory in the index, do not return
+ * anything, as we will want to match it when the traversal descends into
+ * the directory.
+ */
+static int find_cache_pos(struct traverse_info *info,
+                         const struct name_entry *p)
+{
+       int pos;
+       struct unpack_trees_options *o = info->data;
+       struct index_state *index = o->src_index;
+       int pfxlen = info->pathlen;
+       int p_len = tree_entry_len(p->path, p->sha1);
+
+       for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
+               struct cache_entry *ce = index->cache[pos];
+               const char *ce_name, *ce_slash;
+               int cmp, ce_len;
+
+               if (!ce_in_traverse_path(ce, info))
+                       continue;
+               if (ce->ce_flags & CE_UNPACKED)
+                       continue;
+               ce_name = ce->name + pfxlen;
+               ce_slash = strchr(ce_name, '/');
+               if (ce_slash)
+                       ce_len = ce_slash - ce_name;
+               else
+                       ce_len = ce_namelen(ce) - pfxlen;
+               cmp = name_compare(p->path, p_len, ce_name, ce_len);
+               /*
+                * Exact match; if we have a directory we need to
+                * delay returning it.
+                */
+               if (!cmp)
+                       return ce_slash ? -2 - pos : pos;
+               if (0 < cmp)
+                       continue; /* keep looking */
+               /*
+                * ce_name sorts after p->path; could it be that we
+                * have files under p->path directory in the index?
+                * E.g.  ce_name == "t-i", and p->path == "t"; we may
+                * have "t/a" in the index.
+                */
+               if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) &&
+                   ce_name[p_len] < '/')
+                       continue; /* keep looking */
+               break;
+       }
+       return -1;
+}
+
+static struct cache_entry *find_cache_entry(struct traverse_info *info,
+                                           const struct name_entry *p)
+{
+       int pos = find_cache_pos(info, p);
+       struct unpack_trees_options *o = info->data;
+
+       if (0 <= pos)
+               return o->src_index->cache[pos];
+       else
+               return NULL;
+}
+
 static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
 {
        struct cache_entry *src[MAX_UNPACK_TREES + 1] = { NULL, };
@@ -406,8 +514,14 @@ static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, str
        /* Are we supposed to look at the index too? */
        if (o->merge) {
                while (1) {
-                       struct cache_entry *ce = next_cache_entry(o);
                        int cmp;
+                       struct cache_entry *ce;
+
+                       if (o->diff_index_cached)
+                               ce = next_cache_entry(o);
+                       else
+                               ce = find_cache_entry(info, p);
+
                        if (!ce)
                                break;
                        cmp = compare_entry(ce, info, p);