]>
Commit | Line | Data |
---|---|---|
25ec7bca JH |
1 | #include "cache.h" |
2 | #include "commit.h" | |
3 | #include "config.h" | |
4 | #include "revision.h" | |
dbbcd44f | 5 | #include "strvec.h" |
25ec7bca JH |
6 | #include "list-objects.h" |
7 | #include "list-objects-filter.h" | |
8 | #include "list-objects-filter-options.h" | |
b14ed5ad | 9 | #include "promisor-remote.h" |
489fc9ee | 10 | #include "trace.h" |
e987df5f MD |
11 | #include "url.h" |
12 | ||
13 | static int parse_combine_filter( | |
14 | struct list_objects_filter_options *filter_options, | |
15 | const char *arg, | |
16 | struct strbuf *errbuf); | |
25ec7bca | 17 | |
b9ea2147 TB |
18 | const char *list_object_filter_config_name(enum list_objects_filter_choice c) |
19 | { | |
20 | switch (c) { | |
21 | case LOFC_DISABLED: | |
22 | /* we have no name for "no filter at all" */ | |
23 | break; | |
24 | case LOFC_BLOB_NONE: | |
25 | return "blob:none"; | |
26 | case LOFC_BLOB_LIMIT: | |
27 | return "blob:limit"; | |
28 | case LOFC_TREE_DEPTH: | |
29 | return "tree"; | |
30 | case LOFC_SPARSE_OID: | |
31 | return "sparse:oid"; | |
b0c42a53 PS |
32 | case LOFC_OBJECT_TYPE: |
33 | return "object:type"; | |
b9ea2147 TB |
34 | case LOFC_COMBINE: |
35 | return "combine"; | |
36 | case LOFC__COUNT: | |
37 | /* not a real filter type; just the count of all filters */ | |
38 | break; | |
39 | } | |
5a923bb1 | 40 | BUG("list_object_filter_config_name: invalid argument '%d'", c); |
b9ea2147 TB |
41 | } |
42 | ||
25ec7bca | 43 | /* |
1dde5fa2 | 44 | * Parse value of the argument to the "filter" keyword. |
25ec7bca JH |
45 | * On the command line this looks like: |
46 | * --filter=<arg> | |
47 | * and in the pack protocol as: | |
48 | * "filter" SP <arg> | |
49 | * | |
50 | * The filter keyword will be used by many commands. | |
51 | * See Documentation/rev-list-options.txt for allowed values for <arg>. | |
52 | * | |
53 | * Capture the given arg as the "filter_spec". This can be forwarded to | |
87c2d9d3 JS |
54 | * subordinate commands when necessary (although it's better to pass it through |
55 | * expand_list_objects_filter_spec() first). We also "intern" the arg for the | |
56 | * convenience of the current command. | |
25ec7bca | 57 | */ |
1e1e39b3 JH |
58 | static int gently_parse_list_objects_filter( |
59 | struct list_objects_filter_options *filter_options, | |
60 | const char *arg, | |
61 | struct strbuf *errbuf) | |
25ec7bca JH |
62 | { |
63 | const char *v0; | |
64 | ||
fa3d1b63 CC |
65 | if (!arg) |
66 | return 0; | |
67 | ||
f56f7642 MD |
68 | if (filter_options->choice) |
69 | BUG("filter_options already populated"); | |
25ec7bca JH |
70 | |
71 | if (!strcmp(arg, "blob:none")) { | |
72 | filter_options->choice = LOFC_BLOB_NONE; | |
73 | return 0; | |
25ec7bca | 74 | |
1e1e39b3 JH |
75 | } else if (skip_prefix(arg, "blob:limit=", &v0)) { |
76 | if (git_parse_ulong(v0, &filter_options->blob_limit_value)) { | |
77 | filter_options->choice = LOFC_BLOB_LIMIT; | |
78 | return 0; | |
79 | } | |
25ec7bca | 80 | |
bc5975d2 | 81 | } else if (skip_prefix(arg, "tree:", &v0)) { |
c813a7c3 | 82 | if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) { |
842b0051 | 83 | strbuf_addstr(errbuf, _("expected 'tree:<depth>'")); |
bc5975d2 MD |
84 | return 1; |
85 | } | |
c813a7c3 | 86 | filter_options->choice = LOFC_TREE_DEPTH; |
bc5975d2 MD |
87 | return 0; |
88 | ||
1e1e39b3 | 89 | } else if (skip_prefix(arg, "sparse:oid=", &v0)) { |
4c96a775 | 90 | filter_options->sparse_oid_name = xstrdup(v0); |
25ec7bca JH |
91 | filter_options->choice = LOFC_SPARSE_OID; |
92 | return 0; | |
25ec7bca | 93 | |
1e1e39b3 | 94 | } else if (skip_prefix(arg, "sparse:path=", &v0)) { |
e693237e CC |
95 | if (errbuf) { |
96 | strbuf_addstr( | |
97 | errbuf, | |
98 | _("sparse:path filters support has been dropped")); | |
99 | } | |
100 | return 1; | |
e987df5f | 101 | |
b0c42a53 PS |
102 | } else if (skip_prefix(arg, "object:type=", &v0)) { |
103 | int type = type_from_string_gently(v0, strlen(v0), 1); | |
104 | if (type < 0) { | |
225f7fa8 | 105 | strbuf_addf(errbuf, _("'%s' for 'object:type=<type>' is " |
b0c42a53 PS |
106 | "not a valid object type"), v0); |
107 | return 1; | |
108 | } | |
109 | ||
110 | filter_options->object_type = type; | |
111 | filter_options->choice = LOFC_OBJECT_TYPE; | |
112 | ||
113 | return 0; | |
114 | ||
e987df5f MD |
115 | } else if (skip_prefix(arg, "combine:", &v0)) { |
116 | return parse_combine_filter(filter_options, v0, errbuf); | |
117 | ||
25ec7bca | 118 | } |
5a59a230 NTND |
119 | /* |
120 | * Please update _git_fetch() in git-completion.bash when you | |
121 | * add new filters | |
122 | */ | |
25ec7bca | 123 | |
842b0051 | 124 | strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg); |
cc0b05a4 | 125 | |
1e1e39b3 JH |
126 | memset(filter_options, 0, sizeof(*filter_options)); |
127 | return 1; | |
128 | } | |
129 | ||
e987df5f MD |
130 | static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?"; |
131 | ||
132 | static int has_reserved_character( | |
133 | struct strbuf *sub_spec, struct strbuf *errbuf) | |
1e1e39b3 | 134 | { |
e987df5f MD |
135 | const char *c = sub_spec->buf; |
136 | while (*c) { | |
137 | if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) { | |
138 | strbuf_addf( | |
139 | errbuf, | |
140 | _("must escape char in sub-filter-spec: '%c'"), | |
141 | *c); | |
142 | return 1; | |
143 | } | |
144 | c++; | |
145 | } | |
146 | ||
25ec7bca JH |
147 | return 0; |
148 | } | |
149 | ||
e987df5f MD |
150 | static int parse_combine_subfilter( |
151 | struct list_objects_filter_options *filter_options, | |
152 | struct strbuf *subspec, | |
153 | struct strbuf *errbuf) | |
154 | { | |
5a133e8a | 155 | size_t new_index = filter_options->sub_nr; |
e987df5f MD |
156 | char *decoded; |
157 | int result; | |
158 | ||
5a133e8a MD |
159 | ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, |
160 | filter_options->sub_alloc); | |
e987df5f MD |
161 | |
162 | decoded = url_percent_decode(subspec->buf); | |
163 | ||
164 | result = has_reserved_character(subspec, errbuf) || | |
165 | gently_parse_list_objects_filter( | |
166 | &filter_options->sub[new_index], decoded, errbuf); | |
167 | ||
168 | free(decoded); | |
169 | return result; | |
170 | } | |
171 | ||
172 | static int parse_combine_filter( | |
173 | struct list_objects_filter_options *filter_options, | |
174 | const char *arg, | |
175 | struct strbuf *errbuf) | |
176 | { | |
177 | struct strbuf **subspecs = strbuf_split_str(arg, '+', 0); | |
178 | size_t sub; | |
179 | int result = 0; | |
180 | ||
181 | if (!subspecs[0]) { | |
182 | strbuf_addstr(errbuf, _("expected something after combine:")); | |
183 | result = 1; | |
184 | goto cleanup; | |
185 | } | |
186 | ||
187 | for (sub = 0; subspecs[sub] && !result; sub++) { | |
188 | if (subspecs[sub + 1]) { | |
189 | /* | |
190 | * This is not the last subspec. Remove trailing "+" so | |
191 | * we can parse it. | |
192 | */ | |
193 | size_t last = subspecs[sub]->len - 1; | |
194 | assert(subspecs[sub]->buf[last] == '+'); | |
195 | strbuf_remove(subspecs[sub], last, 1); | |
196 | } | |
197 | result = parse_combine_subfilter( | |
198 | filter_options, subspecs[sub], errbuf); | |
199 | } | |
200 | ||
201 | filter_options->choice = LOFC_COMBINE; | |
202 | ||
203 | cleanup: | |
204 | strbuf_list_free(subspecs); | |
205 | if (result) { | |
206 | list_objects_filter_release(filter_options); | |
207 | memset(filter_options, 0, sizeof(*filter_options)); | |
208 | } | |
209 | return result; | |
210 | } | |
211 | ||
489fc9ee MD |
212 | static int allow_unencoded(char ch) |
213 | { | |
214 | if (ch <= ' ' || ch == '%' || ch == '+') | |
215 | return 0; | |
216 | return !strchr(RESERVED_NON_WS, ch); | |
217 | } | |
218 | ||
219 | static void filter_spec_append_urlencode( | |
220 | struct list_objects_filter_options *filter, const char *raw) | |
1e1e39b3 JH |
221 | { |
222 | struct strbuf buf = STRBUF_INIT; | |
489fc9ee MD |
223 | strbuf_addstr_urlencode(&buf, raw, allow_unencoded); |
224 | trace_printf("Add to combine filter-spec: %s\n", buf.buf); | |
225 | string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL)); | |
226 | } | |
227 | ||
228 | /* | |
229 | * Changes filter_options into an equivalent LOFC_COMBINE filter options | |
230 | * instance. Does not do anything if filter_options is already LOFC_COMBINE. | |
231 | */ | |
232 | static void transform_to_combine_type( | |
233 | struct list_objects_filter_options *filter_options) | |
234 | { | |
235 | assert(filter_options->choice); | |
236 | if (filter_options->choice == LOFC_COMBINE) | |
237 | return; | |
238 | { | |
239 | const int initial_sub_alloc = 2; | |
240 | struct list_objects_filter_options *sub_array = | |
241 | xcalloc(initial_sub_alloc, sizeof(*sub_array)); | |
242 | sub_array[0] = *filter_options; | |
243 | memset(filter_options, 0, sizeof(*filter_options)); | |
244 | filter_options->sub = sub_array; | |
245 | filter_options->sub_alloc = initial_sub_alloc; | |
246 | } | |
247 | filter_options->sub_nr = 1; | |
248 | filter_options->choice = LOFC_COMBINE; | |
249 | string_list_append(&filter_options->filter_spec, xstrdup("combine:")); | |
250 | filter_spec_append_urlencode( | |
251 | filter_options, | |
252 | list_objects_filter_spec(&filter_options->sub[0])); | |
253 | /* | |
254 | * We don't need the filter_spec strings for subfilter specs, only the | |
255 | * top level. | |
256 | */ | |
257 | string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0); | |
258 | } | |
259 | ||
260 | void list_objects_filter_die_if_populated( | |
261 | struct list_objects_filter_options *filter_options) | |
262 | { | |
f56f7642 MD |
263 | if (filter_options->choice) |
264 | die(_("multiple filter-specs cannot be combined")); | |
489fc9ee MD |
265 | } |
266 | ||
90d21f9e | 267 | void parse_list_objects_filter( |
489fc9ee MD |
268 | struct list_objects_filter_options *filter_options, |
269 | const char *arg) | |
270 | { | |
271 | struct strbuf errbuf = STRBUF_INIT; | |
272 | int parse_error; | |
273 | ||
274 | if (!filter_options->choice) { | |
275 | string_list_append(&filter_options->filter_spec, xstrdup(arg)); | |
276 | ||
277 | parse_error = gently_parse_list_objects_filter( | |
278 | filter_options, arg, &errbuf); | |
279 | } else { | |
280 | /* | |
281 | * Make filter_options an LOFC_COMBINE spec so we can trivially | |
282 | * add subspecs to it. | |
283 | */ | |
284 | transform_to_combine_type(filter_options); | |
285 | ||
286 | string_list_append(&filter_options->filter_spec, xstrdup("+")); | |
287 | filter_spec_append_urlencode(filter_options, arg); | |
5a133e8a MD |
288 | ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, |
289 | filter_options->sub_alloc); | |
489fc9ee MD |
290 | |
291 | parse_error = gently_parse_list_objects_filter( | |
5a133e8a MD |
292 | &filter_options->sub[filter_options->sub_nr - 1], arg, |
293 | &errbuf); | |
489fc9ee MD |
294 | } |
295 | if (parse_error) | |
296 | die("%s", errbuf.buf); | |
25ec7bca JH |
297 | } |
298 | ||
299 | int opt_parse_list_objects_filter(const struct option *opt, | |
300 | const char *arg, int unset) | |
301 | { | |
302 | struct list_objects_filter_options *filter_options = opt->value; | |
303 | ||
90d21f9e | 304 | if (unset || !arg) |
aa57b871 | 305 | list_objects_filter_set_no_filter(filter_options); |
90d21f9e MD |
306 | else |
307 | parse_list_objects_filter(filter_options, arg); | |
308 | return 0; | |
25ec7bca | 309 | } |
4875c979 | 310 | |
cf9ceb5a | 311 | const char *list_objects_filter_spec(struct list_objects_filter_options *filter) |
87c2d9d3 | 312 | { |
cf9ceb5a MD |
313 | if (!filter->filter_spec.nr) |
314 | BUG("no filter_spec available for this filter"); | |
315 | if (filter->filter_spec.nr != 1) { | |
316 | struct strbuf concatted = STRBUF_INIT; | |
317 | strbuf_add_separated_string_list( | |
318 | &concatted, "", &filter->filter_spec); | |
319 | string_list_clear(&filter->filter_spec, /*free_util=*/0); | |
320 | string_list_append( | |
321 | &filter->filter_spec, strbuf_detach(&concatted, NULL)); | |
4875c979 | 322 | } |
25ec7bca | 323 | |
cf9ceb5a | 324 | return filter->filter_spec.items[0].string; |
25ec7bca | 325 | } |
4875c979 | 326 | |
cf9ceb5a MD |
327 | const char *expand_list_objects_filter_spec( |
328 | struct list_objects_filter_options *filter) | |
87c2d9d3 | 329 | { |
cf9ceb5a MD |
330 | if (filter->choice == LOFC_BLOB_LIMIT) { |
331 | struct strbuf expanded_spec = STRBUF_INIT; | |
332 | strbuf_addf(&expanded_spec, "blob:limit=%lu", | |
87c2d9d3 | 333 | filter->blob_limit_value); |
cf9ceb5a MD |
334 | string_list_clear(&filter->filter_spec, /*free_util=*/0); |
335 | string_list_append( | |
336 | &filter->filter_spec, | |
337 | strbuf_detach(&expanded_spec, NULL)); | |
338 | } | |
339 | ||
340 | return list_objects_filter_spec(filter); | |
87c2d9d3 JS |
341 | } |
342 | ||
4875c979 JH |
343 | void list_objects_filter_release( |
344 | struct list_objects_filter_options *filter_options) | |
345 | { | |
e987df5f MD |
346 | size_t sub; |
347 | ||
348 | if (!filter_options) | |
349 | return; | |
cf9ceb5a | 350 | string_list_clear(&filter_options->filter_spec, /*free_util=*/0); |
4c96a775 | 351 | free(filter_options->sparse_oid_name); |
e987df5f MD |
352 | for (sub = 0; sub < filter_options->sub_nr; sub++) |
353 | list_objects_filter_release(&filter_options->sub[sub]); | |
354 | free(filter_options->sub); | |
4875c979 JH |
355 | memset(filter_options, 0, sizeof(*filter_options)); |
356 | } | |
1e1e39b3 JH |
357 | |
358 | void partial_clone_register( | |
359 | const char *remote, | |
cf9ceb5a | 360 | struct list_objects_filter_options *filter_options) |
1e1e39b3 | 361 | { |
23547c40 | 362 | struct promisor_remote *promisor_remote; |
b14ed5ad | 363 | char *cfg_name; |
fa3d1b63 | 364 | char *filter_name; |
1e1e39b3 | 365 | |
b14ed5ad | 366 | /* Check if it is already registered */ |
23547c40 JT |
367 | if ((promisor_remote = promisor_remote_find(remote))) { |
368 | if (promisor_remote->partial_clone_filter) | |
369 | /* | |
370 | * Remote is already registered and a filter is already | |
371 | * set, so we don't need to do anything here. | |
372 | */ | |
373 | return; | |
374 | } else { | |
16af5f1a XL |
375 | if (upgrade_repository_format(1) < 0) |
376 | die(_("unable to upgrade repository format to support partial clone")); | |
1e1e39b3 | 377 | |
b14ed5ad CC |
378 | /* Add promisor config for the remote */ |
379 | cfg_name = xstrfmt("remote.%s.promisor", remote); | |
380 | git_config_set(cfg_name, "true"); | |
381 | free(cfg_name); | |
382 | } | |
1e1e39b3 JH |
383 | |
384 | /* | |
385 | * Record the initial filter-spec in the config as | |
386 | * the default for subsequent fetches from this remote. | |
387 | */ | |
fa3d1b63 | 388 | filter_name = xstrfmt("remote.%s.partialclonefilter", remote); |
627b8268 JH |
389 | /* NEEDSWORK: 'expand' result leaking??? */ |
390 | git_config_set(filter_name, | |
391 | expand_list_objects_filter_spec(filter_options)); | |
fa3d1b63 | 392 | free(filter_name); |
b14ed5ad CC |
393 | |
394 | /* Make sure the config info are reset */ | |
395 | promisor_remote_reinit(); | |
1e1e39b3 JH |
396 | } |
397 | ||
398 | void partial_clone_get_default_filter_spec( | |
fa3d1b63 CC |
399 | struct list_objects_filter_options *filter_options, |
400 | const char *remote) | |
1e1e39b3 | 401 | { |
fa3d1b63 | 402 | struct promisor_remote *promisor = promisor_remote_find(remote); |
842b0051 | 403 | struct strbuf errbuf = STRBUF_INIT; |
fa3d1b63 | 404 | |
1e1e39b3 JH |
405 | /* |
406 | * Parse default value, but silently ignore it if it is invalid. | |
407 | */ | |
627b8268 | 408 | if (!promisor) |
cac1137d | 409 | return; |
e987df5f | 410 | |
cf9ceb5a | 411 | string_list_append(&filter_options->filter_spec, |
627b8268 | 412 | promisor->partial_clone_filter); |
1e1e39b3 | 413 | gently_parse_list_objects_filter(filter_options, |
627b8268 | 414 | promisor->partial_clone_filter, |
842b0051 MD |
415 | &errbuf); |
416 | strbuf_release(&errbuf); | |
1e1e39b3 | 417 | } |