Skip to content

Commit 304d297

Browse files
committed
git-wrapper: add code to configure command-lines to be launched
To allow the Git wrapper to replace the `git-bash.bat` script (which would always open the Win32 console, even if the user wants to use a different terminal emulator), we need to offer a way to configure *which* command-line to run. We need to recompile the git-wrapper with the `-mwindows` flag to declare that this is a GUI program (not a console program). Having to recompile it anyway, let's put the new code in a conditionally compiled section so that the builtins (for which we use the git-wrapper, too) do not need to carry around that code. To configure the command-line, we use a way that is very typical for Windows: resources. Windows resources are data that are stored inside .exe files, but can be changed *after* compilation. Therefore, this facility is *exactly* what we want: we can easily copy the .exe to the new name `git-bash.exe`, configure that executable to run the Bash, and then copy it again to the new name `git-cmd.exe` and configure that executable to run `cmd.exe` instead. For even more flexibility, we expand environment variables specified as `@@<VARIABLE-NAME>@@`, and for convenience `@@EXEPATH@@` expands into the directory in which the executable resides. Sadly, an executable cannot configure itself: the `.exe` file is locked while the process is running. This means we have to have a separate executable to edit the resources anyway, so let's just enhance the Git wrapper *itself*: when copied to the new name `edit-res.exe`, it can edit other copies of the Git wrapper like so: edit-res.exe git-cmd.exe command '@@comspec@@ /K' Signed-off-by: Johannes Schindelin <[email protected]>
1 parent d7ff2b2 commit 304d297

File tree

1 file changed

+191
-0
lines changed

1 file changed

+191
-0
lines changed

compat/win32/git-wrapper.c

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,189 @@ static LPWSTR fixup_commandline(LPWSTR exepath, LPWSTR *exep, int *wait,
173173
return cmd;
174174
}
175175

