Skip to content

Commit d0c2309

Browse files
matvoregitster
authored andcommitted
list-objects-filter-options: allow mult. --filter
Allow combining of multiple filters by simply repeating the --filter flag. Before this patch, the user had to combine them in a single flag somewhat awkwardly (e.g. --filter=combine:FOO+BAR), including URL-encoding the individual filters. To make this work, in the --filter flag parsing callback, rather than error out when we detect that the filter_options struct is already populated, we modify it in-place to contain the added sub-filter. The existing sub-filter becomes the lhs of the combined filter, and the next sub-filter becomes the rhs. We also have to URL-encode the LHS and RHS sub-filters. We can simplify the operation if the LHS is already a combine: filter. In that case, we just append the URL-encoded RHS sub-filter to the LHS spec to get the new spec. Helped-by: Emily Shaffer <[email protected]> Helped-by: Jeff Hostetler <[email protected]> Helped-by: Jeff King <[email protected]> Helped-by: Junio C Hamano <[email protected]> Signed-off-by: Matthew DeVore <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent 2d7099b commit d0c2309

File tree

7 files changed

+173
-10
lines changed

7 files changed

+173
-10
lines changed

Documentation/rev-list-options.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,22 @@ explicitly-given commit or tree.
738738
Note that the form '--filter=sparse:path=<path>' that wants to read
739739
from an arbitrary path on the filesystem has been dropped for security
740740
reasons.
741+
+
742+
Multiple '--filter=' flags can be specified to combine filters. Only
743+
objects which are accepted by every filter are included.
744+
+
745+
The form '--filter=combine:<filter1>+<filter2>+...<filterN>' can also be
746+
used to combined several filters, but this is harder than just repeating
747+
the '--filter' flag and is usually not necessary. Filters are joined by
748+
'{plus}' and individual filters are %-encoded (i.e. URL-encoded).
749+
Besides the '{plus}' and '%' characters, the following characters are
750+
reserved and also must be encoded: `~!@#$^&*()[]{}\;",<>?`+&#39;&#96;+
751+
as well as all characters with ASCII code &lt;= `0x20`, which includes
752+
space and newline.
753+
+
754+
Other arbitrary characters can also be encoded. For instance,
755+
'combine:tree:3+blob:none' and 'combine:tree%3A3+blob%3Anone' are
756+
equivalent.
741757

742758
--no-filter::
743759
Turn off any previous `--filter=` argument.

list-objects-filter-options.c

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#include "list-objects.h"
77
#include "list-objects-filter.h"
88
#include "list-objects-filter-options.h"
9+
#include "trace.h"
910
#include "url.h"
1011

