1
0
Fork 0
mirror of https://github.com/git/git.git synced 2024-05-08 00:26:33 +02:00

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 <carenas@gmail.com>
Helped-by: Yuyi Wang <Strawberry_Str@hotmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Derrick Stolee <dstolee@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Johannes Schindelin 2023-08-09 16:54:46 +00:00 committed by Junio C Hamano
parent fb7d80edca
commit 4b8a2717bb
6 changed files with 139 additions and 2 deletions

View File

@ -2778,6 +2778,13 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \
compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null
endif
headless-git.o: compat/win32/headless.c GIT-CFLAGS
$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \
-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<
headless-git$X: headless-git.o git.res GIT-LDFLAGS
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res
git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)
@ -3651,6 +3658,7 @@ clean: profile-clean coverage-clean cocciclean
$(RM) po/git.pot po/git-core.pot
$(RM) git.res
$(RM) $(OBJECTS)
$(RM) headless-git.o
$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)
$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)
$(RM) $(TEST_PROGRAMS)
@ -3679,6 +3687,7 @@ endif
$(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS
ifdef MSVC
$(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS))
$(RM) headless-git.o.pdb
$(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS))
$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))
$(RM) $(patsubst %.exe,%.ipdb,$(OTHER_PROGRAMS))

115
compat/win32/headless.c Normal file
View File

@ -0,0 +1,115 @@
/*
* headless Git - run Git without opening a console window on Windows
*/
#define STRICT
#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <wchar.h>
/*
* If `dir` contains the path to a Git exec directory, extend `PATH` to
* include the corresponding `bin/` directory (which is where all those
* `.dll` files needed by `git.exe` are, on Windows).
*/
static int extend_path(wchar_t *dir, size_t dir_len)
{
const wchar_t *suffix = L"\\libexec\\git-core";
size_t suffix_len = wcslen(suffix);
wchar_t *env;
DWORD len;
if (dir_len < suffix_len)
return 0;
dir_len -= suffix_len;
if (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t)))
return 0;
len = GetEnvironmentVariableW(L"PATH", NULL, 0);
if (!len)
return 0;
env = _alloca((dir_len + 5 + len) * sizeof(wchar_t));
wcsncpy(env, dir, dir_len);
wcscpy(env + dir_len, L"\\bin;");
if (!GetEnvironmentVariableW(L"PATH", env + dir_len + 5, len))
return 0;
SetEnvironmentVariableW(L"PATH", env);
return 1;
}
int WINAPI wWinMain(_In_ HINSTANCE instance,
_In_opt_ HINSTANCE previous_instance,
_In_ LPWSTR command_line, _In_ int show)
{
wchar_t git_command_line[32768];
size_t size = sizeof(git_command_line) / sizeof(wchar_t);
const wchar_t *needs_quotes = L"";
int slash = 0, i;
STARTUPINFO startup_info = {
.cb = sizeof(STARTUPINFO),
.dwFlags = STARTF_USESHOWWINDOW,
.wShowWindow = SW_HIDE,
};
PROCESS_INFORMATION process_info = { 0 };
DWORD creation_flags = CREATE_UNICODE_ENVIRONMENT |
CREATE_NEW_CONSOLE | CREATE_NO_WINDOW;
DWORD exit_code;
/* First, determine the full path of argv[0] */
for (i = 0; _wpgmptr[i]; i++)
if (_wpgmptr[i] == L' ')
needs_quotes = L"\"";
else if (_wpgmptr[i] == L'\\')
slash = i;
if (slash >= size - 11)
return 127; /* Too long path */
/* If it is in Git's exec path, add the bin/ directory to the PATH */
extend_path(_wpgmptr, slash);
/* Then, add the full path of `git.exe` as argv[0] */
i = swprintf_s(git_command_line, size, L"%ls%.*ls\\git.exe%ls",
needs_quotes, slash, _wpgmptr, needs_quotes);
if (i < 0)
return 127; /* Too long path */
if (*command_line) {
/* Now, append the command-line arguments */
i = swprintf_s(git_command_line + i, size - i,
L" %ls", command_line);
if (i < 0)
return 127;
}
startup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
startup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);
startup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);
if (!CreateProcess(NULL, /* infer argv[0] from the command line */
git_command_line, /* modified command line */
NULL, /* inherit process handles? */
NULL, /* inherit thread handles? */
FALSE, /* handles inheritable? */
creation_flags,
NULL, /* use this process' environment */
NULL, /* use this process' working directory */
&startup_info, &process_info))
return 129; /* could not start */
WaitForSingleObject(process_info.hProcess, INFINITE);
if (!GetExitCodeProcess(process_info.hProcess, &exit_code))
exit_code = 130; /* Could not determine exit code? */
CloseHandle(process_info.hProcess);
CloseHandle(process_info.hThread);
return (int)exit_code;
}

View File

@ -526,6 +526,8 @@ else
endif
X = .exe
EXTRA_PROGRAMS += headless-git$X
compat/msvc.o: compat/msvc.c compat/mingw.c GIT-CFLAGS
endif
ifeq ($(uname_S),Interix)
@ -705,6 +707,7 @@ ifeq ($(uname_S),MINGW)
COMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \
-fstack-protector-strong
EXTLIBS += -lntdll
EXTRA_PROGRAMS += headless-git$X
INSTALL = /bin/install
INTERNAL_QSORT = YesPlease
HAVE_LIBCHARSET_H = YesPlease

View File

@ -738,6 +738,15 @@ if(WIN32)
else()
message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
endif()
add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c)
if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
target_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows)
elseif(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
target_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS)
else()
message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
endif()
elseif(UNIX)
target_link_libraries(common-main pthread rt)
endif()

View File

@ -76,7 +76,7 @@ sub createProject {
my $libs_release = "\n ";
my $libs_debug = "\n ";
if (!$static_library) {
if (!$static_library && $name ne 'headless-git') {
$libs_release = join(";", sort(grep /^(?!libgit\.lib|xdiff\/lib\.lib|vcs-svn\/lib\.lib|reftable\/libreftable\.lib)/, @{$$build_structure{"$prefix${name}_LIBS"}}));
$libs_debug = $libs_release;
$libs_debug =~ s/zlib\.lib/zlibd\.lib/g;
@ -230,7 +230,7 @@ sub createProject {
print F << "EOM";
</ItemGroup>
EOM
if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {
if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) {
my $uuid_libgit = $$build_structure{"LIBS_libgit_GUID"};
my $uuid_libreftable = $$build_structure{"LIBS_reftable/libreftable_GUID"};
my $uuid_xdiff_lib = $$build_structure{"LIBS_xdiff/lib_GUID"};

View File

@ -371,6 +371,7 @@ sub handleLinkLine
# exit(1);
foreach (@objfiles) {
my $sourcefile = $_;
$sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/;
$sourcefile =~ s/\.o$/.c/;
push(@sources, $sourcefile);
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});