win32: add a helper to run `git.exe` without a foreground window
[alt-git.git] / compat / win32 / headless.c
blob8b00dfe3bd5d00d20459cc1473a304a5b24a2e73
1 /*
2 * headless Git - run Git without opening a console window on Windows
3 */
5 #define STRICT
6 #define WIN32_LEAN_AND_MEAN
7 #define UNICODE
8 #define _UNICODE
9 #include <windows.h>
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <wchar.h>
15 * If `dir` contains the path to a Git exec directory, extend `PATH` to
16 * include the corresponding `bin/` directory (which is where all those
17 * `.dll` files needed by `git.exe` are, on Windows).
19 static int extend_path(wchar_t *dir, size_t dir_len)
21 const wchar_t *suffix = L"\\libexec\\git-core";
22 size_t suffix_len = wcslen(suffix);
23 wchar_t *env;
24 DWORD len;
26 if (dir_len < suffix_len)
27 return 0;
29 dir_len -= suffix_len;
30 if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t)))
31 return 0;
33 len = GetEnvironmentVariableW(L"PATH", NULL, 0);
34 if (!len)
35 return 0;
37 env = _alloca((dir_len + 5 + len) * sizeof(wchar_t));
38 wcsncpy(env, dir, dir_len);
39 wcscpy(env + dir_len, L"\\bin;");
40 if (!GetEnvironmentVariableW(L"PATH", env + dir_len + 5, len))
41 return 0;
43 SetEnvironmentVariableW(L"PATH", env);
44 return 1;
47 int WINAPI wWinMain(_In_ HINSTANCE instance,
48 _In_opt_ HINSTANCE previous_instance,
49 _In_ LPWSTR command_line, _In_ int show)
51 wchar_t git_command_line[32768];
52 size_t size = sizeof(git_command_line) / sizeof(wchar_t);
53 const wchar_t *needs_quotes = L"";
54 int slash = 0, i;
56 STARTUPINFO startup_info = {
57 .cb = sizeof(STARTUPINFO),
58 .dwFlags = STARTF_USESHOWWINDOW,
59 .wShowWindow = SW_HIDE,
61 PROCESS_INFORMATION process_info = { 0 };
62 DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT |
63 CREATE_NEW_CONSOLE | CREATE_NO_WINDOW;
64 DWORD exit_code;
66 /* First, determine the full path of argv[0] */
67 for (i = 0; _wpgmptr[i]; i++)
68 if (_wpgmptr[i] == L' ')
69 needs_quotes = L"\"";
70 else if (_wpgmptr[i] == L'\\')
71 slash = i;
73 if (slash >= size - 11)
74 return 127; /* Too long path */
76 /* If it is in Git's exec path, add the bin/ directory to the PATH */
77 extend_path(_wpgmptr, slash);
79 /* Then, add the full path of `git.exe` as argv[0] */
80 i = swprintf_s(git_command_line, size, L"%ls%.*ls\\git.exe%ls",
81 needs_quotes, slash, _wpgmptr, needs_quotes);
82 if (i < 0)
83 return 127; /* Too long path */
85 if (*command_line) {
86 /* Now, append the command-line arguments */
87 i = swprintf_s(git_command_line + i, size - i,
88 L" %ls", command_line);
89 if (i < 0)
90 return 127;
93 startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
94 startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
95 startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
97 if (!CreateProcess(NULL, /* infer argv[0] from the command line */
98 git_command_line, /* modified command line */
99 NULL, /* inherit process handles? */
100 NULL, /* inherit thread handles? */
101 FALSE, /* handles inheritable? */
102 creation_flags,
103 NULL, /* use this process' environment */
104 NULL, /* use this process' working directory */
105 &startup_info, &process_info))
106 return 129; /* could not start */
107 WaitForSingleObject(process_info.hProcess, INFINITE);
108 if (!GetExitCodeProcess(process_info.hProcess, &exit_code))
109 exit_code = 130; /* Could not determine exit code? */
111 CloseHandle(process_info.hProcess);
112 CloseHandle(process_info.hThread);
114 return (int)exit_code;