Skip to content

Commit 730f728

Browse files
committed
unpack-trees.c: look ahead in the index
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 e69de29 a/b-2/c/d 100644 blob e69de29 a/b/c/d 100644 blob e69de29 a/x A = HEAD 100644 blob e69de29 a/b-2/c/d 100644 blob e69de29 a/b/c/d 100644 blob 587be6b a/x B = master 120000 blob a36b77384451ea1de7bd340ffca868249626bc52 a/b 100644 blob e69de29 a/b-2/c/d 100644 blob e69de29 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 e69de29 0 a/b-2/c/d 100644 e69de29 1 a/b/c/d 100644 e69de29 2 a/b/c/d 100644 587be6b 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 <[email protected]>
1 parent da165f4 commit 730f728

File tree

5 files changed

+140
-7
lines changed

5 files changed

+140
-7
lines changed

diff-lib.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,7 @@ int run_diff_index(struct rev_info *revs, int cached)
425425
exit(128);
426426

427427
diff_set_mnemonic_prefix(&revs->diffopt, "c/", cached ? "i/" : "w/");
428+
diffcore_fix_diff_index(&revs->diffopt);
428429
diffcore_std(&revs->diffopt);
429430
diff_flush(&revs->diffopt);
430431
return 0;

diff.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3628,6 +3628,23 @@ static void diffcore_skip_stat_unmatch(struct diff_options *diffopt)
36283628
*q = outq;
36293629
}
36303630

3631+
static int diffnamecmp(const void *a_, const void *b_)
3632+
{
3633+
const struct diff_filepair *a = *((const struct diff_filepair **)a_);
3634+
const struct diff_filepair *b = *((const struct diff_filepair **)b_);
3635+
const char *name_a, *name_b;
3636+
3637+
name_a = a->one ? a->one->path : a->two->path;
3638+
name_b = b->one ? b->one->path : b->two->path;
3639+
return strcmp(name_a, name_b);
3640+
}
3641+
3642+
void diffcore_fix_diff_index(struct diff_options *options)
3643+
{
3644+
struct diff_queue_struct *q = &diff_queued_diff;
3645+
qsort(q->queue, q->nr, sizeof(q->queue[0]), diffnamecmp);
3646+
}
3647+
36313648
void diffcore_std(struct diff_options *options)
36323649
{
36333650
if (options->skip_stat_unmatch)

diff.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,7 @@ extern int diff_setup_done(struct diff_options *);
208208
#define DIFF_PICKAXE_REGEX 2
209209

210210
extern void diffcore_std(struct diff_options *);
211+
extern void diffcore_fix_diff_index(struct diff_options *);
211212

212213
#define COMMON_DIFF_OPTIONS_HELP \
213214
"\ncommon diff options:\n" \

t/t1012-read-tree-df.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ test_expect_success setup '
5151
:
5252
'
5353

54-
test_expect_failure '3-way (1)' '
54+
test_expect_success '3-way (1)' '
5555
settree A-000 &&
5656
git read-tree -m -u O-000 A-000 B-000 &&
5757
checkindex <<-EOF
@@ -63,7 +63,7 @@ test_expect_failure '3-way (1)' '
6363
EOF
6464
'
6565

66-
test_expect_failure '3-way (2)' '
66+
test_expect_success '3-way (2)' '
6767
settree A-001 &&
6868
git read-tree -m -u O-000 A-001 B-000 &&
6969
checkindex <<-EOF
@@ -76,7 +76,7 @@ test_expect_failure '3-way (2)' '
7676
EOF
7777
'
7878

79-
test_expect_failure '3-way (3)' '
79+
test_expect_success '3-way (3)' '
8080
settree A-010 &&
8181
git read-tree -m -u O-010 A-010 B-010 &&
8282
checkindex <<-EOF
@@ -90,7 +90,7 @@ test_expect_failure '3-way (3)' '
9090
EOF
9191
'
9292

93-
test_expect_failure '2-way (1)' '
93+
test_expect_success '2-way (1)' '
9494
settree O-020 &&
9595
git read-tree -m -u O-020 A-020 &&
9696
checkindex <<-EOF

unpack-trees.c

Lines changed: 117 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -231,9 +231,37 @@ static int unpack_index_entry(struct cache_entry *ce,
231231
return ret;
232232
}
233233

234+
static int find_cache_pos(struct traverse_info *, const struct name_entry *);
235+
236+
static void restore_cache_bottom(struct traverse_info *info, int bottom)
237+
{
238+
struct unpack_trees_options *o = info->data;
239+
240+
if (o->diff_index_cached)
241+
return;
242+
o->cache_bottom = bottom;
243+
}
244+
245+
static int switch_cache_bottom(struct traverse_info *info)
246+
{
247+
struct unpack_trees_options *o = info->data;
248+
int ret, pos;
249+
250+
if (o->diff_index_cached)
251+
return 0;
252+
ret = o->cache_bottom;
253+
pos = find_cache_pos(info->prev, &info->name);
254+
255+
if (pos < -1)
256+
o->cache_bottom = -2 - pos;
257+
else if (pos < 0)
258+
o->cache_bottom = o->src_index->cache_nr;
259+
return ret;
260+
}
261+
234262
static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long df_conflicts, struct name_entry *names, struct traverse_info *info)
235263
{
236-
int i;
264+
int i, ret, bottom;
237265
struct tree_desc t[MAX_UNPACK_TREES];
238266
struct traverse_info newinfo;
239267
struct name_entry *p;
@@ -254,7 +282,11 @@ static int traverse_trees_recursive(int n, unsigned long dirmask, unsigned long
254282
sha1 = names[i].sha1;
255283
fill_tree_descriptor(t+i, sha1);
256284
}
257-
return traverse_trees(n, t, &newinfo);
285+
286+
bottom = switch_cache_bottom(&newinfo);
287+
ret = traverse_trees(n, t, &newinfo);
288+
restore_cache_bottom(&newinfo, bottom);
289+
return ret;
258290
}
259291

