git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH v1 3/5] list-objects-filter: implement composite filters

From
JHJeff Hostetler <git@jeffhostetler.com>
Date
May 24, 2019, 21:01 UTC
Message-ID
<2b47d4b1-ea62-d59e-77e0-d95dfad084e0@jeffhostetler.com>
In-Reply-To
<1f95597eedc4c651868601c0ff7c4a4d97ca4457.1558484115.git.matvore@google.com>
On 5/21/2019 8:21 PM, Matthew DeVore wrote:
Show 10 quoted lines
> Allow combining filters such that only objects accepted by all filters
> are shown. The motivation for this is to allow getting directory
> listings without also fetching blobs. This can be done by combining
> blob:none with tree:<depth>. There are massive repositories that have
> larger-than-expected trees - even if you include only a single commit.
> 
> The current usage requires passing the filter to rev-list, or sending
> it over the wire, as:
> 
> 	combine:<FILTER1>+<FILTER2>

I must admit I'm not a fan of this syntax and the URL-encoding that it requires. I see that this was already discussed in the RFC version [1] last week, but I'll repeat it here.

I like the repeated used of the "--filter=<f_k>" command line option.

In the RFC version, there was discussion [2] of the wire format and the need to be backwards compatible with existing servers and so use the "combine:" syntax so that we only have a single filter line on the wire. Would it be better to have compliant servers advertise a "filters" (plural) capability in addition to the existing "filter" (singular) capability? Then the client would know that it could send a series of filter lines using the existing syntax. Likewise, if the "filters" capability was omitted, the client could error out without the extra round-trip.

[1] https://public-inbox.org/git/xmqqwoip3gp0.fsf@gitster-ct.c.googlers.com/ [2] https://public-inbox.org/git/1E174CAA-BD57-400B-A83B-4AABFAFBC04B@comcast.net/

[...]
Show 8 quoted lines
>   standard input when --stdin is used). <depth>=1 will include only the
>   tree and blobs which are referenced directly by a commit reachable from
>   <commit> or an explicitly-given object. <depth>=2 is like <depth>=1
>   while also including trees and blobs one more level removed from an
>   explicitly-given commit or tree.
> ++
> +The form '--filter=combine:<filter1>+<filter2>+...<filterN>' combines
> +several filters.

We are allowing an unlimited number of filters in the composition. In the code, the compose filter data has space for a LHS and RHS, so I'm assuming we're mapping

     --filter=f1 --filter=f2 --filter=f3 --filter=f4
or  --filter=combine:f1+f2+f3+f4
into basically
     (compose f1 (compose f2 (compose (f3 f4)))

I wonder if it would be easier to understand if we just built an array or linked list, but I'll read on.

Show 6 quoted lines
>                    Only objects which are accepted by every filter are
> +included. 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:
> +`~!@#$^&*()[]{}\;",<>?`+&#39;&#96;+ as well as all characters with ASCII code
> +&lt;= `0x20`, which includes space and newline.
[...]
Show 84 quoted lines
> diff --git a/list-objects-filter.c b/list-objects-filter.c
> index 8e8616b9b8..b97277a46f 100644
> --- a/list-objects-filter.c
> +++ b/list-objects-filter.c
> @@ -453,34 +453,148 @@ static void filter_sparse_path__init(
>   
>   	ALLOC_GROW(d->array_frame, d->nr + 1, d->alloc);
>   	d->array_frame[d->nr].defval = 0; /* default to include */
>   	d->array_frame[d->nr].child_prov_omit = 0;
>   
>   	ctx->filter_fn = filter_sparse;
>   	ctx->free_fn = filter_sparse_free;
>   	ctx->data = d;
>   }
>   
> +struct filter_combine_data {
> +	/* sub[0] corresponds to lhs, sub[1] to rhs. */
> +	struct {
> +		struct filter_context ctx;
> +		struct oidset seen;
> +		struct object_id skip_tree;
> +		unsigned is_skipping_tree : 1;
> +	} sub[2];
> +
> +	struct oidset rhs_omits;
> +};
> +
> +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_free(void *filter_data)
> +{
> +	struct filter_combine_data *d = filter_data;
> +	int i;
> +
> +	/* Anything omitted by rhs should be added to the overall omits set. */
> +	if (d->sub[0].ctx.omits)
> +		add_all(d->sub[0].ctx.omits, d->sub[1].ctx.omits);
> +
> +	for (i = 0; i < 2; i++) {
> +		list_objects_filter__release(&d->sub[i].ctx);
> +		oidset_clear(&d->sub[i].seen);
> +	}
> +	oidset_clear(&d->rhs_omits);
> +	free(d);
> +}
> +
> +static int should_delegate(enum list_objects_filter_situation filter_situation,
> +			   struct object *obj,
> +			   struct filter_combine_data *d,
> +			   int side)
> +{
> +	if (!d->sub[side].is_skipping_tree)
> +		return 1;
> +	if (filter_situation == LOFS_END_TREE &&
> +		oideq(&obj->oid, &d->sub[side].skip_tree)) {
> +		d->sub[side].is_skipping_tree = 0;
> +		return 1;
> +	}
> +	return 0;
> +}
> +
> +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 filter_context *ctx)
> +{
> +	struct filter_combine_data *d = ctx->data;
> +	enum list_objects_filter_result result[2];
> +	enum list_objects_filter_result combined_result = LOFR_ZERO;
> +	int i;
> +
> +	for (i = 0; i < 2; i++) {
> +		if (oidset_contains(&d->sub[i].seen, &obj->oid) ||
> +			!should_delegate(filter_situation, obj, d, i)) {

Should we swap the order of the terms in the || so that we always clear the d->sub[i].is_skipping_tree on LOFS_END_TREE ?

Show 10 quoted lines
> +			result[i] = LOFR_ZERO;
> +			continue;
> +		}
> +
> +		result[i] = d->sub[i].ctx.filter_fn(
> +			r, filter_situation, obj, pathname, filename,
> +			&d->sub[i].ctx);
> +
> +		if (result[i] & LOFR_MARK_SEEN)
> +			oidset_insert(&d->sub[i].seen, &obj->oid);

So filter[i] has said it never wants to show this object (hard omit). And the guard at the top of the loop will prevent future invocations from checking it again if the object is revisited.

> +
> +		if (result[i] & LOFR_SKIP_TREE) {
> +			d->sub[i].is_skipping_tree = 1;
> +			d->sub[i].skip_tree = obj->oid;

So this marks the tree object at the top of the skip as far as filter[i] is concerned.

Show 7 quoted lines
> +		}
> +	}
> +
> +	if ((result[0] & LOFR_DO_SHOW) && (result[1] & LOFR_DO_SHOW))
> +		combined_result |= LOFR_DO_SHOW;
> +	if (d->sub[0].is_skipping_tree && d->sub[1].is_skipping_tree)
> +		combined_result |= LOFR_SKIP_TREE;

Something about the above bothers me, but I can't quite say what it is.

Do we need to do:
     if ((result[0] & LOFR_MARK_SEEN) && (result[1] & LOFR_MARK_SEEN))
         combined_result |= LOFR_MARK_SEEN;
> +
> +	return combined_result;
> +}
[...]
I'm out of time now, will pick this up again next week.