1112
static int parse_combine_filter(
@@ -178,15 +179,92 @@ static int parse_combine_filter(
178179
return result;
179180
}
180181

181-
int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
182-
const char *arg)
182+
static int allow_unencoded(char ch)
183+
{
184+
if (ch <= ' ' || ch == '%' || ch == '+')
185+
return 0;
186+
return !strchr(RESERVED_NON_WS, ch);
187+
}
188+
189+
static void filter_spec_append_urlencode(
190+
struct list_objects_filter_options *filter, const char *raw)
183191
{
184192
struct strbuf buf = STRBUF_INIT;
193+
strbuf_addstr_urlencode(&buf, raw, allow_unencoded);
194+
trace_printf("Add to combine filter-spec: %s\n", buf.buf);
195+
string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL));
196+
}
197+
198+
/*
199+
* Changes filter_options into an equivalent LOFC_COMBINE filter options
200+
* instance. Does not do anything if filter_options is already LOFC_COMBINE.
201+
*/
202+
static void transform_to_combine_type(
203+
struct list_objects_filter_options *filter_options)
204+
{
205+
assert(filter_options->choice);
206+
if (filter_options->choice == LOFC_COMBINE)
207+
return;
208+
{
209+
const int initial_sub_alloc = 2;
210+
struct list_objects_filter_options *sub_array =
211+
xcalloc(initial_sub_alloc, sizeof(*sub_array));
212+
sub_array[0] = *filter_options;
213+
memset(filter_options, 0, sizeof(*filter_options));
214+
filter_options->sub = sub_array;
215+
filter_options->sub_alloc = initial_sub_alloc;
216+
}
217+
filter_options->sub_nr = 1;
218+
filter_options->choice = LOFC_COMBINE;
219+
string_list_append(&filter_options->filter_spec, xstrdup("combine:"));
220+
filter_spec_append_urlencode(
221+
filter_options,
222+
list_objects_filter_spec(&filter_options->sub[0]));
223+
/*
224+
* We don't need the filter_spec strings for subfilter specs, only the
225+
* top level.
226+
*/
227+
string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0);
228+
}
229+
230+
void list_objects_filter_die_if_populated(
231+
struct list_objects_filter_options *filter_options)
232+
{
185233
if (filter_options->choice)
186234
die(_("multiple filter-specs cannot be combined"));
187-
string_list_append(&filter_options->filter_spec, xstrdup(arg));
188-
if (gently_parse_list_objects_filter(filter_options, arg, &buf))
189-
die("%s", buf.buf);
235+
}
236+
237+
int parse_list_objects_filter(
238+
struct list_objects_filter_options *filter_options,
239+
const char *arg)
240+
{
241+
struct strbuf errbuf = STRBUF_INIT;
242+
int parse_error;
243+
244+
if (!filter_options->choice) {
245+
string_list_append(&filter_options->filter_spec, xstrdup(arg));
246+
247+
parse_error = gently_parse_list_objects_filter(
248+
filter_options, arg, &errbuf);
249+
} else {
250+
/*
251+
* Make filter_options an LOFC_COMBINE spec so we can trivially
252+
* add subspecs to it.
253+
*/
254+
transform_to_combine_type(filter_options);
255+
256+
string_list_append(&filter_options->filter_spec, xstrdup("+"));
257+
filter_spec_append_urlencode(filter_options, arg);
258+
ALLOC_GROW(filter_options->sub, filter_options->sub_nr + 1,
259+
filter_options->sub_alloc);
260+
filter_options = &filter_options->sub[filter_options->sub_nr++];
261+
memset(filter_options, 0, sizeof(*filter_options));
262+
263+
parse_error = gently_parse_list_objects_filter(
264+
filter_options, arg, &errbuf);
265+
}
266+
if (parse_error)
267+
die("%s", errbuf.buf);
190268
return 0;
191269
}
192270

