Merge branch 'md/list-objects-filter-combo'
The list-objects-filter API (used to create a sparse/lazy clone) learned to take a combined filter specification. * md/list-objects-filter-combo: list-objects-filter-options: make parser void list-objects-filter-options: clean up use of ALLOC_GROW list-objects-filter-options: allow mult. --filter strbuf: give URL-encoding API a char predicate fn list-objects-filter-options: make filter_spec a string_list list-objects-filter-options: move error check up list-objects-filter: implement composite filters list-objects-filter-options: always supply *errbuf list-objects-filter: put omits set in filter struct list-objects-filter: encapsulate filter componentsmaint
						commit
						627b826834
					
				|  | @ -756,6 +756,22 @@ explicitly-given commit or tree. | ||||||
| Note that the form '--filter=sparse:path=<path>' that wants to read | Note that the form '--filter=sparse:path=<path>' that wants to read | ||||||
| from an arbitrary path on the filesystem has been dropped for security | from an arbitrary path on the filesystem has been dropped for security | ||||||
| reasons. | reasons. | ||||||
|  | + | ||||||
|  | Multiple '--filter=' flags can be specified to combine filters. Only | ||||||
|  | objects which are accepted by every filter are included. | ||||||
|  | + | ||||||
|  | The form '--filter=combine:<filter1>+<filter2>+...<filterN>' can also be | ||||||
|  | used to combined several filters, but this is harder than just repeating | ||||||
|  | the '--filter' flag and is usually not necessary. Filters are joined by | ||||||
|  | '{plus}' and individual filters are %-encoded (i.e. URL-encoded). | ||||||
|  | Besides the '{plus}' and '%' characters, the following characters are | ||||||
|  | reserved and also must be encoded: `~!@#$^&*()[]{}\;",<>?`+'`+ | ||||||
|  | as well as all characters with ASCII code <= `0x20`, which includes | ||||||
|  | space and newline. | ||||||
|  | + | ||||||
|  | Other arbitrary characters can also be encoded. For instance, | ||||||
|  | 'combine:tree:3+blob:none' and 'combine:tree%3A3+blob%3Anone' are | ||||||
|  | equivalent. | ||||||
|  |  | ||||||
| --no-filter:: | --no-filter:: | ||||||
| 	Turn off any previous `--filter=` argument. | 	Turn off any previous `--filter=` argument. | ||||||
|  |  | ||||||
|  | @ -1160,13 +1160,11 @@ int cmd_clone(int argc, const char **argv, const char *prefix) | ||||||
| 		transport->server_options = &server_options; | 		transport->server_options = &server_options; | ||||||
|  |  | ||||||
| 	if (filter_options.choice) { | 	if (filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = | ||||||
| 		expand_list_objects_filter_spec(&filter_options, | 			expand_list_objects_filter_spec(&filter_options); | ||||||
| 						&expanded_filter_spec); |  | ||||||
| 		transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, | 		transport_set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, | ||||||
| 				     expanded_filter_spec.buf); | 				     spec); | ||||||
| 		transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); | 		transport_set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); | ||||||
| 		strbuf_release(&expanded_filter_spec); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (transport->smart_options && !deepen && !filter_options.choice) | 	if (transport->smart_options && !deepen && !filter_options.choice) | ||||||
|  |  | ||||||
|  | @ -1243,13 +1243,10 @@ static struct transport *prepare_transport(struct remote *remote, int deepen) | ||||||
| 	if (update_shallow) | 	if (update_shallow) | ||||||
| 		set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); | 		set_option(transport, TRANS_OPT_UPDATE_SHALLOW, "yes"); | ||||||
| 	if (filter_options.choice) { | 	if (filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = | ||||||
| 		expand_list_objects_filter_spec(&filter_options, | 			expand_list_objects_filter_spec(&filter_options); | ||||||
| 						&expanded_filter_spec); | 		set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, spec); | ||||||
| 		set_option(transport, TRANS_OPT_LIST_OBJECTS_FILTER, |  | ||||||
| 			   expanded_filter_spec.buf); |  | ||||||
| 		set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); | 		set_option(transport, TRANS_OPT_FROM_PROMISOR, "1"); | ||||||
| 		strbuf_release(&expanded_filter_spec); |  | ||||||
| 	} | 	} | ||||||
| 	if (negotiation_tip.nr) { | 	if (negotiation_tip.nr) { | ||||||
| 		if (transport->smart_options) | 		if (transport->smart_options) | ||||||
|  |  | ||||||
|  | @ -473,8 +473,10 @@ int cmd_rev_list(int argc, const char **argv, const char *prefix) | ||||||
| 				die(_("object filtering requires --objects")); | 				die(_("object filtering requires --objects")); | ||||||
| 			if (filter_options.choice == LOFC_SPARSE_OID && | 			if (filter_options.choice == LOFC_SPARSE_OID && | ||||||
| 			    !filter_options.sparse_oid_value) | 			    !filter_options.sparse_oid_value) | ||||||
| 				die(_("invalid sparse value '%s'"), | 				die( | ||||||
| 				    filter_options.filter_spec); | 					_("invalid sparse value '%s'"), | ||||||
|  | 					list_objects_filter_spec( | ||||||
|  | 						&filter_options)); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
| 		if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { | 		if (!strcmp(arg, ("--no-" CL_ARG__FILTER))) { | ||||||
|  |  | ||||||
							
								
								
									
										22
									
								
								cache.h
								
								
								
								
							
							
						
						
									
										22
									
								
								cache.h
								
								
								
								
							|  | @ -636,6 +636,9 @@ int daemonize(void); | ||||||
|  * at least 'nr' entries; the number of entries currently allocated |  * at least 'nr' entries; the number of entries currently allocated | ||||||
|  * is 'alloc', using the standard growing factor alloc_nr() macro. |  * is 'alloc', using the standard growing factor alloc_nr() macro. | ||||||
|  * |  * | ||||||
|  |  * Consider using ALLOC_GROW_BY instead of ALLOC_GROW as it has some | ||||||
|  |  * added niceties. | ||||||
|  |  * | ||||||
|  * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'. |  * DO NOT USE any expression with side-effect for 'x', 'nr', or 'alloc'. | ||||||
|  */ |  */ | ||||||
| #define ALLOC_GROW(x, nr, alloc) \ | #define ALLOC_GROW(x, nr, alloc) \ | ||||||
|  | @ -649,6 +652,25 @@ int daemonize(void); | ||||||
| 		} \ | 		} \ | ||||||
| 	} while (0) | 	} while (0) | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Similar to ALLOC_GROW but handles updating of the nr value and | ||||||
|  |  * zeroing the bytes of the newly-grown array elements. | ||||||
|  |  * | ||||||
|  |  * DO NOT USE any expression with side-effect for any of the | ||||||
|  |  * arguments. | ||||||
|  |  */ | ||||||
|  | #define ALLOC_GROW_BY(x, nr, increase, alloc) \ | ||||||
|  | 	do { \ | ||||||
|  | 		if (increase) { \ | ||||||
|  | 			size_t new_nr = nr + (increase); \ | ||||||
|  | 			if (new_nr < nr) \ | ||||||
|  | 				BUG("negative growth in ALLOC_GROW_BY"); \ | ||||||
|  | 			ALLOC_GROW(x, new_nr, alloc); \ | ||||||
|  | 			memset((x) + nr, 0, sizeof(*(x)) * (increase)); \ | ||||||
|  | 			nr = new_nr; \ | ||||||
|  | 		} \ | ||||||
|  | 	} while (0) | ||||||
|  |  | ||||||
| /* Initialize and use the cache information */ | /* Initialize and use the cache information */ | ||||||
| struct lock_file; | struct lock_file; | ||||||
| void preload_index(struct index_state *index, | void preload_index(struct index_state *index, | ||||||
|  |  | ||||||
|  | @ -72,15 +72,16 @@ static void store_credential_file(const char *fn, struct credential *c) | ||||||
| 	struct strbuf buf = STRBUF_INIT; | 	struct strbuf buf = STRBUF_INIT; | ||||||
|  |  | ||||||
| 	strbuf_addf(&buf, "%s://", c->protocol); | 	strbuf_addf(&buf, "%s://", c->protocol); | ||||||
| 	strbuf_addstr_urlencode(&buf, c->username, 1); | 	strbuf_addstr_urlencode(&buf, c->username, is_rfc3986_unreserved); | ||||||
| 	strbuf_addch(&buf, ':'); | 	strbuf_addch(&buf, ':'); | ||||||
| 	strbuf_addstr_urlencode(&buf, c->password, 1); | 	strbuf_addstr_urlencode(&buf, c->password, is_rfc3986_unreserved); | ||||||
| 	strbuf_addch(&buf, '@'); | 	strbuf_addch(&buf, '@'); | ||||||
| 	if (c->host) | 	if (c->host) | ||||||
| 		strbuf_addstr_urlencode(&buf, c->host, 1); | 		strbuf_addstr_urlencode(&buf, c->host, is_rfc3986_unreserved); | ||||||
| 	if (c->path) { | 	if (c->path) { | ||||||
| 		strbuf_addch(&buf, '/'); | 		strbuf_addch(&buf, '/'); | ||||||
| 		strbuf_addstr_urlencode(&buf, c->path, 0); | 		strbuf_addstr_urlencode(&buf, c->path, | ||||||
|  | 					is_rfc3986_reserved_or_unreserved); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	rewrite_credential_file(fn, c, &buf); | 	rewrite_credential_file(fn, c, &buf); | ||||||
|  |  | ||||||
							
								
								
									
										20
									
								
								fetch-pack.c
								
								
								
								
							
							
						
						
									
										20
									
								
								fetch-pack.c
								
								
								
								
							|  | @ -338,12 +338,9 @@ static int find_common(struct fetch_negotiator *negotiator, | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if (server_supports_filtering && args->filter_options.choice) { | 	if (server_supports_filtering && args->filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = | ||||||
| 		expand_list_objects_filter_spec(&args->filter_options, | 			expand_list_objects_filter_spec(&args->filter_options); | ||||||
| 						&expanded_filter_spec); | 		packet_buf_write(&req_buf, "filter %s", spec); | ||||||
| 		packet_buf_write(&req_buf, "filter %s", |  | ||||||
| 				 expanded_filter_spec.buf); |  | ||||||
| 		strbuf_release(&expanded_filter_spec); |  | ||||||
| 	} | 	} | ||||||
| 	packet_buf_flush(&req_buf); | 	packet_buf_flush(&req_buf); | ||||||
| 	state_len = req_buf.len; | 	state_len = req_buf.len; | ||||||
|  | @ -1112,7 +1109,7 @@ static int add_haves(struct fetch_negotiator *negotiator, | ||||||
| } | } | ||||||
|  |  | ||||||
| static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, | static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, | ||||||
| 			      const struct fetch_pack_args *args, | 			      struct fetch_pack_args *args, | ||||||
| 			      const struct ref *wants, struct oidset *common, | 			      const struct ref *wants, struct oidset *common, | ||||||
| 			      int *haves_to_send, int *in_vain, | 			      int *haves_to_send, int *in_vain, | ||||||
| 			      int sideband_all) | 			      int sideband_all) | ||||||
|  | @ -1153,13 +1150,10 @@ static int send_fetch_request(struct fetch_negotiator *negotiator, int fd_out, | ||||||
| 	/* Add filter */ | 	/* Add filter */ | ||||||
| 	if (server_supports_feature("fetch", "filter", 0) && | 	if (server_supports_feature("fetch", "filter", 0) && | ||||||
| 	    args->filter_options.choice) { | 	    args->filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = | ||||||
|  | 			expand_list_objects_filter_spec(&args->filter_options); | ||||||
| 		print_verbose(args, _("Server supports filter")); | 		print_verbose(args, _("Server supports filter")); | ||||||
| 		expand_list_objects_filter_spec(&args->filter_options, | 		packet_buf_write(&req_buf, "filter %s", spec); | ||||||
| 						&expanded_filter_spec); |  | ||||||
| 		packet_buf_write(&req_buf, "filter %s", |  | ||||||
| 				 expanded_filter_spec.buf); |  | ||||||
| 		strbuf_release(&expanded_filter_spec); |  | ||||||
| 	} else if (args->filter_options.choice) { | 	} else if (args->filter_options.choice) { | ||||||
| 		warning("filtering not recognized by server, ignoring"); | 		warning("filtering not recognized by server, ignoring"); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								http.c
								
								
								
								
							
							
						
						
									
										6
									
								
								http.c
								
								
								
								
							|  | @ -513,9 +513,11 @@ static void set_proxyauth_name_password(CURL *result) | ||||||
| #else | #else | ||||||
| 		struct strbuf s = STRBUF_INIT; | 		struct strbuf s = STRBUF_INIT; | ||||||
|  |  | ||||||
| 		strbuf_addstr_urlencode(&s, proxy_auth.username, 1); | 		strbuf_addstr_urlencode(&s, proxy_auth.username, | ||||||
|  | 					is_rfc3986_unreserved); | ||||||
| 		strbuf_addch(&s, ':'); | 		strbuf_addch(&s, ':'); | ||||||
| 		strbuf_addstr_urlencode(&s, proxy_auth.password, 1); | 		strbuf_addstr_urlencode(&s, proxy_auth.password, | ||||||
|  | 					is_rfc3986_unreserved); | ||||||
| 		curl_proxyuserpwd = strbuf_detach(&s, NULL); | 		curl_proxyuserpwd = strbuf_detach(&s, NULL); | ||||||
| 		curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd); | 		curl_easy_setopt(result, CURLOPT_PROXYUSERPWD, curl_proxyuserpwd); | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  | @ -7,6 +7,13 @@ | ||||||
| #include "list-objects-filter.h" | #include "list-objects-filter.h" | ||||||
| #include "list-objects-filter-options.h" | #include "list-objects-filter-options.h" | ||||||
| #include "promisor-remote.h" | #include "promisor-remote.h" | ||||||
|  | #include "trace.h" | ||||||
|  | #include "url.h" | ||||||
|  |  | ||||||
|  | static int parse_combine_filter( | ||||||
|  | 	struct list_objects_filter_options *filter_options, | ||||||
|  | 	const char *arg, | ||||||
|  | 	struct strbuf *errbuf); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Parse value of the argument to the "filter" keyword. |  * Parse value of the argument to the "filter" keyword. | ||||||
|  | @ -33,16 +40,8 @@ static int gently_parse_list_objects_filter( | ||||||
| 	if (!arg) | 	if (!arg) | ||||||
| 		return 0; | 		return 0; | ||||||
|  |  | ||||||
| 	if (filter_options->choice) { | 	if (filter_options->choice) | ||||||
| 		if (errbuf) { | 		BUG("filter_options already populated"); | ||||||
| 			strbuf_addstr( |  | ||||||
| 				errbuf, |  | ||||||
| 				_("multiple filter-specs cannot be combined")); |  | ||||||
| 		} |  | ||||||
| 		return 1; |  | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	filter_options->filter_spec = strdup(arg); |  | ||||||
|  |  | ||||||
| 	if (!strcmp(arg, "blob:none")) { | 	if (!strcmp(arg, "blob:none")) { | ||||||
| 		filter_options->choice = LOFC_BLOB_NONE; | 		filter_options->choice = LOFC_BLOB_NONE; | ||||||
|  | @ -56,11 +55,7 @@ static int gently_parse_list_objects_filter( | ||||||
|  |  | ||||||
| 	} else if (skip_prefix(arg, "tree:", &v0)) { | 	} else if (skip_prefix(arg, "tree:", &v0)) { | ||||||
| 		if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) { | 		if (!git_parse_ulong(v0, &filter_options->tree_exclude_depth)) { | ||||||
| 			if (errbuf) { | 			strbuf_addstr(errbuf, _("expected 'tree:<depth>'")); | ||||||
| 				strbuf_addstr( |  | ||||||
| 					errbuf, |  | ||||||
| 					_("expected 'tree:<depth>'")); |  | ||||||
| 			} |  | ||||||
| 			return 1; | 			return 1; | ||||||
| 		} | 		} | ||||||
| 		filter_options->choice = LOFC_TREE_DEPTH; | 		filter_options->choice = LOFC_TREE_DEPTH; | ||||||
|  | @ -88,26 +83,189 @@ static int gently_parse_list_objects_filter( | ||||||
| 				_("sparse:path filters support has been dropped")); | 				_("sparse:path filters support has been dropped")); | ||||||
| 		} | 		} | ||||||
| 		return 1; | 		return 1; | ||||||
|  |  | ||||||
|  | 	} else if (skip_prefix(arg, "combine:", &v0)) { | ||||||
|  | 		return parse_combine_filter(filter_options, v0, errbuf); | ||||||
|  |  | ||||||
| 	} | 	} | ||||||
| 	/* | 	/* | ||||||
| 	 * Please update _git_fetch() in git-completion.bash when you | 	 * Please update _git_fetch() in git-completion.bash when you | ||||||
| 	 * add new filters | 	 * add new filters | ||||||
| 	 */ | 	 */ | ||||||
|  |  | ||||||
| 	if (errbuf) |  | ||||||
| 	strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg); | 	strbuf_addf(errbuf, _("invalid filter-spec '%s'"), arg); | ||||||
|  |  | ||||||
| 	memset(filter_options, 0, sizeof(*filter_options)); | 	memset(filter_options, 0, sizeof(*filter_options)); | ||||||
| 	return 1; | 	return 1; | ||||||
| } | } | ||||||
|  |  | ||||||
| int parse_list_objects_filter(struct list_objects_filter_options *filter_options, | static const char *RESERVED_NON_WS = "~`!@#$^&*()[]{}\\;'\",<>?"; | ||||||
| 			      const char *arg) |  | ||||||
|  | static int has_reserved_character( | ||||||
|  | 	struct strbuf *sub_spec, struct strbuf *errbuf) | ||||||
|  | { | ||||||
|  | 	const char *c = sub_spec->buf; | ||||||
|  | 	while (*c) { | ||||||
|  | 		if (*c <= ' ' || strchr(RESERVED_NON_WS, *c)) { | ||||||
|  | 			strbuf_addf( | ||||||
|  | 				errbuf, | ||||||
|  | 				_("must escape char in sub-filter-spec: '%c'"), | ||||||
|  | 				*c); | ||||||
|  | 			return 1; | ||||||
|  | 		} | ||||||
|  | 		c++; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int parse_combine_subfilter( | ||||||
|  | 	struct list_objects_filter_options *filter_options, | ||||||
|  | 	struct strbuf *subspec, | ||||||
|  | 	struct strbuf *errbuf) | ||||||
|  | { | ||||||
|  | 	size_t new_index = filter_options->sub_nr; | ||||||
|  | 	char *decoded; | ||||||
|  | 	int result; | ||||||
|  |  | ||||||
|  | 	ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, | ||||||
|  | 		      filter_options->sub_alloc); | ||||||
|  |  | ||||||
|  | 	decoded = url_percent_decode(subspec->buf); | ||||||
|  |  | ||||||
|  | 	result = has_reserved_character(subspec, errbuf) || | ||||||
|  | 		gently_parse_list_objects_filter( | ||||||
|  | 			&filter_options->sub[new_index], decoded, errbuf); | ||||||
|  |  | ||||||
|  | 	free(decoded); | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int parse_combine_filter( | ||||||
|  | 	struct list_objects_filter_options *filter_options, | ||||||
|  | 	const char *arg, | ||||||
|  | 	struct strbuf *errbuf) | ||||||
|  | { | ||||||
|  | 	struct strbuf **subspecs = strbuf_split_str(arg, '+', 0); | ||||||
|  | 	size_t sub; | ||||||
|  | 	int result = 0; | ||||||
|  |  | ||||||
|  | 	if (!subspecs[0]) { | ||||||
|  | 		strbuf_addstr(errbuf, _("expected something after combine:")); | ||||||
|  | 		result = 1; | ||||||
|  | 		goto cleanup; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	for (sub = 0; subspecs[sub] && !result; sub++) { | ||||||
|  | 		if (subspecs[sub + 1]) { | ||||||
|  | 			/* | ||||||
|  | 			 * This is not the last subspec. Remove trailing "+" so | ||||||
|  | 			 * we can parse it. | ||||||
|  | 			 */ | ||||||
|  | 			size_t last = subspecs[sub]->len - 1; | ||||||
|  | 			assert(subspecs[sub]->buf[last] == '+'); | ||||||
|  | 			strbuf_remove(subspecs[sub], last, 1); | ||||||
|  | 		} | ||||||
|  | 		result = parse_combine_subfilter( | ||||||
|  | 			filter_options, subspecs[sub], errbuf); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	filter_options->choice = LOFC_COMBINE; | ||||||
|  |  | ||||||
|  | cleanup: | ||||||
|  | 	strbuf_list_free(subspecs); | ||||||
|  | 	if (result) { | ||||||
|  | 		list_objects_filter_release(filter_options); | ||||||
|  | 		memset(filter_options, 0, sizeof(*filter_options)); | ||||||
|  | 	} | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static int allow_unencoded(char ch) | ||||||
|  | { | ||||||
|  | 	if (ch <= ' ' || ch == '%' || ch == '+') | ||||||
|  | 		return 0; | ||||||
|  | 	return !strchr(RESERVED_NON_WS, ch); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void filter_spec_append_urlencode( | ||||||
|  | 	struct list_objects_filter_options *filter, const char *raw) | ||||||
| { | { | ||||||
| 	struct strbuf buf = STRBUF_INIT; | 	struct strbuf buf = STRBUF_INIT; | ||||||
| 	if (gently_parse_list_objects_filter(filter_options, arg, &buf)) | 	strbuf_addstr_urlencode(&buf, raw, allow_unencoded); | ||||||
| 		die("%s", buf.buf); | 	trace_printf("Add to combine filter-spec: %s\n", buf.buf); | ||||||
| 	return 0; | 	string_list_append(&filter->filter_spec, strbuf_detach(&buf, NULL)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Changes filter_options into an equivalent LOFC_COMBINE filter options | ||||||
|  |  * instance. Does not do anything if filter_options is already LOFC_COMBINE. | ||||||
|  |  */ | ||||||
|  | static void transform_to_combine_type( | ||||||
|  | 	struct list_objects_filter_options *filter_options) | ||||||
|  | { | ||||||
|  | 	assert(filter_options->choice); | ||||||
|  | 	if (filter_options->choice == LOFC_COMBINE) | ||||||
|  | 		return; | ||||||
|  | 	{ | ||||||
|  | 		const int initial_sub_alloc = 2; | ||||||
|  | 		struct list_objects_filter_options *sub_array = | ||||||
|  | 			xcalloc(initial_sub_alloc, sizeof(*sub_array)); | ||||||
|  | 		sub_array[0] = *filter_options; | ||||||
|  | 		memset(filter_options, 0, sizeof(*filter_options)); | ||||||
|  | 		filter_options->sub = sub_array; | ||||||
|  | 		filter_options->sub_alloc = initial_sub_alloc; | ||||||
|  | 	} | ||||||
|  | 	filter_options->sub_nr = 1; | ||||||
|  | 	filter_options->choice = LOFC_COMBINE; | ||||||
|  | 	string_list_append(&filter_options->filter_spec, xstrdup("combine:")); | ||||||
|  | 	filter_spec_append_urlencode( | ||||||
|  | 		filter_options, | ||||||
|  | 		list_objects_filter_spec(&filter_options->sub[0])); | ||||||
|  | 	/* | ||||||
|  | 	 * We don't need the filter_spec strings for subfilter specs, only the | ||||||
|  | 	 * top level. | ||||||
|  | 	 */ | ||||||
|  | 	string_list_clear(&filter_options->sub[0].filter_spec, /*free_util=*/0); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void list_objects_filter_die_if_populated( | ||||||
|  | 	struct list_objects_filter_options *filter_options) | ||||||
|  | { | ||||||
|  | 	if (filter_options->choice) | ||||||
|  | 		die(_("multiple filter-specs cannot be combined")); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void parse_list_objects_filter( | ||||||
|  | 	struct list_objects_filter_options *filter_options, | ||||||
|  | 	const char *arg) | ||||||
|  | { | ||||||
|  | 	struct strbuf errbuf = STRBUF_INIT; | ||||||
|  | 	int parse_error; | ||||||
|  |  | ||||||
|  | 	if (!filter_options->choice) { | ||||||
|  | 		string_list_append(&filter_options->filter_spec, xstrdup(arg)); | ||||||
|  |  | ||||||
|  | 		parse_error = gently_parse_list_objects_filter( | ||||||
|  | 			filter_options, arg, &errbuf); | ||||||
|  | 	} else { | ||||||
|  | 		/* | ||||||
|  | 		 * Make filter_options an LOFC_COMBINE spec so we can trivially | ||||||
|  | 		 * add subspecs to it. | ||||||
|  | 		 */ | ||||||
|  | 		transform_to_combine_type(filter_options); | ||||||
|  |  | ||||||
|  | 		string_list_append(&filter_options->filter_spec, xstrdup("+")); | ||||||
|  | 		filter_spec_append_urlencode(filter_options, arg); | ||||||
|  | 		ALLOC_GROW_BY(filter_options->sub, filter_options->sub_nr, 1, | ||||||
|  | 			      filter_options->sub_alloc); | ||||||
|  |  | ||||||
|  | 		parse_error = gently_parse_list_objects_filter( | ||||||
|  | 			&filter_options->sub[filter_options->sub_nr - 1], arg, | ||||||
|  | 			&errbuf); | ||||||
|  | 	} | ||||||
|  | 	if (parse_error) | ||||||
|  | 		die("%s", errbuf.buf); | ||||||
| } | } | ||||||
|  |  | ||||||
| int opt_parse_list_objects_filter(const struct option *opt, | int opt_parse_list_objects_filter(const struct option *opt, | ||||||
|  | @ -115,40 +273,63 @@ int opt_parse_list_objects_filter(const struct option *opt, | ||||||
| { | { | ||||||
| 	struct list_objects_filter_options *filter_options = opt->value; | 	struct list_objects_filter_options *filter_options = opt->value; | ||||||
|  |  | ||||||
| 	if (unset || !arg) { | 	if (unset || !arg) | ||||||
| 		list_objects_filter_set_no_filter(filter_options); | 		list_objects_filter_set_no_filter(filter_options); | ||||||
|  | 	else | ||||||
|  | 		parse_list_objects_filter(filter_options, arg); | ||||||
| 	return 0; | 	return 0; | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	return parse_list_objects_filter(filter_options, arg); |  | ||||||
| } | } | ||||||
|  |  | ||||||
| void expand_list_objects_filter_spec( | const char *list_objects_filter_spec(struct list_objects_filter_options *filter) | ||||||
| 	const struct list_objects_filter_options *filter, |  | ||||||
| 	struct strbuf *expanded_spec) |  | ||||||
| { | { | ||||||
| 	strbuf_init(expanded_spec, strlen(filter->filter_spec)); | 	if (!filter->filter_spec.nr) | ||||||
| 	if (filter->choice == LOFC_BLOB_LIMIT) | 		BUG("no filter_spec available for this filter"); | ||||||
| 		strbuf_addf(expanded_spec, "blob:limit=%lu", | 	if (filter->filter_spec.nr != 1) { | ||||||
|  | 		struct strbuf concatted = STRBUF_INIT; | ||||||
|  | 		strbuf_add_separated_string_list( | ||||||
|  | 			&concatted, "", &filter->filter_spec); | ||||||
|  | 		string_list_clear(&filter->filter_spec, /*free_util=*/0); | ||||||
|  | 		string_list_append( | ||||||
|  | 			&filter->filter_spec, strbuf_detach(&concatted, NULL)); | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return filter->filter_spec.items[0].string; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const char *expand_list_objects_filter_spec( | ||||||
|  | 	struct list_objects_filter_options *filter) | ||||||
|  | { | ||||||
|  | 	if (filter->choice == LOFC_BLOB_LIMIT) { | ||||||
|  | 		struct strbuf expanded_spec = STRBUF_INIT; | ||||||
|  | 		strbuf_addf(&expanded_spec, "blob:limit=%lu", | ||||||
| 			    filter->blob_limit_value); | 			    filter->blob_limit_value); | ||||||
| 	else if (filter->choice == LOFC_TREE_DEPTH) | 		string_list_clear(&filter->filter_spec, /*free_util=*/0); | ||||||
| 		strbuf_addf(expanded_spec, "tree:%lu", | 		string_list_append( | ||||||
| 			    filter->tree_exclude_depth); | 			&filter->filter_spec, | ||||||
| 	else | 			strbuf_detach(&expanded_spec, NULL)); | ||||||
| 		strbuf_addstr(expanded_spec, filter->filter_spec); | 	} | ||||||
|  |  | ||||||
|  | 	return list_objects_filter_spec(filter); | ||||||
| } | } | ||||||
|  |  | ||||||
| void list_objects_filter_release( | void list_objects_filter_release( | ||||||
| 	struct list_objects_filter_options *filter_options) | 	struct list_objects_filter_options *filter_options) | ||||||
| { | { | ||||||
| 	free(filter_options->filter_spec); | 	size_t sub; | ||||||
|  |  | ||||||
|  | 	if (!filter_options) | ||||||
|  | 		return; | ||||||
|  | 	string_list_clear(&filter_options->filter_spec, /*free_util=*/0); | ||||||
| 	free(filter_options->sparse_oid_value); | 	free(filter_options->sparse_oid_value); | ||||||
|  | 	for (sub = 0; sub < filter_options->sub_nr; sub++) | ||||||
|  | 		list_objects_filter_release(&filter_options->sub[sub]); | ||||||
|  | 	free(filter_options->sub); | ||||||
| 	memset(filter_options, 0, sizeof(*filter_options)); | 	memset(filter_options, 0, sizeof(*filter_options)); | ||||||
| } | } | ||||||
|  |  | ||||||
| void partial_clone_register( | void partial_clone_register( | ||||||
| 	const char *remote, | 	const char *remote, | ||||||
| 	const struct list_objects_filter_options *filter_options) | 	struct list_objects_filter_options *filter_options) | ||||||
| { | { | ||||||
| 	char *cfg_name; | 	char *cfg_name; | ||||||
| 	char *filter_name; | 	char *filter_name; | ||||||
|  | @ -168,7 +349,9 @@ void partial_clone_register( | ||||||
| 	 * the default for subsequent fetches from this remote. | 	 * the default for subsequent fetches from this remote. | ||||||
| 	 */ | 	 */ | ||||||
| 	filter_name = xstrfmt("remote.%s.partialclonefilter", remote); | 	filter_name = xstrfmt("remote.%s.partialclonefilter", remote); | ||||||
| 	git_config_set(filter_name, filter_options->filter_spec); | 	/* NEEDSWORK: 'expand' result leaking??? */ | ||||||
|  | 	git_config_set(filter_name, | ||||||
|  | 		       expand_list_objects_filter_spec(filter_options)); | ||||||
| 	free(filter_name); | 	free(filter_name); | ||||||
|  |  | ||||||
| 	/* Make sure the config info are reset */ | 	/* Make sure the config info are reset */ | ||||||
|  | @ -180,12 +363,18 @@ void partial_clone_get_default_filter_spec( | ||||||
| 	const char *remote) | 	const char *remote) | ||||||
| { | { | ||||||
| 	struct promisor_remote *promisor = promisor_remote_find(remote); | 	struct promisor_remote *promisor = promisor_remote_find(remote); | ||||||
|  | 	struct strbuf errbuf = STRBUF_INIT; | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * Parse default value, but silently ignore it if it is invalid. | 	 * Parse default value, but silently ignore it if it is invalid. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (promisor) | 	if (!promisor) | ||||||
|  | 		return; | ||||||
|  |  | ||||||
|  | 	string_list_append(&filter_options->filter_spec, | ||||||
|  | 			   promisor->partial_clone_filter); | ||||||
| 	gently_parse_list_objects_filter(filter_options, | 	gently_parse_list_objects_filter(filter_options, | ||||||
| 					 promisor->partial_clone_filter, | 					 promisor->partial_clone_filter, | ||||||
| 						 NULL); | 					 &errbuf); | ||||||
|  | 	strbuf_release(&errbuf); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,7 @@ | ||||||
| #define LIST_OBJECTS_FILTER_OPTIONS_H | #define LIST_OBJECTS_FILTER_OPTIONS_H | ||||||
|  |  | ||||||
| #include "parse-options.h" | #include "parse-options.h" | ||||||
| #include "strbuf.h" | #include "string-list.h" | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * The list of defined filters for list-objects. |  * The list of defined filters for list-objects. | ||||||
|  | @ -13,6 +13,7 @@ enum list_objects_filter_choice { | ||||||
| 	LOFC_BLOB_LIMIT, | 	LOFC_BLOB_LIMIT, | ||||||
| 	LOFC_TREE_DEPTH, | 	LOFC_TREE_DEPTH, | ||||||
| 	LOFC_SPARSE_OID, | 	LOFC_SPARSE_OID, | ||||||
|  | 	LOFC_COMBINE, | ||||||
| 	LOFC__COUNT /* must be last */ | 	LOFC__COUNT /* must be last */ | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -23,8 +24,10 @@ struct list_objects_filter_options { | ||||||
| 	 * commands that launch filtering sub-processes, or for communication | 	 * commands that launch filtering sub-processes, or for communication | ||||||
| 	 * over the network, don't use this value; use the result of | 	 * over the network, don't use this value; use the result of | ||||||
| 	 * expand_list_objects_filter_spec() instead. | 	 * expand_list_objects_filter_spec() instead. | ||||||
|  | 	 * To get the raw filter spec given by the user, use the result of | ||||||
|  | 	 * list_objects_filter_spec(). | ||||||
| 	 */ | 	 */ | ||||||
| 	char *filter_spec; | 	struct string_list filter_spec; | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * 'choice' is determined by parsing the filter-spec.  This indicates | 	 * 'choice' is determined by parsing the filter-spec.  This indicates | ||||||
|  | @ -38,19 +41,40 @@ struct list_objects_filter_options { | ||||||
| 	unsigned int no_filter : 1; | 	unsigned int no_filter : 1; | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * Parsed values (fields) from within the filter-spec.  These are | 	 * BEGIN choice-specific parsed values from within the filter-spec. Only | ||||||
| 	 * choice-specific; not all values will be defined for any given | 	 * some values will be defined for any given choice. | ||||||
| 	 * choice. |  | ||||||
| 	 */ | 	 */ | ||||||
|  |  | ||||||
| 	struct object_id *sparse_oid_value; | 	struct object_id *sparse_oid_value; | ||||||
| 	unsigned long blob_limit_value; | 	unsigned long blob_limit_value; | ||||||
| 	unsigned long tree_exclude_depth; | 	unsigned long tree_exclude_depth; | ||||||
|  |  | ||||||
|  | 	/* LOFC_COMBINE values */ | ||||||
|  |  | ||||||
|  | 	/* This array contains all the subfilters which this filter combines. */ | ||||||
|  | 	size_t sub_nr, sub_alloc; | ||||||
|  | 	struct list_objects_filter_options *sub; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * END choice-specific parsed values. | ||||||
|  | 	 */ | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /* Normalized command line arguments */ | /* Normalized command line arguments */ | ||||||
| #define CL_ARG__FILTER "filter" | #define CL_ARG__FILTER "filter" | ||||||
|  |  | ||||||
| int parse_list_objects_filter( | void list_objects_filter_die_if_populated( | ||||||
|  | 	struct list_objects_filter_options *filter_options); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Parses the filter spec string given by arg and either (1) simply places the | ||||||
|  |  * result in filter_options if it is not yet populated or (2) combines it with | ||||||
|  |  * the filter already in filter_options if it is already populated. In the case | ||||||
|  |  * of (2), the filter specs are combined as if specified with 'combine:'. | ||||||
|  |  * | ||||||
|  |  * Dies and prints a user-facing message if an error occurs. | ||||||
|  |  */ | ||||||
|  | void parse_list_objects_filter( | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	const char *arg); | 	const char *arg); | ||||||
|  |  | ||||||
|  | @ -65,13 +89,22 @@ int opt_parse_list_objects_filter(const struct option *opt, | ||||||
| /* | /* | ||||||
|  * Translates abbreviated numbers in the filter's filter_spec into their |  * Translates abbreviated numbers in the filter's filter_spec into their | ||||||
|  * fully-expanded forms (e.g., "limit:blob=1k" becomes "limit:blob=1024"). |  * fully-expanded forms (e.g., "limit:blob=1k" becomes "limit:blob=1024"). | ||||||
|  |  * Returns a string owned by the list_objects_filter_options object. | ||||||
|  * |  * | ||||||
|  * This form should be used instead of the raw filter_spec field when |  * This form should be used instead of the raw list_objects_filter_spec() | ||||||
|  * communicating with a remote process or subprocess. |  * value when communicating with a remote process or subprocess. | ||||||
|  */ |  */ | ||||||
| void expand_list_objects_filter_spec( | const char *expand_list_objects_filter_spec( | ||||||
| 	const struct list_objects_filter_options *filter, | 	struct list_objects_filter_options *filter); | ||||||
| 	struct strbuf *expanded_spec); |  | ||||||
|  | /* | ||||||
|  |  * Returns the filter spec string more or less in the form as the user | ||||||
|  |  * entered it. This form of the filter_spec can be used in user-facing | ||||||
|  |  * messages.  Returns a string owned by the list_objects_filter_options | ||||||
|  |  * object. | ||||||
|  |  */ | ||||||
|  | const char *list_objects_filter_spec( | ||||||
|  | 	struct list_objects_filter_options *filter); | ||||||
|  |  | ||||||
| void list_objects_filter_release( | void list_objects_filter_release( | ||||||
| 	struct list_objects_filter_options *filter_options); | 	struct list_objects_filter_options *filter_options); | ||||||
|  | @ -85,7 +118,7 @@ static inline void list_objects_filter_set_no_filter( | ||||||
|  |  | ||||||
| void partial_clone_register( | void partial_clone_register( | ||||||
| 	const char *remote, | 	const char *remote, | ||||||
| 	const struct list_objects_filter_options *filter_options); | 	struct list_objects_filter_options *filter_options); | ||||||
| void partial_clone_get_default_filter_spec( | void partial_clone_get_default_filter_spec( | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	const char *remote); | 	const char *remote); | ||||||
|  |  | ||||||
|  | @ -26,11 +26,46 @@ | ||||||
|  */ |  */ | ||||||
| #define FILTER_SHOWN_BUT_REVISIT (1<<21) | #define FILTER_SHOWN_BUT_REVISIT (1<<21) | ||||||
|  |  | ||||||
| /* | struct subfilter { | ||||||
|  * A filter for list-objects to omit ALL blobs from the traversal. | 	struct filter *filter; | ||||||
|  * And to OPTIONALLY collect a list of the omitted OIDs. | 	struct oidset seen; | ||||||
|  | 	struct oidset omits; | ||||||
|  | 	struct object_id skip_tree; | ||||||
|  | 	unsigned is_skipping_tree : 1; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct filter { | ||||||
|  | 	enum list_objects_filter_result (*filter_object_fn)( | ||||||
|  | 		struct repository *r, | ||||||
|  | 		enum list_objects_filter_situation filter_situation, | ||||||
|  | 		struct object *obj, | ||||||
|  | 		const char *pathname, | ||||||
|  | 		const char *filename, | ||||||
|  | 		struct oidset *omits, | ||||||
|  | 		void *filter_data); | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Optional. If this function is supplied and the filter needs | ||||||
|  | 	 * to collect omits, then this function is called once before | ||||||
|  | 	 * free_fn is called. | ||||||
|  | 	 * | ||||||
|  | 	 * This is required because the following two conditions hold: | ||||||
|  | 	 * | ||||||
|  | 	 *   a. A tree filter can add and remove objects as an object | ||||||
|  | 	 *      graph is traversed. | ||||||
|  | 	 *   b. A combine filter's omit set is the union of all its | ||||||
|  | 	 *      subfilters, which may include tree: filters. | ||||||
|  | 	 * | ||||||
|  | 	 * As such, the omits sets must be separate sets, and can only | ||||||
|  | 	 * be unioned after the traversal is completed. | ||||||
| 	 */ | 	 */ | ||||||
| struct filter_blobs_none_data { | 	void (*finalize_omits_fn)(struct oidset *omits, void *filter_data); | ||||||
|  |  | ||||||
|  | 	void (*free_fn)(void *filter_data); | ||||||
|  |  | ||||||
|  | 	void *filter_data; | ||||||
|  |  | ||||||
|  | 	/* If non-NULL, the filter collects a list of the omitted OIDs here. */ | ||||||
| 	struct oidset *omits; | 	struct oidset *omits; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -40,10 +75,9 @@ static enum list_objects_filter_result filter_blobs_none( | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	const char *pathname, | 	const char *pathname, | ||||||
| 	const char *filename, | 	const char *filename, | ||||||
|  | 	struct oidset *omits, | ||||||
| 	void *filter_data_) | 	void *filter_data_) | ||||||
| { | { | ||||||
| 	struct filter_blobs_none_data *filter_data = filter_data_; |  | ||||||
|  |  | ||||||
| 	switch (filter_situation) { | 	switch (filter_situation) { | ||||||
| 	default: | 	default: | ||||||
| 		BUG("unknown filter_situation: %d", filter_situation); | 		BUG("unknown filter_situation: %d", filter_situation); | ||||||
|  | @ -61,24 +95,18 @@ static enum list_objects_filter_result filter_blobs_none( | ||||||
| 		assert(obj->type == OBJ_BLOB); | 		assert(obj->type == OBJ_BLOB); | ||||||
| 		assert((obj->flags & SEEN) == 0); | 		assert((obj->flags & SEEN) == 0); | ||||||
|  |  | ||||||
| 		if (filter_data->omits) | 		if (omits) | ||||||
| 			oidset_insert(filter_data->omits, &obj->oid); | 			oidset_insert(omits, &obj->oid); | ||||||
| 		return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ | 		return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| static void *filter_blobs_none__init( | static void filter_blobs_none__init( | ||||||
| 	struct oidset *omitted, |  | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	filter_object_fn *filter_fn, | 	struct filter *filter) | ||||||
| 	filter_free_fn *filter_free_fn) |  | ||||||
| { | { | ||||||
| 	struct filter_blobs_none_data *d = xcalloc(1, sizeof(*d)); | 	filter->filter_object_fn = filter_blobs_none; | ||||||
| 	d->omits = omitted; | 	filter->free_fn = free; | ||||||
|  |  | ||||||
| 	*filter_fn = filter_blobs_none; |  | ||||||
| 	*filter_free_fn = free; |  | ||||||
| 	return d; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  | @ -86,8 +114,6 @@ static void *filter_blobs_none__init( | ||||||
|  * Can OPTIONALLY collect a list of the omitted OIDs. |  * Can OPTIONALLY collect a list of the omitted OIDs. | ||||||
|  */ |  */ | ||||||
| struct filter_trees_depth_data { | struct filter_trees_depth_data { | ||||||
| 	struct oidset *omits; |  | ||||||
|  |  | ||||||
| 	/* | 	/* | ||||||
| 	 * Maps trees to the minimum depth at which they were seen. It is not | 	 * Maps trees to the minimum depth at which they were seen. It is not | ||||||
| 	 * necessary to re-traverse a tree at deeper or equal depths than it has | 	 * necessary to re-traverse a tree at deeper or equal depths than it has | ||||||
|  | @ -110,16 +136,16 @@ struct seen_map_entry { | ||||||
| /* Returns 1 if the oid was in the omits set before it was invoked. */ | /* Returns 1 if the oid was in the omits set before it was invoked. */ | ||||||
| static int filter_trees_update_omits( | static int filter_trees_update_omits( | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	struct filter_trees_depth_data *filter_data, | 	struct oidset *omits, | ||||||
| 	int include_it) | 	int include_it) | ||||||
| { | { | ||||||
| 	if (!filter_data->omits) | 	if (!omits) | ||||||
| 		return 0; | 		return 0; | ||||||
|  |  | ||||||
| 	if (include_it) | 	if (include_it) | ||||||
| 		return oidset_remove(filter_data->omits, &obj->oid); | 		return oidset_remove(omits, &obj->oid); | ||||||
| 	else | 	else | ||||||
| 		return oidset_insert(filter_data->omits, &obj->oid); | 		return oidset_insert(omits, &obj->oid); | ||||||
| } | } | ||||||
|  |  | ||||||
| static enum list_objects_filter_result filter_trees_depth( | static enum list_objects_filter_result filter_trees_depth( | ||||||
|  | @ -128,6 +154,7 @@ static enum list_objects_filter_result filter_trees_depth( | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	const char *pathname, | 	const char *pathname, | ||||||
| 	const char *filename, | 	const char *filename, | ||||||
|  | 	struct oidset *omits, | ||||||
| 	void *filter_data_) | 	void *filter_data_) | ||||||
| { | { | ||||||
| 	struct filter_trees_depth_data *filter_data = filter_data_; | 	struct filter_trees_depth_data *filter_data = filter_data_; | ||||||
|  | @ -152,7 +179,7 @@ static enum list_objects_filter_result filter_trees_depth( | ||||||
| 		return LOFR_ZERO; | 		return LOFR_ZERO; | ||||||
|  |  | ||||||
| 	case LOFS_BLOB: | 	case LOFS_BLOB: | ||||||
| 		filter_trees_update_omits(obj, filter_data, include_it); | 		filter_trees_update_omits(obj, omits, include_it); | ||||||
| 		return include_it ? LOFR_MARK_SEEN | LOFR_DO_SHOW : LOFR_ZERO; | 		return include_it ? LOFR_MARK_SEEN | LOFR_DO_SHOW : LOFR_ZERO; | ||||||
|  |  | ||||||
| 	case LOFS_BEGIN_TREE: | 	case LOFS_BEGIN_TREE: | ||||||
|  | @ -173,12 +200,12 @@ static enum list_objects_filter_result filter_trees_depth( | ||||||
| 			filter_res = LOFR_SKIP_TREE; | 			filter_res = LOFR_SKIP_TREE; | ||||||
| 		} else { | 		} else { | ||||||
| 			int been_omitted = filter_trees_update_omits( | 			int been_omitted = filter_trees_update_omits( | ||||||
| 				obj, filter_data, include_it); | 				obj, omits, include_it); | ||||||
| 			seen_info->depth = filter_data->current_depth; | 			seen_info->depth = filter_data->current_depth; | ||||||
|  |  | ||||||
| 			if (include_it) | 			if (include_it) | ||||||
| 				filter_res = LOFR_DO_SHOW; | 				filter_res = LOFR_DO_SHOW; | ||||||
| 			else if (filter_data->omits && !been_omitted) | 			else if (omits && !been_omitted) | ||||||
| 				/* | 				/* | ||||||
| 				 * Must update omit information of children | 				 * Must update omit information of children | ||||||
| 				 * recursively; they have not been omitted yet. | 				 * recursively; they have not been omitted yet. | ||||||
|  | @ -201,21 +228,18 @@ static void filter_trees_free(void *filter_data) { | ||||||
| 	free(d); | 	free(d); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void *filter_trees_depth__init( | static void filter_trees_depth__init( | ||||||
| 	struct oidset *omitted, |  | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	filter_object_fn *filter_fn, | 	struct filter *filter) | ||||||
| 	filter_free_fn *filter_free_fn) |  | ||||||
| { | { | ||||||
| 	struct filter_trees_depth_data *d = xcalloc(1, sizeof(*d)); | 	struct filter_trees_depth_data *d = xcalloc(1, sizeof(*d)); | ||||||
| 	d->omits = omitted; |  | ||||||
| 	oidmap_init(&d->seen_at_depth, 0); | 	oidmap_init(&d->seen_at_depth, 0); | ||||||
| 	d->exclude_depth = filter_options->tree_exclude_depth; | 	d->exclude_depth = filter_options->tree_exclude_depth; | ||||||
| 	d->current_depth = 0; | 	d->current_depth = 0; | ||||||
|  |  | ||||||
| 	*filter_fn = filter_trees_depth; | 	filter->filter_data = d; | ||||||
| 	*filter_free_fn = filter_trees_free; | 	filter->filter_object_fn = filter_trees_depth; | ||||||
| 	return d; | 	filter->free_fn = filter_trees_free; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  | @ -223,7 +247,6 @@ static void *filter_trees_depth__init( | ||||||
|  * And to OPTIONALLY collect a list of the omitted OIDs. |  * And to OPTIONALLY collect a list of the omitted OIDs. | ||||||
|  */ |  */ | ||||||
| struct filter_blobs_limit_data { | struct filter_blobs_limit_data { | ||||||
| 	struct oidset *omits; |  | ||||||
| 	unsigned long max_bytes; | 	unsigned long max_bytes; | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -233,6 +256,7 @@ static enum list_objects_filter_result filter_blobs_limit( | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	const char *pathname, | 	const char *pathname, | ||||||
| 	const char *filename, | 	const char *filename, | ||||||
|  | 	struct oidset *omits, | ||||||
| 	void *filter_data_) | 	void *filter_data_) | ||||||
| { | { | ||||||
| 	struct filter_blobs_limit_data *filter_data = filter_data_; | 	struct filter_blobs_limit_data *filter_data = filter_data_; | ||||||
|  | @ -270,30 +294,27 @@ static enum list_objects_filter_result filter_blobs_limit( | ||||||
| 		if (object_length < filter_data->max_bytes) | 		if (object_length < filter_data->max_bytes) | ||||||
| 			goto include_it; | 			goto include_it; | ||||||
|  |  | ||||||
| 		if (filter_data->omits) | 		if (omits) | ||||||
| 			oidset_insert(filter_data->omits, &obj->oid); | 			oidset_insert(omits, &obj->oid); | ||||||
| 		return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ | 		return LOFR_MARK_SEEN; /* but not LOFR_DO_SHOW (hard omit) */ | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| include_it: | include_it: | ||||||
| 	if (filter_data->omits) | 	if (omits) | ||||||
| 		oidset_remove(filter_data->omits, &obj->oid); | 		oidset_remove(omits, &obj->oid); | ||||||
| 	return LOFR_MARK_SEEN | LOFR_DO_SHOW; | 	return LOFR_MARK_SEEN | LOFR_DO_SHOW; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void *filter_blobs_limit__init( | static void filter_blobs_limit__init( | ||||||
| 	struct oidset *omitted, |  | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	filter_object_fn *filter_fn, | 	struct filter *filter) | ||||||
| 	filter_free_fn *filter_free_fn) |  | ||||||
| { | { | ||||||
| 	struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d)); | 	struct filter_blobs_limit_data *d = xcalloc(1, sizeof(*d)); | ||||||
| 	d->omits = omitted; |  | ||||||
| 	d->max_bytes = filter_options->blob_limit_value; | 	d->max_bytes = filter_options->blob_limit_value; | ||||||
|  |  | ||||||
| 	*filter_fn = filter_blobs_limit; | 	filter->filter_data = d; | ||||||
| 	*filter_free_fn = free; | 	filter->filter_object_fn = filter_blobs_limit; | ||||||
| 	return d; | 	filter->free_fn = free; | ||||||
| } | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  | @ -326,7 +347,6 @@ struct frame { | ||||||
| }; | }; | ||||||
|  |  | ||||||
| struct filter_sparse_data { | struct filter_sparse_data { | ||||||
| 	struct oidset *omits; |  | ||||||
| 	struct exclude_list el; | 	struct exclude_list el; | ||||||
|  |  | ||||||
| 	size_t nr, alloc; | 	size_t nr, alloc; | ||||||
|  | @ -339,6 +359,7 @@ static enum list_objects_filter_result filter_sparse( | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	const char *pathname, | 	const char *pathname, | ||||||
| 	const char *filename, | 	const char *filename, | ||||||
|  | 	struct oidset *omits, | ||||||
| 	void *filter_data_) | 	void *filter_data_) | ||||||
| { | { | ||||||
| 	struct filter_sparse_data *filter_data = filter_data_; | 	struct filter_sparse_data *filter_data = filter_data_; | ||||||
|  | @ -420,8 +441,8 @@ static enum list_objects_filter_result filter_sparse( | ||||||
| 		if (val < 0) | 		if (val < 0) | ||||||
| 			val = frame->defval; | 			val = frame->defval; | ||||||
| 		if (val > 0) { | 		if (val > 0) { | ||||||
| 			if (filter_data->omits) | 			if (omits) | ||||||
| 				oidset_remove(filter_data->omits, &obj->oid); | 				oidset_remove(omits, &obj->oid); | ||||||
| 			return LOFR_MARK_SEEN | LOFR_DO_SHOW; | 			return LOFR_MARK_SEEN | LOFR_DO_SHOW; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | @ -435,8 +456,8 @@ static enum list_objects_filter_result filter_sparse( | ||||||
| 		 * Leave the LOFR_ bits unset so that if the blob appears | 		 * Leave the LOFR_ bits unset so that if the blob appears | ||||||
| 		 * again in the traversal, we will be asked again. | 		 * again in the traversal, we will be asked again. | ||||||
| 		 */ | 		 */ | ||||||
| 		if (filter_data->omits) | 		if (omits) | ||||||
| 			oidset_insert(filter_data->omits, &obj->oid); | 			oidset_insert(omits, &obj->oid); | ||||||
|  |  | ||||||
| 		/* | 		/* | ||||||
| 		 * Remember that at least 1 blob in this tree was | 		 * Remember that at least 1 blob in this tree was | ||||||
|  | @ -456,14 +477,11 @@ static void filter_sparse_free(void *filter_data) | ||||||
| 	free(d); | 	free(d); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void *filter_sparse_oid__init( | static void filter_sparse_oid__init( | ||||||
| 	struct oidset *omitted, |  | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	filter_object_fn *filter_fn, | 	struct filter *filter) | ||||||
| 	filter_free_fn *filter_free_fn) |  | ||||||
| { | { | ||||||
| 	struct filter_sparse_data *d = xcalloc(1, sizeof(*d)); | 	struct filter_sparse_data *d = xcalloc(1, sizeof(*d)); | ||||||
| 	d->omits = omitted; |  | ||||||
| 	if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value, | 	if (add_excludes_from_blob_to_list(filter_options->sparse_oid_value, | ||||||
| 					   NULL, 0, &d->el) < 0) | 					   NULL, 0, &d->el) < 0) | ||||||
| 		die("could not load filter specification"); | 		die("could not load filter specification"); | ||||||
|  | @ -473,16 +491,147 @@ static void *filter_sparse_oid__init( | ||||||
| 	d->array_frame[d->nr].child_prov_omit = 0; | 	d->array_frame[d->nr].child_prov_omit = 0; | ||||||
| 	d->nr++; | 	d->nr++; | ||||||
|  |  | ||||||
| 	*filter_fn = filter_sparse; | 	filter->filter_data = d; | ||||||
| 	*filter_free_fn = filter_sparse_free; | 	filter->filter_object_fn = filter_sparse; | ||||||
| 	return d; | 	filter->free_fn = filter_sparse_free; | ||||||
| } | } | ||||||
|  |  | ||||||
| typedef void *(*filter_init_fn)( | /* A filter which only shows objects shown by all sub-filters. */ | ||||||
| 	struct oidset *omitted, | struct combine_filter_data { | ||||||
|  | 	struct subfilter *sub; | ||||||
|  | 	size_t nr; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static enum list_objects_filter_result process_subfilter( | ||||||
|  | 	struct repository *r, | ||||||
|  | 	enum list_objects_filter_situation filter_situation, | ||||||
|  | 	struct object *obj, | ||||||
|  | 	const char *pathname, | ||||||
|  | 	const char *filename, | ||||||
|  | 	struct subfilter *sub) | ||||||
|  | { | ||||||
|  | 	enum list_objects_filter_result result; | ||||||
|  |  | ||||||
|  | 	/* | ||||||
|  | 	 * Check and update is_skipping_tree before oidset_contains so | ||||||
|  | 	 * that is_skipping_tree gets unset even when the object is | ||||||
|  | 	 * marked as seen.  As of this writing, no filter uses | ||||||
|  | 	 * LOFR_MARK_SEEN on trees that also uses LOFR_SKIP_TREE, so the | ||||||
|  | 	 * ordering is only theoretically important. Be cautious if you | ||||||
|  | 	 * change the order of the below checks and more filters have | ||||||
|  | 	 * been added! | ||||||
|  | 	 */ | ||||||
|  | 	if (sub->is_skipping_tree) { | ||||||
|  | 		if (filter_situation == LOFS_END_TREE && | ||||||
|  | 		    oideq(&obj->oid, &sub->skip_tree)) | ||||||
|  | 			sub->is_skipping_tree = 0; | ||||||
|  | 		else | ||||||
|  | 			return LOFR_ZERO; | ||||||
|  | 	} | ||||||
|  | 	if (oidset_contains(&sub->seen, &obj->oid)) | ||||||
|  | 		return LOFR_ZERO; | ||||||
|  |  | ||||||
|  | 	result = list_objects_filter__filter_object( | ||||||
|  | 		r, filter_situation, obj, pathname, filename, sub->filter); | ||||||
|  |  | ||||||
|  | 	if (result & LOFR_MARK_SEEN) | ||||||
|  | 		oidset_insert(&sub->seen, &obj->oid); | ||||||
|  |  | ||||||
|  | 	if (result & LOFR_SKIP_TREE) { | ||||||
|  | 		sub->is_skipping_tree = 1; | ||||||
|  | 		sub->skip_tree = obj->oid; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static enum list_objects_filter_result filter_combine( | ||||||
|  | 	struct repository *r, | ||||||
|  | 	enum list_objects_filter_situation filter_situation, | ||||||
|  | 	struct object *obj, | ||||||
|  | 	const char *pathname, | ||||||
|  | 	const char *filename, | ||||||
|  | 	struct oidset *omits, | ||||||
|  | 	void *filter_data) | ||||||
|  | { | ||||||
|  | 	struct combine_filter_data *d = filter_data; | ||||||
|  | 	enum list_objects_filter_result combined_result = | ||||||
|  | 		LOFR_DO_SHOW | LOFR_MARK_SEEN | LOFR_SKIP_TREE; | ||||||
|  | 	size_t sub; | ||||||
|  |  | ||||||
|  | 	for (sub = 0; sub < d->nr; sub++) { | ||||||
|  | 		enum list_objects_filter_result sub_result = process_subfilter( | ||||||
|  | 			r, filter_situation, obj, pathname, filename, | ||||||
|  | 			&d->sub[sub]); | ||||||
|  | 		if (!(sub_result & LOFR_DO_SHOW)) | ||||||
|  | 			combined_result &= ~LOFR_DO_SHOW; | ||||||
|  | 		if (!(sub_result & LOFR_MARK_SEEN)) | ||||||
|  | 			combined_result &= ~LOFR_MARK_SEEN; | ||||||
|  | 		if (!d->sub[sub].is_skipping_tree) | ||||||
|  | 			combined_result &= ~LOFR_SKIP_TREE; | ||||||
|  | 	} | ||||||
|  |  | ||||||
|  | 	return combined_result; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void filter_combine__free(void *filter_data) | ||||||
|  | { | ||||||
|  | 	struct combine_filter_data *d = filter_data; | ||||||
|  | 	size_t sub; | ||||||
|  | 	for (sub = 0; sub < d->nr; sub++) { | ||||||
|  | 		list_objects_filter__free(d->sub[sub].filter); | ||||||
|  | 		oidset_clear(&d->sub[sub].seen); | ||||||
|  | 		if (d->sub[sub].omits.set.size) | ||||||
|  | 			BUG("expected oidset to be cleared already"); | ||||||
|  | 	} | ||||||
|  | 	free(d->sub); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void add_all(struct oidset *dest, struct oidset *src) { | ||||||
|  | 	struct oidset_iter iter; | ||||||
|  | 	struct object_id *src_oid; | ||||||
|  |  | ||||||
|  | 	oidset_iter_init(src, &iter); | ||||||
|  | 	while ((src_oid = oidset_iter_next(&iter)) != NULL) | ||||||
|  | 		oidset_insert(dest, src_oid); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void filter_combine__finalize_omits( | ||||||
|  | 	struct oidset *omits, | ||||||
|  | 	void *filter_data) | ||||||
|  | { | ||||||
|  | 	struct combine_filter_data *d = filter_data; | ||||||
|  | 	size_t sub; | ||||||
|  |  | ||||||
|  | 	for (sub = 0; sub < d->nr; sub++) { | ||||||
|  | 		add_all(omits, &d->sub[sub].omits); | ||||||
|  | 		oidset_clear(&d->sub[sub].omits); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  |  | ||||||
|  | static void filter_combine__init( | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options, | ||||||
| 	filter_object_fn *filter_fn, | 	struct filter* filter) | ||||||
| 	filter_free_fn *filter_free_fn); | { | ||||||
|  | 	struct combine_filter_data *d = xcalloc(1, sizeof(*d)); | ||||||
|  | 	size_t sub; | ||||||
|  |  | ||||||
|  | 	d->nr = filter_options->sub_nr; | ||||||
|  | 	d->sub = xcalloc(d->nr, sizeof(*d->sub)); | ||||||
|  | 	for (sub = 0; sub < d->nr; sub++) | ||||||
|  | 		d->sub[sub].filter = list_objects_filter__init( | ||||||
|  | 			filter->omits ? &d->sub[sub].omits : NULL, | ||||||
|  | 			&filter_options->sub[sub]); | ||||||
|  |  | ||||||
|  | 	filter->filter_data = d; | ||||||
|  | 	filter->filter_object_fn = filter_combine; | ||||||
|  | 	filter->free_fn = filter_combine__free; | ||||||
|  | 	filter->finalize_omits_fn = filter_combine__finalize_omits; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | typedef void (*filter_init_fn)( | ||||||
|  | 	struct list_objects_filter_options *filter_options, | ||||||
|  | 	struct filter *filter); | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Must match "enum list_objects_filter_choice". |  * Must match "enum list_objects_filter_choice". | ||||||
|  | @ -493,14 +642,14 @@ static filter_init_fn s_filters[] = { | ||||||
| 	filter_blobs_limit__init, | 	filter_blobs_limit__init, | ||||||
| 	filter_trees_depth__init, | 	filter_trees_depth__init, | ||||||
| 	filter_sparse_oid__init, | 	filter_sparse_oid__init, | ||||||
|  | 	filter_combine__init, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| void *list_objects_filter__init( | struct filter *list_objects_filter__init( | ||||||
| 	struct oidset *omitted, | 	struct oidset *omitted, | ||||||
| 	struct list_objects_filter_options *filter_options, | 	struct list_objects_filter_options *filter_options) | ||||||
| 	filter_object_fn *filter_fn, |  | ||||||
| 	filter_free_fn *filter_free_fn) |  | ||||||
| { | { | ||||||
|  | 	struct filter *filter; | ||||||
| 	filter_init_fn init_fn; | 	filter_init_fn init_fn; | ||||||
|  |  | ||||||
| 	assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT); | 	assert((sizeof(s_filters) / sizeof(s_filters[0])) == LOFC__COUNT); | ||||||
|  | @ -510,10 +659,44 @@ void *list_objects_filter__init( | ||||||
| 		    filter_options->choice); | 		    filter_options->choice); | ||||||
|  |  | ||||||
| 	init_fn = s_filters[filter_options->choice]; | 	init_fn = s_filters[filter_options->choice]; | ||||||
| 	if (init_fn) | 	if (!init_fn) | ||||||
| 		return init_fn(omitted, filter_options, |  | ||||||
| 			       filter_fn, filter_free_fn); |  | ||||||
| 	*filter_fn = NULL; |  | ||||||
| 	*filter_free_fn = NULL; |  | ||||||
| 		return NULL; | 		return NULL; | ||||||
|  |  | ||||||
|  | 	filter = xcalloc(1, sizeof(*filter)); | ||||||
|  | 	filter->omits = omitted; | ||||||
|  | 	init_fn(filter_options, filter); | ||||||
|  | 	return filter; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum list_objects_filter_result list_objects_filter__filter_object( | ||||||
|  | 	struct repository *r, | ||||||
|  | 	enum list_objects_filter_situation filter_situation, | ||||||
|  | 	struct object *obj, | ||||||
|  | 	const char *pathname, | ||||||
|  | 	const char *filename, | ||||||
|  | 	struct filter *filter) | ||||||
|  | { | ||||||
|  | 	if (filter && (obj->flags & NOT_USER_GIVEN)) | ||||||
|  | 		return filter->filter_object_fn(r, filter_situation, obj, | ||||||
|  | 						pathname, filename, | ||||||
|  | 						filter->omits, | ||||||
|  | 						filter->filter_data); | ||||||
|  | 	/* | ||||||
|  | 	 * No filter is active or user gave object explicitly. In this case, | ||||||
|  | 	 * always show the object (except when LOFS_END_TREE, since this tree | ||||||
|  | 	 * had already been shown when LOFS_BEGIN_TREE). | ||||||
|  | 	 */ | ||||||
|  | 	if (filter_situation == LOFS_END_TREE) | ||||||
|  | 		return 0; | ||||||
|  | 	return LOFR_MARK_SEEN | LOFR_DO_SHOW; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void list_objects_filter__free(struct filter *filter) | ||||||
|  | { | ||||||
|  | 	if (!filter) | ||||||
|  | 		return; | ||||||
|  | 	if (filter->finalize_omits_fn && filter->omits) | ||||||
|  | 		filter->finalize_omits_fn(filter->omits, filter->filter_data); | ||||||
|  | 	filter->free_fn(filter->filter_data); | ||||||
|  | 	free(filter); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -60,30 +60,36 @@ enum list_objects_filter_situation { | ||||||
| 	LOFS_BLOB | 	LOFS_BLOB | ||||||
| }; | }; | ||||||
|  |  | ||||||
| typedef enum list_objects_filter_result (*filter_object_fn)( | struct filter; | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Constructor for the set of defined list-objects filters. | ||||||
|  |  * The `omitted` set is optional. It is populated with objects that the | ||||||
|  |  * filter excludes. This set should not be considered finalized until | ||||||
|  |  * after list_objects_filter__free is called on the returned `struct | ||||||
|  |  * filter *`. | ||||||
|  |  */ | ||||||
|  | struct filter *list_objects_filter__init( | ||||||
|  | 	struct oidset *omitted, | ||||||
|  | 	struct list_objects_filter_options *filter_options); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Lets `filter` decide how to handle the `obj`. If `filter` is NULL, this | ||||||
|  |  * function behaves as expected if no filter is configured: all objects are | ||||||
|  |  * included. | ||||||
|  |  */ | ||||||
|  | enum list_objects_filter_result list_objects_filter__filter_object( | ||||||
| 	struct repository *r, | 	struct repository *r, | ||||||
| 	enum list_objects_filter_situation filter_situation, | 	enum list_objects_filter_situation filter_situation, | ||||||
| 	struct object *obj, | 	struct object *obj, | ||||||
| 	const char *pathname, | 	const char *pathname, | ||||||
| 	const char *filename, | 	const char *filename, | ||||||
| 	void *filter_data); | 	struct filter *filter); | ||||||
|  |  | ||||||
| typedef void (*filter_free_fn)(void *filter_data); |  | ||||||
|  |  | ||||||
| /* | /* | ||||||
|  * Constructor for the set of defined list-objects filters. |  * Destroys `filter` and finalizes the `omitted` set, if present. Does | ||||||
|  * Returns a generic "void *filter_data". |  * nothing if `filter` is null. | ||||||
|  * |  | ||||||
|  * The returned "filter_fn" will be used by traverse_commit_list() |  | ||||||
|  * to filter the results. |  | ||||||
|  * |  | ||||||
|  * The returned "filter_free_fn" is a destructor for the |  | ||||||
|  * filter_data. |  | ||||||
|  */ |  */ | ||||||
| void *list_objects_filter__init( | void list_objects_filter__free(struct filter *filter); | ||||||
| 	struct oidset *omitted, |  | ||||||
| 	struct list_objects_filter_options *filter_options, |  | ||||||
| 	filter_object_fn *filter_fn, |  | ||||||
| 	filter_free_fn *filter_free_fn); |  | ||||||
|  |  | ||||||
| #endif /* LIST_OBJECTS_FILTER_H */ | #endif /* LIST_OBJECTS_FILTER_H */ | ||||||
|  |  | ||||||
|  | @ -18,8 +18,7 @@ struct traversal_context { | ||||||
| 	show_object_fn show_object; | 	show_object_fn show_object; | ||||||
| 	show_commit_fn show_commit; | 	show_commit_fn show_commit; | ||||||
| 	void *show_data; | 	void *show_data; | ||||||
| 	filter_object_fn filter_fn; | 	struct filter *filter; | ||||||
| 	void *filter_data; |  | ||||||
| }; | }; | ||||||
|  |  | ||||||
| static void process_blob(struct traversal_context *ctx, | static void process_blob(struct traversal_context *ctx, | ||||||
|  | @ -29,7 +28,7 @@ static void process_blob(struct traversal_context *ctx, | ||||||
| { | { | ||||||
| 	struct object *obj = &blob->object; | 	struct object *obj = &blob->object; | ||||||
| 	size_t pathlen; | 	size_t pathlen; | ||||||
| 	enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW; | 	enum list_objects_filter_result r; | ||||||
|  |  | ||||||
| 	if (!ctx->revs->blob_objects) | 	if (!ctx->revs->blob_objects) | ||||||
| 		return; | 		return; | ||||||
|  | @ -54,11 +53,10 @@ static void process_blob(struct traversal_context *ctx, | ||||||
|  |  | ||||||
| 	pathlen = path->len; | 	pathlen = path->len; | ||||||
| 	strbuf_addstr(path, name); | 	strbuf_addstr(path, name); | ||||||
| 	if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) | 	r = list_objects_filter__filter_object(ctx->revs->repo, | ||||||
| 		r = ctx->filter_fn(ctx->revs->repo, |  | ||||||
| 					       LOFS_BLOB, obj, | 					       LOFS_BLOB, obj, | ||||||
| 					       path->buf, &path->buf[pathlen], | 					       path->buf, &path->buf[pathlen], | ||||||
| 				   ctx->filter_data); | 					       ctx->filter); | ||||||
| 	if (r & LOFR_MARK_SEEN) | 	if (r & LOFR_MARK_SEEN) | ||||||
| 		obj->flags |= SEEN; | 		obj->flags |= SEEN; | ||||||
| 	if (r & LOFR_DO_SHOW) | 	if (r & LOFR_DO_SHOW) | ||||||
|  | @ -157,7 +155,7 @@ static void process_tree(struct traversal_context *ctx, | ||||||
| 	struct object *obj = &tree->object; | 	struct object *obj = &tree->object; | ||||||
| 	struct rev_info *revs = ctx->revs; | 	struct rev_info *revs = ctx->revs; | ||||||
| 	int baselen = base->len; | 	int baselen = base->len; | ||||||
| 	enum list_objects_filter_result r = LOFR_MARK_SEEN | LOFR_DO_SHOW; | 	enum list_objects_filter_result r; | ||||||
| 	int failed_parse; | 	int failed_parse; | ||||||
|  |  | ||||||
| 	if (!revs->tree_objects) | 	if (!revs->tree_objects) | ||||||
|  | @ -186,11 +184,10 @@ static void process_tree(struct traversal_context *ctx, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	strbuf_addstr(base, name); | 	strbuf_addstr(base, name); | ||||||
| 	if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) | 	r = list_objects_filter__filter_object(ctx->revs->repo, | ||||||
| 		r = ctx->filter_fn(ctx->revs->repo, |  | ||||||
| 					       LOFS_BEGIN_TREE, obj, | 					       LOFS_BEGIN_TREE, obj, | ||||||
| 					       base->buf, &base->buf[baselen], | 					       base->buf, &base->buf[baselen], | ||||||
| 				   ctx->filter_data); | 					       ctx->filter); | ||||||
| 	if (r & LOFR_MARK_SEEN) | 	if (r & LOFR_MARK_SEEN) | ||||||
| 		obj->flags |= SEEN; | 		obj->flags |= SEEN; | ||||||
| 	if (r & LOFR_DO_SHOW) | 	if (r & LOFR_DO_SHOW) | ||||||
|  | @ -203,16 +200,14 @@ static void process_tree(struct traversal_context *ctx, | ||||||
| 	else if (!failed_parse) | 	else if (!failed_parse) | ||||||
| 		process_tree_contents(ctx, tree, base); | 		process_tree_contents(ctx, tree, base); | ||||||
|  |  | ||||||
| 	if ((obj->flags & NOT_USER_GIVEN) && ctx->filter_fn) { | 	r = list_objects_filter__filter_object(ctx->revs->repo, | ||||||
| 		r = ctx->filter_fn(ctx->revs->repo, |  | ||||||
| 					       LOFS_END_TREE, obj, | 					       LOFS_END_TREE, obj, | ||||||
| 					       base->buf, &base->buf[baselen], | 					       base->buf, &base->buf[baselen], | ||||||
| 				   ctx->filter_data); | 					       ctx->filter); | ||||||
| 	if (r & LOFR_MARK_SEEN) | 	if (r & LOFR_MARK_SEEN) | ||||||
| 		obj->flags |= SEEN; | 		obj->flags |= SEEN; | ||||||
| 	if (r & LOFR_DO_SHOW) | 	if (r & LOFR_DO_SHOW) | ||||||
| 		ctx->show_object(obj, base->buf, ctx->show_data); | 		ctx->show_object(obj, base->buf, ctx->show_data); | ||||||
| 	} |  | ||||||
|  |  | ||||||
| 	strbuf_setlen(base, baselen); | 	strbuf_setlen(base, baselen); | ||||||
| 	free_tree_buffer(tree); | 	free_tree_buffer(tree); | ||||||
|  | @ -402,8 +397,7 @@ void traverse_commit_list(struct rev_info *revs, | ||||||
| 	ctx.show_commit = show_commit; | 	ctx.show_commit = show_commit; | ||||||
| 	ctx.show_object = show_object; | 	ctx.show_object = show_object; | ||||||
| 	ctx.show_data = show_data; | 	ctx.show_data = show_data; | ||||||
| 	ctx.filter_fn = NULL; | 	ctx.filter = NULL; | ||||||
| 	ctx.filter_data = NULL; |  | ||||||
| 	do_traverse(&ctx); | 	do_traverse(&ctx); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -416,17 +410,12 @@ void traverse_commit_list_filtered( | ||||||
| 	struct oidset *omitted) | 	struct oidset *omitted) | ||||||
| { | { | ||||||
| 	struct traversal_context ctx; | 	struct traversal_context ctx; | ||||||
| 	filter_free_fn filter_free_fn = NULL; |  | ||||||
|  |  | ||||||
| 	ctx.revs = revs; | 	ctx.revs = revs; | ||||||
| 	ctx.show_object = show_object; | 	ctx.show_object = show_object; | ||||||
| 	ctx.show_commit = show_commit; | 	ctx.show_commit = show_commit; | ||||||
| 	ctx.show_data = show_data; | 	ctx.show_data = show_data; | ||||||
| 	ctx.filter_fn = NULL; | 	ctx.filter = list_objects_filter__init(omitted, filter_options); | ||||||
|  |  | ||||||
| 	ctx.filter_data = list_objects_filter__init(omitted, filter_options, |  | ||||||
| 						    &ctx.filter_fn, &filter_free_fn); |  | ||||||
| 	do_traverse(&ctx); | 	do_traverse(&ctx); | ||||||
| 	if (ctx.filter_data && filter_free_fn) | 	list_objects_filter__free(ctx.filter); | ||||||
| 		filter_free_fn(ctx.filter_data); |  | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										15
									
								
								strbuf.c
								
								
								
								
							
							
						
						
									
										15
									
								
								strbuf.c
								
								
								
								
							|  | @ -774,8 +774,10 @@ void strbuf_addstr_xml_quoted(struct strbuf *buf, const char *s) | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
| static int is_rfc3986_reserved(char ch) | int is_rfc3986_reserved_or_unreserved(char ch) | ||||||
| { | { | ||||||
|  | 	if (is_rfc3986_unreserved(ch)) | ||||||
|  | 		return 1; | ||||||
| 	switch (ch) { | 	switch (ch) { | ||||||
| 		case '!': case '*': case '\'': case '(': case ')': case ';': | 		case '!': case '*': case '\'': case '(': case ')': case ';': | ||||||
| 		case ':': case '@': case '&': case '=': case '+': case '$': | 		case ':': case '@': case '&': case '=': case '+': case '$': | ||||||
|  | @ -785,20 +787,19 @@ static int is_rfc3986_reserved(char ch) | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
| static int is_rfc3986_unreserved(char ch) | int is_rfc3986_unreserved(char ch) | ||||||
| { | { | ||||||
| 	return isalnum(ch) || | 	return isalnum(ch) || | ||||||
| 		ch == '-' || ch == '_' || ch == '.' || ch == '~'; | 		ch == '-' || ch == '_' || ch == '.' || ch == '~'; | ||||||
| } | } | ||||||
|  |  | ||||||
| static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len, | static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len, | ||||||
| 				 int reserved) | 				 char_predicate allow_unencoded_fn) | ||||||
| { | { | ||||||
| 	strbuf_grow(sb, len); | 	strbuf_grow(sb, len); | ||||||
| 	while (len--) { | 	while (len--) { | ||||||
| 		char ch = *s++; | 		char ch = *s++; | ||||||
| 		if (is_rfc3986_unreserved(ch) || | 		if (allow_unencoded_fn(ch)) | ||||||
| 		    (!reserved && is_rfc3986_reserved(ch))) |  | ||||||
| 			strbuf_addch(sb, ch); | 			strbuf_addch(sb, ch); | ||||||
| 		else | 		else | ||||||
| 			strbuf_addf(sb, "%%%02x", (unsigned char)ch); | 			strbuf_addf(sb, "%%%02x", (unsigned char)ch); | ||||||
|  | @ -806,9 +807,9 @@ static void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len, | ||||||
| } | } | ||||||
|  |  | ||||||
| void strbuf_addstr_urlencode(struct strbuf *sb, const char *s, | void strbuf_addstr_urlencode(struct strbuf *sb, const char *s, | ||||||
| 			     int reserved) | 			     char_predicate allow_unencoded_fn) | ||||||
| { | { | ||||||
| 	strbuf_add_urlencode(sb, s, strlen(s), reserved); | 	strbuf_add_urlencode(sb, s, strlen(s), allow_unencoded_fn); | ||||||
| } | } | ||||||
|  |  | ||||||
| static void strbuf_humanise(struct strbuf *buf, off_t bytes, | static void strbuf_humanise(struct strbuf *buf, off_t bytes, | ||||||
|  |  | ||||||
							
								
								
									
										7
									
								
								strbuf.h
								
								
								
								
							
							
						
						
									
										7
									
								
								strbuf.h
								
								
								
								
							|  | @ -672,8 +672,13 @@ void strbuf_branchname(struct strbuf *sb, const char *name, | ||||||
|  */ |  */ | ||||||
| int strbuf_check_branch_ref(struct strbuf *sb, const char *name); | int strbuf_check_branch_ref(struct strbuf *sb, const char *name); | ||||||
|  |  | ||||||
|  | typedef int (*char_predicate)(char ch); | ||||||
|  |  | ||||||
|  | int is_rfc3986_unreserved(char ch); | ||||||
|  | int is_rfc3986_reserved_or_unreserved(char ch); | ||||||
|  |  | ||||||
| void strbuf_addstr_urlencode(struct strbuf *sb, const char *name, | void strbuf_addstr_urlencode(struct strbuf *sb, const char *name, | ||||||
| 			     int reserved); | 			     char_predicate allow_unencoded_fn); | ||||||
|  |  | ||||||
| __attribute__((format (printf,1,2))) | __attribute__((format (printf,1,2))) | ||||||
| int printf_ln(const char *fmt, ...); | int printf_ln(const char *fmt, ...); | ||||||
|  |  | ||||||
|  | @ -208,6 +208,25 @@ test_expect_success 'use fsck before and after manually fetching a missing subtr | ||||||
| 	test_cmp unique_types.expected unique_types.observed | 	test_cmp unique_types.expected unique_types.observed | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'implicitly construct combine: filter with repeated flags' ' | ||||||
|  | 	GIT_TRACE=$(pwd)/trace git clone --bare \ | ||||||
|  | 		--filter=blob:none --filter=tree:1 \ | ||||||
|  | 		"file://$(pwd)/srv.bare" pc2 && | ||||||
|  | 	grep "trace:.* git pack-objects .*--filter=combine:blob:none+tree:1" \ | ||||||
|  | 		trace && | ||||||
|  | 	git -C pc2 rev-list --objects --missing=allow-any HEAD >objects && | ||||||
|  |  | ||||||
|  | 	# We should have gotten some root trees. | ||||||
|  | 	grep " $" objects && | ||||||
|  | 	# Should not have gotten any non-root trees or blobs. | ||||||
|  | 	! grep " ." objects && | ||||||
|  |  | ||||||
|  | 	xargs -n 1 git -C pc2 cat-file -t <objects >types && | ||||||
|  | 	sort -u types >unique_types.actual && | ||||||
|  | 	test_write_lines commit tree >unique_types.expected && | ||||||
|  | 	test_cmp unique_types.expected unique_types.actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' ' | test_expect_success 'partial clone fetches blobs pointed to by refs even if normally filtered out' ' | ||||||
| 	rm -rf src dst && | 	rm -rf src dst && | ||||||
| 	git init src && | 	git init src && | ||||||
|  |  | ||||||
|  | @ -278,7 +278,19 @@ test_expect_success 'verify skipping tree iteration when not collecting omits' ' | ||||||
| 	test_line_count = 2 actual && | 	test_line_count = 2 actual && | ||||||
|  |  | ||||||
| 	# Make sure no other trees were considered besides the root. | 	# Make sure no other trees were considered besides the root. | ||||||
| 	! grep "Skipping contents of tree [^.]" filter_trace | 	! grep "Skipping contents of tree [^.]" filter_trace && | ||||||
|  |  | ||||||
|  | 	# Try this again with "combine:". If both sub-filters are skipping | ||||||
|  | 	# trees, the composite filter should also skip trees. This is not | ||||||
|  | 	# important unless the user does combine:tree:X+tree:Y or another filter | ||||||
|  | 	# besides "tree:" is implemented in the future which can skip trees. | ||||||
|  | 	GIT_TRACE=1 git -C r3 rev-list \ | ||||||
|  | 		--objects --filter=combine:tree:1+tree:3 HEAD 2>filter_trace && | ||||||
|  |  | ||||||
|  | 	# Only skip the dir1/ tree, which is shared between the two commits. | ||||||
|  | 	grep "Skipping contents of tree " filter_trace >actual && | ||||||
|  | 	test_write_lines "Skipping contents of tree dir1/..." >expected && | ||||||
|  | 	test_cmp expected actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
| # Test tree:# filters. | # Test tree:# filters. | ||||||
|  | @ -330,6 +342,148 @@ test_expect_success 'verify tree:3 includes everything expected' ' | ||||||
| 	test_line_count = 10 actual | 	test_line_count = 10 actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... for a simple combination' ' | ||||||
|  | 	git -C r3 rev-list --objects --filter=combine:tree:2+blob:none HEAD \ | ||||||
|  | 		>actual && | ||||||
|  |  | ||||||
|  | 	expect_has HEAD "" && | ||||||
|  | 	expect_has HEAD~1 "" && | ||||||
|  | 	expect_has HEAD dir1 && | ||||||
|  |  | ||||||
|  | 	# There are also 2 commit objects | ||||||
|  | 	test_line_count = 5 actual && | ||||||
|  |  | ||||||
|  | 	cp actual expected && | ||||||
|  |  | ||||||
|  | 	# Try again using repeated --filter - this is equivalent to a manual | ||||||
|  | 	# combine with "combine:...+..." | ||||||
|  | 	git -C r3 rev-list --objects --filter=combine:tree:2 \ | ||||||
|  | 		--filter=blob:none HEAD >actual && | ||||||
|  |  | ||||||
|  | 	test_cmp expected actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... with URL encoding' ' | ||||||
|  | 	git -C r3 rev-list --objects \ | ||||||
|  | 		--filter=combine:tree%3a2+blob:%6Eon%65 HEAD >actual && | ||||||
|  |  | ||||||
|  | 	expect_has HEAD "" && | ||||||
|  | 	expect_has HEAD~1 "" && | ||||||
|  | 	expect_has HEAD dir1 && | ||||||
|  |  | ||||||
|  | 	# There are also 2 commit objects | ||||||
|  | 	test_line_count = 5 actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | expect_invalid_filter_spec () { | ||||||
|  | 	spec="$1" && | ||||||
|  | 	err="$2" && | ||||||
|  |  | ||||||
|  | 	test_must_fail git -C r3 rev-list --objects --filter="$spec" HEAD \ | ||||||
|  | 		>actual 2>actual_stderr && | ||||||
|  | 	test_must_be_empty actual && | ||||||
|  | 	test_i18ngrep "$err" actual_stderr | ||||||
|  | } | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... while URL-encoding things that should not be' ' | ||||||
|  | 	expect_invalid_filter_spec combine%3Atree:2+blob:none \ | ||||||
|  | 		"invalid filter-spec" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine: with nothing after the :' ' | ||||||
|  | 	expect_invalid_filter_spec combine: "expected something after combine:" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'parse error in first sub-filter in combine:' ' | ||||||
|  | 	expect_invalid_filter_spec combine:tree:asdf+blob:none \ | ||||||
|  | 		"expected .tree:<depth>." | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... with non-encoded reserved chars' ' | ||||||
|  | 	expect_invalid_filter_spec combine:tree:2+sparse:@xyz \ | ||||||
|  | 		"must escape char in sub-filter-spec: .@." && | ||||||
|  | 	expect_invalid_filter_spec combine:tree:2+sparse:\` \ | ||||||
|  | 		"must escape char in sub-filter-spec: .\`." && | ||||||
|  | 	expect_invalid_filter_spec combine:tree:2+sparse:~abc \ | ||||||
|  | 		"must escape char in sub-filter-spec: .\~." | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'validate err msg for "combine:<valid-filter>+"' ' | ||||||
|  | 	expect_invalid_filter_spec combine:tree:2+ "expected .tree:<depth>." | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... with edge-case hex digits: Ff Aa 0 9' ' | ||||||
|  | 	git -C r3 rev-list --objects --filter="combine:tree:2+bl%6Fb:n%6fne" \ | ||||||
|  | 		HEAD >actual && | ||||||
|  | 	test_line_count = 5 actual && | ||||||
|  | 	git -C r3 rev-list --objects --filter="combine:tree%3A2+blob%3anone" \ | ||||||
|  | 		HEAD >actual && | ||||||
|  | 	test_line_count = 5 actual && | ||||||
|  | 	git -C r3 rev-list --objects --filter="combine:tree:%30" HEAD >actual && | ||||||
|  | 	test_line_count = 2 actual && | ||||||
|  | 	git -C r3 rev-list --objects --filter="combine:tree:%39+blob:none" \ | ||||||
|  | 		HEAD >actual && | ||||||
|  | 	test_line_count = 5 actual | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'add sparse pattern blobs whose paths have reserved chars' ' | ||||||
|  | 	cp r3/pattern r3/pattern1+renamed% && | ||||||
|  | 	cp r3/pattern "r3/p;at%ter+n" && | ||||||
|  | 	cp r3/pattern r3/^~pattern && | ||||||
|  | 	git -C r3 add pattern1+renamed% "p;at%ter+n" ^~pattern && | ||||||
|  | 	git -C r3 commit -m "add sparse pattern files with reserved chars" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'combine:... with more than two sub-filters' ' | ||||||
|  | 	git -C r3 rev-list --objects \ | ||||||
|  | 		--filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern \ | ||||||
|  | 		HEAD >actual && | ||||||
|  |  | ||||||
|  | 	expect_has HEAD "" && | ||||||
|  | 	expect_has HEAD~1 "" && | ||||||
|  | 	expect_has HEAD~2 "" && | ||||||
|  | 	expect_has HEAD dir1 && | ||||||
|  | 	expect_has HEAD dir1/sparse1 && | ||||||
|  | 	expect_has HEAD dir1/sparse2 && | ||||||
|  |  | ||||||
|  | 	# Should also have 3 commits | ||||||
|  | 	test_line_count = 9 actual && | ||||||
|  |  | ||||||
|  | 	# Try again, this time making sure the last sub-filter is only | ||||||
|  | 	# URL-decoded once. | ||||||
|  | 	cp actual expect && | ||||||
|  |  | ||||||
|  | 	git -C r3 rev-list --objects \ | ||||||
|  | 		--filter=combine:tree:3+blob:limit=40+sparse:oid=master:pattern1%2brenamed%25 \ | ||||||
|  | 		HEAD >actual && | ||||||
|  | 	test_cmp expect actual && | ||||||
|  |  | ||||||
|  | 	# Use the same composite filter again, but with a pattern file name that | ||||||
|  | 	# requires encoding multiple characters, and use implicit filter | ||||||
|  | 	# combining. | ||||||
|  | 	test_when_finished "rm -f trace1" && | ||||||
|  | 	GIT_TRACE=$(pwd)/trace1 git -C r3 rev-list --objects \ | ||||||
|  | 		--filter=tree:3 --filter=blob:limit=40 \ | ||||||
|  | 		--filter=sparse:oid="master:p;at%ter+n" \ | ||||||
|  | 		HEAD >actual && | ||||||
|  |  | ||||||
|  | 	test_cmp expect actual && | ||||||
|  | 	grep "Add to combine filter-spec: sparse:oid=master:p%3bat%25ter%2bn" \ | ||||||
|  | 		trace1 && | ||||||
|  |  | ||||||
|  | 	# Repeat the above test, but this time, the characters to encode are in | ||||||
|  | 	# the LHS of the combined filter. | ||||||
|  | 	test_when_finished "rm -f trace2" && | ||||||
|  | 	GIT_TRACE=$(pwd)/trace2 git -C r3 rev-list --objects \ | ||||||
|  | 		--filter=sparse:oid=master:^~pattern \ | ||||||
|  | 		--filter=tree:3 --filter=blob:limit=40 \ | ||||||
|  | 		HEAD >actual && | ||||||
|  |  | ||||||
|  | 	test_cmp expect actual && | ||||||
|  | 	grep "Add to combine filter-spec: sparse:oid=master:%5e%7epattern" \ | ||||||
|  | 		trace2 | ||||||
|  | ' | ||||||
|  |  | ||||||
| # Test provisional omit collection logic with a repo that has objects appearing | # Test provisional omit collection logic with a repo that has objects appearing | ||||||
| # at multiple depths - first deeper than the filter's threshold, then shallow. | # at multiple depths - first deeper than the filter's threshold, then shallow. | ||||||
|  |  | ||||||
|  | @ -373,6 +527,37 @@ test_expect_success 'verify skipping tree iteration when collecting omits' ' | ||||||
| 	test_cmp expect actual | 	test_cmp expect actual | ||||||
| ' | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'setup r5' ' | ||||||
|  | 	git init r5 && | ||||||
|  | 	mkdir -p r5/subdir && | ||||||
|  |  | ||||||
|  | 	echo 1     >r5/short-root          && | ||||||
|  | 	echo 12345 >r5/long-root           && | ||||||
|  | 	echo a     >r5/subdir/short-subdir && | ||||||
|  | 	echo abcde >r5/subdir/long-subdir  && | ||||||
|  |  | ||||||
|  | 	git -C r5 add short-root long-root subdir && | ||||||
|  | 	git -C r5 commit -m "commit msg" | ||||||
|  | ' | ||||||
|  |  | ||||||
|  | test_expect_success 'verify collecting omits in combined: filter' ' | ||||||
|  | 	# Note that this test guards against the naive implementation of simply | ||||||
|  | 	# giving both filters the same "omits" set and expecting it to | ||||||
|  | 	# automatically merge them. | ||||||
|  | 	git -C r5 rev-list --objects --quiet --filter-print-omitted \ | ||||||
|  | 		--filter=combine:tree:2+blob:limit=3 HEAD >actual && | ||||||
|  |  | ||||||
|  | 	# Expect 0 trees/commits, 3 blobs omitted (all blobs except short-root) | ||||||
|  | 	omitted_1=$(echo 12345 | git hash-object --stdin) && | ||||||
|  | 	omitted_2=$(echo a     | git hash-object --stdin) && | ||||||
|  | 	omitted_3=$(echo abcde | git hash-object --stdin) && | ||||||
|  |  | ||||||
|  | 	grep ~$omitted_1 actual && | ||||||
|  | 	grep ~$omitted_2 actual && | ||||||
|  | 	grep ~$omitted_3 actual && | ||||||
|  | 	test_line_count = 3 actual | ||||||
|  | ' | ||||||
|  |  | ||||||
| # Test tree:<depth> where a tree is iterated to twice - once where a subentry is | # Test tree:<depth> where a tree is iterated to twice - once where a subentry is | ||||||
| # too deep to be included, and again where the blob inside it is shallow enough | # too deep to be included, and again where the blob inside it is shallow enough | ||||||
| # to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we | # to be included. This makes sure we don't use LOFR_MARK_SEEN incorrectly (we | ||||||
|  | @ -441,11 +626,4 @@ test_expect_success 'expand blob limit in protocol' ' | ||||||
| 	grep "blob:limit=1024" trace | 	grep "blob:limit=1024" trace | ||||||
| ' | ' | ||||||
|  |  | ||||||
| test_expect_success 'expand tree depth limit in protocol' ' |  | ||||||
| 	GIT_TRACE_PACKET="$(pwd)/tree_trace" git -c protocol.version=2 clone \ |  | ||||||
| 		--filter=tree:0k "file://$(pwd)/r2" tree && |  | ||||||
| 	! grep "tree:0k" tree_trace && |  | ||||||
| 	grep "tree:0" tree_trace |  | ||||||
| ' |  | ||||||
|  |  | ||||||
| test_done | test_done | ||||||
|  |  | ||||||
|  | @ -682,13 +682,9 @@ static int fetch(struct transport *transport, | ||||||
| 		set_helper_option(transport, "update-shallow", "true"); | 		set_helper_option(transport, "update-shallow", "true"); | ||||||
|  |  | ||||||
| 	if (data->transport_options.filter_options.choice) { | 	if (data->transport_options.filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = expand_list_objects_filter_spec( | ||||||
| 		expand_list_objects_filter_spec( | 			&data->transport_options.filter_options); | ||||||
| 			&data->transport_options.filter_options, | 		set_helper_option(transport, "filter", spec); | ||||||
| 			&expanded_filter_spec); |  | ||||||
| 		set_helper_option(transport, "filter", |  | ||||||
| 				  expanded_filter_spec.buf); |  | ||||||
| 		strbuf_release(&expanded_filter_spec); |  | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	if (data->transport_options.negotiation_tips) | 	if (data->transport_options.negotiation_tips) | ||||||
|  |  | ||||||
|  | @ -224,6 +224,7 @@ static int set_git_option(struct git_transport_options *opts, | ||||||
| 		opts->no_dependents = !!value; | 		opts->no_dependents = !!value; | ||||||
| 		return 0; | 		return 0; | ||||||
| 	} else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) { | 	} else if (!strcmp(name, TRANS_OPT_LIST_OBJECTS_FILTER)) { | ||||||
|  | 		list_objects_filter_die_if_populated(&opts->filter_options); | ||||||
| 		parse_list_objects_filter(&opts->filter_options, value); | 		parse_list_objects_filter(&opts->filter_options, value); | ||||||
| 		return 0; | 		return 0; | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -140,18 +140,17 @@ static void create_pack_file(const struct object_array *have_obj, | ||||||
| 		argv_array_push(&pack_objects.args, "--delta-base-offset"); | 		argv_array_push(&pack_objects.args, "--delta-base-offset"); | ||||||
| 	if (use_include_tag) | 	if (use_include_tag) | ||||||
| 		argv_array_push(&pack_objects.args, "--include-tag"); | 		argv_array_push(&pack_objects.args, "--include-tag"); | ||||||
| 	if (filter_options.filter_spec) { | 	if (filter_options.choice) { | ||||||
| 		struct strbuf expanded_filter_spec = STRBUF_INIT; | 		const char *spec = | ||||||
| 		expand_list_objects_filter_spec(&filter_options, | 			expand_list_objects_filter_spec(&filter_options); | ||||||
| 						&expanded_filter_spec); |  | ||||||
| 		if (pack_objects.use_shell) { | 		if (pack_objects.use_shell) { | ||||||
| 			struct strbuf buf = STRBUF_INIT; | 			struct strbuf buf = STRBUF_INIT; | ||||||
| 			sq_quote_buf(&buf, expanded_filter_spec.buf); | 			sq_quote_buf(&buf, spec); | ||||||
| 			argv_array_pushf(&pack_objects.args, "--filter=%s", buf.buf); | 			argv_array_pushf(&pack_objects.args, "--filter=%s", buf.buf); | ||||||
| 			strbuf_release(&buf); | 			strbuf_release(&buf); | ||||||
| 		} else { | 		} else { | ||||||
| 			argv_array_pushf(&pack_objects.args, "--filter=%s", | 			argv_array_pushf(&pack_objects.args, "--filter=%s", | ||||||
| 					 expanded_filter_spec.buf); | 					 spec); | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -884,6 +883,7 @@ static void receive_needs(struct packet_reader *reader, struct object_array *wan | ||||||
| 		if (skip_prefix(reader->line, "filter ", &arg)) { | 		if (skip_prefix(reader->line, "filter ", &arg)) { | ||||||
| 			if (!filter_capability_requested) | 			if (!filter_capability_requested) | ||||||
| 				die("git upload-pack: filtering capability not negotiated"); | 				die("git upload-pack: filtering capability not negotiated"); | ||||||
|  | 			list_objects_filter_die_if_populated(&filter_options); | ||||||
| 			parse_list_objects_filter(&filter_options, arg); | 			parse_list_objects_filter(&filter_options, arg); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
|  | @ -1305,6 +1305,7 @@ static void process_args(struct packet_reader *request, | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if (allow_filter && skip_prefix(arg, "filter ", &p)) { | 		if (allow_filter && skip_prefix(arg, "filter ", &p)) { | ||||||
|  | 			list_objects_filter_die_if_populated(&filter_options); | ||||||
| 			parse_list_objects_filter(&filter_options, p); | 			parse_list_objects_filter(&filter_options, p); | ||||||
| 			continue; | 			continue; | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
							
								
								
									
										6
									
								
								url.c
								
								
								
								
							
							
						
						
									
										6
									
								
								url.c
								
								
								
								
							|  | @ -86,6 +86,12 @@ char *url_decode_mem(const char *url, int len) | ||||||
| 	return url_decode_internal(&url, len, NULL, &out, 0); | 	return url_decode_internal(&url, len, NULL, &out, 0); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | char *url_percent_decode(const char *encoded) | ||||||
|  | { | ||||||
|  | 	struct strbuf out = STRBUF_INIT; | ||||||
|  | 	return url_decode_internal(&encoded, strlen(encoded), NULL, &out, 0); | ||||||
|  | } | ||||||
|  |  | ||||||
| char *url_decode_parameter_name(const char **query) | char *url_decode_parameter_name(const char **query) | ||||||
| { | { | ||||||
| 	struct strbuf out = STRBUF_INIT; | 	struct strbuf out = STRBUF_INIT; | ||||||
|  |  | ||||||
							
								
								
									
										8
									
								
								url.h
								
								
								
								
							
							
						
						
									
										8
									
								
								url.h
								
								
								
								
							|  | @ -7,6 +7,14 @@ int is_url(const char *url); | ||||||
| int is_urlschemechar(int first_flag, int ch); | int is_urlschemechar(int first_flag, int ch); | ||||||
| char *url_decode(const char *url); | char *url_decode(const char *url); | ||||||
| char *url_decode_mem(const char *url, int len); | char *url_decode_mem(const char *url, int len); | ||||||
|  |  | ||||||
|  | /* | ||||||
|  |  * Similar to the url_decode_{,mem} methods above, but doesn't assume there | ||||||
|  |  * is a scheme followed by a : at the start of the string. Instead, %-sequences | ||||||
|  |  * before any : are also parsed. | ||||||
|  |  */ | ||||||
|  | char *url_percent_decode(const char *encoded); | ||||||
|  |  | ||||||
| char *url_decode_parameter_name(const char **query); | char *url_decode_parameter_name(const char **query); | ||||||
| char *url_decode_parameter_value(const char **query); | char *url_decode_parameter_value(const char **query); | ||||||
|  |  | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano