@@ -236,21 +236,38 @@ trailer.<token>.command::
be called to automatically add or modify a trailer with the
specified <token>.
+
-When this option is specified, the behavior is as if a special
-'--trailer <token>=<value>' argument was added at the beginning of
-the "git interpret-trailers" command, where <value> is taken to be the
-standard output of the specified command with any leading and trailing
-whitespace trimmed off.
+When this option is specified, the first occurrence of substring $ARG is
+replaced with the value given to the `interpret-trailer` command for the
+same token. It passes the value through `$ARG`, otherwise this option behaves
+in the same way as 'trailer.<token>.cmd'.
++
+The 'trailer.<token>.command' option has been deprecated due to the fact
+that $ARG in the user's command can only be replaced once and that the
+original way of replacing $ARG was not safe. Now the preferred option is
+'trailer.<token>.cmd', which uses a positional argument to pass the value.
+
-The first occurrence of substring `$ARG` will be replaced with the
-<value> part of an existing trailer with the same <token>, if any,
-before the command is launched.
+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::
+ The command specified by this configuration variable is run
+ with a single argument, which is the <value> part of a
+ `--trailer <token>=<value>` on the command line. The output
+ from the command is then used as the value for the <token>
+ in the resulting trailer.
++
+When this option is specified, the behavior is as if a
+'--trailer <token>=<value>' argument was added at the beginning of
+the "git interpret-trailers" command, the command specified by this
+configuration variable will be called with an empty string as the
+argument.
+
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command is run
-once for each these arguments with the same <token>. And the <value>
-part of these arguments, if any, will be used to replace the first `$ARG`
-string in the command.
+If some '--trailer <token>=<value>' arguments are also passed on the
+command line, when a 'trailer.<token>.cmd' is configured, the command
+is run once for each `--trailer <token>=<value>` on the command line
+with the same <token>. And the <value> part of these arguments, if any,
+will be passed to the command as its first argument.
EXAMPLES
--------
@@ -333,6 +350,53 @@ subject
Fix #42
------------
+* Configure a 'cnt' trailer with a cmd use a global script `gcount`
+to record commit counts of a specified author and show how it works:
++
+------------
+$ cat ~/bin/gcount
+#!/bin/sh
+test -n "$1" && git shortlog -s --author="$1" HEAD || true
+$ git config trailer.cnt.key "Commit-count: "
+$ git config trailer.cnt.ifExists "replace"
+$ git config trailer.cnt.cmd "~/bin/gcount"
+$ git interpret-trailers --trailer="cnt:Junio" <<EOF
+> subject
+>
+> message
+>
+> EOF
+subject
+
+message
+
+Commit-count: 22484 Junio C Hamano
+------------
+
+* Configure a 'ref' trailer with a cmd use a global 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
commit that is related, and show how it works:
+
@@ -51,6 +51,69 @@ test_expect_success 'setup' '
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
+ 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 "replace" &&
+ git config trailer.bug.cmd "echo \"\$1\" is" &&
+ cat >expected2 <<-EOF &&
+
+ 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' '
sed -e "s/ Z\$/ /" >expected <<-\EOF &&
@@ -1274,6 +1337,27 @@ test_expect_success 'setup a commit' '
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' '
git config trailer.fix.ifExists "replace" &&
git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" &&
@@ -14,6 +14,7 @@ struct conf_info {
char *name;
char *key;
char *command;
+ char *cmd;
enum trailer_where where;
enum trailer_if_exists if_exists;
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.key);
free(item->conf.command);
+ free(item->conf.cmd);
free(item->token);
free(item->value);
free(item);
@@ -216,18 +218,24 @@ static int check_if_different(struct trailer_item *in_tok,
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 buf = STRBUF_INIT;
struct child_process cp = CHILD_PROCESS_INIT;
char *result;
- strbuf_addstr(&cmd, command);
- if (arg)
- strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
-
- strvec_push(&cp.args, cmd.buf);
+ if (conf->cmd) {
+ strbuf_addstr(&cmd, conf->cmd);
+ strvec_push(&cp.args, cmd.buf);
+ if (arg)
+ 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.no_stdin = 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)
{
- if (arg_tok->conf.command) {
+ if (arg_tok->conf.command || arg_tok->conf.cmd) {
const char *arg;
if (arg_tok->value && arg_tok->value[0]) {
arg = arg_tok->value;
@@ -257,7 +265,7 @@ static void apply_item_command(struct trailer_item *in_tok, struct arg_item *arg
else
arg = xstrdup("");
}
- arg_tok->value = apply_command(arg_tok->conf.command, arg);
+ arg_tok->value = apply_command(&arg_tok->conf, 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->key = xstrdup_or_null(src->key);
dst->command = xstrdup_or_null(src->command);
+ dst->cmd = xstrdup_or_null(src->cmd);
}
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;
}
-enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_WHERE,
- TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
+enum trailer_info_type { TRAILER_KEY, TRAILER_COMMAND, TRAILER_CMD,
+ TRAILER_WHERE, TRAILER_IF_EXISTS, TRAILER_IF_MISSING };
static struct {
const char *name;
@@ -463,6 +472,7 @@ static struct {
} trailer_config_items[] = {
{ "key", TRAILER_KEY },
{ "command", TRAILER_COMMAND },
+ { "cmd", TRAILER_CMD },
{ "where", TRAILER_WHERE },
{ "ifexists", TRAILER_IF_EXISTS },
{ "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);
conf->command = xstrdup(value);
break;
+ case TRAILER_CMD:
+ if (conf->cmd)
+ warning(_("more than one %s"), conf_key);
+ conf->cmd = xstrdup(value);
+ break;
case TRAILER_WHERE:
if (trailer_set_where(&conf->where, value))
warning(_("unknown value '%s' for key '%s'"), value, conf_key);
@@ -708,7 +723,7 @@ static void process_command_line_args(struct list_head *arg_head,
/* Add an arg item for each configured trailer with a command */
list_for_each(pos, &conf_head) {
item = list_entry(pos, struct arg_item, list);
- if (item->conf.command)
+ if (item->conf.cmd || item->conf.command)
add_arg_item(arg_head,
xstrdup(token_from_item(item, NULL)),
xstrdup(""),