Skip to content

Commit 7c5045f

Browse files
peffgitster
authored andcommitted
ref-filter: apply fallback refname sort only after all user sorts
Commit 9e46833 (ref-filter: fallback on alphabetical comparison, 2015-10-30) taught ref-filter's sort to fallback to comparing refnames. But it did it at the wrong level, overriding the comparison result for a single "--sort" key from the user, rather than after all sort keys have been exhausted. This worked correctly for a single "--sort" option, but not for multiple ones. We'd break any ties in the first key with the refname and never evaluate the second key at all. To make matters even more interesting, we only applied this fallback sometimes! For a field like "taggeremail" which requires a string comparison, we'd truly return the result of strcmp(), even if it was 0. But for numerical "value" fields like "taggerdate", we did apply the fallback. And that's why our multiple-sort test missed this: it uses taggeremail as the main comparison. So let's start by adding a much more rigorous test. We'll have a set of commits expressing every combination of two tagger emails, dates, and refnames. Then we can confirm that our sort is applied with the correct precedence, and we'll be hitting both the string and value comparators. That does show the bug, and the fix is simple: moving the fallback to the outer compare_refs() function, after all ref_sorting keys have been exhausted. Note that in the outer function we don't have an "ignore_case" flag, as it's part of each individual ref_sorting element. It's debatable what such a fallback should do, since we didn't use the user's keys to match. But until now we have been trying to respect that flag, so the least-invasive thing is to try to continue to do so. Since all callers in the current code either set the flag for all keys or for none, we can just pull the flag from the first key. In a hypothetical world where the user really can flip the case-insensitivity of keys separately, we may want to extend the code to distinguish that case from a blanket "--ignore-case". Signed-off-by: Jeff King <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 76f9e56 commit 7c5045f

File tree

2 files changed

+53
-8
lines changed

2 files changed

+53
-8
lines changed

ref-filter.c

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2141,7 +2141,7 @@ static int cmp_ref_sorting(struct ref_sorting *s, struct ref_array_item *a, stru
21412141
if (va->value < vb->value)
21422142
cmp = -1;
21432143
else if (va->value == vb->value)
2144-
cmp = cmp_fn(a->refname, b->refname);
2144+
cmp = 0;
21452145
else
21462146
cmp = 1;
21472147
}
@@ -2160,7 +2160,10 @@ static int compare_refs(const void *a_, const void *b_, void *ref_sorting)
21602160
if (cmp)
21612161
return cmp;
21622162
}
2163-
return 0;
2163+
s = ref_sorting;
2164+
return s && s->ignore_case ?
2165+
strcasecmp(a->refname, b->refname) :
2166+
strcmp(a->refname, b->refname);
21642167
}
21652168

21662169
void ref_sorting_icase_all(struct ref_sorting *sorting, int flag)

t/t6300-for-each-ref.sh

Lines changed: 48 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -583,17 +583,59 @@ test_atom refs/tags/signed-long contents "subject line
583583
body contents
584584
$sig"
585585

586-
cat >expected <<EOF
587-
$(git rev-parse refs/tags/bogo) <[email protected]> refs/tags/bogo
588-
$(git rev-parse refs/tags/master) <[email protected]> refs/tags/master
589-
EOF
586+
test_expect_success 'set up multiple-sort tags' '
587+
for when in 100000 200000
588+
do
589+
for email in user1 user2
590+
do
591+
for ref in ref1 ref2
592+
do
593+
GIT_COMMITTER_DATE="@$when +0000" \
594+
GIT_COMMITTER_EMAIL="[email protected]" \
595+
git tag -m "tag $ref-$when-$email" \
596+
multi-$ref-$when-$email || return 1
597+
done
598+
done
599+
done
600+
'
590601

591602
test_expect_success 'Verify sort with multiple keys' '
592-
git for-each-ref --format="%(objectname) %(taggeremail) %(refname)" --sort=objectname --sort=taggeremail \
593-
refs/tags/bogo refs/tags/master > actual &&
603+
cat >expected <<-\EOF &&
604+
100000 <[email protected]> refs/tags/multi-ref2-100000-user1
605+
100000 <[email protected]> refs/tags/multi-ref1-100000-user1
606+
100000 <[email protected]> refs/tags/multi-ref2-100000-user2
607+
100000 <[email protected]> refs/tags/multi-ref1-100000-user2
608+
200000 <[email protected]> refs/tags/multi-ref2-200000-user1
609+
200000 <[email protected]> refs/tags/multi-ref1-200000-user1
610+
200000 <[email protected]> refs/tags/multi-ref2-200000-user2
611+
200000 <[email protected]> refs/tags/multi-ref1-200000-user2
612+
EOF
613+
git for-each-ref \
614+
--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
615+
--sort=-refname \
616+
--sort=taggeremail \
617+
--sort=taggerdate \
618+
"refs/tags/multi-*" >actual &&
594619
test_cmp expected actual
595620
'
596621

622+
test_expect_success 'equivalent sorts fall back on refname' '
623+
cat >expected <<-\EOF &&
624+
100000 <[email protected]> refs/tags/multi-ref1-100000-user1
625+
100000 <[email protected]> refs/tags/multi-ref1-100000-user2
626+
100000 <[email protected]> refs/tags/multi-ref2-100000-user1
627+
100000 <[email protected]> refs/tags/multi-ref2-100000-user2
628+
200000 <[email protected]> refs/tags/multi-ref1-200000-user1
629+
200000 <[email protected]> refs/tags/multi-ref1-200000-user2
630+
200000 <[email protected]> refs/tags/multi-ref2-200000-user1
631+
200000 <[email protected]> refs/tags/multi-ref2-200000-user2
632+
EOF
633+
git for-each-ref \
634+
--format="%(taggerdate:unix) %(taggeremail) %(refname)" \
635+
--sort=taggerdate \
636+
"refs/tags/multi-*" >actual &&
637+
test_cmp expected actual
638+
'
597639

598640
test_expect_success 'do not dereference NULL upon %(HEAD) on unborn branch' '
599641
test_when_finished "git checkout master" &&

0 commit comments

Comments
 (0)