Merge branch 'js/mingw-isatty'
authorJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:46 +0000 (00:11 -0800)
committerJunio C Hamano <gitster@pobox.com>
Tue, 27 Dec 2016 08:11:46 +0000 (00:11 -0800)
Update the isatty() emulation for Windows by updating the previous
hack that depended on internals of (older) MSVC runtime.

* js/mingw-isatty:
mingw: replace isatty() hack
mingw: fix colourization on Cygwin pseudo terminals
mingw: adjust is_console() to work with stdin

1  2 
compat/winansi.c
diff --combined compat/winansi.c
index 97d84a96ededd9446ad409db2a410f9a03879e9b,477209fce7beedca3159a8140d2ac9e3a69400f3..3c9ed3cfe07c1f3b58f5400af861fb8ae44c3fdb
@@@ -6,9 -6,12 +6,12 @@@
  #include "../git-compat-util.h"
  #include <wingdi.h>
  #include <winreg.h>
+ #include "win32.h"
  
- /* In this file, we actually want to use Windows' own isatty(). */
- #undef isatty
+ static int fd_is_interactive[3] = { 0, 0, 0 };
+ #define FD_CONSOLE 0x1
+ #define FD_SWAPPED 0x2
+ #define FD_MSYS    0x4
  
  /*
   ANSI codes used by git: m, K
@@@ -84,6 -87,7 +87,7 @@@ static void warn_if_raster_font(void
  static int is_console(int fd)
  {
        CONSOLE_SCREEN_BUFFER_INFO sbi;
+       DWORD mode;
        HANDLE hcon;
  
        static int initialized = 0;
                return 0;
  
        /* check if its a handle to a console output screen buffer */
-       if (!GetConsoleScreenBufferInfo(hcon, &sbi))
+       if (!fd) {
+               if (!GetConsoleMode(hcon, &mode))
+                       return 0;
+       } else if (!GetConsoleScreenBufferInfo(hcon, &sbi))
                return 0;
  
+       if (fd >= 0 && fd <= 2)
+               fd_is_interactive[fd] |= FD_CONSOLE;
        /* initialize attributes */
        if (!initialized) {
                console = hcon;
@@@ -462,76 -472,50 +472,50 @@@ static HANDLE duplicate_handle(HANDLE h
        return hresult;
  }
  
- /*
-  * Make MSVCRT's internal file descriptor control structure accessible
-  * so that we can tweak OS handles and flags directly (we need MSVCRT
-  * to treat our pipe handle as if it were a console).
-  *
-  * We assume that the ioinfo structure (exposed by MSVCRT.dll via
-  * __pioinfo) starts with the OS handle and the flags. The exact size
-  * varies between MSVCRT versions, so we try different sizes until
-  * toggling the FDEV bit of _pioinfo(1)->osflags is reflected in
-  * isatty(1).
-  */
- typedef struct {
-       HANDLE osfhnd;
-       char osflags;
- } ioinfo;
- extern __declspec(dllimport) ioinfo *__pioinfo[];
- static size_t sizeof_ioinfo = 0;
- #define IOINFO_L2E 5
- #define IOINFO_ARRAY_ELTS (1 << IOINFO_L2E)
- #define FPIPE 0x08
- #define FDEV  0x40
- static inline ioinfo* _pioinfo(int fd)
- {
-       return (ioinfo*)((char*)__pioinfo[fd >> IOINFO_L2E] +
-                       (fd & (IOINFO_ARRAY_ELTS - 1)) * sizeof_ioinfo);
- }
- static int init_sizeof_ioinfo(void)
- {
-       int istty, wastty;
-       /* don't init twice */
-       if (sizeof_ioinfo)
-               return sizeof_ioinfo >= 256;
-       sizeof_ioinfo = sizeof(ioinfo);
-       wastty = isatty(1);
-       while (sizeof_ioinfo < 256) {
-               /* toggle FDEV flag, check isatty, then toggle back */
-               _pioinfo(1)->osflags ^= FDEV;
-               istty = isatty(1);
-               _pioinfo(1)->osflags ^= FDEV;
-               /* return if we found the correct size */
-               if (istty != wastty)
-                       return 0;
-               sizeof_ioinfo += sizeof(void*);
-       }
-       error("Tweaking file descriptors doesn't work with this MSVCRT.dll");
-       return 1;
- }
  static HANDLE swap_osfhnd(int fd, HANDLE new_handle)
  {
-       ioinfo *pioinfo;
-       HANDLE old_handle;
-       /* init ioinfo size if we haven't done so */
-       if (init_sizeof_ioinfo())
-               return INVALID_HANDLE_VALUE;
-       /* get ioinfo pointer and change the handles */
-       pioinfo = _pioinfo(fd);
-       old_handle = pioinfo->osfhnd;
-       pioinfo->osfhnd = new_handle;
-       return old_handle;
+       /*
+        * Create a copy of the original handle associated with fd
+        * because the original will get closed when we dup2().
+        */
+       HANDLE handle = (HANDLE)_get_osfhandle(fd);
+       HANDLE duplicate = duplicate_handle(handle);
+       /* Create a temp fd associated with the already open "new_handle". */
+       int new_fd = _open_osfhandle((intptr_t)new_handle, O_BINARY);
+       assert((fd == 1) || (fd == 2));
+       /*
+        * Use stock dup2() to re-bind fd to the new handle.  Note that
+        * this will implicitly close(1) and close both fd=1 and the
+        * originally associated handle.  It will open a new fd=1 and
+        * call DuplicateHandle() on the handle associated with new_fd.
+        * It is because of this implicit close() that we created the
+        * copy of the original.
+        *
+        * Note that the OS can recycle HANDLE (numbers) just like it
+        * recycles fd (numbers), so we must update the cached value
+        * of "console".  You can use GetFileType() to see that
+        * handle and _get_osfhandle(fd) may have the same number
+        * value, but they refer to different actual files now.
+        *
+        * Note that dup2() when given target := {0,1,2} will also
+        * call SetStdHandle(), so we don't need to worry about that.
+        */
+       dup2(new_fd, fd);
+       if (console == handle)
+               console = duplicate;
+       handle = INVALID_HANDLE_VALUE;
+       /* Close the temp fd.  This explicitly closes "new_handle"
+        * (because it has been associated with it).
+        */
+       close(new_fd);
+       fd_is_interactive[fd] |= FD_SWAPPED;
+       return duplicate;
  }
  
  #ifdef DETECT_MSYS_TTY
@@@ -556,51 -540,35 +540,35 @@@ static void detect_msys_tty(int fd
                        buffer, sizeof(buffer) - 2, &result)))
                return;
        name = nameinfo->Name.Buffer;
 -      name[nameinfo->Name.Length] = 0;
 +      name[nameinfo->Name.Length / sizeof(*name)] = 0;
  
