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