Subject: Re: [PATCH v3 8/8] fetch: introduce machine-parseable "porcelain" output format

From: Patrick Steinhardt <[email-masked]>
Date: Tue, 9 May 2023 14:41:40 +0200

On Mon, May 08, 2023 at 04:42:46PM -0700, Glen Choo wrote:
> This version looks great! I only have minor comments.
> 
> Patrick Steinhardt <[email-masked]> writes:
> 
> > A notable ommission here is that the output format does not include the
> > remote from which a reference was fetched, which might be important
> > information especially in the context of multi-remote fetches. But as
> > such a format would require us to print the remote for every single
> > reference update due to parallelizable fetches it feels wasteful for the
> > most likely usecase, which is when fetching from a single remote.
> >
> > In a similar spirit, a second restriction is that this cannot be used
> > with `--recurse-submodules`. This is because any reference updates would
> > be ambiguous without also printing the repository in which the update
> > happens.
> >
> > Considering that both multi-remote and submodule fetches are rather
> > niche and likely not going to be useful for the majority of usecases
> > these omissions feel acceptable. If usecases for either of these come up
> > in the future though it is easy enough to add a new "porcelain-v2"
> > format that adds this information.
> 
> As a point of clarification, I think these options aren't niche in
> themselves, but they are more user-facing, so using them _in conjunction
> with_ --porcelain is probably pretty niche, so I think this is okay for
> now.

Yeah, that's what I indeed intended to say. Will clarify.

[snip]
> > @@ -1830,6 +1857,7 @@ struct parallel_fetch_state {
> >  	const char **argv;
> >  	struct string_list *remotes;
> >  	int next, result;
> > +	enum display_format format;
> >  };
> >  
> >  static int fetch_next_remote(struct child_process *cp,
> > @@ -1849,7 +1877,7 @@ static int fetch_next_remote(struct child_process *cp,
> >  	strvec_push(&cp->args, remote);
> >  	cp->git_cmd = 1;
> >  
> > -	if (verbosity >= 0)
> > +	if (verbosity >= 0 && state->format != DISPLAY_FORMAT_PORCELAIN)
> >  		printf(_("Fetching %s\n"), remote);
> >  
> >  	return 1;
> 
> Here and elsewhere, I wonder if it's clearer to name the variable
> "porcelain" and separate it from "enum display_format". Then we can
> check "porcelain" directly instead of using "format ==
> DISPLAY_FORMAT_PORCELAIN" as a proxy...

For now I'd like to keep this as-is: it's easier to keep track of this
when there is only a single variable that keeps track of the output
format. But if we were to add additional porcelain formats in the future
I agree that it would be nice to refactor the code as you propose.

I'd rather keep the simpler version for now though where we only have a
single state to worry about.

> > +static int opt_parse_porcelain(const struct option *opt, const char *arg, int unset)
> > +{
> > +	enum display_format *format = opt->value;
> > +	*format = DISPLAY_FORMAT_PORCELAIN;
> > +	return 0;
> > +}
> > +
> >  int cmd_fetch(int argc, const char **argv, const char *prefix)
> >  {
> >  	const char *bundle_uri;
> > @@ -2104,6 +2140,8 @@ int cmd_fetch(int argc, const char **argv, const char *prefix)
> >  			    PARSE_OPT_OPTARG, option_fetch_parse_recurse_submodules),
> >  		OPT_BOOL(0, "dry-run", &dry_run,
> >  			 N_("dry run")),
> > +		OPT_CALLBACK_F(0, "porcelain", &display_format, NULL, N_("machine-readable output"),
> > +			       PARSE_OPT_NOARG|PARSE_OPT_NONEG, opt_parse_porcelain),
> >  		OPT_BOOL(0, "write-fetch-head", &write_fetch_head,
> >  			 N_("write fetched references to the FETCH_HEAD file")),
> >  		OPT_BOOL('k', "keep", &keep, N_("keep downloaded pack")),
> 
> e.g. since we are reusing the "display_format" variable, we need to make
> sure we parse "--porcelain" after we read "fetch.output". I
> double-checked to make sure we were doing the right thing, though it
> would be nice to not have to worry about those sorts of things. This
> shouldn't hold up the series though.

In fact it is the other way round: we parse `--porcelain` first and then
only read "fetch.output" in the case where the `display_format` variable
is still set to `DISPLAY_FORMAT_UNKNOWN`.

So in the end there is no fragile order dependence here -- it would work
just the same regardless of whether we first parse command line options
or the configuration.

> > +test_expect_success 'fetch porcelain with multiple remotes' '
> > +	test_when_finished "rm -rf porcelain" &&
> > +
> > +	git clone . porcelain &&
> > +	git -C porcelain remote add second-remote "$PWD" &&
> > +	git -C porcelain fetch second-remote &&
> > +
> > +	test_commit --no-tag multi-commit &&
> > +	old_commit=$(git rev-parse HEAD~) &&
> > +	new_commit=$(git rev-parse HEAD) &&
> > +
> > +	cat >expect <<-EOF &&
> > +	  $old_commit $new_commit refs/remotes/origin/force-updated
> > +	  $old_commit $new_commit refs/remotes/second-remote/force-updated
> > +	EOF
> 
> The only thing in this test that relies on the previous test is that
> HEAD is pointing to "force-updated", and it's hard to tell where HEAD is
> since the previous test is so long. Could we create a new branch
> instead?

Makes sense, done.

Patrick