{"thread":{"id":"60091","subject":"[PATCH 0/2] maintenance: do not open console on Windows","startedAt":"2023-08-09T16:54:52Z","lastAt":"2023-08-09T16:54:55Z","messageCount":3,"participants":["Derrick Stolee via GitGitGadget","Johannes Schindelin via GitGitGadget"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"480364","messageId":"pull.1570.git.1691600087.gitgitgadget@gmail.com","threadId":"60091","inReplyTo":null,"subject":"[PATCH 0/2] maintenance: do not open console on Windows","fromName":"Derrick Stolee via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-09T16:54:45Z","receivedAt":"2023-08-09T16:54:52Z","isPatch":true,"sender":{"key":"stolee@gmail.com","avatar":"https://avatars.githubusercontent.com/u/570044?v=4"},"body":"These two patches are cherry-picks of commits that have lived in\ngit-for-windows/git since 2019 when background maintenance was originally\nintroduced.\n\nIf we run the core Git project's version of background maintenance on a\nWindows platform, then each background maintenance run will create a new\nconsole window, visible to the user. This is due to quirks around how\nWindows applications work: they require some parent application and will\ncreate a console window if one does not exist.\n\nThis is solved by creating a new 'headless-git.exe' executable that knows\nhow to hide its own window. This isn't an appropriate change to make to\n'git.exe' since that expects to be run within an existing console or\napplication.\n\n 1. Patch 1 introduces the 'headless-git.exe' executable with the ability to\n    hide its window.\n 2. Patch 2 modifies the schtasks integration to use 'headless-git.exe'\n    instead of 'git.exe'.\n\nThanks, -Stolee\n\nJohannes Schindelin (2):\n  win32: add a helper to run `git.exe` without a foreground window\n  git maintenance: avoid console window in scheduled tasks on Windows\n\n Makefile                                   |   9 ++\n builtin/gc.c                               |   2 +-\n compat/win32/headless.c                    | 115 +++++++++++++++++++++\n config.mak.uname                           |   3 +\n contrib/buildsystems/CMakeLists.txt        |   9 ++\n contrib/buildsystems/Generators/Vcxproj.pm |   4 +-\n contrib/buildsystems/engine.pl             |   1 +\n 7 files changed, 140 insertions(+), 3 deletions(-)\n create mode 100644 compat/win32/headless.c\n\n\nbase-commit: a82fb66fed250e16d3010c75404503bea3f0ab61\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1570%2Fderrickstolee%2Fheadless-git-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1570/derrickstolee/headless-git-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1570\n-- \ngitgitgadget\n"},{"id":"480365","messageId":"d27cda48a5910940a1ca2ed0f04fdf80bb325c28.1691600087.git.gitgitgadget@gmail.com","threadId":"60091","inReplyTo":"pull.1570.git.1691600087.gitgitgadget@gmail.com","subject":"[PATCH 2/2] git maintenance: avoid console window in scheduled tasks on Windows","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-09T16:54:47Z","receivedAt":"2023-08-09T16:54:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nWe just introduced a helper to avoid showing a console window when the\nscheduled task runs `git.exe`. Let's actually use it.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n builtin/gc.c | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 19d73067aad..5c4315f0d81 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -2068,7 +2068,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority\n \t      \"</Settings>\\n\"\n \t      \"<Actions Context=\\\"Author\\\">\\n\"\n \t      \"<Exec>\\n\"\n-\t      \"<Command>\\\"%s\\\\git.exe\\\"</Command>\\n\"\n+\t      \"<Command>\\\"%s\\\\headless-git.exe\\\"</Command>\\n\"\n \t      \"<Arguments>--exec-path=\\\"%s\\\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\\n\"\n \t      \"</Exec>\\n\"\n \t      \"</Actions>\\n\"\n-- \ngitgitgadget\n"},{"id":"480366","messageId":"6192ceef1464c1c54d933ea8b42644a0b224e7f5.1691600087.git.gitgitgadget@gmail.com","threadId":"60091","inReplyTo":"pull.1570.git.1691600087.gitgitgadget@gmail.com","subject":"[PATCH 1/2] win32: add a helper to run `git.exe` without a foreground window","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2023-08-09T16:54:46Z","receivedAt":"2023-08-09T16:54:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nOn Windows, there are two kinds of executables, console ones and\nnon-console ones. Git's executables are all console ones.\n\nWhen launching the former e.g. in a scheduled task, a CMD window pops\nup. This is not what we want for the tasks installed via the `git\nmaintenance` command.\n\nTo work around this, let's introduce `headless-git.exe`, which is a\nnon-console program that does _not_ pop up any window. All it does is to\nre-launch `git.exe`, suppressing that console window, passing through\nall command-line arguments as-are.\n\nHelped-by: Carlo Marcelo Arenas Belón <carenas@gmail.com>\nHelped-by: Yuyi Wang <Strawberry_Str@hotmail.com>\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\nSigned-off-by: Derrick Stolee <dstolee@microsoft.com>\n---\n Makefile                                   |   9 ++\n compat/win32/headless.c                    | 115 +++++++++++++++++++++\n config.mak.uname                           |   3 +\n contrib/buildsystems/CMakeLists.txt        |   9 ++\n contrib/buildsystems/Generators/Vcxproj.pm |   4 +-\n contrib/buildsystems/engine.pl             |   1 +\n 6 files changed, 139 insertions(+), 2 deletions(-)\n create mode 100644 compat/win32/headless.c\n\ndiff --git a/Makefile b/Makefile\nindex fb541dedc9f..15b327b72b9 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -2779,6 +2779,13 @@ compat/nedmalloc/nedmalloc.sp compat/nedmalloc/nedmalloc.o: EXTRA_CPPFLAGS = \\\n compat/nedmalloc/nedmalloc.sp: SP_EXTRA_FLAGS += -Wno-non-pointer-null\n endif\n \n+headless-git.o: compat/win32/headless.c GIT-CFLAGS\n+\t$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \\\n+\t\t-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<\n+\n+headless-git$X: headless-git.o git.res GIT-LDFLAGS\n+\t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) $(ALL_LDFLAGS) -mwindows -o $@ $< git.res\n+\n git-%$X: %.o GIT-LDFLAGS $(GITLIBS)\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)\n \n@@ -3652,6 +3659,7 @@ clean: profile-clean coverage-clean cocciclean\n \t$(RM) po/git.pot po/git-core.pot\n \t$(RM) git.res\n \t$(RM) $(OBJECTS)\n+\t$(RM) headless-git.o\n \t$(RM) $(LIB_FILE) $(XDIFF_LIB) $(REFTABLE_LIB) $(REFTABLE_TEST_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(SCRIPT_LIB) $(BUILT_INS) $(OTHER_PROGRAMS)\n \t$(RM) $(TEST_PROGRAMS)\n@@ -3680,6 +3688,7 @@ endif\n \t$(RM) GIT-SCRIPT-DEFINES GIT-PERL-DEFINES GIT-PERL-HEADER GIT-PYTHON-VARS\n ifdef MSVC\n \t$(RM) $(patsubst %.o,%.o.pdb,$(OBJECTS))\n+\t$(RM) headless-git.o.pdb\n \t$(RM) $(patsubst %.exe,%.pdb,$(OTHER_PROGRAMS))\n \t$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))\n \t$(RM) $(patsubst %.exe,%.ipdb,$(OTHER_PROGRAMS))\ndiff --git a/compat/win32/headless.c b/compat/win32/headless.c\nnew file mode 100644\nindex 00000000000..8b00dfe3bd5\n--- /dev/null\n+++ b/compat/win32/headless.c\n@@ -0,0 +1,115 @@\n+/*\n+ * headless Git - run Git without opening a console window on Windows\n+ */\n+\n+#define STRICT\n+#define WIN32_LEAN_AND_MEAN\n+#define UNICODE\n+#define _UNICODE\n+#include <windows.h>\n+#include <stdio.h>\n+#include <stdlib.h>\n+#include <wchar.h>\n+\n+/*\n+ * If `dir` contains the path to a Git exec directory, extend `PATH` to\n+ * include the corresponding `bin/` directory (which is where all those\n+ * `.dll` files needed by `git.exe` are, on Windows).\n+ */\n+static int extend_path(wchar_t *dir, size_t dir_len)\n+{\n+\tconst wchar_t *suffix = L\"\\\\libexec\\\\git-core\";\n+\tsize_t suffix_len = wcslen(suffix);\n+\twchar_t *env;\n+\tDWORD len;\n+\n+\tif (dir_len < suffix_len)\n+\t\treturn 0;\n+\n+\tdir_len -= suffix_len;\n+\tif (memcmp(dir + dir_len, suffix, suffix_len * sizeof(wchar_t)))\n+\t\treturn 0;\n+\n+\tlen = GetEnvironmentVariableW(L\"PATH\", NULL, 0);\n+\tif (!len)\n+\t\treturn 0;\n+\n+\tenv = _alloca((dir_len + 5 + len) * sizeof(wchar_t));\n+\twcsncpy(env, dir, dir_len);\n+\twcscpy(env + dir_len, L\"\\\\bin;\");\n+\tif (!GetEnvironmentVariableW(L\"PATH\", env + dir_len + 5, len))\n+\t\treturn 0;\n+\n+\tSetEnvironmentVariableW(L\"PATH\", env);\n+\treturn 1;\n+}\n+\n+int WINAPI wWinMain(_In_ HINSTANCE instance,\n+\t\t    _In_opt_ HINSTANCE previous_instance,\n+\t\t    _In_ LPWSTR command_line, _In_ int show)\n+{\n+\twchar_t git_command_line[32768];\n+\tsize_t size = sizeof(git_command_line) / sizeof(wchar_t);\n+\tconst wchar_t *needs_quotes = L\"\";\n+\tint slash = 0, i;\n+\n+\tSTARTUPINFO startup_info = {\n+\t\t.cb = sizeof(STARTUPINFO),\n+\t\t.dwFlags = STARTF_USESHOWWINDOW,\n+\t\t.wShowWindow = SW_HIDE,\n+\t};\n+\tPROCESS_INFORMATION process_info = { 0 };\n+\tDWORD creation_flags = CREATE_UNICODE_ENVIRONMENT |\n+\t\tCREATE_NEW_CONSOLE | CREATE_NO_WINDOW;\n+\tDWORD exit_code;\n+\n+\t/* First, determine the full path of argv[0] */\n+\tfor (i = 0; _wpgmptr[i]; i++)\n+\t\tif (_wpgmptr[i] == L' ')\n+\t\t\tneeds_quotes = L\"\\\"\";\n+\t\telse if (_wpgmptr[i] == L'\\\\')\n+\t\t\tslash = i;\n+\n+\tif (slash >= size - 11)\n+\t\treturn 127; /* Too long path */\n+\n+\t/* If it is in Git's exec path, add the bin/ directory to the PATH */\n+\textend_path(_wpgmptr, slash);\n+\n+\t/* Then, add the full path of `git.exe` as argv[0] */\n+\ti = swprintf_s(git_command_line, size, L\"%ls%.*ls\\\\git.exe%ls\",\n+\t\t       needs_quotes, slash, _wpgmptr, needs_quotes);\n+\tif (i < 0)\n+\t\treturn 127; /* Too long path */\n+\n+\tif (*command_line) {\n+\t\t/* Now, append the command-line arguments */\n+\t\ti = swprintf_s(git_command_line + i, size - i,\n+\t\t\t       L\" %ls\", command_line);\n+\t\tif (i < 0)\n+\t\t\treturn 127;\n+\t}\n+\n+\tstartup_info.hStdInput = GetStdHandle(STD_INPUT_HANDLE);\n+\tstartup_info.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE);\n+\tstartup_info.hStdError = GetStdHandle(STD_ERROR_HANDLE);\n+\n+\tif (!CreateProcess(NULL, /* infer argv[0] from the command line */\n+\t\t\t   git_command_line, /* modified command line */\n+\t\t\t   NULL, /* inherit process handles? */\n+\t\t\t   NULL, /* inherit thread handles? */\n+\t\t\t   FALSE, /* handles inheritable? */\n+\t\t\t   creation_flags,\n+\t\t\t   NULL, /* use this process' environment */\n+\t\t\t   NULL, /* use this process' working directory */\n+\t\t\t   &startup_info, &process_info))\n+\t\treturn 129; /* could not start */\n+\tWaitForSingleObject(process_info.hProcess, INFINITE);\n+\tif (!GetExitCodeProcess(process_info.hProcess, &exit_code))\n+\t\texit_code = 130; /* Could not determine exit code? */\n+\n+\tCloseHandle(process_info.hProcess);\n+\tCloseHandle(process_info.hThread);\n+\n+\treturn (int)exit_code;\n+}\ndiff --git a/config.mak.uname b/config.mak.uname\nindex 64c44db8058..3bb03f423a0 100644\n--- a/config.mak.uname\n+++ b/config.mak.uname\n@@ -526,6 +526,8 @@ else\n endif\n \tX = .exe\n \n+\tEXTRA_PROGRAMS += headless-git$X\n+\n compat/msvc.o: compat/msvc.c compat/mingw.c GIT-CFLAGS\n endif\n ifeq ($(uname_S),Interix)\n@@ -705,6 +707,7 @@ ifeq ($(uname_S),MINGW)\n \tCOMPAT_CFLAGS += -D__USE_MINGW_ANSI_STDIO=0 -DDETECT_MSYS_TTY \\\n \t\t-fstack-protector-strong\n \tEXTLIBS += -lntdll\n+\tEXTRA_PROGRAMS += headless-git$X\n \tINSTALL = /bin/install\n \tINTERNAL_QSORT = YesPlease\n \tHAVE_LIBCHARSET_H = YesPlease\ndiff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt\nindex 4faa419cc3d..6b819e2fbdf 100644\n--- a/contrib/buildsystems/CMakeLists.txt\n+++ b/contrib/buildsystems/CMakeLists.txt\n@@ -738,6 +738,15 @@ if(WIN32)\n \telse()\n \t\tmessage(FATAL_ERROR \"Unhandled compiler: ${CMAKE_C_COMPILER_ID}\")\n \tendif()\n+\n+\tadd_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c)\n+\tif(CMAKE_C_COMPILER_ID STREQUAL \"GNU\" OR CMAKE_C_COMPILER_ID STREQUAL \"Clang\")\n+\t\ttarget_link_options(headless-git PUBLIC -municode -Wl,-subsystem,windows)\n+\telseif(CMAKE_C_COMPILER_ID STREQUAL \"MSVC\")\n+\t\ttarget_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS)\n+\telse()\n+\t\tmessage(FATAL_ERROR \"Unhandled compiler: ${CMAKE_C_COMPILER_ID}\")\n+\tendif()\n elseif(UNIX)\n \ttarget_link_libraries(common-main pthread rt)\n endif()\ndiff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm\nindex 1a25789d285..b2e68a16715 100644\n--- a/contrib/buildsystems/Generators/Vcxproj.pm\n+++ b/contrib/buildsystems/Generators/Vcxproj.pm\n@@ -76,7 +76,7 @@ sub createProject {\n \n     my $libs_release = \"\\n    \";\n     my $libs_debug = \"\\n    \";\n-    if (!$static_library) {\n+    if (!$static_library && $name ne 'headless-git') {\n       $libs_release = join(\";\", sort(grep /^(?!libgit\\.lib|xdiff\\/lib\\.lib|vcs-svn\\/lib\\.lib|reftable\\/libreftable\\.lib)/, @{$$build_structure{\"$prefix${name}_LIBS\"}}));\n       $libs_debug = $libs_release;\n       $libs_debug =~ s/zlib\\.lib/zlibd\\.lib/g;\n@@ -230,7 +230,7 @@ EOM\n     print F << \"EOM\";\n   </ItemGroup>\n EOM\n-    if (!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') {\n+    if ((!$static_library || $target =~ 'vcs-svn' || $target =~ 'xdiff') && !($name =~ /headless-git/)) {\n       my $uuid_libgit = $$build_structure{\"LIBS_libgit_GUID\"};\n       my $uuid_libreftable = $$build_structure{\"LIBS_reftable/libreftable_GUID\"};\n       my $uuid_xdiff_lib = $$build_structure{\"LIBS_xdiff/lib_GUID\"};\ndiff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl\nindex ed6c45988a3..069be7e4bef 100755\n--- a/contrib/buildsystems/engine.pl\n+++ b/contrib/buildsystems/engine.pl\n@@ -371,6 +371,7 @@ sub handleLinkLine\n #    exit(1);\n     foreach (@objfiles) {\n         my $sourcefile = $_;\n+        $sourcefile =~ s/^headless-git\\.o$/compat\\/win32\\/headless.c/;\n         $sourcefile =~ s/\\.o$/.c/;\n         push(@sources, $sourcefile);\n         push(@cflags, @{$compile_options{\"${sourcefile}_CFLAGS\"}});\n-- \ngitgitgadget\n\n"}]}