From e35f11c29391e557964a39204fae6b89afab6a2a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 15 Jan 2018 17:59:43 +0700 Subject: [PATCH 1/7] sq_quote_argv: drop maxlen parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No caller passes anything but "0" for this parameter, which requests that the function ignore it completely. In fact, in all of history there was only one such caller, and it went away in 7f51f8bc2b (alias: use run_command api to execute aliases, 2011-01-07). Signed-off-by: Jeff King Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- builtin/am.c | 2 +- builtin/rev-parse.c | 4 ++-- quote.c | 4 +--- quote.h | 2 +- trace.c | 4 ++-- 5 files changed, 7 insertions(+), 9 deletions(-) diff --git a/builtin/am.c b/builtin/am.c index acfe9d3c8c..5bdd2d7578 100644 --- a/builtin/am.c +++ b/builtin/am.c @@ -1061,7 +1061,7 @@ static void am_setup(struct am_state *state, enum patch_format patch_format, } write_state_text(state, "scissors", str); - sq_quote_argv(&sb, state->git_apply_opts.argv, 0); + sq_quote_argv(&sb, state->git_apply_opts.argv); write_state_text(state, "apply-opt", sb.buf); if (state->rebasing) diff --git a/builtin/rev-parse.c b/builtin/rev-parse.c index 74aa644cbb..96d06a5d01 100644 --- a/builtin/rev-parse.c +++ b/builtin/rev-parse.c @@ -516,7 +516,7 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix) PARSE_OPT_SHELL_EVAL); strbuf_addstr(&parsed, " --"); - sq_quote_argv(&parsed, argv, 0); + sq_quote_argv(&parsed, argv); puts(parsed.buf); return 0; } @@ -526,7 +526,7 @@ static int cmd_sq_quote(int argc, const char **argv) struct strbuf buf = STRBUF_INIT; if (argc) - sq_quote_argv(&buf, argv, 0); + sq_quote_argv(&buf, argv); printf("%s\n", buf.buf); strbuf_release(&buf); diff --git a/quote.c b/quote.c index de2922ddd6..b2970da627 100644 --- a/quote.c +++ b/quote.c @@ -56,7 +56,7 @@ void sq_quotef(struct strbuf *dst, const char *fmt, ...) strbuf_release(&src); } -void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) +void sq_quote_argv(struct strbuf *dst, const char **argv) { int i; @@ -65,8 +65,6 @@ void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen) for (i = 0; argv[i]; ++i) { strbuf_addch(dst, ' '); sq_quote_buf(dst, argv[i]); - if (maxlen && dst->len > maxlen) - die("Too many or long arguments"); } } diff --git a/quote.h b/quote.h index 66f5644aa2..48a75a416b 100644 --- a/quote.h +++ b/quote.h @@ -30,7 +30,7 @@ struct strbuf; */ extern void sq_quote_buf(struct strbuf *, const char *src); -extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen); +extern void sq_quote_argv(struct strbuf *, const char **argv); extern void sq_quotef(struct strbuf *, const char *fmt, ...); /* This unwraps what sq_quote() produces in place, but returns diff --git a/trace.c b/trace.c index b7530b51a9..fa9174fc4b 100644 --- a/trace.c +++ b/trace.c @@ -157,7 +157,7 @@ static void trace_argv_vprintf_fl(const char *file, int line, strbuf_vaddf(&buf, format, ap); - sq_quote_argv(&buf, argv, 0); + sq_quote_argv(&buf, argv); print_trace_line(&trace_default_key, &buf); } @@ -426,6 +426,6 @@ void trace_command_performance(const char **argv) atexit(print_command_performance_atexit); strbuf_reset(&command_line); - sq_quote_argv(&command_line, argv, 0); + sq_quote_argv(&command_line, argv); command_start_time = getnanotime(); } From 1fbdab21bb452ca4732bf088539247047465b99d Mon Sep 17 00:00:00 2001 From: Jeff King Date: Mon, 15 Jan 2018 17:59:44 +0700 Subject: [PATCH 2/7] trace: avoid unnecessary quoting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Trace output which contains arbitrary strings (e.g., the arguments to commands which we are running) is always passed through sq_quote_buf(). That function always adds single-quotes, even if the output consists of vanilla characters. This can make the output a bit hard to read. Let's avoid the quoting if there are no characters which a shell would interpret. Trace output doesn't necessarily need to be shell-compatible, but: - the shell language is a good ballpark for what humans consider readable (well, humans versed in command line tools) - the run_command bits can be cut-and-pasted to a shell, and we'll keep that property - it covers any cases which would make the output visually ambiguous (e.g., embedded whitespace or quotes) Signed-off-by: Jeff King Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- quote.c | 26 ++++++++++++++++++++++++++ quote.h | 8 ++++++++ trace.c | 4 ++-- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/quote.c b/quote.c index b2970da627..37d2686865 100644 --- a/quote.c +++ b/quote.c @@ -43,6 +43,22 @@ void sq_quote_buf(struct strbuf *dst, const char *src) free(to_free); } +void sq_quote_buf_pretty(struct strbuf *dst, const char *src) +{ + static const char ok_punct[] = "+,-./:=@_^"; + const char *p; + + for (p = src; *p; p++) { + if (!isalpha(*p) && !isdigit(*p) && !strchr(ok_punct, *p)) { + sq_quote_buf(dst, src); + return; + } + } + + /* if we get here, we did not need quoting */ + strbuf_addstr(dst, src); +} + void sq_quotef(struct strbuf *dst, const char *fmt, ...) { struct strbuf src = STRBUF_INIT; @@ -68,6 +84,16 @@ void sq_quote_argv(struct strbuf *dst, const char **argv) } } +void sq_quote_argv_pretty(struct strbuf *dst, const char **argv) +{ + int i; + + for (i = 0; argv[i]; i++) { + strbuf_addch(dst, ' '); + sq_quote_buf_pretty(dst, argv[i]); + } +} + static char *sq_dequote_step(char *arg, char **next) { char *dst = arg; diff --git a/quote.h b/quote.h index 48a75a416b..ea992dcc91 100644 --- a/quote.h +++ b/quote.h @@ -33,6 +33,14 @@ extern void sq_quote_buf(struct strbuf *, const char *src); extern void sq_quote_argv(struct strbuf *, const char **argv); extern void sq_quotef(struct strbuf *, const char *fmt, ...); +/* + * These match their non-pretty variants, except that they avoid + * quoting when there are no exotic characters. These should only be used for + * human-readable output, as sq_dequote() is not smart enough to dequote it. + */ +void sq_quote_buf_pretty(struct strbuf *, const char *src); +void sq_quote_argv_pretty(struct strbuf *, const char **argv); + /* This unwraps what sq_quote() produces in place, but returns * NULL if the input does not look like what sq_quote would have * produced. diff --git a/trace.c b/trace.c index fa9174fc4b..9784915be1 100644 --- a/trace.c +++ b/trace.c @@ -157,7 +157,7 @@ static void trace_argv_vprintf_fl(const char *file, int line, strbuf_vaddf(&buf, format, ap); - sq_quote_argv(&buf, argv); + sq_quote_argv_pretty(&buf, argv); print_trace_line(&trace_default_key, &buf); } @@ -426,6 +426,6 @@ void trace_command_performance(const char **argv) atexit(print_command_performance_atexit); strbuf_reset(&command_line); - sq_quote_argv(&command_line, argv); + sq_quote_argv_pretty(&command_line, argv); command_start_time = getnanotime(); } From 33011e769c2dd3eaa63e5b480e5845c5d370ab13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Mon, 15 Jan 2018 17:59:45 +0700 Subject: [PATCH 3/7] trace.c: move strbuf_release() out of print_trace_line() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function is about printing a trace line, not releasing the buffer it receives too. Move strbuf_release() back outside. This makes it easier to see how strbuf is managed. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- trace.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/trace.c b/trace.c index 9784915be1..7f3b08e148 100644 --- a/trace.c +++ b/trace.c @@ -131,7 +131,6 @@ static void print_trace_line(struct trace_key *key, struct strbuf *buf) { strbuf_complete_line(buf); trace_write(key, buf->buf, buf->len); - strbuf_release(buf); } static void trace_vprintf_fl(const char *file, int line, struct trace_key *key, @@ -144,6 +143,7 @@ static void trace_vprintf_fl(const char *file, int line, struct trace_key *key, strbuf_vaddf(&buf, format, ap); print_trace_line(key, &buf); + strbuf_release(&buf); } static void trace_argv_vprintf_fl(const char *file, int line, @@ -159,6 +159,7 @@ static void trace_argv_vprintf_fl(const char *file, int line, sq_quote_argv_pretty(&buf, argv); print_trace_line(&trace_default_key, &buf); + strbuf_release(&buf); } void trace_strbuf_fl(const char *file, int line, struct trace_key *key, @@ -171,6 +172,7 @@ void trace_strbuf_fl(const char *file, int line, struct trace_key *key, strbuf_addbuf(&buf, data); print_trace_line(key, &buf); + strbuf_release(&buf); } static void trace_performance_vprintf_fl(const char *file, int line, @@ -190,6 +192,7 @@ static void trace_performance_vprintf_fl(const char *file, int line, } print_trace_line(&trace_perf_key, &buf); + strbuf_release(&buf); } #ifndef HAVE_VARIADIC_MACROS From e73dd78699c4a0a84960c3519e6bdc6e49009740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 18 Jan 2018 16:45:09 +0700 Subject: [PATCH 4/7] run-command.c: introduce trace_run_command() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is the same as the old code that uses trace_argv_printf() in run-command.c. This function will be improved in later patches to print more information from struct child_process. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- run-command.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/run-command.c b/run-command.c index 31fc5ea86e..eadba8cb0d 100644 --- a/run-command.c +++ b/run-command.c @@ -6,6 +6,7 @@ #include "thread-utils.h" #include "strbuf.h" #include "string-list.h" +#include "quote.h" void child_process_init(struct child_process *child) { @@ -556,6 +557,20 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) return code; } +static void trace_run_command(const struct child_process *cp) +{ + struct strbuf buf = STRBUF_INIT; + + if (!trace_want(&trace_default_key)) + return; + + strbuf_addf(&buf, "trace: run_command:"); + sq_quote_argv_pretty(&buf, cp->argv); + + trace_printf("%s", buf.buf); + strbuf_release(&buf); +} + int start_command(struct child_process *cmd) { int need_in, need_out, need_err; @@ -624,7 +639,8 @@ fail_pipe: cmd->err = fderr[0]; } - trace_argv_printf(cmd->argv, "trace: run_command:"); + trace_run_command(cmd); + fflush(NULL); #ifndef GIT_WINDOWS_NATIVE From 21dfc5e08fec74ded0ab4206bca0927a38feb882 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 18 Jan 2018 16:45:10 +0700 Subject: [PATCH 5/7] run-command.c: print program 'git' when tracing git_cmd mode MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We normally print full command line, including the program and its argument. When git_cmd is set, we have a special code path to run the right "git" program and child_process.argv[0] will not contain the program name anymore. As a result, we print just the command arguments. I thought it was a regression when the code was refactored and git_cmd added, but apparently it's not. git_cmd mode was introduced before tracing was added in 8852f5d704 (run_command(): respect GIT_TRACE - 2008-07-07) so it's more like an oversight in 8852f5d704. Fix it, print the program name "git" in git_cmd mode. It's nice to have now. But it will be more important later when we start to print env variables too, in shell syntax. The lack of a program name would look confusing then. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- run-command.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/run-command.c b/run-command.c index eadba8cb0d..326c33e3cd 100644 --- a/run-command.c +++ b/run-command.c @@ -565,6 +565,8 @@ static void trace_run_command(const struct child_process *cp) return; strbuf_addf(&buf, "trace: run_command:"); + if (cp->git_cmd) + strbuf_addstr(&buf, " git"); sq_quote_argv_pretty(&buf, cp->argv); trace_printf("%s", buf.buf); From c61a975df1d3d5e448622f951e6dd584ee21ebf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 18 Jan 2018 16:45:11 +0700 Subject: [PATCH 6/7] run-command.c: print env vars in trace_run_command() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Occasionally submodule code could execute new commands with GIT_DIR set to some submodule. GIT_TRACE prints just the command line which makes it hard to tell that it's not really executed on this repository. Print the env delta (compared to parent environment) in this case. Helped-by: Junio C Hamano Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- run-command.c | 63 +++++++++++++++++++++++++++++++++++++ t/helper/test-run-command.c | 9 ++++++ t/t0061-run-command.sh | 37 ++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/run-command.c b/run-command.c index 326c33e3cd..1301b878c7 100644 --- a/run-command.c +++ b/run-command.c @@ -557,6 +557,63 @@ static int wait_or_whine(pid_t pid, const char *argv0, int in_signal) return code; } +static void trace_add_env(struct strbuf *dst, const char *const *deltaenv) +{ + struct string_list envs = STRING_LIST_INIT_DUP; + const char *const *e; + int i; + int printed_unset = 0; + + /* Last one wins, see run-command.c:prep_childenv() for context */ + for (e = deltaenv; e && *e; e++) { + struct strbuf key = STRBUF_INIT; + char *equals = strchr(*e, '='); + + if (equals) { + strbuf_add(&key, *e, equals - *e); + string_list_insert(&envs, key.buf)->util = equals + 1; + } else { + string_list_insert(&envs, *e)->util = NULL; + } + strbuf_release(&key); + } + + /* "unset X Y...;" */ + for (i = 0; i < envs.nr; i++) { + const char *var = envs.items[i].string; + const char *val = envs.items[i].util; + + if (val || !getenv(var)) + continue; + + if (!printed_unset) { + strbuf_addstr(dst, " unset"); + printed_unset = 1; + } + strbuf_addf(dst, " %s", var); + } + if (printed_unset) + strbuf_addch(dst, ';'); + + /* ... followed by "A=B C=D ..." */ + for (i = 0; i < envs.nr; i++) { + const char *var = envs.items[i].string; + const char *val = envs.items[i].util; + const char *oldval; + + if (!val) + continue; + + oldval = getenv(var); + if (oldval && !strcmp(val, oldval)) + continue; + + strbuf_addf(dst, " %s=", var); + sq_quote_buf_pretty(dst, val); + } + string_list_clear(&envs, 0); +} + static void trace_run_command(const struct child_process *cp) { struct strbuf buf = STRBUF_INIT; @@ -565,6 +622,12 @@ static void trace_run_command(const struct child_process *cp) return; strbuf_addf(&buf, "trace: run_command:"); + /* + * The caller is responsible for initializing cp->env from + * cp->env_array if needed. We only check one place. + */ + if (cp->env) + trace_add_env(&buf, cp->env); if (cp->git_cmd) strbuf_addstr(&buf, " git"); sq_quote_argv_pretty(&buf, cp->argv); diff --git a/t/helper/test-run-command.c b/t/helper/test-run-command.c index d24d157379..153342e44d 100644 --- a/t/helper/test-run-command.c +++ b/t/helper/test-run-command.c @@ -54,6 +54,15 @@ int cmd_main(int argc, const char **argv) struct child_process proc = CHILD_PROCESS_INIT; int jobs; + if (argc < 3) + return 1; + while (!strcmp(argv[1], "env")) { + if (!argv[2]) + die("env specifier without a value"); + argv_array_push(&proc.env_array, argv[2]); + argv += 2; + argc -= 2; + } if (argc < 3) return 1; proc.argv = (const char **)argv + 2; diff --git a/t/t0061-run-command.sh b/t/t0061-run-command.sh index e4739170aa..24c92b6cd7 100755 --- a/t/t0061-run-command.sh +++ b/t/t0061-run-command.sh @@ -141,4 +141,41 @@ test_expect_success 'run_command outputs ' ' test_cmp expect actual ' +test_trace () { + expect="$1" + shift + GIT_TRACE=1 test-run-command "$@" run-command true 2>&1 >/dev/null | \ + sed 's/.* run_command: //' >actual && + echo "$expect true" >expect && + test_cmp expect actual +} + +test_expect_success 'GIT_TRACE with environment variables' ' + test_trace "abc=1 def=2" env abc=1 env def=2 && + test_trace "abc=2" env abc env abc=1 env abc=2 && + test_trace "abc=2" env abc env abc=2 && + ( + abc=1 && export abc && + test_trace "def=1" env abc=1 env def=1 + ) && + ( + abc=1 && export abc && + test_trace "def=1" env abc env abc=1 env def=1 + ) && + test_trace "def=1" env non-exist env def=1 && + test_trace "abc=2" env abc=1 env abc env abc=2 && + ( + abc=1 def=2 && export abc def && + test_trace "unset abc def;" env abc env def + ) && + ( + abc=1 def=2 && export abc def && + test_trace "unset def; abc=3" env abc env def env abc=3 + ) && + ( + abc=1 && export abc && + test_trace "unset abc;" env abc=2 env abc + ) +' + test_done From 090a09272ac3bead828519b66eb62d53b53ecf7d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Th=C3=A1i=20Ng=E1=BB=8Dc=20Duy?= Date: Thu, 18 Jan 2018 16:45:12 +0700 Subject: [PATCH 7/7] run-command.c: print new cwd in trace_run_command() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If a command sets a new env variable GIT_DIR=.git, we need more context to know where that '.git' is related to. Signed-off-by: Nguyễn Thái Ngọc Duy Signed-off-by: Junio C Hamano --- run-command.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/run-command.c b/run-command.c index 1301b878c7..a483d5904a 100644 --- a/run-command.c +++ b/run-command.c @@ -622,6 +622,11 @@ static void trace_run_command(const struct child_process *cp) return; strbuf_addf(&buf, "trace: run_command:"); + if (cp->dir) { + strbuf_addstr(&buf, " cd "); + sq_quote_buf_pretty(&buf, cp->dir); + strbuf_addch(&buf, ';'); + } /* * The caller is responsible for initializing cp->env from * cp->env_array if needed. We only check one place.