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