list-objects-filter-options: support 'auto' mode for --filter

In a following commit, we are going to allow passing "auto" as a <filterspec> to the `--filter=<filterspec>` option, but only for some commands. Other commands that support the `--filter=<filterspec>` option should still die() when 'auto' is passed. Let's set up the "list-objects-filter-options.{c,h}" infrastructure to support that: - Add a new `unsigned int allow_auto_filter : 1;` flag to `struct list_objects_filter_options` which specifies if "auto" is accepted or not by the current command. - Change gently_parse_list_objects_filter() to parse "auto" if it's accepted. - Make sure we die() if "auto" is combined with another filter. - Update list_objects_filter_release() to preserve the allow_auto_filter flag, as this function is often called (via opt_parse_list_objects_filter) to reset the struct before parsing a new value. Let's also update `list-objects-filter.c` to recognize the new `LOFC_AUTO` choice. Since "auto" must be resolved to a concrete filter before filtering actually begins, initializing a filter with `LOFC_AUTO` is invalid and will trigger a BUG(). Note that ideally combining "auto" with "auto" could be allowed, but in practice, it's probably not worth the added code complexity. And if we really want it, nothing prevents us to allow it in future work. If we ever want to give a meaning to combining "auto" with a different filter too, nothing prevents us to do that in future work either. Also note that the new `allow_auto_filter` flag depends on the command, not user choices, so it should be reset to the command default when `struct list_objects_filter_options` instances are reset. While at it, let's add a new "u-list-objects-filter-options.c" file for `struct list_objects_filter_options` related unit tests. For now it only tests gently_parse_list_objects_filter() though. Signed-off-by: Christian Couder <chriscool@tuxfamily.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Christian Couder committed Feb 16, 2026 at 14:23 UTC cd1a89838ad6753830afeed4ad8319e567b6e43d
6 files changed +105 -3
Makefile
+1
@@ -1518,6 +1518,7 @@ CLAR_TEST_SUITES += u-dir
1518 CLAR_TEST_SUITES += u-example-decorate
1519 CLAR_TEST_SUITES += u-hash
1520 CLAR_TEST_SUITES += u-hashmap
1521 +CLAR_TEST_SUITES += u-list-objects-filter-options
1522 CLAR_TEST_SUITES += u-mem-pool
1523 CLAR_TEST_SUITES += u-oid-array
1524 CLAR_TEST_SUITES += u-oidmap
list-objects-filter-options.c
+36 -3
@@ -20,6 +20,8 @@ const char *list_object_filter_config_name(enum list_objects_filter_choice c)
20 case LOFC_DISABLED:
21 /* we have no name for "no filter at all" */
22 break;
23 + case LOFC_AUTO:
24 + return "auto";
25 case LOFC_BLOB_NONE:
26 return "blob:none";
27 case LOFC_BLOB_LIMIT:
@@ -52,7 +54,16 @@ int gently_parse_list_objects_filter(
54 if (filter_options->choice)
55 BUG("filter_options already populated");
56
55 - if (!strcmp(arg, "blob:none")) {
57 + if (!strcmp(arg, "auto")) {
58 + if (!filter_options->allow_auto_filter) {
59 + strbuf_addstr(errbuf,
60 + _("'auto' filter not supported by this command"));
61 + return 1;
62 + }
63 + filter_options->choice = LOFC_AUTO;
64 + return 0;
65 +
66 + } else if (!strcmp(arg, "blob:none")) {
67 filter_options->choice = LOFC_BLOB_NONE;
68 return 0;
69
@@ -146,10 +157,22 @@ static int parse_combine_subfilter(
157
158 decoded = url_percent_decode(subspec->buf);
159
149 - result = has_reserved_character(subspec, errbuf) ||
150 - gently_parse_list_objects_filter(
160 + result = has_reserved_character(subspec, errbuf);
161 + if (result)
162 + goto cleanup;
163 +
164 + result = gently_parse_list_objects_filter(
165 &filter_options->sub[new_index], decoded, errbuf);
166 + if (result)
167 + goto cleanup;
168 +
169 + result = (filter_options->sub[new_index].choice == LOFC_AUTO);
170 + if (result) {
171 + strbuf_addstr(errbuf, _("an 'auto' filter cannot be combined"));
172 + goto cleanup;
173 + }
174
175 +cleanup:
176 free(decoded);
177 return result;
178 }
@@ -263,6 +286,9 @@ void parse_list_objects_filter(
286 } else {
287 struct list_objects_filter_options *sub;
288
289 + if (filter_options->choice == LOFC_AUTO)
290 + die(_("an 'auto' filter is incompatible with any other filter"));
291 +
292 /*
293 * Make filter_options an LOFC_COMBINE spec so we can trivially
294 * add subspecs to it.
@@ -277,6 +303,9 @@ void parse_list_objects_filter(
303 if (gently_parse_list_objects_filter(sub, arg, &errbuf))
304 die("%s", errbuf.buf);
305
306 + if (sub->choice == LOFC_AUTO)
307 + die(_("an 'auto' filter is incompatible with any other filter"));
308 +
309 strbuf_addch(&filter_options->filter_spec, '+');
310 filter_spec_append_urlencode(filter_options, arg);
311 }
@@ -317,15 +346,19 @@ void list_objects_filter_release(
346 struct list_objects_filter_options *filter_options)
347 {
348 size_t sub;
349 + unsigned int allow_auto_filter;
350
351 if (!filter_options)
352 return;
353 +
354 + allow_auto_filter = filter_options->allow_auto_filter;
355 strbuf_release(&filter_options->filter_spec);
356 free(filter_options->sparse_oid_name);
357 for (sub = 0; sub < filter_options->sub_nr; sub++)
358 list_objects_filter_release(&filter_options->sub[sub]);
359 free(filter_options->sub);
360 list_objects_filter_init(filter_options);
361 + filter_options->allow_auto_filter = allow_auto_filter;
362 }
363
364 void partial_clone_register(
list-objects-filter-options.h
+6
@@ -18,6 +18,7 @@ enum list_objects_filter_choice {
18 LOFC_SPARSE_OID,
19 LOFC_OBJECT_TYPE,
20 LOFC_COMBINE,
21 + LOFC_AUTO,
22 LOFC__COUNT /* must be last */
23 };
24
@@ -50,6 +51,11 @@ struct list_objects_filter_options {
51 */
52 unsigned int no_filter : 1;
53
54 + /*
55 + * Is LOFC_AUTO a valid option?
56 + */
57 + unsigned int allow_auto_filter : 1;
58 +
59 /*
60 * BEGIN choice-specific parsed values from within the filter-spec. Only
61 * some values will be defined for any given choice.
list-objects-filter.c
+8
@@ -745,6 +745,13 @@ static void filter_combine__init(
745 filter->finalize_omits_fn = filter_combine__finalize_omits;
746 }
747
748 +static void filter_auto__init(
749 + struct list_objects_filter_options *filter_options UNUSED,
750 + struct filter *filter UNUSED)
751 +{
752 + BUG("LOFC_AUTO should have been resolved before initializing the filter");
753 +}
754 +
755 typedef void (*filter_init_fn)(
756 struct list_objects_filter_options *filter_options,
757 struct filter *filter);
@@ -760,6 +767,7 @@ static filter_init_fn s_filters[] = {
767 filter_sparse_oid__init,
768 filter_object_type__init,
769 filter_combine__init,
770 + filter_auto__init,
771 };
772
773 struct filter *list_objects_filter__init(
t/meson.build
+1
@@ -4,6 +4,7 @@ clar_test_suites = [
4 'unit-tests/u-example-decorate.c',
5 'unit-tests/u-hash.c',
6 'unit-tests/u-hashmap.c',
7 + 'unit-tests/u-list-objects-filter-options.c',
8 'unit-tests/u-mem-pool.c',
9 'unit-tests/u-oid-array.c',
10 'unit-tests/u-oidmap.c',
t/unit-tests/u-list-objects-filter-options.c new
+53
@@ -0,0 +1,53 @@
1 +#include "unit-test.h"
2 +#include "list-objects-filter-options.h"
3 +#include "strbuf.h"
4 +
5 +/* Helper to test gently_parse_list_objects_filter() */
6 +static void check_gentle_parse(const char *filter_spec,
7 + int expect_success,
8 + int allow_auto,
9 + enum list_objects_filter_choice expected_choice)
10 +{
11 + struct list_objects_filter_options filter_options = LIST_OBJECTS_FILTER_INIT;
12 + struct strbuf errbuf = STRBUF_INIT;
13 + int ret;
14 +
15 + filter_options.allow_auto_filter = allow_auto;
16 +
17 + ret = gently_parse_list_objects_filter(&filter_options, filter_spec, &errbuf);
18 +
19 + if (expect_success) {
20 + cl_assert_equal_i(ret, 0);
21 + cl_assert_equal_i(expected_choice, filter_options.choice);
22 + cl_assert_equal_i(errbuf.len, 0);
23 + } else {
24 + cl_assert(ret != 0);
25 + cl_assert(errbuf.len > 0);
26 + }
27 +
28 + strbuf_release(&errbuf);
29 + list_objects_filter_release(&filter_options);
30 +}
31 +
32 +void test_list_objects_filter_options__regular_filters(void)
33 +{
34 + check_gentle_parse("blob:none", 1, 0, LOFC_BLOB_NONE);
35 + check_gentle_parse("blob:none", 1, 1, LOFC_BLOB_NONE);
36 + check_gentle_parse("blob:limit=5k", 1, 0, LOFC_BLOB_LIMIT);
37 + check_gentle_parse("blob:limit=5k", 1, 1, LOFC_BLOB_LIMIT);
38 + check_gentle_parse("combine:blob:none+tree:0", 1, 0, LOFC_COMBINE);
39 + check_gentle_parse("combine:blob:none+tree:0", 1, 1, LOFC_COMBINE);
40 +}
41 +
42 +void test_list_objects_filter_options__auto_allowed(void)
43 +{
44 + check_gentle_parse("auto", 1, 1, LOFC_AUTO);
45 + check_gentle_parse("auto", 0, 0, 0);
46 +}
47 +
48 +void test_list_objects_filter_options__combine_auto_fails(void)
49 +{
50 + check_gentle_parse("combine:auto+blob:none", 0, 1, 0);
51 + check_gentle_parse("combine:blob:none+auto", 0, 1, 0);
52 + check_gentle_parse("combine:auto+auto", 0, 1, 0);
53 +}