From ee7db55c95812ef49d25a5f2393ad4dd982481f2 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 834d0ab29a1ff401e25ae3ad26c50cfabbf29ddd 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 c0e0247852fafb46de6475a4ba5efb1cd4a26136 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 1ee96a5f4f4788a8437d76f43445dbc79a98ae66 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 dfd06fa8c29603ba39776c654acf1194e3cb82f4 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 3241bc1c80fa65945e5a95be902f17b649a76f61 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 5650d826a5f896df69c5d43589b39b69cbf2a217 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 e22635800a2c93c0cf646d323d9ad6c0b63151c7 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 fd46a9a020d2e2b25528c50ac124543cb5a86b36 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 e1d7e73f09549dd045bd1d6e1a4994674ff8814d 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 8a90d1487ed0eb02470c0b078f542dfcdd4aaf29 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 83af9dd2685157e6cccbf59fc73f739012be40ed 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 8895c149d3bc8d1a69d98bbd7f3f8ae2abc7b4b5 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 7b155aed7f58170dfb57f751d35fda85bd2e53e0 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 951a1ddddac3271187bc10f99d5c3cb1937e2d30 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 96d5174fd6c36fa5760b5153260413e65c6bd6c6 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 4adfe9fbfd74a5cb58d8a27467708be77e1d9037 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 49b8b69c9ba5fd19256d9132fb2c762d8481123d 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 6a403895f8cd1392eec5a527d22fedd8ff188448 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 3932221ba50bde9f95008ca5b1f38e92ccb20c66 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 4fb20c66ab88a4a94f799e6eb850783e28c403c5 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 97d7db9beaf633d9a8500fbf49a6e652a5562830 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 851e2c9802367a9b8db4aa45050a08b3b5debcbe 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 d554ce9c04253e6dd0aaa21abe33adfd84b598c5 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 657ddca0012623c79873ea89efea9d571ac54eb2 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,