describe: fix --exclude, --match with --contains and --all

git describe --contains acts as a wrapper around git name-rev. When operating with --contains and --all, the --match and --exclude patterns are not properly forwarded to name-rev as --exclude and --refs options. This results in the command silently discarding match and exclude requests from the user when operating in --all mode. We could check and die() if the user provides --contains, --all, and --match/--exclude. However, its also straight forward to just pass the filters down to git name-rev. Notice that the documentation for --match and --exclude mention the --all mode. It explains that they operate on refs with the prefix refs/tags, and additionally refs/heads and refs/remotes when using --all. Fix the describe logic to pass the patterns down with the appropriate prefixes when --all is provided. This fixes the support to match the documented behavior. Add tests to check that this works as expected. Reported-by: Tuomas Ahola <taahol@utu.fi> Signed-off-by: Jacob Keller <jacob.keller@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Jacob Keller committed Jun 1, 2026 at 16:36 UTC 1891707d1b8bb0ac3c47343e881fcf28ec69457a
2 files changed +37 -3
builtin/describe.c
+15 -3
@@ -712,13 +712,25 @@ int cmd_describe(int argc,
712 NULL);
713 if (always)
714 strvec_push(&args, "--always");
715 - if (!all) {
715 + if (!all)
716 strvec_push(&args, "--tags");
717 +
718 + for_each_string_list_item(item, &patterns)
719 + strvec_pushf(&args, "--refs=refs/tags/%s", item->string);
720 + for_each_string_list_item(item, &exclude_patterns)
721 + strvec_pushf(&args, "--exclude=refs/tags/%s", item->string);
722 +
723 + if (all) {
724 for_each_string_list_item(item, &patterns)
718 - strvec_pushf(&args, "--refs=refs/tags/%s", item->string);
725 + strvec_pushf(&args, "--refs=refs/heads/%s", item->string);
726 for_each_string_list_item(item, &exclude_patterns)
720 - strvec_pushf(&args, "--exclude=refs/tags/%s", item->string);
727 + strvec_pushf(&args, "--exclude=refs/heads/%s", item->string);
728 + for_each_string_list_item(item, &patterns)
729 + strvec_pushf(&args, "--refs=refs/remotes/%s", item->string);
730 + for_each_string_list_item(item, &exclude_patterns)
731 + strvec_pushf(&args, "--exclude=refs/remotes/%s", item->string);
732 }
733 +
734 if (argc)
735 strvec_pushv(&args, argv);
736 else
t/t6120-describe.sh
+22
@@ -345,6 +345,28 @@ test_expect_success 'describe --contains and --no-match' '
345 test_cmp expect actual
346 '
347
348 +test_expect_success 'describe --contains --all --match no matching commit' '
349 + echo "tags/A^0" >expect &&
350 + tagged_commit=$(git rev-parse "refs/tags/A^0") &&
351 + test_must_fail git describe --contains --all --match="B" $tagged_commit
352 +'
353 +
354 +check_describe "tags/A^0" --contains --all --match="A" $(git rev-parse "refs/tags/A^0")
355 +
356 +check_describe "branch_A" --contains --all --match="branch*" $(git rev-parse "refs/tags/A^0")
357 +
358 +check_describe "branch_C~1" --contains --all --match="branch*" --exclude="branch_A" $(git rev-parse "refs/tags/A^0")
359 +
360 +check_describe "branch_A" --contains --all \
361 + --exclude="A" --exclude="c" --exclude="test*" --exclude="origin/remote_branch_A" \
362 + $(git rev-parse "refs/tags/A^0")
363 +
364 +check_describe "remotes/origin/remote_branch_A" --contains --all --match="origin/remote*" $(git rev-parse "refs/tags/A^0")
365 +
366 +check_describe "remotes/origin/remote_branch_C~1" --contains --all \
367 + --match="origin/remote*" --exclude="origin/remote_branch_A" \
368 + $(git rev-parse "refs/tags/A^0")
369 +
370 test_expect_success 'setup and absorb a submodule' '
371 test_create_repo sub1 &&
372 test_commit -C sub1 initial &&