Skip to content

Commit 579907a

Browse files
committed
Merge branch 'redirect-std-handles'
This topic branch introduces a highly-experimental feature allowing to override stdin/stdout/stderr by setting environment variables e.g. to named pipes, solving a problem in highly multi-threaded applications where inheritable handles could cause blocked Git operations. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 0c402ac + cde7a27 commit 579907a

File tree

3 files changed

+90
-0
lines changed

3 files changed

+90
-0
lines changed

Documentation/git.txt

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1180,6 +1180,23 @@ of clones and fetches.
11801180
- any external helpers are named by their protocol (e.g., use
11811181
`hg` to allow the `git-remote-hg` helper)
11821182

1183+
`GIT_REDIRECT_STDIN`::
1184+
`GIT_REDIRECT_STDOUT`::
1185+
`GIT_REDIRECT_STDERR`::
1186+
(EXPERIMENTAL) Windows-only: allow redirecting the standard
1187+
input/output/error handles. This is particularly useful in
1188+
multi-threaded applications where the canonical way to pass
1189+
standard handles via `CreateProcess()` is not an option because
1190+
it would require the handles to be marked inheritable (and
1191+
consequently *every* spawned process would inherit them, possibly
1192+
blocking regular Git operations). The primary intended use case
1193+
is to use named pipes for communication.
1194+
+
1195+
Two special values are supported: `off` will simply close the
1196+
corresponding standard handle, and if `GIT_REDIRECT_STDERR` is
1197+
`2>&1`, standard error will be redirected to the same handle as
1198+
standard output.
1199+
11831200

11841201
Discussion[[Discussion]]
11851202
------------------------

compat/mingw.c

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3012,6 +3012,62 @@ static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len)
30123012
return memcpy(malloc_startup(len), buffer, len);
30133013
}
30143014

3015+
static void maybe_redirect_std_handle(const wchar_t *key, DWORD std_id, int fd,
3016+
DWORD desired_access, DWORD flags)
3017+
{
3018+
DWORD create_flag = fd ? OPEN_ALWAYS : OPEN_EXISTING;
3019+
wchar_t buf[MAX_LONG_PATH];
3020+
DWORD max = ARRAY_SIZE(buf);
3021+
HANDLE handle;
3022+
DWORD ret = GetEnvironmentVariableW(key, buf, max);
3023+
3024+
if (!ret || ret >= max)
3025+
return;
3026+
3027+
/* make sure this does not leak into child processes */
3028+
SetEnvironmentVariableW(key, NULL);
3029+
if (!wcscmp(buf, L"off")) {
3030+
close(fd);
3031+
handle = GetStdHandle(std_id);
3032+
if (handle != INVALID_HANDLE_VALUE)
3033+
CloseHandle(handle);
3034+
return;
3035+
}
3036+
if (std_id == STD_ERROR_HANDLE && !wcscmp(buf, L"2>&1")) {
3037+
handle = GetStdHandle(STD_OUTPUT_HANDLE);
3038+
if (handle == INVALID_HANDLE_VALUE) {
3039+
close(fd);
3040+
handle = GetStdHandle(std_id);
3041+
if (handle != INVALID_HANDLE_VALUE)
3042+
CloseHandle(handle);
3043+
} else {
3044+
int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
3045+
SetStdHandle(std_id, handle);
3046+
dup2(new_fd, fd);
3047+
/* do *not* close the new_fd: that would close stdout */
3048+
}
3049+
return;
3050+
}
3051+
handle = CreateFileW(buf, desired_access, 0, NULL, create_flag,
3052+
flags, NULL);
3053+
if (handle != INVALID_HANDLE_VALUE) {
3054+
int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
3055+
SetStdHandle(std_id, handle);
3056+
dup2(new_fd, fd);
3057+
close(new_fd);
3058+
}
3059+
}
3060+
3061+
static void maybe_redirect_std_handles(void)
3062+
{
3063+
maybe_redirect_std_handle(L"GIT_REDIRECT_STDIN", STD_INPUT_HANDLE, 0,
3064+
GENERIC_READ, FILE_ATTRIBUTE_NORMAL);
3065+
maybe_redirect_std_handle(L"GIT_REDIRECT_STDOUT", STD_OUTPUT_HANDLE, 1,
3066+
GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL);
3067+
maybe_redirect_std_handle(L"GIT_REDIRECT_STDERR", STD_ERROR_HANDLE, 2,
3068+
GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
3069+
}
3070+
30153071
#if defined(_MSC_VER)
30163072

30173073
#ifdef _DEBUG
@@ -3050,6 +3106,8 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env)
30503106
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
30513107
#endif
30523108

3109+
maybe_redirect_std_handles();
3110+
30533111
/* determine size of argv conversion buffer */
30543112
maxlen = wcslen(_wpgmptr);
30553113
for (k = 1; k < argc; k++)
@@ -3114,6 +3172,8 @@ void mingw_startup(void)
31143172
wchar_t **wenv, **wargv;
31153173
_startupinfo si;
31163174

3175+
maybe_redirect_std_handles();
3176+
31173177
/* get wide char arguments and environment */
31183178
si.newmode = 0;
31193179
if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0)

t/t0001-init.sh

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,17 @@ test_expect_success MINGW 'core.hidedotfiles = false' '
422422
! is_hidden newdir/.git
423423
'
424424

425+
test_expect_success MINGW 'redirect std handles' '
426+
GIT_REDIRECT_STDOUT=output.txt git rev-parse --git-dir &&
427+
test .git = "$(cat output.txt)" &&
428+
test -z "$(GIT_REDIRECT_STDOUT=off git rev-parse --git-dir)" &&
429+
test_must_fail env \
430+
GIT_REDIRECT_STDOUT=output.txt \
431+
GIT_REDIRECT_STDERR="2>&1" \
432+
git rev-parse --git-dir --verify refs/invalid &&
433+
printf ".git\nfatal: Needed a single revision\n" >expect &&
434+
test_cmp expect output.txt
435+
'
436+
437+
425438
test_done

0 commit comments

Comments
 (0)