Skip to content

Commit 7bfa170

Browse files
committed
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 <[email protected]>
1 parent b8e9daa commit 7bfa170

File tree

1 file changed

+39
-8
lines changed

1 file changed

+39
-8
lines changed

compat/win32/git-wrapper.c

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,8 @@ static void setup_environment(LPWSTR exepath)
9696
* trim off the first argument and replace it leaving the rest
9797
* untouched.
9898
*/
99-
static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait)
99+
static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait,
100+
LPWSTR builtin, int builtin_len)
100101
{
101102
int wargc = 0, gui = 0;
102103
LPWSTR cmd = NULL, cmdline = NULL;
@@ -105,7 +106,7 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait)
105106
cmdline = GetCommandLine();
106107
wargv = CommandLineToArgvW(cmdline, &wargc);
107108
cmd = (LPWSTR)malloc(sizeof(WCHAR) *
108-
(wcslen(cmdline) + MAX_PATH));
109+
(wcslen(cmdline) + builtin_len + 1 + MAX_PATH));
109110
if (wargc > 1 && wcsicmp(L"gui", wargv[1]) == 0) {
110111
*wait = 0;
111112
if (wargc > 2 && wcsicmp(L"citool", wargv[2]) == 0) {
@@ -126,6 +127,9 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait)
126127
*exep = NULL;
127128
}
128129
}
130+
else if (builtin)
131+
_swprintf(cmd, L"%s\\%s %.*s",
132+
exepath, L"git.exe", builtin_len, builtin);
129133
else
130134
wcscpy(cmd, L"git.exe");
131135

@@ -147,17 +151,44 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait)
147151

148152
int main(void)
149153
{
150-
int r = 1, wait = 1;
154+
int r = 1, wait = 1, builtin_len = -1;
151155
WCHAR exepath[MAX_PATH], exe[MAX_PATH];
152-
LPWSTR cmd = NULL, exep = exe, basename;
156+
LPWSTR cmd = NULL, exep = exe, builtin = NULL, basename;
153157
UINT codepage = 0;
154158

155159
/* get the installation location */
156160
GetModuleFileName(NULL, exepath, MAX_PATH);
157-
PathRemoveFileSpec(exepath);
158-
PathRemoveFileSpec(exepath);
159-
setup_environment(exepath);
160-
cmd = fixup_commandline(exepath, &exep, &wait);
161+
if (!PathRemoveFileSpec(exepath)) {
162+
fwprintf(stderr, L"Invalid executable path: %s\n", exepath);
163+
ExitProcess(1);
164+
}
165+
basename = exepath + wcslen(exepath) + 1;
166+
if (!wcsncmp(basename, L"git-", 4)) {
167+
/* Call a builtin */
168+
builtin = basename + 4;
169+
builtin_len = wcslen(builtin);
170+
if (!wcscmp(builtin + builtin_len - 4, L".exe"))
171+
builtin_len -= 4;
172+
173+
/* set the default exe module */
174+
wcscpy(exe, exepath);
175+
PathAppend(exe, L"git.exe");
176+
}
177+
else if (!wcscmp(basename, L"git.exe")) {
178+
if (!PathRemoveFileSpec(exepath)) {
179+
fwprintf(stderr,
180+
L"Invalid executable path: %s\n", exepath);
181+
ExitProcess(1);
182+
}
183+
184+
/* set the default exe module */
185+
wcscpy(exe, exepath);
186+
PathAppend(exe, L"bin\\git.exe");
187+
}
188+
189+
if (!builtin)
190+
setup_environment(exepath);
191+
cmd = fixup_commandline(exepath, &exep, &wait, builtin, builtin_len);
161192

162193
/* set the console to ANSI/GUI codepage */
163194
codepage = GetConsoleCP();

0 commit comments

Comments
 (0)