-       /* check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX') */
-       if (!wcsstr(name, L"msys-") || !wcsstr(name, L"-pty"))
+       /*
+        * Check if this could be a MSYS2 pty pipe ('msys-XXXX-ptyN-XX')
+        * or a cygwin pty pipe ('cygwin-XXXX-ptyN-XX')
+        */
+       if ((!wcsstr(name, L"msys-") && !wcsstr(name, L"cygwin-")) ||
+                       !wcsstr(name, L"-pty"))
                return;
  
-       /* init ioinfo size if we haven't done so */
-       if (init_sizeof_ioinfo())
-               return;
-       /* set FDEV flag, reset FPIPE flag */
-       _pioinfo(fd)->osflags &= ~FPIPE;
-       _pioinfo(fd)->osflags |= FDEV;
+       fd_is_interactive[fd] |= FD_MSYS;
  }
  
  #endif
  
+ /*
+  * Wrapper for isatty().  Most calls in the main git code
+  * call isatty(1 or 2) to see if the instance is interactive
+  * and should: be colored, show progress, paginate output.
+  * We lie and give results for what the descriptor WAS at
+  * startup (and ignore any pipe redirection we internally
+  * do).
+  */
+ #undef isatty
  int winansi_isatty(int fd)
  {
-       int res = isatty(fd);
-       if (res) {
-               /*
-                * Make sure that /dev/null is not fooling Git into believing
-                * that we are connected to a terminal, as "_isatty() returns a
-                * nonzero value if the descriptor is associated with a
-                * character device."; for more information, see
-                *
-                * https://msdn.microsoft.com/en-us/library/f4s0ddew.aspx
-                */
-               HANDLE handle = (HANDLE)_get_osfhandle(fd);
-               if (fd == STDIN_FILENO) {
-                       DWORD dummy;
-                       if (!GetConsoleMode(handle, &dummy))
-                               res = 0;
-               } else if (fd == STDOUT_FILENO || fd == STDERR_FILENO) {
-                       CONSOLE_SCREEN_BUFFER_INFO dummy;
-                       if (!GetConsoleScreenBufferInfo(handle, &dummy))
-                               res = 0;
-               }
-       }
-       return res;
+       if (fd >= 0 && fd <= 2)
+               return fd_is_interactive[fd] != 0;
+       return isatty(fd);
  }
  
  void winansi_init(void)
        /* check if either stdout or stderr is a console output screen buffer */
        con1 = is_console(1);
        con2 = is_console(2);
+       /* Also compute console bit for fd 0 even though we don't need the result here. */
+       is_console(0);
        if (!con1 && !con2) {
  #ifdef DETECT_MSYS_TTY
                /* check if stdin / stdout / stderr are MSYS2 pty pipes */
   */
  HANDLE winansi_get_osfhandle(int fd)
  {
-       HANDLE hnd = (HANDLE) _get_osfhandle(fd);
-       if (isatty(fd) && GetFileType(hnd) == FILE_TYPE_PIPE) {
-               if (fd == 1 && hconsole1)
-                       return hconsole1;
-               else if (fd == 2 && hconsole2)
-                       return hconsole2;
-       }
-       return hnd;
+       if (fd == 1 && (fd_is_interactive[1] & FD_SWAPPED))
+               return hconsole1;
+       if (fd == 2 && (fd_is_interactive[2] & FD_SWAPPED))
+               return hconsole2;
+       return (HANDLE)_get_osfhandle(fd);
  }