Thanks Jeff

Previous: Matthew DeVoreNext: Junio C Hamano
Message 17 of 41 in “Filter combination”
  1. 0/5 Filter combinationMatthew DeVore, May 22, 2019
  2. 1/5 list-objects-filter: refactor into a context structMatthew DeVore, May 22, 2019
  3. Emily ShafferMay 24, 2019
  4. Matthew DeVoreMay 28, 2019
  5. list-objects-filter: merge filter data structsMatthew DeVore, May 28, 2019
  6. Junio C HamanoMay 29, 2019
  7. Jeff HostetlerMay 29, 2019
  8. Matthew DeVoreMay 29, 2019
  9. list-objects-filter: merge filter data structsMatthew DeVore, May 30, 2019
  10. Junio C HamanoMay 30, 2019
  11. Matthew DeVoreMay 30, 2019
  12. Matthew DeVoreMay 30, 2019
  13. 2/5 list-objects-filter-options: error is localizeableMatthew DeVore, May 22, 2019
  14. Emily ShafferMay 24, 2019
  15. Matthew DeVoreMay 28, 2019
  16. 3/5 list-objects-filter: implement composite filtersMatthew DeVore, May 22, 2019
  17. Jeff HostetlerMay 24, 2019
  18. Junio C HamanoMay 28, 2019
  19. Matthew DeVoreMay 29, 2019
  20. Jeff HostetlerMay 29, 2019
  21. Matthew DeVoreMay 29, 2019
  22. Jeff HostetlerMay 30, 2019
  23. Matthew DeVoreMay 31, 2019
  24. Jeff HostetlerJun 3, 2019
  25. Matthew DeVoreJun 1, 2019
  26. Emily ShafferMay 28, 2019
  27. Matthew DeVoreMay 31, 2019
  28. Jeff KingMay 31, 2019
  29. Matthew DeVoreJun 1, 2019
  30. Jeff KingJun 3, 2019
  31. Matthew DeVoreJun 3, 2019
  32. Jeff KingJun 4, 2019
  33. Matthew DeVoreJun 4, 2019
  34. Jeff KingJun 4, 2019
  35. Matthew DeVoreJun 4, 2019
  36. Jeff KingJun 4, 2019
  37. Matthew DeVoreJun 4, 2019
  38. Jeff KingJun 9, 2019
  39. 4/5 list-objects-filter-options: move error check upMatthew DeVore, May 22, 2019
  40. 5/5 list-objects-filter-options: allow mult. --filterMatthew DeVore, May 22, 2019
  41. Matthew DeVoreJun 6, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.