Skip to content

Commit ab9f813

Browse files
committed
win32: add a helper to run git.exe without a foreground window
On Windows, there are two kinds of executables, console ones and non-console ones. Git's executables are all console ones. When launching the former e.g. in a scheduled task, a CMD window pops up. This is not what we want for the tasks installed via the `git maintenance` command. To work around this, let's introduce `headless-git.exe`, which is a non-console program that does _not_ pop up any window. All it does is to re-launch `git.exe`, suppressing that console window, passing through all command-line arguments as-are. Helped-by: Carlo Marcelo Arenas Belón <[email protected]> Helped-by: Yuyi Wang <[email protected]> Signed-off-by: Johannes Schindelin <[email protected]> Signed-off-by: Derrick Stolee <[email protected]>
1 parent 2dd3bc0 commit ab9f813

File tree

6 files changed

+139
-2
lines changed

6 files changed

+139
-2
lines changed

Makefile

+9
Original file line numberDiff line numberDiff line change
@@ -2777,6 +2777,13 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
27772777
compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null
27782778
endif
27792779

2780+
headless-git.o: compat/win32/headless.c GIT-CFLAGS
2781+
$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \
2782+
-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<
2783+
2784+
headless-git$X: headless-git.o git.res GIT-LDFLAGS
2785+
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res
2786+
27802787
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
27812788
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
27822789

@@ -3650,6 +3657,7 @@ clean: profile-clean coverage-clean cocciclean
36503657
$(RM) po/git.pot po/git-core.pot
36513658
$(RM) git.res
36523659
$(RM) $(OBJECTS)
3660+
$(RM) headless-git.o
36533661
$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
36543662
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
36553663
$(RM) $(TEST_PROGRAMS)
@@ -3678,6 +3686,7 @@ endif
36783686
$(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS
36793687
ifdef MSVC
36803688
$(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS))
3689+
$(RM) headless-git.o.pdb
36813690
$(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS))
36823691
$(RM) $(patsubst %.exe,%.ilk,$(OTHER_PROGRAMS))
36833692
$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))

compat/win32/headless.c

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
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+
}

config.mak.uname

+3
Original file line numberDiff line numberDiff line change
@@ -527,6 +527,8 @@ else
527527
endif
528528
X = .exe
529529

530+
EXTRA_PROGRAMS += headless-git$X
531+
530532
compat/msvc.o: compat/msvc.c compat/mingw.c GIT-CFLAGS
531533
endif
532534
ifeq ($(uname_S),Interix)
@@ -679,6 +681,7 @@ ifeq ($(uname_S),MINGW)
679681
RC = windres -O coff
680682
NATIVE_CRLF = YesPlease
681683
X = .exe
684+
EXTRA_PROGRAMS += headless-git$X
682685
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
683686
htmldir = doc/git/html/
684687
prefix =

contrib/buildsystems/CMakeLists.txt

+9
Original file line numberDiff line numberDiff line change
@@ -738,6 +738,15 @@ if(WIN32)
738738
else()
739739
message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
740740
endif()
741+
742+
add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c)
743+
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
744+
target_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows)
745+
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
746+
target_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS)
747+
else()
748+
message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
749+
endif()
741750
elseif(UNIX)
742751
target_link_libraries(common-main pthread rt)
743752
endif()

contrib/buildsystems/Generators/Vcxproj.pm

+2-2
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ sub createProject {
7676

7777
my $libs_release = "\n ";
7878
my $libs_debug = "\n ";
79-
if (!$static_library) {
79+
if (!$static_library && $name ne 'headless-git') {
8080
$libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
8181
$libs_debug = $libs_release;
8282
$libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
@@ -254,7 +254,7 @@ EOM
254254
print F << "EOM";
255255
</ItemGroup>
256256
EOM
257-
if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
257+
if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) {
258258
my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
259259
my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"};
260260
my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};

contrib/buildsystems/engine.pl

+1
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,7 @@ sub handleLinkLine
370370
# exit(1);
371371
foreach (@objfiles) {
372372
my $sourcefile = $_;
373+
$sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/;
373374
$sourcefile =~ s/\.o$/.c/;
374375
$sourcefile =~ s/\.res$/.rc/;
375376
push(@sources, $sourcefile);

0 commit comments

Comments
 (0)