Skip to content

Commit b73dec5

Browse files
derrickstoleegitster
authored andcommitted
for-each-ref: add --stdin option
When a user wishes to input a large list of patterns to 'git for-each-ref' (likely a long list of exact refs) there are frequently system limits on the number of command-line arguments. Add a new --stdin option to instead read the patterns from standard input. Add tests that check that any unrecognized arguments are considered an error when --stdin is provided. Also, an empty pattern list is interpreted as the complete ref set. When reading from stdin, we populate the filter.name_patterns array dynamically as opposed to pointing to the 'argv' array directly. This is simple when using a strvec, as it is NULL-terminated in the same way. We then free the memory directly from the strvec. Helped-by: Phillip Wood <[email protected]> Signed-off-by: Derrick Stolee <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent d15644f commit b73dec5

File tree

3 files changed

+65
-2
lines changed

3 files changed

+65
-2
lines changed

Documentation/git-for-each-ref.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@ SYNOPSIS
99
--------
1010
[verse]
1111
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
12-
[(--sort=<key>)...] [--format=<format>] [<pattern>...]
12+
[(--sort=<key>)...] [--format=<format>]
13+
[ --stdin | <pattern>... ]
1314
[--points-at=<object>]
1415
[--merged[=<object>]] [--no-merged[=<object>]]
1516
[--contains[=<object>]] [--no-contains[=<object>]]
@@ -32,6 +33,10 @@ OPTIONS
3233
literally, in the latter case matching completely or from the
3334
beginning up to a slash.
3435

36+
--stdin::
37+
If `--stdin` is supplied, then the list of patterns is read from
38+
standard input instead of from the argument list.
39+
3540
--count=<count>::
3641
By default the command shows all refs that match
3742
`<pattern>`. This option makes it stop after showing

builtin/for-each-ref.c

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "object.h"
66
#include "parse-options.h"
77
#include "ref-filter.h"
8+
#include "strvec.h"
89

910
static char const * const for_each_ref_usage[] = {
1011
N_("git for-each-ref [<options>] [<pattern>]"),
@@ -25,6 +26,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
2526
struct ref_format format = REF_FORMAT_INIT;
2627
struct strbuf output = STRBUF_INIT;
2728
struct strbuf err = STRBUF_INIT;
29+
int from_stdin = 0;
30+
struct strvec vec = STRVEC_INIT;
2831

2932
struct option opts[] = {
3033
OPT_BIT('s', "shell", &format.quote_style,
@@ -49,6 +52,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
4952
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
5053
OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
5154
OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
55+
OPT_BOOL(0, "stdin", &from_stdin, N_("read reference patterns from stdin")),
5256
OPT_END(),
5357
};
5458

@@ -75,7 +79,23 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
7579
ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
7680
filter.ignore_case = icase;
7781

78-
filter.name_patterns = argv;
82+
if (from_stdin) {
83+
struct strbuf line = STRBUF_INIT;
84+
85+
if (argv[0])
86+
die(_("unknown arguments supplied with --stdin"));
87+
88+
while (strbuf_getline(&line, stdin) != EOF)
89+
strvec_push(&vec, line.buf);
90+
91+
strbuf_release(&line);
92+
93+
/* vec.v is NULL-terminated, just like 'argv'. */
94+
filter.name_patterns = vec.v;
95+
} else {
96+
filter.name_patterns = argv;
97+
}
98+
7999
filter.match_as_path = 1;
80100
filter_refs(&array, &filter, FILTER_REFS_ALL);
81101
ref_array_sort(sorting, &array);
@@ -97,5 +117,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
97117
free_commit_list(filter.with_commit);
98118
free_commit_list(filter.no_commit);
99119
ref_sorting_release(sorting);
120+
strvec_clear(&vec);
100121
return 0;
101122
}

t/t6300-for-each-ref.sh

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1464,4 +1464,41 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
14641464
sig_crlf=${sig_crlf%dummy}
14651465
test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
14661466

1467+
test_expect_success 'git for-each-ref --stdin: empty' '
1468+
>in &&
1469+
git for-each-ref --format="%(refname)" --stdin <in >actual &&
1470+
git for-each-ref --format="%(refname)" >expect &&
1471+
test_cmp expect actual
1472+
'
1473+
1474+
test_expect_success 'git for-each-ref --stdin: fails if extra args' '
1475+
>in &&
1476+
test_must_fail git for-each-ref --format="%(refname)" \
1477+
--stdin refs/heads/extra <in 2>err &&
1478+
grep "unknown arguments supplied with --stdin" err
1479+
'
1480+
1481+
test_expect_success 'git for-each-ref --stdin: matches' '
1482+
cat >in <<-EOF &&
1483+
refs/tags/multi*
1484+
refs/heads/amb*
1485+
EOF
1486+
1487+
cat >expect <<-EOF &&
1488+
refs/heads/ambiguous
1489+
refs/tags/multi-ref1-100000-user1
1490+
refs/tags/multi-ref1-100000-user2
1491+
refs/tags/multi-ref1-200000-user1
1492+
refs/tags/multi-ref1-200000-user2
1493+
refs/tags/multi-ref2-100000-user1
1494+
refs/tags/multi-ref2-100000-user2
1495+
refs/tags/multi-ref2-200000-user1
1496+
refs/tags/multi-ref2-200000-user2
1497+
refs/tags/multiline
1498+
EOF
1499+
1500+
git for-each-ref --format="%(refname)" --stdin <in >actual &&
1501+
test_cmp expect actual
1502+
'
1503+
14671504
test_done

0 commit comments

Comments
 (0)