From edbcf3d73c12b3cc20e1bd08b2d90dcbefbe789c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 14:16:43 +0100 Subject: [PATCH 01/21] 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 4b79f5e397aaa6ce5e1be28a8fbaffac765bd66b Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 15:36:41 +0100 Subject: [PATCH 02/21] 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. Signed-off-by: Johannes Schindelin --- config.mak.uname | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/config.mak.uname b/config.mak.uname index 0ab7f286d6..fefa4e6fb2 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -564,6 +564,14 @@ 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) -Wall -s -o $@ $^ -lshell32 -lshlwapi + +compat/win32/git-wrapper.o: %.o: %.c + $(QUIET_CC)$(CC) -o $*.o -c -Wall -Wwrite-strings $< + endif endif ifeq ($(uname_S),QNX) From c743356dd83b0a0638c91c73a2c9d1c77f0c7479 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 15:35:21 +0100 Subject: [PATCH 03/21] 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 dd078571e9fd691772614c742c343df31197d116 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 16:00:02 +0100 Subject: [PATCH 04/21] 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 157abcf7e4c934c13f40c56070b9bdf60788f5e1 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Fri, 13 Mar 2015 16:40:05 +0100 Subject: [PATCH 05/21] 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 20058f1de7..cc6bd7c32c 100644 --- a/Makefile +++ b/Makefile @@ -1658,11 +1658,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 @@ -2225,6 +2231,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 "$$exectir/git$X" "$$exectir/$$p" 2>/dev/null || \ + ln -s "git$X" "$$exectir/$$p" 2>/dev/null || \ + cp "$$exectir/git$X" "$$exectir/$$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)' @@ -2263,17 +2287,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 fefa4e6fb2..a7e6fbbd9b 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -565,6 +565,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) -Wall -s -o $@ $^ -lshell32 -lshlwapi From 27a48743677457e88e1266dec3bdadbd23a1c6fa Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Mon, 23 Mar 2015 14:31:42 +0100 Subject: [PATCH 06/21] 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 dcef1dd5d0195c4f3c5cb17d0714da627d84f7fc Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Wed, 25 Mar 2015 16:34:48 +0100 Subject: [PATCH 07/21] 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 ef43288ca1f64eddada458a117791277a4cb3df6 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 12:11:05 +0100 Subject: [PATCH 08/21] 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 8458e5c2662fe911d330dc00c5b840471e4dfa72 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 26 Mar 2015 17:06:22 +0100 Subject: [PATCH 09/21] 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 | 106 ++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c index 1d9adebe4b..e489847a9d 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -173,6 +173,105 @@ 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 = 0, 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"Copy %s to edit-res.exe and call\n" + L"\n\tedit-res.exe command %s " + L"\"\"\n", + basename, basename); + 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 +291,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 18f4d66eddea96d77143705822267e1e1fe66fa8 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 15:06:22 +0100 Subject: [PATCH 10/21] 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 e489847a9d..a304ab1159 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -325,6 +325,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 1e0154b0ccbe744145407a997def506bcbdb7751 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 17:29:01 +0100 Subject: [PATCH 11/21] 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 a304ab1159..f521d51aa6 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 85df2534025afc698d26b9c3407439b8d9579c05 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:13:14 +0100 Subject: [PATCH 12/21] 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 f521d51aa6..594a8330d2 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; @@ -248,7 +263,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; @@ -326,7 +341,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 9e1c906bb89d7e9e9724eec6df85951ba3cbee7c Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:20:04 +0100 Subject: [PATCH 13/21] 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 594a8330d2..82efea862a 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 = 0, wargc; LPWSTR *wargv; @@ -255,7 +255,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; } @@ -281,7 +288,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 f752904f671adcb4521b1fbc5be608a39f471130 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 19:15:57 +0100 Subject: [PATCH 14/21] 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 82efea862a..6be155214b 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 = 0, wargc; + int id = 0, 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) { @@ -193,6 +199,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) @@ -262,6 +274,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; @@ -270,7 +284,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; @@ -288,7 +303,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)) { @@ -346,7 +362,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 afdbd9f1316d529b4bb023585886db4d7b710e9d Mon Sep 17 00:00:00 2001 From: nalla Date: Thu, 19 Mar 2015 16:33:44 +0100 Subject: [PATCH 15/21] mingw: Support `git_terminal_prompt` with more terminals MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `git_terminal_prompt()` function expects the terminal window to be attached to a Win32 Console. However, this is not the case with terminal windows other than `cmd.exe`'s, e.g. with MSys2's own `mintty`. Non-cmd terminals such as `mintty` still have to have a Win32 Console to be proper console programs, but have to hide the Win32 Console to be able to provide more flexibility (such as being resizeable not only vertically but also horizontally). By writing to that Win32 Console, `git_terminal_prompt()` manages only to send the prompt to nowhere and to wait for input from a Console to which the user has no access. This commit introduces a function specifically to support `mintty` -- or other terminals that are compatible with MSys2's `/dev/tty` emulation. We use the `TERM` environment variable as an indicator for that: if the value starts with "xterm" (such as `mintty`'s "xterm_256color"), we prefer to let `xterm_prompt()` handle the user interaction. To handle the case when standard input/output are redirected – as is the case when pushing via HTTPS: `git-remote-https`' standard input and output are pipes from/to the main Git executable – we make use of the `MSYS_TTY_HANDLES` environment variable that was introduced to fix another bug in MSys2-based Git: this environment variable contains the Win32 `HANDLE`s of the standard input, output and error as originally passed from MSys2 to the Git executable, enclosed within space characters, skipping handles that do not refer to the terminal window (e.g. when they were redirected). We will only use those handles when that environment variable lists all three handles because then we can be 100% certain that we are running inside a terminal window, and that we know exactly which Win32 handles to use to communicate with it. Helped-by: Johannes Schindelin Signed-off-by: nalla --- compat/terminal.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/compat/terminal.c b/compat/terminal.c index 313897d581..bc18833a7b 100644 --- a/compat/terminal.c +++ b/compat/terminal.c @@ -1,4 +1,6 @@ +#include #include "git-compat-util.h" +#include "run-command.h" #include "compat/terminal.h" #include "sigchain.h" #include "strbuf.h" @@ -91,6 +93,53 @@ static int disable_echo(void) return 0; } +static char *xterm_prompt(const char *prompt, int echo) +{ + const char *env = getenv("MSYS_TTY_HANDLES"); + const char *echo_off[] = { "sh", "-c", "stty -echo Date: Mon, 30 Mar 2015 15:10:38 +0100 Subject: [PATCH 16/21] 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 6be155214b..58f1aecf27 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); @@ -346,7 +346,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 62ba61e49fe3ccc6ee99ca3101b96189dc6633a4 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Thu, 2 Apr 2015 11:23:34 +0100 Subject: [PATCH 17/21] 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 58f1aecf27..561483a212 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -307,6 +307,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 57860dde5f87dfcceddec8780fae57c769f50f49 Mon Sep 17 00:00:00 2001 From: Johannes Schindelin Date: Sat, 28 Mar 2015 18:20:04 +0100 Subject: [PATCH 18/21] 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 561483a212..714c3d8ef8 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 = 0, minimal_search_path, wargc; @@ -267,12 +267,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; @@ -284,10 +289,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. */ @@ -303,7 +308,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 */ } @@ -392,12 +397,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); } } @@ -436,7 +441,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 32aedd345c3ef73849ddf9d29b35134cd15dd708 Mon Sep 17 00:00:00 2001 From: Vitaly Takmazov Date: Wed, 15 Apr 2015 15:18:14 +0300 Subject: [PATCH 19/21] 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 714c3d8ef8..393f7d6723 100644 --- a/compat/win32/git-wrapper.c +++ b/compat/win32/git-wrapper.c @@ -312,7 +312,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, @@ -338,20 +338,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); @@ -367,7 +367,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 194e094d4f823cde4e48ffa7f63a7b0046cf73f3 Mon Sep 17 00:00:00 2001 From: Karsten Blees Date: Mon, 6 Apr 2015 20:54:46 +0200 Subject: [PATCH 20/21] 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 393f7d6723..8c328e1083 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 d5373499375e9b96cafbcabc69640ed0702b6b63 Mon Sep 17 00:00:00 2001 From: Karsten Blees Date: Mon, 6 Apr 2015 21:20:46 +0200 Subject: [PATCH 21/21] 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 8c328e1083..8534e1c12d 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");