|
| 1 | +/* |
| 2 | + * headless Git - run Git without opening a console window on Windows |
| 3 | + */ |
| 4 | + |
| 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> |
| 13 | + |
| 14 | +/* |
| 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). |
| 18 | + */ |
| 19 | +static int extend_path(wchar_t *dir, size_t dir_len) |
| 20 | +{ |
| 21 | + const wchar_t *suffix = L"\\libexec\\git-core"; |
| 22 | + size_t suffix_len = wcslen(suffix); |
| 23 | + wchar_t *env; |
| 24 | + DWORD len; |
| 25 | + |
| 26 | + if (dir_len < suffix_len) |
| 27 | + return 0; |
| 28 | + |
| 29 | + dir_len -= suffix_len; |
| 30 | + if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t))) |
| 31 | + return 0; |
| 32 | + |
| 33 | + len = GetEnvironmentVariableW(L"PATH", NULL, 0); |
| 34 | + if (!len) |
| 35 | + return 0; |
| 36 | + |
| 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; |
| 42 | + |
| 43 | + SetEnvironmentVariableW(L"PATH", env); |
| 44 | + return 1; |
| 45 | +} |
| 46 | + |
| 47 | +int WINAPI wWinMain(_In_ HINSTANCE instance, |
| 48 | + _In_opt_ HINSTANCE previous_instance, |
| 49 | + _In_ LPWSTR command_line, _In_ int show) |
| 50 | +{ |
| 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; |
| 55 | + |
| 56 | + STARTUPINFO startup_info = { |
| 57 | + .cb = sizeof(STARTUPINFO), |
| 58 | + .dwFlags = STARTF_USESHOWWINDOW, |
| 59 | + .wShowWindow = SW_HIDE, |
| 60 | + }; |
| 61 | + PROCESS_INFORMATION process_info = { 0 }; |
| 62 | + DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT | |
| 63 | + CREATE_NEW_CONSOLE | CREATE_NO_WINDOW; |
| 64 | + DWORD exit_code; |
| 65 | + |
| 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; |
| 72 | + |
| 73 | + if (slash >= size - 11) |
| 74 | + return 127; /* Too long path */ |
| 75 | + |
| 76 | + /* If it is in Git's exec path, add the bin/ directory to the PATH */ |
| 77 | + extend_path(_wpgmptr, slash); |
| 78 | + |
| 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 */ |
| 84 | + |
| 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; |
| 91 | + } |
| 92 | + |
| 93 | + startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
| 94 | + startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
| 95 | + startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
| 96 | + |
| 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? */ |
| 110 | + |
| 111 | + CloseHandle(process_info.hProcess); |
| 112 | + CloseHandle(process_info.hThread); |
| 113 | + |
| 114 | + return (int)exit_code; |
| 115 | +} |
0 commit comments