Merge branch 'zh/trailer-cmd'

The way the command line specified by the trailer.<token>.command
configuration variable receives the end-user supplied value was
both error prone and misleading.  An alternative to achieve the
same goal in a safer and more intuitive way has been added, as
the trailer.<token>.cmd configuration variable, to replace it.

* zh/trailer-cmd:
  trailer: add new .cmd config option
  docs: correct descript of trailer.<token>.command
maint
Junio C Hamano 2021-05-11 15:27:23 +09:00
commit 2cd6ce21f3
3 changed files with 187 additions and 26 deletions

View File

@ -232,25 +232,38 @@ trailer.<token>.ifmissing::
that option for trailers with the specified <token>. that option for trailers with the specified <token>.


trailer.<token>.command:: trailer.<token>.command::
This option can be used to specify a shell command that will This option behaves in the same way as 'trailer.<token>.cmd', except
be called to automatically add or modify a trailer with the that it doesn't pass anything as argument to the specified command.
specified <token>. Instead the first occurrence of substring $ARG is replaced by the
value that would be passed as argument.
+ +
When this option is specified, the behavior is as if a special The 'trailer.<token>.command' option has been deprecated in favor of
'<token>=<value>' argument were added at the beginning of the command 'trailer.<token>.cmd' due to the fact that $ARG in the user's command is
line, where <value> is taken to be the standard output of the only replaced once and that the original way of replacing $ARG is not safe.
specified command with any leading and trailing whitespace trimmed
off.
+ +
If the command contains the `$ARG` string, this string will be When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given
replaced with the <value> part of an existing trailer with the same for the same <token>, 'trailer.<token>.cmd' is used and
<token>, if any, before the command is launched. 'trailer.<token>.command' is ignored.

trailer.<token>.cmd::
This option can be used to specify a shell command that will be called:
once to automatically add a trailer with the specified <token>, and then
each time a '--trailer <token>=<value>' argument to modify the <value> of
the trailer that this option would produce.
+ +
If some '<token>=<value>' arguments are also passed on the command When the specified command is first called to add a trailer
line, when a 'trailer.<token>.command' is configured, the command will with the specified <token>, the behavior is as if a special
also be executed for each of these arguments. And the <value> part of '--trailer <token>=<value>' argument was added at the beginning
these arguments, if any, will be used to replace the `$ARG` string in of the "git interpret-trailers" command, where <value>
the command. is taken to be the standard output of the command with any
leading and trailing whitespace trimmed off.
+
If some '--trailer <token>=<value>' arguments are also passed
on the command line, the command is called again once for each
of these arguments with the same <token>. And the <value> part
of these arguments, if any, will be passed to the command as its
first argument. This way the command can produce a <value> computed
from the <value> passed in the '--trailer <token>=<value>' argument.


EXAMPLES EXAMPLES
-------- --------
@ -333,6 +346,55 @@ subject
Fix #42 Fix #42
------------ ------------


* Configure a 'help' trailer with a cmd use a script `glog-find-author`
which search specified author identity from git log in git repository
and show how it works:
+
------------
$ cat ~/bin/glog-find-author
#!/bin/sh
test -n "$1" && git log --author="$1" --pretty="%an <%ae>" -1 || true
$ git config trailer.help.key "Helped-by: "
$ git config trailer.help.ifExists "addIfDifferentNeighbor"
$ git config trailer.help.cmd "~/bin/glog-find-author"
$ git interpret-trailers --trailer="help:Junio" --trailer="help:Couder" <<EOF
> subject
>
> message
>
> EOF
subject

message

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
------------

* Configure a 'ref' trailer with a cmd use a script `glog-grep`
to grep last relevant commit from git log in the git repository
and show how it works:
+
------------
$ cat ~/bin/glog-grep
#!/bin/sh
test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
$ git config trailer.ref.key "Reference-to: "
$ git config trailer.ref.ifExists "replace"
$ git config trailer.ref.cmd "~/bin/glog-grep"
$ git interpret-trailers --trailer="ref:Add copyright notices." <<EOF
> subject
>
> message
>
> EOF
subject

message

Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)
------------

