Re: [PATCH 4/9] update-ref: organize commands in an array
- From
Patrick Steinhardt <ps@pks.im>
- Date
- Mar 30, 2020, 08:05 UTC
- Message-ID
- <20200330080551.GA186019@ncase.pks.im>
- In-Reply-To
- <xmqqh7y9fq29.fsf@gitster.c.googlers.com>
On Fri, Mar 27, 2020 at 02:25:34PM -0700, Junio C Hamano wrote:
Show 67 quoted lines
> Patrick Steinhardt <ps@pks.im> writes:
>
> > +static const struct parse_cmd {
> > + const char *prefix;
> > + const char *(*fn)(struct ref_transaction *, struct strbuf *, const char *);
> > +} commands[] = {
>
> Do not call an array the represents a set of THINGs "type things[]";
> instead call it "type thing[]", so that the 0th thing can be
> referred to as thing[0], not things[0].
>
> One exception is when the set as a whole is referred to more often
> than individual element of an array, in which case "things" (without
> the [index]) becomes a sensible way to refer to the set.
>
> > + { "update", parse_cmd_update },
> > + { "create", parse_cmd_create },
> > + { "delete", parse_cmd_delete },
> > + { "verify", parse_cmd_verify },
> > + { "option", parse_cmd_option },
> > +};
> > +
> > static void update_refs_stdin(struct ref_transaction *transaction)
> > {
> > struct strbuf input = STRBUF_INIT;
> > const char *next;
> > + int i;
> >
> > if (strbuf_read(&input, 0, 1000) < 0)
> > die_errno("could not read from stdin");
> > next = input.buf;
> > /* Read each line dispatch its command */
> > while (next < input.buf + input.len) {
> > + const struct parse_cmd *cmd = NULL;
> > +
> > if (*next == line_termination)
> > die("empty command in input");
> > else if (isspace(*next))
> > die("whitespace before command: %s", next);
> > - else if (skip_prefix(next, "update ", &next))
> > - next = parse_cmd_update(transaction, &input, next);
> > - else if (skip_prefix(next, "create ", &next))
> > - next = parse_cmd_create(transaction, &input, next);
> > - else if (skip_prefix(next, "delete ", &next))
> > - next = parse_cmd_delete(transaction, &input, next);
> > - else if (skip_prefix(next, "verify ", &next))
> > - next = parse_cmd_verify(transaction, &input, next);
> > - else if (skip_prefix(next, "option ", &next))
> > - next = parse_cmd_option(&input, next);
> > - else
> > +
> > + for (i = 0; i < ARRAY_SIZE(commands); i++) {
> > + if (!skip_prefix(next, commands[i].prefix , &next))
> > + continue;
> > + cmd = &commands[i];
> > + break;
> > + }
>
> The only reason why you had to sprinkle
>
> if (!skip_prefix(next, " ", &next))
> die(_("%s: missing space after command"), cmd);
>
> all over the place is because the table lacks the trailing SP (which
> makes sense---after all, you are making a table of commands). In
> other words, it's not like some command called from this dispatcher
> would require " " after the command name and some others would not.Show 14 quoted lines
> So why not avoid touching the parse_cmd_<cmd>() at all (except for
> the "option" thing that now needs to take the transaction object for
> uniformity), and then verify the presence of " " here, perhaps like
> this:
>
> for (i = 0; i < ARRAY_SIZE(command); i++) {
> const char *eoc;
> if (!skip_prefix(next, commands[i].prefix, &eoc) ||
> *eoc != ' ')
> continue;
> cmd = &command[i];
> next = eoc;
> break;
> }The reason why I moved those `skip_prefix` calls into each of the respective commands is that this patch series introduces calls that do not accept a trailing space at all. Thus we cannot handle the space generically here, as that would was soon as we introduce the set of new commands.
Initially I just had a trailing " " in the `command` array as you hint at, but it felt a bit magical to me and might make readers wonder why some commands are spelt "update " and why some are spelt "commit", without a trailing space.
Show 18 quoted lines
> Note that you cannot reuse &next here to future-proof the code;
> otherwise, you wouldn't be able to add a new command, e.g. "options",
> that sits next to the existing command "option", in the future.
>
> > + if (!cmd)
> > die("unknown command: %s", next);
> >
> > + if (input.buf[strlen(cmd->prefix)] != line_termination &&
> > + input.buf[strlen(cmd->prefix)] != '\0' &&
> > + input.buf[strlen(cmd->prefix)] != ' ')
> > + die("%s: no separator after command", cmd->prefix);
>
> This part of your version does not make sense to me. If the input
> line began with "update" with some separator that is not a SP, the
> original would not have matched. But with this code, "update\0"
> would pass this code and cause cmd->fn() to be called, only to get
> the input rejected, because next is not pointing to SP. So why do
> you even need this if statement?I hope this makes more sense with the above background of commands without trailing space. It would probably make sense to move this into the command-matching loop though to be able to discern "option" and a potentially new "options" command in the future. Instead of dying, we'd then have to `continue` the loop and check whether any of the remaining commands matches.
Patrick