176+
#ifdef MAGIC_RESOURCE
177+
178+
static int wsuffixcmp(LPWSTR text, LPWSTR suffix)
179+
{
180+
int text_len = wcslen(text), suffix_len = wcslen(suffix);
181+
182+
if (text_len < suffix_len)
183+
return -1;
184+
185+
return wcscmp(text + (text_len - suffix_len), suffix);
186+
}
187+
188+
static int edit_resources(LPWSTR exe_path,
189+
LPWSTR *commands, int command_count)
190+
{
191+
WORD language = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
192+
HANDLE handle;
193+
int i;
194+
195+
if (command_count > 16) {
196+
fwprintf(stderr, L"Cannot handle more than 16 commands\n");
197+
return -1;
198+
}
199+
200+
if (wsuffixcmp(exe_path, L".exe")) {
201+
fwprintf(stderr, L"Not an .exe file: '%s'", exe_path);
202+
return -1;
203+
}
204+
if (_waccess(exe_path, 0) == -1) {
205+
fwprintf(stderr, L"File not found: '%s'", exe_path);
206+
return -1;
207+
}
208+
209+
handle = BeginUpdateResource(exe_path, FALSE);
210+
if (!handle) {
211+
fwprintf(stderr,
212+
L"Could not update resources of '%s'", exe_path);
213+
return -1;
214+
}
215+
216+
if (command_count >= 0) {
217+
LPWSTR buffer, p;
218+
int alloc = 16; /* 16 words with string lengths, for sure... */
219+
220+
for (i = 0; i < command_count; i++) {
221+
int len = wcslen(commands[i]);
222+
if (len > 0xffff) {
223+
fwprintf(stderr, L"Too long command: %s\n",
224+
commands[i]);
225+
return -1;
226+
}
227+
alloc += len;
228+
}
229+
230+
p = buffer = calloc(alloc, sizeof(WCHAR));
231+
232+
for (i = 0; i < command_count; i++)
233+
p += swprintf(p, alloc - (p - buffer), L"%c%s",
234+
(WCHAR) wcslen(commands[i]), commands[i]);
235+
236+
UpdateResource(handle, RT_STRING, MAKEINTRESOURCE(1),
237+
language, buffer, sizeof(WCHAR) * alloc);
238+
}
239+
240+
if (EndUpdateResource(handle, FALSE))
241+
return 0;
242+
243+
fwprintf(stderr, L"Error %d updating resources\n",
244+
(int) GetLastError());
245+
return -1;
246+
}
247+
248+
static int configure_via_resource(LPWSTR basename, LPWSTR exepath, LPWSTR exep,
249+
LPWSTR *prefix_args, int *prefix_args_len,
250+
int *is_git_command, int *start_in_home)
251+
{
252+
int id = 0, wargc;
253+
LPWSTR *wargv;
254+
255+
#define BUFSIZE 65536
256+
static WCHAR buf[BUFSIZE];
257+
int len;
258+
259+
if (!wcscmp(basename, L"edit-res.exe")) {
260+
LPWSTR cmdline = GetCommandLine();
261+
262+
wargv = CommandLineToArgvW(cmdline, &wargc);
263+
264+
if (wargv[1]) {
265+
if (wargc > 1 && !wcscmp(wargv[1], L"command"))
266+
exit(edit_resources(wargv[2],
267+
wargv + 3, wargc - 3));
268+
}
269+
fwprintf(stderr,
270+
L"Usage: %s command <exe> <args>...\n",
271+
basename);
272+
exit(1);
273+
}
274+
275+
SetEnvironmentVariable(L"EXEPATH", exepath);
276+
for (id = 0; ; id++) {
277+
len = LoadString(NULL, id, buf, BUFSIZE);
278+
279+
if (!len) {
280+
fwprintf(stderr, L"Need a valid command-line; "
281+
L"Copy %s to edit-res.exe and call\n"
282+
L"\n\tedit-res.exe command %s "
283+
L"\"<command-line>\"\n",
284+
basename, basename);
285+
exit(1);
286+
}
287+
288+
if (len >= BUFSIZE) {
289+
fwprintf(stderr,
290+
L"Could not read resource (too large)\n");
291+
exit(1);
292+
}
293+
294+
buf[len] = L'\0';
295+
296+
for (;;) {
297+
LPWSTR atat = wcsstr(buf, L"@@"), atat2;
298+
WCHAR save;
299+
int env_len, delta;
300+
301+
if (!atat)
302+
break;
303+
304+
atat2 = wcsstr(atat + 2, L"@@");
305+
if (!atat2)
306+
break;
307+
308+
*atat2 = L'\0';
309+
env_len = GetEnvironmentVariable(atat + 2, NULL, 0);
310+
delta = env_len - 1 - (atat2 + 2 - atat);
311+
if (len + delta >= BUFSIZE) {
312+
fwprintf(stderr,
313+
L"Substituting '%s' results in too "
314+
L"large a command-line\n", atat + 2);
315+
exit(1);
316+
}
317+
if (delta)
318+
memmove(atat2 + 2 + delta, atat2 + 2,
319+
sizeof(WCHAR) * (len + 1
320+
- (atat2 + 2 - buf)));
321+
len += delta;
322+
save = atat[env_len - 1];
323+
GetEnvironmentVariable(atat + 2, atat, env_len);
324+
atat[env_len - 1] = save;
325+
}
326+
327+
/* parse first argument */
328+
wargv = CommandLineToArgvW(buf, &wargc);
329+
if (wargc < 1) {
330+
fwprintf(stderr, L"Invalid command-line: '%s'\n", buf);
331+
exit(1);
332+
}
333+
if (*wargv[0] == L'\\' ||
334+
(isalpha(*wargv[0]) && wargv[0][1] == L':'))
335+
wcscpy(exep, wargv[0]);
336+
else {
337+
wcscpy(exep, exepath);
338+
PathAppend(exep, wargv[0]);
339+
}
340+
341+
if (_waccess(exep, 0) != -1)
342+
break;
343+
fwprintf(stderr,
344+
L"Skipping command-line '%s'\n('%s' not found)\n",
345+
buf, exep);
346+
}
347+
348+
*prefix_args = buf;
349+
*prefix_args_len = wcslen(buf);
350+
351+
*is_git_command = 0;
352+
*start_in_home = 1;
353+
354+
return 1;
355+
}
356+
357+
#endif
358+
176359
int main(void)
177360
{
178361
int r = 1, wait = 1, prefix_args_len = -1, needs_env_setup = 1,
@@ -192,6 +375,14 @@ int main(void)
192375
ExitProcess(1);
193376
}
194377
basename = exepath + wcslen(exepath) + 1;
378+
#ifdef MAGIC_RESOURCE
379+
if (configure_via_resource(basename, exepath, exep,
380+
&prefix_args, &prefix_args_len,
381+
&is_git_command, &start_in_home)) {
382+
/* do nothing */
383+
}
384+
else
385+
#endif
195386
if (!wcsncmp(basename, L"git-", 4)) {
196387
needs_env_setup = 0;
197388

0 commit comments

Comments
 (0)