* Configure a 'see' trailer with a command to show the subject of a * Configure a 'see' trailer with a command to show the subject of a
commit that is related, and show how it works: commit that is related, and show how it works:
+ +

View File

@ -51,6 +51,69 @@ test_expect_success 'setup' '
EOF EOF
' '


test_expect_success 'with cmd' '
test_when_finished "git config --remove-section trailer.bug" &&
git config trailer.bug.key "Bug-maker: " &&
git config trailer.bug.ifExists "add" &&
git config trailer.bug.cmd "echo \"maybe is\"" &&
cat >expected2 <<-EOF &&

Bug-maker: maybe is him
Bug-maker: maybe is me
EOF
git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
>actual2 &&
test_cmp expected2 actual2
'

test_expect_success 'with cmd and $1' '
test_when_finished "git config --remove-section trailer.bug" &&
git config trailer.bug.key "Bug-maker: " &&
git config trailer.bug.ifExists "add" &&
git config trailer.bug.cmd "echo \"\$1\" is" &&
cat >expected2 <<-EOF &&

Bug-maker: him is him
Bug-maker: me is me
EOF
git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
>actual2 &&
test_cmp expected2 actual2
'

test_expect_success 'with cmd and $1 with sh -c' '
test_when_finished "git config --remove-section trailer.bug" &&
git config trailer.bug.key "Bug-maker: " &&
git config trailer.bug.ifExists "replace" &&
git config trailer.bug.cmd "sh -c \"echo who is \"\$1\"\"" &&
cat >expected2 <<-EOF &&

Bug-maker: who is me
EOF
git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
>actual2 &&
test_cmp expected2 actual2
'

test_expect_success 'with cmd and $1 with shell script' '
test_when_finished "git config --remove-section trailer.bug" &&
git config trailer.bug.key "Bug-maker: " &&
git config trailer.bug.ifExists "replace" &&
git config trailer.bug.cmd "./echoscript" &&
cat >expected2 <<-EOF &&

Bug-maker: who is me
EOF
cat >echoscript <<-EOF &&
#!/bin/sh
echo who is "\$1"
EOF
chmod +x echoscript &&
git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
>actual2 &&
test_cmp expected2 actual2
'

test_expect_success 'without config' ' test_expect_success 'without config' '
sed -e "s/ Z\$/ /" >expected <<-\EOF && sed -e "s/ Z\$/ /" >expected <<-\EOF &&


@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '
git commit -m "Add file a.txt" git commit -m "Add file a.txt"
' '


test_expect_success 'cmd takes precedence over command' '
test_when_finished "git config --unset trailer.fix.cmd" &&
git config trailer.fix.ifExists "replace" &&
git config trailer.fix.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%aN)\" \
--abbrev-commit --abbrev=14 \"\$1\" || true" &&
git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
--abbrev-commit --abbrev=14 \$ARG" &&
FIXED=$(git log -1 --oneline --format="%h (%aN)" --abbrev-commit --abbrev=14 HEAD) &&
cat complex_message_body >expected2 &&
sed -e "s/ Z\$/ /" >>expected2 <<-EOF &&
Fixes: $FIXED
Acked-by= Z
Reviewed-by:
Signed-off-by: Z
Signed-off-by: A U Thor <author@example.com>
EOF
git interpret-trailers --trailer "review:" --trailer "fix=HEAD" \
<complex_message >actual2 &&
test_cmp expected2 actual2
'

test_expect_success 'with command using $ARG' ' test_expect_success 'with command using $ARG' '
git config trailer.fix.ifExists "replace" && git config trailer.fix.ifExists "replace" &&
git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" && git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" &&

View File

@ -14,6 +14,7 @@ struct conf_info {
char *name; char *name;
char *key; char *key;
char *command; char *command;
char *cmd;
enum trailer_where where; enum trailer_where where;
enum trailer_if_exists if_exists; enum trailer_if_exists if_exists;
enum trailer_if_missing if_missing; enum trailer_if_missing if_missing;
@ -127,6 +128,7 @@ static void free_arg_item(struct arg_item *item)
free(item->conf.name); free(item->conf.name);
free(item->conf.key); free(item->conf.key);
free(item->conf.command); free(item->conf.command);
free(item->conf.cmd);
free(item->token); free(item->token);
free(item->value); free(item->value);
free(item); free(item);
@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,
return 1; return 1;
} }


