Files
git/compat/win32/git-wrapper.c
Johannes Schindelin 9c69393cd9 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-<subcommand>". Example: the "log" command can be called
via "git log <parameters>" or via "git-log <parameters>". 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 <johannes.schindelin@gmx.de>
2016-06-14 08:22:18 +02:00

235 lines
6.1 KiB
C

/*
* git-wrapper - replace cmd\git.cmd with an executable
*
* Copyright (C) 2012 Pat Thoyts <patthoyts@users.sourceforge.net>
*/
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <shlwapi.h>
#include <shellapi.h>
#include <stdio.h>
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 len;
LPWSTR path2 = NULL;
/* 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.
*/
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;
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 (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)
_swprintf(cmd, L"%s\\%s %.*s",
exepath, L"git.exe", prefix_args_len, prefix_args);
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, prefix_args_len = -1;
WCHAR exepath[MAX_PATH], exe[MAX_PATH];
LPWSTR cmd = NULL, exep = exe, prefix_args = NULL, basename;
UINT codepage = 0;
/* 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 (!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();
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);
}