Merge branch 'js/mingw-redirect-std-handles' into maint
authorJunio C Hamano <gitster@pobox.com>
Wed, 15 Nov 2017 03:05:03 +0000 (12:05 +0900)
committerJunio C Hamano <gitster@pobox.com>
Wed, 15 Nov 2017 03:05:03 +0000 (12:05 +0900)
MinGW updates.

* js/mingw-redirect-std-handles:
mingw: document the standard handle redirection
mingw: optionally redirect stderr/stdout via the same handle
mingw: add experimental feature to redirect standard handles

Documentation/git.txt
compat/mingw.c
t/t0001-init.sh
index 7a1d629ca068059d274da66728eb7f886b43081d..463b0eb0f5c3c8e694a509d3e06aee29056f970d 100644 (file)
@@ -709,6 +709,24 @@ of clones and fetches.
        the background which do not want to cause lock contention with
        other operations on the repository.  Defaults to `1`.
 
+`GIT_REDIRECT_STDIN`::
+`GIT_REDIRECT_STDOUT`::
+`GIT_REDIRECT_STDERR`::
+       Windows-only: allow redirecting the standard input/output/error
+       handles to paths specified by the environment variables. 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
+       (e.g. `\\.\pipe\my-git-stdin-123`).
++
+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]]
 ------------------------
 
index 8b6fa0db446aee9888ff6484560131143c7e22e5..2d44d21aca8d31f67b16cb9a90245b4db526ff76 100644 (file)
@@ -2139,6 +2139,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_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);
+}
+
 void mingw_startup(void)
 {
        int i, maxlen, argc;
@@ -2146,6 +2202,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)
index 86c1a51654fa6c1b049a313fe8d880c6a266ee62..c413bff9cf1f3a79ef494b39844c42d3a8c877f1 100755 (executable)
@@ -453,4 +453,16 @@ test_expect_success 're-init from a linked worktree' '
        )
 '
 
+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