Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 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 | |
Jeff King | 1414918 | 2024-08-27 23:59:52 -0400 | [diff] [blame] | 14 | #pragma GCC diagnostic ignored "-Wunused-parameter" |
| 15 | |
Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 16 | /* |
| 17 | * If `dir` contains the path to a Git exec directory, extend `PATH` to |
| 18 | * include the corresponding `bin/` directory (which is where all those |
| 19 | * `.dll` files needed by `git.exe` are, on Windows). |
| 20 | */ |
| 21 | static int extend_path(wchar_t *dir, size_t dir_len) |
| 22 | { |
| 23 | const wchar_t *suffix = L"\\libexec\\git-core"; |
| 24 | size_t suffix_len = wcslen(suffix); |
| 25 | wchar_t *env; |
| 26 | DWORD len; |
| 27 | |
| 28 | if (dir_len < suffix_len) |
| 29 | return 0; |
| 30 | |
| 31 | dir_len -= suffix_len; |
| 32 | if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t))) |
| 33 | return 0; |
| 34 | |
| 35 | len = GetEnvironmentVariableW(L"PATH", NULL, 0); |
| 36 | if (!len) |
| 37 | return 0; |
| 38 | |
| 39 | env = _alloca((dir_len + 5 + len) * sizeof(wchar_t)); |
| 40 | wcsncpy(env, dir, dir_len); |
| 41 | wcscpy(env + dir_len, L"\\bin;"); |
| 42 | if (!GetEnvironmentVariableW(L"PATH", env + dir_len + 5, len)) |
| 43 | return 0; |
| 44 | |
| 45 | SetEnvironmentVariableW(L"PATH", env); |
| 46 | return 1; |
| 47 | } |
| 48 | |
| 49 | int WINAPI wWinMain(_In_ HINSTANCE instance, |
| 50 | _In_opt_ HINSTANCE previous_instance, |
| 51 | _In_ LPWSTR command_line, _In_ int show) |
| 52 | { |
| 53 | wchar_t git_command_line[32768]; |
| 54 | size_t size = sizeof(git_command_line) / sizeof(wchar_t); |
| 55 | const wchar_t *needs_quotes = L""; |
Patrick Steinhardt | 709fdce | 2024-12-06 11:27:18 +0100 | [diff] [blame] | 56 | size_t slash = 0; |
| 57 | int len; |
Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 58 | |
| 59 | STARTUPINFO startup_info = { |
| 60 | .cb = sizeof(STARTUPINFO), |
| 61 | .dwFlags = STARTF_USESHOWWINDOW, |
| 62 | .wShowWindow = SW_HIDE, |
| 63 | }; |
| 64 | PROCESS_INFORMATION process_info = { 0 }; |
| 65 | DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT | |
| 66 | CREATE_NEW_CONSOLE | CREATE_NO_WINDOW; |
| 67 | DWORD exit_code; |
| 68 | |
| 69 | /* First, determine the full path of argv[0] */ |
Patrick Steinhardt | 709fdce | 2024-12-06 11:27:18 +0100 | [diff] [blame] | 70 | for (size_t i = 0; _wpgmptr[i]; i++) |
Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 71 | if (_wpgmptr[i] == L' ') |
| 72 | needs_quotes = L"\""; |
| 73 | else if (_wpgmptr[i] == L'\\') |
| 74 | slash = i; |
| 75 | |
| 76 | if (slash >= size - 11) |
| 77 | return 127; /* Too long path */ |
| 78 | |
| 79 | /* If it is in Git's exec path, add the bin/ directory to the PATH */ |
| 80 | extend_path(_wpgmptr, slash); |
| 81 | |
| 82 | /* Then, add the full path of `git.exe` as argv[0] */ |
Patrick Steinhardt | 709fdce | 2024-12-06 11:27:18 +0100 | [diff] [blame] | 83 | len = swprintf_s(git_command_line, size, L"%ls%.*ls\\git.exe%ls", |
| 84 | needs_quotes, (int) slash, _wpgmptr, needs_quotes); |
| 85 | if (len < 0) |
Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 86 | return 127; /* Too long path */ |
| 87 | |
| 88 | if (*command_line) { |
| 89 | /* Now, append the command-line arguments */ |
Patrick Steinhardt | 709fdce | 2024-12-06 11:27:18 +0100 | [diff] [blame] | 90 | len = swprintf_s(git_command_line + len, size - len, |
| 91 | L" %ls", command_line); |
| 92 | if (len < 0) |
Johannes Schindelin | 4b8a271 | 2023-08-09 16:54:46 +0000 | [diff] [blame] | 93 | return 127; |
| 94 | } |
| 95 | |
| 96 | startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE); |
| 97 | startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); |
| 98 | startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE); |
| 99 | |
| 100 | if (!CreateProcess(NULL, /* infer argv[0] from the command line */ |
| 101 | git_command_line, /* modified command line */ |
| 102 | NULL, /* inherit process handles? */ |
| 103 | NULL, /* inherit thread handles? */ |
| 104 | FALSE, /* handles inheritable? */ |
| 105 | creation_flags, |
| 106 | NULL, /* use this process' environment */ |
| 107 | NULL, /* use this process' working directory */ |
| 108 | &startup_info, &process_info)) |
| 109 | return 129; /* could not start */ |
| 110 | WaitForSingleObject(process_info.hProcess, INFINITE); |
| 111 | if (!GetExitCodeProcess(process_info.hProcess, &exit_code)) |
| 112 | exit_code = 130; /* Could not determine exit code? */ |
| 113 | |
| 114 | CloseHandle(process_info.hProcess); |
| 115 | CloseHandle(process_info.hThread); |
| 116 | |
| 117 | return (int)exit_code; |
| 118 | } |