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
+}