Merge 'git-wrapper' into HEAD

Use msysGit's `git-wrapper` instead of the builtins. This works around
two issues:

- when the file system does not allow hard links, we would waste over
  800 megabyte by having 109 copies of a multi-megabyte executable

- even when the file system allows hard links, the Windows Explorer
  counts the disk usage as if it did not. Many users complained about
  Git for Windows using too much space (when it actually did not). We
  can easily avoid those user complaints by merging this branch.

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This commit is contained in:
Johannes Schindelin
2015-04-01 10:03:55 +01:00
4 changed files with 524 additions and 8 deletions

View File

@@ -1655,11 +1655,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
@@ -2222,6 +2228,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)'
@@ -2260,17 +2284,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 \

View File

@@ -1,4 +1,6 @@
#include <inttypes.h>
#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 </dev/tty", NULL };
const char *echo_on[] = { "sh", "-c", "stty echo </dev/tty", NULL };
static char buffer[1024];
DWORD len, dummy;
size_t tty0, tty1, tty2;
HANDLE in_handle, out_handle;
if (!env || 3 != sscanf(env,
" %" SCNuPTR " %" SCNuPTR " %" SCNuPTR " ",
&tty0, &tty1, &tty2)) {
warning("Cannot read from xterm");
return NULL;
}
in_handle = (HANDLE)tty0;
out_handle = (HANDLE)tty1;
if (!echo && run_command_v_opt(echo_off, 0))
warning("Could not disable echo on xterm");
if (!WriteFile(out_handle, prompt, strlen(prompt), &dummy, NULL)) {
warning("Could not write to xterm");
return NULL;
}
if (!ReadFile(in_handle, buffer, 1024, &len, NULL)) {
warning("Could not read from xterm");
return NULL;
}
if (len && buffer[len - 1] == '\n')
buffer[--len] = '\0';
if (len && buffer[len - 1] == '\r')
buffer[--len] = '\0';
if (!echo) {
if(run_command_v_opt(echo_on, 0))
warning("Could not re-enable echo on xterm");
WriteFile(out_handle, "\n", 1, &dummy, NULL);
}
return len == 0 ? NULL : buffer;
}
#endif
#ifndef FORCE_TEXT
@@ -102,6 +151,12 @@ char *git_terminal_prompt(const char *prompt, int echo)
static struct strbuf buf = STRBUF_INIT;
int r;
FILE *input_fh, *output_fh;
#ifdef GIT_WINDOWS_NATIVE
const char *term = getenv("TERM");
if (term && starts_with(term, "xterm"))
return xterm_prompt(prompt, echo);
#endif
input_fh = fopen(INPUT_PATH, "r" FORCE_TEXT);
if (!input_fh)

434
compat/win32/git-wrapper.c Normal file
View File

@@ -0,0 +1,434 @@
/*
* 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>
#include <wchar.h>
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 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");
/* 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);
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);
}
LocalFree(wargv);
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 *full_path,
int *skip_arguments)
{
int id = 0, minimal_search_path, wargc;
LPWSTR *wargv;
#define BUFSIZE 65536
static WCHAR buf[BUFSIZE];
int len;
for (id = 0; ; id++) {
minimal_search_path = 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"Copy %s to edit-res.exe and call\n"
L"\n\tedit-res.exe command %s "
L"\"<command-line>\"\n",
basename, basename);
exit(1);
}
if (len >= BUFSIZE) {
fwprintf(stderr,
L"Could not read resource (too large)\n");
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)
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;
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 (minimal_search_path)
*full_path = 0;
LocalFree(wargv);
return 1;
}
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;
WCHAR exepath[MAX_PATH], exe[MAX_PATH];
LPWSTR cmd = NULL, dir = 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)) {
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, &start_in_home,
&full_path, &skip_arguments)) {
/* do nothing */
}
else if (!wcsncmp(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"))
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, msystem_bin);
PathAppend(exe, L"git.exe");
if (_waccess(exe, 0) == -1) {
wcscpy(exe, exepath);
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, full_path);
cmd = fixup_commandline(exepath, &exep, &wait,
prefix_args, prefix_args_len, is_git_command, skip_arguments);
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();
SetConsoleCP(GetACP());
{
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? */
creation_flags,
NULL, /* environment: use parent */
dir, /* 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);
}

View File

@@ -561,6 +561,15 @@ else
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
compat/win32/git-wrapper.o: %.o: %.c
$(QUIET_CC)$(CC) -o $*.o -c -Wall -Wwrite-strings $<
endif
endif
ifeq ($(uname_S),QNX)