mirror of
https://github.com/git/git.git
synced 2026-01-27 10:58:50 +00:00
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 <johannes.schindelin@gmx.de>
This commit is contained in:
@@ -1180,6 +1180,23 @@ of clones and fetches.
|
||||
- any external helpers are named by their protocol (e.g., use
|
||||
`hg` to allow the `git-remote-hg` helper)
|
||||
|
||||
`GIT_REDIRECT_STDIN`::
|
||||
`GIT_REDIRECT_STDOUT`::
|
||||
`GIT_REDIRECT_STDERR`::
|
||||
(EXPERIMENTAL) Windows-only: allow redirecting the standard
|
||||
input/output/error handles. This is particularly useful in
|
||||
multi-threaded applications where the canonical way to pass
|
||||
standard handles via `CreateProcess()` is not an option because
|
||||
it would require the handles to be marked inheritable (and
|
||||
consequently *every* spawned process would inherit them, possibly
|
||||
blocking regular Git operations). The primary intended use case
|
||||
is to use named pipes for communication.
|
||||
+
|
||||
Two special values are supported: `off` will simply close the
|
||||
corresponding standard handle, and if `GIT_REDIRECT_STDERR` is
|
||||
`2>&1`, standard error will be redirected to the same handle as
|
||||
standard output.
|
||||
|
||||
|
||||
Discussion[[Discussion]]
|
||||
------------------------
|
||||
|
||||
@@ -3012,6 +3012,62 @@ static char *wcstoutfdup_startup(char *buffer, const wchar_t *wcs, size_t len)
|
||||
return memcpy(malloc_startup(len), buffer, len);
|
||||
}
|
||||
|
||||
static void maybe_redirect_std_handle(const wchar_t *key, DWORD std_id, int fd,
|
||||
DWORD desired_access, DWORD flags)
|
||||
{
|
||||
DWORD create_flag = fd ? OPEN_ALWAYS : OPEN_EXISTING;
|
||||
wchar_t buf[MAX_LONG_PATH];
|
||||
DWORD max = ARRAY_SIZE(buf);
|
||||
HANDLE handle;
|
||||
DWORD ret = GetEnvironmentVariableW(key, buf, max);
|
||||
|
||||
if (!ret || ret >= max)
|
||||
return;
|
||||
|
||||
/* make sure this does not leak into child processes */
|
||||
SetEnvironmentVariableW(key, NULL);
|
||||
if (!wcscmp(buf, L"off")) {
|
||||
close(fd);
|
||||
handle = GetStdHandle(std_id);
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
return;
|
||||
}
|
||||
if (std_id == STD_ERROR_HANDLE && !wcscmp(buf, L"2>&1")) {
|
||||
handle = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
close(fd);
|
||||
handle = GetStdHandle(std_id);
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
} else {
|
||||
int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
|
||||
SetStdHandle(std_id, handle);
|
||||
dup2(new_fd, fd);
|
||||
/* do *not* close the new_fd: that would close stdout */
|
||||
}
|
||||
return;
|
||||
}
|
||||
handle = CreateFileW(buf, desired_access, 0, NULL, create_flag,
|
||||
flags, NULL);
|
||||
if (handle != INVALID_HANDLE_VALUE) {
|
||||
int new_fd = _open_osfhandle((intptr_t)handle, O_BINARY);
|
||||
SetStdHandle(std_id, handle);
|
||||
dup2(new_fd, fd);
|
||||
close(new_fd);
|
||||
}
|
||||
}
|
||||
|
||||
static void maybe_redirect_std_handles(void)
|
||||
{
|
||||
maybe_redirect_std_handle(L"GIT_REDIRECT_STDIN", STD_INPUT_HANDLE, 0,
|
||||
GENERIC_READ, FILE_ATTRIBUTE_NORMAL);
|
||||
maybe_redirect_std_handle(L"GIT_REDIRECT_STDOUT", STD_OUTPUT_HANDLE, 1,
|
||||
GENERIC_WRITE, FILE_ATTRIBUTE_NORMAL);
|
||||
maybe_redirect_std_handle(L"GIT_REDIRECT_STDERR", STD_ERROR_HANDLE, 2,
|
||||
GENERIC_WRITE, FILE_FLAG_NO_BUFFERING);
|
||||
}
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
|
||||
#ifdef _DEBUG
|
||||
@@ -3050,6 +3106,8 @@ int msc_startup(int argc, wchar_t **w_argv, wchar_t **w_env)
|
||||
_CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
|
||||
#endif
|
||||
|
||||
maybe_redirect_std_handles();
|
||||
|
||||
/* determine size of argv conversion buffer */
|
||||
maxlen = wcslen(_wpgmptr);
|
||||
for (k = 1; k < argc; k++)
|
||||
@@ -3114,6 +3172,8 @@ void mingw_startup(void)
|
||||
wchar_t **wenv, **wargv;
|
||||
_startupinfo si;
|
||||
|
||||
maybe_redirect_std_handles();
|
||||
|
||||
/* get wide char arguments and environment */
|
||||
si.newmode = 0;
|
||||
if (__wgetmainargs(&argc, &wargv, &wenv, _CRT_glob, &si) < 0)
|
||||
|
||||
@@ -422,4 +422,17 @@ test_expect_success MINGW 'core.hidedotfiles = false' '
|
||||
! is_hidden newdir/.git
|
||||
'
|
||||
|
||||
test_expect_success MINGW 'redirect std handles' '
|
||||
GIT_REDIRECT_STDOUT=output.txt git rev-parse --git-dir &&
|
||||
test .git = "$(cat output.txt)" &&
|
||||
test -z "$(GIT_REDIRECT_STDOUT=off git rev-parse --git-dir)" &&
|
||||
test_must_fail env \
|
||||
GIT_REDIRECT_STDOUT=output.txt \
|
||||
GIT_REDIRECT_STDERR="2>&1" \
|
||||
git rev-parse --git-dir --verify refs/invalid &&
|
||||
printf ".git\nfatal: Needed a single revision\n" >expect &&
|
||||
test_cmp expect output.txt
|
||||
'
|
||||
|
||||
|
||||
test_done
|
||||
|
||||
Reference in New Issue
Block a user