list-objects-filter-options.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,17 @@ struct list_objects_filter_options {
6363
/* Normalized command line arguments */
6464
#define CL_ARG__FILTER "filter"
6565

66+
void list_objects_filter_die_if_populated(
67+
struct list_objects_filter_options *filter_options);
68+
69+
/*
70+
* Parses the filter spec string given by arg and either (1) simply places the
71+
* result in filter_options if it is not yet populated or (2) combines it with
72+
* the filter already in filter_options if it is already populated. In the case
73+
* of (2), the filter specs are combined as if specified with 'combine:'.
74+
*
75+
* Dies and prints a user-facing message if an error occurs.
76+
*/
6677
int parse_list_objects_filter(
6778
struct list_objects_filter_options *filter_options,
6879
const char *arg);

t/t5616-partial-clone.sh

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,25 @@ test_expect_success 'use fsck before and after manually fetching a missing subtr
208208
test_cmp unique_types.expected unique_types.observed
209209
'
210210

211+
test_expect_success 'implicitly construct combine: filter with repeated flags' '
212+
GIT_TRACE=$(pwd)/trace git clone --bare \
213+
--filter=blob:none --filter=tree:1 \
214+
"file://$(pwd)/srv.bare" pc2 &&
215+
grep "trace:.* git pack-objects .*--filter=combine:blob:none+tree:1" \
216+
trace &&
217+
git -C pc2 rev-list --objects --missing=allow-any HEAD >objects &&
218+
219+
# We should have gotten some root trees.
220+
grep " $" objects &&
221+
# Should not have gotten any non-root trees or blobs.
222+
! grep " ." objects &&
223+
224+
xargs -n 1 git -C pc2 cat-file -t <objects >types &&
225+
sort -u types >unique_types.actual &&
226+
test_write_lines commit tree >unique_types.expected &&
227+
test_cmp unique_types.expected unique_types.actual
228+
'
229+
211230
test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' '
212231
rm -rf src dst &&
213232
git init src &&

t/t6112-rev-list-filters-objects.sh

Lines changed: 41 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,16 @@ test_expect_success 'combine:... for a simple combination' '
351351
expect_has HEAD dir1 &&
352352
353353
# There are also 2 commit objects
354-
test_line_count = 5 actual
354+
test_line_count = 5 actual &&
355+
356+
cp actual expected &&
357+
358+
# Try again using repeated --filter - this is equivalent to a manual
359+
# combine with "combine:...+..."
360+
git -C r3 rev-list --objects --filter=combine:tree:2 \
361+
--filter=blob:none HEAD >actual &&
362+
363+
test_cmp expected actual
355364
'
356365

357366
test_expect_success 'combine:... with URL encoding' '
@@ -417,10 +426,12 @@ test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' '
417426
test_line_count = 5 actual
418427
'
419428

420-
test_expect_success 'add a sparse pattern blob whose path has reserved chars' '
429+
test_expect_success 'add sparse pattern blobs whose paths have reserved chars' '
421430
cp r3/pattern r3/pattern1+renamed% &&
422-
git -C r3 add pattern1+renamed% &&
423-
git -C r3 commit -m "add sparse pattern file with reserved chars"
431+
cp r3/pattern "r3/p;at%ter+n" &&
432+
cp r3/pattern r3/^~pattern &&
433+
git -C r3 add pattern1+renamed% "p;at%ter+n" ^~pattern &&
434+
git -C r3 commit -m "add sparse pattern files with reserved chars"
424435
'
425436

426437
test_expect_success 'combine:... with more than two sub-filters' '
@@ -445,7 +456,32 @@ test_expect_success 'combine:... with more than two sub-filters' '
445456
git -C r3 rev-list --objects \
446457
--filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern1%2brenamed%25 \
447458
HEAD >actual &&
448-
test_cmp expect actual
459+
test_cmp expect actual &&
460+
461+
# Use the same composite filter again, but with a pattern file name that
462+
# requires encoding multiple characters, and use implicit filter
463+
# combining.
464+
test_when_finished "rm -f trace1" &&
465+
GIT_TRACE=$(pwd)/trace1 git -C r3 rev-list --objects \
466+
--filter=tree:3 --filter=blob:limit=40 \
467+
--filter=sparse:oid="master:p;at%ter+n" \
468+
HEAD >actual &&
469+
470+
test_cmp expect actual &&
471+
grep "Add to combine filter-spec: sparse:oid=master:p%3bat%25ter%2bn" \
472+
trace1 &&
473+
474+
# Repeat the above test, but this time, the characters to encode are in
475+
# the LHS of the combined filter.
476+
test_when_finished "rm -f trace2" &&
477+
GIT_TRACE=$(pwd)/trace2 git -C r3 rev-list --objects \
478+
--filter=sparse:oid=master:^~pattern \
479+
--filter=tree:3 --filter=blob:limit=40 \
480+
HEAD >actual &&
481+
482+
test_cmp expect actual &&
483+
grep "Add to combine filter-spec: sparse:oid=master:%5e%7epattern" \
484+
trace2
449485
'
450486

451487
# Test provisional omit collection logic with a repo that has objects appearing

transport.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ static int set_git_option(struct git_transport_options *opts,
224224
opts->no_dependents = !!value;
225225
return 0;
226226
} else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) {
227+
list_objects_filter_die_if_populated(&opts->filter_options);
227228
parse_list_objects_filter(&opts->filter_options, value);
228229
return 0;
229230
}

upload-pack.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,7 @@ static void receive_needs(struct packet_reader *reader, struct object_array *wan
883883
if (skip_prefix(reader->line, "filter ", &arg)) {
884884
if (!filter_capability_requested)
885885
die("git upload-pack: filtering capability not negotiated");
886+
list_objects_filter_die_if_populated(&filter_options);
886887
parse_list_objects_filter(&filter_options, arg);
887888
continue;
888889
}
@@ -1304,6 +1305,7 @@ static void process_args(struct packet_reader *request,
13041305
}
13051306

13061307
if (allow_filter && skip_prefix(arg, "filter ", &p)) {
1308+
list_objects_filter_die_if_populated(&filter_options);
13071309
parse_list_objects_filter(&filter_options, p);
13081310
continue;
13091311
}

0 commit comments

Comments
 (0)