diff --git a/Makefile b/Makefile index 0286bb58b8f3d2..8f584ede5ea4b4 100644 --- a/Makefile +++ b/Makefile @@ -1691,11 +1691,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 @@ -2260,6 +2266,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)' @@ -2298,17 +2322,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/compat/win32/git-wrapper.c b/compat/win32/git-wrapper.c new file mode 100644 index 00000000000000..1e214af2a4c4a6 --- /dev/null +++ b/compat/win32/git-wrapper.c @@ -0,0 +1,503 @@ +/* + * 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 +#include + +static WCHAR msystem_bin[64]; + +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); +} + +static void setup_environment(LPWSTR exepath, int full_path) +{ + WCHAR msystem[64]; + LPWSTR path2 = NULL; + int len; + + /* Set MSYSTEM */ + swprintf(msystem, sizeof(msystem), + L"MINGW%d", (int) sizeof(void *) * 8); + SetEnvironmentVariable(L"MSYSTEM", msystem); + + /* 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) { + 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) { + e = (LPWSTR)malloc(len * sizeof(WCHAR)); + GetEnvironmentVariable(L"USERPROFILE", e, len); + 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); + if (!full_path) + PathAppend(path2, L"cmd;"); + else { + 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); + +} + +/* + * 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, + LPWSTR prefix_args, int prefix_args_len, int is_git_command, + int skip_arguments) +{ + int wargc = 0; + LPWSTR cmd = NULL, cmdline = NULL; + LPWSTR *wargv = NULL, p = NULL; + + cmdline = GetCommandLine(); + wargv = CommandLineToArgvW(cmdline, &wargc); + cmd = (LPWSTR)malloc(sizeof(WCHAR) * + (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", + prefix_args_len, prefix_args); + else + _swprintf(cmd, L"%.*s", prefix_args_len, prefix_args); + + } + else + wcscpy(cmd, L"git.exe"); + + /* 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); + } + + if (wargc > 1 && !wcscmp(wargv[1], L"gui")) + *wait = 0; + + LocalFree(wargv); + + 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, + int *skip_arguments, int *allocate_console, int *show_console) +{ + int id, minimal_search_path, needs_a_console, no_hide, wargc; + LPWSTR *wargv; + +#define BUFSIZE 65536 + static WCHAR buf[BUFSIZE]; + int len; + + for (id = 0; ; id++) { + minimal_search_path = 0; + needs_a_console = 0; + no_hide = 0; + 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); + } + + 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 if (strip_prefix(buf, &len, L"SHOW_CONSOLE=1 ")) + no_hide = 1; + else + break; + } + + 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; + *working_directory = (LPWSTR) 1; + wargv = CommandLineToArgvW(GetCommandLine(), &wargc); + 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; + if (needs_a_console) + *allocate_console = 1; + if (no_hide) + *show_console = 1; + LocalFree(wargv); + + return 1; +} + +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, show_console = 0; + WCHAR exepath[MAX_PATH], exe[MAX_PATH]; + LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename; + LPWSTR working_directory = NULL; + + /* 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)) { + fwprintf(stderr, L"Invalid executable path: %s\n", exepath); + ExitProcess(1); + } + basename = exepath + wcslen(exepath) + 1; + if (configure_via_resource(basename, exepath, exep, + &prefix_args, &prefix_args_len, + &is_git_command, &working_directory, + &full_path, &skip_arguments, &allocate_console, + &show_console)) { + /* do nothing */ + } + else if (!wcsicmp(basename, L"git-gui.exe")) { + static WCHAR buffer[BUFSIZE]; + wait = 0; + allocate_console = 1; + 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 (!wcsnicmp(basename, L"git-", 4)) { + needs_env_setup = 0; + + /* Call a builtin */ + prefix_args = basename + 4; + prefix_args_len = wcslen(prefix_args); + 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 (!wcsicmp(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, msystem_bin); + PathAppend(exe, L"git.exe"); + if (_waccess(exe, 0) == -1) { + wcscpy(exe, exepath); + PathAppend(exe, L"bin\\git.exe"); + } + } + 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); + ExitProcess(1); + } + + /* set the default exe module */ + wcscpy(exe, exepath); + swprintf(buffer, BUFSIZE, L"\"%s\"", 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, full_path); + cmd = fixup_commandline(exepath, &exep, &wait, + prefix_args, prefix_args_len, is_git_command, skip_arguments); + + if (working_directory == (LPWSTR)1) { + int len = GetEnvironmentVariable(L"HOME", NULL, 0); + + if (len) { + working_directory = malloc(sizeof(WCHAR) * len); + GetEnvironmentVariable(L"HOME", working_directory, len); + } + } + + { + 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); + + 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, + 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; + + + 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 */ + NULL, /* process handle inheritance */ + NULL, /* thread handle inheritance */ + /* handles inheritable? */ + allocate_console ? FALSE : TRUE, + creation_flags, + NULL, /* environment: use parent */ + working_directory, /* use parent's */ + &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); + + ExitProcess(r); +} diff --git a/config.mak.uname b/config.mak.uname index 2569b23fa9c9a0..1a91b4d988f2ef 100644 --- a/config.mak.uname +++ b/config.mak.uname @@ -573,6 +573,15 @@ else COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO 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 + +compat/win32/git-wrapper.o: %.o: %.c GIT-PREFIX + $(QUIET_CC)$(CC) -o $*.o -c -Wall -Wwrite-strings $< + endif endif ifeq ($(uname_S),QNX)