> Teach 'git hook list <hookname>', which checks the known configs in > order to create an ordered list of hooks to run on a given hook event. > > Multiple commands can be specified for a given hook by providing > multiple "hook.<hookname>.command = <path-to-hook>" lines. Hooks will be > run in config order. If more properties need to be set on a given hook > in the future, commands can also be specified by providing > "hook.<hookname>.command = <hookcmd-name>", as well as a "[hookcmd > <hookcmd-name>]" subsection; at minimum, this subsection must contain a > "hookcmd.<hookcmd-name>.command = <path-to-hook>" line. I learned later that this isn't true - in patch 6, the commit message and one of the tests therein describe being able to skip a previously inline command by making a hookcmd section of the same name and just specifying "skip = true" there (without any command). Maybe just delete the "at minimum" part. > +static int list(int argc, const char **argv, const char *prefix) > { > - struct option builtin_hook_options[] = { > + struct list_head *head, *pos; > + struct hook *item; You asked for review on nits too so here's one: "item" should be declared in the list_for_each block. (That also makes it easier to see that we don't need to free it.) > diff --git a/hook.c b/hook.c > new file mode 100644 > index 0000000000..937dc768c8 > --- /dev/null > +++ b/hook.c > @@ -0,0 +1,115 @@ > +#include "cache.h" > + > +#include "hook.h" > +#include "config.h" Usually we put all the includes together without any intervening blank lines. > +static void append_or_move_hook(struct list_head *head, const char *command) > +{ > + struct list_head *pos = NULL, *tmp = NULL; > + struct hook *to_add = NULL; > + > + /* > + * remove the prior entry with this command; we'll replace it at the > + * end. > + */ > + list_for_each_safe(pos, tmp, head) { > + struct hook *it = list_entry(pos, struct hook, list); > + if (!strcmp(it->command.buf, command)) { > + list_del(pos); > + /* we'll simply move the hook to the end */ > + to_add = it; "break" here? > + } > + } > + > + if (!to_add) { > + /* adding a new hook, not moving an old one */ > + to_add = xmalloc(sizeof(struct hook)); Style is to write sizeof(*to_add), I think. [snip] > +struct hook_config_cb > +{ > + struct strbuf *hookname; struct declarations have "{" not on a line on its own. Also, "hookname" could just be a char *? > + struct list_head *list; > +}; > + > +static int hook_config_lookup(const char *key, const char *value, void *cb_data) > +{ > + struct hook_config_cb *data = cb_data; > + const char *hook_key = data->hookname->buf; > + struct list_head *head = data->list; > + > + if (!strcmp(key, hook_key)) { > + const char *command = value; > + struct strbuf hookcmd_name = STRBUF_INIT; > + > + /* Check if a hookcmd with that name exists. */ > + strbuf_addf(&hookcmd_name, "hookcmd.%s.command", command); > + git_config_get_value(hookcmd_name.buf, &command); So we don't really care whether git_config_get_value returns 0 or 1 as long as it doesn't touch "command" if there is no such hookcmd. That fits with how git_config_get_value() is documented, so that's great. Perhaps better to document as: If a hookcmd.%s.command config exists, replace the command with the value of that config. (If not, do nothing - git_config_get_value() is documented to not overwrite the value argument in this case.) > + > + if (!command) { > + strbuf_release(&hookcmd_name); > + BUG("git_config_get_value overwrote a string it shouldn't have"); > + } > + > + /* > + * TODO: implement an option-getting callback, e.g. > + * get configs by pattern hookcmd.$value.* > + * for each key+value, do_callback(key, value, cb_data) > + */ > + > + append_or_move_hook(head, command); > + > + strbuf_release(&hookcmd_name); > + } > + > + return 0; > +} > + > +struct list_head* hook_list(const struct strbuf* hookname) "const char *hookname" should suffice? Also, search for "* " and replace with " *" where applicable (also in the .h file). > +{ > + struct strbuf hook_key = STRBUF_INIT; > + struct list_head *hook_head = xmalloc(sizeof(struct list_head)); > + struct hook_config_cb cb_data = { &hook_key, hook_head }; > + > + INIT_LIST_HEAD(hook_head); > + > + if (!hookname) > + return NULL; > + > + strbuf_addf(&hook_key, "hook.%s.command", hookname->buf); > + > + git_config(hook_config_lookup, (void*)&cb_data); Do we need this void* cast? > + > + strbuf_release(&hook_key); > + return hook_head; > +} > diff --git a/hook.h b/hook.h > new file mode 100644 > index 0000000000..8ffc4f14b6 > --- /dev/null > +++ b/hook.h > @@ -0,0 +1,26 @@ > +#include "config.h" > +#include "list.h" > +#include "strbuf.h" > + > +struct hook > +{ > + struct list_head list; > + /* > + * Config file which holds the hook.*.command definition. > + * (This has nothing to do with the hookcmd.<name>.* configs.) > + */ > + enum config_scope origin; > + /* The literal command to run. */ > + struct strbuf command; "char *" would suffice? The tests look fine.