Skip to content

Commit e09867f

Browse files
peffgitster
authored andcommitted
intersect_paths: respect mode in git's tree-sort
When we do a combined diff, we individually diff against each parent, and then use intersect_paths to do a parallel walk through the sorted results and come up with a final list of interesting paths. The sort order here is that returned by the diffs, which means it is in git's tree-order which sorts sub-trees as if their paths have "/" at the end. When we do our parallel walk, we need to use a comparison function which provides the same order. Since 8518ff8 (combine-diff: optimize combine_diff_path sets intersection, 2014-01-20), we use a simple strcmp to compare the pathnames, and get this wrong. It's somewhat hard to trigger because normally a diff does not produce tree entries at all, and therefore the sort order is the same as a strcmp. However, if the "-t" option is used with the diff, then we will produce diff_filepairs for both trees and files. We can use base_name_compare to do the comparison, just as the tree-diff code does. Even though what we have are not technically base names (they are full paths within the tree), the end result is the same (we do not care about interior slashes at all, only about the final character). However, since we do not have the length of each path stored, we take a slight shortcut: if neither of the entries is a sub-tree then the comparison is equivalent to a strcmp. This lets us skip the extra strlen calls in the common case without having to reimplement base_name_compare from scratch. Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent fce135c commit e09867f

File tree

2 files changed

+45
-1
lines changed

2 files changed

+45
-1
lines changed

combine-diff.c

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,16 @@
1212
#include "sha1-array.h"
1313
#include "revision.h"
1414

15+
static int compare_paths(const struct combine_diff_path *one,
16+
const struct diff_filespec *two)
17+
{
18+
if (!S_ISDIR(one->mode) && !S_ISDIR(two->mode))
19+
return strcmp(one->path, two->path);
20+
21+
return base_name_compare(one->path, strlen(one->path), one->mode,
22+
two->path, strlen(two->path), two->mode);
23+
}
24+
1525
static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr, int n, int num_parent)
1626
{
1727
struct diff_queue_struct *q = &diff_queued_diff;
@@ -52,7 +62,7 @@ static struct combine_diff_path *intersect_paths(struct combine_diff_path *curr,
5262
i = 0;
5363
while ((p = *tail) != NULL) {
5464
cmp = ((i >= q->nr)
55-
? -1 : strcmp(p->path, q->queue[i]->two->path));
65+
? -1 : compare_paths(p, q->queue[i]->two));
5666

5767
if (cmp < 0) {
5868
/* p->path not in q->queue[]; drop it */

t/t4038-diff-combined.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -401,4 +401,38 @@ test_expect_success 'combine diff missing delete bug' '
401401
compare_diff_patch expected actual
402402
'
403403

404+
test_expect_success 'combine diff gets tree sorting right' '
405+
# create a directory and a file that sort differently in trees
406+
# versus byte-wise (implied "/" sorts after ".")
407+
git checkout -f master &&
408+
mkdir foo &&
409+
echo base >foo/one &&
410+
echo base >foo/two &&
411+
echo base >foo.ext &&
412+
git add foo foo.ext &&
413+
git commit -m base &&
414+
415+
# one side modifies a file in the directory, along with the root
416+
# file...
417+
echo master >foo/one &&
418+
echo master >foo.ext &&
419+
git commit -a -m master &&
420+
421+
# the other side modifies the other file in the directory
422+
git checkout -b other HEAD^ &&
423+
echo other >foo/two &&
424+
git commit -a -m other &&
425+
426+
# And now we merge. The files in the subdirectory will resolve cleanly,
427+
# meaning that a combined diff will not find them interesting. But it
428+
# will find the tree itself interesting, because it had to be merged.
429+
git checkout master &&
430+
git merge other &&
431+
432+
printf "MM\tfoo\n" >expect &&
433+
git diff-tree -c --name-status -t HEAD >actual.tmp &&
434+
sed 1d <actual.tmp >actual &&
435+
test_cmp expect actual
436+
'
437+
404438
test_done

0 commit comments

Comments
 (0)