Skip to content

Commit 9e0129d

Browse files
committed
Merge branch 'jk/trace-stdin' into HEAD
* jk/trace-stdin: run-command config experiment trace: add GIT_TRACE_STDIN trace: add pid to each output line trace: implement %p placeholder for filenames
2 parents c1e7159 + 260eb92 commit 9e0129d

5 files changed

Lines changed: 139 additions & 5 deletions

File tree

Documentation/git.adoc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -804,7 +804,8 @@ trace messages into this file descriptor.
804804
Alternatively, if the variable is set to an absolute path
805805
(starting with a '/' character), Git will interpret this
806806
as a file path and will try to append the trace messages
807-
to it.
807+
to it. If the filename contains the string `%p`, that string
808+
will be replaced with the PID of the traced process.
808809
+
809810
Unsetting the variable, or setting it to empty, "0" or
810811
"false" (case insensitive) disables trace messages.

git.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -913,6 +913,65 @@ static int run_argv(struct strvec *args)
913913
return done_alias;
914914
}
915915

916+
static int copy_stdin(int in, int out, void *data)
917+
{
918+
struct trace_key *key = data;
919+
while (1) {
920+
char buf[8192];
921+
ssize_t len = xread(in, buf, sizeof(buf));
922+
if (!len)
923+
break;
924+
if (len < 0) {
925+
warning("error reading stdin trace: %s",
926+
strerror(errno));
927+
break;
928+
}
929+
930+
trace_verbatim(key, buf, len);
931+
if (write_in_full(out, buf, len) < 0) {
932+
warning("error writing stdin trace: %s",
933+
strerror(errno));
934+
break;
935+
}
936+
}
937+
close(in);
938+
close(out);
939+
return 0;
940+
}
941+
942+
static void trace_stdin(void)
943+
{
944+
static struct trace_key key = TRACE_KEY_INIT(STDIN);
945+
static struct async async;
946+
947+
if (!trace_want(&key))
948+
return;
949+
950+
memset(&async, 0, sizeof(async));
951+
async.proc = copy_stdin;
952+
async.data = &key;
953+
async.in = dup(0);
954+
async.out = -1;
955+
956+
if (async.in < 0 || start_async(&async) < 0) {
957+
warning("unable to trace stdin: %s", strerror(errno));
958+
return ;
959+
}
960+
961+
/*
962+
* At this point we've handed stdin off to the async process,
963+
* so there we are past the point of no return.
964+
*/
965+
if (dup2(async.out, 0))
966+
die_errno("unable to redirect stdin from async process");
967+
close(async.out);
968+
969+
/*
970+
* leak async; we would know to finish_async() only when we are
971+
* exiting, and there is no point then
972+
*/
973+
}
974+
916975
int cmd_main(int argc, const char **argv)
917976
{
918977
struct strvec args = STRVEC_INIT;
@@ -929,6 +988,7 @@ int cmd_main(int argc, const char **argv)
929988
}
930989

931990
trace_command_performance(argv);
991+
trace_stdin();
932992

