trailer: add new .cmd config option
The `trailer.<token>.command` configuration variable specifies a command (run via the shell, so it does not have to be a single name or path to the command, but can be a shell script), and the first occurrence of substring $ARG is replaced with the value given to the `interpret-trailer` command for the token in a '--trailer <token>=<value>' argument. This has three downsides: * The use of $ARG in the mechanism misleads the users that the value is passed in the shell variable, and tempt them to use $ARG more than once, but that would not work, as the second and subsequent $ARG are not replaced. * Because $ARG is textually replaced without regard to the shell language syntax, even '$ARG' (inside a single-quote pair), which a user would expect to stay intact, would be replaced, and worse, if the value had an unmatched single quote (imagine a name like "O'Connor", substituted into NAME='$ARG' to make it NAME='O'Connor'), it would result in a broken command that is not syntactically correct (or worse). * The first occurrence of substring `$ARG` will be replaced with the empty string, in the command when the command is first called to add a trailer with the specified <token>. This is a bad design, the nature of automatic execution causes it to add a trailer that we don't expect. Introduce a new `trailer.<token>.cmd` configuration that takes higher precedence to deprecate and eventually remove `trailer.<token>.command`, which passes the value as an argument to the command. Instead of "$ARG", users can refer to the value as positional argument, $1, in their scripts. At the same time, in order to allow `git interpret-trailers` to better simulate the behavior of `git command -s`, 'trailer.<token>.cmd' will not automatically execute. Helped-by: Junio C Hamano <gitster@pobox.com> Helped-by: Christian Couder <christian.couder@gmail.com> Signed-off-by: ZheNing Hu <adlternative@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
parent
57dcb6575b
commit
c364b7ef51
@ -232,6 +232,20 @@ 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 behaves in the same way as 'trailer.<token>.cmd', except
|
||||||
|
that it doesn't pass anything as argument to the specified command.
|
||||||
|
Instead the first occurrence of substring $ARG is replaced by the
|
||||||
|
value that would be passed as argument.
|
||||||
|
+
|
||||||
|
The 'trailer.<token>.command' option has been deprecated in favor of
|
||||||
|
'trailer.<token>.cmd' due to the fact that $ARG in the user's command is
|
||||||
|
only replaced once and that the original way of replacing $ARG is not safe.
|
||||||
|
+
|
||||||
|
When both 'trailer.<token>.cmd' and 'trailer.<token>.command' are given
|
||||||
|
for the same <token>, 'trailer.<token>.cmd' is used and
|
||||||
|
'trailer.<token>.command' is ignored.
|
||||||
|
|
||||||
|
trailer.<token>.cmd::
|
||||||
This option can be used to specify a shell command that will be called:
|
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
|
once to automatically add a trailer with the specified <token>, and then
|
||||||
each time a '--trailer <token>=<value>' argument to modify the <value> of
|
each time a '--trailer <token>=<value>' argument to modify the <value> of
|
||||||
@ -247,15 +261,9 @@ leading and trailing whitespace trimmed off.
|
|||||||
If some '--trailer <token>=<value>' arguments are also passed
|
If some '--trailer <token>=<value>' arguments are also passed
|
||||||
on the command line, the command is called again once for each
|
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 with the same <token>. And the <value> part
|
||||||
of these arguments, if any, will be used to replace the first
|
of these arguments, if any, will be passed to the command as its
|
||||||
occurrence of substring `$ARG` in the command. This way the
|
first argument. This way the command can produce a <value> computed
|
||||||
command can produce a <value> computed from the <value> passed
|
from the <value> passed in the '--trailer <token>=<value>' argument.
|
||||||
in the '--trailer <token>=<value>' argument.
|
|
||||||
+
|
|
||||||
For consistency, the first occurrence of substring `$ARG` is
|
|
||||||
also replaced, this time with the empty string, in the command
|
|
||||||
when the command is first called to add a trailer with the
|
|
||||||
specified <token>.
|
|
||||||
|
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
--------
|
--------
|
||||||
@ -338,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:
|
||||||
+
|
+
|
||||||
|
@ -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" &&
|
||||||
|
35
trailer.c
35
trailer.c
@ -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);
|
||||||
|
Loading…
Reference in New Issue
Block a user