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 <phillip.wood123@gmail.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
d15644fe02
commit
b73dec5530
@ -9,7 +9,8 @@ SYNOPSIS
|
|||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
|
'git for-each-ref' [--count=<count>] [--shell|--perl|--python|--tcl]
|
||||||
[(--sort=<key>)...] [--format=<format>] [<pattern>...]
|
[(--sort=<key>)...] [--format=<format>]
|
||||||
|
[ --stdin | <pattern>... ]
|
||||||
[--points-at=<object>]
|
[--points-at=<object>]
|
||||||
[--merged[=<object>]] [--no-merged[=<object>]]
|
[--merged[=<object>]] [--no-merged[=<object>]]
|
||||||
[--contains[=<object>]] [--no-contains[=<object>]]
|
[--contains[=<object>]] [--no-contains[=<object>]]
|
||||||
@ -32,6 +33,10 @@ OPTIONS
|
|||||||
literally, in the latter case matching completely or from the
|
literally, in the latter case matching completely or from the
|
||||||
beginning up to a slash.
|
beginning up to a slash.
|
||||||
|
|
||||||
|
--stdin::
|
||||||
|
If `--stdin` is supplied, then the list of patterns is read from
|
||||||
|
standard input instead of from the argument list.
|
||||||
|
|
||||||
--count=<count>::
|
--count=<count>::
|
||||||
By default the command shows all refs that match
|
By default the command shows all refs that match
|
||||||
`<pattern>`. This option makes it stop after showing
|
`<pattern>`. This option makes it stop after showing
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "object.h"
|
#include "object.h"
|
||||||
#include "parse-options.h"
|
#include "parse-options.h"
|
||||||
#include "ref-filter.h"
|
#include "ref-filter.h"
|
||||||
|
#include "strvec.h"
|
||||||
|
|
||||||
static char const * const for_each_ref_usage[] = {
|
static char const * const for_each_ref_usage[] = {
|
||||||
N_("git for-each-ref [<options>] [<pattern>]"),
|
N_("git for-each-ref [<options>] [<pattern>]"),
|
||||||
@ -25,6 +26,8 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
struct ref_format format = REF_FORMAT_INIT;
|
struct ref_format format = REF_FORMAT_INIT;
|
||||||
struct strbuf output = STRBUF_INIT;
|
struct strbuf output = STRBUF_INIT;
|
||||||
struct strbuf err = STRBUF_INIT;
|
struct strbuf err = STRBUF_INIT;
|
||||||
|
int from_stdin = 0;
|
||||||
|
struct strvec vec = STRVEC_INIT;
|
||||||
|
|
||||||
struct option opts[] = {
|
struct option opts[] = {
|
||||||
OPT_BIT('s', "shell", &format.quote_style,
|
OPT_BIT('s', "shell", &format.quote_style,
|
||||||
@ -49,6 +52,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
|
OPT_CONTAINS(&filter.with_commit, N_("print only refs which contain the commit")),
|
||||||
OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
|
OPT_NO_CONTAINS(&filter.no_commit, N_("print only refs which don't contain the commit")),
|
||||||
OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
|
OPT_BOOL(0, "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
|
||||||
|
OPT_BOOL(0, "stdin", &from_stdin, N_("read reference patterns from stdin")),
|
||||||
OPT_END(),
|
OPT_END(),
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -75,7 +79,23 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
|
ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
|
||||||
filter.ignore_case = icase;
|
filter.ignore_case = icase;
|
||||||
|
|
||||||
|
if (from_stdin) {
|
||||||
|
struct strbuf line = STRBUF_INIT;
|
||||||
|
|
||||||
|
if (argv[0])
|
||||||
|
die(_("unknown arguments supplied with --stdin"));
|
||||||
|
|
||||||
|
while (strbuf_getline(&line, stdin) != EOF)
|
||||||
|
strvec_push(&vec, line.buf);
|
||||||
|
|
||||||
|
strbuf_release(&line);
|
||||||
|
|
||||||
|
/* vec.v is NULL-terminated, just like 'argv'. */
|
||||||
|
filter.name_patterns = vec.v;
|
||||||
|
} else {
|
||||||
filter.name_patterns = argv;
|
filter.name_patterns = argv;
|
||||||
|
}
|
||||||
|
|
||||||
filter.match_as_path = 1;
|
filter.match_as_path = 1;
|
||||||
filter_refs(&array, &filter, FILTER_REFS_ALL);
|
filter_refs(&array, &filter, FILTER_REFS_ALL);
|
||||||
ref_array_sort(sorting, &array);
|
ref_array_sort(sorting, &array);
|
||||||
@ -97,5 +117,6 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
|
|||||||
free_commit_list(filter.with_commit);
|
free_commit_list(filter.with_commit);
|
||||||
free_commit_list(filter.no_commit);
|
free_commit_list(filter.no_commit);
|
||||||
ref_sorting_release(sorting);
|
ref_sorting_release(sorting);
|
||||||
|
strvec_clear(&vec);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -1464,4 +1464,41 @@ sig_crlf="$(printf "%s" "$sig" | append_cr; echo dummy)"
|
|||||||
sig_crlf=${sig_crlf%dummy}
|
sig_crlf=${sig_crlf%dummy}
|
||||||
test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
|
test_atom refs/tags/fake-sig-crlf contents:signature "$sig_crlf"
|
||||||
|
|
||||||
|
test_expect_success 'git for-each-ref --stdin: empty' '
|
||||||
|
>in &&
|
||||||
|
git for-each-ref --format="%(refname)" --stdin <in >actual &&
|
||||||
|
git for-each-ref --format="%(refname)" >expect &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git for-each-ref --stdin: fails if extra args' '
|
||||||
|
>in &&
|
||||||
|
test_must_fail git for-each-ref --format="%(refname)" \
|
||||||
|
--stdin refs/heads/extra <in 2>err &&
|
||||||
|
grep "unknown arguments supplied with --stdin" err
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git for-each-ref --stdin: matches' '
|
||||||
|
cat >in <<-EOF &&
|
||||||
|
refs/tags/multi*
|
||||||
|
refs/heads/amb*
|
||||||
|
EOF
|
||||||
|
|
||||||
|
cat >expect <<-EOF &&
|
||||||
|
refs/heads/ambiguous
|
||||||
|
refs/tags/multi-ref1-100000-user1
|
||||||
|
refs/tags/multi-ref1-100000-user2
|
||||||
|
refs/tags/multi-ref1-200000-user1
|
||||||
|
refs/tags/multi-ref1-200000-user2
|
||||||
|
refs/tags/multi-ref2-100000-user1
|
||||||
|
refs/tags/multi-ref2-100000-user2
|
||||||
|
refs/tags/multi-ref2-200000-user1
|
||||||
|
refs/tags/multi-ref2-200000-user2
|
||||||
|
refs/tags/multiline
|
||||||
|
EOF
|
||||||
|
|
||||||
|
git for-each-ref --format="%(refname)" --stdin <in >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
Loading…
x
Reference in New Issue
Block a user