933993
/*
934994
* "git-xxxx" is the same as "git xxxx", but we obviously:

run-command.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,9 @@ int start_command(struct child_process *cmd)
680680
int failed_errno;
681681
const char *str;
682682

683+
if (cmd->git_cmd)
684+
trace_config_for(NULL, cmd->args.v[0], &cmd->env);
685+
683686
/*
684687
* In case of errors we must keep the promise to close FDs
685688
* that have been passed in via ->in and ->out.

trace.c

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
#include "quote.h"
3030
#include "setup.h"
3131
#include "trace.h"
32+
#include "strvec.h"
33+
#include "config.h"
3234

3335
struct trace_key trace_default_key = { "GIT_TRACE", 0, 0, 0 };
3436
struct trace_key trace_perf_key = TRACE_KEY_INIT(PERFORMANCE);
@@ -53,15 +55,28 @@ static int get_trace_fd(struct trace_key *key, const char *override_envvar)
5355
else if (strlen(trace) == 1 && isdigit(*trace))
5456
key->fd = atoi(trace);
5557
else if (is_absolute_path(trace)) {
56-
int fd = open(trace, O_WRONLY | O_APPEND | O_CREAT, 0666);
58+
struct strbuf name = STRBUF_INIT;
59+
int fd;
60+
61+
while (strbuf_expand_step(&name, &trace)) {
62+
if (skip_prefix(trace, "%", &trace))
63+
strbuf_addch(&name, '%');
64+
else if (skip_prefix(trace, "p", &trace))
65+
strbuf_addf(&name, "%lu", (unsigned long)getpid());
66+
else
67+
strbuf_addch(&name, '%');
68+
}
69+
70+
fd = open(name.buf, O_WRONLY | O_APPEND | O_CREAT, 0666);
5771
if (fd == -1) {
5872
warning("could not open '%s' for tracing: %s",
59-
trace, strerror(errno));
73+
name.buf, strerror(errno));
6074
trace_disable(key);
6175
} else {
6276
key->fd = fd;
6377
key->need_close = 1;
6478
}
79+
strbuf_release(&name);
6580
} else {
6681
warning("unknown trace value for '%s': %s\n"
6782
" If you want to trace into a file, then please set %s\n"
@@ -116,8 +131,9 @@ static int prepare_trace_line(const char *file, int line,
116131
localtime_r(&secs, &tm);
117132
strbuf_addf(buf, "%02d:%02d:%02d.%06ld %s:%d", tm.tm_hour, tm.tm_min,
118133
tm.tm_sec, (long) tv.tv_usec, file, line);
119-
/* align trace output (column 40 catches most files names in git) */
120-
while (buf->len < 40)
134+
strbuf_addf(buf, "[pid=%lu] ", (unsigned long)getpid());
135+
/* align trace output (column 50 catches most files names in git) */
136+
while (buf->len < 50)
121137
strbuf_addch(buf, ' ');
122138

123139
return 1;
@@ -428,3 +444,47 @@ void trace_command_performance(const char **argv)
428444
sq_quote_argv_pretty(&command_line, argv);
429445
trace_performance_enter();
430446
}
447+
448+
struct trace_config_data {
449+
const char *want_cmd;
450+
struct strvec *out;
451+
};
452+
453+
static int trace_config_cb(const char *var, const char *value,
454+
const struct config_context *ctx UNUSED, void *vdata)
455+
{
456+
struct trace_config_data *data = vdata;
457+
const char *have_cmd, *key;
458+
size_t have_len;
459+
460+
if (!parse_config_key(var, "trace", &have_cmd, &have_len, &key) &&
461+
have_cmd &&
462+
!strncmp(data->want_cmd, have_cmd, have_len) &&
463+
data->want_cmd[have_len] == '\0') {
464+
struct strbuf buf = STRBUF_INIT;
465+
466+
strbuf_addstr(&buf, "GIT_TRACE_");
467+
while (*key)
468+
strbuf_addch(&buf, toupper(*key++));
469+
470+
/*
471+
* Environment always takes precedence over config, so do not
472+
* override existing variables. We cannot rely on setenv()'s
473+
* overwrite flag here, because we may pass the list off to
474+
* a spawn() implementation, which always overwrites.
475+
*/
476+
if (!getenv(buf.buf))
477+
strvec_pushf(data->out, "%s=%s", buf.buf, value);
478+
479+
strbuf_release(&buf);
480+
}
481+
return 0;
482+
}
483+
484+
void trace_config_for(struct repository *r, const char *cmd, struct strvec *out)
485+
{
486+
struct trace_config_data data;
487+
data.want_cmd = cmd;
488+
data.out = out;
489+
repo_config(r, trace_config_cb, &data);
490+
}

trace.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33

44
#include "strbuf.h"
55

6+
struct repository;
7+
struct strvec;
8+
69
/**
710
* The trace API can be used to print debug messages to stderr or a file. Trace
811
* code is inactive unless explicitly enabled by setting `GIT_TRACE*` environment
@@ -127,6 +130,13 @@ void trace_command_performance(const char **argv);
127130
void trace_verbatim(struct trace_key *key, const void *buf, unsigned len);
128131
uint64_t trace_performance_enter(void);
129132

133+
/**
134+
* Load any trace-related config for git command "cmd", and insert the matching
135+
* environment variables into "out", which is suitable for use by run-command
136+
* and friends.
137+
*/
138+
void trace_config_for(struct repository *r, const char *cmd, struct strvec *out);
139+
130140
/*
131141
* TRACE_CONTEXT may be set to __FUNCTION__ if the compiler supports it. The
132142
* default is __FILE__, as it is consistent with assert(), and static function

0 commit comments

Comments
 (0)