list-objects-filter-options: make filter_spec a string_list
[git] / list-objects-filter-options.c
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 "url.h"
10
11 static int parse_combine_filter(
12         struct list_objects_filter_options *filter_options,
13         const char *arg,
14         struct strbuf *errbuf);
15
16 /*
17  * Parse value of the argument to the "filter" keyword.
18  * On the command line this looks like:
19  *       --filter=<arg>
20  * and in the pack protocol as:
21  *       "filter" SP <arg>
22  *
23  * The filter keyword will be used by many commands.
24  * See Documentation/rev-list-options.txt for allowed values for <arg>.
25  *
26  * Capture the given arg as the "filter_spec".  This can be forwarded to
27  * subordinate commands when necessary (although it's better to pass it through
28  * expand_list_objects_filter_spec() first).  We also "intern" the arg for the
29  * convenience of the current command.
30  */
31 static int gently_parse_list_objects_filter(
32         struct list_objects_filter_options *filter_options,
33         const char *arg,
34         struct strbuf *errbuf)
35 {
36         const char *v0;
37
38         if (filter_options->choice)
39                 BUG("filter_options already populated");
40
41         if (!strcmp(arg, "blob:none")) {
42                 filter_options->choice = LOFC_BLOB_NONE;
43                 return 0;
44
45         } else if (skip_prefix(arg, "blob:limit=", &v0)) {
46                 if (git_parse_ulong(v0, &filter_options->blob_limit_value)) {
47                         filter_options->choice = LOFC_BLOB_LIMIT;
48                         return 0;
49                 }
50
51         } else if (skip_prefix(arg, "tree:", &v0)) {
52                 if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) {
53                         strbuf_addstr(errbuf, _("expected 'tree:<depth>'"));
54                         return 1;
55                 }
56                 filter_options->choice = LOFC_TREE_DEPTH;
57                 return 0;
58
59         } else if (skip_prefix(arg, "sparse:oid=", &v0)) {
60                 struct object_context oc;
61                 struct object_id sparse_oid;
62
63                 /*
64                  * Try to parse <oid-expression> into an OID for the current
65                  * command, but DO NOT complain if we don't have the blob or
66                  * ref locally.
67                  */
68                 if (!get_oid_with_context(the_repository, v0, GET_OID_BLOB,
69                                           &sparse_oid, &oc))
70                         filter_options->sparse_oid_value = oiddup(&sparse_oid);
71                 filter_options->choice = LOFC_SPARSE_OID;
72                 return 0;
73
74         } else if (skip_prefix(arg, "sparse:path=", &v0)) {
75                 if (errbuf) {
76                         strbuf_addstr(
77                                 errbuf,
78                                 _("sparse:path filters support has been dropped"));
79                 }
80                 return 1;
81
82         } else if (skip_prefix(arg, "combine:", &v0)) {
83                 return parse_combine_filter(filter_options, v0, errbuf);
84
85         }
86         /*
87          * Please update _git_fetch() in git-completion.bash when you
88          * add new filters
89          */
90
91         strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg);
92
93         memset(filter_options, 0, sizeof(*filter_options));
94         return 1;
95 }
96
97 static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?";
98
99 static int has_reserved_character(
100         struct strbuf *sub_spec, struct strbuf *errbuf)
101 {
102         const char *c = sub_spec->buf;
103         while (*c) {
104                 if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) {
105                         strbuf_addf(
106                                 errbuf,
107                                 _("must escape char in sub-filter-spec: '%c'"),
108                                 *c);
109                         return 1;
110                 }
111                 c++;
112         }
113
114         return 0;
115 }
116
117 static int parse_combine_subfilter(
118         struct list_objects_filter_options *filter_options,
119         struct strbuf *subspec,
120         struct strbuf *errbuf)
121 {
122         size_t new_index = filter_options->sub_nr++;
123         char *decoded;
124         int result;
125
126         ALLOC_GROW(filter_options->sub, filter_options->sub_nr,
127                    filter_options->sub_alloc);
128         memset(&filter_options->sub[new_index], 0,
129                sizeof(*filter_options->sub));
130
131         decoded = url_percent_decode(subspec->buf);
132
133         result = has_reserved_character(subspec, errbuf) ||
134                 gently_parse_list_objects_filter(
135                         &filter_options->sub[new_index], decoded, errbuf);
136
137         free(decoded);
138         return result;
139 }
140
141 static int parse_combine_filter(
142         struct list_objects_filter_options *filter_options,
143         const char *arg,
144         struct strbuf *errbuf)
145 {
146         struct strbuf **subspecs = strbuf_split_str(arg, '+', 0);
147         size_t sub;
148         int result = 0;
149
150         if (!subspecs[0]) {
151                 strbuf_addstr(errbuf, _("expected something after combine:"));
152                 result = 1;
153                 goto cleanup;
154         }
155
156         for (sub = 0; subspecs[sub] && !result; sub++) {
157                 if (subspecs[sub + 1]) {
158                         /*
159                          * This is not the last subspec. Remove trailing "+" so
160                          * we can parse it.
161                          */
162                         size_t last = subspecs[sub]->len - 1;
163                         assert(subspecs[sub]->buf[last] == '+');
164                         strbuf_remove(subspecs[sub], last, 1);
165                 }
166                 result = parse_combine_subfilter(
167                         filter_options, subspecs[sub], errbuf);
168         }
169
170         filter_options->choice = LOFC_COMBINE;
171
172 cleanup:
173         strbuf_list_free(subspecs);
174         if (result) {
175                 list_objects_filter_release(filter_options);
176                 memset(filter_options, 0, sizeof(*filter_options));
177         }
178         return result;
179 }
180
181 int parse_list_objects_filter(struct list_objects_filter_options *filter_options,
182                               const char *arg)
183 {
184         struct strbuf buf = STRBUF_INIT;
185         if (filter_options->choice)
186                 die(_("multiple filter-specs cannot be combined"));
187         string_list_append(&filter_options->filter_spec, xstrdup(arg));
188         if (gently_parse_list_objects_filter(filter_options, arg, &buf))
189                 die("%s", buf.buf);
190         return 0;
191 }
192
193 int opt_parse_list_objects_filter(const struct option *opt,
194                                   const char *arg, int unset)
195 {
196         struct list_objects_filter_options *filter_options = opt->value;
197
198         if (unset || !arg) {
199                 list_objects_filter_set_no_filter(filter_options);
200                 return 0;
201         }
202
203         return parse_list_objects_filter(filter_options, arg);
204 }
205
206 const char *list_objects_filter_spec(struct list_objects_filter_options *filter)
207 {
208         if (!filter->filter_spec.nr)
209                 BUG("no filter_spec available for this filter");
210         if (filter->filter_spec.nr != 1) {
211                 struct strbuf concatted = STRBUF_INIT;
212                 strbuf_add_separated_string_list(
213                         &concatted, "", &filter->filter_spec);
214                 string_list_clear(&filter->filter_spec, /*free_util=*/0);
215                 string_list_append(
216                         &filter->filter_spec, strbuf_detach(&concatted, NULL));
217         }
218
219         return filter->filter_spec.items[0].string;
220 }
221
222 const char *expand_list_objects_filter_spec(
223         struct list_objects_filter_options *filter)
224 {
225         if (filter->choice == LOFC_BLOB_LIMIT) {
226                 struct strbuf expanded_spec = STRBUF_INIT;
227                 strbuf_addf(&expanded_spec, "blob:limit=%lu",
228                             filter->blob_limit_value);
229                 string_list_clear(&filter->filter_spec, /*free_util=*/0);
230                 string_list_append(
231                         &filter->filter_spec,
232                         strbuf_detach(&expanded_spec, NULL));
233         }
234
235         return list_objects_filter_spec(filter);
236 }
237
238 void list_objects_filter_release(
239         struct list_objects_filter_options *filter_options)
240 {
241         size_t sub;
242
243         if (!filter_options)
244                 return;
245         string_list_clear(&filter_options->filter_spec, /*free_util=*/0);
246         free(filter_options->sparse_oid_value);
247         for (sub = 0; sub < filter_options->sub_nr; sub++)
248                 list_objects_filter_release(&filter_options->sub[sub]);
249         free(filter_options->sub);
250         memset(filter_options, 0, sizeof(*filter_options));
251 }
252
253 void partial_clone_register(
254         const char *remote,
255         struct list_objects_filter_options *filter_options)
256 {
257         /*
258          * Record the name of the partial clone remote in the
259          * config and in the global variable -- the latter is
260          * used throughout to indicate that partial clone is
261          * enabled and to expect missing objects.
262          */
263         if (repository_format_partial_clone &&
264             *repository_format_partial_clone &&
265             strcmp(remote, repository_format_partial_clone))
266                 die(_("cannot change partial clone promisor remote"));
267
268         git_config_set("core.repositoryformatversion", "1");
269         git_config_set("extensions.partialclone", remote);
270
271         repository_format_partial_clone = xstrdup(remote);
272
273         /*
274          * Record the initial filter-spec in the config as
275          * the default for subsequent fetches from this remote.
276          */
277         core_partial_clone_filter_default =
278                 xstrdup(expand_list_objects_filter_spec(filter_options));
279         git_config_set("core.partialclonefilter",
280                        core_partial_clone_filter_default);
281 }
282
283 void partial_clone_get_default_filter_spec(
284         struct list_objects_filter_options *filter_options)
285 {
286         struct strbuf errbuf = STRBUF_INIT;
287
288         /*
289          * Parse default value, but silently ignore it if it is invalid.
290          */
291         if (!core_partial_clone_filter_default)
292                 return;
293
294         string_list_append(&filter_options->filter_spec,
295                            core_partial_clone_filter_default);
296         gently_parse_list_objects_filter(filter_options,
297                                          core_partial_clone_filter_default,
298                                          &errbuf);
299         strbuf_release(&errbuf);
300 }