260292
/*
@@ -393,6 +425,82 @@ static int unpack_failed(struct unpack_trees_options *o, const char *message)
393425
return -1;
394426
}
395427

428+
/* NEEDSWORK: give this a better name and share with tree-walk.c */
429+
static int name_compare(const char *a, int a_len,
430+
const char *b, int b_len)
431+
{
432+
int len = (a_len < b_len) ? a_len : b_len;
433+
int cmp = memcmp(a, b, len);
434+
if (cmp)
435+
return cmp;
436+
return (a_len - b_len);
437+
}
438+
439+
/*
440+
* The tree traversal is looking at name p. If we have a matching entry,
441+
* return it. If name p is a directory in the index, do not return
442+
* anything, as we will want to match it when the traversal descends into
443+
* the directory.
444+
*/
445+
static int find_cache_pos(struct traverse_info *info,
446+
const struct name_entry *p)
447+
{
448+
int pos;
449+
struct unpack_trees_options *o = info->data;
450+
struct index_state *index = o->src_index;
451+
int pfxlen = info->pathlen;
452+
int p_len = tree_entry_len(p->path, p->sha1);
453+
454+
for (pos = o->cache_bottom; pos < index->cache_nr; pos++) {
455+
struct cache_entry *ce = index->cache[pos];
456+
const char *ce_name, *ce_slash;
457+
int cmp, ce_len;
458+
459+
if (!ce_in_traverse_path(ce, info))
460+
continue;
461+
if (ce->ce_flags & CE_UNPACKED)
462+
continue;
463+
ce_name = ce->name + pfxlen;
464+
ce_slash = strchr(ce_name, '/');
465+
if (ce_slash)
466+
ce_len = ce_slash - ce_name;
467+
else
468+
ce_len = ce_namelen(ce) - pfxlen;
469+
cmp = name_compare(p->path, p_len, ce_name, ce_len);
470+
/*
471+
* Exact match; if we have a directory we need to
472+
* delay returning it.
473+
*/
474+
if (!cmp)
475+
return ce_slash ? -2 - pos : pos;
476+
if (0 < cmp)
477+
continue; /* keep looking */
478+
/*
479+
* ce_name sorts after p->path; could it be that we
480+
* have files under p->path directory in the index?
481+
* E.g. ce_name == "t-i", and p->path == "t"; we may
482+
* have "t/a" in the index.
483+
*/
484+
if (p_len < ce_len && !memcmp(ce_name, p->path, p_len) &&
485+
ce_name[p_len] < '/')
486+
continue; /* keep looking */
487+
break;
488+
}
489+
return -1;
490+
}
491+
492+
static struct cache_entry *find_cache_entry(struct traverse_info *info,
493+
const struct name_entry *p)
494+
{
495+
int pos = find_cache_pos(info, p);
496+
struct unpack_trees_options *o = info->data;
497+
498+
if (0 <= pos)
499+
return o->src_index->cache[pos];
500+
else
501+
return NULL;
502+
}
503+
396504
static int unpack_callback(int n, unsigned long mask, unsigned long dirmask, struct name_entry *names, struct traverse_info *info)
397505
{
398506
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
406514
/* Are we supposed to look at the index too? */
407515
if (o->merge) {
408516
while (1) {
409-
struct cache_entry *ce = next_cache_entry(o);
410517
int cmp;
518+
struct cache_entry *ce;
519+
520+
if (o->diff_index_cached)
521+
ce = next_cache_entry(o);
522+
else
523+
ce = find_cache_entry(info, p);
524+
411525
if (!ce)
412526
break;
413527
cmp = compare_entry(ce, info, p);

0 commit comments

Comments
 (0)