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