Skip to content

Maintenance: create headless-git.exe to avoid foreground windows #304

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2522,6 +2522,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
$(QUIET_CC)$(CC) $(ALL_CFLAGS) $(COMPAT_CFLAGS) \
-fno-stack-protector -o $@ -c -Wall -Wwrite-strings $<

headless-git$X: headless-git.o git.res
$(QUIET_LINK)$(CC) $(ALL_LDFLAGS) -mwindows $(COMPAT_CFLAGS) -o $@ $^

git-%$X: %.o GIT-LDFLAGS $(GITLIBS)
$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS)

Expand Down
9 changes: 5 additions & 4 deletions builtin/gc.c
Original file line number Diff line number Diff line change
Expand Up @@ -1610,7 +1610,7 @@ static int launchctl_schedule_plist(const char *exec_path, enum schedule_priorit
die(_("failed to create directories for '%s'"), filename);
plist = xfopen(filename, "w");

preamble = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n"
preamble = "<?xml version=\"1.0\"?>\n"
"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
"<plist version=\"1.0\">"
"<dict>\n"
Expand Down Expand Up @@ -1733,14 +1733,15 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
char *name = schtasks_task_name(frequency);
struct strbuf tfilename = STRBUF_INIT;

strbuf_addf(&tfilename, "schedule_%s_XXXXXX", frequency);
strbuf_addf(&tfilename, "%s/schedule_%s_XXXXXX",
get_git_common_dir(), frequency);
tfile = xmks_tempfile(tfilename.buf);
strbuf_release(&tfilename);

if (!fdopen_tempfile(tfile, "w"))
die(_("failed to create temp xml file"));

xml = "<?xml version=\"1.0\" encoding=\"US-ASCII\"?>\n"
xml = "<?xml version=\"1.0\" ?>\n"
"<Task version=\"1.4\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">\n"
"<Triggers>\n"
"<CalendarTrigger>\n";
Expand Down Expand Up @@ -1813,7 +1814,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
"</Settings>\n"
"<Actions Context=\"Author\">\n"
"<Exec>\n"
"<Command>\"%s\\git.exe\"</Command>\n"
"<Command>\"%s\\headless-git.exe\"</Command>\n"
"<Arguments>--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s</Arguments>\n"
"</Exec>\n"
"</Actions>\n"
Expand Down
114 changes: 114 additions & 0 deletions compat/win32/headless.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
/*
* 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(HINSTANCE instance, HINSTANCE previous_instance,
wchar_t *command_line, 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 = {
.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 + 11 >= sizeof(git_command_line) / sizeof(wchar_t))
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.cb = sizeof(STARTUPINFO);

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)) {
CloseHandle(process_info.hProcess);
return 130; /* Could not determine exit code? */
}

return (int)exit_code;
}
3 changes: 3 additions & 0 deletions config.mak.uname
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,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)
Expand Down Expand Up @@ -627,6 +629,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
RC = windres -O coff
NATIVE_CRLF = YesPlease
X = .exe
EXTRA_PROGRAMS += headless-git$X
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir = doc/git/html/
prefix =
Expand Down
3 changes: 3 additions & 0 deletions contrib/buildsystems/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,9 @@ if(WIN32)
else()
message(FATAL_ERROR "Unhandled compiler: ${CMAKE_C_COMPILER_ID}")
endif()

add_executable(headless-git ${CMAKE_SOURCE_DIR}/compat/win32/headless.c)
target_link_options(headless-git PUBLIC /NOLOGO /ENTRY:wWinMainCRTStartup /SUBSYSTEM:WINDOWS)
elseif(UNIX)
target_link_libraries(common-main pthread rt)
endif()
Expand Down
24 changes: 1 addition & 23 deletions gvfs-helper-client.c
Original file line number Diff line number Diff line change
Expand Up @@ -168,28 +168,6 @@ static int gh_client__send__objects_prefetch(struct child_process *process,
return 0;
}

/*
* Verify that the pathname found in the "odb" response line matches
* what we requested.
*
* Since we ALWAYS send a "--shared-cache=<path>" arg to "gvfs-helper",
* we should be able to verify that the value is what we requested.
* In particular, I don't see a need to try to search for the response
* value in from our list of alternates.
*/
static void gh_client__verify_odb_line(const char *line)
{
const char *v1_odb_path;

if (!skip_prefix(line, "odb ", &v1_odb_path))
BUG("verify_odb_line: invalid line '%s'", line);

if (!gh_client__chosen_odb ||
strcmp(v1_odb_path, gh_client__chosen_odb->path))
BUG("verify_odb_line: unexpeced odb path '%s' vs '%s'",
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jeffhostetler @dscho I hit this BUG during one of the functional tests near the end of the Scalar release build process so I'm going to cherry-pick this on top of vfs-2.30.0.

v1_odb_path, gh_client__chosen_odb->path);
}

/*
* Update the loose object cache to include the newly created
* object.
Expand Down Expand Up @@ -291,7 +269,7 @@ static int gh_client__objects__receive_response(
break;

if (starts_with(line, "odb")) {
gh_client__verify_odb_line(line);
/* trust that this matches what we expect */
}

else if (starts_with(line, "packfile")) {
Expand Down
7 changes: 3 additions & 4 deletions t/t7900-maintenance.sh
Original file line number Diff line number Diff line change
Expand Up @@ -535,17 +535,16 @@ test_expect_success 'start and stop Windows maintenance' '
EOF

rm -f args &&
GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" GIT_TRACE2_PERF=1 git maintenance start &&
GIT_TEST_MAINT_SCHEDULER="schtasks:./print-args" git maintenance start &&

# start registers the repo
git config --get --global maintenance.repo "$(pwd)" &&

for frequency in hourly daily weekly
do
grep "/create /tn Git Maintenance ($frequency) /f /xml" args &&
file=$(ls schedule_$frequency*.xml) &&
test_xmllint "$file" &&
grep "encoding=.US-ASCII." "$file" || return 1
file=$(ls .git/schedule_${frequency}*.xml) &&
test_xmllint "$file" || return 1
done &&

rm -f args &&
Expand Down