Re: [PATCH v2 1/8] for-each-ref: add --stdin option
- From
Ævar Arnfjörð Bjarmason <avarab@gmail.com>
- Date
- Mar 15, 2023, 13:37 UTC
- Message-ID
- <230315.86h6umxh7c.gmgdl@evledraar.gmail.com>
- In-Reply-To
- <a1d9e0f6ff6660c9264673be18bc24956f74eb9c.1678468864.git.gitgitgadget@gmail.com>
On Fri, Mar 10 2023, Derrick Stolee via GitGitGadget wrote:
Show 5 quoted lines
> From: Derrick Stolee <derrickstolee@github.com> > > 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.
Okey, and the current API assumes you just assign "argv" to this, but...
> When reading from stdin, we populate the filter.name_patterns array > dynamically as opposed to pointing to the 'argv' array directly. This > requires a careful cast while freeing the individual strings, > conditioned on the --stdin option.
..sounds potentially nasty...
Show 40 quoted lines
> @@ -75,7 +77,27 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
> ref_sorting_set_sort_flags_all(sorting, REF_SORTING_ICASE, icase);
> filter.ignore_case = icase;
>
> - filter.name_patterns = argv;
> + if (from_stdin) {
> + struct strbuf line = STRBUF_INIT;
> + size_t nr = 0, alloc = 16;
> +
> + if (argv[0])
> + die(_("unknown arguments supplied with --stdin"));
> +
> + CALLOC_ARRAY(filter.name_patterns, alloc);
> +
> + while (strbuf_getline(&line, stdin) != EOF) {
> + ALLOC_GROW(filter.name_patterns, nr + 1, alloc);
> + filter.name_patterns[nr++] = strbuf_detach(&line, NULL);
> + }
> +
> + /* Add a terminating NULL string. */
> + ALLOC_GROW(filter.name_patterns, nr + 1, alloc);
> + filter.name_patterns[nr + 1] = NULL;
> + } else {
> + filter.name_patterns = argv;
> + }
> +
> filter.match_as_path = 1;
> filter_refs(&array, &filter, FILTER_REFS_ALL);
> ref_array_sort(sorting, &array);
> @@ -97,5 +119,10 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
> free_commit_list(filter.with_commit);
> free_commit_list(filter.no_commit);
> ref_sorting_release(sorting);
> + if (from_stdin) {
> + for (size_t i = 0; filter.name_patterns[i]; i++)
> + free((char *)filter.name_patterns[i]);
> + free(filter.name_patterns);
> + }
> return 0;
> }Why do we need to seemingly re-invent a "struct strvec" here? I tried to
simplify this on top of this (well, "seen"), and we can get rid of all
of this manual memory management & trailing NULL juggling as a result:
diff --git a/builtin/for-each-ref.c b/builtin/for-each-ref.c
index cf5ba6ffc12..13b75eff28c 100644
--- a/builtin/for-each-ref.c
+++ b/builtin/for-each-ref.c
@@ -7,6 +7,7 @@
#include "parse-options.h"
#include "ref-filter.h"
#include "commit-reach.h"
+#include "strvec.h"
static char const * const for_each_ref_usage[] = {
N_("git for-each-ref [<options>] [<pattern>]"),
@@ -27,6 +28,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
struct ref_format format = REF_FORMAT_INIT;
struct strbuf output = STRBUF_INIT;
struct strbuf err = STRBUF_INIT;
+ struct strvec stdin_pat = STRVEC_INIT;
int from_stdin = 0;
struct option opts[] = {
@@ -81,21 +83,13 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix)
if (from_stdin) {
struct strbuf line = STRBUF_INIT;
- size_t nr = 0, alloc = 16;
if (argv[0])
die(_("unknown arguments supplied with --stdin"));
- CALLOC_ARRAY(filter.name_patterns, alloc);
-
- while (strbuf_getline(&line, stdin) != EOF) {
- ALLOC_GROW(filter.name_patterns, nr + 1, alloc);
- filter.name_patterns[nr++] = strbuf_detach(&line, NULL);
- }
-
- /* Add a terminating NULL string. */
- ALLOC_GROW(filter.name_patterns, nr + 1, alloc);
- filter.name_patterns[nr + 1] = NULL;
+ while (strbuf_getline(&line, stdin) != EOF)
+ strvec_push(&stdin_pat, line.buf);
+ filter.name_patterns = stdin_pat.v;
} else {
filter.name_patterns = argv;
}
@@ -123,10 +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.no_commit);
ref_sorting_release(sorting);
- if (from_stdin) {
- for (size_t i = 0; filter.name_patterns[i]; i++)
- free(filter.name_patterns[i]);
- free(filter.name_patterns);
- }
+ strvec_clear(&stdin_pat);
return 0;
}It *is* an extra copy though, as your implementation re-uses the strbuf we already allocated.
But presumably that's trivial in this case, and if we care I think we should resurrect something like [1] instead, i.e. we could just teach the strvec API to have a strvec_push_nodup(). But I doubt that in this case it'll matter.
In any case, if you don't want to take this as-is, please fix this so that we're not reaching into the "filter.name_patterns" and casting its "const char" to "char".
If we're going to add a hack here that API should instead know how to free its own resources (so we could clean up the free_commit_list() here seen in the context), and we could carry some "my argv needs free-ing".
But none of that seems needed in this case, this is just another case where we can pretend that we have a "normal" argv, and then clean up our own strvec, no?
1. https://lore.kernel.org/git/65a620b08ef359e29d678497f1b529e3ce6477b1.1673475190.git.gitgitgadget@gmail.com/