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

From: Matthew DeVore <[email-masked]>
Date: Fri, 31 May 2019 13:48:21 -0700

On Tue, May 28, 2019 at 02:53:59PM -0700, Emily Shaffer wrote:
> > +	} else if (skip_prefix(arg, "combine:", &v0)) {
> > +		int sub_parse_res = parse_combine_filter(
> > +			filter_options, v0, errbuf);
> > +		if (sub_parse_res)
> > +			return sub_parse_res;
> > +		return 0;
> 
> Couldn't the three lines above be said more succinctly as "return
> sub_parse_res;"?

Oh yes, that's much better. Don't even need the sub_parse_res variable.

> > +static int digit_value(int c, struct strbuf *errbuf) {
> > +	if (c >= '0' && c <= '9')
> > +		return c - '0';
> > +	if (c >= 'a' && c <= 'f')
> > +		return c - 'a' + 10;
> > +	if (c >= 'A' && c <= 'F')
> > +		return c - 'A' + 10;
> 
> I'm sure there's something I'm missing here. But why are you manually
> decoding hex instead of using strtol or sscanf or something?
> 

I'll have to give this a try. Thank you for the suggestion.

> > +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))
> > +			goto found_reserved;
> > +		c++;
> > +	}
> > +
> > +	return 0;
> > +
> > +found_reserved:
> 
> What's the value of doing this in a goto instead of embedded in the
> while loop?
> 

That's to reduce indentation. Note that if I "inlined" the goto logic in the
while loop, I'd get at least 5 tabs of indentation, and the error message would
be split across a couple lines.

> > +
> > +	result = gently_parse_list_objects_filter(filter_options->lhs,
> > +						  sub_specs[0]->buf,
> > +						  errbuf) ||
> > +		parse_combine_filter(filter_options->rhs,
> > +				      sub_specs[1]->buf,
> > +				      errbuf);
> 
> I guess you're recursing to combine filter 2 onto filter 1 which has
> been combined onto filter 0 here. But why not just use a list or array?
> 

I switched this to use an array at your and Jeff's proddings, and it's much
better now. Thanks! It will be in the next roll-up.

> >  
> >  void list_objects_filter_release(
> >  	struct list_objects_filter_options *filter_options)
> >  {
> > +	if (!filter_options)
> > +		return;
> >  	free(filter_options->filter_spec);
> >  	free(filter_options->sparse_oid_value);
> >  	free(filter_options->sparse_path_value);
> > +	list_objects_filter_release(filter_options->lhs);
> > +	free(filter_options->lhs);
> > +	list_objects_filter_release(filter_options->rhs);
> > +	free(filter_options->rhs);
> 
> Is there a reason that the free shouldn't be included in
> list_objects_filter_release()? Maybe this is a common style guideline
> I've missed, but it seems to me like I'd expect a magic memory cleanup
> function to do it all, and not leave it to me to free.
> 

Because there are a couple times the list_objects_filter_options struct is
allocated on the stack or inline in some other struct. This is similar to how
strbuf and other such utility structs are used.

> Jeff H had a comment about this too, but this seems unwieldy for >2
> filters. (I also personally don't like using set index to incidate
> lhs/rhs.) Why not an array of multiple `struct sub`? There's a macro
> utility to generate types and helpers for an array of arbitrary struct
> that may suit...
> 

This code is now cleaner that it's using an array.

> > +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++) {
> 
> I suppose your lhs and rhs are in sub[0] and sub[1] in part for the sake
> of this loop. But I think it would be easier to understand what is going
> on if you were to perform the loop contents in a helper function (as the
> name of the function would provide some more documentation).
> 

Agreed, this is how it will be done in the next roll-up.

> I see that you tested that >2 filters works okay. But by doing it the
> way you have it seems like you're setting up to need recursion all over
> the place to check against all the filters. I suppose I don't see the
> benefit of doing all this recursively, as compared to doing it
> iteratively.

Somehow, the recursive appraoch made more sense to me when I was first writing
the code. But using an array is nicer.