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