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