[PATCH 2/2] builtin/show-ref.c: limit output with `--count`
`git show-ref` is a useful tool for answering existential questions about whether or not certain (kinds of) references exist or not. For example, to quickly determine whether or not a repository has any tags, one could run:
$ git show-ref -q --tags | head -1
and check whether there was any output.
But certain environments (like spawning Git processes from Ruby code) do not have ergonomic ways to simulate sending SIGPIPE back to `show-ref` when they have seen enough output.
Callers _could_ use `for-each-ref`, which has supports a `--count` option to limit output, but this is sub-par for latency-sensitive callers since `for-each-ref` buffers all results before printing anything. In the above instance, even something like:
$ git for-each-ref --count=1 -- 'refs/tags/*'
will enumerate every tag in a repository before deciding to print just the first one. (The current behavior exists to accommodate sorting the output from for-each-ref, and could be improved. See [1] for previous work in this area).
In the meantime, introduce support for a similar `--count` option in `show-ref`, so that callers can run:
$ git show-ref -q --tags --count=1
to quickly determine whether a repository has any (e.g.) tags or not by only enumerating at most one reference.
(This option is currently "incompatible" with `--verify`, though there is no reason why the meaning of `--count` couldn't be extended to mean, "with `--verify`, only verify `<n>` references".)
[1]: https://lore.kernel.org/git/YTNpQ7Od1U%2F5i0R7@coredump.intra.peff.net/
Signed-off-by: Taylor Blau <me@ttaylorr.com>
---
Documentation/git-show-ref.txt | 7 ++++++-
builtin/show-ref.c | 23 ++++++++++++++++++++++-
t/t1403-show-ref.sh | 21 +++++++++++++++++++++
3 files changed, 49 insertions(+), 2 deletions(-)
Show changes to 3 files +49 −2
Documentation/git-show-ref.txt, builtin/show-ref.c, t/t1403-show-ref.sh
diff --git a/Documentation/git-show-ref.txt b/Documentation/git-show-ref.txt
index ab4d271925..28256c04dd 100644
--- a/Documentation/git-show-ref.txt
+++ b/Documentation/git-show-ref.txt
@@ -10,7 +10,7 @@ SYNOPSIS
[verse]
'git show-ref' [-q|--quiet] [--verify] [--head] [-d|--dereference]
[-s|--hash[=<n>]] [--abbrev[=<n>]] [--tags]
- [--heads] [--] [<pattern>...]
+ [--heads] [--count=<n>] [--] [<pattern>...]
'git show-ref' --exclude-existing[=<pattern>]
DESCRIPTION
@@ -84,6 +84,11 @@ OPTIONS
(4) ignore if refname is a ref that exists in the local repository;
(5) otherwise output the line.
+--count::
+
+ Do not print more than `<n>` matching references, or print all
+ references if `<n>` is 0. Incompatible with `--exclude-existing`
+ and `--verify`.
<pattern>...::
diff --git a/builtin/show-ref.c b/builtin/show-ref.c
index 17d5f98fe4..f0af8e8eec 100644
--- a/builtin/show-ref.c
+++ b/builtin/show-ref.c
@@ -15,7 +15,7 @@ static const char * const show_ref_usage[] = {
};
static int deref_tags, show_head, tags_only, heads_only, matches_nr, verify,
- quiet, hash_only, abbrev, exclude_arg;
+ quiet, hash_only, abbrev, exclude_arg, max_count = 0;
static const char **pattern;
static const char *exclude_existing_arg;
@@ -82,6 +82,8 @@ static int show_ref(const char *refname, const struct object_id *oid,
show_one(refname, oid);
+ if (max_count && matches_nr >= max_count)
+ return -1; /* avoid opening any more refs */
return 0;
}
@@ -178,6 +180,7 @@ static const struct option show_ref_options[] = {
OPT_CALLBACK_F(0, "exclude-existing", &exclude_existing_arg,
N_("pattern"), N_("show refs from stdin that aren't in local repository"),
PARSE_OPT_OPTARG | PARSE_OPT_NONEG, exclude_existing_callback),
+ OPT_INTEGER(0, "count", &max_count, N_("show only <n> matched refs")),
OPT_END()
};
@@ -188,6 +191,24 @@ int cmd_show_ref(int argc, const char **argv, const char *prefix)
argc = parse_options(argc, argv, prefix, show_ref_options,
show_ref_usage, 0);
+ if (max_count) {
+ int compatible = 0;
+
+ if (max_count < 0)
+ error(_("invalid --count argument: (`%d' < 0)"),
+ max_count);
+ else if (verify)
+ error(_("--count is incompatible with %s"), "--verify");
+ else if (exclude_arg)
+ error(_("--count is incompatible with %s"),
+ "--exclude-existing");
+ else
+ compatible = 1;
+
+ if (!compatible)
+ usage_with_options(show_ref_usage, show_ref_options);
+ }
+
if (exclude_arg)
return exclude_existing(exclude_existing_arg);
diff --git a/t/t1403-show-ref.sh b/t/t1403-show-ref.sh
index 9252a581ab..b79e114c1e 100755
--- a/t/t1403-show-ref.sh
+++ b/t/t1403-show-ref.sh
@@ -196,4 +196,25 @@ test_expect_success 'show-ref --verify with dangling ref' '
)
'
+test_expect_success 'show-ref --count limits relevant output' '
+ git show-ref --heads --count=1 >out &&
+ test_line_count = 1 out
+'
+
+test_expect_success 'show-ref --count rejects invalid input' '
+ test_must_fail git show-ref --count=-1 2>err &&
+ grep "invalid ..count argument: (.-1. < 0)" err
+'
+
+test_expect_success 'show-ref --count incompatible with --verify' '
+ test_must_fail git show-ref --count=1 --verify HEAD 2>err &&
+ grep "..count is incompatible with ..verify" err
+'
+
+test_expect_success 'show-ref --count incompatible with --exclude-existing' '
+ echo "refs/heads/main" >in &&
+ test_must_fail git show-ref --count=1 --exclude-existing <in 2>err &&
+ grep "..count is incompatible with ..exclude.existing" err
+'
+
test_done
--
2.36.1.94.gb0d54bedca