From 449688331720632905e3ee7e08f2f45ec1bc0fce Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 14:16:43 +0100 Subject: [PATCH 01/25] Add Git for Windows' wrapper executable On Windows, Git is faced by the challenge that it has to set up certain environment variables before running Git under special circumstances such as when Git is called directly from cmd.exe (i.e. outside any Bash environment). This source code was taken from msysGit's commit 74a198d: https://github.com/msysgit/msysgit/blob/74a198d/src/git-wrapper/git-wrapper.c Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 194 +++++++++++++++++++++++++++++++++++++ 1 file changed, 194 insertions(+) create mode 100644 compat/win32/git-wrapper.c diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c new file mode 100644 index 0000000000..a7495c095f --- /dev/null +++ b/compat/win32/git-wrapper.c @@ -0,0 +1,194 @@ +/* + * git-wrapper - replace cmd\git.cmd with an executable + * + * Copyright (C) 2012 Pat Thoyts + */ + +#define STRICT +#define WIN32_LEAN_AND_MEAN +#define UNICODE +#define _UNICODE +#include +#include +#include +#include + +static void print_error(LPCWSTR prefix, DWORD error_number) +{ + LPWSTR buffer = NULL; + DWORD count = 0; + + count = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_SYSTEM + | FORMAT_MESSAGE_IGNORE_INSERTS, + NULL, error_number, LANG_NEUTRAL, + (LPTSTR)&buffer, 0, NULL); + if (count < 1) + count = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER + | FORMAT_MESSAGE_FROM_STRING + | FORMAT_MESSAGE_ARGUMENT_ARRAY, + L"Code 0x%1!08x!", + 0, LANG_NEUTRAL, (LPTSTR)&buffer, 0, + (va_list*)&error_number); + fwprintf(stderr, L"%s: %s", prefix, buffer); + LocalFree((HLOCAL)buffer); +} + +int main(void) +{ + int r = 1, wait = 1; + WCHAR exepath[MAX_PATH], exe[MAX_PATH]; + LPWSTR cmd = NULL, path2 = NULL, exep = exe; + UINT codepage = 0; + int len; + + /* get the installation location */ + GetModuleFileName(NULL, exepath, MAX_PATH); + PathRemoveFileSpec(exepath); + PathRemoveFileSpec(exepath); + + /* set the default exe module */ + wcscpy(exe, exepath); + PathAppend(exe, L"bin\\git.exe"); + + /* if not set, set TERM to msys */ + if (!GetEnvironmentVariable(L"TERM", NULL, 0)) + SetEnvironmentVariable(L"TERM", L"msys"); + + /* if not set, set PLINK_PROTOCOL to ssh */ + if (!GetEnvironmentVariable(L"PLINK_PROTOCOL", NULL, 0)) + SetEnvironmentVariable(L"PLINK_PROTOCOL", L"ssh"); + + /* set HOME to %HOMEDRIVE%%HOMEPATH% or %USERPROFILE% + * With roaming profiles: HOMEPATH is the roaming location and + * USERPROFILE is the local location + */ + if (!GetEnvironmentVariable(L"HOME", NULL, 0)) { + LPWSTR e = NULL; + len = GetEnvironmentVariable(L"HOMEPATH", NULL, 0); + if (len == 0) { + len = GetEnvironmentVariable(L"USERPROFILE", NULL, 0); + if (len != 0) { + e = (LPWSTR)malloc(len * sizeof(WCHAR)); + GetEnvironmentVariable(L"USERPROFILE", e, len); + SetEnvironmentVariable(L"HOME", e); + free(e); + } + } + else { + int n; + len += GetEnvironmentVariable(L"HOMEDRIVE", NULL, 0); + e = (LPWSTR)malloc(sizeof(WCHAR) * (len + 2)); + n = GetEnvironmentVariable(L"HOMEDRIVE", e, len); + GetEnvironmentVariable(L"HOMEPATH", &e[n], len-n); + SetEnvironmentVariable(L"HOME", e); + free(e); + } + } + + /* extend the PATH */ + len = GetEnvironmentVariable(L"PATH", NULL, 0); + len = sizeof(WCHAR) * (len + 2 * MAX_PATH); + path2 = (LPWSTR)malloc(len); + wcscpy(path2, exepath); + PathAppend(path2, L"bin;"); + /* should do this only if it exists */ + wcscat(path2, exepath); + PathAppend(path2, L"mingw\\bin;"); + GetEnvironmentVariable(L"PATH", &path2[wcslen(path2)], + (len/sizeof(WCHAR))-wcslen(path2)); + SetEnvironmentVariable(L"PATH", path2); + free(path2); + + + /* fix up the command line to call git.exe + * We have to be very careful about quoting here so we just + * trim off the first argument and replace it leaving the rest + * untouched. + */ + { + int wargc = 0, gui = 0; + LPWSTR cmdline = NULL; + LPWSTR *wargv = NULL, p = NULL; + cmdline = GetCommandLine(); + wargv = CommandLineToArgvW(cmdline, &wargc); + cmd = (LPWSTR)malloc(sizeof(WCHAR) * + (wcslen(cmdline) + MAX_PATH)); + if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { + wait = 0; + if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { + wait = 1; + wcscpy(cmd, L"git.exe"); + } + else { + WCHAR script[MAX_PATH]; + gui = 1; + wcscpy(script, exepath); + PathAppend(script, + L"libexec\\git-core\\git-gui"); + PathQuoteSpaces(script); + wcscpy(cmd, L"wish.exe "); + wcscat(cmd, script); + wcscat(cmd, L" --"); + /* find the module from the commandline */ + exep = NULL; + } + } + else + wcscpy(cmd, L"git.exe"); + + /* append all after first space after the initial parameter */ + p = wcschr(&cmdline[wcslen(wargv[0])], L' '); + if (p && *p) { + /* for git gui subcommands, remove the 'gui' word */ + if (gui) { + while (*p == L' ') ++p; + p = wcschr(p, L' '); + } + if (p && *p) + wcscat(cmd, p); + } + LocalFree(wargv); + } + + /* set the console to ANSI/GUI codepage */ + codepage = GetConsoleCP(); + SetConsoleCP(GetACP()); + + { + STARTUPINFO si; + PROCESS_INFORMATION pi; + BOOL br = FALSE; + ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); + ZeroMemory(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + br = CreateProcess(/* module: null means use command line */ + exep, + cmd, /* modified command line */ + NULL, /* process handle inheritance */ + NULL, /* thread handle inheritance */ + TRUE, /* handles inheritable? */ + CREATE_UNICODE_ENVIRONMENT, + NULL, /* environment: use parent */ + NULL, /* starting directory: use parent */ + &si, &pi); + if (br) { + if (wait) + WaitForSingleObject(pi.hProcess, INFINITE); + if (!GetExitCodeProcess(pi.hProcess, (DWORD *)&r)) + print_error(L"error reading exit code", + GetLastError()); + CloseHandle(pi.hProcess); + } + else { + print_error(L"error launching git", GetLastError()); + r = 1; + } + } + + free(cmd); + + /* reset the console codepage */ + SetConsoleCP(codepage); + ExitProcess(r); +} From 40d9a7a840f2620e0a0f7bd5d2a00eea366b3160 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 15:36:41 +0100 Subject: [PATCH 02/25] mingw: Compile the Git wrapper We take care to embed the manifest, too, because we will modify the wrapper in the next few commits to serve as a drop-in replacement for the built-ins, i.e. we will want to call the wrapper under names such as 'git-patch-id.exe', too. To allow 32-bit and 64-bit builds in the same directory, we let git-wrapper.o depend on GIT-PREFIX so that it gets recompiled when compiling for a different architecture. Signed-off-by: Johannes Schindelin --- config.mak.uname | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.mak.uname b/config.mak.uname index d6f7980bb9..0a4f3c076d 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -569,6 +569,16 @@ else COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO NO_CURL = YesPlease endif + OTHER_PROGRAMS += git-wrapper$(X) + +git-wrapper$(X): compat/win32/git-wrapper.o git.res + $(QUIET_LINK)$(CC) $(ALL_LDFLAGS) $(COMPAT_CFLAGS) \ + -fno-stack-protector -Wall -s -o $@ $^ -lshell32 -lshlwapi + +compat/win32/git-wrapper.o: %.o: %.c GIT-PREFIX + $(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \ + -fno-stack-protector -o $*.o -c -Wall -Wwrite-strings $< + endif endif ifeq ($(uname_S),QNX) From 6157bcb6f61eb64e4e2908819734eb03afa89cf9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 15:35:21 +0100 Subject: [PATCH 03/25] Refactor git-wrapper into more functions This prepares the wrapper for modifications to serve as a drop-in replacement for the builtins. This commit's diff is best viewed with the `-w` flag. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 128 ++++++++++++++++++++----------------- 1 file changed, 68 insertions(+), 60 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index a7495c095f..d4cfb563bb 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -34,22 +34,10 @@ static void print_error(LPCWSTR prefix, DWORD error_number) LocalFree((HLOCAL)buffer); } -int main(void) +static void setup_environment(LPWSTR exepath) { - int r = 1, wait = 1; - WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, path2 = NULL, exep = exe; - UINT codepage = 0; int len; - - /* get the installation location */ - GetModuleFileName(NULL, exepath, MAX_PATH); - PathRemoveFileSpec(exepath); - PathRemoveFileSpec(exepath); - - /* set the default exe module */ - wcscpy(exe, exepath); - PathAppend(exe, L"bin\\git.exe"); + LPWSTR path2 = NULL; /* if not set, set TERM to msys */ if (!GetEnvironmentVariable(L"TERM", NULL, 0)) @@ -100,56 +88,76 @@ int main(void) SetEnvironmentVariable(L"PATH", path2); free(path2); +} - /* fix up the command line to call git.exe - * We have to be very careful about quoting here so we just - * trim off the first argument and replace it leaving the rest - * untouched. - */ - { - int wargc = 0, gui = 0; - LPWSTR cmdline = NULL; - LPWSTR *wargv = NULL, p = NULL; - cmdline = GetCommandLine(); - wargv = CommandLineToArgvW(cmdline, &wargc); - cmd = (LPWSTR)malloc(sizeof(WCHAR) * - (wcslen(cmdline) + MAX_PATH)); - if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { - wait = 0; - if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { - wait = 1; - wcscpy(cmd, L"git.exe"); - } - else { - WCHAR script[MAX_PATH]; - gui = 1; - wcscpy(script, exepath); - PathAppend(script, - L"libexec\\git-core\\git-gui"); - PathQuoteSpaces(script); - wcscpy(cmd, L"wish.exe "); - wcscat(cmd, script); - wcscat(cmd, L" --"); - /* find the module from the commandline */ - exep = NULL; - } - } - else +/* + * Fix up the command line to call git.exe + * We have to be very careful about quoting here so we just + * trim off the first argument and replace it leaving the rest + * untouched. + */ +static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait) +{ + int wargc = 0, gui = 0; + LPWSTR cmd = NULL, cmdline = NULL; + LPWSTR *wargv = NULL, p = NULL; + + cmdline = GetCommandLine(); + wargv = CommandLineToArgvW(cmdline, &wargc); + cmd = (LPWSTR)malloc(sizeof(WCHAR) * + (wcslen(cmdline) + MAX_PATH)); + if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { + *wait = 0; + if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { + *wait = 1; wcscpy(cmd, L"git.exe"); - - /* append all after first space after the initial parameter */ - p = wcschr(&cmdline[wcslen(wargv[0])], L' '); - if (p && *p) { - /* for git gui subcommands, remove the 'gui' word */ - if (gui) { - while (*p == L' ') ++p; - p = wcschr(p, L' '); - } - if (p && *p) - wcscat(cmd, p); } - LocalFree(wargv); + else { + WCHAR script[MAX_PATH]; + gui = 1; + wcscpy(script, exepath); + PathAppend(script, + L"libexec\\git-core\\git-gui"); + PathQuoteSpaces(script); + wcscpy(cmd, L"wish.exe "); + wcscat(cmd, script); + wcscat(cmd, L" --"); + /* find the module from the commandline */ + *exep = NULL; + } } + else + wcscpy(cmd, L"git.exe"); + + /* append all after first space after the initial parameter */ + p = wcschr(&cmdline[wcslen(wargv[0])], L' '); + if (p && *p) { + /* for git gui subcommands, remove the 'gui' word */ + if (gui) { + while (*p == L' ') ++p; + p = wcschr(p, L' '); + } + if (p && *p) + wcscat(cmd, p); + } + LocalFree(wargv); + + return cmd; +} + +int main(void) +{ + int r = 1, wait = 1; + WCHAR exepath[MAX_PATH], exe[MAX_PATH]; + LPWSTR cmd = NULL, exep = exe, basename; + UINT codepage = 0; + + /* get the installation location */ + GetModuleFileName(NULL, exepath, MAX_PATH); + PathRemoveFileSpec(exepath); + PathRemoveFileSpec(exepath); + setup_environment(exepath); + cmd = fixup_commandline(exepath, &exep, &wait); /* set the console to ANSI/GUI codepage */ codepage = GetConsoleCP(); From da0ca84c443c53eb2f4869fcb09d90f26a374f2e Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 16:00:02 +0100 Subject: [PATCH 04/25] Let the Git wrapper serve as a drop-in replacement for builtins Git started out as a bunch of separate commands, in the true Unix spirit. Over time, more and more functionality was shared between the different Git commands, though, so it made sense to introduce the notion of "builtins": programs that are actually integrated into the main Git executable. These builtins can be called in two ways: either by specifying a subcommand as the first command-line argument, or -- for backwards compatibility -- by calling the Git executable hardlinked to a filename of the form "git-". Example: the "log" command can be called via "git log " or via "git-log ". The latter form is actually deprecated and only supported for scripts; calling "git-log" interactively will not even work by default because the libexec/git-core/ directory is not in the PATH. All of this is well and groovy as long as hard links are supported. Sadly, this is not the case in general on Windows. So it actually hurts quite a bit when you have to fall back to copying all of git.exe's currently 7.5MB 109 times, just for backwards compatibility. The simple solution would be to install really trivial shell script wrappers in place of the builtins: for builtin in $BUILTINS do rm git-$builtin.exe printf '#!/bin/sh\nexec git %s "$@"\n' $builtin > git-builtin chmod a+x git-builtin done This method would work -- even on Windows because Git for Windows ships a full-fledged Bash. However, the Windows Bash comes at a price: it needs to spin up a full-fledged POSIX emulation layer everytime it starts. Therefore, the shell script solution would incur a significant performance penalty. The best solution the Git for Windows team could come up with is to extend the Git wrapper -- that is needed to call Git from cmd.exe anyway, and that weighs in with a scant 19KB -- to also serve as a drop-in replacement for the builtins so that the following workaround is satisfactory: for builtin in $BUILTINS do cp git-wrapper.exe git-$builtin.exe done This commit allows for this, by extending the module file parsing to turn builtin command names like `git-log.exe ...` into calls to the main Git executable: `git.exe log ...`. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 48 +++++++++++++++++++++++++++++++------- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index d4cfb563bb..39045764a9 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -96,7 +96,8 @@ static void setup_environment(LPWSTR exepath) * trim off the first argument and replace it leaving the rest * untouched. */ -static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait) +static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, + LPWSTR prefix_args, int prefix_args_len) { int wargc = 0, gui = 0; LPWSTR cmd = NULL, cmdline = NULL; @@ -105,7 +106,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait) cmdline = GetCommandLine(); wargv = CommandLineToArgvW(cmdline, &wargc); cmd = (LPWSTR)malloc(sizeof(WCHAR) * - (wcslen(cmdline) + MAX_PATH)); + (wcslen(cmdline) + prefix_args_len + 1 + MAX_PATH)); if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { *wait = 0; if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { @@ -126,6 +127,9 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait) *exep = NULL; } } + else if (prefix_args) + _swprintf(cmd, L"%s\\%s %.*s", + exepath, L"git.exe", prefix_args_len, prefix_args); else wcscpy(cmd, L"git.exe"); @@ -147,17 +151,45 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait) int main(void) { - int r = 1, wait = 1; + int r = 1, wait = 1, prefix_args_len = -1; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, exep = exe, basename; + LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; /* get the installation location */ GetModuleFileName(NULL, exepath, MAX_PATH); - PathRemoveFileSpec(exepath); - PathRemoveFileSpec(exepath); - setup_environment(exepath); - cmd = fixup_commandline(exepath, &exep, &wait); + if (!PathRemoveFileSpec(exepath)) { + fwprintf(stderr, L"Invalid executable path: %s\n", exepath); + ExitProcess(1); + } + basename = exepath + wcslen(exepath) + 1; + if (!wcsncmp(basename, L"git-", 4)) { + /* Call a builtin */ + prefix_args = basename + 4; + prefix_args_len = wcslen(prefix_args); + if (!wcscmp(prefix_args + prefix_args_len - 4, L".exe")) + prefix_args_len -= 4; + + /* set the default exe module */ + wcscpy(exe, exepath); + PathAppend(exe, L"git.exe"); + } + else if (!wcscmp(basename, L"git.exe")) { + if (!PathRemoveFileSpec(exepath)) { + fwprintf(stderr, + L"Invalid executable path: %s\n", exepath); + ExitProcess(1); + } + + /* set the default exe module */ + wcscpy(exe, exepath); + PathAppend(exe, L"bin\\git.exe"); + } + + if (!prefix_args) + setup_environment(exepath); + cmd = fixup_commandline(exepath, &exep, &wait, + prefix_args, prefix_args_len); /* set the console to ANSI/GUI codepage */ codepage = GetConsoleCP(); From f6d2e60b69190ee7531ef9d867f656f0f03f18cf Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 16:40:05 +0100 Subject: [PATCH 05/25] mingw: Use the Git wrapper for builtins This reduces the disk footprint of a full Git for Windows setup dramatically because on Windows, one cannot assume that hard links are supported. The net savings are calculated easily: the 32-bit `git.exe` file weighs in with 7662 kB while the `git-wrapper.exe` file (modified to serve as a drop-in replacement for builtins) weighs a scant 21 kB. At this point, there are 109 builtins which results in a total of 813 MB disk space being freed up by this commit. Yes, that is really more than half a gigabyte. Signed-off-by: Johannes Schindelin --- Makefile | 34 ++++++++++++++++++++++++++-------- config.mak.uname | 1 + 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index 8000492146..268dc72ec4 100644 --- a/Makefile +++ b/Makefile @@ -1715,11 +1715,17 @@ version.sp version.s version.o: EXTRA_CPPFLAGS = \ '-DGIT_VERSION="$(GIT_VERSION)"' \ '-DGIT_USER_AGENT=$(GIT_USER_AGENT_CQ_SQ)' +ifeq (,$(BUILT_IN_WRAPPER)) $(BUILT_INS): git$X $(QUIET_BUILT_IN)$(RM) $@ && \ ln $< $@ 2>/dev/null || \ ln -s $< $@ 2>/dev/null || \ cp $< $@ +else +$(BUILT_INS): $(BUILT_IN_WRAPPER) + $(QUIET_BUILT_IN)$(RM) $@ && \ + cp $< $@ +endif common-cmds.h: generate-cmdlist.sh command-list.txt @@ -2284,6 +2290,24 @@ profile-install: profile profile-fast-install: profile-fast $(MAKE) install +ifeq (,$(BUILT_IN_WRAPPER)) +LN_OR_CP_BUILT_IN_BINDIR = \ + test -z "$(NO_INSTALL_HARDLINKS)" && \ + ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ + cp "$$bindir/git$X" "$$bindir/$$p" || exit; +LN_OR_CP_BUILT_IN_EXECDIR = \ + test -z "$(NO_INSTALL_HARDLINKS)" && \ + ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \ + cp "$$execdir/git$X" "$$execdir/$$p" || exit; +else +LN_OR_CP_BUILT_IN_BINDIR = \ + cp "$(BUILT_IN_WRAPPER)" "$$bindir/$$p" || exit; +LN_OR_CP_BUILT_IN_EXECDIR = \ + cp "$(BUILT_IN_WRAPPER)" "$$execdir/$$p" || exit; +endif + install: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(gitexec_instdir_SQ)' @@ -2322,17 +2346,11 @@ endif } && \ for p in $(filter $(install_bindir_programs),$(BUILT_INS)); do \ $(RM) "$$bindir/$$p" && \ - test -z "$(NO_INSTALL_HARDLINKS)" && \ - ln "$$bindir/git$X" "$$bindir/$$p" 2>/dev/null || \ - ln -s "git$X" "$$bindir/$$p" 2>/dev/null || \ - cp "$$bindir/git$X" "$$bindir/$$p" || exit; \ + $(LN_OR_CP_BUILT_IN_BINDIR) \ done && \ for p in $(BUILT_INS); do \ $(RM) "$$execdir/$$p" && \ - test -z "$(NO_INSTALL_HARDLINKS)" && \ - ln "$$execdir/git$X" "$$execdir/$$p" 2>/dev/null || \ - ln -s "git$X" "$$execdir/$$p" 2>/dev/null || \ - cp "$$execdir/git$X" "$$execdir/$$p" || exit; \ + $(LN_OR_CP_BUILT_IN_EXECDIR) \ done && \ remote_curl_aliases="$(REMOTE_CURL_ALIASES)" && \ for p in $$remote_curl_aliases; do \ diff --git a/config.mak.uname b/config.mak.uname index 0a4f3c076d..9de3240c9e 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -570,6 +570,7 @@ else NO_CURL = YesPlease endif OTHER_PROGRAMS += git-wrapper$(X) + BUILT_IN_WRAPPER = git-wrapper$(X) git-wrapper$(X): compat/win32/git-wrapper.o git.res $(QUIET_LINK)$(CC) $(ALL_LDFLAGS) $(COMPAT_CFLAGS) \ From 67fbeb78ebe868cd9e1be71916ac3993e9049baa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 Mar 2015 14:31:42 +0100 Subject: [PATCH 06/25] git-wrapper: support MSys2 The original purpose of the Git wrapper is to run from inside Git for Windows' /cmd/ directory, to allow setting up some environment variables before Git is allowed to take over. Due to differences in the file system layout, MSys2 requires some changes for that to work. In addition, we must take care to set the `MSYSTEM` environment variable to `MINGW32` or `MINGW64`, respectively, to allow MSys2 to be configured correctly in case Git launches a shell or Perl script. We also need to change the `TERM` variable to `cygwin` instead of `msys`, otherwise the pager `less.exe` (spawned e.g. by `git log`) will simply crash with a message similar to this one: 1 [main] less 9832 cygwin_exception::open_stackdumpfile: Dumping stack trace to less.exe.stackdump Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 48 ++++++++++++++++++++++++++++++-------- 1 file changed, 38 insertions(+), 10 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 39045764a9..ffae0bc554 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -12,6 +12,9 @@ #include #include #include +#include + +static WCHAR msystem_bin[64]; static void print_error(LPCWSTR prefix, DWORD error_number) { @@ -36,12 +39,18 @@ static void print_error(LPCWSTR prefix, DWORD error_number) static void setup_environment(LPWSTR exepath) { - int len; + WCHAR msystem[64]; LPWSTR path2 = NULL; + int len; - /* if not set, set TERM to msys */ + /* Set MSYSTEM */ + swprintf(msystem, sizeof(msystem), + L"MINGW%d", (int) sizeof(void *) * 8); + SetEnvironmentVariable(L"MSYSTEM", msystem); + + /* if not set, set TERM to cygwin */ if (!GetEnvironmentVariable(L"TERM", NULL, 0)) - SetEnvironmentVariable(L"TERM", L"msys"); + SetEnvironmentVariable(L"TERM", L"cygwin"); /* if not set, set PLINK_PROTOCOL to ssh */ if (!GetEnvironmentVariable(L"PLINK_PROTOCOL", NULL, 0)) @@ -79,12 +88,22 @@ static void setup_environment(LPWSTR exepath) len = sizeof(WCHAR) * (len + 2 * MAX_PATH); path2 = (LPWSTR)malloc(len); wcscpy(path2, exepath); - PathAppend(path2, L"bin;"); - /* should do this only if it exists */ - wcscat(path2, exepath); - PathAppend(path2, L"mingw\\bin;"); - GetEnvironmentVariable(L"PATH", &path2[wcslen(path2)], - (len/sizeof(WCHAR))-wcslen(path2)); + PathAppend(path2, msystem_bin); + if (_waccess(path2, 0) != -1) { + /* We are in an MSys2-based setup */ + wcscat(path2, L";"); + wcscat(path2, exepath); + PathAppend(path2, L"usr\\bin;"); + } + else { + /* Fall back to MSys1 paths */ + wcscpy(path2, exepath); + PathAppend(path2, L"bin;"); + wcscat(path2, exepath); + PathAppend(path2, L"mingw\\bin;"); + } + GetEnvironmentVariable(L"PATH", path2 + wcslen(path2), + (len / sizeof(WCHAR)) - wcslen(path2)); SetEnvironmentVariable(L"PATH", path2); free(path2); @@ -156,6 +175,10 @@ int main(void) LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; + /* Determine MSys2-based Git path. */ + swprintf(msystem_bin, sizeof(msystem_bin), + L"mingw%d\\bin", (int) sizeof(void *) * 8); + /* get the installation location */ GetModuleFileName(NULL, exepath, MAX_PATH); if (!PathRemoveFileSpec(exepath)) { @@ -183,7 +206,12 @@ int main(void) /* set the default exe module */ wcscpy(exe, exepath); - PathAppend(exe, L"bin\\git.exe"); + PathAppend(exe, msystem_bin); + PathAppend(exe, L"git.exe"); + if (_waccess(exe, 0) == -1) { + wcscpy(exe, exepath); + PathAppend(exe, L"bin\\git.exe"); + } } if (!prefix_args) From 3d4dec2449420923b0208a61eddc8706d0da17ec Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 16:34:48 +0100 Subject: [PATCH 07/25] git-wrapper: prepare for executing configurable command-lines We are about to use the Git wrapper to call the Git Bash of Git for Windows. All the wrapper needs to do for that is to set up the environment variables, use the home directory as working directory and then hand off to a user-specified command-line. We prepare the existing code for this change by introducing flags to set up the environment variables, to launch a non-Git program, and to use the home directory as working directory. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 35 ++++++++++++++++++++++++++--------- 1 file changed, 26 insertions(+), 9 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index ffae0bc554..d82d4657ae 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -116,7 +116,7 @@ static void setup_environment(LPWSTR exepath) * untouched. */ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, - LPWSTR prefix_args, int prefix_args_len) + LPWSTR prefix_args, int prefix_args_len, int is_git_command) { int wargc = 0, gui = 0; LPWSTR cmd = NULL, cmdline = NULL; @@ -146,9 +146,14 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, *exep = NULL; } } - else if (prefix_args) - _swprintf(cmd, L"%s\\%s %.*s", - exepath, L"git.exe", prefix_args_len, prefix_args); + else if (prefix_args) { + if (is_git_command) + _swprintf(cmd, L"%s\\%s %.*s", exepath, L"git.exe", + prefix_args_len, prefix_args); + else + _swprintf(cmd, L"%.*s", prefix_args_len, prefix_args); + + } else wcscpy(cmd, L"git.exe"); @@ -170,9 +175,10 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, int main(void) { - int r = 1, wait = 1, prefix_args_len = -1; + int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, + is_git_command = 1, start_in_home = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; + LPWSTR cmd = NULL, dir = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; /* Determine MSys2-based Git path. */ @@ -187,6 +193,8 @@ int main(void) } basename = exepath + wcslen(exepath) + 1; if (!wcsncmp(basename, L"git-", 4)) { + needs_env_setup = 0; + /* Call a builtin */ prefix_args = basename + 4; prefix_args_len = wcslen(prefix_args); @@ -214,10 +222,19 @@ int main(void) } } - if (!prefix_args) + if (needs_env_setup) setup_environment(exepath); cmd = fixup_commandline(exepath, &exep, &wait, - prefix_args, prefix_args_len); + prefix_args, prefix_args_len, is_git_command); + + if (start_in_home) { + int len = GetEnvironmentVariable(L"HOME", NULL, 0); + + if (len) { + dir = malloc(sizeof(WCHAR) * len); + GetEnvironmentVariable(L"HOME", dir, len); + } + } /* set the console to ANSI/GUI codepage */ codepage = GetConsoleCP(); @@ -238,7 +255,7 @@ int main(void) TRUE, /* handles inheritable? */ CREATE_UNICODE_ENVIRONMENT, NULL, /* environment: use parent */ - NULL, /* starting directory: use parent */ + dir, /* starting directory: use parent */ &si, &pi); if (br) { if (wait) From 5cef61b57053464ba80e940d3b891b990517416b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 12:11:05 +0100 Subject: [PATCH 08/25] git-wrapper: inherit stdin/stdout/stderr even without a console Otherwise the output of Git commands cannot be caught by, say, Git GUI (because it is running detached from any console, which would make `git.exe` inherit the standard handles implicitly). Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index d82d4657ae..1d9adebe4b 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -243,17 +243,33 @@ int main(void) { STARTUPINFO si; PROCESS_INFORMATION pi; + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT; + HANDLE console_handle; BOOL br = FALSE; ZeroMemory(&pi, sizeof(PROCESS_INFORMATION)); ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); + + console_handle = CreateFile(L"CONOUT$", GENERIC_WRITE, + FILE_SHARE_WRITE, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); + if (console_handle != INVALID_HANDLE_VALUE) + CloseHandle(console_handle); + else { + si.dwFlags = STARTF_USESTDHANDLES; + si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); + si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); + si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + + creation_flags |= CREATE_NO_WINDOW; + } br = CreateProcess(/* module: null means use command line */ exep, cmd, /* modified command line */ NULL, /* process handle inheritance */ NULL, /* thread handle inheritance */ TRUE, /* handles inheritable? */ - CREATE_UNICODE_ENVIRONMENT, + creation_flags, NULL, /* environment: use parent */ dir, /* starting directory: use parent */ &si, &pi); From 098617e46d847c40591ef1a67e09ac43a3089279 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 26 Mar 2015 17:06:22 +0100 Subject: [PATCH 09/25] Git wrapper: allow overriding what executable is called The Git wrapper does one thing, and does it well: setting up the environment required to run Git and its scripts, and then hand off to another program. We already do this for the Git executable itself; in Git for Windows' context, we have exactly the same need also when calling the Git Bash or Git CMD. However, both are tied to what particular shell environment you use, though: MSys or MSys2 (or whatever else cunning developers make work for them). This means that the Git Bash and Git CMD need to be compiled in the respective context (e.g. when compiling the mingw-w64-git package in the MSys2 context). Happily, Windows offers a way to configure compiled executables: resources. So let's just look whether the current executable has a string resource and use it as the command-line to execute after the environment is set up. To support MSys2's Git Bash better (where `mintty` should, but might not, be available), we verify whether the specified executable exists, and keep looking for string resources if it does not. For even more flexibility, we expand environment variables specified as `@@@@`, and for convenience `@@EXEPATH@@` expands into the directory in which the executable resides. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 103 ++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 1d9adebe4b..0dc2ae698b 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -173,6 +173,102 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, return cmd; } +static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, + LPWSTR *prefix_args, int *prefix_args_len, + int *is_git_command, int *start_in_home) +{ + int id, wargc; + LPWSTR *wargv; + +#define BUFSIZE 65536 + static WCHAR buf[BUFSIZE]; + int len; + + for (id = 0; ; id++) { + len = LoadString(NULL, id, buf, BUFSIZE); + + if (!len) { + if (!id) + return 0; /* no resources found */ + + fwprintf(stderr, L"Need a valid command-line; " + L"Edit the string resources accordingly\n"); + exit(1); + } + + if (len >= BUFSIZE) { + fwprintf(stderr, + L"Could not read resource (too large)\n"); + exit(1); + } + + buf[len] = L'\0'; + + if (!id) + SetEnvironmentVariable(L"EXEPATH", exepath); + + for (;;) { + LPWSTR atat = wcsstr(buf, L"@@"), atat2; + WCHAR save; + int env_len, delta; + + if (!atat) + break; + + atat2 = wcsstr(atat + 2, L"@@"); + if (!atat2) + break; + + *atat2 = L'\0'; + env_len = GetEnvironmentVariable(atat + 2, NULL, 0); + delta = env_len - 1 - (atat2 + 2 - atat); + if (len + delta >= BUFSIZE) { + fwprintf(stderr, + L"Substituting '%s' results in too " + L"large a command-line\n", atat + 2); + exit(1); + } + if (delta) + memmove(atat2 + 2 + delta, atat2 + 2, + sizeof(WCHAR) * (len + 1 + - (atat2 + 2 - buf))); + len += delta; + save = atat[env_len - 1]; + GetEnvironmentVariable(atat + 2, atat, env_len); + atat[env_len - 1] = save; + } + + /* parse first argument */ + wargv = CommandLineToArgvW(buf, &wargc); + if (wargc < 1) { + fwprintf(stderr, L"Invalid command-line: '%s'\n", buf); + exit(1); + } + if (*wargv[0] == L'\\' || + (isalpha(*wargv[0]) && wargv[0][1] == L':')) + wcscpy(exep, wargv[0]); + else { + wcscpy(exep, exepath); + PathAppend(exep, wargv[0]); + } + LocalFree(wargv); + + if (_waccess(exep, 0) != -1) + break; + fwprintf(stderr, + L"Skipping command-line '%s'\n('%s' not found)\n", + buf, exep); + } + + *prefix_args = buf; + *prefix_args_len = wcslen(buf); + + *is_git_command = 0; + *start_in_home = 1; + + return 1; +} + int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, @@ -192,7 +288,12 @@ int main(void) ExitProcess(1); } basename = exepath + wcslen(exepath) + 1; - if (!wcsncmp(basename, L"git-", 4)) { + if (configure_via_resource(basename, exepath, exep, + &prefix_args, &prefix_args_len, + &is_git_command, &start_in_home)) { + /* do nothing */ + } + else if (!wcsncmp(basename, L"git-", 4)) { needs_env_setup = 0; /* Call a builtin */ From 6798e597986292be21aca10819210066cc28a246 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 15:06:22 +0100 Subject: [PATCH 10/25] Let the Git wrapper replace cmd\gitk.cmd, too In a push to polish Git for Windows more, we are moving away from scripts toward proper binaries. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 0dc2ae698b..2c78048786 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -322,6 +322,30 @@ int main(void) PathAppend(exe, L"bin\\git.exe"); } } + else if (!wcscmp(basename, L"gitk.exe")) { + static WCHAR buffer[BUFSIZE]; + if (!PathRemoveFileSpec(exepath)) { + fwprintf(stderr, + L"Invalid executable path: %s\n", exepath); + ExitProcess(1); + } + + /* set the default exe module */ + wcscpy(exe, exepath); + wcscpy(buffer, exepath); + PathAppend(exe, msystem_bin); + PathAppend(exe, L"wish.exe"); + if (_waccess(exe, 0) != -1) + PathAppend(buffer, msystem_bin); + else { + wcscpy(exe, exepath); + PathAppend(exe, L"mingw\\bin\\wish.exe"); + PathAppend(buffer, L"mingw\\bin"); + } + PathAppend(buffer, L"gitk"); + prefix_args = buffer; + prefix_args_len = wcslen(buffer); + } if (needs_env_setup) setup_environment(exepath); From 15e4eafb45014405f859127162b28ddcff53f253 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 17:29:01 +0100 Subject: [PATCH 11/25] git-wrapper: remove 'gui' and 'citool' handling In the meantime, Git for Windows learned to handle those subcommands quite well itself; There is no longer a need to special-case them in the wrapper. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 35 ++++------------------------------- 1 file changed, 4 insertions(+), 31 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 2c78048786..c720023a1c 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -118,7 +118,7 @@ static void setup_environment(LPWSTR exepath) static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, LPWSTR prefix_args, int prefix_args_len, int is_git_command) { - int wargc = 0, gui = 0; + int wargc = 0; LPWSTR cmd = NULL, cmdline = NULL; LPWSTR *wargv = NULL, p = NULL; @@ -126,27 +126,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, wargv = CommandLineToArgvW(cmdline, &wargc); cmd = (LPWSTR)malloc(sizeof(WCHAR) * (wcslen(cmdline) + prefix_args_len + 1 + MAX_PATH)); - if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) { - *wait = 0; - if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) { - *wait = 1; - wcscpy(cmd, L"git.exe"); - } - else { - WCHAR script[MAX_PATH]; - gui = 1; - wcscpy(script, exepath); - PathAppend(script, - L"libexec\\git-core\\git-gui"); - PathQuoteSpaces(script); - wcscpy(cmd, L"wish.exe "); - wcscat(cmd, script); - wcscat(cmd, L" --"); - /* find the module from the commandline */ - *exep = NULL; - } - } - else if (prefix_args) { + if (prefix_args) { if (is_git_command) _swprintf(cmd, L"%s\\%s %.*s", exepath, L"git.exe", prefix_args_len, prefix_args); @@ -159,15 +139,8 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, /* append all after first space after the initial parameter */ p = wcschr(&cmdline[wcslen(wargv[0])], L' '); - if (p && *p) { - /* for git gui subcommands, remove the 'gui' word */ - if (gui) { - while (*p == L' ') ++p; - p = wcschr(p, L' '); - } - if (p && *p) - wcscat(cmd, p); - } + if (p && *p) + wcscat(cmd, p); LocalFree(wargv); return cmd; From 176848f4de52459e2b42591f9f3cafeb24375ed9 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:13:14 +0100 Subject: [PATCH 12/25] git-wrapper: make command-line argument skipping more robust When we rewrite the command-line to call the *real* Git, we want to skip the first command-line parameter. The previous code worked in most circumstances, but was a bit fragile because it assumed that no fancy quoting would take place. In the next commit, we will want to have the option to skip more than just one command-line parameter, so we have to be much more careful with the command-line handling. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index c720023a1c..8b5ea998a8 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -116,7 +116,8 @@ static void setup_environment(LPWSTR exepath) * untouched. */ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, - LPWSTR prefix_args, int prefix_args_len, int is_git_command) + LPWSTR prefix_args, int prefix_args_len, int is_git_command, + int skip_arguments) { int wargc = 0; LPWSTR cmd = NULL, cmdline = NULL; @@ -137,10 +138,24 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, else wcscpy(cmd, L"git.exe"); - /* append all after first space after the initial parameter */ - p = wcschr(&cmdline[wcslen(wargv[0])], L' '); - if (p && *p) + /* skip wargv[0], append the remaining arguments */ + ++skip_arguments; + if (skip_arguments < wargc) { + int i; + for (i = 0, p = cmdline; p && *p && i < skip_arguments; i++) { + if (i) + while (isspace(*p)) + p++; + if (*p == L'"') + p++; + p += wcslen(wargv[i]); + if (*p == L'"') + p++; + while (*p && !isspace(*p)) + p++; + } wcscat(cmd, p); + } LocalFree(wargv); return cmd; @@ -245,7 +260,7 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, - is_git_command = 1, start_in_home = 0; + is_git_command = 1, start_in_home = 0, skip_arguments = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; LPWSTR cmd = NULL, dir = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; @@ -323,7 +338,7 @@ int main(void) if (needs_env_setup) setup_environment(exepath); cmd = fixup_commandline(exepath, &exep, &wait, - prefix_args, prefix_args_len, is_git_command); + prefix_args, prefix_args_len, is_git_command, skip_arguments); if (start_in_home) { int len = GetEnvironmentVariable(L"HOME", NULL, 0); From 64c9607a6519bd44089e4802d8bc56fdce947342 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:20:04 +0100 Subject: [PATCH 13/25] git-wrapper: optionally skip `cd $HOME` when configured via resources We recently added the ability to configure copies of the Git wrapper to launch custom command-lines, configured via plain old Windows resources. The main user is Git for Windows' `git-bash.exe`, of course. When the user double-clicks the `git bash` icon, it makes sense to start the Bash in the user's home directory. Third-party software, such as TortoiseGit or GitHub for Windows, may want to start the Git Bash in another directory, though. Now, when third-party software wants to call Git, they already have to construct a command-line, and can easily pass a command-line option `--no-cd` (which this commit introduces), and since that option is not available when the user double-clicks an icon on the Desktop or in the Explorer, let's keep the default to switch to the home directory if the `--no-cd` flag was not passed along. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 8b5ea998a8..2a34ebddd5 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -163,7 +163,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, - int *is_git_command, int *start_in_home) + int *is_git_command, int *start_in_home, int *skip_arguments) { int id, wargc; LPWSTR *wargv; @@ -252,7 +252,14 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, *prefix_args_len = wcslen(buf); *is_git_command = 0; - *start_in_home = 1; + wargv = CommandLineToArgvW(GetCommandLine(), &wargc); + if (wargc < 2 || wcscmp(L"--no-cd", wargv[1])) + *start_in_home = 1; + else { + *start_in_home = 0; + *skip_arguments = 1; + } + LocalFree(wargv); return 1; } @@ -278,7 +285,7 @@ int main(void) basename = exepath + wcslen(exepath) + 1; if (configure_via_resource(basename, exepath, exep, &prefix_args, &prefix_args_len, - &is_git_command, &start_in_home)) { + &is_git_command, &start_in_home, &skip_arguments)) { /* do nothing */ } else if (!wcsncmp(basename, L"git-", 4)) { From 352d19e8d7b4c111b8138c5b0d6bcfc15ef4d24b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 19:15:57 +0100 Subject: [PATCH 14/25] git-wrapper: Allow `git-cmd.exe` to add only /cmd/ to the PATH The idea of having the Git wrapper in the /cmd/ directory is to allow adding only a *tiny* set of executables to the search path, to allow minimal interference with other software applications. It is quite likely, for example, that other software applications require their own version of zlib1.dll and would not be overly happy to find the version Git for Windows ships. The /cmd/ directory also gives us the opportunity to let the Git wrapper handle the `gitk` script. It is a Tcl/Tk script that is not recognized by Windows, therefore calling `gitk` in `cmd.exe` would not work, even if we add all of Git for Windows' bin/ directories. So let's use the /cmd/ directory instead of adding /mingw??/bin/ and /usr/bin/ to the PATH when launching Git CMD. The way we implemented Git CMD is to embed the appropriate command line as string resource into a copy of the Git wrapper. Therefore we extended that syntax to allow for configuring a minimal search path. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 52 +++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 18 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 2a34ebddd5..57a26c20d6 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -37,7 +37,7 @@ static void print_error(LPCWSTR prefix, DWORD error_number) LocalFree((HLOCAL)buffer); } -static void setup_environment(LPWSTR exepath) +static void setup_environment(LPWSTR exepath, int full_path) { WCHAR msystem[64]; LPWSTR path2 = NULL; @@ -88,19 +88,23 @@ static void setup_environment(LPWSTR exepath) len = sizeof(WCHAR) * (len + 2 * MAX_PATH); path2 = (LPWSTR)malloc(len); wcscpy(path2, exepath); - PathAppend(path2, msystem_bin); - if (_waccess(path2, 0) != -1) { - /* We are in an MSys2-based setup */ - wcscat(path2, L";"); - wcscat(path2, exepath); - PathAppend(path2, L"usr\\bin;"); - } + if (!full_path) + PathAppend(path2, L"cmd;"); else { - /* Fall back to MSys1 paths */ - wcscpy(path2, exepath); - PathAppend(path2, L"bin;"); - wcscat(path2, exepath); - PathAppend(path2, L"mingw\\bin;"); + PathAppend(path2, msystem_bin); + if (_waccess(path2, 0) != -1) { + /* We are in an MSys2-based setup */ + wcscat(path2, L";"); + wcscat(path2, exepath); + PathAppend(path2, L"usr\\bin;"); + } + else { + /* Fall back to MSys1 paths */ + wcscpy(path2, exepath); + PathAppend(path2, L"bin;"); + wcscat(path2, exepath); + PathAppend(path2, L"mingw\\bin;"); + } } GetEnvironmentVariable(L"PATH", path2 + wcslen(path2), (len / sizeof(WCHAR)) - wcslen(path2)); @@ -163,9 +167,10 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, - int *is_git_command, int *start_in_home, int *skip_arguments) + int *is_git_command, int *start_in_home, int *full_path, + int *skip_arguments) { - int id, wargc; + int id, minimal_search_path, wargc; LPWSTR *wargv; #define BUFSIZE 65536 @@ -173,6 +178,7 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, int len; for (id = 0; ; id++) { + minimal_search_path = 0; len = LoadString(NULL, id, buf, BUFSIZE); if (!len) { @@ -190,6 +196,12 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, exit(1); } + if (!wcsncmp(L"MINIMAL_PATH=1 ", buf, 15)) { + minimal_search_path = 1; + memmove(buf, buf + 15, + sizeof(WCHAR) * (wcslen(buf + 15) + 1)); + } + buf[len] = L'\0'; if (!id) @@ -259,6 +271,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, *start_in_home = 0; *skip_arguments = 1; } + if (minimal_search_path) + *full_path = 0; LocalFree(wargv); return 1; @@ -267,7 +281,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, - is_git_command = 1, start_in_home = 0, skip_arguments = 0; + is_git_command = 1, start_in_home = 0, full_path = 1, + skip_arguments = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; LPWSTR cmd = NULL, dir = NULL, exep = exe, prefix_args = NULL, basename; UINT codepage = 0; @@ -285,7 +300,8 @@ int main(void) basename = exepath + wcslen(exepath) + 1; if (configure_via_resource(basename, exepath, exep, &prefix_args, &prefix_args_len, - &is_git_command, &start_in_home, &skip_arguments)) { + &is_git_command, &start_in_home, + &full_path, &skip_arguments)) { /* do nothing */ } else if (!wcsncmp(basename, L"git-", 4)) { @@ -343,7 +359,7 @@ int main(void) } if (needs_env_setup) - setup_environment(exepath); + setup_environment(exepath, full_path); cmd = fixup_commandline(exepath, &exep, &wait, prefix_args, prefix_args_len, is_git_command, skip_arguments); From e949d0176403a4a13662043aba0e49c73b7922c0 Mon Sep 17 00:00:00 2001 From: nalla Date: Mon, 30 Mar 2015 15:10:38 +0100 Subject: [PATCH 15/25] git-wrapper: support git.exe and gitk.exe to be in a spaced dir When *Git for Windows* is installed into a directory that has spaces in it, e.g. `C:\Program Files\Git`, the `git-wrapper` appends this directory unquoted when fixing up the command line. To resolve this, just quote the provided `execpath`. Signed-off-by: nalla --- compat/win32/git-wrapper.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 57a26c20d6..e17b985849 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -133,7 +133,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, (wcslen(cmdline) + prefix_args_len + 1 + MAX_PATH)); if (prefix_args) { if (is_git_command) - _swprintf(cmd, L"%s\\%s %.*s", exepath, L"git.exe", + _swprintf(cmd, L"\"%s\\%s\" %.*s", exepath, L"git.exe", prefix_args_len, prefix_args); else _swprintf(cmd, L"%.*s", prefix_args_len, prefix_args); @@ -343,7 +343,7 @@ int main(void) /* set the default exe module */ wcscpy(exe, exepath); - wcscpy(buffer, exepath); + swprintf(buffer, BUFSIZE, L"\"%s\"", exepath); PathAppend(exe, msystem_bin); PathAppend(exe, L"wish.exe"); if (_waccess(exe, 0) != -1) From 944192580b4e2b69f29222d8aea50c2c05580893 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 2 Apr 2015 11:23:34 +0100 Subject: [PATCH 16/25] git-wrapper: serve as git-gui.exe, too To avoid that ugly Console window when calling \cmd\git.exe gui... To avoid confusion with builtins, we need to move the code block handling gitk (and now git-gui, too) to intercept before git-gui is mistaken for a builtin. Unfortunately, git-gui is in libexec/git-core/ while gitk is in bin/, therefore we need slightly more adjustments than just moving and augmenting the gitk case. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index e17b985849..29777d16cd 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -304,6 +304,32 @@ int main(void) &full_path, &skip_arguments)) { /* do nothing */ } + else if (!wcscmp(basename, L"git-gui.exe")) { + static WCHAR buffer[BUFSIZE]; + if (!PathRemoveFileSpec(exepath)) { + fwprintf(stderr, + L"Invalid executable path: %s\n", exepath); + ExitProcess(1); + } + + /* set the default exe module */ + wcscpy(exe, exepath); + PathAppend(exe, msystem_bin); + PathAppend(exe, L"wish.exe"); + if (_waccess(exe, 0) != -1) + swprintf(buffer, BUFSIZE, + L"\"%s\\%.*s\\libexec\\git-core\"", + exepath, wcslen(msystem_bin) - 4, msystem_bin); + else { + wcscpy(exe, exepath); + PathAppend(exe, L"mingw\\bin\\wish.exe"); + swprintf(buffer, BUFSIZE, + L"\"%s\\mingw\\libexec\\git-core\"", exepath); + } + PathAppend(buffer, L"git-gui"); + prefix_args = buffer; + prefix_args_len = wcslen(buffer); + } else if (!wcsncmp(basename, L"git-", 4)) { needs_env_setup = 0; From 3da87d9188f5cde03e0a605eb14f789a34a1bf9f Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:20:04 +0100 Subject: [PATCH 17/25] git-wrapper: interpret `--cd=` when configured via resources This change accompanies the `--no-cd` option when configured via resources. It is required to support `Git Bash Here`: when right-clicking an icon in the Explorer to start a Bash, the working directory is actually the directory that is displayed in the Explorer. That means if the clicked icon actually refers to a directory, the working directory would be its *parent* directory. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 29777d16cd..2d55554c80 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -167,7 +167,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, - int *is_git_command, int *start_in_home, int *full_path, + int *is_git_command, LPWSTR *working_directory, int *full_path, int *skip_arguments) { int id, minimal_search_path, wargc; @@ -264,12 +264,17 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, *prefix_args_len = wcslen(buf); *is_git_command = 0; + *working_directory = (LPWSTR) 1; wargv = CommandLineToArgvW(GetCommandLine(), &wargc); - if (wargc < 2 || wcscmp(L"--no-cd", wargv[1])) - *start_in_home = 1; - else { - *start_in_home = 0; - *skip_arguments = 1; + if (wargc > 1) { + if (!wcscmp(L"--no-cd", wargv[1])) { + *working_directory = NULL; + *skip_arguments = 1; + } + else if (!wcsncmp(L"--cd=", wargv[1], 5)) { + *working_directory = wcsdup(wargv[1] + 5); + *skip_arguments = 1; + } } if (minimal_search_path) *full_path = 0; @@ -281,10 +286,10 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, - is_git_command = 1, start_in_home = 0, full_path = 1, - skip_arguments = 0; + is_git_command = 1, full_path = 1, skip_arguments = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; - LPWSTR cmd = NULL, dir = NULL, exep = exe, prefix_args = NULL, basename; + LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; + LPWSTR working_directory = NULL; UINT codepage = 0; /* Determine MSys2-based Git path. */ @@ -300,7 +305,7 @@ int main(void) basename = exepath + wcslen(exepath) + 1; if (configure_via_resource(basename, exepath, exep, &prefix_args, &prefix_args_len, - &is_git_command, &start_in_home, + &is_git_command, &working_directory, &full_path, &skip_arguments)) { /* do nothing */ } @@ -389,12 +394,12 @@ int main(void) cmd = fixup_commandline(exepath, &exep, &wait, prefix_args, prefix_args_len, is_git_command, skip_arguments); - if (start_in_home) { + if (working_directory == (LPWSTR)1) { int len = GetEnvironmentVariable(L"HOME", NULL, 0); if (len) { - dir = malloc(sizeof(WCHAR) * len); - GetEnvironmentVariable(L"HOME", dir, len); + working_directory = malloc(sizeof(WCHAR) * len); + GetEnvironmentVariable(L"HOME", working_directory, len); } } @@ -433,7 +438,7 @@ int main(void) TRUE, /* handles inheritable? */ creation_flags, NULL, /* environment: use parent */ - dir, /* starting directory: use parent */ + working_directory, /* use parent's */ &si, &pi); if (br) { if (wait) From d453338e7687f447cbc459f19dc90d04c1b64ea3 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 15 Apr 2015 15:18:14 +0300 Subject: [PATCH 18/25] git-wrapper: case-insensitive path comparison --- compat/win32/git-wrapper.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 2d55554c80..1d77a4e5ae 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -309,7 +309,7 @@ int main(void) &full_path, &skip_arguments)) { /* do nothing */ } - else if (!wcscmp(basename, L"git-gui.exe")) { + else if (!wcsicmp(basename, L"git-gui.exe")) { static WCHAR buffer[BUFSIZE]; if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr, @@ -335,20 +335,20 @@ int main(void) prefix_args = buffer; prefix_args_len = wcslen(buffer); } - else if (!wcsncmp(basename, L"git-", 4)) { + else if (!wcsnicmp(basename, L"git-", 4)) { needs_env_setup = 0; /* Call a builtin */ prefix_args = basename + 4; prefix_args_len = wcslen(prefix_args); - if (!wcscmp(prefix_args + prefix_args_len - 4, L".exe")) + if (!wcsicmp(prefix_args + prefix_args_len - 4, L".exe")) prefix_args_len -= 4; /* set the default exe module */ wcscpy(exe, exepath); PathAppend(exe, L"git.exe"); } - else if (!wcscmp(basename, L"git.exe")) { + else if (!wcsicmp(basename, L"git.exe")) { if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr, L"Invalid executable path: %s\n", exepath); @@ -364,7 +364,7 @@ int main(void) PathAppend(exe, L"bin\\git.exe"); } } - else if (!wcscmp(basename, L"gitk.exe")) { + else if (!wcsicmp(basename, L"gitk.exe")) { static WCHAR buffer[BUFSIZE]; if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr, From f54ed96b915f2e57ca4957c5efa51ebb79cc1bda Mon Sep 17 00:00:00 2001 From: Karsten Blees Date: Mon, 6 Apr 2015 20:54:46 +0200 Subject: [PATCH 19/25] git-wrapper: fix HOME initialization git-wrapper fails to initialize HOME correctly if $HOMEDRIVE$HOMEPATH points to a disconnected network drive. Check if the directory exists before using $HOMEDRIVE$HOMEPATH. Signed-off-by: Karsten Blees --- compat/win32/git-wrapper.c | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 1d77a4e5ae..d088d7d05f 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -56,13 +56,29 @@ static void setup_environment(LPWSTR exepath, int full_path) if (!GetEnvironmentVariable(L"PLINK_PROTOCOL", NULL, 0)) SetEnvironmentVariable(L"PLINK_PROTOCOL", L"ssh"); - /* set HOME to %HOMEDRIVE%%HOMEPATH% or %USERPROFILE% + /* + * set HOME to %HOMEDRIVE%%HOMEPATH% or %USERPROFILE% * With roaming profiles: HOMEPATH is the roaming location and * USERPROFILE is the local location */ if (!GetEnvironmentVariable(L"HOME", NULL, 0)) { LPWSTR e = NULL; len = GetEnvironmentVariable(L"HOMEPATH", NULL, 0); + if (len) { + DWORD attr, drvlen = GetEnvironmentVariable(L"HOMEDRIVE", NULL, 0); + e = (LPWSTR)malloc(sizeof(WCHAR) * (drvlen + len)); + drvlen = GetEnvironmentVariable(L"HOMEDRIVE", e, drvlen); + GetEnvironmentVariable(L"HOMEPATH", e + drvlen, len); + /* check if the path exists */ + attr = GetFileAttributesW(e); + if (attr != INVALID_FILE_ATTRIBUTES + && (attr & FILE_ATTRIBUTE_DIRECTORY)) + SetEnvironmentVariable(L"HOME", e); + else + len = 0; /* use USERPROFILE */ + free(e); + } + if (len == 0) { len = GetEnvironmentVariable(L"USERPROFILE", NULL, 0); if (len != 0) { @@ -72,15 +88,6 @@ static void setup_environment(LPWSTR exepath, int full_path) free(e); } } - else { - int n; - len += GetEnvironmentVariable(L"HOMEDRIVE", NULL, 0); - e = (LPWSTR)malloc(sizeof(WCHAR) * (len + 2)); - n = GetEnvironmentVariable(L"HOMEDRIVE", e, len); - GetEnvironmentVariable(L"HOMEPATH", &e[n], len-n); - SetEnvironmentVariable(L"HOME", e); - free(e); - } } /* extend the PATH */ From a80664b5626e36ab8d5288903276e656951563fe Mon Sep 17 00:00:00 2001 From: Karsten Blees Date: Mon, 6 Apr 2015 21:20:46 +0200 Subject: [PATCH 20/25] git-wrapper: remove redundant TERM initialization Remove redundant TERM initialization from git-wrapper in favor of TERM initialization in git itself. Signed-off-by: Karsten Blees --- compat/win32/git-wrapper.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index d088d7d05f..b5e5ae899e 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -48,10 +48,6 @@ static void setup_environment(LPWSTR exepath, int full_path) L"MINGW%d", (int) sizeof(void *) * 8); SetEnvironmentVariable(L"MSYSTEM", msystem); - /* if not set, set TERM to cygwin */ - if (!GetEnvironmentVariable(L"TERM", NULL, 0)) - SetEnvironmentVariable(L"TERM", L"cygwin"); - /* if not set, set PLINK_PROTOCOL to ssh */ if (!GetEnvironmentVariable(L"PLINK_PROTOCOL", NULL, 0)) SetEnvironmentVariable(L"PLINK_PROTOCOL", L"ssh"); From e7c1ba0c36f322c990642bacabc467a14fa854d7 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 4 May 2015 06:34:52 -0700 Subject: [PATCH 21/25] git-wrapper: prepare to allow more options than MINIMAL_PATH With the resource-driven command-line configuration, we introduced the option to ensure that only the PATH environment variable is edited only minimally, i.e. only /cmd/ is added (as appropriate for _Git CMD_). We are about to add another option, so let's refactor the equivalent of Git's `strip_prefix()` function; It is not *quite* the same because we have to `memmove()` the remainder to the beginning of the buffer. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index b5e5ae899e..20e8936eef 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -168,6 +168,22 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, return cmd; } +static int strip_prefix(LPWSTR str, int *len, LPCWSTR prefix) +{ + LPWSTR start = str; + do { + if (str - start > *len) + return 0; + if (!*prefix) { + *len -= str - start; + memmove(start, str, + sizeof(WCHAR) * (wcslen(str) + 1)); + return 1; + } + } while (*str++ == *prefix++); + return 0; +} + static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, int *is_git_command, LPWSTR *working_directory, int *full_path, @@ -199,10 +215,11 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, exit(1); } - if (!wcsncmp(L"MINIMAL_PATH=1 ", buf, 15)) { - minimal_search_path = 1; - memmove(buf, buf + 15, - sizeof(WCHAR) * (wcslen(buf + 15) + 1)); + for (;;) { + if (strip_prefix(buf, &len, L"MINIMAL_PATH=1 ")) + minimal_search_path = 1; + else + break; } buf[len] = L'\0'; From 13f188f42604dc03a82d26bdd0bf7e50174667f2 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 4 May 2015 03:18:20 -0700 Subject: [PATCH 22/25] git wrapper: allow _Git Bash_ to run with a newly allocated console With a recent change in Cygwin (which is the basis of the msys2-runtime), a GUI process desiring to launch an MSys2 executable needs to allocate a console for the new process (otherwise the process will just hang on Windows XP). _Git Bash_ is such a GUI process. While at it, use correct handles when inheriting the stdin/stdout/stderr handles: `GetStdHandle()` returns NULL for invalid handles, but the STARTUPINFO must specify `INVALID_HANDLE_VALUE` instead. Originally, the hope was that only this `NULL` => `INVALID_HANDLE_VALUE` conversion would be required to fix the Windows XP issue mentioned above (extensive debugging revealed that starting _Git Bash_ on Windows XP would yield invalid handles for `stdin` and `stderr`, but *not* for `stdout`). However, while _Git Bash_ eventually showed a `mintty` when not allocating a new console, it took around one second to show it, and several seconds to close it. So let's just include both fixes. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 35 ++++++++++++++++++++++++----------- 1 file changed, 24 insertions(+), 11 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 20e8936eef..d57095a605 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -187,9 +187,9 @@ static int strip_prefix(LPWSTR str, int *len, LPCWSTR prefix) static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, int *is_git_command, LPWSTR *working_directory, int *full_path, - int *skip_arguments) + int *skip_arguments, int *allocate_console) { - int id, minimal_search_path, wargc; + int id, minimal_search_path, needs_a_console, wargc; LPWSTR *wargv; #define BUFSIZE 65536 @@ -198,6 +198,7 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, for (id = 0; ; id++) { minimal_search_path = 0; + needs_a_console = 0; len = LoadString(NULL, id, buf, BUFSIZE); if (!len) { @@ -218,6 +219,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, for (;;) { if (strip_prefix(buf, &len, L"MINIMAL_PATH=1 ")) minimal_search_path = 1; + else if (strip_prefix(buf, &len, L"ALLOC_CONSOLE=1 ")) + needs_a_console = 1; else break; } @@ -298,6 +301,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, } if (minimal_search_path) *full_path = 0; + if (needs_a_console) + *allocate_console = 1; LocalFree(wargv); return 1; @@ -306,7 +311,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, - is_git_command = 1, full_path = 1, skip_arguments = 0; + is_git_command = 1, full_path = 1, skip_arguments = 0, + allocate_console = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; LPWSTR working_directory = NULL; @@ -326,11 +332,12 @@ int main(void) if (configure_via_resource(basename, exepath, exep, &prefix_args, &prefix_args_len, &is_git_command, &working_directory, - &full_path, &skip_arguments)) { + &full_path, &skip_arguments, &allocate_console)) { /* do nothing */ } else if (!wcsicmp(basename, L"git-gui.exe")) { static WCHAR buffer[BUFSIZE]; + allocate_console = 1; if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr, L"Invalid executable path: %s\n", exepath); @@ -386,6 +393,7 @@ int main(void) } else if (!wcsicmp(basename, L"gitk.exe")) { static WCHAR buffer[BUFSIZE]; + allocate_console = 1; if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr, L"Invalid executable path: %s\n", exepath); @@ -437,16 +445,20 @@ int main(void) ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); - console_handle = CreateFile(L"CONOUT$", GENERIC_WRITE, + if (allocate_console) + creation_flags |= CREATE_NEW_CONSOLE; + else if ((console_handle = CreateFile(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, NULL); - if (console_handle != INVALID_HANDLE_VALUE) + FILE_ATTRIBUTE_NORMAL, NULL)) != + INVALID_HANDLE_VALUE) CloseHandle(console_handle); else { +#define STD_HANDLE(field, id) si.hStd##field = GetStdHandle(STD_##id); if (!si.hStd##field) si.hStd##field = INVALID_HANDLE_VALUE + STD_HANDLE(Input, INPUT_HANDLE); + STD_HANDLE(Output, OUTPUT_HANDLE); + STD_HANDLE(Error, ERROR_HANDLE); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - si.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); - si.hStdError = GetStdHandle(STD_ERROR_HANDLE); + creation_flags |= CREATE_NO_WINDOW; } @@ -455,7 +467,8 @@ int main(void) cmd, /* modified command line */ NULL, /* process handle inheritance */ NULL, /* thread handle inheritance */ - TRUE, /* handles inheritable? */ + /* handles inheritable? */ + allocate_console ? FALSE : TRUE, creation_flags, NULL, /* environment: use parent */ working_directory, /* use parent's */ From 5a2b6f0302cdc9473f463de97d3499eeb721009b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 6 May 2015 12:09:03 +0000 Subject: [PATCH 23/25] git-wrapper: support the non-mintty fall-back for Git Bash When we fall back to starting the Git Bash in the regular Windows console, we need to show said console's window... So let's introduce yet another configuration knob for use via string resources. Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index d57095a605..093d20838e 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -187,9 +187,9 @@ static int strip_prefix(LPWSTR str, int *len, LPCWSTR prefix) static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, LPWSTR *prefix_args, int *prefix_args_len, int *is_git_command, LPWSTR *working_directory, int *full_path, - int *skip_arguments, int *allocate_console) + int *skip_arguments, int *allocate_console, int *show_console) { - int id, minimal_search_path, needs_a_console, wargc; + int id, minimal_search_path, needs_a_console, no_hide, wargc; LPWSTR *wargv; #define BUFSIZE 65536 @@ -199,6 +199,7 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, for (id = 0; ; id++) { minimal_search_path = 0; needs_a_console = 0; + no_hide = 0; len = LoadString(NULL, id, buf, BUFSIZE); if (!len) { @@ -221,6 +222,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, minimal_search_path = 1; else if (strip_prefix(buf, &len, L"ALLOC_CONSOLE=1 ")) needs_a_console = 1; + else if (strip_prefix(buf, &len, L"SHOW_CONSOLE=1 ")) + no_hide = 1; else break; } @@ -303,6 +306,8 @@ static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep, *full_path = 0; if (needs_a_console) *allocate_console = 1; + if (no_hide) + *show_console = 1; LocalFree(wargv); return 1; @@ -312,7 +317,7 @@ int main(void) { int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1, is_git_command = 1, full_path = 1, skip_arguments = 0, - allocate_console = 0; + allocate_console = 0, show_console = 0; WCHAR exepath[MAX_PATH], exe[MAX_PATH]; LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; LPWSTR working_directory = NULL; @@ -332,7 +337,8 @@ int main(void) if (configure_via_resource(basename, exepath, exep, &prefix_args, &prefix_args_len, &is_git_command, &working_directory, - &full_path, &skip_arguments, &allocate_console)) { + &full_path, &skip_arguments, &allocate_console, + &show_console)) { /* do nothing */ } else if (!wcsicmp(basename, L"git-gui.exe")) { @@ -445,7 +451,7 @@ int main(void) ZeroMemory(&si, sizeof(STARTUPINFO)); si.cb = sizeof(STARTUPINFO); - if (allocate_console) + if (allocate_console | show_console) creation_flags |= CREATE_NEW_CONSOLE; else if ((console_handle = CreateFile(L"CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, @@ -462,6 +468,10 @@ int main(void) creation_flags |= CREATE_NO_WINDOW; } + if (show_console) { + si.dwFlags |= STARTF_USESHOWWINDOW; + si.wShowWindow = SW_SHOW; + } br = CreateProcess(/* module: null means use command line */ exep, cmd, /* modified command line */ From e25a9fec7dfa0b6f418844735ca7d075207391fb Mon Sep 17 00:00:00 2001 From: Karsten Blees Date: Fri, 8 May 2015 00:31:33 +0200 Subject: [PATCH 24/25] git-wrapper: don't set the console input code page Using different code pages for console input (SetConsoleCP()) and console output (SetConsoleOutputCP()) doesn't make much sense and may be hazardous for native Windows programs. Git uses UTF-8 internally, so it actually needs 'SetConsoleCP(CP_UTF8)' rather than 'SetConsoleCP(GetACP())'. However, ReadFile() / ReadConsoleA() are broken with CP_UTF8 (and thus any higher level APIs such as fgetc(), getchar() etc.). Unicode-aware console input would have to be implemented via mingw_* wrappers using ReadConsoleW(). As Git typically launches an editor for anything more complex than ASCII-only, yes/no-style questions, this is currently not a problem. Drop 'SetConsoleCP()' from the git-wrapper, so that input and output code pages stay in sync. Signed-off-by: Karsten Blees --- compat/win32/git-wrapper.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 093d20838e..f888064def 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -321,7 +321,6 @@ int main(void) WCHAR exepath[MAX_PATH], exe[MAX_PATH]; LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; LPWSTR working_directory = NULL; - UINT codepage = 0; /* Determine MSys2-based Git path. */ swprintf(msystem_bin, sizeof(msystem_bin), @@ -437,10 +436,6 @@ int main(void) } } - /* set the console to ANSI/GUI codepage */ - codepage = GetConsoleCP(); - SetConsoleCP(GetACP()); - { STARTUPINFO si; PROCESS_INFORMATION pi; @@ -499,7 +494,5 @@ int main(void) free(cmd); - /* reset the console codepage */ - SetConsoleCP(codepage); ExitProcess(r); } From e028d3eb3bbaf4bba08001348b59fbf3de27d8ac Mon Sep 17 00:00:00 2001 From: Nico Rieck Date: Sun, 31 May 2015 01:18:22 +0200 Subject: [PATCH 25/25] git-wrapper: let git gui run in the background This fixes https://github.com/git-for-windows/git/issues/172. Signed-off-by: Nico Rieck Signed-off-by: Johannes Schindelin --- compat/win32/git-wrapper.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index f888064def..1e214af2a4 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -163,6 +163,10 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait, } wcscat(cmd, p); } + + if (wargc > 1 && !wcscmp(wargv[1], L"gui")) + *wait = 0; + LocalFree(wargv); return cmd; @@ -342,6 +346,7 @@ int main(void) } else if (!wcsicmp(basename, L"git-gui.exe")) { static WCHAR buffer[BUFSIZE]; + wait = 0; allocate_console = 1; if (!PathRemoveFileSpec(exepath)) { fwprintf(stderr,