From 8c8db314996b96aadc946c63280d83c21ee0c554 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 df04b69460de009bf4df5fb9b18352816bd92398 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 a88f13989a..fd3d266056 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -572,6 +572,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 1f2cd03c94690515363c2b0ccbaa7b64f24e5277 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 9c69393cd90ebdc9b3d08bc369eaacb7be26ea71 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 fe650b440a8b06d6fcc2017f112f86360bd9c2b2 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 de5a030256..0cd24639b2 100644 --- a/Makefile +++ b/Makefile @@ -1724,11 +1724,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 @@ -2297,6 +2303,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)' @@ -2335,17 +2359,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 fd3d266056..c6f785bef6 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -573,6 +573,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 64be96bc5e2ae5ea646f86d800c0b97030bbfe8e 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 7a664c4474773a6c424042e8e8643f585b4a4a93 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 7cfb33877d1de07bdf7f26d1b7caf603f6ade923 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 528d5462556b61245b3c44830c74ebd8036564ff 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 e05aa211686fb80cbc2301f6dfa3625a81b88479 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 6ac8756f8309931753e79d2da769d7899d2a9088 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 b4e53e95522494fe559fb3da6229aa4dc7fbf42d 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 90aab5a79b1b4f08355966b41e4c068608aaa0ef 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 e7dddf4e86fb5aff056edabd4eaff9748470c7cb 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 f021c40bbe9f450a24604f5e815caecb7b491d30 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 2215c4c787642f165e0266b1800e783ea0f62f7f 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 6cf748305cdd0e0d5b54f30a6a3284903bbce97c 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 c49e61be17d2aa777424b3471428f3095ea86142 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 b621a9d8dff12b1b4e66f9b559bd67e3629c26e6 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 376ef32e20bd6444350a57573dbbf2c2649e371f 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 5a346950261c08e4cf61dd965a4374627ad6a98c 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 48b84040f14f55a02bc1c8c50f1a74a20609d455 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 328db4c32cd2fc3c4ed524141f7e1bbafec486ae 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 d16513f1f80abd179d0f49cdf985f977b888907c 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 c2b4f19dff0a2d389c669d7d830ba2faffb76813 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,