All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] [GSOC]trailer: change $ARG to environment variable
@ 2021-03-23 14:53 ZheNing Hu via GitGitGadget
  2021-03-24 15:42 ` [PATCH v2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-23 14:53 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original implementation of `trailer.<token>.command`,
use `strbuf_replace` to replace `$ARG` in the <value> of the trailer,
but it have a problem:`strbuf_replace` replace the `$ARG` in command
only once, If the user's trailer command have used more then one `$ARG`,
error will occur.

So pass `$ARG` as an environment variable to the trailer command,
all `$ARG` in the command will be replaced, which will fix this problem.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC] trailer: change $ARG to environment variable
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG.
    
    The current new solution is to pass $ARG as an environment variable into
    the command.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/913

 Documentation/git-interpret-trailers.txt |  3 ++-
 t/t7513-interpret-trailers.sh            | 17 +++++++++++++++++
 trailer.c                                |  9 ++++++---
 3 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..7cf7c032a0e9 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -242,7 +242,8 @@ line, where <value> is taken to be the standard output of the
 specified command with any leading and trailing whitespace trimmed
 off.
 +
-If the command contains the `$ARG` string, this string will be
+If the command contains the `$ARG` string (`$ARG` is an exported
+environment variable), this string will be
 replaced with the <value> part of an existing trailer with the same
 <token>, if any, before the command is launched.
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..d303cf0e4b36 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -1291,6 +1291,23 @@ test_expect_success 'with command using $ARG' '
 	test_cmp expected actual
 '
 
+test_expect_success 'with command using more than one $ARG' '
+	git config trailer.fix.ifExists "replace" &&
+	git config trailer.fix.command "test -n $ARG && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG || true" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
+	cat complex_message_body >expected &&
+	sed -e "s/ Z\$/ /" >>expected <<-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 >actual &&
+	test_cmp expected actual
+'
+
 test_expect_success 'with failing command using $ARG' '
 	git config trailer.fix.ifExists "replace" &&
 	git config trailer.fix.command "false \$ARG" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..42e3b818327a 100644
--- a/trailer.c
+++ b/trailer.c
@@ -44,7 +44,7 @@ static char *separators = ":";
 
 static int configured;
 
-#define TRAILER_ARG_STRING "$ARG"
+#define TRAILER_ARG_STRING "ARG"
 
 static const char *git_generated_prefixes[] = {
 	"Signed-off-by: ",
@@ -222,13 +222,16 @@ static char *apply_command(const char *command, const char *arg)
 	struct strbuf buf = STRBUF_INIT;
 	struct child_process cp = CHILD_PROCESS_INIT;
 	char *result;
+	const char *const *var;
 
 	strbuf_addstr(&cmd, command);
+	for (var = local_repo_env; *var; var++)
+		strvec_push(&cp.env_array, *var);
 	if (arg)
-		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+		strvec_pushf(&cp.env_array, "%s=%s", TRAILER_ARG_STRING, arg);
 
 	strvec_push(&cp.args, cmd.buf);
-	cp.env = local_repo_env;
+
 	cp.no_stdin = 1;
 	cp.use_shell = 1;
 

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v2] [GSOC]trailer: pass arg as positional parameter
  2021-03-23 14:53 [PATCH] [GSOC]trailer: change $ARG to environment variable ZheNing Hu via GitGitGadget
@ 2021-03-24 15:42 ` ZheNing Hu via GitGitGadget
  2021-03-24 20:18   ` Junio C Hamano
  2021-03-25 11:53   ` [PATCH v3] " ZheNing Hu via GitGitGadget
  0 siblings, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-24 15:42 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original implementation of `trailer.<token>.command`,
use `strbuf_replace` to replace $ARG in the <value> of the
trailer, but it have a problem: `strbuf_replace` replace the
$ARG in command only once, If the user's trailer command have
used more than one $ARG, error will occur.

If directly modify the implementation of the original
`trailer.<token>.command`, The user’s previous `'$ARG'` in
trailer command will not be replaced. So now add new
config "trailer.<token>.cmd", pass trailer's value as
positional parameter 1 to the user's command, users can
use $1 as trailer's value, to implement original variable
replacement.

Original `trailer.<token>.command` can still be used until git
completely abandoned it.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC]trailer: pass arg as positional parameter
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG.
    
    Now pass trailer value as $1 to the trailer command with another
    trailer.<token>.cmd config.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v1:

 1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable
     @@ Metadata
      Author: ZheNing Hu <adlternative@gmail.com>
      
       ## Commit message ##
     -    [GSOC]trailer: change $ARG to environment variable
     +    [GSOC]trailer: pass arg as positional parameter
      
          In the original implementation of `trailer.<token>.command`,
     -    use `strbuf_replace` to replace `$ARG` in the <value> of the trailer,
     -    but it have a problem:`strbuf_replace` replace the `$ARG` in command
     -    only once, If the user's trailer command have used more then one `$ARG`,
     -    error will occur.
     +    use `strbuf_replace` to replace $ARG in the <value> of the
     +    trailer, but it have a problem: `strbuf_replace` replace the
     +    $ARG in command only once, If the user's trailer command have
     +    used more than one $ARG, error will occur.
      
     -    So pass `$ARG` as an environment variable to the trailer command,
     -    all `$ARG` in the command will be replaced, which will fix this problem.
     +    If directly modify the implementation of the original
     +    `trailer.<token>.command`, The user’s previous `'$ARG'` in
     +    trailer command will not be replaced. So now add new
     +    config "trailer.<token>.cmd", pass trailer's value as
     +    positional parameter 1 to the user's command, users can
     +    use $1 as trailer's value, to implement original variable
     +    replacement.
     +
     +    Original `trailer.<token>.command` can still be used until git
     +    completely abandoned it.
      
          Signed-off-by: ZheNing Hu <adlternative@gmail.com>
      
       ## Documentation/git-interpret-trailers.txt ##
     -@@ Documentation/git-interpret-trailers.txt: line, where <value> is taken to be the standard output of the
     - specified command with any leading and trailing whitespace trimmed
     - off.
     - +
     --If the command contains the `$ARG` string, this string will be
     -+If the command contains the `$ARG` string (`$ARG` is an exported
     -+environment variable), this string will be
     - replaced with the <value> part of an existing trailer with the same
     - <token>, if any, before the command is launched.
     - +
     +@@ Documentation/git-interpret-trailers.txt: also be executed for each of these arguments. And the <value> part of
     + these arguments, if any, will be used to replace the `$ARG` string in
     + the command.
     + 
     ++trailer.<token>.cmd::
     ++	Similar to 'trailer.<token>.command'. But the difference is that
     ++	`$1` is used in the command to replace the value of the trailer
     ++	instead of the original `$ARG`, which means that we can quote the
     ++	trailer value multiple times in the command.
     ++	E.g. `trailer.sign.cmd="test -n \"$1\" && echo \"$1\" || true "`
     ++
     + EXAMPLES
     + --------
     + 
      
       ## t/t7513-interpret-trailers.sh ##
     -@@ t/t7513-interpret-trailers.sh: test_expect_success 'with command using $ARG' '
     - 	test_cmp expected actual
     +@@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
     + 	git commit -m "Add file a.txt"
       '
       
     -+test_expect_success 'with command using more than one $ARG' '
     ++test_expect_success 'with cmd using $1' '
     ++	test_when_finished "git config --unset trailer.fix.cmd" &&
      +	git config trailer.fix.ifExists "replace" &&
     -+	git config trailer.fix.command "test -n $ARG && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG || true" &&
     ++	git config trailer.fix.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" \
     ++		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
      +	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
     -+	cat complex_message_body >expected &&
     -+	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
     ++	cat complex_message_body >expected2 &&
     ++	sed -e "s/ Z\$/ /" >>expected2 <<-EOF &&
      +		Fixes: $FIXED
      +		Acked-by= Z
      +		Reviewed-by:
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'with command using $ARG' '
      +		Signed-off-by: A U Thor <author@example.com>
      +	EOF
      +	git interpret-trailers --trailer "review:" --trailer "fix=HEAD" \
     -+		<complex_message >actual &&
     -+	test_cmp expected actual
     ++		<complex_message >actual2 &&
     ++	test_cmp expected2 actual2
      +'
      +
     - test_expect_success 'with failing command using $ARG' '
     + test_expect_success 'with command using $ARG' '
       	git config trailer.fix.ifExists "replace" &&
     - 	git config trailer.fix.command "false \$ARG" &&
     +-	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" &&
     + 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
     + 	cat complex_message_body >expected &&
     + 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
      
       ## trailer.c ##
     -@@ trailer.c: static char *separators = ":";
     - 
     - static int configured;
     - 
     --#define TRAILER_ARG_STRING "$ARG"
     -+#define TRAILER_ARG_STRING "ARG"
     +@@ trailer.c: struct conf_info {
     + 	char *name;
     + 	char *key;
     + 	char *command;
     ++	int is_new_cmd;
     + 	enum trailer_where where;
     + 	enum trailer_if_exists if_exists;
     + 	enum trailer_if_missing if_missing;
     +@@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
     + 	return 1;
     + }
       
     - static const char *git_generated_prefixes[] = {
     - 	"Signed-off-by: ",
     -@@ trailer.c: static char *apply_command(const char *command, const char *arg)
     +-static char *apply_command(const char *command, const char *arg)
     ++static char *apply_command(const char *command, int is_new_cmd , const char *arg)
     + {
     + 	struct strbuf cmd = STRBUF_INIT;
       	struct strbuf buf = STRBUF_INIT;
     - 	struct child_process cp = CHILD_PROCESS_INIT;
     +@@ trailer.c: static char *apply_command(const char *command, const char *arg)
       	char *result;
     -+	const char *const *var;
       
       	strbuf_addstr(&cmd, command);
     -+	for (var = local_repo_env; *var; var++)
     -+		strvec_push(&cp.env_array, *var);
     - 	if (arg)
     +-	if (arg)
      -		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
     -+		strvec_pushf(&cp.env_array, "%s=%s", TRAILER_ARG_STRING, arg);
     - 
     +-
       	strvec_push(&cp.args, cmd.buf);
     --	cp.env = local_repo_env;
     -+
     ++	if (arg) {
     ++		if (is_new_cmd)
     ++			strvec_push(&cp.args, arg);
     ++		else
     ++			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
     ++	}
     + 	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     +@@ trailer.c: 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.command, arg_tok->conf.is_new_cmd, arg);
     + 		free((char *)arg);
     + 	}
     + }
     +@@ trailer.c: 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;
     +@@ trailer.c: 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 }
     +@@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value, void *cb)
     + 	case TRAILER_COMMAND:
     + 		if (conf->command)
     + 			warning(_("more than one %s"), conf_key);
     ++		conf->is_new_cmd = 0;
     ++		conf->command = xstrdup(value);
     ++		break;
     ++	case TRAILER_CMD:
     ++		if (conf->command)
     ++			warning(_("more than one %s"), conf_key);
     ++		conf->is_new_cmd = 1;
     + 		conf->command = xstrdup(value);
     + 		break;
     + 	case TRAILER_WHERE:


 Documentation/git-interpret-trailers.txt |  7 +++++++
 t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-
 trailer.c                                | 26 +++++++++++++++++-------
 3 files changed, 47 insertions(+), 8 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..38656b1b3841 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of
 these arguments, if any, will be used to replace the `$ARG` string in
 the command.
 
+trailer.<token>.cmd::
+	Similar to 'trailer.<token>.command'. But the difference is that
+	`$1` is used in the command to replace the value of the trailer
+	instead of the original `$ARG`, which means that we can quote the
+	trailer value multiple times in the command.
+	E.g. `trailer.sign.cmd="test -n \"$1\" && echo \"$1\" || true "`
+
 EXAMPLES
 --------
 
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..7cb81201442a 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -1274,9 +1274,29 @@ test_expect_success 'setup a commit' '
 	git commit -m "Add file a.txt"
 '
 
+test_expect_success 'with cmd using $1' '
+	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 (%s)\" \
+		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..80f47657ff1a 100644
--- a/trailer.c
+++ b/trailer.c
@@ -14,6 +14,7 @@ struct conf_info {
 	char *name;
 	char *key;
 	char *command;
+	int is_new_cmd;
 	enum trailer_where where;
 	enum trailer_if_exists if_exists;
 	enum trailer_if_missing if_missing;
@@ -216,7 +217,7 @@ 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(const char *command, int is_new_cmd , const char *arg)
 {
 	struct strbuf cmd = STRBUF_INIT;
 	struct strbuf buf = STRBUF_INIT;
@@ -224,10 +225,13 @@ static char *apply_command(const char *command, const char *arg)
 	char *result;
 
 	strbuf_addstr(&cmd, command);
-	if (arg)
-		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
-
 	strvec_push(&cp.args, cmd.buf);
+	if (arg) {
+		if (is_new_cmd)
+			strvec_push(&cp.args, arg);
+		else
+			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+	}
 	cp.env = local_repo_env;
 	cp.no_stdin = 1;
 	cp.use_shell = 1;
@@ -257,7 +261,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.command, arg_tok->conf.is_new_cmd, arg);
 		free((char *)arg);
 	}
 }
@@ -454,8 +458,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 +467,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 }
@@ -540,6 +545,13 @@ static int git_trailer_config(const char *conf_key, const char *value, void *cb)
 	case TRAILER_COMMAND:
 		if (conf->command)
 			warning(_("more than one %s"), conf_key);
+		conf->is_new_cmd = 0;
+		conf->command = xstrdup(value);
+		break;
+	case TRAILER_CMD:
+		if (conf->command)
+			warning(_("more than one %s"), conf_key);
+		conf->is_new_cmd = 1;
 		conf->command = xstrdup(value);
 		break;
 	case TRAILER_WHERE:

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v2] [GSOC]trailer: pass arg as positional parameter
  2021-03-24 15:42 ` [PATCH v2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
@ 2021-03-24 20:18   ` Junio C Hamano
  2021-03-25  1:43     ` ZheNing Hu
  2021-03-25 11:53   ` [PATCH v3] " ZheNing Hu via GitGitGadget
  1 sibling, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-03-24 20:18 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: ZheNing Hu <adlternative@gmail.com>
>
> In the original implementation of `trailer.<token>.command`,
> use `strbuf_replace` to replace $ARG in the <value> of the
> trailer, but it have a problem: `strbuf_replace` replace the
> $ARG in command only once, If the user's trailer command have
> used more than one $ARG, error will occur.
>
> If directly modify the implementation of the original
> `trailer.<token>.command`, The user’s previous `'$ARG'` in
> trailer command will not be replaced. So now add new
> config "trailer.<token>.cmd", pass trailer's value as
> positional parameter 1 to the user's command, users can
> use $1 as trailer's value, to implement original variable
> replacement.
>
> Original `trailer.<token>.command` can still be used until git
> completely abandoned it.

Sorry, but that's quite an ungrammatical mess X-<.

>  1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable
>      @@ Metadata
>       Author: ZheNing Hu <adlternative@gmail.com>

As this is completely a different design and does not share anything
with the earlier round, the range-diff is merely distracting and
useless.

>  Documentation/git-interpret-trailers.txt |  7 +++++++
>  t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-
>  trailer.c                                | 26 +++++++++++++++++-------
>  3 files changed, 47 insertions(+), 8 deletions(-)
>
> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 96ec6499f001..38656b1b3841 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of
>  these arguments, if any, will be used to replace the `$ARG` string in
>  the command.
>  
> +trailer.<token>.cmd::
> +	Similar to 'trailer.<token>.command'. But the difference is that
> +	`$1` is used in the command to replace the value of the trailer
> +	instead of the original `$ARG`, which means that we can quote the

"quote"?

> +	trailer value multiple times in the command.
> +	E.g. `trailer.sign.cmd="test -n \"$1\" && echo \"$1\" || true "`

This needs to explain what happens if the user gives both .cmd and
.command to the same token.  Is it an error?  Is the newly invented
.cmd takes precedence?  Something else?

Whatever the answer is, the reasoning behind reaching the design
must be explained and defended in the proposed log message.


> diff --git a/trailer.c b/trailer.c
> index be4e9726421c..80f47657ff1a 100644
> --- a/trailer.c
> +++ b/trailer.c
> @@ -14,6 +14,7 @@ struct conf_info {
>  	char *name;
>  	char *key;
>  	char *command;
> +	int is_new_cmd;

Poor naming.  The .cmd thing may be "new" right now in your mind,
but how would you transition out of it when design flaws are
discovered in it and replace it with yet another mechanism?

Add a new "char *cmd" field, and at the using site, define the
precedence between the two when both cmd and command members of the
structure are populated, perhaps?

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v2] [GSOC]trailer: pass arg as positional parameter
  2021-03-24 20:18   ` Junio C Hamano
@ 2021-03-25  1:43     ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-03-25  1:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年3月25日周四 上午4:18写道:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > In the original implementation of `trailer.<token>.command`,
> > use `strbuf_replace` to replace $ARG in the <value> of the
> > trailer, but it have a problem: `strbuf_replace` replace the
> > $ARG in command only once, If the user's trailer command have
> > used more than one $ARG, error will occur.
> >
> > If directly modify the implementation of the original
> > `trailer.<token>.command`, The user’s previous `'$ARG'` in
> > trailer command will not be replaced. So now add new
> > config "trailer.<token>.cmd", pass trailer's value as
> > positional parameter 1 to the user's command, users can
> > use $1 as trailer's value, to implement original variable
> > replacement.
> >
> > Original `trailer.<token>.command` can still be used until git
> > completely abandoned it.
>
> Sorry, but that's quite an ungrammatical mess X-<.
>

Somewhat embarrassing. I have tried to fix it...

> >  1:  abc5b04d152f ! 1:  185356d6fc90 [GSOC]trailer: change $ARG to environment variable
> >      @@ Metadata
> >       Author: ZheNing Hu <adlternative@gmail.com>
>
> As this is completely a different design and does not share anything
> with the earlier round, the range-diff is merely distracting and
> useless.
>

I thought the designs of the two were very similar.

> >  Documentation/git-interpret-trailers.txt |  7 +++++++
> >  t/t7513-interpret-trailers.sh            | 22 +++++++++++++++++++-
> >  trailer.c                                | 26 +++++++++++++++++-------
> >  3 files changed, 47 insertions(+), 8 deletions(-)
> >
> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> > index 96ec6499f001..38656b1b3841 100644
> > --- a/Documentation/git-interpret-trailers.txt
> > +++ b/Documentation/git-interpret-trailers.txt
> > @@ -252,6 +252,13 @@ also be executed for each of these arguments. And the <value> part of
> >  these arguments, if any, will be used to replace the `$ARG` string in
> >  the command.
> >
> > +trailer.<token>.cmd::
> > +     Similar to 'trailer.<token>.command'. But the difference is that
> > +     `$1` is used in the command to replace the value of the trailer
> > +     instead of the original `$ARG`, which means that we can quote the
>
> "quote"?
>

parse.

> > +     trailer value multiple times in the command.
> > +     E.g. `trailer.sign.cmd="test -n \"$1\" && echo \"$1\" || true "`
>
> This needs to explain what happens if the user gives both .cmd and
> .command to the same token.  Is it an error?  Is the newly invented
> .cmd takes precedence?  Something else?
>

For the time being, if I make "cmd" and "command" equivalent, it will
only trigger a warning.

> Whatever the answer is, the reasoning behind reaching the design
> must be explained and defended in the proposed log message.
>

OK.

>
> > diff --git a/trailer.c b/trailer.c
> > index be4e9726421c..80f47657ff1a 100644
> > --- a/trailer.c
> > +++ b/trailer.c
> > @@ -14,6 +14,7 @@ struct conf_info {
> >       char *name;
> >       char *key;
> >       char *command;
> > +     int is_new_cmd;
>
> Poor naming.  The .cmd thing may be "new" right now in your mind,
> but how would you transition out of it when design flaws are
> discovered in it and replace it with yet another mechanism?
>

I thought if the "command" will need to be replaced in later releases,
 "is_new_cmd" will be removed at the same time, now I think
 "is_new_cmd" may cause misunderstandings.

> Add a new "char *cmd" field, and at the using site, define the
> precedence between the two when both cmd and command members of the
> structure are populated, perhaps?

It sounds feasible, I will try.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v3] [GSOC]trailer: pass arg as positional parameter
  2021-03-24 15:42 ` [PATCH v2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
  2021-03-24 20:18   ` Junio C Hamano
@ 2021-03-25 11:53   ` ZheNing Hu via GitGitGadget
  2021-03-25 22:28     ` Junio C Hamano
  2021-03-26 16:13     ` [PATCH v4] " ZheNing Hu via GitGitGadget
  1 sibling, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-25 11:53 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

Original implementation of `trailer.<token>.command` use
`strbuf_replace` to replace $ARG in command with the <value>
of the trailer, but it have a problem: `strbuf_replace`
replace the $ARG only once, If the user's trailer command
have used more than one $ARG, the remaining replacement will
fail.

If directly modify the implementation of the original
`trailer.<token>.command`, The user’s previous `'$ARG'` in
trailer command will not be replaced. So now add new config
"trailer.<token>.cmd", pass trailer's value as positional
parameter 1 to the user's command, the user can use $1 as
trailer's value, to implement original variable replacement.

If the user has these two configuration: "trailer.<token>.cmd"
and "trailer.<token>.command", "cmd" will execute and "command"
will not executed.

Original `trailer.<token>.command` can still be used until git
completely abandoned it.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC]trailer: pass arg as positional parameter
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG.
    
    Now pass trailer value as $1 to the trailer command with another
    trailer.<token>.cmd config.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v2:

 1:  185356d6fc90 ! 1:  b268ecd7b395 [GSOC]trailer: pass arg as positional parameter
     @@ Metadata
       ## Commit message ##
          [GSOC]trailer: pass arg as positional parameter
      
     -    In the original implementation of `trailer.<token>.command`,
     -    use `strbuf_replace` to replace $ARG in the <value> of the
     -    trailer, but it have a problem: `strbuf_replace` replace the
     -    $ARG in command only once, If the user's trailer command have
     -    used more than one $ARG, error will occur.
     +    Original implementation of `trailer.<token>.command` use
     +    `strbuf_replace` to replace $ARG in command with the <value>
     +    of the trailer, but it have a problem: `strbuf_replace`
     +    replace the $ARG only once, If the user's trailer command
     +    have used more than one $ARG, the remaining replacement will
     +    fail.
      
          If directly modify the implementation of the original
          `trailer.<token>.command`, The user’s previous `'$ARG'` in
     -    trailer command will not be replaced. So now add new
     -    config "trailer.<token>.cmd", pass trailer's value as
     -    positional parameter 1 to the user's command, users can
     -    use $1 as trailer's value, to implement original variable
     -    replacement.
     +    trailer command will not be replaced. So now add new config
     +    "trailer.<token>.cmd", pass trailer's value as positional
     +    parameter 1 to the user's command, the user can use $1 as
     +    trailer's value, to implement original variable replacement.
     +
     +    If the user has these two configuration: "trailer.<token>.cmd"
     +    and "trailer.<token>.command", "cmd" will execute and "command"
     +    will not executed.
      
          Original `trailer.<token>.command` can still be used until git
          completely abandoned it.
     @@ Documentation/git-interpret-trailers.txt: also be executed for each of these arg
      +trailer.<token>.cmd::
      +	Similar to 'trailer.<token>.command'. But the difference is that
      +	`$1` is used in the command to replace the value of the trailer
     -+	instead of the original `$ARG`, which means that we can quote the
     ++	instead of the original `$ARG`, which means that we can pass the
      +	trailer value multiple times in the command.
     -+	E.g. `trailer.sign.cmd="test -n \"$1\" && echo \"$1\" || true "`
     ++	E.g. `git config trailer.sign.cmd "test -n \"$1\" && echo \"$1\" || true "`.
     ++	If the user has these two configuration: "trailer.<token>.cmd"
     ++	and "trailer.<token>.command", "cmd" will be executed and "command"
     ++	will not be executed.
      +
       EXAMPLES
       --------
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
       	git commit -m "Add file a.txt"
       '
       
     -+test_expect_success 'with cmd using $1' '
     ++test_expect_success 'with cmd and $1' '
      +	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 (%s)\" \
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
      +		<complex_message >actual2 &&
      +	test_cmp expected2 actual2
      +'
     ++
     ++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" &&
     @@ trailer.c: struct conf_info {
       	char *name;
       	char *key;
       	char *command;
     -+	int is_new_cmd;
     ++	char *cmd;
       	enum trailer_where where;
       	enum trailer_if_exists if_exists;
       	enum trailer_if_missing if_missing;
     +@@ trailer.c: 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);
      @@ trailer.c: 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(const char *command, int is_new_cmd , const char *arg)
     ++static char *apply_command(const char *command, const char *cmd_, const char *arg)
       {
       	struct strbuf cmd = STRBUF_INIT;
       	struct strbuf buf = STRBUF_INIT;
     -@@ trailer.c: static char *apply_command(const char *command, const char *arg)
     + 	struct child_process cp = CHILD_PROCESS_INIT;
       	char *result;
       
     - 	strbuf_addstr(&cmd, command);
     +-	strbuf_addstr(&cmd, command);
      -	if (arg)
      -		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
      -
     - 	strvec_push(&cp.args, cmd.buf);
     -+	if (arg) {
     -+		if (is_new_cmd)
     +-	strvec_push(&cp.args, cmd.buf);
     ++	if (cmd_) {
     ++		strbuf_addstr(&cmd, cmd_);
     ++		strvec_push(&cp.args, cmd.buf);
     ++		if (arg)
      +			strvec_push(&cp.args, arg);
     -+		else
     ++	} else if (command) {
     ++		strbuf_addstr(&cmd, command);
     ++		strvec_push(&cp.args, cmd.buf);
     ++		if (arg)
      +			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
      +	}
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     +@@ trailer.c: 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;
      @@ trailer.c: 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.command, arg_tok->conf.is_new_cmd, arg);
     ++		arg_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);
       		free((char *)arg);
       	}
       }
     +@@ trailer.c: 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)
      @@ trailer.c: static struct arg_item *get_conf_item(const char *name)
       	return item;
       }
     @@ trailer.c: static struct {
       	{ "ifexists", TRAILER_IF_EXISTS },
       	{ "ifmissing", TRAILER_IF_MISSING }
      @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value, void *cb)
     - 	case TRAILER_COMMAND:
     - 		if (conf->command)
       			warning(_("more than one %s"), conf_key);
     -+		conf->is_new_cmd = 0;
     -+		conf->command = xstrdup(value);
     -+		break;
     -+	case TRAILER_CMD:
     -+		if (conf->command)
     -+			warning(_("more than one %s"), conf_key);
     -+		conf->is_new_cmd = 1;
       		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);
     +@@ trailer.c: 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(""),


 Documentation/git-interpret-trailers.txt | 10 ++++++
 t/t7513-interpret-trailers.sh            | 43 +++++++++++++++++++++++-
 trailer.c                                | 37 ++++++++++++++------
 3 files changed, 78 insertions(+), 12 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..f796041514bf 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of
 these arguments, if any, will be used to replace the `$ARG` string in
 the command.
 
+trailer.<token>.cmd::
+	Similar to 'trailer.<token>.command'. But the difference is that
+	`$1` is used in the command to replace the value of the trailer
+	instead of the original `$ARG`, which means that we can pass the
+	trailer value multiple times in the command.
+	E.g. `git config trailer.sign.cmd "test -n \"$1\" && echo \"$1\" || true "`.
+	If the user has these two configuration: "trailer.<token>.cmd"
+	and "trailer.<token>.command", "cmd" will be executed and "command"
+	will not be executed.
+
 EXAMPLES
 --------
 
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..059beec0c0de 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -1274,9 +1274,50 @@ test_expect_success 'setup a commit' '
 	git commit -m "Add file a.txt"
 '
 
+test_expect_success 'with cmd and $1' '
+	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 (%s)\" \
+		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --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 '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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..634d3f1ff04a 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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(const char *command, const char *cmd_, 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 (cmd_) {
+		strbuf_addstr(&cmd, cmd_);
+		strvec_push(&cp.args, cmd.buf);
+		if (arg)
+			strvec_push(&cp.args, arg);
+	} else if (command) {
+		strbuf_addstr(&cmd, command);
+		strvec_push(&cp.args, cmd.buf);
+		if (arg)
+			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+	}
 	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.command, arg_tok->conf.cmd, 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(""),

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v3] [GSOC]trailer: pass arg as positional parameter
  2021-03-25 11:53   ` [PATCH v3] " ZheNing Hu via GitGitGadget
@ 2021-03-25 22:28     ` Junio C Hamano
  2021-03-26 13:29       ` ZheNing Hu
  2021-03-26 16:13     ` [PATCH v4] " ZheNing Hu via GitGitGadget
  1 sibling, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-03-25 22:28 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: ZheNing Hu <adlternative@gmail.com>
>
> Original implementation of `trailer.<token>.command` use

uses

> `strbuf_replace` to replace $ARG in command with the <value>
> of the trailer, but it have a problem: `strbuf_replace`

has

> replace the $ARG only once, If the user's trailer command

replaces the $ARG only once.

> have used more than one $ARG, the remaining replacement will
> fail.

"will fail" is quite vague.  It is just left unreplaced, and if the
user expects all of them to be replaced, then the expectation and
reality would not match, but all of that you have already said by
"replaces the $ARG only once.", so I think this sentence should be
removed.

> If directly modify the implementation of the original
> `trailer.<token>.command`, The user’s previous `'$ARG'` in
> trailer command will not be replaced.

That statement does not make much sense.  Depending on the way how
the implementation is "directly" modified, you can fix the "replaces
only once" problem without introducing such a problem.  Just look
for '$ARG' in the string and replace all of them, not just the first
one.  It's not too difficult.

This confusion primarily comes from the fact that you forgot to
explain the other problem you are fixing, I think.  Even though the
trailer.<token>.command documentation implies that the user is
expected to give a shell script or some sort as the command and the
use of $ARG makes it look like a shell variable, the original
implementation does not treat it as a shell variable at all.  And
the textual replacement is done without making sure the value being
replaced has characters with special meaning in the shell language.

So existing .command may incorrectly use $ARG inside a single-quote
pair and expect it to be replaced to a string inside a single-quote
pair.  A malformed, or worse, malicious, value would escape out of
the single-quote pair (remember, the '; rm -fr .' example?) and
execute arbitrary code in an unexpected way.  The (ungrammatical)
"if directly modify the implementation" refers to a potential way to
fix these two problems at the same time by doing the $ARG thing
without using textual replacement, namely, exporting the value as an
environment variable ARG.  If that approach was taken, then, $ARG
enclosed in a single-quote pair will no longer be replaced, which
makes it a backward incompatible change.

But without describing what solution you are talking about, your
three-line description does not make much sense.

> So now add new config
> "trailer.<token>.cmd", pass trailer's value as positional
> parameter 1 to the user's command, the user can use $1 as
> trailer's value, to implement original variable replacement.
>
> If the user has these two configuration: "trailer.<token>.cmd"
> and "trailer.<token>.command", "cmd" will execute and "command"
> will not executed.
>
> Original `trailer.<token>.command` can still be used until git
> completely abandoned it.
>
> Signed-off-by: ZheNing Hu <adlternative@gmail.com>

Let's rewrite it completely.

	The `trailer.<token>.command` configuration variable
	specifies a command (run via the shall, so it does not have
	to be a single name of 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.  This has two 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 unmatching 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).

	Introduce a new `trailer.<token>.cmd` configuration that
	takes higher precedence to deprecate and eventually remove
	`trailer.<token>.command`, which passes the value as a
	parameter to the command.  Instead of "$ARG", the users will
	refer to the value as positional argument, $1, in their
	scripts.

I tried to cover everything we need to tell the reviewers about this
change with the above.  Did I miss anything?

> +trailer.<token>.cmd::
> +	Similar to 'trailer.<token>.command'. But the difference is that
> +	`$1` is used in the command to replace the value of the trailer
> +	instead of the original `$ARG`, which means that we can pass the
> +	trailer value multiple times in the command.

We eventually want to deprecate the .command, so we'd prefer not to
rely on its description too much (e.g. try to find a way to say what
you want to say without "instead of the original `$ARG`").

	The command specified by this configuration variable is run
	with a single parameter, which is the <value> part of an
	existing trailer with the same <token>.  The output from the
	command is then used as the value for the <token> in the
	resulting trailer.

would be the replacement for the part that talks about $ARG in the
description of trailer.<token>.command.

The original description for `trailer.<token>.command` is so jumbled
(not your failure at all) that I had a hard time to understand what
it is trying to say (e.g. what does "as if a special <token>=<value>
argument were added at the beginning of the command line" mean?  Is
it making a one-shot export of environment variable to run the
command???), so the above may need further adjustment.  Christian?
Care to help out?

> +	E.g. `git config trailer.sign.cmd "test -n \"$1\" && echo \"$1\" || true "`.

An example is good.  There is a whole EXAMPLES section in this
manual page, and the one that uses $ARG may be a good candidate to
look at and change to use .cmd (instead of .command).

> +	If the user has these two configuration: "trailer.<token>.cmd"
> +	and "trailer.<token>.command", "cmd" will be executed and "command"
> +	will not be executed.

	When both .cmd and .command are given for the same <token>,
	.cmd is used and .command is ignored.

> diff --git a/trailer.c b/trailer.c
> index be4e9726421c..634d3f1ff04a 100644
> --- a/trailer.c
> +++ b/trailer.c
> @@ -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(const char *command, const char *cmd_, 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 (cmd_) {
> +		strbuf_addstr(&cmd, cmd_);
> +		strvec_push(&cp.args, cmd.buf);
> +		if (arg)
> +			strvec_push(&cp.args, arg);
> +	} else if (command) {
> +		strbuf_addstr(&cmd, command);
> +		strvec_push(&cp.args, cmd.buf);
> +		if (arg)
> +			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
> +	}

OK.  it is clear cmd_ takes precedence this way.

Later (not as part of this patch, but a few releases down the road),
we may want to add a warning() about using a deprecated feature when
"else if (command)" block is taken.

> @@ -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.command, arg_tok->conf.cmd, arg);

It might be cleaner to just pass arg_tok->conf to apply_command()
and hide "cmd takes precedence over command" as an implementation
detail of that helper function.

The implementation looks as good as the original "command" with that
change at this point.  Documentation may need a bit more polishing.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v3] [GSOC]trailer: pass arg as positional parameter
  2021-03-25 22:28     ` Junio C Hamano
@ 2021-03-26 13:29       ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-03-26 13:29 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年3月26日周五 上午6:28写道:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > Original implementation of `trailer.<token>.command` use
>
> uses
>
> > `strbuf_replace` to replace $ARG in command with the <value>
> > of the trailer, but it have a problem: `strbuf_replace`
>
> has
>
> > replace the $ARG only once, If the user's trailer command
>
> replaces the $ARG only once.
>

Okay... singular and plural problems.

> > have used more than one $ARG, the remaining replacement will
> > fail.
>
> "will fail" is quite vague.  It is just left unreplaced, and if the
> user expects all of them to be replaced, then the expectation and
> reality would not match, but all of that you have already said by
> "replaces the $ARG only once.", so I think this sentence should be
> removed.
>

Indeed so.

> > If directly modify the implementation of the original
> > `trailer.<token>.command`, The user’s previous `'$ARG'` in
> > trailer command will not be replaced.
>
> That statement does not make much sense.  Depending on the way how
> the implementation is "directly" modified, you can fix the "replaces
> only once" problem without introducing such a problem.  Just look
> for '$ARG' in the string and replace all of them, not just the first
> one.  It's not too difficult.
>
> This confusion primarily comes from the fact that you forgot to
> explain the other problem you are fixing, I think.  Even though the
> trailer.<token>.command documentation implies that the user is
> expected to give a shell script or some sort as the command and the
> use of $ARG makes it look like a shell variable, the original
> implementation does not treat it as a shell variable at all.  And
> the textual replacement is done without making sure the value being
> replaced has characters with special meaning in the shell language.
>

Yes! The accidental injection problem caused should have been the focus
of my explanation.

> So existing .command may incorrectly use $ARG inside a single-quote
> pair and expect it to be replaced to a string inside a single-quote
> pair.  A malformed, or worse, malicious, value would escape out of
> the single-quote pair (remember, the '; rm -fr .' example?) and
> execute arbitrary code in an unexpected way.  The (ungrammatical)
> "if directly modify the implementation" refers to a potential way to
> fix these two problems at the same time by doing the $ARG thing
> without using textual replacement, namely, exporting the value as an
> environment variable ARG.  If that approach was taken, then, $ARG
> enclosed in a single-quote pair will no longer be replaced, which
> makes it a backward incompatible change.
>

Oh, I remeber it: terrible shell injection. Specifically, it looks like this:

$ git config trailer.sign.command "git log --author='\$ARG'"
$ git interpret-trailers --trailer "sign = adl' && rm -rf ./repo/'"

now I know that should be "backward incompatible change" as you said.

> But without describing what solution you are talking about, your
> three-line description does not make much sense.
>
> > So now add new config
> > "trailer.<token>.cmd", pass trailer's value as positional
> > parameter 1 to the user's command, the user can use $1 as
> > trailer's value, to implement original variable replacement.
> >
> > If the user has these two configuration: "trailer.<token>.cmd"
> > and "trailer.<token>.command", "cmd" will execute and "command"
> > will not executed.
> >
> > Original `trailer.<token>.command` can still be used until git
> > completely abandoned it.
> >
> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>
>
> Let's rewrite it completely.
>
>         The `trailer.<token>.command` configuration variable
>         specifies a command (run via the shall, so it does not have
>         to be a single name of 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.  This has two 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 unmatching 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).
>
>         Introduce a new `trailer.<token>.cmd` configuration that
>         takes higher precedence to deprecate and eventually remove
>         `trailer.<token>.command`, which passes the value as a
>         parameter to the command.  Instead of "$ARG", the users will
>         refer to the value as positional argument, $1, in their
>         scripts.
>
> I tried to cover everything we need to tell the reviewers about this
> change with the above.  Did I miss anything?

Nothing to blame, feature of the old command, 2 problems, 1 solution,
this is what the log should look like.

>
> > +trailer.<token>.cmd::
> > +     Similar to 'trailer.<token>.command'. But the difference is that
> > +     `$1` is used in the command to replace the value of the trailer
> > +     instead of the original `$ARG`, which means that we can pass the
> > +     trailer value multiple times in the command.
>
> We eventually want to deprecate the .command, so we'd prefer not to
> rely on its description too much (e.g. try to find a way to say what
> you want to say without "instead of the original `$ARG`").
>

Oh! here I can’t rely on the documentation of the old `.command`, otherwise it’s
not easy to delete the old documentation.

>         The command specified by this configuration variable is run
>         with a single parameter, which is the <value> part of an
>         existing trailer with the same <token>.  The output from the
>         command is then used as the value for the <token> in the
>         resulting trailer.
>
> would be the replacement for the part that talks about $ARG in the
> description of trailer.<token>.command.
>
> The original description for `trailer.<token>.command` is so jumbled
> (not your failure at all) that I had a hard time to understand what
> it is trying to say (e.g. what does "as if a special <token>=<value>
> argument were added at the beginning of the command line" mean?  Is
> it making a one-shot export of environment variable to run the
> command???), so the above may need further adjustment.  Christian?
> Care to help out?
>
> > +     E.g. `git config trailer.sign.cmd "test -n \"$1\" && echo \"$1\" || true "`.
>
> An example is good.  There is a whole EXAMPLES section in this
> manual page, and the one that uses $ARG may be a good candidate to
> look at and change to use .cmd (instead of .command).
>

Okay, I will modify the paragraphs containing `.command` in these examples.

> > +     If the user has these two configuration: "trailer.<token>.cmd"
> > +     and "trailer.<token>.command", "cmd" will be executed and "command"
> > +     will not be executed.
>
>         When both .cmd and .command are given for the same <token>,
>         .cmd is used and .command is ignored.
>
> > diff --git a/trailer.c b/trailer.c
> > index be4e9726421c..634d3f1ff04a 100644
> > --- a/trailer.c
> > +++ b/trailer.c
> > @@ -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(const char *command, const char *cmd_, 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 (cmd_) {
> > +             strbuf_addstr(&cmd, cmd_);
> > +             strvec_push(&cp.args, cmd.buf);
> > +             if (arg)
> > +                     strvec_push(&cp.args, arg);
> > +     } else if (command) {
> > +             strbuf_addstr(&cmd, command);
> > +             strvec_push(&cp.args, cmd.buf);
> > +             if (arg)
> > +                     strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
> > +     }
>
> OK.  it is clear cmd_ takes precedence this way.
>
> Later (not as part of this patch, but a few releases down the road),
> we may want to add a warning() about using a deprecated feature when
> "else if (command)" block is taken.
>

Fine, I will keep this version of "cmd priority execution" for now.

> > @@ -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.command, arg_tok->conf.cmd, arg);
>
> It might be cleaner to just pass arg_tok->conf to apply_command()
> and hide "cmd takes precedence over command" as an implementation
> detail of that helper function.
>
> The implementation looks as good as the original "command" with that
> change at this point.  Documentation may need a bit more polishing.
>

you're right.

> Thanks.

Thanks, Junio.
You and the people in the git community are very enthusiastic,
You have patiently explained these small mistakes that I made,
and taught me a lot of problems that I didn't notice.

Grateful.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-25 11:53   ` [PATCH v3] " ZheNing Hu via GitGitGadget
  2021-03-25 22:28     ` Junio C Hamano
@ 2021-03-26 16:13     ` ZheNing Hu via GitGitGadget
  2021-03-27 18:04       ` Junio C Hamano
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
  1 sibling, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-26 16:13 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

The `trailer.<token>.command` configuration variable
specifies a command (run via the shell, so it does not have
to be a single name of 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.  This has two 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 unmatching 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).

Introduce a new `trailer.<token>.cmd` configuration that
takes higher precedence to deprecate and eventually remove
`trailer.<token>.command`, which passes the value as a
parameter to the command.  Instead of "$ARG", the users will
refer to the value as positional argument, $1, in their
scripts.

Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC]trailer: pass arg as positional parameter
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG.
    
    Now pass trailer value as $1 to the trailer command with another
    trailer.<token>.cmd config.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v4
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v4
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v3:

 1:  b268ecd7b395 ! 1:  e2bbdcb943c2 [GSOC]trailer: pass arg as positional parameter
     @@ Metadata
       ## Commit message ##
          [GSOC]trailer: pass arg as positional parameter
      
     -    Original implementation of `trailer.<token>.command` use
     -    `strbuf_replace` to replace $ARG in command with the <value>
     -    of the trailer, but it have a problem: `strbuf_replace`
     -    replace the $ARG only once, If the user's trailer command
     -    have used more than one $ARG, the remaining replacement will
     -    fail.
     +    The `trailer.<token>.command` configuration variable
     +    specifies a command (run via the shell, so it does not have
     +    to be a single name of 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.  This has two downsides:
      
     -    If directly modify the implementation of the original
     -    `trailer.<token>.command`, The user’s previous `'$ARG'` in
     -    trailer command will not be replaced. So now add new config
     -    "trailer.<token>.cmd", pass trailer's value as positional
     -    parameter 1 to the user's command, the user can use $1 as
     -    trailer's value, to implement original variable replacement.
     +    * 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.
      
     -    If the user has these two configuration: "trailer.<token>.cmd"
     -    and "trailer.<token>.command", "cmd" will execute and "command"
     -    will not executed.
     +    * 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 unmatching 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).
      
     -    Original `trailer.<token>.command` can still be used until git
     -    completely abandoned it.
     +    Introduce a new `trailer.<token>.cmd` configuration that
     +    takes higher precedence to deprecate and eventually remove
     +    `trailer.<token>.command`, which passes the value as a
     +    parameter to the command.  Instead of "$ARG", the users will
     +    refer to the value as positional argument, $1, in their
     +    scripts.
      
     +    Helped-by: Junio C Hamano <gitster@pobox.com>
          Signed-off-by: ZheNing Hu <adlternative@gmail.com>
      
       ## Documentation/git-interpret-trailers.txt ##
     @@ Documentation/git-interpret-trailers.txt: also be executed for each of these arg
       the command.
       
      +trailer.<token>.cmd::
     -+	Similar to 'trailer.<token>.command'. But the difference is that
     -+	`$1` is used in the command to replace the value of the trailer
     -+	instead of the original `$ARG`, which means that we can pass the
     -+	trailer value multiple times in the command.
     -+	E.g. `git config trailer.sign.cmd "test -n \"$1\" && echo \"$1\" || true "`.
     -+	If the user has these two configuration: "trailer.<token>.cmd"
     -+	and "trailer.<token>.command", "cmd" will be executed and "command"
     -+	will not be executed.
     ++	The command specified by this configuration variable is run
     ++	with a single parameter, which is the <value> part of an
     ++	existing trailer with the same <token>.  The output from the
     ++	command is then used as the value for the <token> in the
     ++	resulting trailer.
     ++	The command is expected to replace `trailer.<token>.cmd`.
     ++	When both .cmd and .command are given for the same <token>,
     ++        .cmd is used and .command is ignored.
      +
       EXAMPLES
       --------
       
     +@@ Documentation/git-interpret-trailers.txt: $ git format-patch -1
     + $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch
     + ------------
     + 
     +-* Configure a 'sign' trailer with a command to automatically add a
     ++* Configure a 'sign' trailer with a cmd to automatically add a
     +   'Signed-off-by: ' with the author information only if there is no
     +   'Signed-off-by: ' already, and show how it works:
     + +
     +@@ Documentation/git-interpret-trailers.txt: $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
     + $ git config trailer.sign.key "Signed-off-by: "
     + $ git config trailer.sign.ifmissing add
     + $ git config trailer.sign.ifexists doNothing
     +-$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
     ++$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
     + $ git interpret-trailers <<EOF
     + > EOF
     + 
     +@@ Documentation/git-interpret-trailers.txt: subject
     + Fix #42
     + ------------
     + 
     +-* Configure a 'see' trailer with a command to show the subject of a
     ++* Configure a 'see' trailer with a cmd to show the subject of a
     +   commit that is related, and show how it works:
     + +
     + ------------
     + $ git config trailer.see.key "See-also: "
     + $ git config trailer.see.ifExists "replace"
     + $ git config trailer.see.ifMissing "doNothing"
     +-$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
     ++$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
     + $ git interpret-trailers <<EOF
     + > subject
     + > 
      
       ## t/t7513-interpret-trailers.sh ##
      @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       }
       
      -static char *apply_command(const char *command, const char *arg)
     -+static char *apply_command(const char *command, const char *cmd_, const char *arg)
     ++static char *apply_command(struct conf_info *conf, const char *arg)
       {
       	struct strbuf cmd = STRBUF_INIT;
       	struct strbuf buf = STRBUF_INIT;
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
      -		strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
      -
      -	strvec_push(&cp.args, cmd.buf);
     -+	if (cmd_) {
     -+		strbuf_addstr(&cmd, cmd_);
     ++	if (conf->cmd) {
     ++		strbuf_addstr(&cmd, conf->cmd);
      +		strvec_push(&cp.args, cmd.buf);
      +		if (arg)
      +			strvec_push(&cp.args, arg);
     -+	} else if (command) {
     -+		strbuf_addstr(&cmd, command);
     ++	} else if (conf->command) {
     ++		strbuf_addstr(&cmd, conf->command);
      +		strvec_push(&cp.args, cmd.buf);
      +		if (arg)
      +			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
     @@ trailer.c: static void apply_item_command(struct trailer_item *in_tok, struct ar
       				arg = xstrdup("");
       		}
      -		arg_tok->value = apply_command(arg_tok->conf.command, arg);
     -+		arg_tok->value = apply_command(arg_tok->conf.command, arg_tok->conf.cmd, arg);
     ++		arg_tok->value = apply_command(&arg_tok->conf, arg);
       		free((char *)arg);
       	}
       }


 Documentation/git-interpret-trailers.txt | 18 +++++++---
 t/t7513-interpret-trailers.sh            | 43 +++++++++++++++++++++++-
 trailer.c                                | 37 ++++++++++++++------
 3 files changed, 82 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..b73f9c8d71eb 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of
 these arguments, if any, will be used to replace the `$ARG` string in
 the command.
 
+trailer.<token>.cmd::
+	The command specified by this configuration variable is run
+	with a single parameter, which is the <value> part of an
+	existing trailer with the same <token>.  The output from the
+	command is then used as the value for the <token> in the
+	resulting trailer.
+	The command is expected to replace `trailer.<token>.cmd`.
+	When both .cmd and .command are given for the same <token>,
+        .cmd is used and .command is ignored.
+
 EXAMPLES
 --------
 
@@ -301,7 +311,7 @@ $ git format-patch -1
 $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch
 ------------
 
-* Configure a 'sign' trailer with a command to automatically add a
+* Configure a 'sign' trailer with a cmd to automatically add a
   'Signed-off-by: ' with the author information only if there is no
   'Signed-off-by: ' already, and show how it works:
 +
@@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
 $ git config trailer.sign.key "Signed-off-by: "
 $ git config trailer.sign.ifmissing add
 $ git config trailer.sign.ifexists doNothing
-$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
+$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
 $ git interpret-trailers <<EOF
 > EOF
 
@@ -333,14 +343,14 @@ subject
 Fix #42
 ------------
 
-* Configure a 'see' trailer with a command to show the subject of a
+* Configure a 'see' trailer with a cmd to show the subject of a
   commit that is related, and show how it works:
 +
 ------------
 $ git config trailer.see.key "See-also: "
 $ git config trailer.see.ifExists "replace"
 $ git config trailer.see.ifMissing "doNothing"
-$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
+$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
 $ git interpret-trailers <<EOF
 > subject
 > 
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..059beec0c0de 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -1274,9 +1274,50 @@ test_expect_success 'setup a commit' '
 	git commit -m "Add file a.txt"
 '
 
+test_expect_success 'with cmd and $1' '
+	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 (%s)\" \
+		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --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 '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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..278e40974a4c 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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);
+		strvec_push(&cp.args, cmd.buf);
+		if (arg)
+			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+	}
 	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(""),

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-26 16:13     ` [PATCH v4] " ZheNing Hu via GitGitGadget
@ 2021-03-27 18:04       ` Junio C Hamano
  2021-03-27 19:53         ` Christian Couder
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
  1 sibling, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-03-27 18:04 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of
>  these arguments, if any, will be used to replace the `$ARG` string in
>  the command.
>  
> +trailer.<token>.cmd::
> +	The command specified by this configuration variable is run
> +	with a single parameter, which is the <value> part of an
> +	existing trailer with the same <token>.  The output from the
> +	command is then used as the value for the <token> in the
> +	resulting trailer.
> +	The command is expected to replace `trailer.<token>.cmd`.
> +	When both .cmd and .command are given for the same <token>,
> +        .cmd is used and .command is ignored.

Christian, because ".cmd" is trying to eventually replace it, I find
it a bit disturbing that the description we give here looks a lot
smaller compared to the one for ".command".  I am afraid that we may
have failed to reproduce something important from the description of
the ".command" for the above; care to rend a hand or two here to
complete the description?

As I cannot grok what the description for ".command" is trying to
say, especially around this part:

    When this option is specified, the behavior is as if a special
    '<token>=<value>' argument were added at the beginning of the command
    line, where <value> is ...

and

    If some '<token>=<value>' arguments are also passed on the command
    line, when a 'trailer.<token>.command' is configured, the command will
    also be executed for each of these arguments.

I cannot quite judge if what we came up with in the above
description is sufficient.

> -* Configure a 'sign' trailer with a command to automatically add a
> +* Configure a 'sign' trailer with a cmd to automatically add a
>    'Signed-off-by: ' with the author information only if there is no
>    'Signed-off-by: ' already, and show how it works:
>  +
> @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
>  $ git config trailer.sign.key "Signed-off-by: "
>  $ git config trailer.sign.ifmissing add
>  $ git config trailer.sign.ifexists doNothing
> -$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
> +$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
>  $ git interpret-trailers <<EOF
>  > EOF

This change would definitely be needed when the support for
".command" is removed after deprecation period.  As it does not take
any argument, .cmd and .command should behave identically, so making
this change now, without waiting, may make sense.

> @@ -333,14 +343,14 @@ subject
>  Fix #42
>  ------------
>  
> -* Configure a 'see' trailer with a command to show the subject of a
> +* Configure a 'see' trailer with a cmd to show the subject of a
>    commit that is related, and show how it works:
>  +
>  ------------
>  $ git config trailer.see.key "See-also: "
>  $ git config trailer.see.ifExists "replace"
>  $ git config trailer.see.ifMissing "doNothing"
> -$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
> +$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
>  $ git interpret-trailers <<EOF
>  > subject

This, too, but until ".command" is removed, wouldn't it be better
for readers to keep both variants, as the distinction between $ARG
and $1 needs to be illustrated?

Besides, the examples given here are not equivalent.  The original
assumes that ARG is there, or it is OK to default to HEAD; the new
one gives no output when $ARG/$1 is not supplied.  It would confuse
readers to give two too-similar-but-subtly-different examles, as
they will be forced to wonder if the difference is something needed
to transition from .command to .cmd (and I am guessing that it is
not).

Rewriting both to use "--pretty=reference" may be worth doing.  As
can be seen in these examples:

git show -s --pretty=reference \$1
git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$1

that it makes the result much easier to read.

Thanks.  Do not send a reroll prematurely; I want to see area
expert's input at this point.



^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-27 18:04       ` Junio C Hamano
@ 2021-03-27 19:53         ` Christian Couder
  2021-03-28 10:46           ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-03-27 19:53 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of
> >  these arguments, if any, will be used to replace the `$ARG` string in
> >  the command.
> >
> > +trailer.<token>.cmd::
> > +     The command specified by this configuration variable is run
> > +     with a single parameter, which is the <value> part of an
> > +     existing trailer with the same <token>.  The output from the
> > +     command is then used as the value for the <token> in the
> > +     resulting trailer.
> > +     The command is expected to replace `trailer.<token>.cmd`.

s/trailer.<token>.cmd/trailer.<token>.command/

> > +     When both .cmd and .command are given for the same <token>,
> > +        .cmd is used and .command is ignored.
>
> Christian, because ".cmd" is trying to eventually replace it, I find
> it a bit disturbing that the description we give here looks a lot
> smaller compared to the one for ".command".  I am afraid that we may
> have failed to reproduce something important from the description of
> the ".command" for the above; care to rend a hand or two here to
> complete the description?

Yeah, sure. I just saw that you already asked about this in this
thread. Sorry for not answering earlier.

> As I cannot grok what the description for ".command" is trying to
> say, especially around this part:
>
>     When this option is specified, the behavior is as if a special
>     '<token>=<value>' argument were added at the beginning of the command
>     line, where <value> is ...

This is because when a number of trailers are passed on the command
line, and some other trailers are in the input file, the order in
which the different trailers are processed and their priorities can be
important. So by saying the above, people can get an idea about at
which point and with which priority a trailer coming from such a
config option will be processed.

> and
>
>     If some '<token>=<value>' arguments are also passed on the command
>     line, when a 'trailer.<token>.command' is configured, the command will
>     also be executed for each of these arguments.

Yeah, this means that when a 'trailer.foo.command' is configured, it
is always executed at least once. The first time it is executed, it is
passed nothing ($ARG is replaced with the empty string). Then for each
'foo=<value>' argument passed on the command line, it is executed once
more with $ARG replaced by <value>.

The reason it is always executed first with $ARG replaced with the
empty string is that this way it makes it possible to set up commands
that will always be executed when `git interpret-trailers` is run.
This makes it possible to automatically add some trailers if they are
missing for example.

Another way to do it would be to have another config option called
`trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
`trailer.<token>.cmd` should be run even if no '<token>=<value>'
argument is passed on the command line. As we are introducing
`trailer.<token>.cmd`, it's a good time to wonder if this would be a
better design. But this issue is quite complex, because of the fact
that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
take a part in deciding if the command will be run.

This mechanism is the reason why a trick, when setting up a
'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'
to "replace", so that the first time the command is run (with $ARG
replaced with the empty string) it will add a foo trailer with a
default value, and if it is run another time, because a 'foo=bar'
argument is passed on the command line, then the trailer with the
default value will be replaced by the value computed from running the
command again with $ARG replaced with "bar".

Another trick is to have the command output nothing when $ARG is the
empty string along with using --trim-empty. This way the command will
create an empty trailer, when it is run the first time, and if it's
not another time, then this empty trailer will be removed because of
--trim-empty.

> I cannot quite judge if what we came up with in the above
> description is sufficient.

I don't think it's sufficient. I think that, while we are at it, a bit
more thinking/discussion is required to make sure we want to keep the
same design as 'trailer.<token>.command'.

> > -* Configure a 'sign' trailer with a command to automatically add a
> > +* Configure a 'sign' trailer with a cmd to automatically add a
> >    'Signed-off-by: ' with the author information only if there is no
> >    'Signed-off-by: ' already, and show how it works:
> >  +
> > @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
> >  $ git config trailer.sign.key "Signed-off-by: "
> >  $ git config trailer.sign.ifmissing add
> >  $ git config trailer.sign.ifexists doNothing
> > -$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
> > +$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
> >  $ git interpret-trailers <<EOF
> >  > EOF
>
> This change would definitely be needed when the support for
> ".command" is removed after deprecation period.  As it does not take
> any argument, .cmd and .command should behave identically, so making
> this change now, without waiting, may make sense.

By the way the above example is an example of why we might want any
configured command to be executed at least once, even when no
corresponding '<token>=<value>' argument is passed on the command
line.

> > @@ -333,14 +343,14 @@ subject
> >  Fix #42
> >  ------------
> >
> > -* Configure a 'see' trailer with a command to show the subject of a
> > +* Configure a 'see' trailer with a cmd to show the subject of a
> >    commit that is related, and show how it works:
> >  +
> >  ------------
> >  $ git config trailer.see.key "See-also: "
> >  $ git config trailer.see.ifExists "replace"
> >  $ git config trailer.see.ifMissing "doNothing"
> > -$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
> > +$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
> >  $ git interpret-trailers <<EOF
> >  > subject
>
> This, too, but until ".command" is removed, wouldn't it be better
> for readers to keep both variants, as the distinction between $ARG
> and $1 needs to be illustrated?
>
> Besides, the examples given here are not equivalent.  The original
> assumes that ARG is there, or it is OK to default to HEAD; the new
> one gives no output when $ARG/$1 is not supplied.

Yeah, I agree they are not equivalent.

> It would confuse
> readers to give two too-similar-but-subtly-different examles, as
> they will be forced to wonder if the difference is something needed
> to transition from .command to .cmd (and I am guessing that it is
> not).

I agree.

> Rewriting both to use "--pretty=reference" may be worth doing.  As
> can be seen in these examples:
>
> git show -s --pretty=reference \$1
> git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$1
>
> that it makes the result much easier to read.

Yeah, thanks for the good suggestion.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-27 19:53         ` Christian Couder
@ 2021-03-28 10:46           ` ZheNing Hu
  2021-03-29  9:04             ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-03-28 10:46 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

Christian Couder <christian.couder@gmail.com> 于2021年3月28日周日 上午3:53写道:
>
> On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
> >
> > > @@ -252,6 +252,16 @@ also be executed for each of these arguments. And the <value> part of
> > >  these arguments, if any, will be used to replace the `$ARG` string in
> > >  the command.
> > >
> > > +trailer.<token>.cmd::
> > > +     The command specified by this configuration variable is run
> > > +     with a single parameter, which is the <value> part of an
> > > +     existing trailer with the same <token>.  The output from the
> > > +     command is then used as the value for the <token> in the
> > > +     resulting trailer.
> > > +     The command is expected to replace `trailer.<token>.cmd`.
>
> s/trailer.<token>.cmd/trailer.<token>.command/
>
> > > +     When both .cmd and .command are given for the same <token>,
> > > +        .cmd is used and .command is ignored.
> >
> > Christian, because ".cmd" is trying to eventually replace it, I find
> > it a bit disturbing that the description we give here looks a lot
> > smaller compared to the one for ".command".  I am afraid that we may
> > have failed to reproduce something important from the description of
> > the ".command" for the above; care to rend a hand or two here to
> > complete the description?
>
> Yeah, sure. I just saw that you already asked about this in this
> thread. Sorry for not answering earlier.
>
> > As I cannot grok what the description for ".command" is trying to
> > say, especially around this part:
> >
> >     When this option is specified, the behavior is as if a special
> >     '<token>=<value>' argument were added at the beginning of the command
> >     line, where <value> is ...
>
> This is because when a number of trailers are passed on the command
> line, and some other trailers are in the input file, the order in
> which the different trailers are processed and their priorities can be
> important. So by saying the above, people can get an idea about at
> which point and with which priority a trailer coming from such a
> config option will be processed.
>

This shows that .command itself has the characteristic of alwaysRun:
even if <token> <value> is not specified, the shell in .command will be
executed at least once, $ARG is empty by default. This is why I asked
`log --author=$ARG -1` will show the last commit identity when `--trailer`
 is not used.

> > and
> >
> >     If some '<token>=<value>' arguments are also passed on the command
> >     line, when a 'trailer.<token>.command' is configured, the command will
> >     also be executed for each of these arguments.
>
> Yeah, this means that when a 'trailer.foo.command' is configured, it
> is always executed at least once. The first time it is executed, it is
> passed nothing ($ARG is replaced with the empty string). Then for each
> 'foo=<value>' argument passed on the command line, it is executed once
> more with $ARG replaced by <value>.
>
> The reason it is always executed first with $ARG replaced with the
> empty string is that this way it makes it possible to set up commands
> that will always be executed when `git interpret-trailers` is run.
> This makes it possible to automatically add some trailers if they are
> missing for example.
>

Yes, $ARG or $1 are always exist because of:

               arg = xstrdup("");

so I think maybe we don't even need this judge in `apply_command`?
+               if (arg)
+                       strvec_push(&cp.args, arg);

> Another way to do it would be to have another config option called
> `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
> `trailer.<token>.cmd` should be run even if no '<token>=<value>'
> argument is passed on the command line. As we are introducing
> `trailer.<token>.cmd`, it's a good time to wonder if this would be a
> better design. But this issue is quite complex, because of the fact
> that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
> take a part in deciding if the command will be run.
>

In fact, I would prefer this design, because if I don’t add any trailers,
the trailer.<token>.command I set will be executed, which may be very
distressing sometimes, and `alwayRunCmd` is the user I hope that "trailers"
can be added automatically, and other trailers.<token>.command will not be
executed automatically. This allows the user to reasonably configure the
commands that need to be executed. This must be a very comfortable thing.

But as you said, to disable the automatic addition in the original .command
and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.
Perhaps future series of patches can be considered to do it.

> This mechanism is the reason why a trick, when setting up a
> 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'
> to "replace", so that the first time the command is run (with $ARG
> replaced with the empty string) it will add a foo trailer with a
> default value, and if it is run another time, because a 'foo=bar'
> argument is passed on the command line, then the trailer with the
> default value will be replaced by the value computed from running the
> command again with $ARG replaced with "bar".
>
> Another trick is to have the command output nothing when $ARG is the
> empty string along with using --trim-empty. This way the command will
> create an empty trailer, when it is run the first time, and if it's
> not another time, then this empty trailer will be removed because of
> --trim-empty.
>

It looks very practical indeed.

> > I cannot quite judge if what we came up with in the above
> > description is sufficient.
>
> I don't think it's sufficient. I think that, while we are at it, a bit
> more thinking/discussion is required to make sure we want to keep the
> same design as 'trailer.<token>.command'.

Sure. I agree that more discussion is needed.
I think if the documents that once belonged to .command are copied to .cmd,
will the readers be too burdensome to read them? Will it be better to migrate
its documentation until we completely delete .command?

>
> > > -* Configure a 'sign' trailer with a command to automatically add a
> > > +* Configure a 'sign' trailer with a cmd to automatically add a
> > >    'Signed-off-by: ' with the author information only if there is no
> > >    'Signed-off-by: ' already, and show how it works:
> > >  +
> > > @@ -309,7 +319,7 @@ $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
> > >  $ git config trailer.sign.key "Signed-off-by: "
> > >  $ git config trailer.sign.ifmissing add
> > >  $ git config trailer.sign.ifexists doNothing
> > > -$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
> > > +$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
> > >  $ git interpret-trailers <<EOF
> > >  > EOF
> >
> > This change would definitely be needed when the support for
> > ".command" is removed after deprecation period.  As it does not take
> > any argument, .cmd and .command should behave identically, so making
> > this change now, without waiting, may make sense.
>
> By the way the above example is an example of why we might want any
> configured command to be executed at least once, even when no
> corresponding '<token>=<value>' argument is passed on the command
> line.

Already noticed that.

>
> > > @@ -333,14 +343,14 @@ subject
> > >  Fix #42
> > >  ------------
> > >
> > > -* Configure a 'see' trailer with a command to show the subject of a
> > > +* Configure a 'see' trailer with a cmd to show the subject of a
> > >    commit that is related, and show how it works:
> > >  +
> > >  ------------
> > >  $ git config trailer.see.key "See-also: "
> > >  $ git config trailer.see.ifExists "replace"
> > >  $ git config trailer.see.ifMissing "doNothing"
> > > -$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
> > > +$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
> > >  $ git interpret-trailers <<EOF
> > >  > subject
> >
> > This, too, but until ".command" is removed, wouldn't it be better
> > for readers to keep both variants, as the distinction between $ARG
> > and $1 needs to be illustrated?

So the correct solution should be to keep the original .command Examples,
and then give the .cmd examples again.

> >
> > Besides, the examples given here are not equivalent.  The original
> > assumes that ARG is there, or it is OK to default to HEAD; the new
> > one gives no output when $ARG/$1 is not supplied.
>
> Yeah, I agree they are not equivalent.
>
> > It would confuse
> > readers to give two too-similar-but-subtly-different examles, as
> > they will be forced to wonder if the difference is something needed
> > to transition from .command to .cmd (and I am guessing that it is
> > not).
>
> I agree.

OK...I will modify it.

>
> > Rewriting both to use "--pretty=reference" may be worth doing.  As
> > can be seen in these examples:
> >
> > git show -s --pretty=reference \$1
> > git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$1
> >
> > that it makes the result much easier to read.
>
> Yeah, thanks for the good suggestion.

Yes, `--pretty=reference` is similar to `--format="%h(%s)"` and provides better
readability.

Thanks,Junio and Christian!

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-28 10:46           ` ZheNing Hu
@ 2021-03-29  9:04             ` Christian Couder
  2021-03-29 13:43               ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-03-29  9:04 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

On Sun, Mar 28, 2021 at 12:46 PM ZheNing Hu <adlternative@gmail.com> wrote:
>
> Christian Couder <christian.couder@gmail.com> 于2021年3月28日周日 上午3:53写道:
> >
> > On Sat, Mar 27, 2021 at 7:04 PM Junio C Hamano <gitster@pobox.com> wrote:

> > > As I cannot grok what the description for ".command" is trying to
> > > say, especially around this part:
> > >
> > >     When this option is specified, the behavior is as if a special
> > >     '<token>=<value>' argument were added at the beginning of the command
> > >     line, where <value> is ...
> >
> > This is because when a number of trailers are passed on the command
> > line, and some other trailers are in the input file, the order in
> > which the different trailers are processed and their priorities can be
> > important. So by saying the above, people can get an idea about at
> > which point and with which priority a trailer coming from such a
> > config option will be processed.
>
> This shows that .command itself has the characteristic of alwaysRun:
> even if <token> <value> is not specified, the shell in .command will be
> executed at least once, $ARG is empty by default. This is why I asked
> `log --author=$ARG -1` will show the last commit identity when `--trailer`
>  is not used.

Yeah, that's the reason.

> > > and
> > >
> > >     If some '<token>=<value>' arguments are also passed on the command
> > >     line, when a 'trailer.<token>.command' is configured, the command will
> > >     also be executed for each of these arguments.
> >
> > Yeah, this means that when a 'trailer.foo.command' is configured, it
> > is always executed at least once. The first time it is executed, it is
> > passed nothing ($ARG is replaced with the empty string). Then for each
> > 'foo=<value>' argument passed on the command line, it is executed once
> > more with $ARG replaced by <value>.
> >
> > The reason it is always executed first with $ARG replaced with the
> > empty string is that this way it makes it possible to set up commands
> > that will always be executed when `git interpret-trailers` is run.
> > This makes it possible to automatically add some trailers if they are
> > missing for example.
>
> Yes, $ARG or $1 are always exist because of:
>
>                arg = xstrdup("");
>
> so I think maybe we don't even need this judge in `apply_command`?
> +               if (arg)
> +                       strvec_push(&cp.args, arg);

Yeah, I haven't looked at the code, but that might be a good
simplification. If you work on this, please submit it in a separate
commit.

> > Another way to do it would be to have another config option called
> > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
> > `trailer.<token>.cmd` should be run even if no '<token>=<value>'
> > argument is passed on the command line. As we are introducing
> > `trailer.<token>.cmd`, it's a good time to wonder if this would be a
> > better design. But this issue is quite complex, because of the fact
> > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
> > take a part in deciding if the command will be run.

Actually after thinking about it, I think it might be better, instead
of `trailer.<token>.alwaysRunCmd`, to add something like
`trailer.<token>.runMode` that could take multiple values like:

- "beforeCLI": would make it run once, like ".command" does now before
any CLI trailer are processed

- "forEachCLIToken": would make it run once for each trailer that has
the token, like ".command" also does now, the difference would be that
the value for the token would be passed in the $1 argument

- "afterCLI": would make it run once after all the CLI trailers have
been processed and it could pass the different values for the token if
any in different arguments: $1, $2, $3, ...

This would make it possible to extend later if the need arises for
more different times or ways to run configured commands.

> In fact, I would prefer this design, because if I don’t add any trailers,
> the trailer.<token>.command I set will be executed, which may be very
> distressing sometimes, and `alwayRunCmd` is the user I hope that "trailers"
> can be added automatically, and other trailers.<token>.command will not be
> executed automatically. This allows the user to reasonably configure the
> commands that need to be executed. This must be a very comfortable thing.

I agree that it should be easier and more straightforward, than it is
now, to configure this.

> But as you said, to disable the automatic addition in the original .command
> and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.
> Perhaps future series of patches can be considered to do it.

Yeah, support for `trailer.<token>.runMode` might be added in
different commits at least and possibly later in a different patch
series. There are the following issues to resolve, though, if we want
to focus only on a new ".cmd" config option:

- how and when should it run by default,
- how to explain that in the doc, and maybe
- how to improve the current description of what happens for ".command"

> > This mechanism is the reason why a trick, when setting up a
> > 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'
> > to "replace", so that the first time the command is run (with $ARG
> > replaced with the empty string) it will add a foo trailer with a
> > default value, and if it is run another time, because a 'foo=bar'
> > argument is passed on the command line, then the trailer with the
> > default value will be replaced by the value computed from running the
> > command again with $ARG replaced with "bar".
> >
> > Another trick is to have the command output nothing when $ARG is the
> > empty string along with using --trim-empty. This way the command will
> > create an empty trailer, when it is run the first time, and if it's
> > not another time, then this empty trailer will be removed because of
> > --trim-empty.
> >
>
> It looks very practical indeed.
>
> > > I cannot quite judge if what we came up with in the above
> > > description is sufficient.
> >
> > I don't think it's sufficient. I think that, while we are at it, a bit
> > more thinking/discussion is required to make sure we want to keep the
> > same design as 'trailer.<token>.command'.
>
> Sure. I agree that more discussion is needed.
> I think if the documents that once belonged to .command are copied to .cmd,
> will the readers be too burdensome to read them? Will it be better to migrate
> its documentation until we completely delete .command?

My opinion (if we focus only on adding ".cmd") is that:

- for simplicity for now it should run at the same time as ".command",
the only difference being how the argument is passed (using $1 instead
of textually replacing $ARG)
- the doc for ".command" should be first improved if possible, and
then moved over to ".cmd" saying for ".command" that ".command" is
deprecated in favor of ".cmd" but otherwise works as ".cmd" except
that instead using $1 the value is passed by textually replacing $ARG
which could be a safety and correctness issue.

Another way to work on all this, would be to first work on adding
support for `trailer.<token>.runMode` and on improving existing
documentation, and then to add ".cmd", which could then by default use
a different ".runMode" than ".command".

> > > This, too, but until ".command" is removed, wouldn't it be better
> > > for readers to keep both variants, as the distinction between $ARG
> > > and $1 needs to be illustrated?
>
> So the correct solution should be to keep the original .command Examples,
> and then give the .cmd examples again.

Maybe we could take advantage of ".cmd" to show other nice
possibilities to use all of this. Especially if support for `git
commit --trailer ...` is already merged, we might be able to use it in
those examples, or perhaps add some examples to the git commit doc.

Best,
Christian.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-29  9:04             ` Christian Couder
@ 2021-03-29 13:43               ` ZheNing Hu
  2021-03-30  8:45                 ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-03-29 13:43 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道:
>
> >
> > Yes, $ARG or $1 are always exist because of:
> >
> >                arg = xstrdup("");
> >
> > so I think maybe we don't even need this judge in `apply_command`?
> > +               if (arg)
> > +                       strvec_push(&cp.args, arg);
>
> Yeah, I haven't looked at the code, but that might be a good
> simplification. If you work on this, please submit it in a separate
> commit.
>

Well, if necessary, I'll put it in another commit, maybe I should double check
to see if there's anything special going on.

> > > Another way to do it would be to have another config option called
> > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
> > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'
> > > argument is passed on the command line. As we are introducing
> > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a
> > > better design. But this issue is quite complex, because of the fact
> > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
> > > take a part in deciding if the command will be run.
>
> Actually after thinking about it, I think it might be better, instead
> of `trailer.<token>.alwaysRunCmd`, to add something like
> `trailer.<token>.runMode` that could take multiple values like:
>

If really can achieve it is certainly better than 'alwaysRunCmd'.
The following three small configuration options look delicious.
But I think it needs to be discussed in more detail:

> - "beforeCLI": would make it run once, like ".command" does now before
> any CLI trailer are processed
>

Does "beforeCLI" handle all trailers? Or is it just doing something to add empty
value trailers?

> - "forEachCLIToken": would make it run once for each trailer that has
> the token, like ".command" also does now, the difference would be that
> the value for the token would be passed in the $1 argument
>

This is exactly same as before.

> - "afterCLI": would make it run once after all the CLI trailers have
> been processed and it could pass the different values for the token if
> any in different arguments: $1, $2, $3, ...
>

I might get a little confused here: What's the input for $1,$2,$3?
Is users more interested in dealing with trailers value or a line of the
trailer?

> This would make it possible to extend later if the need arises for
> more different times or ways to run configured commands.
>
> > In fact, I would prefer this design, because if I don’t add any trailers,
> > the trailer.<token>.command I set will be executed, which may be very
> > distressing sometimes, and `alwayRunCmd` is the user I hope that "trailers"
> > can be added automatically, and other trailers.<token>.command will not be
> > executed automatically. This allows the user to reasonably configure the
> > commands that need to be executed. This must be a very comfortable thing.
>
> I agree that it should be easier and more straightforward, than it is
> now, to configure this.
>
> > But as you said, to disable the automatic addition in the original .command
> > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.
> > Perhaps future series of patches can be considered to do it.
>
> Yeah, support for `trailer.<token>.runMode` might be added in
> different commits at least and possibly later in a different patch
> series. There are the following issues to resolve, though, if we want
> to focus only on a new ".cmd" config option:
>
> - how and when should it run by default,

Do you mean that ".cmd" can get rid of the ".command" auto-add problem
in this patch series?
This might be a good idea if I can add the three modes you mentioned above
in the later patch series.

> - how to explain that in the doc, and maybe
> - how to improve the current description of what happens for ".command"
>
> > > This mechanism is the reason why a trick, when setting up a
> > > 'trailer.foo.command' trailer, is to also set 'trailer.foo.ifexists'
> > > to "replace", so that the first time the command is run (with $ARG
> > > replaced with the empty string) it will add a foo trailer with a
> > > default value, and if it is run another time, because a 'foo=bar'
> > > argument is passed on the command line, then the trailer with the
> > > default value will be replaced by the value computed from running the
> > > command again with $ARG replaced with "bar".
> > >
> > > Another trick is to have the command output nothing when $ARG is the
> > > empty string along with using --trim-empty. This way the command will
> > > create an empty trailer, when it is run the first time, and if it's
> > > not another time, then this empty trailer will be removed because of
> > > --trim-empty.
> > >
> >
> > It looks very practical indeed.
> >
> > > > I cannot quite judge if what we came up with in the above
> > > > description is sufficient.
> > >
> > > I don't think it's sufficient. I think that, while we are at it, a bit
> > > more thinking/discussion is required to make sure we want to keep the
> > > same design as 'trailer.<token>.command'.
> >
> > Sure. I agree that more discussion is needed.
> > I think if the documents that once belonged to .command are copied to .cmd,
> > will the readers be too burdensome to read them? Will it be better to migrate
> > its documentation until we completely delete .command?
>
> My opinion (if we focus only on adding ".cmd") is that:
>
> - for simplicity for now it should run at the same time as ".command",
> the only difference being how the argument is passed (using $1 instead
> of textually replacing $ARG)
> - the doc for ".command" should be first improved if possible, and
> then moved over to ".cmd" saying for ".command" that ".command" is
> deprecated in favor of ".cmd" but otherwise works as ".cmd" except
> that instead using $1 the value is passed by textually replacing $ARG
> which could be a safety and correctness issue.
>

I agree with you. There may be need some discretion.

> Another way to work on all this, would be to first work on adding
> support for `trailer.<token>.runMode` and on improving existing
> documentation, and then to add ".cmd", which could then by default use
> a different ".runMode" than ".command".
>

I think the task can be put off until April.
Deal with the easier ".cmd" first.

> > > > This, too, but until ".command" is removed, wouldn't it be better
> > > > for readers to keep both variants, as the distinction between $ARG
> > > > and $1 needs to be illustrated?
> >
> > So the correct solution should be to keep the original .command Examples,
> > and then give the .cmd examples again.
>
> Maybe we could take advantage of ".cmd" to show other nice
> possibilities to use all of this. Especially if support for `git
> commit --trailer ...` is already merged, we might be able to use it in
> those examples, or perhaps add some examples to the git commit doc.
>

Oh, the 'commit --trailer' may still be queuing, It may take a while.

> Best,
> Christian.

Thanks.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-29 13:43               ` ZheNing Hu
@ 2021-03-30  8:45                 ` Christian Couder
  2021-03-30 11:22                   ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-03-30  8:45 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

On Mon, Mar 29, 2021 at 3:44 PM ZheNing Hu <adlternative@gmail.com> wrote:
>
> Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道:
> >
> > >
> > > Yes, $ARG or $1 are always exist because of:
> > >
> > >                arg = xstrdup("");
> > >
> > > so I think maybe we don't even need this judge in `apply_command`?
> > > +               if (arg)
> > > +                       strvec_push(&cp.args, arg);
> >
> > Yeah, I haven't looked at the code, but that might be a good
> > simplification. If you work on this, please submit it in a separate
> > commit.
>
> Well, if necessary, I'll put it in another commit, maybe I should double check
> to see if there's anything special going on.
>
> > > > Another way to do it would be to have another config option called
> > > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
> > > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'
> > > > argument is passed on the command line. As we are introducing
> > > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a
> > > > better design. But this issue is quite complex, because of the fact
> > > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
> > > > take a part in deciding if the command will be run.
> >
> > Actually after thinking about it, I think it might be better, instead
> > of `trailer.<token>.alwaysRunCmd`, to add something like
> > `trailer.<token>.runMode` that could take multiple values like:
>
> If really can achieve it is certainly better than 'alwaysRunCmd'.
> The following three small configuration options look delicious.
> But I think it needs to be discussed in more detail:
>
> > - "beforeCLI": would make it run once, like ".command" does now before
> > any CLI trailer are processed
>
> Does "beforeCLI" handle all trailers? Or is it just doing something to add empty
> value trailers?

I am not sure what you mean by "handle all trailers". What I mean is
that it would just work like ".command" does right now before the
"--trailers ..." options are processed.

Let's suppose the "trailer.foo.command" config option is set to "bar".
Then the "bar" command will be run just before the "--trailers ..."
options are processed and the output of that, let's say "baz" will be
used to add a new "foo: baz" trailer to the ouput of `git
interpret-trailers`.

For example:

-------
$ git -c trailer.foo.command='echo baz' interpret-trailers<<EOF
EOF

foo: baz
-------

In other words an empty value trailer is just a special case when the
command that is run does not output anything. But such commands are
expected to output something not trivial at least in some cases.

See also the example in the doc that uses:

$ git config trailer.sign.command 'echo "$(git config user.name)
<$(git config user.email)>"'

> > - "forEachCLIToken": would make it run once for each trailer that has
> > the token, like ".command" also does now, the difference would be that
> > the value for the token would be passed in the $1 argument
>
> This is exactly same as before.

Yeah it is the same as before when the "--trailers ..." options are
processed, but not before that.

To get exactly the same as before one would need to configure both
"beforeCLI" _and_ "forEachCLIToken", for example like this (note that
we use "--add" when adding "forEachCLIToken"):

$ git config trailer.foo.runMode beforeCLI
$ git config --add trailer.foo.runMode forEachCLIToken
$ git config -l | grep foo
trailer.foo.runmode=beforeCLI
trailer.foo.runmode=forEachCLIToken

> > - "afterCLI": would make it run once after all the CLI trailers have
> > been processed and it could pass the different values for the token if
> > any in different arguments: $1, $2, $3, ...
>
> I might get a little confused here: What's the input for $1,$2,$3?

The input would be the different values that are used for the token in
the "--trailer ..." CLI arguments.

For (an hypothetical) example:

------
$ git config trailer.foo.runMode afterCLI
$ git config trailer.foo.cmd 'echo $@'
$ git interpret-trailers --trailer foo=a --trailer foo=b --trailer foo=c<<EOF
EOF

foo: a b c
$ git interpret-trailers<<EOF
EOF

foo:
------

I am not sure "afterCLI" would be useful, but we might not want to
implement it right now. It's just an example to show that we could add
other modes to run the configured ".cmd" (and maybe ".command" too).

> Is users more interested in dealing with trailers value or a line of the
> trailer?

I am not sure what you mean here. If "a line of the trailer" means a
trailer that is already in the input file that is passed to `git
interpret-trailers`, and if "trailers value" means a "--trailer ..."
argument, then I would say that users could be interested in dealing
with both.

It's true that right now the command configured by a ".command" is not
run when `git interpret-trailers` processes in input file that
contains a trailer with the corresponding token. So new values for
".runMode" could be implemented to make that happen.

> > > But as you said, to disable the automatic addition in the original .command
> > > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.
> > > Perhaps future series of patches can be considered to do it.
> >
> > Yeah, support for `trailer.<token>.runMode` might be added in
> > different commits at least and possibly later in a different patch
> > series. There are the following issues to resolve, though, if we want
> > to focus only on a new ".cmd" config option:
> >
> > - how and when should it run by default,
>
> Do you mean that ".cmd" can get rid of the ".command" auto-add problem
> in this patch series?

I am not sure what you mean with "auto-add". Do you mean that fact
that the ".command" runs once before the CLI "--trailer ..." options
are processed?

> This might be a good idea if I can add the three modes you mentioned above
> in the later patch series.

I like that your are interested in improving trailer handling in Git,
but I must say that if you intend to apply for the GSoC, you might
want to work on your application document first, as it will need to be
discussed on the mailing list too and it will take some time. You are
also free to work on this too, but that shouldn't be your priority.

By the way if this (or another Git related) subject is more
interesting to you than the project ideas we propose on
https://git.github.io/SoC-2021-Ideas/, then you are welcome to write a
proposal about working on this (improving trailer handling) rather
than on a project idea from that page. You might want to make sure
that some people would be willing to (co-)mentor you working on it
though.

[...]

> > Another way to work on all this, would be to first work on adding
> > support for `trailer.<token>.runMode` and on improving existing
> > documentation, and then to add ".cmd", which could then by default use
> > a different ".runMode" than ".command".
>
> I think the task can be put off until April.
> Deal with the easier ".cmd" first.

Ok for me, but see above about GSoC application.


> > > > > This, too, but until ".command" is removed, wouldn't it be better
> > > > > for readers to keep both variants, as the distinction between $ARG
> > > > > and $1 needs to be illustrated?
> > >
> > > So the correct solution should be to keep the original .command Examples,
> > > and then give the .cmd examples again.
> >
> > Maybe we could take advantage of ".cmd" to show other nice
> > possibilities to use all of this. Especially if support for `git
> > commit --trailer ...` is already merged, we might be able to use it in
> > those examples, or perhaps add some examples to the git commit doc.
>
> Oh, the 'commit --trailer' may still be queuing, It may take a while.

You might want to check if it needs another reroll or if there are
other reasons (like no reviews) why it's not listed in the last
"What's cooking ..." email from Junio. If you think it is ready and
has been forgotten, you can ping reviewers (including me), to ask them
to review it one more time, or Junio if the last version you sent has
already been reviewed.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-30  8:45                 ` Christian Couder
@ 2021-03-30 11:22                   ` ZheNing Hu
  2021-03-30 15:07                     ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-03-30 11:22 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

Christian Couder <christian.couder@gmail.com> 于2021年3月30日周二 下午4:45写道:
>
> On Mon, Mar 29, 2021 at 3:44 PM ZheNing Hu <adlternative@gmail.com> wrote:
> >
> > Christian Couder <christian.couder@gmail.com> 于2021年3月29日周一 下午5:05写道:
> > >
> > > >
> > > > Yes, $ARG or $1 are always exist because of:
> > > >
> > > >                arg = xstrdup("");
> > > >
> > > > so I think maybe we don't even need this judge in `apply_command`?
> > > > +               if (arg)
> > > > +                       strvec_push(&cp.args, arg);
> > >
> > > Yeah, I haven't looked at the code, but that might be a good
> > > simplification. If you work on this, please submit it in a separate
> > > commit.
> >
> > Well, if necessary, I'll put it in another commit, maybe I should double check
> > to see if there's anything special going on.
> >
> > > > > Another way to do it would be to have another config option called
> > > > > `trailer.<token>.alwaysRunCmd` to tell if the cmd specified by
> > > > > `trailer.<token>.cmd` should be run even if no '<token>=<value>'
> > > > > argument is passed on the command line. As we are introducing
> > > > > `trailer.<token>.cmd`, it's a good time to wonder if this would be a
> > > > > better design. But this issue is quite complex, because of the fact
> > > > > that 'trailer.<token>.ifMissing' and 'trailer.<token>.ifExists' also
> > > > > take a part in deciding if the command will be run.
> > >
> > > Actually after thinking about it, I think it might be better, instead
> > > of `trailer.<token>.alwaysRunCmd`, to add something like
> > > `trailer.<token>.runMode` that could take multiple values like:
> >
> > If really can achieve it is certainly better than 'alwaysRunCmd'.
> > The following three small configuration options look delicious.
> > But I think it needs to be discussed in more detail:
> >
> > > - "beforeCLI": would make it run once, like ".command" does now before
> > > any CLI trailer are processed
> >
> > Does "beforeCLI" handle all trailers? Or is it just doing something to add empty
> > value trailers?
>
> I am not sure what you mean by "handle all trailers". What I mean is
> that it would just work like ".command" does right now before the
> "--trailers ..." options are processed.
>
> Let's suppose the "trailer.foo.command" config option is set to "bar".
> Then the "bar" command will be run just before the "--trailers ..."
> options are processed and the output of that, let's say "baz" will be
> used to add a new "foo: baz" trailer to the ouput of `git
> interpret-trailers`.
>
> For example:
>
> -------
> $ git -c trailer.foo.command='echo baz' interpret-trailers<<EOF
> EOF
>
> foo: baz
> -------
>
> In other words an empty value trailer is just a special case when the
> command that is run does not output anything. But such commands are
> expected to output something not trivial at least in some cases.
>
> See also the example in the doc that uses:
>
> $ git config trailer.sign.command 'echo "$(git config user.name)
> <$(git config user.email)>"'
>

I see what you mean, which is to provide a default value for any
trailers that haven't been run command yet.

> > > - "forEachCLIToken": would make it run once for each trailer that has
> > > the token, like ".command" also does now, the difference would be that
> > > the value for the token would be passed in the $1 argument
> >
> > This is exactly same as before.
>
> Yeah it is the same as before when the "--trailers ..." options are
> processed, but not before that.
>
> To get exactly the same as before one would need to configure both
> "beforeCLI" _and_ "forEachCLIToken", for example like this (note that
> we use "--add" when adding "forEachCLIToken"):
>
> $ git config trailer.foo.runMode beforeCLI
> $ git config --add trailer.foo.runMode forEachCLIToken
> $ git config -l | grep foo
> trailer.foo.runmode=beforeCLI
> trailer.foo.runmode=forEachCLIToken
>
> > > - "afterCLI": would make it run once after all the CLI trailers have
> > > been processed and it could pass the different values for the token if
> > > any in different arguments: $1, $2, $3, ...
> >
> > I might get a little confused here: What's the input for $1,$2,$3?
>
> The input would be the different values that are used for the token in
> the "--trailer ..." CLI arguments.
>
> For (an hypothetical) example:
>
> ------
> $ git config trailer.foo.runMode afterCLI
> $ git config trailer.foo.cmd 'echo $@'
> $ git interpret-trailers --trailer foo=a --trailer foo=b --trailer foo=c<<EOF
> EOF
>
> foo: a b c
> $ git interpret-trailers<<EOF
> EOF
>
> foo:
> ------
>
> I am not sure "afterCLI" would be useful, but we might not want to
> implement it right now. It's just an example to show that we could add
> other modes to run the configured ".cmd" (and maybe ".command" too).
>

Yes, not so useful.

> > Is users more interested in dealing with trailers value or a line of the
> > trailer?
>
> I am not sure what you mean here. If "a line of the trailer" means a
> trailer that is already in the input file that is passed to `git
> interpret-trailers`, and if "trailers value" means a "--trailer ..."
> argument, then I would say that users could be interested in dealing
> with both.
>

Sorry, I mean after we running those command, a line trailer is
"foo: bar" and trailers value will be "bar".

> It's true that right now the command configured by a ".command" is not
> run when `git interpret-trailers` processes in input file that
> contains a trailer with the corresponding token. So new values for
> ".runMode" could be implemented to make that happen.
>

Sure.

> > > > But as you said, to disable the automatic addition in the original .command
> > > > and use the new .alwaysRunCmd, I’m afraid there are a lot of things to consider.
> > > > Perhaps future series of patches can be considered to do it.
> > >
> > > Yeah, support for `trailer.<token>.runMode` might be added in
> > > different commits at least and possibly later in a different patch
> > > series. There are the following issues to resolve, though, if we want
> > > to focus only on a new ".cmd" config option:
> > >
> > > - how and when should it run by default,
> >
> > Do you mean that ".cmd" can get rid of the ".command" auto-add problem
> > in this patch series?
>
> I am not sure what you mean with "auto-add". Do you mean that fact
> that the ".command" runs once before the CLI "--trailer ..." options
> are processed?
>

I'm talking about the empty values $ARG passing to the user's command,
those command  at least run once, You say "how and when should it run by
default", I was wondering if I could not run .cmd without passing trailer.

> > This might be a good idea if I can add the three modes you mentioned above
> > in the later patch series.
>
> I like that your are interested in improving trailer handling in Git,
> but I must say that if you intend to apply for the GSoC, you might
> want to work on your application document first, as it will need to be
> discussed on the mailing list too and it will take some time. You are
> also free to work on this too, but that shouldn't be your priority.
>

In fact, I had written the proposal carefully.
I have been studying what went wrong with OIga's improvement of cat-file
recently.

I may have thought of some ideas, and has been written in Proposal,
I will submit it in about two days :)

> By the way if this (or another Git related) subject is more
> interesting to you than the project ideas we propose on
> https://git.github.io/SoC-2021-Ideas/, then you are welcome to write a
> proposal about working on this (improving trailer handling) rather
> than on a project idea from that page. You might want to make sure
> that some people would be willing to (co-)mentor you working on it
> though.
>

Aha, for the time being, you are the most suitable mentor,
But I might just take improvement of `interpret-tarilers` as my interest to
do something. I will choice the project of "git cat-file" .

> [...]
>
> > > Another way to work on all this, would be to first work on adding
> > > support for `trailer.<token>.runMode` and on improving existing
> > > documentation, and then to add ".cmd", which could then by default use
> > > a different ".runMode" than ".command".
> >
> > I think the task can be put off until April.
> > Deal with the easier ".cmd" first.
>
> Ok for me, but see above about GSoC application.
>
>
> > > > > > This, too, but until ".command" is removed, wouldn't it be better
> > > > > > for readers to keep both variants, as the distinction between $ARG
> > > > > > and $1 needs to be illustrated?
> > > >
> > > > So the correct solution should be to keep the original .command Examples,
> > > > and then give the .cmd examples again.
> > >
> > > Maybe we could take advantage of ".cmd" to show other nice
> > > possibilities to use all of this. Especially if support for `git
> > > commit --trailer ...` is already merged, we might be able to use it in
> > > those examples, or perhaps add some examples to the git commit doc.
> >
> > Oh, the 'commit --trailer' may still be queuing, It may take a while.
>
> You might want to check if it needs another reroll or if there are
> other reasons (like no reviews) why it's not listed in the last
> "What's cooking ..." email from Junio. If you think it is ready and
> has been forgotten, you can ping reviewers (including me), to ask them
> to review it one more time, or Junio if the last version you sent has
> already been reviewed.

It should still be in "seen" inheritance, Junio is advancing it.
Maybe you think it has something to improve, please feel free to tell me.

In addition, I now found a small bug in ".cmd",

git config -l |grep bug
trailer.bug.key=bug-descibe:
trailer.bug.ifexists=replace
trailer.bug.cmd=echo 123

see what will happen:

git interpret-trailers --trailer="bug:text" <<-EOF
`heredocd> EOF

bug-descibe:123 text

"text" seem print to stdout.

I'm looking at what's going on here.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-30 11:22                   ` ZheNing Hu
@ 2021-03-30 15:07                     ` ZheNing Hu
  2021-03-30 17:14                       ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-03-30 15:07 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Hi, Junio,

ZheNing Hu <adlternative@gmail.com> 于2021年3月30日周二 下午7:22写道:
>
> In addition, I now found a small bug in ".cmd",
>
> git config -l |grep bug
> trailer.bug.key=bug-descibe:
> trailer.bug.ifexists=replace
> trailer.bug.cmd=echo 123
>
> see what will happen:
>
> git interpret-trailers --trailer="bug:text" <<-EOF
> `heredocd> EOF
>
> bug-descibe:123 text
>
> "text" seem print to stdout.
>
> I'm looking at what's going on here.
>

Here I may need to think with you whether it is reasonable to pass "$1".

I found that we passed the parameters in the above situation like this:

(gdb) print cp.args.v[0]
$7 = 0x5555558f4e20 "echo \"123\""
(gdb) print cp.args.v[1]
$8 = 0x5555558ee150 "text"

At this time, our idea is base on that v[0] will be the content of the shell,
and v[1] will be the $1 of the shell.

But in fact, git handles shell subprocesses in a special way:

The `prepare_shell_cmd()` in "run-command.c" seem to use "$@" to pass
shell args.

Before exec:

(gdb) print argv.v[1]
$22 = 0x5555558edfd0 "/bin/sh"
(gdb) print argv.v[2]
$23 = 0x5555558f4c80 "-c"
(gdb) print argv.v[3]
$24 = 0x5555558ed4b0 "echo \"123\" \"$@\""
(gdb) print argv.v[4]
$25 = 0x5555558f5980 "echo \"123\""
(gdb) print argv.v[5]
$26 = 0x5555558edab0 "abc"
(gdb) print argv.v[6]
$27 = 0x0

Some unexpected things happened here.
Maybe "abc" was wrongly used as the parameter of "echo"?
Looking forward to your reply.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-30 15:07                     ` ZheNing Hu
@ 2021-03-30 17:14                       ` Junio C Hamano
  2021-03-31  5:14                         ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-03-30 17:14 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

> The `prepare_shell_cmd()` in "run-command.c" seem to use "$@" to pass
> shell args.

Yes. "$@" is a way to write "$1" "$2" "$3"...
Since you are passing only one, 

	echo "$@"

and

	echo "$1"

would be the equivalent.

I am not sure what program you fed to the gdb (and remote debugging
over e-mail is not my forte ;-), but let's see.

> Before exec:
>
> (gdb) print argv.v[1]
> $22 = 0x5555558edfd0 "/bin/sh"
> (gdb) print argv.v[2]
> $23 = 0x5555558f4c80 "-c"
> (gdb) print argv.v[3]
> $24 = 0x5555558ed4b0 "echo \"123\" \"$@\""
> (gdb) print argv.v[4]
> $25 = 0x5555558f5980 "echo \"123\""
> (gdb) print argv.v[5]
> $26 = 0x5555558edab0 "abc"
> (gdb) print argv.v[6]
> $27 = 0x0
>
> Some unexpected things happened here.
> Maybe "abc" was wrongly used as the parameter of "echo"?
> Looking forward to your reply.

Observe

	$ sh -c '
		echo "\$0 == $0"
		count=0
		for arg in "$@"
		do
			count=$(( $count + 1 ))
			echo "\$$count == $arg"
		done
	' 0 1 2
	$0 == 0
	$1 == 1
	$2 == 2

i.e. the first arg after

	argv[1] = "/bin/sh"
        argv[2] = "-c"
	argv[3] = "script"

is used to give the script the name of the program ($0).  Are we
getting hit by this common confusion?

It is customery to write such an invocation with '-' as the "name of
the program" thing, so that ordinary positional parameters are
available starting at $1, not $0, like so:

	sh -c 'script' - arg1 arg2 ...

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-30 17:14                       ` Junio C Hamano
@ 2021-03-31  5:14                         ` ZheNing Hu
  2021-03-31 18:19                           ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-03-31  5:14 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年3月31日周三 上午1:14写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > The `prepare_shell_cmd()` in "run-command.c" seem to use "$@" to pass
> > shell args.
>
> Yes. "$@" is a way to write "$1" "$2" "$3"...
> Since you are passing only one,
>
>         echo "$@"
>
> and
>
>         echo "$1"
>
> would be the equivalent.
>
> I am not sure what program you fed to the gdb (and remote debugging
> over e-mail is not my forte ;-), but let's see.
>



> > Before exec:
> >
> > (gdb) print argv.v[1]
> > $22 = 0x5555558edfd0 "/bin/sh"
> > (gdb) print argv.v[2]
> > $23 = 0x5555558f4c80 "-c"
> > (gdb) print argv.v[3]
> > $24 = 0x5555558ed4b0 "echo \"123\" \"$@\""
> > (gdb) print argv.v[4]
> > $25 = 0x5555558f5980 "echo \"123\""
> > (gdb) print argv.v[5]
> > $26 = 0x5555558edab0 "abc"
> > (gdb) print argv.v[6]
> > $27 = 0x0
> >
> > Some unexpected things happened here.
> > Maybe "abc" was wrongly used as the parameter of "echo"?
> > Looking forward to your reply.
>
> Observe
>
>         $ sh -c '
>                 echo "\$0 == $0"
>                 count=0
>                 for arg in "$@"
>                 do
>                         count=$(( $count + 1 ))
>                         echo "\$$count == $arg"
>                 done
>         ' 0 1 2
>         $0 == 0
>         $1 == 1
>         $2 == 2
>
> i.e. the first arg after
>
>         argv[1] = "/bin/sh"
>         argv[2] = "-c"
>         argv[3] = "script"
>
> is used to give the script the name of the program ($0).  Are we
> getting hit by this common confusion?
>
> It is customery to write such an invocation with '-' as the "name of
> the program" thing, so that ordinary positional parameters are
> available starting at $1, not $0, like so:
>
>         sh -c 'script' - arg1 arg2 ...

The configuration is like this:
trailer.bug.key=BUG:
trailer.bug.ifexists=add
trailer.bug.cmd=echo "123"

And use:

$ git interpret-trailers --trailer="bug:456" --trailer="bug:789"<<-EOF
EOF

BUG: 123
BUG: 123 456
BUG: 123 789

I just want three "BUG: 123", but "456" and "789" appeared...

In fact, I think about this problem like this way:
When we execute a child process that runs the shell,
the function`prepare_shell_cmd()` will actively add "$@" to the end of our
shell command when we have more than zero args ,

e.g.

"echo \"123\"" "abc"

will turn to

 "echo \"123\" \"$@\"" "echo \"123\"" "abc"

Normally, $@ should not cause any problems because it passes arguments
to the script what we provide.

But now, what we actually want is take any $1 that appears in the script as an
argument, the automatically added $@ causes $1 to be implicitly included.
And the original $ARG does not have this problem, Or if we pass environment
variables, this kind of problem will not occur.

Or If we want to avoid this problem, should we add one new options in
`struct child_process` , such as: "shell_no_implicit_args" , let git not add
 extra "$@" before we run the shell script?

Thanks.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter
  2021-03-26 16:13     ` [PATCH v4] " ZheNing Hu via GitGitGadget
  2021-03-27 18:04       ` Junio C Hamano
@ 2021-03-31 10:05       ` ZheNing Hu via GitGitGadget
  2021-03-31 10:05         ` [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option ZheNing Hu via GitGitGadget
                           ` (3 more replies)
  1 sibling, 4 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-31 10:05 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config.

ZheNing Hu (2):
  [GSOC] run-command: add shell_no_implicit_args option
  [GSOC]trailer: pass arg as positional parameter

 Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++++----
 run-command.c                            |  8 +--
 run-command.h                            |  1 +
 t/t7513-interpret-trailers.sh            | 61 ++++++++++++++++++-
 trailer.c                                | 38 ++++++++----
 5 files changed, 157 insertions(+), 26 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v5
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v5
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v4:

 -:  ------------ > 1:  4c59cab53a0d [GSOC] run-command: add shell_no_implicit_args option
 1:  e2bbdcb943c2 ! 2:  5894d8c4b364 [GSOC]trailer: pass arg as positional parameter
     @@ Commit message
          scripts.
      
          Helped-by: Junio C Hamano <gitster@pobox.com>
     +    Helped-by: Christian Couder <christian.couder@gmail.com>
          Signed-off-by: ZheNing Hu <adlternative@gmail.com>
      
       ## Documentation/git-interpret-trailers.txt ##
     -@@ Documentation/git-interpret-trailers.txt: also be executed for each of these arguments. And the <value> part of
     - these arguments, if any, will be used to replace the `$ARG` string in
     - the command.
     - 
     +@@ Documentation/git-interpret-trailers.txt: 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
     +-'<token>=<value>' argument were added at the beginning of the command
     +-line, 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.
     + +
     +-If the command contains the `$ARG` string, this string will be
     +-replaced with the <value> part of an existing trailer with the same
     +-<token>, if any, before the command is launched.
     ++".command" has been deprecated due to the $ARG in the user's command can
     ++only be replaced once and the original way of replacing $ARG was not safe.
     ++Now the preferred option is using "trailer.<token>.cmd", which use position
     ++argument to pass the value.
     +++
     ++When both .cmd and .command are given for the same <token>,
     ++.cmd is used and .command is ignored.
     ++
      +trailer.<token>.cmd::
      +	The command specified by this configuration variable is run
      +	with a single parameter, which is the <value> part of an
      +	existing trailer with the same <token>.  The output from the
      +	command is then used as the value for the <token> in the
      +	resulting trailer.
     -+	The command is expected to replace `trailer.<token>.cmd`.
     -+	When both .cmd and .command are given for the same <token>,
     -+        .cmd is used and .command is ignored.
     -+
     - EXAMPLES
     - --------
     - 
     -@@ Documentation/git-interpret-trailers.txt: $ git format-patch -1
     - $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Reviewed-by: Bob <bob@example.com>' 0001-foo.patch >0001-bar.patch
     - ------------
     - 
     --* Configure a 'sign' trailer with a command to automatically add a
     -+* Configure a 'sign' trailer with a cmd to automatically add a
     -   'Signed-off-by: ' with the author information only if there is no
     -   'Signed-off-by: ' already, and show how it works:
     +++
     ++When this option is specified, If there is no trailer with same <token>,
     ++the behavior is as if a special '<token>=<value>' argument were added at
     ++the beginning of the command, <value> will be passed to the user's
     ++command as an empty value.
       +
     -@@ Documentation/git-interpret-trailers.txt: $ git interpret-trailers --trailer 'Cc: Alice <alice@example.com>' --trailer 'Re
     - $ git config trailer.sign.key "Signed-off-by: "
     - $ git config trailer.sign.ifmissing add
     - $ git config trailer.sign.ifexists doNothing
     --$ git config trailer.sign.command 'echo "$(git config user.name) <$(git config user.email)>"'
     -+$ git config trailer.sign.cmd 'echo "$(git config user.name) <$(git config user.email)>"'
     - $ git interpret-trailers <<EOF
     - > EOF
     + If some '<token>=<value>' arguments are also passed on the command
     + line, when a 'trailer.<token>.command' is configured, the command will
     + also be executed for each of these arguments. And the <value> part of
     +-these arguments, if any, will be used to replace the `$ARG` string in
     +-the command.
     ++these arguments, if any, will be passed to the command as first parameter.
       
     + EXAMPLES
     + --------
      @@ Documentation/git-interpret-trailers.txt: subject
       Fix #42
       ------------
       
     --* Configure a 'see' trailer with a command to show the subject of a
      +* Configure a 'see' trailer with a cmd to show the subject of a
     ++  commit that is related, and show how it works:
     +++
     ++------------
     ++$ git config trailer.see.key "See-also: "
     ++$ git config trailer.see.ifExists "replace"
     ++$ git config trailer.see.ifMissing "doNothing"
     ++$ git config trailer.see.cmd "git show -s --pretty=reference \"\$1\""
     ++$ git interpret-trailers <<EOF
     ++> subject
     ++> 
     ++> message
     ++> 
     ++> see: HEAD~2
     ++> EOF
     ++subject
     ++
     ++message
     ++
     ++See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)
     ++------------
     ++
     ++* Configure a 'bug' trailer with a cmd to show when and where
     ++  was the bug introduced, and show how it works:
     +++
     ++------------
     ++$ git config trailer.bug.key "Bug-from: "
     ++$ git config trailer.bug.ifExists "replace"
     ++$ git config trailer.bug.cmd "git log --grep \"\$1\" -1 --pretty=\"%h %aD\""
     ++$ git interpret-trailers --trailer="bug:the information manager from hell" <<EOF
     ++> subject
     ++> 
     ++> message
     ++> 
     ++> EOF
     ++subject
     ++
     ++message
     ++
     ++Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700
     ++------------
     ++
     + * Configure a 'see' trailer with a command to show the subject of a
         commit that is related, and show how it works:
       +
     - ------------
     - $ git config trailer.see.key "See-also: "
     - $ git config trailer.see.ifExists "replace"
     - $ git config trailer.see.ifMissing "doNothing"
     --$ git config trailer.see.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG"
     -+$ git config trailer.see.cmd "test -n \"\$1\" && git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \"\$1\"|| true "
     - $ git interpret-trailers <<EOF
     - > subject
     - > 
      
       ## t/t7513-interpret-trailers.sh ##
     +@@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
     + 	EOF
     + '
     + 
     ++test_expect_success 'with cmd' '
     ++	test_when_finished "git config --unset trailer.bug.key && \
     ++	git config --unset trailer.bug.ifExists && \
     ++	git config --unset trailer.bug.cmd" &&
     ++	git config trailer.bug.key "Bug-maker: " &&
     ++	git config trailer.bug.ifExists "add" &&
     ++	git config trailer.bug.cmd "echo \"\$@\"" &&
     ++	cat >>expected2 <<-EOF &&
     ++
     ++	Bug-maker: 
     ++	Bug-maker: jocker
     ++	Bug-maker: batman
     ++	EOF
     ++	git interpret-trailers --trailer "bug: jocker" --trailer "bug:batman" \
     ++		>actual2 &&
     ++	test_cmp expected2 actual2
     ++'
     ++
     + test_expect_success 'without config' '
     + 	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
     + 
      @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
       	git commit -m "Add file a.txt"
       '
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
      -
      -	strvec_push(&cp.args, cmd.buf);
      +	if (conf->cmd) {
     ++		cp.shell_no_implicit_args = 1;
      +		strbuf_addstr(&cmd, conf->cmd);
      +		strvec_push(&cp.args, cmd.buf);
      +		if (arg)
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     +-	cp.shell_no_implicit_args = 1;
     + 
     + 	if (capture_command(&cp, &buf, 1024)) {
     + 		error(_("running trailer command '%s' failed"), cmd.buf);
      @@ trailer.c: 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)

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
@ 2021-03-31 10:05         ` ZheNing Hu via GitGitGadget
  2021-04-01  7:22           ` Christian Couder
  2021-03-31 10:05         ` [PATCH v5 2/2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
                           ` (2 subsequent siblings)
  3 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-31 10:05 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

When we use subprocess to run a shell-script, if we have any
args, git will add extra $@ to the end of the shell-script,
This can pass positional parameters correctly, But if we just
want to use some of these passed parameters, git will still
add an extra "$@", which contains all positional parameters we
passed. This does not meet our expectations.

E.g. our shell-script is:
"echo \"\$1\""
and pass $1 "abc", git will change our script to:
"echo \"\$1\" \"$@\""

The positional parameters we entered will be printed
repeatedly. So let add a new `shell_no_implicit_args`
to `struct child_process`, which can suppress the
joining of $@ if `shell_no_implicit_args` is set to 1,
this will allow us to use only few of positional args
in multi-parameter shell script, instead of using all
of them.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 run-command.c | 8 ++++----
 run-command.h | 1 +
 trailer.c     | 1 +
 3 files changed, 6 insertions(+), 4 deletions(-)

diff --git a/run-command.c b/run-command.c
index be6bc128cd9d..a2cf6177f522 100644
--- a/run-command.c
+++ b/run-command.c
@@ -264,7 +264,7 @@ int sane_execvp(const char *file, char * const argv[])
 	return -1;
 }
 
-static const char **prepare_shell_cmd(struct strvec *out, const char **argv)
+static const char **prepare_shell_cmd(struct strvec *out, const char **argv, int shell_no_implicit_args)
 {
 	if (!argv[0])
 		BUG("shell command is empty");
@@ -281,7 +281,7 @@ static const char **prepare_shell_cmd(struct strvec *out, const char **argv)
 		 * If we have no extra arguments, we do not even need to
 		 * bother with the "$@" magic.
 		 */
-		if (!argv[1])
+		if (!argv[1] || shell_no_implicit_args)
 			strvec_push(out, argv[0]);
 		else
 			strvec_pushf(out, "%s \"$@\"", argv[0]);
@@ -416,7 +416,7 @@ static int prepare_cmd(struct strvec *out, const struct child_process *cmd)
 	if (cmd->git_cmd) {
 		prepare_git_cmd(out, cmd->argv);
 	} else if (cmd->use_shell) {
-		prepare_shell_cmd(out, cmd->argv);
+		prepare_shell_cmd(out, cmd->argv, cmd->shell_no_implicit_args);
 	} else {
 		strvec_pushv(out, cmd->argv);
 	}
@@ -929,7 +929,7 @@ int start_command(struct child_process *cmd)
 	if (cmd->git_cmd)
 		cmd->argv = prepare_git_cmd(&nargv, cmd->argv);
 	else if (cmd->use_shell)
-		cmd->argv = prepare_shell_cmd(&nargv, cmd->argv);
+		cmd->argv = prepare_shell_cmd(&nargv, cmd->argv, cmd->shell_no_implicit_args);
 
 	cmd->pid = mingw_spawnvpe(cmd->argv[0], cmd->argv, (char**) cmd->env,
 			cmd->dir, fhin, fhout, fherr);
diff --git a/run-command.h b/run-command.h
index d08414a92e73..9597c987c5bb 100644
--- a/run-command.h
+++ b/run-command.h
@@ -133,6 +133,7 @@ struct child_process {
 	 * argv[1], etc, do not need to be shell-quoted.
 	 */
 	unsigned use_shell:1;
+	unsigned shell_no_implicit_args:1;
 
 	unsigned stdout_to_stderr:1;
 	unsigned clean_on_exit:1;
diff --git a/trailer.c b/trailer.c
index be4e9726421c..35dd0f4c8512 100644
--- a/trailer.c
+++ b/trailer.c
@@ -231,6 +231,7 @@ static char *apply_command(const char *command, const char *arg)
 	cp.env = local_repo_env;
 	cp.no_stdin = 1;
 	cp.use_shell = 1;
+	cp.shell_no_implicit_args = 1;
 
 	if (capture_command(&cp, &buf, 1024)) {
 		error(_("running trailer command '%s' failed"), cmd.buf);
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v5 2/2] [GSOC]trailer: pass arg as positional parameter
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
  2021-03-31 10:05         ` [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option ZheNing Hu via GitGitGadget
@ 2021-03-31 10:05         ` ZheNing Hu via GitGitGadget
  2021-04-01  7:28         ` [PATCH v5 0/2] " Christian Couder
  2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
  3 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-03-31 10:05 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

The `trailer.<token>.command` configuration variable
specifies a command (run via the shell, so it does not have
to be a single name of 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.  This has two 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 unmatching 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).

Introduce a new `trailer.<token>.cmd` configuration that
takes higher precedence to deprecate and eventually remove
`trailer.<token>.command`, which passes the value as a
parameter to the command.  Instead of "$ARG", the users will
refer to the value as positional argument, $1, in their
scripts.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 61 ++++++++++++++++++-
 trailer.c                                | 39 ++++++++----
 3 files changed, 152 insertions(+), 23 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..bbd1c9bfd65e 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -236,21 +236,34 @@ 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
-'<token>=<value>' argument were added at the beginning of the command
-line, 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.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+".command" has been deprecated due to the $ARG in the user's command can
+only be replaced once and the original way of replacing $ARG was not safe.
+Now the preferred option is using "trailer.<token>.cmd", which use position
+argument to pass the value.
++
+When both .cmd and .command are given for the same <token>,
+.cmd is used and .command is ignored.
+
+trailer.<token>.cmd::
+	The command specified by this configuration variable is run
+	with a single parameter, which is the <value> part of an
+	existing trailer with the same <token>.  The output from the
+	command is then used as the value for the <token> in the
+	resulting trailer.
++
+When this option is specified, If there is no trailer with same <token>,
+the behavior is as if a special '<token>=<value>' argument were added at
+the beginning of the command, <value> will be passed to the user's
+command as an empty value.
 +
 If some '<token>=<value>' arguments are also passed on the command
 line, when a 'trailer.<token>.command' is configured, the command will
 also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+these arguments, if any, will be passed to the command as first parameter.
 
 EXAMPLES
 --------
@@ -333,6 +346,48 @@ subject
 Fix #42
 ------------
 
+* Configure a 'see' trailer with a cmd to show the subject of a
+  commit that is related, and show how it works:
++
+------------
+$ git config trailer.see.key "See-also: "
+$ git config trailer.see.ifExists "replace"
+$ git config trailer.see.ifMissing "doNothing"
+$ git config trailer.see.cmd "git show -s --pretty=reference \"\$1\""
+$ git interpret-trailers <<EOF
+> subject
+> 
+> message
+> 
+> see: HEAD~2
+> EOF
+subject
+
+message
+
+See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)
+------------
+
+* Configure a 'bug' trailer with a cmd to show when and where
+  was the bug introduced, and show how it works:
++
+------------
+$ git config trailer.bug.key "Bug-from: "
+$ git config trailer.bug.ifExists "replace"
+$ git config trailer.bug.cmd "git log --grep \"\$1\" -1 --pretty=\"%h %aD\""
+$ git interpret-trailers --trailer="bug:the information manager from hell" <<EOF
+> subject
+> 
+> message
+> 
+> EOF
+subject
+
+message
+
+Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700
+------------
+
 * Configure a 'see' trailer with a command to show the subject of a
   commit that is related, and show how it works:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..aec240f1dc05 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -51,6 +51,24 @@ test_expect_success 'setup' '
 	EOF
 '
 
+test_expect_success 'with cmd' '
+	test_when_finished "git config --unset trailer.bug.key && \
+	git config --unset trailer.bug.ifExists && \
+	git config --unset trailer.bug.cmd" &&
+	git config trailer.bug.key "Bug-maker: " &&
+	git config trailer.bug.ifExists "add" &&
+	git config trailer.bug.cmd "echo \"\$@\"" &&
+	cat >>expected2 <<-EOF &&
+
+	Bug-maker: 
+	Bug-maker: jocker
+	Bug-maker: batman
+	EOF
+	git interpret-trailers --trailer "bug: jocker" --trailer "bug:batman" \
+		>actual2 &&
+	test_cmp expected2 actual2
+'
+
 test_expect_success 'without config' '
 	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
 
@@ -1274,9 +1292,50 @@ test_expect_success 'setup a commit' '
 	git commit -m "Add file a.txt"
 '
 
+test_expect_success 'with cmd and $1' '
+	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 (%s)\" \
+		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
+	FIXED=$(git log -1 --oneline --format="%h (%s)" --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 '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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index 35dd0f4c8512..a000293d6e7e 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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,22 +218,28 @@ 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) {
+		cp.shell_no_implicit_args = 1;
+		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);
+		strvec_push(&cp.args, cmd.buf);
+		if (arg)
+			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+	}
 	cp.env = local_repo_env;
 	cp.no_stdin = 1;
 	cp.use_shell = 1;
-	cp.shell_no_implicit_args = 1;
 
 	if (capture_command(&cp, &buf, 1024)) {
 		error(_("running trailer command '%s' failed"), cmd.buf);
@@ -248,7 +256,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;
@@ -258,7 +266,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);
 	}
 }
@@ -431,6 +439,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)
@@ -455,8 +464,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;
@@ -464,6 +473,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 }
@@ -543,6 +553,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);
@@ -709,7 +724,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(""),
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-31  5:14                         ` ZheNing Hu
@ 2021-03-31 18:19                           ` Junio C Hamano
  2021-03-31 18:29                             ` Junio C Hamano
  2021-04-01  3:39                             ` ZheNing Hu
  0 siblings, 2 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-03-31 18:19 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

> The configuration is like this:
> trailer.bug.key=BUG:
> trailer.bug.ifexists=add
> trailer.bug.cmd=echo "123"
>
> And use:
>
> $ git interpret-trailers --trailer="bug:456" --trailer="bug:789"<<-EOF
> EOF
>
> BUG: 123
> BUG: 123 456
> BUG: 123 789

I think that is quite expected.  You said the command to run is
'echo 123', and that is not "pick a directory $D on $PATH where
there is an executable '$D/echo 123' exists, and run that".  It
runs the given command with the shell, and in general that is
what we want for end-user supplied commands specified in the
configuration file [*1*].

So we form a shell command whose beginning is 'echo 123' and tuck
the argument after that command line, so it is understandable that
"echo 123 456" gets executed for "--trailer=bug:456".

I wasn't following the discussion between you and Christian closely
but I recall seeing him saying that the command is executed one
extra time without any arg before it is run for actual --trailer
requests with the value?  I am guessing that is where the first
output "BUG: 123" (without anything else) is coming from.


*1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want
    Git to find a directory on $PATH that has an executable whose
    name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as
    the first argument to execlp()).  Instead, you'd want to behave
    as if the user typed "emacs -nw", followed by any arguments we
    want to give to it (in .editor's case, the name of the file to
    be edited) properly quoted for the shell.

    And the way we do so is to form a moral equivalent of

	execlp("sh", "-c", "emacs -nw $@", ...);

    and put the arguments at the end where I wrote ... (we actually
    do so with execvp(), but illustrating with execlp() is easier to
    read and write---hence "a moral equivalent of").

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-31 18:19                           ` Junio C Hamano
@ 2021-03-31 18:29                             ` Junio C Hamano
  2021-04-01  3:56                               ` ZheNing Hu
  2021-04-01  3:39                             ` ZheNing Hu
  1 sibling, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-03-31 18:29 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> writes:

> ZheNing Hu <adlternative@gmail.com> writes:
>
>> The configuration is like this:
>> trailer.bug.key=BUG:
>> trailer.bug.ifexists=add
>> trailer.bug.cmd=echo "123"
>>
>> And use:
>>
>> $ git interpret-trailers --trailer="bug:456" --trailer="bug:789"<<-EOF
>> EOF
>>
>> BUG: 123
>> BUG: 123 456
>> BUG: 123 789
>
> I think that is quite expected.  You said the command to run is
> 'echo 123', and that is not "pick a directory $D on $PATH where
> there is an executable '$D/echo 123' exists, and run that".  It
> runs the given command with the shell, and in general that is
> what we want for end-user supplied commands specified in the
> configuration file [*1*].
> ...
> *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want
>     Git to find a directory on $PATH that has an executable whose
>     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as
>     the first argument to execlp()).  Instead, you'd want to behave
>     as if the user typed "emacs -nw", followed by any arguments we
>     want to give to it (in .editor's case, the name of the file to
>     be edited) properly quoted for the shell.
>
>     And the way we do so is to form a moral equivalent of
>
> 	execlp("sh", "-c", "emacs -nw \"$@\"", ...);
>
>     and put the arguments at the end where I wrote ... (we actually
>     do so with execvp(), but illustrating with execlp() is easier to
>     read and write---hence "a moral equivalent of").

So, learning from that .editor example, what you can do when you do
not want to take any parameter is to explicitly ignore them.  

Let's take the very basic form first.  Imagine you wrote a little
script and wanted to see three "123", ignoring end-user input after
"--trailer=bug:".

    .cmd = my-script 123

would run 'my-script "$@"'.  What should you write in my-script to
cause that happen?  Here is an example solution:

    #!/bin/sh
    echo 123

Notice that "$1" is completely ignored, even if the machinery that
drives .cmd makes three calls?

	sh -c 'my-script 123 "$@"'
	sh -c 'my-script 123 "$@"' 456
	sh -c 'my-script 123 "$@"' 789

The way to do the same without an extra script on disk is for you to
use sh-c yourself.

    .cmd = sh -c 'echo 123'

And if you do want to use $1, you can do the same.  E.g. if you want
to double them in the output, you'd probably do something like this:

    .cmd = sh -c 'echo "<$1 - $1>"'

You'd need to quote the value appropriately for the config file,
though.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-31 18:19                           ` Junio C Hamano
  2021-03-31 18:29                             ` Junio C Hamano
@ 2021-04-01  3:39                             ` ZheNing Hu
  1 sibling, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-01  3:39 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年4月1日周四 上午2:20写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > The configuration is like this:
> > trailer.bug.key=BUG:
> > trailer.bug.ifexists=add
> > trailer.bug.cmd=echo "123"
> >
> > And use:
> >
> > $ git interpret-trailers --trailer="bug:456" --trailer="bug:789"<<-EOF
> > EOF
> >
> > BUG: 123
> > BUG: 123 456
> > BUG: 123 789
>
> I think that is quite expected.  You said the command to run is
> 'echo 123', and that is not "pick a directory $D on $PATH where
> there is an executable '$D/echo 123' exists, and run that".  It
> runs the given command with the shell, and in general that is
> what we want for end-user supplied commands specified in the
> configuration file [*1*].
>

I agree that if you want to use execv directly to execute a terminal
command, if arg[0] is something like "emacs -nw", error will be
reported:"No such file or directory". But by wrapping a layer of
"sh" "-c", The program name 'emacs' in 'emacs -nw' can be
found and executed normally.

> So we form a shell command whose beginning is 'echo 123' and tuck
> the argument after that command line, so it is understandable that
> "echo 123 456" gets executed for "--trailer=bug:456".
>
> I wasn't following the discussion between you and Christian closely
> but I recall seeing him saying that the command is executed one
> extra time without any arg before it is run for actual --trailer
> requests with the value?  I am guessing that is where the first
> output "BUG: 123" (without anything else) is coming from.
>

Exactly.
Each .command/.cmd willl executes this 'beforeCLI' operation  once,
I use ifexists='add' here just for seeing the effect, In general, we will
use ifexists='replace'.

>
> *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want
>     Git to find a directory on $PATH that has an executable whose
>     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as
>     the first argument to execlp()).  Instead, you'd want to behave
>     as if the user typed "emacs -nw", followed by any arguments we
>     want to give to it (in .editor's case, the name of the file to
>     be edited) properly quoted for the shell.
>
>     And the way we do so is to form a moral equivalent of
>
>         execlp("sh", "-c", "emacs -nw $@", ...);
>
>     and put the arguments at the end where I wrote ... (we actually
>     do so with execvp(), but illustrating with execlp() is easier to
>     read and write---hence "a moral equivalent of").

I can see the benefits of this.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-03-31 18:29                             ` Junio C Hamano
@ 2021-04-01  3:56                               ` ZheNing Hu
  2021-04-01 19:49                                 ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-01  3:56 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年4月1日周四 上午2:29写道:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > ZheNing Hu <adlternative@gmail.com> writes:
> >
> >> The configuration is like this:
> >> trailer.bug.key=BUG:
> >> trailer.bug.ifexists=add
> >> trailer.bug.cmd=echo "123"
> >>
> >> And use:
> >>
> >> $ git interpret-trailers --trailer="bug:456" --trailer="bug:789"<<-EOF
> >> EOF
> >>
> >> BUG: 123
> >> BUG: 123 456
> >> BUG: 123 789
> >
> > I think that is quite expected.  You said the command to run is
> > 'echo 123', and that is not "pick a directory $D on $PATH where
> > there is an executable '$D/echo 123' exists, and run that".  It
> > runs the given command with the shell, and in general that is
> > what we want for end-user supplied commands specified in the
> > configuration file [*1*].
> > ...
> > *1* Imagine .editor set to 'emacs -nw' or 'vim -f'; we do not want
> >     Git to find a directory on $PATH that has an executable whose
> >     name is 'emacs -nw' and run that file (i.e. give 'emacs -nw' as
> >     the first argument to execlp()).  Instead, you'd want to behave
> >     as if the user typed "emacs -nw", followed by any arguments we
> >     want to give to it (in .editor's case, the name of the file to
> >     be edited) properly quoted for the shell.
> >
> >     And the way we do so is to form a moral equivalent of
> >
> >       execlp("sh", "-c", "emacs -nw \"$@\"", ...);
> >
> >     and put the arguments at the end where I wrote ... (we actually
> >     do so with execvp(), but illustrating with execlp() is easier to
> >     read and write---hence "a moral equivalent of").
>
> So, learning from that .editor example, what you can do when you do
> not want to take any parameter is to explicitly ignore them.
>
> Let's take the very basic form first.  Imagine you wrote a little
> script and wanted to see three "123", ignoring end-user input after
> "--trailer=bug:".
>
>     .cmd = my-script 123
>
> would run 'my-script "$@"'.  What should you write in my-script to
> cause that happen?  Here is an example solution:
>
>     #!/bin/sh
>     echo 123
>
> Notice that "$1" is completely ignored, even if the machinery that
> drives .cmd makes three calls?
>
>         sh -c 'my-script 123 "$@"'
>         sh -c 'my-script 123 "$@"' 456
>         sh -c 'my-script 123 "$@"' 789
>
> The way to do the same without an extra script on disk is for you to
> use sh-c yourself.
>
>     .cmd = sh -c 'echo 123'
>

This is indeed a viable solution, But the extra "sh -c" seems to put an
unnecessary burden on the user.
Sometimes I wonder, why not recommend using environment variables
like $ARG?

> And if you do want to use $1, you can do the same.  E.g. if you want
> to double them in the output, you'd probably do something like this:
>
>     .cmd = sh -c 'echo "<$1 - $1>"'
>
> You'd need to quote the value appropriately for the config file,
> though.

In fact, In the following example, trailer <value> contains whitespaces ,

$ git interpret-trailers --trailer="bug:the information manager from hell"

which can make it to work properly (But it's a little bit tedious):

$ git config trailer.bug.cmd "sh -c \"echo \\\"\$1\"\\\""
$ git interpret-trailers --trailer="bug:the information manager from hell"

Bug-from:
Bug-from: the information manager from hell

Is there an easier way?
Or can make the user ignore the details of "sh -c"?

Thanks.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option
  2021-03-31 10:05         ` [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option ZheNing Hu via GitGitGadget
@ 2021-04-01  7:22           ` Christian Couder
  2021-04-01  9:58             ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-01  7:22 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: ZheNing Hu <adlternative@gmail.com>
>
> When we use subprocess to run a shell-script, if we have any

Maybe: s/subprocess/a subprocess/

> args, git will add extra $@ to the end of the shell-script,
> This can pass positional parameters correctly, But if we just
> want to use some of these passed parameters, git will still
> add an extra "$@", which contains all positional parameters we
> passed. This does not meet our expectations.

I am not sure explaining things using $@ is the best way to make this
as clear as possible. I don't have a clear alternative right now
though.

> E.g. our shell-script is:
> "echo \"\$1\""
> and pass $1 "abc",

Maybe: s/pass $1 "abc"/we pass "abc" as $1/

> git will change our script to:
> "echo \"\$1\" \"$@\""

Where will "abc" appear then?

> The positional parameters we entered will be printed
> repeatedly.

If you take us passing "abc" in $1 as an example, then I think it's a
good idea to show us the result of that.

> So let add a new `shell_no_implicit_args`

Maybe: s/`shell_no_implicit_args`/`shell_no_implicit_args` flag/

> to `struct child_process`, which can suppress the
> joining of $@ if `shell_no_implicit_args` is set to 1,
> this will allow us to use only few of positional args
> in multi-parameter shell script, instead of using all
> of them.

I think our goal is more to have each argument we pass be passed just once.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
  2021-03-31 10:05         ` [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option ZheNing Hu via GitGitGadget
  2021-03-31 10:05         ` [PATCH v5 2/2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
@ 2021-04-01  7:28         ` Christian Couder
  2021-04-01 10:02           ` ZheNing Hu
  2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
  3 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-01  7:28 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> Christian talked about the problem of using strbuf_replace() to replace
> $ARG.

It's better to sum up a bit the discussions. It's nice to provide a
link to the discussions though.

> Now pass trailer value as $1 to the trailer command with another
> trailer.<token>.cmd config.

If this patch series introduces a new trailer.<token>.cmd config
option, then I would expect one of the patch in the series to have a
subject like "trailer: add new trailer.<token>.cmd config option".

> ZheNing Hu (2):
>   [GSOC] run-command: add shell_no_implicit_args option
>   [GSOC]trailer: pass arg as positional parameter

I guess the "trailer: pass arg as positional parameter" is the one
introducing the new trailer.<token>.cmd config option.

Also it seems strange that there is no space between "[GSOC]" and "trailer".

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option
  2021-04-01  7:22           ` Christian Couder
@ 2021-04-01  9:58             ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-01  9:58 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano

Christian Couder <christian.couder@gmail.com> 于2021年4月1日周四 下午3:22写道:
>
> On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > When we use subprocess to run a shell-script, if we have any
>
> Maybe: s/subprocess/a subprocess/
>
> > args, git will add extra $@ to the end of the shell-script,
> > This can pass positional parameters correctly, But if we just
> > want to use some of these passed parameters, git will still
> > add an extra "$@", which contains all positional parameters we
> > passed. This does not meet our expectations.
>
> I am not sure explaining things using $@ is the best way to make this
> as clear as possible. I don't have a clear alternative right now
> though.
>
> > E.g. our shell-script is:
> > "echo \"\$1\""
> > and pass $1 "abc",
>
> Maybe: s/pass $1 "abc"/we pass "abc" as $1/
>
> > git will change our script to:
> > "echo \"\$1\" \"$@\""
>
> Where will "abc" appear then?
>
> > The positional parameters we entered will be printed
> > repeatedly.
>
> If you take us passing "abc" in $1 as an example, then I think it's a
> good idea to show us the result of that.
>
> > So let add a new `shell_no_implicit_args`
>
> Maybe: s/`shell_no_implicit_args`/`shell_no_implicit_args` flag/
>

Thanks for these grammar corrections.

> > to `struct child_process`, which can suppress the
> > joining of $@ if `shell_no_implicit_args` is set to 1,
> > this will allow us to use only few of positional args
> > in multi-parameter shell script, instead of using all
> > of them.
>
> I think our goal is more to have each argument we pass be passed just once.

More accurately, we only want those explicit positional
parameters to be replaced.
But Junio probably thinks it's OK to put on a layer of
"sh -c"  to "absorb" the "$@". I think it works, but it may
cause some trouble for users.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v5 0/2] [GSOC]trailer: pass arg as positional parameter
  2021-04-01  7:28         ` [PATCH v5 0/2] " Christian Couder
@ 2021-04-01 10:02           ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-01 10:02 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano

Christian Couder <christian.couder@gmail.com> 于2021年4月1日周四 下午3:28写道:
>
> On Wed, Mar 31, 2021 at 12:05 PM ZheNing Hu via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> > Christian talked about the problem of using strbuf_replace() to replace
> > $ARG.
>
> It's better to sum up a bit the discussions. It's nice to provide a
> link to the discussions though.
>
> > Now pass trailer value as $1 to the trailer command with another
> > trailer.<token>.cmd config.
>
> If this patch series introduces a new trailer.<token>.cmd config
> option, then I would expect one of the patch in the series to have a
> subject like "trailer: add new trailer.<token>.cmd config option".
>
> > ZheNing Hu (2):
> >   [GSOC] run-command: add shell_no_implicit_args option
> >   [GSOC]trailer: pass arg as positional parameter
>
> I guess the "trailer: pass arg as positional parameter" is the one
> introducing the new trailer.<token>.cmd config option.
>
> Also it seems strange that there is no space between "[GSOC]" and "trailer".

Thanks, I'll fix them.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-04-01  3:56                               ` ZheNing Hu
@ 2021-04-01 19:49                                 ` Junio C Hamano
  2021-04-02  2:08                                   ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-01 19:49 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

>> The way to do the same without an extra script on disk is for you to
>> use sh-c yourself.
>>
>>     .cmd = sh -c 'echo 123'
>
> This is indeed a viable solution, But the extra "sh -c" seems to put an
> unnecessary burden on the user.

Nobody forces you to write long script in the configuration file.
In fact, the "find author from history" is so useful that I'd think
people have an alias or a script in ~/bin/ already for their own
interactive use.  E.g.

    $ cat ~/bin/git-who
    #!/bin/sh
    git log -1 --format="%an <%ae>" --author="$1"
    $ cat ~/bin/git-one
    #!/bin/sh
    git show -s --pretty=reference "$1"

and with them:

	trailer.key.cmd = git who

that is internally wrapped into 

	sh -c 'git who "$@"'

and fed "gitster@" as the first parameter when "--trailer=key:gitster@"
is given would work just fine.

> Sometimes I wonder, why not recommend using environment variables
> like $ARG?

I am also fine with that; when we discovered the design flaw of
.command, I think I suggested either would make a viable choice.
The only downside is that it would squat on a good name $ARG and
forbids end-users from using the symbol for other purpose, but as
long as the application is limited in scope, that would be fine.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v4] [GSOC]trailer: pass arg as positional parameter
  2021-04-01 19:49                                 ` Junio C Hamano
@ 2021-04-02  2:08                                   ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-02  2:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年4月2日周五 上午3:49写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> >> The way to do the same without an extra script on disk is for you to
> >> use sh-c yourself.
> >>
> >>     .cmd = sh -c 'echo 123'
> >
> > This is indeed a viable solution, But the extra "sh -c" seems to put an
> > unnecessary burden on the user.
>
> Nobody forces you to write long script in the configuration file.
> In fact, the "find author from history" is so useful that I'd think
> people have an alias or a script in ~/bin/ already for their own
> interactive use.  E.g.
>
>     $ cat ~/bin/git-who
>     #!/bin/sh
>     git log -1 --format="%an <%ae>" --author="$1"
>     $ cat ~/bin/git-one
>     #!/bin/sh
>     git show -s --pretty=reference "$1"
>
> and with them:
>
>         trailer.key.cmd = git who
>
> that is internally wrapped into
>
>         sh -c 'git who "$@"'
>
> and fed "gitster@" as the first parameter when "--trailer=key:gitster@"
> is given would work just fine.
>

Okay, now I get it. This allows us to run some common scripts or just use
"sh -c". I might need some additional tests to illustrate those changes made
by using '.cmd'.

> > Sometimes I wonder, why not recommend using environment variables
> > like $ARG?
>
> I am also fine with that; when we discovered the design flaw of
> .command, I think I suggested either would make a viable choice.
> The only downside is that it would squat on a good name $ARG and
> forbids end-users from using the symbol for other purpose, but as
> long as the application is limited in scope, that would be fine.

I agree with you. This may indeed be a minor drawback.

Thanks, Junio.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
                           ` (2 preceding siblings ...)
  2021-04-01  7:28         ` [PATCH v5 0/2] " Christian Couder
@ 2021-04-02 13:26         ` ZheNing Hu via GitGitGadget
  2021-04-02 20:48           ` Junio C Hamano
                             ` (2 more replies)
  3 siblings, 3 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-02 13:26 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

The `trailer.<token>.command` configuration variable
specifies a command (run via the shell, so it does not have
to be a single name of 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.  This has two 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 unmatching 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).

Introduce a new `trailer.<token>.cmd` configuration that
takes higher precedence to deprecate and eventually remove
`trailer.<token>.command`, which passes the value as a
parameter to the command.  Instead of "$ARG", the users will
refer to the value as positional argument, $1, in their
scripts.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC] trailer: add new trailer..cmd config option
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG:
    
     1. if user's script have more than one $ARG, only the first one will be
        replaced, which is incorrected.
     2. $ARG is textually replaced without shell syntax, which may result a
        broken command when $ARG include some unmatching single quote, very
        unsafe.
    
    Now pass trailer value as $1 to the trailer command with another
    trailer.<token>.cmd config, to solve these above two problems,

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v5:

 1:  4c59cab53a0d < -:  ------------ [GSOC] run-command: add shell_no_implicit_args option
 2:  5894d8c4b364 ! 1:  3aed77d077b9 [GSOC]trailer: pass arg as positional parameter
     @@ Metadata
      Author: ZheNing Hu <adlternative@gmail.com>
      
       ## Commit message ##
     -    [GSOC]trailer: pass arg as positional parameter
     +    [GSOC] trailer: add new trailer.<token>.cmd config option
      
          The `trailer.<token>.command` configuration variable
          specifies a command (run via the shell, so it does not have
     @@ Documentation/git-interpret-trailers.txt: subject
       Fix #42
       ------------
       
     -+* Configure a 'see' trailer with a cmd to show the subject of a
     -+  commit that is related, and show how it works:
     ++* Configure a 'see' trailer with a cmd use a global script `git-one`
     ++  to show the subject of a commit that is related, and show how it works:
      ++
      +------------
     ++$ cat ~/bin/git-one
     ++#!/bin/sh
     ++git show -s --pretty=reference "$1"
      +$ git config trailer.see.key "See-also: "
      +$ git config trailer.see.ifExists "replace"
      +$ git config trailer.see.ifMissing "doNothing"
     -+$ git config trailer.see.cmd "git show -s --pretty=reference \"\$1\""
     ++$ git config trailer.see.cmd "~/bin/git-one"
      +$ git interpret-trailers <<EOF
      +> subject
      +> 
     @@ Documentation/git-interpret-trailers.txt: subject
      +
      +message
      +
     -+See-also: fe3187489d69c4 (subject of related commit, 2021-3-20)
     ++See-also: fe3187e (subject of related commit, 2021-4-2)
      +------------
      +
     -+* Configure a 'bug' trailer with a cmd to show when and where
     -+  was the bug introduced, and show how it works:
     ++* Configure a 'who' trailer with a cmd use a global script `git-who`
     ++  to find the recent matching "author <mail>" pair in git log and
     ++  show how it works:
      ++
      +------------
     -+$ git config trailer.bug.key "Bug-from: "
     -+$ git config trailer.bug.ifExists "replace"
     -+$ git config trailer.bug.cmd "git log --grep \"\$1\" -1 --pretty=\"%h %aD\""
     -+$ git interpret-trailers --trailer="bug:the information manager from hell" <<EOF
     ++$ cat ~/bin/git-who
     ++ #!/bin/sh
     ++    git log -1 --format="%an <%ae>" --author="$1"
     ++$ git config trailer.help.key "Helped-by: "
     ++$ git config trailer.help.ifExists "replace"
     ++$ git config trailer.help.cmd "~/bin/git-who"
     ++$ git interpret-trailers --trailer="help:gitster@" <<EOF
      +> subject
      +> 
      +> message
     @@ Documentation/git-interpret-trailers.txt: subject
      +
      +message
      +
     -+Bug-from: 57d84f8d93 Mon, 6 Aug 2012 18:27:09 +0700
     ++Helped-by: Junio C Hamano <gitster@pobox.com>
      +------------
      +
       * Configure a 'see' trailer with a command to show the subject of a
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
       
      +test_expect_success 'with cmd' '
      +	test_when_finished "git config --unset trailer.bug.key && \
     -+	git config --unset trailer.bug.ifExists && \
     -+	git config --unset trailer.bug.cmd" &&
     ++			    git config --unset trailer.bug.ifExists && \
     ++			    git config --unset trailer.bug.cmd" &&
      +	git config trailer.bug.key "Bug-maker: " &&
      +	git config trailer.bug.ifExists "add" &&
     -+	git config trailer.bug.cmd "echo \"\$@\"" &&
     -+	cat >>expected2 <<-EOF &&
     ++	git config trailer.bug.cmd "echo \"maybe is\"" &&
     ++	cat >expected2 <<-EOF &&
      +
     -+	Bug-maker: 
     -+	Bug-maker: jocker
     -+	Bug-maker: batman
     ++	Bug-maker: maybe is
     ++	Bug-maker: maybe is him
     ++	Bug-maker: maybe is me
      +	EOF
     -+	git interpret-trailers --trailer "bug: jocker" --trailer "bug:batman" \
     ++	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 --unset trailer.bug.key && \
     ++			    git config --unset trailer.bug.ifExists && \
     ++			    git config --unset trailer.bug.cmd" &&
     ++	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 --unset trailer.bug.key && \
     ++			    git config --unset trailer.bug.ifExists && \
     ++			    git config --unset trailer.bug.cmd" &&
     ++	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 --unset trailer.bug.key && \
     ++			    git config --unset trailer.bug.ifExists && \
     ++			    git config --unset trailer.bug.cmd" &&
     ++	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
      +'
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
       	git commit -m "Add file a.txt"
       '
       
     -+test_expect_success 'with cmd and $1' '
     -+	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 (%s)\" \
     -+		--abbrev-commit --abbrev=14 \"\$1\" || true" &&
     -+	FIXED=$(git log -1 --oneline --format="%h (%s)" --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 'cmd takes precedence over command' '
      +	test_when_finished "git config --unset trailer.fix.cmd" &&
      +	git config trailer.fix.ifExists "replace" &&
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
      -
      -	strvec_push(&cp.args, cmd.buf);
      +	if (conf->cmd) {
     -+		cp.shell_no_implicit_args = 1;
     ++		// cp.shell_no_implicit_args = 1;
      +		strbuf_addstr(&cmd, conf->cmd);
      +		strvec_push(&cp.args, cmd.buf);
      +		if (arg)
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     --	cp.shell_no_implicit_args = 1;
     - 
     - 	if (capture_command(&cp, &buf, 1024)) {
     - 		error(_("running trailer command '%s' failed"), cmd.buf);
      @@ trailer.c: 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)


 Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---
 t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-
 trailer.c                                | 38 +++++++---
 3 files changed, 193 insertions(+), 22 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..67649ec6134c 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -236,21 +236,34 @@ 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
-'<token>=<value>' argument were added at the beginning of the command
-line, 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.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+".command" has been deprecated due to the $ARG in the user's command can
+only be replaced once and the original way of replacing $ARG was not safe.
+Now the preferred option is using "trailer.<token>.cmd", which use position
+argument to pass the value.
++
+When both .cmd and .command are given for the same <token>,
+.cmd is used and .command is ignored.
+
+trailer.<token>.cmd::
+	The command specified by this configuration variable is run
+	with a single parameter, which is the <value> part of an
+	existing trailer with the same <token>.  The output from the
+	command is then used as the value for the <token> in the
+	resulting trailer.
++
+When this option is specified, If there is no trailer with same <token>,
+the behavior is as if a special '<token>=<value>' argument were added at
+the beginning of the command, <value> will be passed to the user's
+command as an empty value.
 +
 If some '<token>=<value>' arguments are also passed on the command
 line, when a 'trailer.<token>.command' is configured, the command will
 also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+these arguments, if any, will be passed to the command as first parameter.
 
 EXAMPLES
 --------
@@ -333,6 +346,55 @@ subject
 Fix #42
 ------------
 
+* Configure a 'see' trailer with a cmd use a global script `git-one`
+  to show the subject of a commit that is related, and show how it works:
++
+------------
+$ cat ~/bin/git-one
+#!/bin/sh
+git show -s --pretty=reference "$1"
+$ git config trailer.see.key "See-also: "
+$ git config trailer.see.ifExists "replace"
+$ git config trailer.see.ifMissing "doNothing"
+$ git config trailer.see.cmd "~/bin/git-one"
+$ git interpret-trailers <<EOF
+> subject
+> 
+> message
+> 
+> see: HEAD~2
+> EOF
+subject
+
+message
+
+See-also: fe3187e (subject of related commit, 2021-4-2)
+------------
+
+* Configure a 'who' trailer with a cmd use a global script `git-who`
+  to find the recent matching "author <mail>" pair in git log and
+  show how it works:
++
+------------
+$ cat ~/bin/git-who
+ #!/bin/sh
+    git log -1 --format="%an <%ae>" --author="$1"
+$ git config trailer.help.key "Helped-by: "
+$ git config trailer.help.ifExists "replace"
+$ git config trailer.help.cmd "~/bin/git-who"
+$ git interpret-trailers --trailer="help:gitster@" <<EOF
+> subject
+> 
+> message
+> 
+> EOF
+subject
+
+message
+
+Helped-by: Junio C Hamano <gitster@pobox.com>
+------------
+
 * Configure a 'see' trailer with a command to show the subject of a
   commit that is related, and show how it works:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..923923e57573 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -51,6 +51,77 @@ test_expect_success 'setup' '
 	EOF
 '
 
+test_expect_success 'with cmd' '
+	test_when_finished "git config --unset trailer.bug.key && \
+			    git config --unset trailer.bug.ifExists && \
+			    git config --unset trailer.bug.cmd" &&
+	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 --unset trailer.bug.key && \
+			    git config --unset trailer.bug.ifExists && \
+			    git config --unset trailer.bug.cmd" &&
+	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 --unset trailer.bug.key && \
+			    git config --unset trailer.bug.ifExists && \
+			    git config --unset trailer.bug.cmd" &&
+	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 --unset trailer.bug.key && \
+			    git config --unset trailer.bug.ifExists && \
+			    git config --unset trailer.bug.cmd" &&
+	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,9 +1345,31 @@ 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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..6aeff6a1bd33 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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,25 @@ 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) {
+		// cp.shell_no_implicit_args = 1;
+		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);
+		strvec_push(&cp.args, cmd.buf);
+		if (arg)
+			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
+	}
 	cp.env = local_repo_env;
 	cp.no_stdin = 1;
 	cp.use_shell = 1;
@@ -247,7 +256,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 +266,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 +439,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 +464,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 +473,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 +553,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 +724,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(""),

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-02 20:48           ` Junio C Hamano
  2021-04-03  5:08             ` ZheNing Hu
                               ` (2 more replies)
  2021-04-02 23:44           ` Junio C Hamano
  2021-04-04 13:11           ` [PATCH v7] " ZheNing Hu via GitGitGadget
  2 siblings, 3 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-02 20:48 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> quote (imagine a name like "O'Connor", substituted into
> NAME='$ARG' to make it NAME='O'Connor), it would result in

You inherited a typo from my review comments here.  This line should
have said

    NAME='$ARG' to make it NAME='O'Connor'), it would result in

I will tweak locally while queuing (read: no need to send an update
only to fix this line---but please do not forget to change it if you
are going to send an update to fix or improve other things).


>  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---
>  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-
>  trailer.c                                | 38 +++++++---
>  3 files changed, 193 insertions(+), 22 deletions(-)
>
> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 96ec6499f001..67649ec6134c 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -236,21 +236,34 @@ 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
> -'<token>=<value>' argument were added at the beginning of the command
> -line, 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.
>  +
> -If the command contains the `$ARG` string, this string will be
> -replaced with the <value> part of an existing trailer with the same
> -<token>, if any, before the command is launched.
> +".command" has been deprecated due to the $ARG in the user's command can
> +only be replaced once and the original way of replacing $ARG was not safe.
> +Now the preferred option is using "trailer.<token>.cmd", which use position
> +argument to pass the value.
> ++
> +When both .cmd and .command are given for the same <token>,
> +.cmd is used and .command is ignored.

Warning about unsafe replacement is a good idea.  OK.

> +trailer.<token>.cmd::
> +	The command specified by this configuration variable is run
> +	with a single parameter, which is the <value> part of an
> +	existing trailer with the same <token>.  The output from the
> +	command is then used as the value for the <token> in the
> +	resulting trailer.
> ++
> +When this option is specified, If there is no trailer with same <token>,

s/If/if/ (downcase).

> +the behavior is as if a special '<token>=<value>' argument were added at
> +the beginning of the command, <value> will be passed to the user's
> +command as an empty value.

Do the two occurrences of the word "command" in the sentence refer
to different things?  I do not think this is an existing problem
inherited from the original, but as we are trying to improve the
description, I wonder if we can clarify them a bit.

	... as if a '<token>=<value>' argument were 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.

is my attempt, but I am not still sure what that "as if" part is
trying to say.  Does it mean with

	[trailer "Foo"] cmd = foo-cmd

and the 'input-file' does not have "Foo: <some existing value>"
trailer in it, the command "git interpret-trailers input-file"
would behave as if this command was run

	$ Foo= git interpret-trailers input-file

(as there is no <value>, I am not sure what <value> is used when
<token>=<value> is prefixed to the command)?

Puzzled and confused utterly am I...  Help, Christian?

>  +
>  If some '<token>=<value>' arguments are also passed on the command
>  line, when a 'trailer.<token>.command' is configured, the command will
>  also be executed for each of these arguments. And the <value> part of

This talks about 'trailer.<token>.command'.  Should this be changed
to '.cmd'? 

Or does everything after "When this option is specified, if there is
no trailer with ..." apply to both the old .command and new .cmd?
If so, that was not clear at all---we'd need to clarify this part.

> -these arguments, if any, will be used to replace the `$ARG` string in
> -the command.
> +these arguments, if any, will be passed to the command as first parameter.
>  
>  EXAMPLES
>  --------
> @@ -333,6 +346,55 @@ subject
>  Fix #42
>  ------------
>  
> +* Configure a 'see' trailer with a cmd use a global script `git-one`
> +  to show the subject of a commit that is related, and show how it works:
> ++
> +------------
> +$ cat ~/bin/git-one
> +#!/bin/sh
> +git show -s --pretty=reference "$1"
> +$ git config trailer.see.key "See-also: "
> +$ git config trailer.see.ifExists "replace"
> +$ git config trailer.see.ifMissing "doNothing"
> +$ git config trailer.see.cmd "~/bin/git-one"
> +$ git interpret-trailers <<EOF
> +> subject
> +> 
> +> message
> +> 
> +> see: HEAD~2
> +> EOF
> +subject
> +
> +message
> +
> +See-also: fe3187e (subject of related commit, 2021-4-2)
> +------------
> +
> +* Configure a 'who' trailer with a cmd use a global script `git-who`
> +  to find the recent matching "author <mail>" pair in git log and
> +  show how it works:
> ++
> +------------
> +$ cat ~/bin/git-who
> + #!/bin/sh
> +    git log -1 --format="%an <%ae>" --author="$1"

Unusual indentation here.  But more importantly, I am not sure if 
having both 'see' and 'help' examples is worth it---they are similar
enough that the second one does not teach anything new to those who
studied the first one already, aren't they?

> +$ git config trailer.help.key "Helped-by: "
> +$ git config trailer.help.ifExists "replace"
> +$ git config trailer.help.cmd "~/bin/git-who"
> +$ git interpret-trailers --trailer="help:gitster@" <<EOF
> +> subject
> +> 
> +> message
> +> 
> +> EOF
> +subject
> +
> +message
> +
> +Helped-by: Junio C Hamano <gitster@pobox.com>
> +------------
> +
>  * Configure a 'see' trailer with a command to show the subject of a
>    commit that is related, and show how it works:
>  +



> diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
> index 6602790b5f4c..923923e57573 100755
> --- a/t/t7513-interpret-trailers.sh
> +++ b/t/t7513-interpret-trailers.sh
> @@ -51,6 +51,77 @@ test_expect_success 'setup' '
>  	EOF
>  '
>  
> +test_expect_success 'with cmd' '
> +	test_when_finished "git config --unset trailer.bug.key && \
> +			    git config --unset trailer.bug.ifExists && \
> +			    git config --unset trailer.bug.cmd" &&

It is unwise to use && between these three "git config" invocations,
I suspect.  "git config --unset" exits with non-zero status when you
attempt to remove with an non-existent key, but you would remove the
.ifExists and .cmd even if .key is not defined.  Perhaps

	test_when_finished "git config --unset-all trailer.bug.key
			    git config --unset-all trailer.bug.ifExists
			    git config --unset-all trailer.bug.cmd" &&

would be more sensible.  Or if we just want to remove everything
under the trailer.bug.* section, this might be even better:

	test_when_finished "git config --remove-section trailer.bug" &&

as we can add new trailer.bug.* to the system and use them in this
test, but removing the entire section would still be a good way to
clean after ourselves.

> diff --git a/trailer.c b/trailer.c
> index be4e9726421c..6aeff6a1bd33 100644
> --- a/trailer.c
> +++ b/trailer.c
> ...
> +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) {
> +		// cp.shell_no_implicit_args = 1;

Do not add new code that is commented out.  Besides we do not use // comment.

> +		strbuf_addstr(&cmd, conf->cmd);
> +		strvec_push(&cp.args, cmd.buf);
> +		if (arg)
> +			strvec_push(&cp.args, arg);

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
  2021-04-02 20:48           ` Junio C Hamano
@ 2021-04-02 23:44           ` Junio C Hamano
  2021-04-03  3:22             ` ZheNing Hu
  2021-04-04 13:11           ` [PATCH v7] " ZheNing Hu via GitGitGadget
  2 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-02 23:44 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> From: ZheNing Hu <adlternative@gmail.com>
>
> The `trailer.<token>.command` configuration variable
> specifies a command (run via the shell, so it does not have
> to be a single name of 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.  This has two downsides:
> ...
>
>  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---
>  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-
>  trailer.c                                | 38 +++++++---
>  3 files changed, 193 insertions(+), 22 deletions(-)

Merging this to anything that has zh/commit-trailer (which extends
the tests in t7502) seems to break t7502.  Running the test with the
"-i -v" option ends like so.

expecting success of 7502.24 'commit --trailer with -c and command': 
	trailer_commit_base &&
	cat >expected <<-\EOF &&
	hello

	Signed-off-by: C O Mitter <committer@example.com>
	Signed-off-by: C1 E1
	Helped-by: C2 E2
	Mentored-by: C4 E4
	Reported-by: A U Thor <author@example.com>
	EOF
	git -c trailer.report.key="Reported-by: " \
		-c trailer.report.ifexists="replace" \
		-c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
		git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
		commit --trailer "report = author" --amend &&
	git cat-file commit HEAD >commit.msg &&
	sed -e "1,/^\$/d" commit.msg >actual &&
	test_cmp expected actual

[main 6b1e5e9] hello
 Author: A U Thor <author@example.com>
 1 file changed, 1 insertion(+)
[main 97c7a39] hello
 Author: A U Thor <author@example.com>
 Date: Thu Apr 7 15:22:13 2005 -0700
 1 file changed, 1 insertion(+)
--- expected	2021-04-02 23:43:10.649082950 +0000
+++ actual	2021-04-02 23:43:10.673085111 +0000
@@ -4,4 +4,4 @@
 Signed-off-by: C1 E1
 Helped-by: C2 E2
 Mentored-by: C4 E4
-Reported-by: A U Thor <author@example.com>
+Reported-by:
not ok 24 - commit --trailer with -c and command
#	
#		trailer_commit_base &&
#		cat >expected <<-\EOF &&
#		hello
#	
#		Signed-off-by: C O Mitter <committer@example.com>
#		Signed-off-by: C1 E1
#		Helped-by: C2 E2
#		Mentored-by: C4 E4
#		Reported-by: A U Thor <author@example.com>
#		EOF
#		git -c trailer.report.key="Reported-by: " \
#			-c trailer.report.ifexists="replace" \
#			-c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
#			git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
#			commit --trailer "report = author" --amend &&
#		git cat-file commit HEAD >commit.msg &&
#		sed -e "1,/^\$/d" commit.msg >actual &&
#		test_cmp expected actual
#	

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 23:44           ` Junio C Hamano
@ 2021-04-03  3:22             ` ZheNing Hu
  2021-04-03  4:31               ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-03  3:22 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 上午7:44写道:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > The `trailer.<token>.command` configuration variable
> > specifies a command (run via the shell, so it does not have
> > to be a single name of 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.  This has two downsides:
> > ...
> >
> >  Documentation/git-interpret-trailers.txt | 82 +++++++++++++++++---
> >  t/t7513-interpret-trailers.sh            | 95 +++++++++++++++++++++++-
> >  trailer.c                                | 38 +++++++---
> >  3 files changed, 193 insertions(+), 22 deletions(-)
>
> Merging this to anything that has zh/commit-trailer (which extends
> the tests in t7502) seems to break t7502.  Running the test with the
> "-i -v" option ends like so.
>
> expecting success of 7502.24 'commit --trailer with -c and command':
>         trailer_commit_base &&
>         cat >expected <<-\EOF &&
>         hello
>
>         Signed-off-by: C O Mitter <committer@example.com>
>         Signed-off-by: C1 E1
>         Helped-by: C2 E2
>         Mentored-by: C4 E4
>         Reported-by: A U Thor <author@example.com>
>         EOF
>         git -c trailer.report.key="Reported-by: " \
>                 -c trailer.report.ifexists="replace" \
>                 -c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
>                 git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
>                 commit --trailer "report = author" --amend &&
>         git cat-file commit HEAD >commit.msg &&
>         sed -e "1,/^\$/d" commit.msg >actual &&
>         test_cmp expected actual
>
> [main 6b1e5e9] hello
>  Author: A U Thor <author@example.com>
>  1 file changed, 1 insertion(+)
> [main 97c7a39] hello
>  Author: A U Thor <author@example.com>
>  Date: Thu Apr 7 15:22:13 2005 -0700
>  1 file changed, 1 insertion(+)
> --- expected    2021-04-02 23:43:10.649082950 +0000
> +++ actual      2021-04-02 23:43:10.673085111 +0000
> @@ -4,4 +4,4 @@
>  Signed-off-by: C1 E1
>  Helped-by: C2 E2
>  Mentored-by: C4 E4
> -Reported-by: A U Thor <author@example.com>
> +Reported-by:
> not ok 24 - commit --trailer with -c and command
> #
> #               trailer_commit_base &&
> #               cat >expected <<-\EOF &&
> #               hello
> #
> #               Signed-off-by: C O Mitter <committer@example.com>
> #               Signed-off-by: C1 E1
> #               Helped-by: C2 E2
> #               Mentored-by: C4 E4
> #               Reported-by: A U Thor <author@example.com>
> #               EOF
> #               git -c trailer.report.key="Reported-by: " \
> #                       -c trailer.report.ifexists="replace" \
> #                       -c trailer.report.command="NAME=\"\$ARG\"; test -n \"\$NAME\" && \
> #                       git log --author=\"\$NAME\" -1 --format=\"format:%aN <%aE>\" || true" \
> #                       commit --trailer "report = author" --amend &&
> #               git cat-file commit HEAD >commit.msg &&
> #               sed -e "1,/^\$/d" commit.msg >actual &&
> #               test_cmp expected actual
> #

Little bug, Change it like this will work:

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

thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-03  3:22             ` ZheNing Hu
@ 2021-04-03  4:31               ` Junio C Hamano
  2021-04-03  5:15                 ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-03  4:31 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

> Little bug, Change it like this will work:
>
>         } else if (conf->command) {
>                 strbuf_addstr(&cmd, conf->command);
> -               strvec_push(&cp.args, cmd.buf);
>                 if (arg)
>                         strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
> +               strvec_push(&cp.args, cmd.buf);
>         }

It means the coverage of the test included in this patch was not
sufficient, right?

I queued this separately from the "commit --trailer" topic in
tonight's pushout, but it may make sense to queue this step
immediately on top of the "commit --trailer" patch.

In any case, I suspect we would not hear from Christian right away
due to timezone differences, so perhaps let this v6 simmer on the
list, and then hopefully update the documentation part with his
input.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 20:48           ` Junio C Hamano
@ 2021-04-03  5:08             ` ZheNing Hu
  2021-04-04  5:34               ` Junio C Hamano
  2021-04-03  5:51             ` Christian Couder
  2021-04-04  5:43             ` ZheNing Hu
  2 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-03  5:08 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 上午4:49写道:

> > +the behavior is as if a special '<token>=<value>' argument were added at
> > +the beginning of the command, <value> will be passed to the user's
> > +command as an empty value.
>
> Do the two occurrences of the word "command" in the sentence refer
> to different things?  I do not think this is an existing problem
> inherited from the original, but as we are trying to improve the
> description, I wonder if we can clarify them a bit.
>
>         ... as if a '<token>=<value>' argument were 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.
>
> is my attempt, but I am not still sure what that "as if" part is
> trying to say.  Does it mean with
>
>         [trailer "Foo"] cmd = foo-cmd
>
> and the 'input-file' does not have "Foo: <some existing value>"
> trailer in it, the command "git interpret-trailers input-file"
> would behave as if this command was run
>
>         $ Foo= git interpret-trailers input-file
>
> (as there is no <value>, I am not sure what <value> is used when
> <token>=<value> is prefixed to the command)?
>
> Puzzled and confused utterly am I...  Help, Christian?
>

If we don’t want a input-file without any trailers to execute this command,
 I’m thinking that we maybe need "trailer.<token>.ifmissing=doNothing"
or something else, otherwise execute this 'alwaysRunCmd' for create a
empty trailer like "Signed-off-by" and users can use it to add content.

> >  +
> >  If some '<token>=<value>' arguments are also passed on the command
> >  line, when a 'trailer.<token>.command' is configured, the command will
> >  also be executed for each of these arguments. And the <value> part of
>
> This talks about 'trailer.<token>.command'.  Should this be changed
> to '.cmd'?
>
> Or does everything after "When this option is specified, if there is
> no trailer with ..." apply to both the old .command and new .cmd?
> If so, that was not clear at all---we'd need to clarify this part.
>

Because ".command" will be eliminated, may be only leave those
description Information to ".cmd" is better.

> > -these arguments, if any, will be used to replace the `$ARG` string in
> > -the command.
> > +these arguments, if any, will be passed to the command as first parameter.
> >
> >  EXAMPLES
> >  --------
> > @@ -333,6 +346,55 @@ subject
> >  Fix #42
> >  ------------
> >
> > +* Configure a 'see' trailer with a cmd use a global script `git-one`
> > +  to show the subject of a commit that is related, and show how it works:
> > ++
> > +------------
> > +$ cat ~/bin/git-one
> > +#!/bin/sh
> > +git show -s --pretty=reference "$1"
> > +$ git config trailer.see.key "See-also: "
> > +$ git config trailer.see.ifExists "replace"
> > +$ git config trailer.see.ifMissing "doNothing"
> > +$ git config trailer.see.cmd "~/bin/git-one"
> > +$ git interpret-trailers <<EOF
> > +> subject
> > +>
> > +> message
> > +>
> > +> see: HEAD~2
> > +> EOF
> > +subject
> > +
> > +message
> > +
> > +See-also: fe3187e (subject of related commit, 2021-4-2)
> > +------------
> > +
> > +* Configure a 'who' trailer with a cmd use a global script `git-who`
> > +  to find the recent matching "author <mail>" pair in git log and
> > +  show how it works:
> > ++
> > +------------
> > +$ cat ~/bin/git-who
> > + #!/bin/sh
> > +    git log -1 --format="%an <%ae>" --author="$1"
>
> Unusual indentation here.  But more importantly, I am not sure if
> having both 'see' and 'help' examples is worth it---they are similar
> enough that the second one does not teach anything new to those who
> studied the first one already, aren't they?
>

Ok, I will think about other examples.

> > +$ git config trailer.help.key "Helped-by: "
> > +$ git config trailer.help.ifExists "replace"
> > +$ git config trailer.help.cmd "~/bin/git-who"
> > +$ git interpret-trailers --trailer="help:gitster@" <<EOF
> > +> subject
> > +>
> > +> message
> > +>
> > +> EOF
> > +subject
> > +
> > +message
> > +
> > +Helped-by: Junio C Hamano <gitster@pobox.com>
> > +------------
> > +
> >  * Configure a 'see' trailer with a command to show the subject of a
> >    commit that is related, and show how it works:
> >  +
>
>
>
> > diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
> > index 6602790b5f4c..923923e57573 100755
> > --- a/t/t7513-interpret-trailers.sh
> > +++ b/t/t7513-interpret-trailers.sh
> > @@ -51,6 +51,77 @@ test_expect_success 'setup' '
> >       EOF
> >  '
> >
> > +test_expect_success 'with cmd' '
> > +     test_when_finished "git config --unset trailer.bug.key && \
> > +                         git config --unset trailer.bug.ifExists && \
> > +                         git config --unset trailer.bug.cmd" &&
>
> It is unwise to use && between these three "git config" invocations,
> I suspect.  "git config --unset" exits with non-zero status when you
> attempt to remove with an non-existent key, but you would remove the
> .ifExists and .cmd even if .key is not defined.  Perhaps
>
>         test_when_finished "git config --unset-all trailer.bug.key
>                             git config --unset-all trailer.bug.ifExists
>                             git config --unset-all trailer.bug.cmd" &&
>
> would be more sensible.  Or if we just want to remove everything
> under the trailer.bug.* section, this might be even better:
>
>         test_when_finished "git config --remove-section trailer.bug" &&
>
> as we can add new trailer.bug.* to the system and use them in this
> test, but removing the entire section would still be a good way to
> clean after ourselves.
>

Good idea, This removing is also more efficient.

> > diff --git a/trailer.c b/trailer.c
> > index be4e9726421c..6aeff6a1bd33 100644
> > --- a/trailer.c
> > +++ b/trailer.c
> > ...
> > +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) {
> > +             // cp.shell_no_implicit_args = 1;
>
> Do not add new code that is commented out.  Besides we do not use // comment.
>
> > +             strbuf_addstr(&cmd, conf->cmd);
> > +             strvec_push(&cp.args, cmd.buf);
> > +             if (arg)
> > +                     strvec_push(&cp.args, arg);
>
> Thanks.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-03  4:31               ` Junio C Hamano
@ 2021-04-03  5:15                 ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-03  5:15 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月3日周六 下午12:31写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > Little bug, Change it like this will work:
> >
> >         } else if (conf->command) {
> >                 strbuf_addstr(&cmd, conf->command);
> > -               strvec_push(&cp.args, cmd.buf);
> >                 if (arg)
> >                         strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
> > +               strvec_push(&cp.args, cmd.buf);
> >         }
>
> It means the coverage of the test included in this patch was not
> sufficient, right?
>

Indeed, I might have to think what tests need to be added to the ".cmd".

> I queued this separately from the "commit --trailer" topic in
> tonight's pushout, but it may make sense to queue this step
> immediately on top of the "commit --trailer" patch.
>

Maybe I can rebase this "trailer .cmd" to "master" which already have
include "commit --trailer" later, this will cover "commit --trailer" tests.

> In any case, I suspect we would not hear from Christian right away
> due to timezone differences, so perhaps let this v6 simmer on the
> list, and then hopefully update the documentation part with his
> input.
>

Yes, it's worth waiting patiently,

> Thanks.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 20:48           ` Junio C Hamano
  2021-04-03  5:08             ` ZheNing Hu
@ 2021-04-03  5:51             ` Christian Couder
  2021-04-04 23:26               ` Junio C Hamano
  2021-04-04  5:43             ` ZheNing Hu
  2 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-03  5:51 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> > +trailer.<token>.cmd::
> > +     The command specified by this configuration variable is run
> > +     with a single parameter, which is the <value> part of an

I prefer "argument" over "parameter". In the next paragraph you use
"argument" (in "as if a special '<token>=<value>' argument"), so it
would be more consistent and easier to read if "argument" was used
here too.

> > +     existing trailer with the same <token>.

It would be better to say something like "which is the <value> part of
a `--trailer <token>=<value>` on the command line". (Yeah, the
existing doc might be bad about this.)

Also it might be better to explicitly say that the command is run once
for each `--trailer <token>=<value>` on the command line with the same
<token>.

> > The output from the
> > +     command is then used as the value for the <token> in the
> > +     resulting trailer.
> > ++
> > +When this option is specified, If there is no trailer with same <token>,

As above, it's better to be more explicit and say something like "if
there is no `--trailer <token>=<value>` with the same <token> on the
command line". That's because there could already be such trailers in
the input file, but those don't trigger the command.

Anyway see below as you might need to change the whole paragraph anyway.

> s/If/if/ (downcase).
>
> > +the behavior is as if a special '<token>=<value>' argument were added at
> > +the beginning of the command, <value> will be passed to the user's
> > +command as an empty value.

The problem with this is that people could understand that the command
will run with an empty argument only if there is no trailer with the
same <token>. The current situation though is that the command will
run once with an empty argument whether there are `--trailer
<token>=<value>` options with the same <token> passed on the command
line or not.

> Do the two occurrences of the word "command" in the sentence refer
> to different things?  I do not think this is an existing problem
> inherited from the original, but as we are trying to improve the
> description, I wonder if we can clarify them a bit.
>
>         ... as if a '<token>=<value>' argument were 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.
>
> is my attempt,

It looks better to me.

> but I am not still sure what that "as if" part is
> trying to say.  Does it mean with
>
>         [trailer "Foo"] cmd = foo-cmd
>
> and the 'input-file' does not have "Foo: <some existing value>"
> trailer in it, the command "git interpret-trailers input-file"
> would behave as if this command was run
>
>         $ Foo= git interpret-trailers input-file

I would say it would behave as if:

$ git interpret-trailers --trailer Foo= input-file

> (as there is no <value>, I am not sure what <value> is used when
> <token>=<value> is prefixed to the command)?

Nothing is done when such things are prefixed to the command.

> Puzzled and confused utterly am I...  Help, Christian?

I hope the above helps.

> >  If some '<token>=<value>' arguments are also passed on the command
> >  line, when a 'trailer.<token>.command' is configured, the command will
> >  also be executed for each of these arguments. And the <value> part of
>
> This talks about 'trailer.<token>.command'.  Should this be changed
> to '.cmd'?

I think so.

> Or does everything after "When this option is specified, if there is
> no trailer with ..." apply to both the old .command and new .cmd?
> If so, that was not clear at all---we'd need to clarify this part.

Yeah, in the doc about ".command" I think we should say that it
behaves in a similar way as ".cmd" except for the $ARG vs $1 issue.

> > -these arguments, if any, will be used to replace the `$ARG` string in
> > -the command.
> > +these arguments, if any, will be passed to the command as first parameter.

s/as first parameter/as its first argument/

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-03  5:08             ` ZheNing Hu
@ 2021-04-04  5:34               ` Junio C Hamano
  0 siblings, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-04  5:34 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

>> >  If some '<token>=<value>' arguments are also passed on the command
>> >  line, when a 'trailer.<token>.command' is configured, the command will
>> >  also be executed for each of these arguments. And the <value> part of
>>
>> This talks about 'trailer.<token>.command'.  Should this be changed
>> to '.cmd'?
>>
>> Or does everything after "When this option is specified, if there is
>> no trailer with ..." apply to both the old .command and new .cmd?
>> If so, that was not clear at all---we'd need to clarify this part.
>>
>
> Because ".command" will be eliminated, may be only leave those
> description Information to ".cmd" is better.

Not really.

Until .command goes away, people who want to migrate a config file
written in the .command days to .cmd would need to know what rules
govern .command variant.  Otherwise they would not know what the
original they inherited, using .command, wanted to do, and have no
way to emulate it with the new .cmd approach.  If something is
shared between the two, at least you need to mention that it applies
to both.

Until .command actually gets removed, that is.

>> Unusual indentation here.  But more importantly, I am not sure if
>> having both 'see' and 'help' examples is worth it---they are similar
>> enough that the second one does not teach anything new to those who
>> studied the first one already, aren't they?
>>
>
> Ok, I will think about other examples.

Or just use one, and let somebody else in the future encounter real
world example that is different enough to come up with a follow up
patch to describe that example.

>> > diff --git a/trailer.c b/trailer.c
>> > index be4e9726421c..6aeff6a1bd33 100644
>> > --- a/trailer.c
>> > +++ b/trailer.c
>> > ...
>> > -     strvec_push(&cp.args, cmd.buf);
>> > +     if (conf->cmd) {
>> > +             // cp.shell_no_implicit_args = 1;
>>
>> Do not add new code that is commented out.  Besides we do not use // comment.
>>
>> > +             strbuf_addstr(&cmd, conf->cmd);
>> > +             strvec_push(&cp.args, cmd.buf);
>> > +             if (arg)
>> > +                     strvec_push(&cp.args, arg);
>>
>> Thanks.
>
> Thanks.


^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 20:48           ` Junio C Hamano
  2021-04-03  5:08             ` ZheNing Hu
  2021-04-03  5:51             ` Christian Couder
@ 2021-04-04  5:43             ` ZheNing Hu
  2021-04-04  8:52               ` Christian Couder
  2 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-04  5:43 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Hi, Junio and Christian,

> > +------------
> > +$ cat ~/bin/git-one
> > +#!/bin/sh
> > +git show -s --pretty=reference "$1"
> > +$ git config trailer.see.key "See-also: "
> > +$ git config trailer.see.ifExists "replace"
> > +$ git config trailer.see.ifMissing "doNothing"
> > +$ git config trailer.see.cmd "~/bin/git-one"
> > +$ git interpret-trailers <<EOF
> > +> subject
> > +>
> > +> message
> > +>
> > +> see: HEAD~2
> > +> EOF
> > +subject
> > +
> > +message
> > +
> > +See-also: fe3187e (subject of related commit, 2021-4-2)
> > +------------
> > +
> > +* Configure a 'who' trailer with a cmd use a global script `git-who`
> > +  to find the recent matching "author <mail>" pair in git log and
> > +  show how it works:
> > ++
> > +------------
> > +$ cat ~/bin/git-who
> > + #!/bin/sh
> > +    git log -1 --format="%an <%ae>" --author="$1"
>
> Unusual indentation here.  But more importantly, I am not sure if
> having both 'see' and 'help' examples is worth it---they are similar
> enough that the second one does not teach anything new to those who
> studied the first one already, aren't they?
>

This may be an off-topic question:
I wanted to use `shortlog -s` instead of the document example,
But I found a very strange place:
Here we have two shell scripts:

$ cat ~/bin/gcount
#!/bin/sh
if test "$1" != ""
then
git log -1 --author="$1"
else
echo "hello there"
fi

cat ~/bin/gcount2
#!/bin/sh
if test "$1" != ""
then
git shortlog -1 --author="$1"
else
echo "hello there"
fi

If I use them in the terminal, there is no problem with them,

$ ~/bin/gcount gitster
commit 142430338477d9d1bb25be66267225fb58498d92
(interpret-trailers-command-arg, abc5b)
Author: Junio C Hamano <gitster@pobox.com>
Date:   Mon Mar 22 14:00:00 2021 -0700

    The second batch

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

$ ~/bin/gcount2 gitster
Junio C Hamano (1):
      The second batch

if I use .cmd to run these scripts, the situation is totally different:

$ git config -l | grep trailer
trailer.cnt.ifexists=add
trailer.cnt.key=Cnt:
trailer.cnt.cmd=~/bin/gcount

$ git interpret-trailers --trailer="cnt:gitster" <<EOF
EOF

Cnt: hello there
Cnt: commit 142430338477d9d1bb25be66267225fb58498d92
Author: Junio C Hamano <gitster@pobox.com>
Date:   Mon Mar 22 14:00:00 2021 -0700

    The second batch

    Signed-off-by: Junio C Hamano <gitster@pobox.com>

And if I turn to use gcount2:
$ git config trailer.cnt.cmd "~/bin/gcount2"
$ git interpret-trailers --trailer="cnt:gitster" <<EOF
EOF

Cnt: hello there
Cnt:

It looks like `shortlog` does not write to standard output.
Note that in `short_log.c`, log will be output to `log->file`.
Does it make the above behavior different?
Is there a good solution?


> > +$ git config trailer.help.key "Helped-by: "
> > +$ git config trailer.help.ifExists "replace"
> > +$ git config trailer.help.cmd "~/bin/git-who"
> > +$ git interpret-trailers --trailer="help:gitster@" <<EOF
> > +> subject
> > +>
> > +> message
> > +>
> > +> EOF
> > +subject
> > +
> > +message
> > +
> > +Helped-by: Junio C Hamano <gitster@pobox.com>
> > +------------
> > +
> >  * Configure a 'see' trailer with a command to show the subject of a
> >    commit that is related, and show how it works:
> >  +

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-04  5:43             ` ZheNing Hu
@ 2021-04-04  8:52               ` Christian Couder
  2021-04-04  9:53                 ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-04  8:52 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, Git List

On Sun, Apr 4, 2021 at 7:44 AM ZheNing Hu <adlternative@gmail.com> wrote:

> This may be an off-topic question:
> I wanted to use `shortlog -s` instead of the document example,
> But I found a very strange place:
> Here we have two shell scripts:
>
> $ cat ~/bin/gcount
> #!/bin/sh
> if test "$1" != ""

It's better to use `test -n "$1"` instead of `test "$1" != ""`.

> then
> git log -1 --author="$1"
> else
> echo "hello there"
> fi
>
> cat ~/bin/gcount2
> #!/bin/sh
> if test "$1" != ""
> then
> git shortlog -1 --author="$1"
> else
> echo "hello there"
> fi
>
> If I use them in the terminal, there is no problem with them,
>
> $ ~/bin/gcount gitster
> commit 142430338477d9d1bb25be66267225fb58498d92
> (interpret-trailers-command-arg, abc5b)
> Author: Junio C Hamano <gitster@pobox.com>
> Date:   Mon Mar 22 14:00:00 2021 -0700
>
>     The second batch
>
>     Signed-off-by: Junio C Hamano <gitster@pobox.com>
>
> $ ~/bin/gcount2 gitster
> Junio C Hamano (1):
>       The second batch
>
> if I use .cmd to run these scripts, the situation is totally different:
>
> $ git config -l | grep trailer
> trailer.cnt.ifexists=add
> trailer.cnt.key=Cnt:
> trailer.cnt.cmd=~/bin/gcount

The script/command used in ".cmd" or ".command" should really return a
short string with no newline or line feed char in it. Here your script
can return multiple lines which could mess things up and be difficult
to understand.

> $ git interpret-trailers --trailer="cnt:gitster" <<EOF
> EOF
>
> Cnt: hello there
> Cnt: commit 142430338477d9d1bb25be66267225fb58498d92
> Author: Junio C Hamano <gitster@pobox.com>
> Date:   Mon Mar 22 14:00:00 2021 -0700
>
>     The second batch
>
>     Signed-off-by: Junio C Hamano <gitster@pobox.com>
>
> And if I turn to use gcount2:
> $ git config trailer.cnt.cmd "~/bin/gcount2"
> $ git interpret-trailers --trailer="cnt:gitster" <<EOF
> EOF
>
> Cnt: hello there
> Cnt:
>
> It looks like `shortlog` does not write to standard output.

In shortlog's doc there is:

"If no revisions are passed on the command line and either standard
input is not a terminal or there is no current branch, git shortlog
will output a summary of the log read from standard input, without
reference to the current repository."

I guess that's what's happening here.

> Note that in `short_log.c`, log will be output to `log->file`.
> Does it make the above behavior different?
> Is there a good solution?

I would try to pass a revision on the command line.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-04  8:52               ` Christian Couder
@ 2021-04-04  9:53                 ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-04  9:53 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, Git List

Christian Couder <christian.couder@gmail.com> 于2021年4月4日周日 下午4:52写道:
>
> On Sun, Apr 4, 2021 at 7:44 AM ZheNing Hu <adlternative@gmail.com> wrote:
>
> > This may be an off-topic question:
> > I wanted to use `shortlog -s` instead of the document example,
> > But I found a very strange place:
> > Here we have two shell scripts:
> >
> > $ cat ~/bin/gcount
> > #!/bin/sh
> > if test "$1" != ""
>
> It's better to use `test -n "$1"` instead of `test "$1" != ""`.
>
> > then
> > git log -1 --author="$1"
> > else
> > echo "hello there"
> > fi
> >
> > cat ~/bin/gcount2
> > #!/bin/sh
> > if test "$1" != ""
> > then
> > git shortlog -1 --author="$1"
> > else
> > echo "hello there"
> > fi
> >
> > If I use them in the terminal, there is no problem with them,
> >
> > $ ~/bin/gcount gitster
> > commit 142430338477d9d1bb25be66267225fb58498d92
> > (interpret-trailers-command-arg, abc5b)
> > Author: Junio C Hamano <gitster@pobox.com>
> > Date:   Mon Mar 22 14:00:00 2021 -0700
> >
> >     The second batch
> >
> >     Signed-off-by: Junio C Hamano <gitster@pobox.com>
> >
> > $ ~/bin/gcount2 gitster
> > Junio C Hamano (1):
> >       The second batch
> >
> > if I use .cmd to run these scripts, the situation is totally different:
> >
> > $ git config -l | grep trailer
> > trailer.cnt.ifexists=add
> > trailer.cnt.key=Cnt:
> > trailer.cnt.cmd=~/bin/gcount
>
> The script/command used in ".cmd" or ".command" should really return a
> short string with no newline or line feed char in it. Here your script
> can return multiple lines which could mess things up and be difficult
> to understand.
>

Yes, it's just a small test, I want use `git shortlog -s --author="$1"` to show
commit count of one author.

> > $ git interpret-trailers --trailer="cnt:gitster" <<EOF
> > EOF
> >
> > Cnt: hello there
> > Cnt: commit 142430338477d9d1bb25be66267225fb58498d92
> > Author: Junio C Hamano <gitster@pobox.com>
> > Date:   Mon Mar 22 14:00:00 2021 -0700
> >
> >     The second batch
> >
> >     Signed-off-by: Junio C Hamano <gitster@pobox.com>
> >
> > And if I turn to use gcount2:
> > $ git config trailer.cnt.cmd "~/bin/gcount2"
> > $ git interpret-trailers --trailer="cnt:gitster" <<EOF
> > EOF
> >
> > Cnt: hello there
> > Cnt:
> >
> > It looks like `shortlog` does not write to standard output.
>
> In shortlog's doc there is:
>
> "If no revisions are passed on the command line and either standard
> input is not a terminal or there is no current branch, git shortlog
> will output a summary of the log read from standard input, without
> reference to the current repository."
>
> I guess that's what's happening here.
>

This solved my confusion ;-)

> > Note that in `short_log.c`, log will be output to `log->file`.
> > Does it make the above behavior different?
> > Is there a good solution?
>
> I would try to pass a revision on the command line.

Thanks, Christian.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
  2021-04-02 20:48           ` Junio C Hamano
  2021-04-02 23:44           ` Junio C Hamano
@ 2021-04-04 13:11           ` ZheNing Hu via GitGitGadget
  2021-04-06 16:23             ` Christian Couder
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
  2 siblings, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-04 13:11 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

The `trailer.<token>.command` configuration variable
specifies a command (run via the shell, so it does not have
to be a single name of 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.  This has two 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 unmatching 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).

Introduce a new `trailer.<token>.cmd` configuration that
takes higher precedence to deprecate and eventually remove
`trailer.<token>.command`, which passes the value as a
parameter to the command.  Instead of "$ARG", the users will
refer to the value as positional argument, $1, in their
scripts.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
    [GSOC] trailer: add new trailer..cmd config option
    
    In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
    Christian talked about the problem of using strbuf_replace() to replace
    $ARG:
    
     1. if user's script have more than one $ARG, only the first one will be
        replaced, which is incorrected.
     2. $ARG is textually replaced without shell syntax, which may result a
        broken command when $ARG include some unmatching single quote, very
        unsafe.
    
    Now pass trailer value as $1 to the trailer command with another
    trailer.<token>.cmd config, to solve these above two problems,
    
    We are now writing documents that are more readable and correct than
    before.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v6:

 1:  3aed77d077b9 ! 1:  1e9a6572ac6f [GSOC] trailer: add new trailer.<token>.cmd config option
     @@ Commit message
          pair), which a user would expect to stay intact, would be
          replaced, and worse, if the value had an unmatching single
          quote (imagine a name like "O'Connor", substituted into
     -    NAME='$ARG' to make it NAME='O'Connor), it would result in
     +    NAME='$ARG' to make it NAME='O'Connor'), it would result in
          a broken command that is not syntactically correct (or
          worse).
      
     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::
      -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.
     ++same token. This option behaves in a similar way as ".cmd", however, it
     ++passes the value through $ARG.
       +
      -If the command contains the `$ARG` string, this string will be
      -replaced with the <value> part of an existing trailer with the same
     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::
      +
      +trailer.<token>.cmd::
      +	The command specified by this configuration variable is run
     -+	with a single parameter, which is the <value> part of an
     -+	existing trailer with the same <token>.  The output from the
     -+	command is then used as the value for the <token> in the
     -+	resulting trailer.
     ++	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, If there is no trailer with same <token>,
     -+the behavior is as if a special '<token>=<value>' argument were added at
     -+the beginning of the command, <value> will be passed to the user's
     -+command as an empty value.
     ++When this option is specified, the behavior is as if a '<token>=<value>'
     ++argument were 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 will
     - also be executed for each of these arguments. And the <value> part of
     +-line, when a 'trailer.<token>.command' is configured, the command will
     +-also be executed for each of these arguments. And the <value> part of
      -these arguments, if any, will be used to replace the `$ARG` string in
      -the command.
     -+these arguments, if any, will be passed to the command as first parameter.
     ++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
       --------
     @@ Documentation/git-interpret-trailers.txt: subject
       Fix #42
       ------------
       
     -+* Configure a 'see' trailer with a cmd use a global script `git-one`
     -+  to show the subject of a commit that is related, and show how it works:
     ++* 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/git-one
     ++$ cat ~/bin/gcount
      +#!/bin/sh
     -+git show -s --pretty=reference "$1"
     -+$ git config trailer.see.key "See-also: "
     -+$ git config trailer.see.ifExists "replace"
     -+$ git config trailer.see.ifMissing "doNothing"
     -+$ git config trailer.see.cmd "~/bin/git-one"
     -+$ git interpret-trailers <<EOF
     ++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
      +> 
     -+> see: HEAD~2
      +> EOF
      +subject
      +
      +message
      +
     -+See-also: fe3187e (subject of related commit, 2021-4-2)
     ++Commit-count: 22484     Junio C Hamano
      +------------
      +
     -+* Configure a 'who' trailer with a cmd use a global script `git-who`
     -+  to find the recent matching "author <mail>" pair in git log and
     -+  show how it works:
     ++* 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/git-who
     -+ #!/bin/sh
     -+    git log -1 --format="%an <%ae>" --author="$1"
     -+$ git config trailer.help.key "Helped-by: "
     -+$ git config trailer.help.ifExists "replace"
     -+$ git config trailer.help.cmd "~/bin/git-who"
     -+$ git interpret-trailers --trailer="help:gitster@" <<EOF
     ++$ 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
     @@ Documentation/git-interpret-trailers.txt: subject
      +
      +message
      +
     -+Helped-by: Junio C Hamano <gitster@pobox.com>
     ++Reference-to: 8bc9a0c769 (Add copyright notices., 2005-04-07)
      +------------
      +
       * Configure a 'see' trailer with a command to show the subject of a
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
       '
       
      +test_expect_success 'with cmd' '
     -+	test_when_finished "git config --unset trailer.bug.key && \
     -+			    git config --unset trailer.bug.ifExists && \
     -+			    git config --unset trailer.bug.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\"" &&
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +'
      +
      +test_expect_success 'with cmd and $1' '
     -+	test_when_finished "git config --unset trailer.bug.key && \
     -+			    git config --unset trailer.bug.ifExists && \
     -+			    git config --unset trailer.bug.cmd" &&
     ++	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" &&
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +'
      +
      +test_expect_success 'with cmd and $1 with sh -c' '
     -+	test_when_finished "git config --unset trailer.bug.key && \
     -+			    git config --unset trailer.bug.ifExists && \
     -+			    git config --unset trailer.bug.cmd" &&
     ++	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\"\"" &&
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +'
      +
      +test_expect_success 'with cmd and $1 with shell script' '
     -+	test_when_finished "git config --unset trailer.bug.key && \
     -+			    git config --unset trailer.bug.ifExists && \
     -+			    git config --unset trailer.bug.cmd" &&
     ++	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" &&
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
      -
      -	strvec_push(&cp.args, cmd.buf);
      +	if (conf->cmd) {
     -+		// cp.shell_no_implicit_args = 1;
      +		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);
     -+		strvec_push(&cp.args, cmd.buf);
      +		if (arg)
      +			strbuf_replace(&cmd, TRAILER_ARG_STRING, arg);
     ++		strvec_push(&cp.args, cmd.buf);
      +	}
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;


 Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-
 trailer.c                                | 37 +++++++---
 3 files changed, 186 insertions(+), 24 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..83600e93390d 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -236,21 +236,36 @@ 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
-'<token>=<value>' argument were added at the beginning of the command
-line, 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. This option behaves in a similar way as ".cmd", however, it
+passes the value through $ARG.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+".command" has been deprecated due to the $ARG in the user's command can
+only be replaced once and the original way of replacing $ARG was not safe.
+Now the preferred option is using "trailer.<token>.cmd", which use position
+argument to pass the value.
++
+When both .cmd and .command are given for the same <token>,
+.cmd is used and .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 '<token>=<value>'
+argument were 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 will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-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 +348,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:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..6d26a2606604 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -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,9 +1337,31 @@ 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" &&
+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
+		--abbrev-commit --abbrev=14 \$ARG" &&
 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
 	cat complex_message_body >expected &&
 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..bd384befe15b 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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(""),

base-commit: 142430338477d9d1bb25be66267225fb58498d92
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-03  5:51             ` Christian Couder
@ 2021-04-04 23:26               ` Junio C Hamano
  2021-04-06  3:47                 ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-04 23:26 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

Christian Couder <christian.couder@gmail.com> writes:

> On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:
>> ...
>>         ... as if a '<token>=<value>' argument were 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.
>>
>> is my attempt,
>
> It looks better to me.
>
>> but I am not still sure what that "as if" part is
>> trying to say.  Does it mean with
>>
>>         [trailer "Foo"] cmd = foo-cmd
>>
>> and the 'input-file' does not have "Foo: <some existing value>"
>> trailer in it, the command "git interpret-trailers input-file"
>> would behave as if this command was run
>>
>>         $ Foo= git interpret-trailers input-file
>
> I would say it would behave as if:
>
> $ git interpret-trailers --trailer Foo= input-file

Hmmm.  That means that the descrition in the original is quite
misleading, no?  If it said

	... as if "--trailer" "<token>=<value>" arguments were given
	to "git interpret-trailers" command near the beginning of
	its command line

then that may be closer description of the command line you are
forming, but as its written (with or without my attempt to clarify
above), it was impossible to infer that you are behaving as if
another --trailer option (with <token>=<value> as its value) was
given.



^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-04 23:26               ` Junio C Hamano
@ 2021-04-06  3:47                 ` Christian Couder
  2021-04-06  3:52                   ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-06  3:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:
>
> Christian Couder <christian.couder@gmail.com> writes:
>
> > On Fri, Apr 2, 2021 at 10:49 PM Junio C Hamano <gitster@pobox.com> wrote:
> >> ...
> >>         ... as if a '<token>=<value>' argument were 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.
> >>
> >> is my attempt,
> >
> > It looks better to me.
> >
> >> but I am not still sure what that "as if" part is
> >> trying to say.  Does it mean with
> >>
> >>         [trailer "Foo"] cmd = foo-cmd
> >>
> >> and the 'input-file' does not have "Foo: <some existing value>"
> >> trailer in it, the command "git interpret-trailers input-file"
> >> would behave as if this command was run
> >>
> >>         $ Foo= git interpret-trailers input-file
> >
> > I would say it would behave as if:
> >
> > $ git interpret-trailers --trailer Foo= input-file
>
> Hmmm.  That means that the descrition in the original is quite
> misleading, no?

Yeah, I agree it is misleading and difficult to understand.

> If it said
>
>         ... as if "--trailer" "<token>=<value>" arguments were given
>         to "git interpret-trailers" command near the beginning of
>         its command line
>
> then that may be closer description of the command line you are
> forming, but as its written (with or without my attempt to clarify
> above), it was impossible to infer that you are behaving as if
> another --trailer option (with <token>=<value> as its value) was
> given.

I agree.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-06  3:47                 ` Christian Couder
@ 2021-04-06  3:52                   ` Christian Couder
  2021-04-06  5:16                     ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-06  3:52 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

On Tue, Apr 6, 2021 at 5:47 AM Christian Couder
<christian.couder@gmail.com> wrote:
>
> On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Christian Couder <christian.couder@gmail.com> writes:

> > > I would say it would behave as if:
> > >
> > > $ git interpret-trailers --trailer Foo= input-file
> >
> > Hmmm.  That means that the descrition in the original is quite
> > misleading, no?
>
> Yeah, I agree it is misleading and difficult to understand.
>
> > If it said
> >
> >         ... as if "--trailer" "<token>=<value>" arguments were given
> >         to "git interpret-trailers" command near the beginning of
> >         its command line
> >
> > then that may be closer description of the command line you are
> > forming, but as its written (with or without my attempt to clarify
> > above), it was impossible to infer that you are behaving as if
> > another --trailer option (with <token>=<value> as its value) was
> > given.
>
> I agree.

By the way it might be better to first have a patch that clarifies the
existing documentation of ".command", before the patch that adds
".cmd". This way the first patch that only clarifies the documentation
could be backported to maintenance branches of old releases.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-06  3:52                   ` Christian Couder
@ 2021-04-06  5:16                     ` ZheNing Hu
  2021-04-06  5:34                       ` Junio C Hamano
  2021-04-06  5:37                       ` Junio C Hamano
  0 siblings, 2 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-06  5:16 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

Hi, Junio and Christian,

Christian Couder <christian.couder@gmail.com> 于2021年4月6日周二 上午11:52写道:
>
> On Tue, Apr 6, 2021 at 5:47 AM Christian Couder
> <christian.couder@gmail.com> wrote:
> >
> > On Mon, Apr 5, 2021 at 1:26 AM Junio C Hamano <gitster@pobox.com> wrote:
> > >
> > > Christian Couder <christian.couder@gmail.com> writes:
>
> > > > I would say it would behave as if:
> > > >
> > > > $ git interpret-trailers --trailer Foo= input-file
> > >
> > > Hmmm.  That means that the descrition in the original is quite
> > > misleading, no?
> >
> > Yeah, I agree it is misleading and difficult to understand.
> >
> > > If it said
> > >
> > >         ... as if "--trailer" "<token>=<value>" arguments were given
> > >         to "git interpret-trailers" command near the beginning of
> > >         its command line
> > >
> > > then that may be closer description of the command line you are
> > > forming, but as its written (with or without my attempt to clarify
> > > above), it was impossible to infer that you are behaving as if
> > > another --trailer option (with <token>=<value> as its value) was
> > > given.
> >
> > I agree.
>
> By the way it might be better to first have a patch that clarifies the
> existing documentation of ".command", before the patch that adds
> ".cmd". This way the first patch that only clarifies the documentation
> could be backported to maintenance branches of old releases.

This first patch should explain errors of ".command", and then explain that
we will no longer use it, right?

I find in the status update description:
"Seems to break new tests for .command variant in zh/commit-trailer"
I have tryed to merge my local branch 1e9a657(trailer-cmd) and
2daae3d(upstream/zh/commit-trailer)

I can't find any tests errors after merging, is there something unexpected
happened?

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-06  5:16                     ` ZheNing Hu
@ 2021-04-06  5:34                       ` Junio C Hamano
  2021-04-06  5:37                       ` Junio C Hamano
  1 sibling, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-06  5:34 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

> I find in the status update description:
> "Seems to break new tests for .command variant in zh/commit-trailer"
> I have tryed to merge my local branch 1e9a657(trailer-cmd) and
> 2daae3d(upstream/zh/commit-trailer)

Thanks for noticing---the description is stale.  Remember that after
I pointed out the breakage, you responded that the conversion for
the .command codepath was wrong?  What is queued in my tree has a
version with fix of that bug, so the comment does not apply.




^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-06  5:16                     ` ZheNing Hu
  2021-04-06  5:34                       ` Junio C Hamano
@ 2021-04-06  5:37                       ` Junio C Hamano
  1 sibling, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-06  5:37 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

>> By the way it might be better to first have a patch that clarifies the
>> existing documentation of ".command", before the patch that adds
>> ".cmd". This way the first patch that only clarifies the documentation
>> could be backported to maintenance branches of old releases.
>
> This first patch should explain errors of ".command", and then explain that
> we will no longer use it, right?

If I am reading what Christian wrote correctly, I think it is not
"errors of .command" but to clarify the misleading description
(especially around the "as if <token>=<value> were added at the
beginning").  As if nothing is planned for its deprecation.



^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-04 13:11           ` [PATCH v7] " ZheNing Hu via GitGitGadget
@ 2021-04-06 16:23             ` Christian Couder
  2021-04-07  4:51               ` ZheNing Hu
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
  1 sibling, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-06 16:23 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Sun, Apr 4, 2021 at 3:11 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: ZheNing Hu <adlternative@gmail.com>
>
> The `trailer.<token>.command` configuration variable
> specifies a command (run via the shell, so it does not have
> to be a single name of 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.  This has two 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 unmatching single

s/unmatching/unmatched/

> 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).

Good explanation of the issues with ".command"!

> Introduce a new `trailer.<token>.cmd` configuration that
> takes higher precedence to deprecate and eventually remove
> `trailer.<token>.command`, which passes the value as a
> parameter to the command.  Instead of "$ARG", the users will

s/a parameter/an argument/
s/the users will/users can/

> refer to the value as positional argument, $1, in their
> scripts.
>
> Helped-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Christian Couder <christian.couder@gmail.com>
> Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> ---
>     [GSOC] trailer: add new trailer..cmd config option

Maybe <token> has been removed from "trailer.<token>.cmd" above
because it has been interpreted as an HTML or XML tag?

>     In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
>     Christian talked about the problem of using strbuf_replace() to replace
>     $ARG:
>
>      1. if user's script have more than one $ARG, only the first one will be

s/user's script have/the user's script has/

>         replaced, which is incorrected.
>      2. $ARG is textually replaced without shell syntax, which may result a
>         broken command when $ARG include some unmatching single quote, very
>         unsafe.

Yeah, good summary of the issues with ".command"

>     Now pass trailer value as $1 to the trailer command with another
>     trailer.<token>.cmd config, to solve these above two problems,
>
>     We are now writing documents that are more readable and correct than
>     before.

Yeah, correcting the doc is a good thing to do. By the way, as I said
to Junio, it might be better to make the doc for ".command" more
readable and correct in a first patch separate from the patch
introducing ".cmd".

If you really want to do both in the same patch you should tell that
in the commit message too, not just here after the "---" line.

>  Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----
>  t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-
>  trailer.c                                | 37 +++++++---
>  3 files changed, 186 insertions(+), 24 deletions(-)
>
> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 96ec6499f001..83600e93390d 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -236,21 +236,36 @@ 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
> -'<token>=<value>' argument were added at the beginning of the command
> -line, 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. This option behaves in a similar way as ".cmd", however, it
> +passes the value through $ARG.

Maybe this last sentence could be replaced with "Otherwise this option
behaves in the same way as 'trailer.<token>.cmd'."

> -If the command contains the `$ARG` string, this string will be
> -replaced with the <value> part of an existing trailer with the same
> -<token>, if any, before the command is launched.
> +".command" has been deprecated due to the $ARG in the user's command can

s/".command"/The 'trailer.<token>.command' option/
s/to the $ARG/to the fact that `$ARG`/

> +only be replaced once and the original way of replacing $ARG was not safe.

s/only be/can only be/
s/and the/and that the/

> +Now the preferred option is using "trailer.<token>.cmd", which use position

s/using//
s/use/uses/
s/position/a positional/

Also please make sure that trailer.<token>.cmd and
trailer.<token>.command are always quoted in the same way. I think
single quotes are used in the current doc, so please keep using single
quotes.

> +argument to pass the value.
> ++
> +When both .cmd and .command are given for the same <token>,
> +.cmd is used and .command is ignored.

Please spell and quote ".cmd" and ".command" consistently, so for
example like: 'trailer.<token>.cmd'

> +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 '<token>=<value>'

s/'<token>=<value>'/`--trailer <token>=<value>`/  (let's try to be as
explicit as possible)

> +argument were added at the beginning of the "git interpret-trailers"

s/were/was/

> +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 will
> -also be executed for each of these arguments. And the <value> part of
> -these arguments, if any, will be used to replace the `$ARG` string in
> -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.

Yeah, it's much better than it was, but I think we can do better. I
will try to come up with something soon.

Also as I said above and in reply to Junio, I think it might be better
to split this in 2 patches.

>  EXAMPLES
>  --------
> @@ -333,6 +348,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)

The added examples look good!

> +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
> +'

I guess this shows that the command is called multiple times, the
first time with an empty first arg.

> +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
> +'

I guess this shows that the argument is also available as "$1".

> +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
> +'

Ok, this shows how `sh -c ...` can be used in ".cmd".

> +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
> +'

Ok.

>  test_expect_success 'without config' '
>         sed -e "s/ Z\$/ /" >expected <<-\EOF &&
>
> @@ -1274,9 +1337,31 @@ 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
> +'

Ok.

>  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" &&
> +       git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
> +               --abbrev-commit --abbrev=14 \$ARG" &&

This is just an indent change. I am not sure it's worth doing in this
patch. If you think that the file needs better indentation though,
then you might do it in a separate preparatory patch at the beginning
of the current patch series.

If there is only this place in the file where such an indentation
improvement is needed, this might be ok, but please mention in the
commit message that while at it you are also doing this small change.

The other parts of the patch look good to me.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v7] [GSOC] trailer: add new trailer.<token>.cmd config option
  2021-04-06 16:23             ` Christian Couder
@ 2021-04-07  4:51               ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-07  4:51 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano

Christian Couder <christian.couder@gmail.com> 于2021年4月7日周三 上午12:23写道:
>
> On Sun, Apr 4, 2021 at 3:11 PM ZheNing Hu via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > The `trailer.<token>.command` configuration variable
> > specifies a command (run via the shell, so it does not have
> > to be a single name of 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.  This has two 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 unmatching single
>
> s/unmatching/unmatched/
>
> > 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).
>
> Good explanation of the issues with ".command"!
>

Credit for Junio.

> > Introduce a new `trailer.<token>.cmd` configuration that
> > takes higher precedence to deprecate and eventually remove
> > `trailer.<token>.command`, which passes the value as a
> > parameter to the command.  Instead of "$ARG", the users will
>
> s/a parameter/an argument/
> s/the users will/users can/
>
> > refer to the value as positional argument, $1, in their
> > scripts.
> >
> > Helped-by: Junio C Hamano <gitster@pobox.com>
> > Helped-by: Christian Couder <christian.couder@gmail.com>
> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> > ---
> >     [GSOC] trailer: add new trailer..cmd config option
>
> Maybe <token> has been removed from "trailer.<token>.cmd" above
> because it has been interpreted as an HTML or XML tag?
>

Aha, It may well be so.
Maybe change to "[GSOC] trailer add .cmd config option"?

> >     In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> >     Christian talked about the problem of using strbuf_replace() to replace
> >     $ARG:
> >
> >      1. if user's script have more than one $ARG, only the first one will be
>
> s/user's script have/the user's script has/
>
> >         replaced, which is incorrected.
> >      2. $ARG is textually replaced without shell syntax, which may result a
> >         broken command when $ARG include some unmatching single quote, very
> >         unsafe.
>
> Yeah, good summary of the issues with ".command"
>
> >     Now pass trailer value as $1 to the trailer command with another
> >     trailer.<token>.cmd config, to solve these above two problems,
> >
> >     We are now writing documents that are more readable and correct than
> >     before.
>
> Yeah, correcting the doc is a good thing to do. By the way, as I said
> to Junio, it might be better to make the doc for ".command" more
> readable and correct in a first patch separate from the patch
> introducing ".cmd".
>
> If you really want to do both in the same patch you should tell that
> in the commit message too, not just here after the "---" line.
>

I actually tried to write it yesterday, but...

diff --git a/Documentation/git-interpret-trailers.txt
b/Documentation/git-interpret-trailers.txt
index 96ec6499f0..39f742b3dc 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -237,20 +237,20 @@ trailer.<token>.command::
        specified <token>.
 +
 When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+'<token>=<value>' argument were 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.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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.
 +
 If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-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
+used to replace the fist $ARG string in the command.

Since I am based on the document in the second patch, you have also
uncovered some points worthy of modification below,
so temporarily what I wrote is not accurate enough.

> >  Documentation/git-interpret-trailers.txt | 86 +++++++++++++++++++----
> >  t/t7513-interpret-trailers.sh            | 87 +++++++++++++++++++++++-
> >  trailer.c                                | 37 +++++++---
> >  3 files changed, 186 insertions(+), 24 deletions(-)
> >
> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> > index 96ec6499f001..83600e93390d 100644
> > --- a/Documentation/git-interpret-trailers.txt
> > +++ b/Documentation/git-interpret-trailers.txt
> > @@ -236,21 +236,36 @@ 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
> > -'<token>=<value>' argument were added at the beginning of the command
> > -line, 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. This option behaves in a similar way as ".cmd", however, it
> > +passes the value through $ARG.
>
> Maybe this last sentence could be replaced with "Otherwise this option
> behaves in the same way as 'trailer.<token>.cmd'."
>
> > -If the command contains the `$ARG` string, this string will be
> > -replaced with the <value> part of an existing trailer with the same
> > -<token>, if any, before the command is launched.
> > +".command" has been deprecated due to the $ARG in the user's command can
>
> s/".command"/The 'trailer.<token>.command' option/
> s/to the $ARG/to the fact that `$ARG`/
>
> > +only be replaced once and the original way of replacing $ARG was not safe.
>
> s/only be/can only be/
> s/and the/and that the/
>
> > +Now the preferred option is using "trailer.<token>.cmd", which use position
>
> s/using//
> s/use/uses/
> s/position/a positional/
>
> Also please make sure that trailer.<token>.cmd and
> trailer.<token>.command are always quoted in the same way. I think
> single quotes are used in the current doc, so please keep using single
> quotes.
>
> > +argument to pass the value.
> > ++
> > +When both .cmd and .command are given for the same <token>,
> > +.cmd is used and .command is ignored.
>
> Please spell and quote ".cmd" and ".command" consistently, so for
> example like: 'trailer.<token>.cmd'
>

OK.

> > +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 '<token>=<value>'
>
> s/'<token>=<value>'/`--trailer <token>=<value>`/  (let's try to be as
> explicit as possible)
>
> > +argument were added at the beginning of the "git interpret-trailers"
>
> s/were/was/
>
> > +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 will
> > -also be executed for each of these arguments. And the <value> part of
> > -these arguments, if any, will be used to replace the `$ARG` string in
> > -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.
>
> Yeah, it's much better than it was, but I think we can do better. I
> will try to come up with something soon.
>
> Also as I said above and in reply to Junio, I think it might be better
> to split this in 2 patches.
>
> >  EXAMPLES
> >  --------
> > @@ -333,6 +348,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)
>
> The added examples look good!
>

Thanks.

> > +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
> > +'
>
> I guess this shows that the command is called multiple times, the
> first time with an empty first arg.
>
> > +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
> > +'
>
> I guess this shows that the argument is also available as "$1".
>
> > +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
> > +'
>
> Ok, this shows how `sh -c ...` can be used in ".cmd".
>
> > +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
> > +'
>
> Ok.
>
> >  test_expect_success 'without config' '
> >         sed -e "s/ Z\$/ /" >expected <<-\EOF &&
> >
> > @@ -1274,9 +1337,31 @@ 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
> > +'
>
> Ok.
>
> >  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" &&
> > +       git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
> > +               --abbrev-commit --abbrev=14 \$ARG" &&
>
> This is just an indent change. I am not sure it's worth doing in this
> patch. If you think that the file needs better indentation though,
> then you might do it in a separate preparatory patch at the beginning
> of the current patch series.
>
> If there is only this place in the file where such an indentation
> improvement is needed, this might be ok, but please mention in the
> commit message that while at it you are also doing this small change.
>

Well, I may often think that these small changes are irrelevant.
Since you said that, I will cancel the modification of this place.

> The other parts of the patch look good to me.

Thanks, Christian.
--
ZheNing Hu

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v8 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-04 13:11           ` [PATCH v7] " ZheNing Hu via GitGitGadget
  2021-04-06 16:23             ` Christian Couder
@ 2021-04-09 13:37             ` ZheNing Hu via GitGitGadget
  2021-04-09 13:37               ` [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
                                 ` (3 more replies)
  1 sibling, 4 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-09 13:37 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG:

 1. if the user's script has more than one $ARG, only the first one will be
    replaced, which is incorrected.
 2. $ARG is textually replaced without shell syntax, which may result a
    broken command when $ARG include some unmatching single quote, very
    unsafe.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config, to solve these above two problems,

We are now writing documents that are more readable and correct than before.

ZheNing Hu (2):
  [GSOC] docs: correct descript of trailer.<token>.command
  [GSOC] trailer: add new .cmd config option

 Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++
 trailer.c                                | 37 +++++++---
 3 files changed, 187 insertions(+), 24 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v8
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v7:

 -:  ------------ > 1:  505903811df8 [GSOC] docs: correct descript of trailer.<token>.command
 1:  1e9a6572ac6f ! 2:  3dc8983a4702 [GSOC] trailer: add new trailer.<token>.cmd config option
     @@ Metadata
      Author: ZheNing Hu <adlternative@gmail.com>
      
       ## Commit message ##
     -    [GSOC] trailer: add new trailer.<token>.cmd config option
     +    [GSOC] trailer: add new .cmd config option
      
          The `trailer.<token>.command` configuration variable
          specifies a command (run via the shell, so it does not have
     @@ Commit message
          * 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 unmatching single
     +    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
     @@ Commit message
      
          Introduce a new `trailer.<token>.cmd` configuration that
          takes higher precedence to deprecate and eventually remove
     -    `trailer.<token>.command`, which passes the value as a
     -    parameter to the command.  Instead of "$ARG", the users will
     +    `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.
      
     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::
       	specified <token>.
       +
      -When this option is specified, the behavior is as if a special
     --'<token>=<value>' argument were added at the beginning of the command
     --line, where <value> is taken to be the standard output of the
     --specified command with any leading and trailing whitespace trimmed
     --off.
     +-'--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. This option behaves in a similar way as ".cmd", however, it
     -+passes the value through $ARG.
     - +
     --If the command contains the `$ARG` string, this string will be
     --replaced with the <value> part of an existing trailer with the same
     --<token>, if any, before the command is launched.
     -+".command" has been deprecated due to the $ARG in the user's command can
     -+only be replaced once and the original way of replacing $ARG was not safe.
     -+Now the preferred option is using "trailer.<token>.cmd", which use position
     -+argument to pass the value.
     ++same token. It passes the value through `$ARG`, otherwise this option behaves
     ++in the same way as 'trailer.<token>.cmd'.
      ++
     -+When both .cmd and .command are given for the same <token>,
     -+.cmd is used and .command is ignored.
     ++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
     @@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::
      +	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 '<token>=<value>'
     -+argument were 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.
     ++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 will
     --also be executed for each of these arguments. And the <value> part of
     --these arguments, if any, will be used to replace the `$ARG` string in
     --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.
     +-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
       --------
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
      +	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" &&
     ++	--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) &&
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup a commit' '
      +
       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" &&
     -+	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" \
     -+		--abbrev-commit --abbrev=14 \$ARG" &&
     - 	FIXED=$(git log -1 --oneline --format="%h (%s)" --abbrev-commit --abbrev=14 HEAD) &&
     - 	cat complex_message_body >expected &&
     - 	sed -e "s/ Z\$/ /" >>expected <<-EOF &&
     + 	git config trailer.fix.command "git log -1 --oneline --format=\"%h (%s)\" --abbrev-commit --abbrev=14 \$ARG" &&
      
       ## trailer.c ##
      @@ trailer.c: struct conf_info {

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
@ 2021-04-09 13:37               ` ZheNing Hu via GitGitGadget
  2021-04-09 19:02                 ` Christian Couder
  2021-04-09 13:37               ` [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
                                 ` (2 subsequent siblings)
  3 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-09 13:37 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original documentation of `trailer.<token>.command`,
some descriptions are easily misunderstood. So let's modify
it to increase its readability.

In addition, clarify that `$ARG` in command can only be
replaced once since `$ARG` is text replacement.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------
 1 file changed, 11 insertions(+), 11 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..3e5aa3a65ae9 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -237,20 +237,20 @@ trailer.<token>.command::
 	specified <token>.
 +
 When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+'--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.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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.
 +
 If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-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.
 
 EXAMPLES
 --------
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
  2021-04-09 13:37               ` [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-09 13:37               ` ZheNing Hu via GitGitGadget
  2021-04-09 20:18                 ` Christian Couder
  2021-04-09 19:59               ` [PATCH v8 0/2] " Christian Couder
  2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
  3 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-09 13:37 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

The `trailer.<token>.command` configuration variable
specifies a command (run via the shell, so it does not have
to be a single name of 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.  This has two 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).

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.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++
 trailer.c                                | 37 +++++++---
 3 files changed, 187 insertions(+), 24 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 3e5aa3a65ae9..1a874a93f49b 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -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:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..68353af3e62e 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -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" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..bd384befe15b 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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(""),
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-09 13:37               ` [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-09 19:02                 ` Christian Couder
  2021-04-10 13:40                   ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-09 19:02 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: ZheNing Hu <adlternative@gmail.com>
>
> In the original documentation of `trailer.<token>.command`,
> some descriptions are easily misunderstood. So let's modify
> it to increase its readability.
>
> In addition, clarify that `$ARG` in command can only be
> replaced once since `$ARG` is text replacement.

I think you can remove the "since `$ARG` is text replacement" part.
Otherwise this looks fine.

> Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> ---
>  Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------
>  1 file changed, 11 insertions(+), 11 deletions(-)
>
> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 96ec6499f001..3e5aa3a65ae9 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -237,20 +237,20 @@ trailer.<token>.command::
>         specified <token>.

The beginning of the doc for trailer.<token>.command could already be
improved. It is:

       This option can be used to specify a shell command that will
       be called to automatically add or modify a trailer with the
       specified <token>.

Instead we could say for example:

       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

>  When this option is specified, the behavior is as if a special

I would rather say:

When the specified command is first called to add a trailer with the
specified <token>, the behavior is as if a special

> -'<token>=<value>' argument were added at the beginning of the command
> -line, where <value> is taken to be the standard output of the
> -specified command with any leading and trailing whitespace trimmed
> -off.
> +'--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

Here we can remove "specified" as we now use it at the beginning of
the sentence.

> +whitespace trimmed off.
>  +
> -If the command contains the `$ARG` string, this string will be
> -replaced with the <value> part of an existing trailer with the same
> -<token>, if any, before the command is launched.
> +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.

I think we should not talk about `$ARG` at this point, let's remove
this and see what we can do below in the next paragraph or after it.

>  If some '<token>=<value>' arguments are also passed on the command

s/'<token>=<value>'/'--trailer <token>=<value>'/

> -line, when a 'trailer.<token>.command' is configured, the command will
> -also be executed for each of these arguments. And the <value> part of
> -these arguments, if any, will be used to replace the `$ARG` string in
> -the command.
> +line, when a 'trailer.<token>.command' is configured, the command is run

s/when a 'trailer.<token>.command' is configured, //
s/the command is run/the command is called again/

> +once for each these arguments with the same <token>.

s/each these/each of these/

> And the <value>
> +part of these arguments, if any, will be used to replace the first `$ARG`
> +string in the command.

s/first `$ARG` string/first occurrence of substring `$ARG`/

Let's also add something to explain the purpose of this, for example
we could add:

"This way the command can produce a <value> computed from the <value>
passed in the '--trailer <token>=<value>' argument."

And then let's also explain what happens when the command is called
the first time, with for example:

"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>."

To sum up this would give the following (not properly formatted) description:

-------------------
trailer.<token>.command::
       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

       When the specified command is first called to add a trailer
with the specified <token>, 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 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 used
to replace the first occurrence
       of substring `$ARG` in the command. This way the command can
produce a <value>
       computed from the <value> passed 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>.
-------------------

Thanks!

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v8 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
  2021-04-09 13:37               ` [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
  2021-04-09 13:37               ` [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-09 19:59               ` Christian Couder
  2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
  3 siblings, 0 replies; 101+ messages in thread
From: Christian Couder @ 2021-04-09 19:59 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> Christian talked about the problem of using strbuf_replace() to replace
> $ARG:
>
>  1. if the user's script has more than one $ARG, only the first one will be
>     replaced, which is incorrected.

Maybe: s/is incorrected/can be puzzling/

>  2. $ARG is textually replaced without shell syntax, which may result a

Not sure what "without shell syntax" means here.

Also: s/may result a/may result in a/

>     broken command when $ARG include some unmatching single quote, very

s/when/if/
s/include/includes/
s/some unmatching/an unmatched/

>     unsafe.
>
> Now pass trailer value as $1 to the trailer command with another
> trailer.<token>.cmd config, to solve these above two problems,

I think the important thing here is to explain that we want to
introduce a new 'trailer.<token>.cmd' config option, so that we can
start deprecating 'trailer.<token>.command' when people have started
using the new 'trailer.<token>.cmd' config option. Maybe something
like:

"To address these issues, let's introduce a new 'trailer.<token>.cmd'
config option that behaves in the same way as
'trailer.<token>.command' except that it passes the trailer value as
$1 to the configured command instead of textually replacing the first
occurence of '$ARG' in it. This will let us slowly deprecate
'trailer.<token>.command' in favor of 'trailer.<token>.cmd' in the
future."

> We are now writing documents that are more readable and correct than before.

I would suggest removing this sentence as I don't think it adds much
to the above.

> ZheNing Hu (2):
>   [GSOC] docs: correct descript of trailer.<token>.command

By the way the following title might be a bit better and shorter:

"[GSOC] docs: improve 'trailer.<token>.command' doc"

>   [GSOC] trailer: add new .cmd config option

Maybe we can afford: s/.cmd/'trailer.<token>.cmd'/

>      -    [GSOC] trailer: add new trailer.<token>.cmd config option
>      +    [GSOC] trailer: add new .cmd config option

Was the previous title too long? Or is there an issue with
GitGitGadget because it contains <token>?

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-09 13:37               ` [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-09 20:18                 ` Christian Couder
  2021-04-10 14:09                   ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-09 20:18 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Junio C Hamano, ZheNing Hu

On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget
<gitgitgadget@gmail.com> wrote:
>
> From: ZheNing Hu <adlternative@gmail.com>
>
> The `trailer.<token>.command` configuration variable
> specifies a command (run via the shell, so it does not have
> to be a single name of or path to the command, but can be a

s/of or/or/

> shell script), and the first occurrence of substring $ARG is
> replaced with the value given to the `interpret-trailer`
> command for the token.  This has two downsides:

Maybe: s/for the token/for the token in a '--trailer <token>=<value>' argument/

> * 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).
>
> 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.

Good!

> Helped-by: Junio C Hamano <gitster@pobox.com>
> Helped-by: Christian Couder <christian.couder@gmail.com>
> Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> ---
>  Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----
>  t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++
>  trailer.c                                | 37 +++++++---
>  3 files changed, 187 insertions(+), 24 deletions(-)
>
> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 3e5aa3a65ae9..1a874a93f49b 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -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'.

Actually I think that we should say first that this behaves in the
same way as the 'trailer.<token>.cmd'.

And this should also replace the first paragraph in the description of
'trailer.<token>.command', not just the second one.

Maybe:

"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 due to the fact

s/deprecated/deprecated in favor of 'trailer.<token>.cmd'/

> +that $ARG in the user's command can only be replaced once and that the

s/can only be/is only/

> +original way of replacing $ARG was not safe.

s/was/is/

> Now the preferred option is 'trailer.<token>.cmd', which uses a positional argument to pass the value.

I think we can remove this sentence especially if we say "deprecated
in favor of 'trailer.<token>.cmd'" above.

> -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.

Ok.

> +trailer.<token>.cmd::

I think we should base the description of this option on what I
suggest in patch 1/2. So let's agree on patch 1/2 before discussing
this.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-09 19:02                 ` Christian Couder
@ 2021-04-10 13:40                   ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-10 13:40 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano

Christian Couder <christian.couder@gmail.com> 于2021年4月10日周六 上午3:02写道:
>
> On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > In the original documentation of `trailer.<token>.command`,
> > some descriptions are easily misunderstood. So let's modify
> > it to increase its readability.
> >
> > In addition, clarify that `$ARG` in command can only be
> > replaced once since `$ARG` is text replacement.
>
> I think you can remove the "since `$ARG` is text replacement" part.
> Otherwise this looks fine.
>

Ok.

> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> > ---
> >  Documentation/git-interpret-trailers.txt | 22 +++++++++++-----------
> >  1 file changed, 11 insertions(+), 11 deletions(-)
> >
> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> > index 96ec6499f001..3e5aa3a65ae9 100644
> > --- a/Documentation/git-interpret-trailers.txt
> > +++ b/Documentation/git-interpret-trailers.txt
> > @@ -237,20 +237,20 @@ trailer.<token>.command::
> >         specified <token>.
>
> The beginning of the doc for trailer.<token>.command could already be
> improved. It is:
>
>        This option can be used to specify a shell command that will
>        be called to automatically add or modify a trailer with the
>        specified <token>.
>
> Instead we could say for example:
>
>        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
>
> >  When this option is specified, the behavior is as if a special
>
> I would rather say:
>
> When the specified command is first called to add a trailer with the
> specified <token>, the behavior is as if a special
>
> > -'<token>=<value>' argument were added at the beginning of the command
> > -line, where <value> is taken to be the standard output of the
> > -specified command with any leading and trailing whitespace trimmed
> > -off.
> > +'--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
>
> Here we can remove "specified" as we now use it at the beginning of
> the sentence.
>
> > +whitespace trimmed off.
> >  +
> > -If the command contains the `$ARG` string, this string will be
> > -replaced with the <value> part of an existing trailer with the same
> > -<token>, if any, before the command is launched.
> > +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.
>
> I think we should not talk about `$ARG` at this point, let's remove
> this and see what we can do below in the next paragraph or after it.
>

Here I may want to keep the content similar to the original paragraph.
But if you say so, it will be fine.

> >  If some '<token>=<value>' arguments are also passed on the command
>
> s/'<token>=<value>'/'--trailer <token>=<value>'/
>
> > -line, when a 'trailer.<token>.command' is configured, the command will
> > -also be executed for each of these arguments. And the <value> part of
> > -these arguments, if any, will be used to replace the `$ARG` string in
> > -the command.
> > +line, when a 'trailer.<token>.command' is configured, the command is run
>
> s/when a 'trailer.<token>.command' is configured, //
> s/the command is run/the command is called again/
>
> > +once for each these arguments with the same <token>.
>
> s/each these/each of these/
>
> > And the <value>
> > +part of these arguments, if any, will be used to replace the first `$ARG`
> > +string in the command.
>
> s/first `$ARG` string/first occurrence of substring `$ARG`/
>
> Let's also add something to explain the purpose of this, for example
> we could add:
>
> "This way the command can produce a <value> computed from the <value>
> passed in the '--trailer <token>=<value>' argument."
>
> And then let's also explain what happens when the command is called
> the first time, with for example:
>
> "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>."
>
> To sum up this would give the following (not properly formatted) description:
>
> -------------------
> trailer.<token>.command::
>        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
>

Maybe I am a little confused: this two "-" meaning? Shouldn't it be the
same paragraph?

-       This option can be used to specify a shell command that will
-       be called to automatically add or modify a trailer with the
-       specified <token>.
+       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.

Wouldn't it be weird?

>        When the specified command is first called to add a trailer
> with the specified <token>, 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 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 used
> to replace the first occurrence
>        of substring `$ARG` in the command. This way the command can
> produce a <value>
>        computed from the <value> passed 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>.
> -------------------
>
> Thanks!

Thanks a lot!
It looks more professional than what I wrote.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-09 20:18                 ` Christian Couder
@ 2021-04-10 14:09                   ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-10 14:09 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, Junio C Hamano

Christian Couder <christian.couder@gmail.com> 于2021年4月10日周六 上午4:19写道:
>
> On Fri, Apr 9, 2021 at 3:37 PM ZheNing Hu via GitGitGadget
> <gitgitgadget@gmail.com> wrote:
> >
> > From: ZheNing Hu <adlternative@gmail.com>
> >
> > The `trailer.<token>.command` configuration variable
> > specifies a command (run via the shell, so it does not have
> > to be a single name of or path to the command, but can be a
>
> s/of or/or/
>
> > shell script), and the first occurrence of substring $ARG is
> > replaced with the value given to the `interpret-trailer`
> > command for the token.  This has two downsides:
>
> Maybe: s/for the token/for the token in a '--trailer <token>=<value>' argument/
>
> > * 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).
> >
> > 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.
>
> Good!
>
> > Helped-by: Junio C Hamano <gitster@pobox.com>
> > Helped-by: Christian Couder <christian.couder@gmail.com>
> > Signed-off-by: ZheNing Hu <adlternative@gmail.com>
> > ---
> >  Documentation/git-interpret-trailers.txt | 90 ++++++++++++++++++++----
> >  t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++
> >  trailer.c                                | 37 +++++++---
> >  3 files changed, 187 insertions(+), 24 deletions(-)
> >
> > diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> > index 3e5aa3a65ae9..1a874a93f49b 100644
> > --- a/Documentation/git-interpret-trailers.txt
> > +++ b/Documentation/git-interpret-trailers.txt
> > @@ -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'.
>
> Actually I think that we should say first that this behaves in the
> same way as the 'trailer.<token>.cmd'.
>
> And this should also replace the first paragraph in the description of
> 'trailer.<token>.command', not just the second one.
>

Make sence. After all, the primary purpose of this patch is to show that
".command" has been deprecated.

> Maybe:
>
> "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 due to the fact
>
> s/deprecated/deprecated in favor of 'trailer.<token>.cmd'/
>
> > +that $ARG in the user's command can only be replaced once and that the
>
> s/can only be/is only/
>
> > +original way of replacing $ARG was not safe.
>
> s/was/is/
>
> > Now the preferred option is 'trailer.<token>.cmd', which uses a positional argument to pass the value.
>
> I think we can remove this sentence especially if we say "deprecated
> in favor of 'trailer.<token>.cmd'" above.
>

I agree.

> > -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.
>
> Ok.
>
> > +trailer.<token>.cmd::
>
> I think we should base the description of this option on what I
> suggest in patch 1/2. So let's agree on patch 1/2 before discussing
> this.

Yes, The description of the new .cmd is best approximated by
the .command in 1/2 .

I should @Junio, I don't know if he has any other opinions.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v9 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
                                 ` (2 preceding siblings ...)
  2021-04-09 19:59               ` [PATCH v8 0/2] " Christian Couder
@ 2021-04-12 16:39               ` ZheNing Hu via GitGitGadget
  2021-04-12 16:39                 ` [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
                                   ` (2 more replies)
  3 siblings, 3 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-12 16:39 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG:

 1. if the user's script has more than one $ARG, only the first one will be
    replaced, which is incorrected.
 2. $ARG is textually replaced without shell syntax, which may result a
    broken command when $ARG include some unmatching single quote, very
    unsafe.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config, to solve these above two problems,

We are now writing documents that are more readable and correct than before.

ZheNing Hu (2):
  [GSOC] docs: correct descript of trailer.<token>.command
  [GSOC] trailer: add new .cmd config option

 Documentation/git-interpret-trailers.txt | 96 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++
 trailer.c                                | 37 ++++++---
 3 files changed, 190 insertions(+), 27 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v9
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v9
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v8:

 1:  505903811df8 ! 1:  8129ef6c476b [GSOC] docs: correct descript of trailer.<token>.command
     @@ Commit message
          it to increase its readability.
      
          In addition, clarify that `$ARG` in command can only be
     -    replaced once since `$ARG` is text replacement.
     +    replaced once.
      
          Signed-off-by: ZheNing Hu <adlternative@gmail.com>
      
       ## Documentation/git-interpret-trailers.txt ##
     -@@ Documentation/git-interpret-trailers.txt: trailer.<token>.command::
     - 	specified <token>.
     +@@ Documentation/git-interpret-trailers.txt: trailer.<token>.ifmissing::
     + 	that option for trailers with the specified <token>.
     + 
     + trailer.<token>.command::
     +-	This option can be used to specify a shell command that will
     +-	be called to automatically add or modify a trailer with the
     +-	specified <token>.
     ++	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.
       +
     - When this option is specified, the behavior is as if a special
     +-When this option is specified, the behavior is as if a special
      -'<token>=<value>' argument were added at the beginning of the command
      -line, where <value> is taken to be the standard output of the
      -specified command with any leading and trailing whitespace trimmed
      -off.
     -+'--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 the specified command is first called to add a trailer
     ++with the specified <token>, 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 command with any
     ++leading and trailing whitespace trimmed off.
       +
      -If the command contains the `$ARG` string, this string will be
      -replaced with the <value> part of an existing trailer with the same
      -<token>, if any, before the command is launched.
     -+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.
     ++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 used to replace the first
     ++occurrence of substring `$ARG` in the command. This way the
     ++command can produce a <value> computed from the <value> passed
     ++in the '--trailer <token>=<value>' argument.
       +
     - If some '<token>=<value>' arguments are also passed on the command
     +-If some '<token>=<value>' arguments are also passed on the command
      -line, when a 'trailer.<token>.command' is configured, the command will
      -also be executed for each of these arguments. And the <value> part of
      -these arguments, if any, will be used to replace the `$ARG` string in
      -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.
     ++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
       --------
 2:  3dc8983a4702 ! 2:  7f645ec95f48 [GSOC] trailer: add new .cmd config option
     @@ Commit message
      
          The `trailer.<token>.command` configuration variable
          specifies a command (run via the shell, so it does not have
     -    to be a single name of or path to the command, but can be a
     +    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.  This has two downsides:
     +    command for the token in a '--trailer <token>=<value>' argument.
     +
     +    This has two downsides:
      
          * The use of $ARG in the mechanism misleads the users that
          the value is passed in the shell variable, and tempt them
     @@ Commit message
          Signed-off-by: ZheNing Hu <adlternative@gmail.com>
      
       ## Documentation/git-interpret-trailers.txt ##
     -@@ Documentation/git-interpret-trailers.txt: 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'.
     +@@ Documentation/git-interpret-trailers.txt: trailer.<token>.ifmissing::
     + 	that option for trailers with the specified <token>.
     + 
     + 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.
      ++
     -+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.
     + 	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
     +@@ Documentation/git-interpret-trailers.txt: 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 used to replace the first
     +-occurrence of substring `$ARG` in the command. This way the
     +-command can produce a <value> computed from the <value> passed
     +-in the '--trailer <token>=<value>' argument.
     ++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.
       +
     --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.
     +-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>.
     ++For consistency, the $1 is also passed, this time with the empty string,
     ++in the command when the command is first called to add a trailer with
     ++the specified <token>.
       
       EXAMPLES
       --------

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
@ 2021-04-12 16:39                 ` ZheNing Hu via GitGitGadget
  2021-04-12 20:42                   ` Junio C Hamano
  2021-04-12 16:39                 ` [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
  2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
  2 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-12 16:39 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original documentation of `trailer.<token>.command`,
some descriptions are easily misunderstood. So let's modify
it to increase its readability.

In addition, clarify that `$ARG` in command can only be
replaced once.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..6f2a7a130464 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 trailer.<token>.command::
-	This option can be used to specify a shell command that will
-	be called to automatically add or modify a trailer with the
-	specified <token>.
+	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.
 +
-When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+When the specified command is first called to add a trailer
+with the specified <token>, 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 command with any
+leading and trailing whitespace trimmed off.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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 used to replace the first
+occurrence of substring `$ARG` in the command. This way the
+command can produce a <value> computed from the <value> passed
+in the '--trailer <token>=<value>' argument.
 +
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+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
 --------
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
  2021-04-12 16:39                 ` [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-12 16:39                 ` ZheNing Hu via GitGitGadget
  2021-04-12 20:51                   ` Junio C Hamano
  2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
  2 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-12 16:39 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

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 two 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).

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.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++---
 t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++
 trailer.c                                | 37 +++++++----
 3 files changed, 177 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 6f2a7a130464..dc1f69fa8b67 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 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:
 	once to automatically add a trailer with the specified <token>, and then
 	each time a '--trailer <token>=<value>' argument to modify the <value> of
@@ -247,15 +261,13 @@ 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 used to replace the first
-occurrence of substring `$ARG` in the command. This way the
-command can produce a <value> computed from the <value> passed
-in the '--trailer <token>=<value>' argument.
+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.
 +
-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>.
+For consistency, the $1 is also passed, this time with the empty string,
+in the command when the command is first called to add a trailer with
+the specified <token>.
 
 EXAMPLES
 --------
@@ -338,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:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..68353af3e62e 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -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" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..bd384befe15b 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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(""),
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-12 16:39                 ` [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-12 20:42                   ` Junio C Hamano
  2021-04-16 12:03                     ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-12 20:42 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

>  trailer.<token>.command::
> +	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.
>  +
> -When this option is specified, the behavior is as if a special
> +When the specified command is first called to add a trailer
> +with the specified <token>, 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 command with any
> +leading and trailing whitespace trimmed off.

So, with

	[trailer "foo"] command = "echo $ARG"

in your .git/config

    $ git interpret-trailers </dev/null

gives 'foo:'.

> +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 used to replace the first
> +occurrence of substring `$ARG` in the command. This way the
> +command can produce a <value> computed from the <value> passed
> +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>.

And then

    $ git interpret-trailers --trailer=foo:F </dev/null

would give you

    foo:
    foo: F

The above is quite an easy to read explanation of the behaviour.

I somehow wonder if this "run with empty even without anything on
the command line" a misfeature, and I'd prefer to iron it out before
we add .cmd, because we may not want to inherit it to the new .cmd,
just like we avoided '$ARG' that does not properly quote and
replaces only once from getting inherited by .cmd mechanism.

The reason why I suspect this may be a misfeature is because I do
not see any way to avoid 'foo' trailer once trailer.foo.command is
set.  Which means I cannot use this mechanism to emulate "commit -s",
which would hopefully be something like

	[trailer "signoff"]
		command = "git var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/"
		ifexists = addIfDifferentNeighbor

And trailer.signoff.ifmissing=donothing would not help in this case,
either, I am afraid, as that would not just suppress the automatic
empty one, which is fairly useless, but also suppresses the one that
is made in response to the option from the command line.

Christian?  What's your thought on this?

I can understand that it sometimes may be useful to unconditionally
be able to add a trailer without doing anything from the command
line, but it feels fairly useless that an empty one is automatically
added, that the only way to hide that empty one from the end result
is to use ifexists=replace, and that there is no apparent way to remove
the empty one.

The --trim-empty option is a bit too crude a band-aid to use, as the
existing log message may have an unrelated trailer for which it is
perfectly valid not to have any value.

The fix we'd do when introducing .cmd should also get rid of this
initial "run with an empty even when not asked"?

Or if the execution without any input from the command line were
truly useful sometimes, a configuration variable that disables this
behaviour, i.e.

	[trailer "signoff"]
		command = "git who"
		ifexists = addIfDifferentNeighbor
                implicitExecution = false		

so that

	git commit --trailer=signoff:Couder --trailer=signoff:gitster@

would give us two sign-offs without the empty one, perhaps?


In any case, the documentation update in this step looks reasonably
well written.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-12 16:39                 ` [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-12 20:51                   ` Junio C Hamano
  2021-04-13  7:33                     ` Christian Couder
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-12 20:51 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> +For consistency, the $1 is also passed, this time with the empty string,
> +in the command when the command is first called to add a trailer with
> +the specified <token>.

I guess the same question as 1/2 applies to this part.  I am not
sure what "consistency" the behaviour of calling the configured
command with no argument is trying to achieve.  To me, .cmd doing
this may be for consistency with .command but I am not sure why
the consistency is even desiable.

> +$ 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
> +------------

This and the other (omitted) example demonstrates how the initial
"empty" invocation is useless by using "replace".  Which also means
that you cannot add more than one trailer of the same <key> with the
mechanism (since the older ones are replaced with the latest).

The code change and the test change are consistent with the design,
though.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-12 20:51                   ` Junio C Hamano
@ 2021-04-13  7:33                     ` Christian Couder
  2021-04-13 12:02                       ` ZheNing Hu
  2021-04-13 18:14                       ` Junio C Hamano
  0 siblings, 2 replies; 101+ messages in thread
From: Christian Couder @ 2021-04-13  7:33 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

On Mon, Apr 12, 2021 at 10:51 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > +For consistency, the $1 is also passed, this time with the empty string,
> > +in the command when the command is first called to add a trailer with
> > +the specified <token>.
>
> I guess the same question as 1/2 applies to this part.  I am not
> sure what "consistency" the behaviour of calling the configured
> command with no argument is trying to achieve.  To me, .cmd doing
> this may be for consistency with .command but I am not sure why
> the consistency is even desiable.

It might be desirable to make it easier for people to migrate from
".command" to ".cmd". I agree this is debatable, but I don't see a big
downside in it. Maybe, if no argument was passed at all the first time
the command is called instead of the empty string, the command could
then know that it's called for the first time. I am not sure this
would be very helpful in practice though.

> > +$ 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
> > +------------
>
> This and the other (omitted) example demonstrates how the initial
> "empty" invocation is useless by using "replace".  Which also means
> that you cannot add more than one trailer of the same <key> with the
> mechanism (since the older ones are replaced with the latest).

You can add more than one trailer with the same key if you don't use
"replace" but use "--trim-empty" on the command line, so that an empty
trailer added by the initial invocation is removed. And we can later
add a `trailer.<token>.runMode` to remove the initial invocation.

> The code change and the test change are consistent with the design,
> though.

Yeah, this patch looks good to me now.

Thanks!

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-13  7:33                     ` Christian Couder
@ 2021-04-13 12:02                       ` ZheNing Hu
  2021-04-13 19:18                         ` Junio C Hamano
  2021-04-13 18:14                       ` Junio C Hamano
  1 sibling, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-13 12:02 UTC (permalink / raw)
  To: Christian Couder; +Cc: Junio C Hamano, ZheNing Hu via GitGitGadget, git

Hi, Christian and Junio,

Christian Couder <christian.couder@gmail.com> 于2021年4月13日周二 下午3:33写道:
>
> On Mon, Apr 12, 2021 at 10:51 PM Junio C Hamano <gitster@pobox.com> wrote:
> >
> > "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
> >
> > > +For consistency, the $1 is also passed, this time with the empty string,
> > > +in the command when the command is first called to add a trailer with
> > > +the specified <token>.
> >
> > I guess the same question as 1/2 applies to this part.  I am not
> > sure what "consistency" the behaviour of calling the configured
> > command with no argument is trying to achieve.  To me, .cmd doing
> > this may be for consistency with .command but I am not sure why
> > the consistency is even desirable.
>
> It might be desirable to make it easier for people to migrate from
> ".command" to ".cmd". I agree this is debatable, but I don't see a big
> downside in it. Maybe, if no argument was passed at all the first time
> the command is called instead of the empty string, the command could
> then know that it's called for the first time. I am not sure this
> would be very helpful in practice though.
>

If i'm not wrong, Christan meant that this command must run so it's
"consistency", and Junio thinks this "consistency" is not needed.

It is true that there is not much harm in keeping `.cmd` at the behavior
of `.command` now. But I remember the `trailer.<token>.runmode` that
Christan said before, perhaps adding it in the subsequent iterations can
solve Junio's doubts. Sometimes I also confirm that the behavior of
`git interpret-trailers` is very strange too.

> > > +$ 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
> > > +------------
> >
> > This and the other (omitted) example demonstrates how the initial
> > "empty" invocation is useless by using "replace".  Which also means
> > that you cannot add more than one trailer of the same <key> with the
> > mechanism (since the older ones are replaced with the latest).
>
> You can add more than one trailer with the same key if you don't use
> "replace" but use "--trim-empty" on the command line, so that an empty
> trailer added by the initial invocation is removed. And we can later
> add a `trailer.<token>.runMode` to remove the initial invocation.
>

Yes, something like:

trailer.see.command=echo "$ARG"

git interpret-trailers --trim-empty --trailer="see = lll"
--trailer="see:jj"</dev/null

see: lll
see: jj

> > The code change and the test change are consistent with the design,
> > though.
>
> Yeah, this patch looks good to me now.
>
> Thanks!

So is there anything else should I improve?

Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-13  7:33                     ` Christian Couder
  2021-04-13 12:02                       ` ZheNing Hu
@ 2021-04-13 18:14                       ` Junio C Hamano
  1 sibling, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-13 18:14 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

Christian Couder <christian.couder@gmail.com> writes:

> It might be desirable to make it easier for people to migrate from
> ".command" to ".cmd". I agree this is debatable, but I don't see a big
> downside in it. Maybe, if no argument was passed at all the first time
> the command is called instead of the empty string, the command could
> then know that it's called for the first time. I am not sure this
> would be very helpful in practice though.

An integration of the trailer mechanism into the "git commit"
command, which cannot emulate "-s" at the end-user level, is
something I would call a big failure, though.

>> This and the other (omitted) example demonstrates how the initial
>> "empty" invocation is useless by using "replace".  Which also means
>> that you cannot add more than one trailer of the same <key> with the
>> mechanism (since the older ones are replaced with the latest).
>
> You can add more than one trailer with the same key if you don't use
> "replace" but use "--trim-empty" on the command line, so that an empty
> trailer added by the initial invocation is removed. And we can later
> add a `trailer.<token>.runMode` to remove the initial invocation.

As I said in the other message (not the one you are responding to),
trim-empty is not a viable workaround.  Imagine what happens when
you want to always add a trailer (unrelated to signed-off-by), for
which it is perfectly valid not to have any value.  An attempt to
work around the problem "extra call without being asked from the
command line" behaviour has on our emulated "signed-off-by" example
with --trim-empty would nuke such an unrelated trailer.

Besides, if the command produced non-empty value, e.g.

	[trailer "baz"] command = "echo B $ARG Z"

the extra call without being asked from the command line will still
give "baz: B  Z" that is not empty.

So... I am very curious to learn what the intended use case is for
this puzzling feature.  If it turns out to be that it behaves only
because it was coded that way without any real benefit, I would
strongly prefer not to carry it over to the new .cmd thing, which is
an attempt to deprecate .command to get rid of its broken parts (so
far, we identified two---only the first occurrence is replaced, and
the replacement can break command line syntax) while salvaging its
good parts (being able to programmatically decide the value).  And
so far what I heard hasn't convinced me that this behaviour falls
into the latter category.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-13 12:02                       ` ZheNing Hu
@ 2021-04-13 19:18                         ` Junio C Hamano
  2021-04-14 13:27                           ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-13 19:18 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

>> It might be desirable to make it easier for people to migrate from
>> ".command" to ".cmd". I agree this is debatable, but I don't see a big
>> downside in it. Maybe, if no argument was passed at all the first time
>> the command is called instead of the empty string, the command could
>> then know that it's called for the first time. I am not sure this
>> would be very helpful in practice though.
>>
>
> If i'm not wrong, Christan meant that this command must run so it's
> "consistency", and Junio thinks this "consistency" is not needed.

My stance actually is a bit stronger than that.  I suspect that
running the command without argument once even when no --trailer on
the command line asks for that <key> is a misfeature, if not a bug
(only because it is now documented by 1/2 as such---before that, at
least I did not read the document that way).  And unless it is shown
that it is not a misfeature but is a useful behaviour with an example
use case that benefits from it, I would prefer not to replicate it
in the ".cmd".

> It is true that there is not much harm in keeping `.cmd` at the behavior
> of `.command` now.

It is far from "there is not much harm".  It misses the whole point
of the exercise.

Only replacing the first occurrence and not the second and
subsequent occurrence is not what we are keeping in ".cmd".

Replacing in an unsafe way with respect to the command line syntax
is not what we are keeping in ".cmd".

That is because these two are misfeatures if not bugs (only because
they are documented).

In fact, the only reason why we are introducing .cmd is so that we
can deprecate .command and get rid of its misfeatures while keeping
only good bits.

So I am waiting to hear why it is not a misfeature.  If it is not,
then surely I am fine to keep it for now and add a workaround later,
but until that happens, I do not think "commit --trailer" can be
used as a way to allow end-users emulate "-s" for their favorite
trailer like helped-by, acked-by, etc.





^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-13 19:18                         ` Junio C Hamano
@ 2021-04-14 13:27                           ` ZheNing Hu
  2021-04-14 20:33                             ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-14 13:27 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年4月14日周三 上午3:18写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> >> It might be desirable to make it easier for people to migrate from
> >> ".command" to ".cmd". I agree this is debatable, but I don't see a big
> >> downside in it. Maybe, if no argument was passed at all the first time
> >> the command is called instead of the empty string, the command could
> >> then know that it's called for the first time. I am not sure this
> >> would be very helpful in practice though.
> >>
> >
> > If i'm not wrong, Christan meant that this command must run so it's
> > "consistency", and Junio thinks this "consistency" is not needed.
>
> My stance actually is a bit stronger than that.  I suspect that
> running the command without argument once even when no --trailer on
> the command line asks for that <key> is a misfeature, if not a bug
> (only because it is now documented by 1/2 as such---before that, at
> least I did not read the document that way).  And unless it is shown
> that it is not a misfeature but is a useful behaviour with an example
> use case that benefits from it, I would prefer not to replicate it
> in the ".cmd".
>
> > It is true that there is not much harm in keeping `.cmd` at the behavior
> > of `.command` now.
>
> It is far from "there is not much harm".  It misses the whole point
> of the exercise.
>
> Only replacing the first occurrence and not the second and
> subsequent occurrence is not what we are keeping in ".cmd".
>
> Replacing in an unsafe way with respect to the command line syntax
> is not what we are keeping in ".cmd".
>
> That is because these two are misfeatures if not bugs (only because
> they are documented).
>
> In fact, the only reason why we are introducing .cmd is so that we
> can deprecate .command and get rid of its misfeatures while keeping
> only good bits.
>
> So I am waiting to hear why it is not a misfeature.  If it is not,
> then surely I am fine to keep it for now and add a workaround later,
> but until that happens, I do not think "commit --trailer" can be
> used as a way to allow end-users emulate "-s" for their favorite
> trailer like helped-by, acked-by, etc.
>

If it is really necessary to solve this "empty execution" in .cmd,
Maybe we need to consider two points:
* Do we need a new config flag as you said `[implicitExecution = false]`
or just drop it? Has anyone been relying on the "empty execution" of
.command before? This may be worthy of concern.
*  Do we need `trailer.<token>.runMode` as Christan said before?
I rejected his this suggestion before, and now I regret it a bit.

--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-14 13:27                           ` ZheNing Hu
@ 2021-04-14 20:33                             ` Junio C Hamano
  2021-04-15 15:32                               ` ZheNing Hu
  2021-04-16 12:54                               ` Christian Couder
  0 siblings, 2 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-14 20:33 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

>> So I am waiting to hear why it is not a misfeature.  If it is not,
>> then surely I am fine to keep it for now and add a workaround later,
>> but until that happens, I do not think "commit --trailer" can be
>> used as a way to allow end-users emulate "-s" for their favorite
>> trailer like helped-by, acked-by, etc.
>>
>
> If it is really necessary to solve this "empty execution" in .cmd,

> Maybe we need to consider two points:
> * Do we need a new config flag as you said `[implicitExecution = false]`
> or just drop it? Has anyone been relying on the "empty execution" of
> .command before? This may be worthy of concern.

Yes, if it is useful sometimes to run the .command or .cmd with
empty <value> even when nobody asks for it on the command line with
a "--trailer=<key>:<value>" option, then I agree that the users
should be able to choose between running and not running [*].

> *  Do we need `trailer.<token>.runMode` as Christan said before?
> I rejected his this suggestion before, and now I regret it a bit.

So far, I haven't heard anything that indicates it is a useful
behaviour for .command, so my preference is to get rid of the
behaviour when we introduce .cmd to deprecate .command; yes, until
we get rid of .command, the behaviour between the two would be
inconsistent but that is unavoidable when making a bugfix that is
backward incompatible.

When (and only when) anybody finds a credible use case, we can add a
mechanism to optionally turn it on (e.g. .runMode).

That is my thinking right at this moment, but that's of course
subject to change when a use case that would be helped by having
this extra execution.


[Footnote]

* Right now, all I know is that not being able to turn it off makes
  it impossible to use "git commit --trailer" as a more general
  substitute for "git commit --signoff" without breaking other
  trailers (e.g. --trim-empty may get rid of the result of the extra
  execution, but would remove other trailers that can be
  legitimately empty).  And making it on by default with
  configuration would mean that even though we designed .cmd as a
  better version of the .command feature with its misdesign
  corrected, we'd inherit one misdesign from it, which defeats one
  third of the point of introducing the .cmd in the first place ;-)

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-14 20:33                             ` Junio C Hamano
@ 2021-04-15 15:32                               ` ZheNing Hu
  2021-04-15 17:41                                 ` Junio C Hamano
  2021-04-16 12:54                               ` Christian Couder
  1 sibling, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-15 15:32 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

Junio C Hamano <gitster@pobox.com> 于2021年4月15日周四 上午4:33写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> >> So I am waiting to hear why it is not a misfeature.  If it is not,
> >> then surely I am fine to keep it for now and add a workaround later,
> >> but until that happens, I do not think "commit --trailer" can be
> >> used as a way to allow end-users emulate "-s" for their favorite
> >> trailer like helped-by, acked-by, etc.
> >>
> >
> > If it is really necessary to solve this "empty execution" in .cmd,
>
> > Maybe we need to consider two points:
> > * Do we need a new config flag as you said `[implicitExecution = false]`
> > or just drop it? Has anyone been relying on the "empty execution" of
> > .command before? This may be worthy of concern.
>
> Yes, if it is useful sometimes to run the .command or .cmd with
> empty <value> even when nobody asks for it on the command line with
> a "--trailer=<key>:<value>" option, then I agree that the users
> should be able to choose between running and not running [*].
>
> > *  Do we need `trailer.<token>.runMode` as Christan said before?
> > I rejected his this suggestion before, and now I regret it a bit.
>
> So far, I haven't heard anything that indicates it is a useful
> behaviour for .command, so my preference is to get rid of the
> behaviour when we introduce .cmd to deprecate .command; yes, until
> we get rid of .command, the behaviour between the two would be
> inconsistent but that is unavoidable when making a bugfix that is
> backward incompatible.
>
> When (and only when) anybody finds a credible use case, we can add a
> mechanism to optionally turn it on (e.g. .runMode).
>
> That is my thinking right at this moment, but that's of course
> subject to change when a use case that would be helped by having
> this extra execution.
>
>
> [Footnote]
>
> * Right now, all I know is that not being able to turn it off makes
>   it impossible to use "git commit --trailer" as a more general
>   substitute for "git commit --signoff" without breaking other
>   trailers (e.g. --trim-empty may get rid of the result of the extra
>   execution, but would remove other trailers that can be
>   legitimately empty).  And making it on by default with
>   configuration would mean that even though we designed .cmd as a
>   better version of the .command feature with its misdesign
>   corrected, we'd inherit one misdesign from it, which defeats one
>   third of the point of introducing the .cmd in the first place ;-)

Perhaps such a modification can meet our temporary needs!

@@ -721,9 +738,10 @@ static void process_command_line_args(struct
list_head *arg_head,
        char *cl_separators = xstrfmt("=%s", separators);

        /* 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.cmd || item->conf.command)
+               if (item->conf.command)

I'm so busy today that I probably haven't had time to put this into a
patch today,
If this solution is reasonable, I will send a new version of the patch tomorrow.
As you said, first solve the misfeature, and we can add [trailer.runMode] later.

Thanks, Junio!
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-15 15:32                               ` ZheNing Hu
@ 2021-04-15 17:41                                 ` Junio C Hamano
  0 siblings, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-15 17:41 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: Christian Couder, ZheNing Hu via GitGitGadget, git

ZheNing Hu <adlternative@gmail.com> writes:

> Junio C Hamano <gitster@pobox.com> 于2021年4月15日周四 上午4:33写道:
>>
>> So far, I haven't heard anything that indicates it is a useful
>> behaviour for .command, so my preference is to get rid of the
>> behaviour when we introduce .cmd to deprecate .command; yes, until
>> we get rid of .command, the behaviour between the two would be
>> inconsistent but that is unavoidable when making a bugfix that is
>> backward incompatible.
>>
>> When (and only when) anybody finds a credible use case, we can add a
>> mechanism to optionally turn it on (e.g. .runMode).
> ...
> Perhaps such a modification can meet our temporary needs!
>
> @@ -721,9 +738,10 @@ static void process_command_line_args(struct
> list_head *arg_head,
>         char *cl_separators = xstrfmt("=%s", separators);
>
>         /* 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.cmd || item->conf.command)
> +               if (item->conf.command)
>
> I'm so busy today that I probably haven't had time to put this into a
> patch today,
> If this solution is reasonable, I will send a new version of the patch tomorrow.
> As you said, first solve the misfeature, and we can add [trailer.runMode] later.

I am perfectly fine (or rather I'd be happy) to wait for a while to
see such a patch, as it would be a good idea to give time to those
who do have need for this extra/empty execution to chime in.  So,
there is no need to rush.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v10 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
  2021-04-12 16:39                 ` [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
  2021-04-12 16:39                 ` [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-16  8:47                 ` ZheNing Hu via GitGitGadget
  2021-04-16  8:47                   ` [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
                                     ` (2 more replies)
  2 siblings, 3 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-16  8:47 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG:

 1. if the user's script has more than one $ARG, only the first one will be
    replaced, which is incorrected.
 2. $ARG is textually replaced without shell syntax, which may result a
    broken command when $ARG include some unmatching single quote, very
    unsafe.

At the same time, the trailer.<token>.command has another design
disadvantages: It will automatically execute with a empty value in <token>
<value> pair, which will create a trailer that we don’t expect.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config, to solve these above problems.

We are now writing documents that are more readable and correct than before.

ZheNing Hu (2):
  [GSOC] docs: correct descript of trailer.<token>.command
  [GSOC] trailer: add new .cmd config option

 Documentation/git-interpret-trailers.txt | 93 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++
 trailer.c                                | 35 ++++++---
 3 files changed, 186 insertions(+), 26 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v10
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v10
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v9:

 1:  8129ef6c476b = 1:  8129ef6c476b [GSOC] docs: correct descript of trailer.<token>.command
 2:  7f645ec95f48 ! 2:  daa889bd0ade [GSOC] trailer: add new .cmd config option
     @@ Commit message
          replaced with the value given to the `interpret-trailer`
          command for the token in a '--trailer <token>=<value>' argument.
      
     -    This has two downsides:
     +    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
     @@ Commit message
          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.
     +    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>
     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme
      -occurrence of substring `$ARG` in the command. This way the
      -command can produce a <value> computed from the <value> passed
      -in the '--trailer <token>=<value>' argument.
     -+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.
     - +
     +-+
      -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>.
     -+For consistency, the $1 is also passed, this time with the empty string,
     -+in the command when the command is first called to add a trailer with
     -+the specified <token>.
     ++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
       --------
     @@ Documentation/git-interpret-trailers.txt: subject
      +#!/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.ifExists "addIfDifferentNeighbor"
      +$ git config trailer.cnt.cmd "~/bin/gcount"
     -+$ git interpret-trailers --trailer="cnt:Junio" <<EOF
     ++$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
      +> subject
      +> 
      +> message
     @@ Documentation/git-interpret-trailers.txt: subject
      +message
      +
      +Commit-count: 22484     Junio C Hamano
     ++Commit-count: 1117      Linus Torvalds
      +------------
      +
      +* Configure a 'ref' trailer with a cmd use a global script `glog-grep`
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	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
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +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.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" \
     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value
       	case TRAILER_WHERE:
       		if (trailer_set_where(&conf->where, value))
       			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
     -@@ trailer.c: 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(""),

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
@ 2021-04-16  8:47                   ` ZheNing Hu via GitGitGadget
  2021-04-16 19:11                     ` Junio C Hamano
  2021-04-16  8:47                   ` [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
  2 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-16  8:47 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original documentation of `trailer.<token>.command`,
some descriptions are easily misunderstood. So let's modify
it to increase its readability.

In addition, clarify that `$ARG` in command can only be
replaced once.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..6f2a7a130464 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 trailer.<token>.command::
-	This option can be used to specify a shell command that will
-	be called to automatically add or modify a trailer with the
-	specified <token>.
+	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.
 +
-When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+When the specified command is first called to add a trailer
+with the specified <token>, 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 command with any
+leading and trailing whitespace trimmed off.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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 used to replace the first
+occurrence of substring `$ARG` in the command. This way the
+command can produce a <value> computed from the <value> passed
+in the '--trailer <token>=<value>' argument.
 +
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+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
 --------
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
  2021-04-16  8:47                   ` [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-16  8:47                   ` ZheNing Hu via GitGitGadget
  2021-04-16 19:13                     ` Junio C Hamano
  2021-04-16 19:21                     ` Junio C Hamano
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
  2 siblings, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-16  8:47 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

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>
---
 Documentation/git-interpret-trailers.txt | 74 ++++++++++++++++++---
 t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++
 trailer.c                                | 35 +++++++---
 3 files changed, 174 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 6f2a7a130464..88084ca59654 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 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:
 	once to automatically add a trailer with the specified <token>, and then
 	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
 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 used to replace the first
-occurrence of substring `$ARG` in the command. This way the
-command can produce a <value> computed from the <value> passed
-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>.
+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
 --------
@@ -338,6 +346,54 @@ 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 "addIfDifferentNeighbor"
+$ git config trailer.cnt.cmd "~/bin/gcount"
+$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
+> subject
+> 
+> message
+> 
+> EOF
+subject
+
+message
+
+Commit-count: 22484     Junio C Hamano
+Commit-count: 1117      Linus Torvalds
+------------
+
+* 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:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..04885d0a5e5c 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -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 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' '
 	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" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..7c7cb61a945c 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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);
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-12 20:42                   ` Junio C Hamano
@ 2021-04-16 12:03                     ` Christian Couder
  2021-04-17  1:54                       ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: Christian Couder @ 2021-04-16 12:03 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

Sorry for the late answer.

On Mon, Apr 12, 2021 at 10:42 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> >  trailer.<token>.command::
> > +     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.
> >  +
> > -When this option is specified, the behavior is as if a special
> > +When the specified command is first called to add a trailer
> > +with the specified <token>, 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 command with any
> > +leading and trailing whitespace trimmed off.
>
> So, with
>
>         [trailer "foo"] command = "echo $ARG"
>
> in your .git/config
>
>     $ git interpret-trailers </dev/null
>
> gives 'foo:'.

Yeah:

------
$ git -c trailer.foo.command='echo $ARG' interpret-trailers </dev/null

foo:
------

> > +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 used to replace the first
> > +occurrence of substring `$ARG` in the command. This way the
> > +command can produce a <value> computed from the <value> passed
> > +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>.
>
> And then
>
>     $ git interpret-trailers --trailer=foo:F </dev/null
>
> would give you
>
>     foo:
>     foo: F

Yeah:

------
git -c trailer.foo.command='echo $ARG' interpret-trailers
--trailer=foo:F </dev/null

foo:
foo: F
------

> The above is quite an easy to read explanation of the behaviour.
>
> I somehow wonder if this "run with empty even without anything on
> the command line" a misfeature, and I'd prefer to iron it out before
> we add .cmd, because we may not want to inherit it to the new .cmd,
> just like we avoided '$ARG' that does not properly quote and
> replaces only once from getting inherited by .cmd mechanism.

I don't think it's a misfeature. I think it can be very useful in some
cases like with:

$ git config trailer.sign.command 'echo "$(git config user.name)
<$(git config user.email)>"'

My opinion is that we should have added a `trailer.<token>.runMode`
config option along with `trailer.<token>.command`. This was not
discussed unfortunately when ".command" was implemented, but it seems
to be a good idea now.

> The reason why I suspect this may be a misfeature is because I do
> not see any way to avoid 'foo' trailer once trailer.foo.command is
> set.

It can be avoided when the --trim-empty CLI option can be used. A hook
to remove empty trailers (which might call `git interpret-trailers
--trim-empty` itself) could also be used when --trim-empty cannot be
used directly.

> Which means I cannot use this mechanism to emulate "commit -s",
> which would hopefully be something like
>
>         [trailer "signoff"]
>                 command = "git var GIT_COMMITTER_IDENT | sed -e 's/>.*/>/"
>                 ifexists = addIfDifferentNeighbor
>
> And trailer.signoff.ifmissing=donothing would not help in this case,
> either, I am afraid, as that would not just suppress the automatic
> empty one, which is fairly useless, but also suppresses the one that
> is made in response to the option from the command line.

I agree that the current mechanism cannot easily emulate "commit -s".

> Christian?  What's your thought on this?
>
> I can understand that it sometimes may be useful to unconditionally
> be able to add a trailer without doing anything from the command
> line, but it feels fairly useless that an empty one is automatically
> added, that the only way to hide that empty one from the end result
> is to use ifexists=replace, and that there is no apparent way to remove
> the empty one.
>
> The --trim-empty option is a bit too crude a band-aid to use, as the
> existing log message may have an unrelated trailer for which it is
> perfectly valid not to have any value.

I agree that --trim-empty is not usable sometimes. Another idea would
be to add 'trailer.<token>.trimEmpty' to be able to do things like:

------
git -c trailer.foo.trimEmpty=true -c trailer.foo.command='echo $ARG'
interpret-trailers --trailer=foo:F </dev/null

foo: F
------

It could also be used along with `git commit --trailer=foo:F`.

> The fix we'd do when introducing .cmd should also get rid of this
> initial "run with an empty even when not asked"?

I don't think it's a good idea to make ".cmd" behave in a different
way as ".command" regarding this. Yes, there could be a short term
gain because people could use ".cmd" when they don't want the initial
"run with an empty value", but, as some people might still want this
initial run, long term it looks like a burden that might:

  - make it more difficult to understand how ".cmd" and ".command" both work
  - make it more difficult to migrate from ".command" to ".cmd"
  - prevent us from deprecating ".command" in favor of ".cmd"

> Or if the execution without any input from the command line were
> truly useful sometimes, a configuration variable that disables this
> behaviour, i.e.
>
>         [trailer "signoff"]
>                 command = "git who"
>                 ifexists = addIfDifferentNeighbor
>                 implicitExecution = false
>
> so that
>
>         git commit --trailer=signoff:Couder --trailer=signoff:gitster@
>
> would give us two sign-offs without the empty one, perhaps?

Yeah, that's basically the same idea that we previously discussed with
'tailer.<token>.runMode' (or 'trailer.<token>.alwaysRunCmd') in:

https://lore.kernel.org/git/CAP8UFD0OMJfkuX_JoDros7h0B20D8sm0ZbtkVpL3dCYRV_M=OA@mail.gmail.com/

> In any case, the documentation update in this step looks reasonably
> well written.

Thanks!

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-14 20:33                             ` Junio C Hamano
  2021-04-15 15:32                               ` ZheNing Hu
@ 2021-04-16 12:54                               ` Christian Couder
  1 sibling, 0 replies; 101+ messages in thread
From: Christian Couder @ 2021-04-16 12:54 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu, ZheNing Hu via GitGitGadget, git

On Wed, Apr 14, 2021 at 10:33 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> >> So I am waiting to hear why it is not a misfeature.  If it is not,
> >> then surely I am fine to keep it for now and add a workaround later,
> >> but until that happens, I do not think "commit --trailer" can be
> >> used as a way to allow end-users emulate "-s" for their favorite
> >> trailer like helped-by, acked-by, etc.
> >>
> >
> > If it is really necessary to solve this "empty execution" in .cmd,
>
> > Maybe we need to consider two points:
> > * Do we need a new config flag as you said `[implicitExecution = false]`
> > or just drop it? Has anyone been relying on the "empty execution" of
> > .command before? This may be worthy of concern.
>
> Yes, if it is useful sometimes to run the .command or .cmd with
> empty <value> even when nobody asks for it on the command line with
> a "--trailer=<key>:<value>" option, then I agree that the users
> should be able to choose between running and not running [*].
>
> > *  Do we need `trailer.<token>.runMode` as Christan said before?
> > I rejected his this suggestion before, and now I regret it a bit.
>
> So far, I haven't heard anything that indicates it is a useful
> behaviour for .command,

Well the `git config trailer.sign.command 'echo "$(git config
user.name) <$(git config user.email)>"'` has been documented in the
EXAMPLES section of the `git interpret-trailers` doc since when
".command" was implemented, and I believe that reviewers thought that
it was a good feature to have then.

> so my preference is to get rid of the
> behaviour when we introduce .cmd to deprecate .command; yes, until
> we get rid of .command, the behaviour between the two would be
> inconsistent but that is unavoidable when making a bugfix that is
> backward incompatible.
>
> When (and only when) anybody finds a credible use case, we can add a
> mechanism to optionally turn it on (e.g. .runMode).
>
> That is my thinking right at this moment, but that's of course
> subject to change when a use case that would be helped by having
> this extra execution.

Such use cases and some of this were discussed when `git
interpret-trailers` was initially implemented.

For example in https://lore.kernel.org/git/CAP8UFD2oXpW9QEkSh+vpNGRAxRFp0zJF39ZZ8sUZLTcKB9mHWQ@mail.gmail.com/
I suggested the following:

         [trailer "m-o-b"]
                 key = "Made-on-branch: "
                 command = "git name-rev --name-only HEAD"

when someone wanted a way to always add "a trailer for the branch that
the commit was checked in to at the time"

In https://lore.kernel.org/git/534414FB.6040604@alum.mit.edu/ Michael
Haggerty suggested:

"Maybe there should be a trailer.<token>.trimEmpty config option."

I haven't fully checked the discussions, so there might be other examples.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-16  8:47                   ` [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-04-16 19:11                     ` Junio C Hamano
  0 siblings, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-16 19:11 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> Subject: Re: [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command

s/descript/&ion/ (no need to resend only to fix this).

> diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
> index 96ec6499f001..6f2a7a130464 100644
> --- a/Documentation/git-interpret-trailers.txt
> +++ b/Documentation/git-interpret-trailers.txt
> @@ -232,25 +232,30 @@ trailer.<token>.ifmissing::
>  	that option for trailers with the specified <token>.
>  
>  trailer.<token>.command::
> +	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.
>  +
> +When the specified command is first called to add a trailer
> +with the specified <token>, 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 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 used to replace the first
> +occurrence of substring `$ARG` in the command. This way the
> +command can produce a <value> computed from the <value> passed
> +in the '--trailer <token>=<value>' argument.
>  +

Makes quite a lot of sense.  I wouldn't have got confused by the
behaviour of .command if it were documented like the above from day
one.  Very nice.

> +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>.

OK, so "for consistency" is about consistency between the "execute
once first without being asked" case (where there is no plausible
source of $ARG), and "execute because we were asked by a command
line option (where we are given <value> to replace $ARG).

Makes sense.

Will queue.  Thanks.





^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-16  8:47                   ` [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-16 19:13                     ` Junio C Hamano
  2021-04-16 19:21                     ` Junio C Hamano
  1 sibling, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-16 19:13 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

Hmph, I am getting the following while applying.

.git/rebase-apply/patch:67: trailing whitespace.
> 
.git/rebase-apply/patch:69: trailing whitespace.
> 
.git/rebase-apply/patch:92: trailing whitespace.
> 
.git/rebase-apply/patch:94: trailing whitespace.
> 
warning: 4 lines applied after fixing whitespace errors.
Applying: trailer: add new .cmd config option

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-16  8:47                   ` [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
  2021-04-16 19:13                     ` Junio C Hamano
@ 2021-04-16 19:21                     ` Junio C Hamano
  2021-04-16 19:25                       ` Junio C Hamano
  1 sibling, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-16 19:21 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> 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.

OK.  I think there still will be disagreement on this last point
between Christian and I, but I'd be happy with this as the first cut
for newly introduced .cmd and then when it becomes needed add
something like the attached patch on top to optionally run the given
command when configured.


 trailer.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git c/trailer.c w/trailer.c
index 7c7cb61a94..39132211cc 100644
--- c/trailer.c
+++ w/trailer.c
@@ -723,7 +723,8 @@ 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.run_implicitly && item->conf.cmd) ||
+		    item->conf.command)
 			add_arg_item(arg_head,
 				     xstrdup(token_from_item(item, NULL)),
 				     xstrdup(""),


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-16 19:21                     ` Junio C Hamano
@ 2021-04-16 19:25                       ` Junio C Hamano
  2021-04-17  2:58                         ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-16 19:25 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

Junio C Hamano <gitster@pobox.com> writes:

> OK.  I think there still will be disagreement on this last point
> between Christian and I, but I'd be happy with this as the first cut
> for newly introduced .cmd and then when it becomes needed add
> something like the attached patch on top to optionally run the given
> command when configured.
>
>
>  trailer.c | 3 ++-
>  1 file changed, 2 insertions(+), 1 deletion(-)
>
> diff --git c/trailer.c w/trailer.c
> index 7c7cb61a94..39132211cc 100644
> --- c/trailer.c
> +++ w/trailer.c
> @@ -723,7 +723,8 @@ 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.run_implicitly && item->conf.cmd) ||
> +		    item->conf.command)
>  			add_arg_item(arg_head,
>  				     xstrdup(token_from_item(item, NULL)),
>  				     xstrdup(""),

Actually, if we were to do this, I actually suspect that, unlike the
textual replacement of $ARG in the old .command configuration, we
should take advantage of the fact that the command can tell the
cases between an empty string given as $1 and no positional argument
was given for $1.  So what add_arg_item() does here for .command
(which has to give an empty string, because it will textually
replace an occurrence of $ARG) and for .cmd may have to be different.
Perhaps record NULL here, so that when the command line is formed,
we can tell that we do not want add an extra "" that becomes $1,
when we are dealing with .cmd in this codepath.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-04-16 12:03                     ` Christian Couder
@ 2021-04-17  1:54                       ` Junio C Hamano
  0 siblings, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-17  1:54 UTC (permalink / raw)
  To: Christian Couder; +Cc: ZheNing Hu via GitGitGadget, git, ZheNing Hu

Christian Couder <christian.couder@gmail.com> writes:

> $ git config trailer.sign.command 'echo "$(git config user.name)
> <$(git config user.email)>"'
>
> My opinion is that we should have added a `trailer.<token>.runMode`
> config option along with `trailer.<token>.command`. This was not
> discussed unfortunately when ".command" was implemented, but it seems
> to be a good idea now.

Yes, without a knob to diable/enable, the "feature" is pretty much
useless.  

> It can be avoided when the --trim-empty CLI option can be used. A hook
> to remove empty trailers (which might call `git interpret-trailers
> --trim-empty` itself) could also be used when --trim-empty cannot be
> used directly.

And as you know, --trim-empty that applies to all trailer keys would
destroy other trailers and trailer.<token>.trimEmpty, even if it
were available, would not work at all to remove it for the case you
cited above, to add "user.name <user.email>", which is not an empty
string.

> I agree that the current mechanism cannot easily emulate "commit -s".
> ...
> I agree that --trim-empty is not usable sometimes. Another idea would
> be to add 'trailer.<token>.trimEmpty' to be able to do things like:

"easily"?  "sometimes"?

"--trim-empty" is unusable, trailer.<token>.trimEmpty would not work
even if it were added because the reason why this is broken is not
because it gives an empty value, but because it runs even when it is
not asked and there is no way to turn it off.

This shows that even the only plausibly-useful use case we've seen
so far is not very well supported, and necessary options/knobs to
make it usable have not been invented and implemented.

It is time for us to admit that this is a misfeature that is not
well thought out.  Recognising that .command is broken is the first
step to remedy it with a better alternative in .cmd; otherwise we
would inherit the same breakage in the replacement.

The only reasonable way out I would think of is to hide the
unconditional execution behind trailer.<token>.runMode, and make the
default for runMode to "do not run when --trailer=<token>[:<value>]
is not asked from the command line" for .cmd; it is OK for .command
not to honor the knob, as we will get rid of it once we see .cmd
works well.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-16 19:25                       ` Junio C Hamano
@ 2021-04-17  2:58                         ` Junio C Hamano
  2021-04-17  3:36                           ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-17  2:58 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

Junio C Hamano <gitster@pobox.com> writes:

> Junio C Hamano <gitster@pobox.com> writes:
>
>> OK.  I think there still will be disagreement on this last point
>> between Christian and I, but I'd be happy with this as the first cut
>> for newly introduced .cmd and then when it becomes needed add
>> something like the attached patch on top to optionally run the given
>> command when configured.
>>
>>
>>  trailer.c | 3 ++-
>>  1 file changed, 2 insertions(+), 1 deletion(-)
>>
>> diff --git c/trailer.c w/trailer.c
>> index 7c7cb61a94..39132211cc 100644
>> --- c/trailer.c
>> +++ w/trailer.c
>> @@ -723,7 +723,8 @@ 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.run_implicitly && item->conf.cmd) ||
>> +		    item->conf.command)
>>  			add_arg_item(arg_head,
>>  				     xstrdup(token_from_item(item, NULL)),
>>  				     xstrdup(""),
>
> Actually, if we were to do this, I actually suspect that, unlike the
> textual replacement of $ARG in the old .command configuration, we
> should take advantage of the fact that the command can tell the
> cases between an empty string given as $1 and no positional argument
> was given for $1.  So what add_arg_item() does here for .command
> (which has to give an empty string, because it will textually
> replace an occurrence of $ARG) and for .cmd may have to be different.
> Perhaps record NULL here, so that when the command line is formed,
> we can tell that we do not want add an extra "" that becomes $1,
> when we are dealing with .cmd in this codepath.

And continuing this line of thought, I think it would be a perfectly
fine extension to allow the script/program that is launched by the
.command or .cmd mechanism to signal interpret-trailers that it does
not want it to add a trailer as the result of this invocation by
exiting with non-zero.  And that would be a reasonable way forward
without having to add yet another ugly workaround .runMode.

For example, trailer.signoff.cmd could be this script:

	#!/bin/sh
	if test $# != 1
	then
		exit 1
	else
		git log -1 --author="$1" --format='"%aN" <$aE>'
	fi

where the "implicit" invocation is signalled by not passing any
argument, to which the script reacts by exiting with 1, and the
interpret-trailers would discard the result of the unasked-for
invocation.





^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-17  2:58                         ` Junio C Hamano
@ 2021-04-17  3:36                           ` Junio C Hamano
  2021-04-17  7:41                             ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-17  3:36 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

Junio C Hamano <gitster@pobox.com> writes:

> And continuing this line of thought, I think it would be a perfectly
> fine extension to allow the script/program that is launched by the
> .command or .cmd mechanism to signal interpret-trailers that it does
> not want it to add a trailer as the result of this invocation by
> exiting with non-zero.  And that would be a reasonable way forward
> without having to add yet another ugly workaround .runMode.
>
> For example, trailer.signoff.cmd could be this script:
>
> 	#!/bin/sh
> 	if test $# != 1
> 	then
> 		exit 1
> 	else
> 		git log -1 --author="$1" --format='"%aN" <$aE>'
> 	fi
>
> where the "implicit" invocation is signalled by not passing any
> argument, to which the script reacts by exiting with 1, and the
> interpret-trailers would discard the result of the unasked-for
> invocation.

The beauty of this approach, compared to say .runMode, is that the
program specified by .cmd (and .command, except that it cannot tell
if the invocation is in response to explicit --trailer=<key>:<value>
request, or the extra one that is done even without being asked)
have even better control in squelching the trailer output.  Not just
to silence the extra unasked-for invocation, it can inspect the
value given to each of --trailer=<key>:<value> option and decide not
to add a trailer in response to it.


^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-17  3:36                           ` Junio C Hamano
@ 2021-04-17  7:41                             ` ZheNing Hu
  2021-04-17  8:11                               ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-17  7:41 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月17日周六 上午11:36写道:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > And continuing this line of thought, I think it would be a perfectly
> > fine extension to allow the script/program that is launched by the
> > .command or .cmd mechanism to signal interpret-trailers that it does
> > not want it to add a trailer as the result of this invocation by
> > exiting with non-zero.  And that would be a reasonable way forward
> > without having to add yet another ugly workaround .runMode.
> >
> > For example, trailer.signoff.cmd could be this script:
> >
> >       #!/bin/sh
> >       if test $# != 1
> >       then
> >               exit 1
> >       else
> >               git log -1 --author="$1" --format='"%aN" <$aE>'
> >       fi
> >
> > where the "implicit" invocation is signalled by not passing any
> > argument, to which the script reacts by exiting with 1, and the
> > interpret-trailers would discard the result of the unasked-for
> > invocation.
>
> The beauty of this approach, compared to say .runMode, is that the
> program specified by .cmd (and .command, except that it cannot tell
> if the invocation is in response to explicit --trailer=<key>:<value>
> request, or the extra one that is done even without being asked)
> have even better control in squelching the trailer output.  Not just
> to silence the extra unasked-for invocation, it can inspect the
> value given to each of --trailer=<key>:<value> option and decide not
> to add a trailer in response to it.
>

If I understand correctly, Your approach may be like this:

First, Those `<token> <value>` pairs we passed on the command line
will use one positional parameter in the shell-script.

Second, if it is the trailer implicitly added, originally it was
`<token> ""`, now we turn it to `<token> NULL`, This will make
the shell-script not pass positional parameters.

Then our shell script can distinguish them by the value of $#.

If we want shell-script to execute implicitly anyway, We can do nothing.
the shell-script can be like  as Christian mention:

#!/bin/sh
echo "$(git config user.name) <$(git config user.email)>"

But If we want shell-script to reject implicit execution, We can judge the
value of $# : 0 exit , 1 normal as you mention:

#!/bin/sh
if test $# != 1
then
    exit 0
else
    git log -1 --author="$1" --format='%aN <%aE>'
fi

I agree with your approach.
But when I am reproducing your solution, I may be in trouble:
If we let shell-script `exit(1)`, `capture_command()` will output
"running trailer command '%s' failed" which is its origin strategy
in order to catch the user's command error.

But If we use `exit(0)` , The headache is coming again:

Signed-off-by:

the empty value trailer still output. What we want is that those
"<token> NULL" are rejected here, we don’t want any other output,
do we?


Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-17  7:41                             ` ZheNing Hu
@ 2021-04-17  8:11                               ` Junio C Hamano
  0 siblings, 0 replies; 101+ messages in thread
From: Junio C Hamano @ 2021-04-17  8:11 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

> But when I am reproducing your solution, I may be in trouble:
> If we let shell-script `exit(1)`, `capture_command()` will output
> "running trailer command '%s' failed" which is its origin strategy
> in order to catch the user's command error.

Yes, if we were to take that route, the trailer.c code needs to be
taught to conditionally not to pass $1, and capture_command() needs
to be taught to conditionally be silent on non-zero exit, as it now
is a perfectly sane for the program to exit with non-zero status in
some cases (like this one).

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
  2021-04-16  8:47                   ` [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
  2021-04-16  8:47                   ` [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-17 15:13                   ` ZheNing Hu via GitGitGadget
  2021-04-17 15:13                     ` [PATCH v11 1/2] [GSOC] docs: correct description of .command ZheNing Hu via GitGitGadget
                                       ` (3 more replies)
  2 siblings, 4 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-17 15:13 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG:

 1. if the user's script has more than one $ARG, only the first one will be
    replaced, which is incorrected.
 2. $ARG is textually replaced without shell syntax, which may result a
    broken command when $ARG include some unmatching single quote, very
    unsafe.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config, to solve these above problems.

We are now writing documents that are more readable and correct than before.

ZheNing Hu (2):
  [GSOC] docs: correct description of .command
  [GSOC] trailer: add new .cmd config option

 Documentation/git-interpret-trailers.txt | 111 ++++++++++++++++++---
 t/t7513-interpret-trailers.sh            | 122 +++++++++++++++++++++++
 trailer.c                                |  79 ++++++++++++---
 3 files changed, 280 insertions(+), 32 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v11
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v11
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v10:

 1:  8129ef6c476b ! 1:  34210e5bd3da [GSOC] docs: correct descript of trailer.<token>.command
     @@ Metadata
      Author: ZheNing Hu <adlternative@gmail.com>
      
       ## Commit message ##
     -    [GSOC] docs: correct descript of trailer.<token>.command
     +    [GSOC] docs: correct description of .command
      
          In the original documentation of `trailer.<token>.command`,
          some descriptions are easily misunderstood. So let's modify
 2:  daa889bd0ade ! 2:  9c0fc91aba24 [GSOC] trailer: add new .cmd config option
     @@ Commit message
          replaced with the value given to the `interpret-trailer`
          command for the token in a '--trailer <token>=<value>' argument.
      
     -    This has three downsides:
     +    This has two downsides:
      
          * The use of $ARG in the mechanism misleads the users that
          the value is passed in the shell variable, and tempt them
     @@ Commit message
          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
     @@ Commit message
          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.
     +    of `git command -s`, the first implicitly executed command
     +    will not pass positional parameters, users can use this
     +    feature to suppress its output.
      
          Helped-by: Junio C Hamano <gitster@pobox.com>
          Helped-by: Christian Couder <christian.couder@gmail.com>
     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme
      -occurrence of substring `$ARG` in the command. This way the
      -command can produce a <value> computed from the <value> passed
      -in the '--trailer <token>=<value>' argument.
     --+
     ++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.
     + +
      -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>.
     -+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.
     ++It is worth mentioning that the command is first called to add a
     ++trailer with the specified <token> and without positional argument.
     ++Users can make use of this output when they need automatically add
     ++some trailers. On the other hand, users can use a trick to suppress
     ++this output by judging whether the number of positional parameters
     ++is equal to one, if it is true, execute the commands, otherwise exit
     ++with non-zero to suppress the output.
       
       EXAMPLES
       --------
     @@ Documentation/git-interpret-trailers.txt: subject
      +------------
      +$ cat ~/bin/gcount
      +#!/bin/sh
     -+test -n "$1" && git shortlog -s --author="$1" HEAD || true
     ++if test "$#" != 1
     ++then
     ++	exit 1
     ++else
     ++	test -n "$1" && git shortlog -s --author="$1" HEAD || true
     ++fi
      +$ git config trailer.cnt.key "Commit-count: "
      +$ git config trailer.cnt.ifExists "addIfDifferentNeighbor"
      +$ git config trailer.cnt.cmd "~/bin/gcount"
      +$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
      +> subject
     -+> 
     ++>
      +> message
     -+> 
     ++>
      +> EOF
      +subject
      +
     @@ Documentation/git-interpret-trailers.txt: subject
      +------------
      +$ cat ~/bin/glog-grep
      +#!/bin/sh
     -+test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
     ++if test "$#" != 1
     ++then
     ++	exit 1
     ++else
     ++	test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
     ++fi
      +$ 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
      +
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	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
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	git config trailer.bug.cmd "echo \"\$1\" is" &&
      +	cat >expected2 <<-EOF &&
      +
     ++	Bug-maker: is
      +	Bug-maker: him is him
      +	Bug-maker: me is me
      +	EOF
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	EOF
      +	cat >echoscript <<-EOF &&
      +	#!/bin/sh
     -+	echo who is "\$1"
     ++	if test "\$#" != 1
     ++	then
     ++		exit 1
     ++	else
     ++		echo who is "\$1"
     ++	fi
      +	EOF
      +	chmod +x echoscript &&
      +	git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
      +		>actual2 &&
      +	test_cmp expected2 actual2
      +'
     ++
     ++test_expect_success 'with cmd, $1 and without --trailer' '
     ++	test_when_finished "git config --remove-section trailer.bug" &&
     ++	test_when_finished "git config --remove-section trailer.gub" &&
     ++	git config trailer.bug.key "Bug-maker: " &&
     ++	git config trailer.bug.ifExists "replace" &&
     ++	git config trailer.bug.cmd "./echoscript" &&
     ++	git config trailer.gub.key "Gub-maker: " &&
     ++	git config trailer.gub.ifExists "replace" &&
     ++	git config trailer.gub.cmd "./echoscript2" &&
     ++	cat >expected2 <<-EOF &&
     ++
     ++	Gub-maker: si ohw
     ++	EOF
     ++	cat >echoscript <<-EOF &&
     ++	#!/bin/sh
     ++	if test "\$#" != 1
     ++	then
     ++		exit 1
     ++	else
     ++		echo who is "\$1"
     ++	fi
     ++	EOF
     ++	cat >echoscript2 <<-EOF &&
     ++		echo si ohw "\$1"
     ++	EOF
     ++	chmod +x echoscript &&
     ++	chmod +x echoscript2 &&
     ++	git interpret-trailers >actual2 &&
     ++	test_cmp expected2 actual2
     ++'
      +
       test_expect_success 'without config' '
       	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
     @@ trailer.c: static void free_arg_item(struct arg_item *item)
       	free(item->conf.command);
      +	free(item->conf.cmd);
       	free(item->token);
     - 	free(item->value);
     +-	free(item->value);
     ++	if (item->value)
     ++		FREE_AND_NULL(item->value);
       	free(item);
     + }
     + 
      @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	return 1;
       }
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     + 
     + 	if (capture_command(&cp, &buf, 1024)) {
     +-		error(_("running trailer command '%s' failed"), cmd.buf);
     + 		strbuf_release(&buf);
     +-		result = xstrdup("");
     ++		if (!conf->cmd || arg) {
     ++			error(_("running trailer command '%s' failed"), cmd.buf);
     ++			result = xstrdup("");
     ++		} else
     ++			result = NULL;
     + 	} else {
     + 		strbuf_trim(&buf);
     + 		result = strbuf_detach(&buf, NULL);
      @@ trailer.c: 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) {
     +-		const char *arg;
     +-		if (arg_tok->value && arg_tok->value[0]) {
      +	if (arg_tok->conf.command || arg_tok->conf.cmd) {
     - 		const char *arg;
     - 		if (arg_tok->value && arg_tok->value[0]) {
     ++		const char *arg = NULL;
     ++
     ++		if ((arg_tok->value && arg_tok->value[0]) ||
     ++		   (arg_tok->conf.cmd && !arg_tok->value)) {
       			arg = arg_tok->value;
     + 		} else {
     + 			if (in_tok && in_tok->value)
      @@ trailer.c: 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);
     +-		free((char *)arg);
      +		arg_tok->value = apply_command(&arg_tok->conf, arg);
     - 		free((char *)arg);
     ++		if (arg)
     ++			free((char *)arg);
       	}
       }
     + 
     +@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,
     + 		break;
     + 	case EXISTS_REPLACE:
     + 		apply_item_command(in_tok, arg_tok);
     ++		if (!arg_tok->value) {
     ++			free_arg_item(arg_tok);
     ++			return;
     ++		}
     + 		add_arg_to_input_list(on_tok, arg_tok);
     + 		list_del(&in_tok->list);
     + 		free_trailer_item(in_tok);
     + 		break;
     + 	case EXISTS_ADD:
     + 		apply_item_command(in_tok, arg_tok);
     ++		if (!arg_tok->value) {
     ++			free_arg_item(arg_tok);
     ++			return;
     ++		}
     + 		add_arg_to_input_list(on_tok, arg_tok);
     + 		break;
     + 	case EXISTS_ADD_IF_DIFFERENT:
     + 		apply_item_command(in_tok, arg_tok);
     ++		if (!arg_tok->value) {
     ++			free_arg_item(arg_tok);
     ++			return;
     ++		}
     + 		if (check_if_different(in_tok, arg_tok, 1, head))
     + 			add_arg_to_input_list(on_tok, arg_tok);
     + 		else
     +@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,
     + 		break;
     + 	case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
     + 		apply_item_command(in_tok, arg_tok);
     ++		if (!arg_tok->value) {
     ++			free_arg_item(arg_tok);
     ++			return;
     ++		}
     + 		if (check_if_different(on_tok, arg_tok, 0, head))
     + 			add_arg_to_input_list(on_tok, arg_tok);
     + 		else
     +@@ trailer.c: static void apply_arg_if_missing(struct list_head *head,
     + 	case MISSING_ADD:
     + 		where = arg_tok->conf.where;
     + 		apply_item_command(NULL, arg_tok);
     ++		if (!arg_tok->value) {
     ++			free_arg_item(arg_tok);
     ++			return;
     ++		}
     + 		to_add = trailer_from_arg(arg_tok);
     + 		if (after_or_end(where))
     + 			list_add_tail(&to_add->list, head);
      @@ trailer.c: 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);
     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value
       	case TRAILER_WHERE:
       		if (trailer_set_where(&conf->where, value))
       			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
     +@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,
     + 				     xstrdup(token_from_item(item, NULL)),
     + 				     xstrdup(""),
     + 				     &item->conf, NULL);
     ++		else if (item->conf.cmd)
     ++			add_arg_item(arg_head,
     ++				     xstrdup(token_from_item(item, NULL)),
     ++				     NULL,
     ++				     &item->conf, NULL);
     + 	}
     + 
     + 	/* Add an arg item for each trailer on the command line */

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v11 1/2] [GSOC] docs: correct description of .command
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
@ 2021-04-17 15:13                     ` ZheNing Hu via GitGitGadget
  2021-04-17 15:13                     ` [PATCH v11 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
                                       ` (2 subsequent siblings)
  3 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-17 15:13 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original documentation of `trailer.<token>.command`,
some descriptions are easily misunderstood. So let's modify
it to increase its readability.

In addition, clarify that `$ARG` in command can only be
replaced once.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..6f2a7a130464 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 trailer.<token>.command::
-	This option can be used to specify a shell command that will
-	be called to automatically add or modify a trailer with the
-	specified <token>.
+	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.
 +
-When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+When the specified command is first called to add a trailer
+with the specified <token>, 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 command with any
+leading and trailing whitespace trimmed off.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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 used to replace the first
+occurrence of substring `$ARG` in the command. This way the
+command can produce a <value> computed from the <value> passed
+in the '--trailer <token>=<value>' argument.
 +
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+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
 --------
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v11 2/2] [GSOC] trailer: add new .cmd config option
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
  2021-04-17 15:13                     ` [PATCH v11 1/2] [GSOC] docs: correct description of .command ZheNing Hu via GitGitGadget
@ 2021-04-17 15:13                     ` ZheNing Hu via GitGitGadget
  2021-04-17 22:26                     ` [PATCH v11 0/2] " Junio C Hamano
  2021-05-03 15:41                     ` [PATCH v12 " ZheNing Hu via GitGitGadget
  3 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-04-17 15:13 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

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 two 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).

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`, the first implicitly executed command
will not pass positional parameters, users can use this
feature to suppress its output.

Helped-by: Junio C Hamano <gitster@pobox.com>
Helped-by: Christian Couder <christian.couder@gmail.com>
Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt |  90 +++++++++++++++--
 t/t7513-interpret-trailers.sh            | 122 +++++++++++++++++++++++
 trailer.c                                |  79 ++++++++++++---
 3 files changed, 267 insertions(+), 24 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 6f2a7a130464..3fcc721bb3a2 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 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:
 	once to automatically add a trailer with the specified <token>, and then
 	each time a '--trailer <token>=<value>' argument to modify the <value> of
@@ -247,15 +261,17 @@ 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 used to replace the first
-occurrence of substring `$ARG` in the command. This way the
-command can produce a <value> computed from the <value> passed
-in the '--trailer <token>=<value>' argument.
+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.
 +
-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>.
+It is worth mentioning that the command is first called to add a
+trailer with the specified <token> and without positional argument.
+Users can make use of this output when they need automatically add
+some trailers. On the other hand, users can use a trick to suppress
+this output by judging whether the number of positional parameters
+is equal to one, if it is true, execute the commands, otherwise exit
+with non-zero to suppress the output.
 
 EXAMPLES
 --------
@@ -338,6 +354,64 @@ 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
+if test "$#" != 1
+then
+	exit 1
+else
+	test -n "$1" && git shortlog -s --author="$1" HEAD || true
+fi
+$ git config trailer.cnt.key "Commit-count: "
+$ git config trailer.cnt.ifExists "addIfDifferentNeighbor"
+$ git config trailer.cnt.cmd "~/bin/gcount"
+$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
+> subject
+>
+> message
+>
+> EOF
+subject
+
+message
+
+Commit-count: 22484     Junio C Hamano
+Commit-count: 1117      Linus Torvalds
+------------
+
+* 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
+if test "$#" != 1
+then
+	exit 1
+else
+	test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
+fi
+$ 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:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..1e5f6160dd5a 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -51,6 +51,107 @@ 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 "add" &&
+	git config trailer.bug.cmd "echo \"\$1\" is" &&
+	cat >expected2 <<-EOF &&
+
+	Bug-maker: is
+	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
+	if test "\$#" != 1
+	then
+		exit 1
+	else
+		echo who is "\$1"
+	fi
+	EOF
+	chmod +x echoscript &&
+	git interpret-trailers --trailer "bug: him" --trailer "bug:me" \
+		>actual2 &&
+	test_cmp expected2 actual2
+'
+
+test_expect_success 'with cmd, $1 and without --trailer' '
+	test_when_finished "git config --remove-section trailer.bug" &&
+	test_when_finished "git config --remove-section trailer.gub" &&
+	git config trailer.bug.key "Bug-maker: " &&
+	git config trailer.bug.ifExists "replace" &&
+	git config trailer.bug.cmd "./echoscript" &&
+	git config trailer.gub.key "Gub-maker: " &&
+	git config trailer.gub.ifExists "replace" &&
+	git config trailer.gub.cmd "./echoscript2" &&
+	cat >expected2 <<-EOF &&
+
+	Gub-maker: si ohw
+	EOF
+	cat >echoscript <<-EOF &&
+	#!/bin/sh
+	if test "\$#" != 1
+	then
+		exit 1
+	else
+		echo who is "\$1"
+	fi
+	EOF
+	cat >echoscript2 <<-EOF &&
+		echo si ohw "\$1"
+	EOF
+	chmod +x echoscript &&
+	chmod +x echoscript2 &&
+	git interpret-trailers >actual2 &&
+	test_cmp expected2 actual2
+'
+
 test_expect_success 'without config' '
 	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
 
@@ -1274,6 +1375,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" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..2f5f8f5b4b59 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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,8 +128,10 @@ 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);
+	if (item->value)
+		FREE_AND_NULL(item->value);
 	free(item);
 }
 
@@ -216,26 +219,35 @@ 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;
 
 	if (capture_command(&cp, &buf, 1024)) {
-		error(_("running trailer command '%s' failed"), cmd.buf);
 		strbuf_release(&buf);
-		result = xstrdup("");
+		if (!conf->cmd || arg) {
+			error(_("running trailer command '%s' failed"), cmd.buf);
+			result = xstrdup("");
+		} else
+			result = NULL;
 	} else {
 		strbuf_trim(&buf);
 		result = strbuf_detach(&buf, NULL);
@@ -247,9 +259,11 @@ 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) {
-		const char *arg;
-		if (arg_tok->value && arg_tok->value[0]) {
+	if (arg_tok->conf.command || arg_tok->conf.cmd) {
+		const char *arg = NULL;
+
+		if ((arg_tok->value && arg_tok->value[0]) ||
+		   (arg_tok->conf.cmd && !arg_tok->value)) {
 			arg = arg_tok->value;
 		} else {
 			if (in_tok && in_tok->value)
@@ -257,8 +271,9 @@ 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);
-		free((char *)arg);
+		arg_tok->value = apply_command(&arg_tok->conf, arg);
+		if (arg)
+			free((char *)arg);
 	}
 }
 
@@ -273,16 +288,28 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,
 		break;
 	case EXISTS_REPLACE:
 		apply_item_command(in_tok, arg_tok);
+		if (!arg_tok->value) {
+			free_arg_item(arg_tok);
+			return;
+		}
 		add_arg_to_input_list(on_tok, arg_tok);
 		list_del(&in_tok->list);
 		free_trailer_item(in_tok);
 		break;
 	case EXISTS_ADD:
 		apply_item_command(in_tok, arg_tok);
+		if (!arg_tok->value) {
+			free_arg_item(arg_tok);
+			return;
+		}
 		add_arg_to_input_list(on_tok, arg_tok);
 		break;
 	case EXISTS_ADD_IF_DIFFERENT:
 		apply_item_command(in_tok, arg_tok);
+		if (!arg_tok->value) {
+			free_arg_item(arg_tok);
+			return;
+		}
 		if (check_if_different(in_tok, arg_tok, 1, head))
 			add_arg_to_input_list(on_tok, arg_tok);
 		else
@@ -290,6 +317,10 @@ static void apply_arg_if_exists(struct trailer_item *in_tok,
 		break;
 	case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
 		apply_item_command(in_tok, arg_tok);
+		if (!arg_tok->value) {
+			free_arg_item(arg_tok);
+			return;
+		}
 		if (check_if_different(on_tok, arg_tok, 0, head))
 			add_arg_to_input_list(on_tok, arg_tok);
 		else
@@ -314,6 +345,10 @@ static void apply_arg_if_missing(struct list_head *head,
 	case MISSING_ADD:
 		where = arg_tok->conf.where;
 		apply_item_command(NULL, arg_tok);
+		if (!arg_tok->value) {
+			free_arg_item(arg_tok);
+			return;
+		}
 		to_add = trailer_from_arg(arg_tok);
 		if (after_or_end(where))
 			list_add_tail(&to_add->list, head);
@@ -430,6 +465,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 +490,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 +499,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 +579,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);
@@ -713,6 +755,11 @@ static void process_command_line_args(struct list_head *arg_head,
 				     xstrdup(token_from_item(item, NULL)),
 				     xstrdup(""),
 				     &item->conf, NULL);
+		else if (item->conf.cmd)
+			add_arg_item(arg_head,
+				     xstrdup(token_from_item(item, NULL)),
+				     NULL,
+				     &item->conf, NULL);
 	}
 
 	/* Add an arg item for each trailer on the command line */
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
  2021-04-17 15:13                     ` [PATCH v11 1/2] [GSOC] docs: correct description of .command ZheNing Hu via GitGitGadget
  2021-04-17 15:13                     ` [PATCH v11 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
@ 2021-04-17 22:26                     ` Junio C Hamano
  2021-04-18  7:47                       ` ZheNing Hu
  2021-05-03 15:41                     ` [PATCH v12 " ZheNing Hu via GitGitGadget
  3 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-17 22:26 UTC (permalink / raw)
  To: ZheNing Hu via GitGitGadget; +Cc: git, Christian Couder, ZheNing Hu

"ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:

> In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> Christian talked about the problem of using strbuf_replace() to replace
> $ARG:
>
>  1. if the user's script has more than one $ARG, only the first one will be
>     replaced, which is incorrected.
>  2. $ARG is textually replaced without shell syntax, which may result a
>     broken command when $ARG include some unmatching single quote, very
>     unsafe.
>
> Now pass trailer value as $1 to the trailer command with another
> trailer.<token>.cmd config, to solve these above problems.
>
> We are now writing documents that are more readable and correct than before.

Here is a good spot to summarize what changed since the previous
round.

It seems that this now has "exit non-zero to tell the caller not to
add the trailer for this execuation".  Is that the only change you
made?

I was hoping that we'd declare victory with what was in v10 (with
possibly typos and minor stylistic fixes if needed---I no longer
remember details), let it go through the usual course of cooking in
'next' and merged down to 'master', and then after the dust settles,
we'd be adding this "by exiting with non-zero status, scripts can
signal a trailer not to be added for a particular invocation" as a
new feature, if it turns out to be necessary.

But let's see what's new in this iteration.


>       +#!/bin/sh
>      -+test -n "$1" && git shortlog -s --author="$1" HEAD || true
>      ++if test "$#" != 1
>      ++then
>      ++	exit 1
>      ++else
>      ++	test -n "$1" && git shortlog -s --author="$1" HEAD || true
>      ++fi

I find this dubious.  Why not

	if test "$#" != 1 || test -z "$1"
	then
		exit 1
	else
		git shortlog -s --author="$1" HEAD
	fi

That is, if you happened to give an empty string, your version gives
"" to <value> and returns success, letting a trailer "cnt:" with
empty value.  Is that what we really want?

>       +$ git config trailer.cnt.key "Commit-count: "
>       +$ git config trailer.cnt.ifExists "addIfDifferentNeighbor"
>       +$ git config trailer.cnt.cmd "~/bin/gcount"
>       +$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
>       +> subject
>      -+> 
>      ++>
>       +> message
>      -+> 
>      ++>
>       +> EOF
>       +subject
>       +
>      @@ Documentation/git-interpret-trailers.txt: subject
>       +------------
>       +$ cat ~/bin/glog-grep
>       +#!/bin/sh
>      -+test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
>      ++if test "$#" != 1
>      ++then
>      ++	exit 1
>      ++else
>      ++	test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
>      ++fi

Ditto.

>      + 	if (capture_command(&cp, &buf, 1024)) {
>      +-		error(_("running trailer command '%s' failed"), cmd.buf);
>      + 		strbuf_release(&buf);
>      +-		result = xstrdup("");
>      ++		if (!conf->cmd || arg) {
>      ++			error(_("running trailer command '%s' failed"), cmd.buf);

I am not sure about this part.  If .cmd (the new style) exits with a
non-zero status for user-supplied --trailer=<token>:<value> (because
it did not like the <value>), is that "running failed"?  The script
is expected to express yes/no with its exit status, so I would say it
is not failing, but successfully expressed its displeasure and vetoed
the particular trailer from getting added.  IOW, "|| arg" part in
the condition feels iffy to me.

>      ++			result = xstrdup("");
>      ++		} else
>      ++			result = NULL;
>      + 	} else {
>      + 		strbuf_trim(&buf);
>      + 		result = strbuf_detach(&buf, NULL);

OK.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-17 22:26                     ` [PATCH v11 0/2] " Junio C Hamano
@ 2021-04-18  7:47                       ` ZheNing Hu
  2021-04-21  0:09                         ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-18  7:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月18日周日 上午6:26写道:
>
> "ZheNing Hu via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
> > In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
> > Christian talked about the problem of using strbuf_replace() to replace
> > $ARG:
> >
> >  1. if the user's script has more than one $ARG, only the first one will be
> >     replaced, which is incorrected.
> >  2. $ARG is textually replaced without shell syntax, which may result a
> >     broken command when $ARG include some unmatching single quote, very
> >     unsafe.
> >
> > Now pass trailer value as $1 to the trailer command with another
> > trailer.<token>.cmd config, to solve these above problems.
> >
> > We are now writing documents that are more readable and correct than before.
>
> Here is a good spot to summarize what changed since the previous
> round.
>
> It seems that this now has "exit non-zero to tell the caller not to
> add the trailer for this execuation".  Is that the only change you
> made?
>

Yes, I think the more accurate one should be "exit non-zero to tell
the caller not to add the trailer for this implicit execuation", You also
said below, it may not be so good.

> I was hoping that we'd declare victory with what was in v10 (with
> possibly typos and minor stylistic fixes if needed---I no longer
> remember details), let it go through the usual course of cooking in
> 'next' and merged down to 'master', and then after the dust settles,
> we'd be adding this "by exiting with non-zero status, scripts can
> signal a trailer not to be added for a particular invocation" as a
> new feature, if it turns out to be necessary.
>

Thanks for your and Christian's help this month!

OK, I understand, then I can wait for a while until `trailer_cmd` merge
to master.

> But let's see what's new in this iteration.
>
>
> >       +#!/bin/sh
> >      -+test -n "$1" && git shortlog -s --author="$1" HEAD || true
> >      ++if test "$#" != 1
> >      ++then
> >      ++       exit 1
> >      ++else
> >      ++       test -n "$1" && git shortlog -s --author="$1" HEAD || true
> >      ++fi
>
> I find this dubious.  Why not
>
>         if test "$#" != 1 || test -z "$1"
>         then
>                 exit 1
>         else
>                 git shortlog -s --author="$1" HEAD
>         fi
>
> That is, if you happened to give an empty string, your version gives
> "" to <value> and returns success, letting a trailer "cnt:" with
> empty value.  Is that what we really want?

If it's the user use `--trailer="cnt:"` instread of command implict running,
I think keep it is right.

In fact, it should be said that it is equivalent to exit(0) if the user use
`--trailer="cnt:"`.

>
> >       +$ git config trailer.cnt.key "Commit-count: "
> >       +$ git config trailer.cnt.ifExists "addIfDifferentNeighbor"
> >       +$ git config trailer.cnt.cmd "~/bin/gcount"
> >       +$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
> >       +> subject
> >      -+>
> >      ++>
> >       +> message
> >      -+>
> >      ++>
> >       +> EOF
> >       +subject
> >       +
> >      @@ Documentation/git-interpret-trailers.txt: subject
> >       +------------
> >       +$ cat ~/bin/glog-grep
> >       +#!/bin/sh
> >      -+test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
> >      ++if test "$#" != 1
> >      ++then
> >      ++       exit 1
> >      ++else
> >      ++       test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
> >      ++fi
>
> Ditto.
>
> >      +        if (capture_command(&cp, &buf, 1024)) {
> >      +-               error(_("running trailer command '%s' failed"), cmd.buf);
> >      +                strbuf_release(&buf);
> >      +-               result = xstrdup("");
> >      ++               if (!conf->cmd || arg) {
> >      ++                       error(_("running trailer command '%s' failed"), cmd.buf);
>
> I am not sure about this part.  If .cmd (the new style) exits with a
> non-zero status for user-supplied --trailer=<token>:<value> (because
> it did not like the <value>), is that "running failed"?  The script
> is expected to express yes/no with its exit status, so I would say it
> is not failing, but successfully expressed its displeasure and vetoed
> the particular trailer from getting added.  IOW, "|| arg" part in
> the condition feels iffy to me.
>

Well, you mean we can take advantage of non-zero exits instead of
just removing implicitly executed content. I argee with you, this
place is worth change.

> >      ++                       result = xstrdup("");
> >      ++               } else
> >      ++                       result = NULL;
> >      +        } else {
> >      +                strbuf_trim(&buf);
> >      +                result = strbuf_detach(&buf, NULL);
>
> OK.

Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-18  7:47                       ` ZheNing Hu
@ 2021-04-21  0:09                         ` Junio C Hamano
  2021-04-21  5:47                           ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-21  0:09 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

> OK, I understand, then I can wait for a while until `trailer_cmd` merge
> to master.
>
>> But let's see what's new in this iteration.
>>
>>
>> >       +#!/bin/sh
>> >      -+test -n "$1" && git shortlog -s --author="$1" HEAD || true
>> >      ++if test "$#" != 1
>> >      ++then
>> >      ++       exit 1
>> >      ++else
>> >      ++       test -n "$1" && git shortlog -s --author="$1" HEAD || true
>> >      ++fi
>>
>> I find this dubious.  Why not
>>
>>         if test "$#" != 1 || test -z "$1"
>>         then
>>                 exit 1
>>         else
>>                 git shortlog -s --author="$1" HEAD
>>         fi
>>
>> That is, if you happened to give an empty string, your version gives
>> "" to <value> and returns success, letting a trailer "cnt:" with
>> empty value.  Is that what we really want?
>
> If it's the user use `--trailer="cnt:"` instread of command implict running,
> I think keep it is right.

No, if you give an empty string, you'd end up running "shortlog"
with --author="" and give whatever random number it comes up with,
which I do not think is what you would want.

That is why --trailer=cnt: without name to match --author can be
rejected with "exit 1" to demonstrate the feature.  The .cmd can
squelch not just the "unasked for extra invocation", but invocation
from the command line whose <value> was bogus, unlike the .runmode
feature we've seen proposed earlier.

>> >      +        if (capture_command(&cp, &buf, 1024)) {
>> >      +-               error(_("running trailer command '%s' failed"), cmd.buf);
>> >      +                strbuf_release(&buf);
>> >      +-               result = xstrdup("");
>> >      ++               if (!conf->cmd || arg) {
>> >      ++                       error(_("running trailer command '%s' failed"), cmd.buf);
>>
>> I am not sure about this part.  If .cmd (the new style) exits with a
>> non-zero status for user-supplied --trailer=<token>:<value> (because
>> it did not like the <value>), is that "running failed"?  The script
>> is expected to express yes/no with its exit status, so I would say it
>> is not failing, but successfully expressed its displeasure and vetoed
>> the particular trailer from getting added.  IOW, "|| arg" part in
>> the condition feels iffy to me.
>
> Well, you mean we can take advantage of non-zero exits instead of
> just removing implicitly executed content. I argee with you, this
> place is worth change.

Yup, that is what I meant.

In any case, let's see how well the base topic fares.

Thanks.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-21  0:09                         ` Junio C Hamano
@ 2021-04-21  5:47                           ` ZheNing Hu
  2021-04-21 23:40                             ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-21  5:47 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月21日周三 上午8:09写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > OK, I understand, then I can wait for a while until `trailer_cmd` merge
> > to master.
> >
> >> But let's see what's new in this iteration.
> >>
> >>
> >> >       +#!/bin/sh
> >> >      -+test -n "$1" && git shortlog -s --author="$1" HEAD || true
> >> >      ++if test "$#" != 1
> >> >      ++then
> >> >      ++       exit 1
> >> >      ++else
> >> >      ++       test -n "$1" && git shortlog -s --author="$1" HEAD || true
> >> >      ++fi
> >>
> >> I find this dubious.  Why not
> >>
> >>         if test "$#" != 1 || test -z "$1"
> >>         then
> >>                 exit 1
> >>         else
> >>                 git shortlog -s --author="$1" HEAD
> >>         fi
> >>
> >> That is, if you happened to give an empty string, your version gives
> >> "" to <value> and returns success, letting a trailer "cnt:" with
> >> empty value.  Is that what we really want?
> >
> > If it's the user use `--trailer="cnt:"` instread of command implict running,
> > I think keep it is right.
>
> No, if you give an empty string, you'd end up running "shortlog"
> with --author="" and give whatever random number it comes up with,
> which I do not think is what you would want.
>
> That is why --trailer=cnt: without name to match --author can be
> rejected with "exit 1" to demonstrate the feature.  The .cmd can
> squelch not just the "unasked for extra invocation", but invocation
> from the command line whose <value> was bogus, unlike the .runmode
> feature we've seen proposed earlier.
>

I admit that your idea makes sense, but we actually have another requirement:
Construct a trailer with an empty value.

The Commit-Count example above may not be good, Let’s take a look at the
Signed-off-by.

e.g. `--trailer="sign:"`, we expect to output a "Signed-off-by: ",
then we can fill it
with the "name <email>" pair we want, this is when we shouldn't return non-zero
and suppress its output.

> >> >      +        if (capture_command(&cp, &buf, 1024)) {
> >> >      +-               error(_("running trailer command '%s' failed"), cmd.buf);
> >> >      +                strbuf_release(&buf);
> >> >      +-               result = xstrdup("");
> >> >      ++               if (!conf->cmd || arg) {
> >> >      ++                       error(_("running trailer command '%s' failed"), cmd.buf);
> >>
> >> I am not sure about this part.  If .cmd (the new style) exits with a
> >> non-zero status for user-supplied --trailer=<token>:<value> (because
> >> it did not like the <value>), is that "running failed"?  The script
> >> is expected to express yes/no with its exit status, so I would say it
> >> is not failing, but successfully expressed its displeasure and vetoed
> >> the particular trailer from getting added.  IOW, "|| arg" part in
> >> the condition feels iffy to me.
> >
> > Well, you mean we can take advantage of non-zero exits instead of
> > just removing implicitly executed content. I argee with you, this
> > place is worth change.
>
> Yup, that is what I meant.
>
> In any case, let's see how well the base topic fares.
>

Yes, I don't know if Christian agrees with temporary situation.

> Thanks.

Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-21  5:47                           ` ZheNing Hu
@ 2021-04-21 23:40                             ` Junio C Hamano
  2021-04-22  9:20                               ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-21 23:40 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

> I admit that your idea makes sense, but we actually have another requirement:
> Construct a trailer with an empty value.

It can be done with a different script given to .cmd, which would
say "exit 0" when allowing an empty string given as its input to
appear in the output.

I was reacting what the "count" example does, and found that
counting commits by all authors (that is what an empty string would
match when given to --author="") somewhat illogical in the context
of that example.

After all, these examples are to pique readers' interest by
demonstrating what the mechanism can do and how it can be used, and
for this feature, I think showing that

 (1) we can squelch the output from unasked-for execution;

 (2) we can reject --trailer=<key>:<value> when we do not like the
     given <value>;

 (3) we can insert the trailer with the value we compute (and it is
     OK for the computed result happens to be an empty string).

should be plenty sufficient.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-21 23:40                             ` Junio C Hamano
@ 2021-04-22  9:20                               ` ZheNing Hu
  2021-04-27  6:49                                 ` Junio C Hamano
  0 siblings, 1 reply; 101+ messages in thread
From: ZheNing Hu @ 2021-04-22  9:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月22日周四 上午7:40写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > I admit that your idea makes sense, but we actually have another requirement:
> > Construct a trailer with an empty value.
>
> It can be done with a different script given to .cmd, which would
> say "exit 0" when allowing an empty string given as its input to
> appear in the output.
>

Now I think that we should keep those trailers which ask for a
"name <email>" pair, like "Helped-by", "Signed-off-by", when we
provide a "help:","sign:" in command line, This allows the user to
dynamically fill in the "name <email>" pair of other people in the
commit message later. It is worthwhile for users to exit with exit(0).

But those dispensable things like "Commit-Count", It must depend
on a person's statistics in the git repository. So "cnt:" is meaningless,
users' script can let it exit(1).

> I was reacting what the "count" example does, and found that
> counting commits by all authors (that is what an empty string would
> match when given to --author="") somewhat illogical in the context
> of that example.
>

The "Commit-Count" in the example here can only target a specific person,
which has great limitations.

I have a bold idea:

Our current --trailer can only fill in one data item, and we don't
expect it to fill
in multiple rows. something like "Commit-Count", we hope to count the number of
commits from everyone. But as we can see:

Commit-count: 7 Linus Arver
  1117  Linus Torvalds

So If we have the opportunity to capture every line or every "block" of content,
and feed it to git interpret-trailer, maybe we can get something like:

Commit-count: 7 Linus Arver
Commit-count: 1117  Linus Torvalds

This will definitely make it easy for us to generate a lot of trailer at once.
For example, a newbie like me, after making a patch, want to --cc all authors
of one file, maybe I only need a command to get it.

I don't know if it's a bit whimsical.

> After all, these examples are to pique readers' interest by
> demonstrating what the mechanism can do and how it can be used, and
> for this feature, I think showing that
>
>  (1) we can squelch the output from unasked-for execution;
>
>  (2) we can reject --trailer=<key>:<value> when we do not like the
>      given <value>;
>
>  (3) we can insert the trailer with the value we compute (and it is
>      OK for the computed result happens to be an empty string).
>
> should be plenty sufficient.

OK. I will add these three examples in the new patch (when .cmd merge to
master).

Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-22  9:20                               ` ZheNing Hu
@ 2021-04-27  6:49                                 ` Junio C Hamano
  2021-04-27 12:24                                   ` ZheNing Hu
  0 siblings, 1 reply; 101+ messages in thread
From: Junio C Hamano @ 2021-04-27  6:49 UTC (permalink / raw)
  To: ZheNing Hu; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

ZheNing Hu <adlternative@gmail.com> writes:

> Now I think that we should keep those trailers which ask for a
> "name <email>" pair, like "Helped-by", "Signed-off-by", when we
> provide a "help:","sign:" in command line, This allows the user to
> dynamically fill in the "name <email>" pair of other people in the
> commit message later. It is worthwhile for users to exit with exit(0).
>
> But those dispensable things like "Commit-Count", It must depend
> on a person's statistics in the git repository. So "cnt:" is meaningless,
> users' script can let it exit(1).

Perhaps, but at this point what you think (or what I think) does not
matter.  That was the whole point of letting .cmd script signal Git
if the result from the invocation should be kept or discarded with
its exit status.  What would be sufficient here for us to do is to
agree that it would be good to have a minimal set (perhaps a pair)
of examples to demonstrate that the script can choose to keep or
discard a meaningless trailer entry with its exit status.

^ permalink raw reply	[flat|nested] 101+ messages in thread

* Re: [PATCH v11 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-27  6:49                                 ` Junio C Hamano
@ 2021-04-27 12:24                                   ` ZheNing Hu
  0 siblings, 0 replies; 101+ messages in thread
From: ZheNing Hu @ 2021-04-27 12:24 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: ZheNing Hu via GitGitGadget, Git List, Christian Couder

Junio C Hamano <gitster@pobox.com> 于2021年4月27日周二 下午2:49写道:
>
> ZheNing Hu <adlternative@gmail.com> writes:
>
> > Now I think that we should keep those trailers which ask for a
> > "name <email>" pair, like "Helped-by", "Signed-off-by", when we
> > provide a "help:","sign:" in command line, This allows the user to
> > dynamically fill in the "name <email>" pair of other people in the
> > commit message later. It is worthwhile for users to exit with exit(0).
> >
> > But those dispensable things like "Commit-Count", It must depend
> > on a person's statistics in the git repository. So "cnt:" is meaningless,
> > users' script can let it exit(1).
>
> Perhaps, but at this point what you think (or what I think) does not
> matter.  That was the whole point of letting .cmd script signal Git
> if the result from the invocation should be kept or discarded with
> its exit status.  What would be sufficient here for us to do is to
> agree that it would be good to have a minimal set (perhaps a pair)
> of examples to demonstrate that the script can choose to keep or
> discard a meaningless trailer entry with its exit status.

Yes, I argee.
Due to previous attempts, it seems that such an example is well given:
"Commit-Count" is the trailer that should be discarded.
"Signed-off-by" is the trailer worth be kept.

Thanks.
--
ZheNing Hu

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v12 0/2] [GSOC] trailer: add new .cmd config option
  2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
                                       ` (2 preceding siblings ...)
  2021-04-17 22:26                     ` [PATCH v11 0/2] " Junio C Hamano
@ 2021-05-03 15:41                     ` ZheNing Hu via GitGitGadget
  2021-05-03 15:41                       ` [PATCH v12 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
  2021-05-03 15:41                       ` [PATCH v12 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
  3 siblings, 2 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-05-03 15:41 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu

In https://lore.kernel.org/git/xmqqv99i4ck2.fsf@gitster.g/ Junio and
Christian talked about the problem of using strbuf_replace() to replace
$ARG:

 1. if the user's script has more than one $ARG, only the first one will be
    replaced, which is incorrected.
 2. $ARG is textually replaced without shell syntax, which may result a
    broken command when $ARG include some unmatching single quote, very
    unsafe.

Now pass trailer value as $1 to the trailer command with another
trailer.<token>.cmd config, to solve these above problems.

We are now writing documents that are more readable and correct than before.

Change from last version: Change docs example "Count-count" to "Helped-by".

ZheNing Hu (2):
  [GSOC] docs: correct descript of trailer.<token>.command
  [GSOC] trailer: add new .cmd config option

 Documentation/git-interpret-trailers.txt | 94 ++++++++++++++++++++----
 t/t7513-interpret-trailers.sh            | 84 +++++++++++++++++++++
 trailer.c                                | 35 ++++++---
 3 files changed, 187 insertions(+), 26 deletions(-)


base-commit: 142430338477d9d1bb25be66267225fb58498d92
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-913%2Fadlternative%2Ftrailer-pass-ARG-env-v12
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-913/adlternative/trailer-pass-ARG-env-v12
Pull-Request: https://github.com/gitgitgadget/git/pull/913

Range-diff vs v11:

 1:  34210e5bd3da ! 1:  8129ef6c476b [GSOC] docs: correct description of .command
     @@ Metadata
      Author: ZheNing Hu <adlternative@gmail.com>
      
       ## Commit message ##
     -    [GSOC] docs: correct description of .command
     +    [GSOC] docs: correct descript of trailer.<token>.command
      
          In the original documentation of `trailer.<token>.command`,
          some descriptions are easily misunderstood. So let's modify
 2:  9c0fc91aba24 ! 2:  edb7f1961ddb [GSOC] trailer: add new .cmd config option
     @@ Commit message
          replaced with the value given to the `interpret-trailer`
          command for the token in a '--trailer <token>=<value>' argument.
      
     -    This has two downsides:
     +    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
     @@ Commit message
          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
     @@ Commit message
          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`, the first implicitly executed command
     -    will not pass positional parameters, users can use this
     -    feature to suppress its output.
     +    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>
     @@ Documentation/git-interpret-trailers.txt: leading and trailing whitespace trimme
      -occurrence of substring `$ARG` in the command. This way the
      -command can produce a <value> computed from the <value> passed
      -in the '--trailer <token>=<value>' argument.
     -+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.
     - +
     +-+
      -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>.
     -+It is worth mentioning that the command is first called to add a
     -+trailer with the specified <token> and without positional argument.
     -+Users can make use of this output when they need automatically add
     -+some trailers. On the other hand, users can use a trick to suppress
     -+this output by judging whether the number of positional parameters
     -+is equal to one, if it is true, execute the commands, otherwise exit
     -+with non-zero to suppress the output.
     ++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
       --------
     @@ Documentation/git-interpret-trailers.txt: 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:
     ++* 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/gcount
     ++$ cat ~/bin/glog-find-author
      +#!/bin/sh
     -+if test "$#" != 1
     -+then
     -+	exit 1
     -+else
     -+	test -n "$1" && git shortlog -s --author="$1" HEAD || true
     -+fi
     -+$ git config trailer.cnt.key "Commit-count: "
     -+$ git config trailer.cnt.ifExists "addIfDifferentNeighbor"
     -+$ git config trailer.cnt.cmd "~/bin/gcount"
     -+$ git interpret-trailers --trailer="cnt:Junio" --trailer="cnt:Linus Torvalds"<<EOF
     ++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
      +
     -+Commit-count: 22484     Junio C Hamano
     -+Commit-count: 1117      Linus Torvalds
     ++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 global script `glog-grep`
     ++* 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
     -+if test "$#" != 1
     -+then
     -+	exit 1
     -+else
     -+	test -n "$1" && git log --grep "$1" --pretty=reference -1 || true
     -+fi
     ++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
      +
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	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
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	git config trailer.bug.cmd "echo \"\$1\" is" &&
      +	cat >expected2 <<-EOF &&
      +
     -+	Bug-maker: is
      +	Bug-maker: him is him
      +	Bug-maker: me is me
      +	EOF
     @@ t/t7513-interpret-trailers.sh: test_expect_success 'setup' '
      +	EOF
      +	cat >echoscript <<-EOF &&
      +	#!/bin/sh
     -+	if test "\$#" != 1
     -+	then
     -+		exit 1
     -+	else
     -+		echo who is "\$1"
     -+	fi
     ++	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 'with cmd, $1 and without --trailer' '
     -+	test_when_finished "git config --remove-section trailer.bug" &&
     -+	test_when_finished "git config --remove-section trailer.gub" &&
     -+	git config trailer.bug.key "Bug-maker: " &&
     -+	git config trailer.bug.ifExists "replace" &&
     -+	git config trailer.bug.cmd "./echoscript" &&
     -+	git config trailer.gub.key "Gub-maker: " &&
     -+	git config trailer.gub.ifExists "replace" &&
     -+	git config trailer.gub.cmd "./echoscript2" &&
     -+	cat >expected2 <<-EOF &&
     -+
     -+	Gub-maker: si ohw
     -+	EOF
     -+	cat >echoscript <<-EOF &&
     -+	#!/bin/sh
     -+	if test "\$#" != 1
     -+	then
     -+		exit 1
     -+	else
     -+		echo who is "\$1"
     -+	fi
     -+	EOF
     -+	cat >echoscript2 <<-EOF &&
     -+		echo si ohw "\$1"
     -+	EOF
     -+	chmod +x echoscript &&
     -+	chmod +x echoscript2 &&
     -+	git interpret-trailers >actual2 &&
     -+	test_cmp expected2 actual2
     -+'
      +
       test_expect_success 'without config' '
       	sed -e "s/ Z\$/ /" >expected <<-\EOF &&
     @@ trailer.c: static void free_arg_item(struct arg_item *item)
       	free(item->conf.command);
      +	free(item->conf.cmd);
       	free(item->token);
     --	free(item->value);
     -+	if (item->value)
     -+		FREE_AND_NULL(item->value);
     + 	free(item->value);
       	free(item);
     - }
     - 
      @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	return 1;
       }
     @@ trailer.c: static int check_if_different(struct trailer_item *in_tok,
       	cp.env = local_repo_env;
       	cp.no_stdin = 1;
       	cp.use_shell = 1;
     - 
     - 	if (capture_command(&cp, &buf, 1024)) {
     --		error(_("running trailer command '%s' failed"), cmd.buf);
     - 		strbuf_release(&buf);
     --		result = xstrdup("");
     -+		if (!conf->cmd || arg) {
     -+			error(_("running trailer command '%s' failed"), cmd.buf);
     -+			result = xstrdup("");
     -+		} else
     -+			result = NULL;
     - 	} else {
     - 		strbuf_trim(&buf);
     - 		result = strbuf_detach(&buf, NULL);
      @@ trailer.c: 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) {
     --		const char *arg;
     --		if (arg_tok->value && arg_tok->value[0]) {
      +	if (arg_tok->conf.command || arg_tok->conf.cmd) {
     -+		const char *arg = NULL;
     -+
     -+		if ((arg_tok->value && arg_tok->value[0]) ||
     -+		   (arg_tok->conf.cmd && !arg_tok->value)) {
     + 		const char *arg;
     + 		if (arg_tok->value && arg_tok->value[0]) {
       			arg = arg_tok->value;
     - 		} else {
     - 			if (in_tok && in_tok->value)
      @@ trailer.c: 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);
     --		free((char *)arg);
      +		arg_tok->value = apply_command(&arg_tok->conf, arg);
     -+		if (arg)
     -+			free((char *)arg);
     + 		free((char *)arg);
       	}
       }
     - 
     -@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,
     - 		break;
     - 	case EXISTS_REPLACE:
     - 		apply_item_command(in_tok, arg_tok);
     -+		if (!arg_tok->value) {
     -+			free_arg_item(arg_tok);
     -+			return;
     -+		}
     - 		add_arg_to_input_list(on_tok, arg_tok);
     - 		list_del(&in_tok->list);
     - 		free_trailer_item(in_tok);
     - 		break;
     - 	case EXISTS_ADD:
     - 		apply_item_command(in_tok, arg_tok);
     -+		if (!arg_tok->value) {
     -+			free_arg_item(arg_tok);
     -+			return;
     -+		}
     - 		add_arg_to_input_list(on_tok, arg_tok);
     - 		break;
     - 	case EXISTS_ADD_IF_DIFFERENT:
     - 		apply_item_command(in_tok, arg_tok);
     -+		if (!arg_tok->value) {
     -+			free_arg_item(arg_tok);
     -+			return;
     -+		}
     - 		if (check_if_different(in_tok, arg_tok, 1, head))
     - 			add_arg_to_input_list(on_tok, arg_tok);
     - 		else
     -@@ trailer.c: static void apply_arg_if_exists(struct trailer_item *in_tok,
     - 		break;
     - 	case EXISTS_ADD_IF_DIFFERENT_NEIGHBOR:
     - 		apply_item_command(in_tok, arg_tok);
     -+		if (!arg_tok->value) {
     -+			free_arg_item(arg_tok);
     -+			return;
     -+		}
     - 		if (check_if_different(on_tok, arg_tok, 0, head))
     - 			add_arg_to_input_list(on_tok, arg_tok);
     - 		else
     -@@ trailer.c: static void apply_arg_if_missing(struct list_head *head,
     - 	case MISSING_ADD:
     - 		where = arg_tok->conf.where;
     - 		apply_item_command(NULL, arg_tok);
     -+		if (!arg_tok->value) {
     -+			free_arg_item(arg_tok);
     -+			return;
     -+		}
     - 		to_add = trailer_from_arg(arg_tok);
     - 		if (after_or_end(where))
     - 			list_add_tail(&to_add->list, head);
      @@ trailer.c: 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);
     @@ trailer.c: static int git_trailer_config(const char *conf_key, const char *value
       	case TRAILER_WHERE:
       		if (trailer_set_where(&conf->where, value))
       			warning(_("unknown value '%s' for key '%s'"), value, conf_key);
     -@@ trailer.c: static void process_command_line_args(struct list_head *arg_head,
     - 				     xstrdup(token_from_item(item, NULL)),
     - 				     xstrdup(""),
     - 				     &item->conf, NULL);
     -+		else if (item->conf.cmd)
     -+			add_arg_item(arg_head,
     -+				     xstrdup(token_from_item(item, NULL)),
     -+				     NULL,
     -+				     &item->conf, NULL);
     - 	}
     - 
     - 	/* Add an arg item for each trailer on the command line */

-- 
gitgitgadget

^ permalink raw reply	[flat|nested] 101+ messages in thread

* [PATCH v12 1/2] [GSOC] docs: correct descript of trailer.<token>.command
  2021-05-03 15:41                     ` [PATCH v12 " ZheNing Hu via GitGitGadget
@ 2021-05-03 15:41                       ` ZheNing Hu via GitGitGadget
  2021-05-03 15:41                       ` [PATCH v12 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
  1 sibling, 0 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-05-03 15:41 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

In the original documentation of `trailer.<token>.command`,
some descriptions are easily misunderstood. So let's modify
it to increase its readability.

In addition, clarify that `$ARG` in command can only be
replaced once.

Signed-off-by: ZheNing Hu <adlternative@gmail.com>
---
 Documentation/git-interpret-trailers.txt | 37 ++++++++++++++----------
 1 file changed, 21 insertions(+), 16 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 96ec6499f001..6f2a7a130464 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,25 +232,30 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 trailer.<token>.command::
-	This option can be used to specify a shell command that will
-	be called to automatically add or modify a trailer with the
-	specified <token>.
+	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.
 +
-When this option is specified, the behavior is as if a special
-'<token>=<value>' argument were added at the beginning of the command
-line, where <value> is taken to be the standard output of the
-specified command with any leading and trailing whitespace trimmed
-off.
+When the specified command is first called to add a trailer
+with the specified <token>, 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 command with any
+leading and trailing whitespace trimmed off.
 +
-If the command contains the `$ARG` string, this string will be
-replaced with the <value> part of an existing trailer with the same
-<token>, if any, before the command is launched.
+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 used to replace the first
+occurrence of substring `$ARG` in the command. This way the
+command can produce a <value> computed from the <value> passed
+in the '--trailer <token>=<value>' argument.
 +
-If some '<token>=<value>' arguments are also passed on the command
-line, when a 'trailer.<token>.command' is configured, the command will
-also be executed for each of these arguments. And the <value> part of
-these arguments, if any, will be used to replace the `$ARG` string in
-the command.
+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
 --------
-- 
gitgitgadget


^ permalink raw reply related	[flat|nested] 101+ messages in thread

* [PATCH v12 2/2] [GSOC] trailer: add new .cmd config option
  2021-05-03 15:41                     ` [PATCH v12 " ZheNing Hu via GitGitGadget
  2021-05-03 15:41                       ` [PATCH v12 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
@ 2021-05-03 15:41                       ` ZheNing Hu via GitGitGadget
  1 sibling, 0 replies; 101+ messages in thread
From: ZheNing Hu via GitGitGadget @ 2021-05-03 15:41 UTC (permalink / raw)
  To: git; +Cc: Christian Couder, Junio C Hamano, ZheNing Hu, ZheNing Hu

From: ZheNing Hu <adlternative@gmail.com>

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>
---
 Documentation/git-interpret-trailers.txt | 75 ++++++++++++++++++---
 t/t7513-interpret-trailers.sh            | 84 ++++++++++++++++++++++++
 trailer.c                                | 35 +++++++---
 3 files changed, 175 insertions(+), 19 deletions(-)

diff --git a/Documentation/git-interpret-trailers.txt b/Documentation/git-interpret-trailers.txt
index 6f2a7a130464..1f76e0270053 100644
--- a/Documentation/git-interpret-trailers.txt
+++ b/Documentation/git-interpret-trailers.txt
@@ -232,6 +232,20 @@ trailer.<token>.ifmissing::
 	that option for trailers with the specified <token>.
 
 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:
 	once to automatically add a trailer with the specified <token>, and then
 	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
 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 used to replace the first
-occurrence of substring `$ARG` in the command. This way the
-command can produce a <value> computed from the <value> passed
-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>.
+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
 --------
@@ -338,6 +346,55 @@ subject
 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
   commit that is related, and show how it works:
 +
diff --git a/t/t7513-interpret-trailers.sh b/t/t7513-interpret-trailers.sh
index 6602790b5f4c..04885d0a5e5c 100755
--- a/t/t7513-interpret-trailers.sh
+++ b/t/t7513-interpret-trailers.sh
@@ -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 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' '
 	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" &&
diff --git a/trailer.c b/trailer.c
index be4e9726421c..7c7cb61a945c 100644
--- a/trailer.c
+++ b/trailer.c
@@ -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);
-- 
gitgitgadget

^ permalink raw reply related	[flat|nested] 101+ messages in thread

end of thread, other threads:[~2021-05-03 15:41 UTC | newest]

Thread overview: 101+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2021-03-23 14:53 [PATCH] [GSOC]trailer: change $ARG to environment variable ZheNing Hu via GitGitGadget
2021-03-24 15:42 ` [PATCH v2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
2021-03-24 20:18   ` Junio C Hamano
2021-03-25  1:43     ` ZheNing Hu
2021-03-25 11:53   ` [PATCH v3] " ZheNing Hu via GitGitGadget
2021-03-25 22:28     ` Junio C Hamano
2021-03-26 13:29       ` ZheNing Hu
2021-03-26 16:13     ` [PATCH v4] " ZheNing Hu via GitGitGadget
2021-03-27 18:04       ` Junio C Hamano
2021-03-27 19:53         ` Christian Couder
2021-03-28 10:46           ` ZheNing Hu
2021-03-29  9:04             ` Christian Couder
2021-03-29 13:43               ` ZheNing Hu
2021-03-30  8:45                 ` Christian Couder
2021-03-30 11:22                   ` ZheNing Hu
2021-03-30 15:07                     ` ZheNing Hu
2021-03-30 17:14                       ` Junio C Hamano
2021-03-31  5:14                         ` ZheNing Hu
2021-03-31 18:19                           ` Junio C Hamano
2021-03-31 18:29                             ` Junio C Hamano
2021-04-01  3:56                               ` ZheNing Hu
2021-04-01 19:49                                 ` Junio C Hamano
2021-04-02  2:08                                   ` ZheNing Hu
2021-04-01  3:39                             ` ZheNing Hu
2021-03-31 10:05       ` [PATCH v5 0/2] " ZheNing Hu via GitGitGadget
2021-03-31 10:05         ` [PATCH v5 1/2] [GSOC] run-command: add shell_no_implicit_args option ZheNing Hu via GitGitGadget
2021-04-01  7:22           ` Christian Couder
2021-04-01  9:58             ` ZheNing Hu
2021-03-31 10:05         ` [PATCH v5 2/2] [GSOC]trailer: pass arg as positional parameter ZheNing Hu via GitGitGadget
2021-04-01  7:28         ` [PATCH v5 0/2] " Christian Couder
2021-04-01 10:02           ` ZheNing Hu
2021-04-02 13:26         ` [PATCH v6] [GSOC] trailer: add new trailer.<token>.cmd config option ZheNing Hu via GitGitGadget
2021-04-02 20:48           ` Junio C Hamano
2021-04-03  5:08             ` ZheNing Hu
2021-04-04  5:34               ` Junio C Hamano
2021-04-03  5:51             ` Christian Couder
2021-04-04 23:26               ` Junio C Hamano
2021-04-06  3:47                 ` Christian Couder
2021-04-06  3:52                   ` Christian Couder
2021-04-06  5:16                     ` ZheNing Hu
2021-04-06  5:34                       ` Junio C Hamano
2021-04-06  5:37                       ` Junio C Hamano
2021-04-04  5:43             ` ZheNing Hu
2021-04-04  8:52               ` Christian Couder
2021-04-04  9:53                 ` ZheNing Hu
2021-04-02 23:44           ` Junio C Hamano
2021-04-03  3:22             ` ZheNing Hu
2021-04-03  4:31               ` Junio C Hamano
2021-04-03  5:15                 ` ZheNing Hu
2021-04-04 13:11           ` [PATCH v7] " ZheNing Hu via GitGitGadget
2021-04-06 16:23             ` Christian Couder
2021-04-07  4:51               ` ZheNing Hu
2021-04-09 13:37             ` [PATCH v8 0/2] [GSOC] trailer: add new .cmd " ZheNing Hu via GitGitGadget
2021-04-09 13:37               ` [PATCH v8 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
2021-04-09 19:02                 ` Christian Couder
2021-04-10 13:40                   ` ZheNing Hu
2021-04-09 13:37               ` [PATCH v8 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
2021-04-09 20:18                 ` Christian Couder
2021-04-10 14:09                   ` ZheNing Hu
2021-04-09 19:59               ` [PATCH v8 0/2] " Christian Couder
2021-04-12 16:39               ` [PATCH v9 " ZheNing Hu via GitGitGadget
2021-04-12 16:39                 ` [PATCH v9 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
2021-04-12 20:42                   ` Junio C Hamano
2021-04-16 12:03                     ` Christian Couder
2021-04-17  1:54                       ` Junio C Hamano
2021-04-12 16:39                 ` [PATCH v9 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
2021-04-12 20:51                   ` Junio C Hamano
2021-04-13  7:33                     ` Christian Couder
2021-04-13 12:02                       ` ZheNing Hu
2021-04-13 19:18                         ` Junio C Hamano
2021-04-14 13:27                           ` ZheNing Hu
2021-04-14 20:33                             ` Junio C Hamano
2021-04-15 15:32                               ` ZheNing Hu
2021-04-15 17:41                                 ` Junio C Hamano
2021-04-16 12:54                               ` Christian Couder
2021-04-13 18:14                       ` Junio C Hamano
2021-04-16  8:47                 ` [PATCH v10 0/2] " ZheNing Hu via GitGitGadget
2021-04-16  8:47                   ` [PATCH v10 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
2021-04-16 19:11                     ` Junio C Hamano
2021-04-16  8:47                   ` [PATCH v10 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
2021-04-16 19:13                     ` Junio C Hamano
2021-04-16 19:21                     ` Junio C Hamano
2021-04-16 19:25                       ` Junio C Hamano
2021-04-17  2:58                         ` Junio C Hamano
2021-04-17  3:36                           ` Junio C Hamano
2021-04-17  7:41                             ` ZheNing Hu
2021-04-17  8:11                               ` Junio C Hamano
2021-04-17 15:13                   ` [PATCH v11 0/2] " ZheNing Hu via GitGitGadget
2021-04-17 15:13                     ` [PATCH v11 1/2] [GSOC] docs: correct description of .command ZheNing Hu via GitGitGadget
2021-04-17 15:13                     ` [PATCH v11 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget
2021-04-17 22:26                     ` [PATCH v11 0/2] " Junio C Hamano
2021-04-18  7:47                       ` ZheNing Hu
2021-04-21  0:09                         ` Junio C Hamano
2021-04-21  5:47                           ` ZheNing Hu
2021-04-21 23:40                             ` Junio C Hamano
2021-04-22  9:20                               ` ZheNing Hu
2021-04-27  6:49                                 ` Junio C Hamano
2021-04-27 12:24                                   ` ZheNing Hu
2021-05-03 15:41                     ` [PATCH v12 " ZheNing Hu via GitGitGadget
2021-05-03 15:41                       ` [PATCH v12 1/2] [GSOC] docs: correct descript of trailer.<token>.command ZheNing Hu via GitGitGadget
2021-05-03 15:41                       ` [PATCH v12 2/2] [GSOC] trailer: add new .cmd config option ZheNing Hu via GitGitGadget

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.