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