static char *apply_command(const char *command, const char *arg) static char *apply_command(struct conf_info *conf, const char *arg)
{ {
struct strbuf cmd = STRBUF_INIT; struct strbuf cmd = STRBUF_INIT;
struct strbuf buf = STRBUF_INIT; struct strbuf buf = STRBUF_INIT;
struct child_process cp = CHILD_PROCESS_INIT; struct child_process cp = CHILD_PROCESS_INIT;
char *result; char *result;


strbuf_addstr(&cmd, command); if (conf->cmd) {
if (arg) strbuf_addstr(&cmd, conf->cmd);
strbuf_replace(&cmd, TRAILER_ARG_STRING, arg); strvec_push(&cp.args, cmd.buf);

if (arg)
strvec_push(&cp.args, cmd.buf); strvec_push(&cp.args, arg);
} else if (conf->command) {
strbuf_addstr(&cmd, conf->command);
if (arg)
strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
strvec_push(&cp.args, cmd.buf);
}
cp.env = local_repo_env; cp.env = local_repo_env;
cp.no_stdin = 1; cp.no_stdin = 1;
cp.use_shell = 1; cp.use_shell = 1;
@ -247,7 +255,7 @@ static char *apply_command(const char *command, const char *arg)


static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok) static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg_tok)
{ {
if (arg_tok->conf.command) { if (arg_tok->conf.command || arg_tok->conf.cmd) {
const char *arg; const char *arg;
if (arg_tok->value && arg_tok->value[0]) { if (arg_tok->value && arg_tok->value[0]) {
arg = arg_tok->value; arg = arg_tok->value;
@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg
else else
arg = xstrdup(""); arg = xstrdup("");
} }
arg_tok->value = apply_command(arg_tok->conf.command, arg); arg_tok->value = apply_command(&arg_tok->conf, arg);
free((char *)arg); free((char *)arg);
} }
} }
@ -430,6 +438,7 @@ static void duplicate_conf(struct conf_info *dst, const struct conf_info *src)
dst->name = xstrdup_or_null(src->name); dst->name = xstrdup_or_null(src->name);
dst->key = xstrdup_or_null(src->key); dst->key = xstrdup_or_null(src->key);
dst->command = xstrdup_or_null(src->command); dst->command = xstrdup_or_null(src->command);
dst->cmd = xstrdup_or_null(src->cmd);
} }


static struct arg_item *get_conf_item(const char *name) static struct arg_item *get_conf_item(const char *name)
@ -454,8 +463,8 @@ static struct arg_item *get_conf_item(const char *name)
return item; return item;
} }


enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE, enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,
TRAILER_IF_EXISTS, TRAILER_IF_MISSING }; TRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };


static struct { static struct {
const char *name; const char *name;
@ -463,6 +472,7 @@ static struct {
} trailer_config_items[] = { } trailer_config_items[] = {
{ "key", TRAILER_KEY }, { "key", TRAILER_KEY },
{ "command", TRAILER_COMMAND }, { "command", TRAILER_COMMAND },
{ "cmd", TRAILER_CMD },
{ "where", TRAILER_WHERE }, { "where", TRAILER_WHERE },
{ "ifexists", TRAILER_IF_EXISTS }, { "ifexists", TRAILER_IF_EXISTS },
{ "ifmissing", TRAILER_IF_MISSING } { "ifmissing", TRAILER_IF_MISSING }
@ -542,6 +552,11 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
warning(_("more than one %s"), conf_key); warning(_("more than one %s"), conf_key);
conf->command = xstrdup(value); conf->command = xstrdup(value);
break; break;
case TRAILER_CMD:
if (conf->cmd)
warning(_("more than one %s"), conf_key);
conf->cmd = xstrdup(value);
break;
case TRAILER_WHERE: case TRAILER_WHERE:
if (trailer_set_where(&conf->where, value)) if (trailer_set_where(&conf->where, value))
warning(_("unknown value '%s' for key '%s'"), value, conf_key); warning(_("unknown value '%s' for key '%s'"), value, conf_key);