diff --git a/Makefile b/Makefile
index cac3452edb..8e60021b4f 100644
--- a/Makefile
+++ b/Makefile
@@ -2665,6 +2665,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)
@@ -3454,6 +3461,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)
@@ -3482,13 +3490,17 @@ 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,%.ilk,$(OTHER_PROGRAMS))
$(RM) $(patsubst %.exe,%.iobj,$(OTHER_PROGRAMS))
$(RM) $(patsubst %.exe,%.ipdb,$(OTHER_PROGRAMS))
$(RM) $(patsubst %.exe,%.pdb,$(PROGRAMS))
+ $(RM) $(patsubst %.exe,%.ilk,$(PROGRAMS))
$(RM) $(patsubst %.exe,%.iobj,$(PROGRAMS))
$(RM) $(patsubst %.exe,%.ipdb,$(PROGRAMS))
$(RM) $(patsubst %.exe,%.pdb,$(TEST_PROGRAMS))
+ $(RM) $(patsubst %.exe,%.ilk,$(TEST_PROGRAMS))
$(RM) $(patsubst %.exe,%.iobj,$(TEST_PROGRAMS))
$(RM) $(patsubst %.exe,%.ipdb,$(TEST_PROGRAMS))
$(RM) compat/vcbuild/MSVC-DEFS-GEN
diff --git a/builtin/gc.c b/builtin/gc.c
index 2753bd15a5..effe7ac156 100644
--- a/builtin/gc.c
+++ b/builtin/gc.c
@@ -1998,7 +1998,7 @@ static int schtasks_schedule_task(const char *exec_path, enum schedule_priority
"\n"
"\n"
"\n"
- "\"%s\\git.exe\"\n"
+ "\"%s\\headless-git.exe\"\n"
"--exec-path=\"%s\" for-each-repo --config=maintenance.repo maintenance run --schedule=%s\n"
"\n"
"\n"
diff --git a/compat/vcbuild/find_vs_env.bat b/compat/vcbuild/find_vs_env.bat
index b35d264c0e..379b16296e 100644
--- a/compat/vcbuild/find_vs_env.bat
+++ b/compat/vcbuild/find_vs_env.bat
@@ -99,6 +99,7 @@ REM ================================================================
SET sdk_dir=%WindowsSdkDir%
SET sdk_ver=%WindowsSDKVersion%
+ SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%%tgt%
SET si=%sdk_dir%Include\%sdk_ver%
SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared"
SET sl=%sdk_dir%lib\%sdk_ver%
@@ -130,6 +131,7 @@ REM ================================================================
SET sdk_dir=%WindowsSdkDir%
SET sdk_ver=%WindowsSDKVersion%
+ SET sdk_ver_bin_dir=%WindowsSdkVerBinPath%bin\amd64
SET si=%sdk_dir%Include\%sdk_ver%
SET sdk_includes=-I"%si%ucrt" -I"%si%um" -I"%si%shared" -I"%si%winrt"
SET sl=%sdk_dir%lib\%sdk_ver%
@@ -160,6 +162,11 @@ REM ================================================================
echo msvc_includes=%msvc_includes%
echo msvc_libs=%msvc_libs%
+ echo sdk_ver_bin_dir=%sdk_ver_bin_dir%
+ SET X1=%sdk_ver_bin_dir:C:=/C%
+ SET X2=%X1:\=/%
+ echo sdk_ver_bin_dir_msys=%X2%
+
echo sdk_includes=%sdk_includes%
echo sdk_libs=%sdk_libs%
diff --git a/compat/vcbuild/scripts/clink.pl b/compat/vcbuild/scripts/clink.pl
index c4c99d1a11..677d44e46f 100755
--- a/compat/vcbuild/scripts/clink.pl
+++ b/compat/vcbuild/scripts/clink.pl
@@ -15,6 +15,7 @@ my @cflags = ();
my @lflags = ();
my $is_linking = 0;
my $is_debug = 0;
+my $is_gui = 0;
while (@ARGV) {
my $arg = shift @ARGV;
if ("$arg" eq "-DDEBUG") {
@@ -67,7 +68,11 @@ while (@ARGV) {
}
push(@args, $lib);
} elsif ("$arg" eq "-lexpat") {
+ if ($is_debug) {
+ push(@args, "libexpatd.lib");
+ } else {
push(@args, "libexpat.lib");
+ }
} elsif ("$arg" =~ /^-L/ && "$arg" ne "-LTCG") {
$arg =~ s/^-L/-LIBPATH:/;
push(@lflags, $arg);
@@ -119,11 +124,23 @@ while (@ARGV) {
push(@cflags, "-wd4996");
} elsif ("$arg" =~ /^-W[a-z]/) {
# let's ignore those
+ } elsif ("$arg" eq "-fno-stack-protector") {
+ # eat this
+ } elsif ("$arg" eq "-mwindows") {
+ $is_gui = 1;
} else {
push(@args, $arg);
}
}
if ($is_linking) {
+ if ($is_gui) {
+ push(@args, "-ENTRY:wWinMainCRTStartup");
+ push(@args, "-SUBSYSTEM:WINDOWS");
+ } else {
+ push(@args, "-ENTRY:wmainCRTStartup");
+ push(@args, "-SUBSYSTEM:CONSOLE");
+ }
+
push(@args, @lflags);
unshift(@args, "link.exe");
} else {
diff --git a/compat/vcbuild/scripts/rc.pl b/compat/vcbuild/scripts/rc.pl
new file mode 100644
index 0000000000..7bca4cd81c
--- /dev/null
+++ b/compat/vcbuild/scripts/rc.pl
@@ -0,0 +1,46 @@
+#!/usr/bin/perl -w
+######################################################################
+# Compile Resources on Windows
+#
+# This is a wrapper to facilitate the compilation of Git with MSVC
+# using GNU Make as the build system. So, instead of manipulating the
+# Makefile into something nasty, just to support non-space arguments
+# etc, we use this wrapper to fix the command line options
+#
+######################################################################
+use strict;
+my @args = ();
+my @input = ();
+
+while (@ARGV) {
+ my $arg = shift @ARGV;
+ if ("$arg" =~ /^-[dD]/) {
+ # GIT_VERSION gets passed with too many
+ # layers of dquote escaping.
+ $arg =~ s/\\"/"/g;
+
+ push(@args, $arg);
+
+ } elsif ("$arg" eq "-i") {
+ my $arg = shift @ARGV;
+ # TODO complain if NULL or is dashed ??
+ push(@input, $arg);
+
+ } elsif ("$arg" eq "-o") {
+ my $arg = shift @ARGV;
+ # TODO complain if NULL or is dashed ??
+ push(@args, "-fo$arg");
+
+ } else {
+ push(@args, $arg);
+ }
+}
+
+push(@args, "-nologo");
+push(@args, "-v");
+push(@args, @input);
+
+unshift(@args, "rc.exe");
+printf("**** @args\n");
+
+exit (system(@args) != 0);
diff --git a/compat/win32/headless.c b/compat/win32/headless.c
new file mode 100644
index 0000000000..8b00dfe3bd
--- /dev/null
+++ b/compat/win32/headless.c
@@ -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
+#include
+#include
+#include
+
+/*
+ * 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;
+}
diff --git a/config.mak.uname b/config.mak.uname
index ff9e41213d..6e42d6b9dc 100644
--- a/config.mak.uname
+++ b/config.mak.uname
@@ -425,7 +425,7 @@ ifeq ($(uname_S),Windows)
# link.exe next to, and required by, cl.exe, we have to prepend this
# onto the existing $PATH.
#
- SANE_TOOL_PATH ?= $(msvc_bin_dir_msys)
+ SANE_TOOL_PATH ?= $(msvc_bin_dir_msys):$(sdk_ver_bin_dir_msys)
HAVE_ALLOCA_H = YesPlease
NO_PREAD = YesPlease
NEEDS_CRYPTO_WITH_SSL = YesPlease
@@ -488,12 +488,14 @@ endif
compat/win32/trace2_win32_process_info.o \
compat/win32/dirent.o
COMPAT_CFLAGS = -D__USE_MINGW_ACCESS -DDETECT_MSYS_TTY -DENSURE_MSYSTEM_IS_SET -DNOGDI -DHAVE_STRING_H -Icompat -Icompat/regex -Icompat/win32 -DSTRIP_EXTENSION=\".exe\"
- BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO -ENTRY:wmainCRTStartup -SUBSYSTEM:CONSOLE
+ BASIC_LDFLAGS = -IGNORE:4217 -IGNORE:4049 -NOLOGO
# invalidcontinue.obj allows Git's source code to close the same file
# handle twice, or to access the osfhandle of an already-closed stdout
# See https://msdn.microsoft.com/en-us/library/ms235330.aspx
EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj kernel32.lib ntdll.lib
+ GITLIBS += git.res
PTHREAD_LIBS =
+ RC = compat/vcbuild/scripts/rc.pl
lib =
BASIC_CFLAGS += $(vcpkg_inc) $(sdk_includes) $(msvc_includes)
ifndef DEBUG
@@ -525,6 +527,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)
@@ -677,6 +681,7 @@ ifeq ($(uname_S),MINGW)
RC = windres -O coff
NATIVE_CRLF = YesPlease
X = .exe
+ EXTRA_PROGRAMS += headless-git$X
ifneq (,$(wildcard ../THIS_IS_MSYSGIT))
htmldir = doc/git/html/
prefix =
diff --git a/contrib/buildsystems/CMakeLists.txt b/contrib/buildsystems/CMakeLists.txt
index ea2a531be8..c560c3c341 100644
--- a/contrib/buildsystems/CMakeLists.txt
+++ b/contrib/buildsystems/CMakeLists.txt
@@ -734,6 +734,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()
diff --git a/contrib/buildsystems/Generators/Vcxproj.pm b/contrib/buildsystems/Generators/Vcxproj.pm
index 81ee2f5a4a..a6d1c6b8d0 100644
--- a/contrib/buildsystems/Generators/Vcxproj.pm
+++ b/contrib/buildsystems/Generators/Vcxproj.pm
@@ -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;
@@ -89,6 +89,16 @@ sub createProject {
$defines =~ s/>/>/g;
$defines =~ s/\'//g;
+ my $rcdefines = $defines;
+ $rcdefines =~ s/(?\$(VCPKGLibDirectory);%(AdditionalLibraryDirectories)
\$(VCPKGLibs);\$(AdditionalDependencies)
invalidcontinue.obj %(AdditionalOptions)
- wmainCRTStartup
+ $entrypoint
$cdup\\compat\\win32\\git.manifest
- Console
+ $subsystem
EOM
if ($target eq 'libgit') {
@@ -203,6 +213,9 @@ EOM
WIN32;_DEBUG;$defines;%(PreprocessorDefinitions)
MultiThreadedDebugDLL
+
+ WIN32;_DEBUG;$rcdefines;%(PreprocessorDefinitions)
+
true
@@ -216,6 +229,9 @@ EOM
true
Speed
+
+ WIN32;NDEBUG;$rcdefines;%(PreprocessorDefinitions)
+
true
true
@@ -225,14 +241,20 @@ EOM
EOM
foreach(@sources) {
- print F << "EOM";
+ if (/\.rc$/) {
+ print F << "EOM";
+
+EOM
+ } else {
+ print F << "EOM";
EOM
+ }
}
print F << "EOM";
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"};
diff --git a/contrib/buildsystems/engine.pl b/contrib/buildsystems/engine.pl
index ed6c45988a..417ae71d44 100755
--- a/contrib/buildsystems/engine.pl
+++ b/contrib/buildsystems/engine.pl
@@ -165,7 +165,7 @@ sub parseMakeOutput
next;
}
- if($text =~ / -c /) {
+ if($text =~ / -c / || $text =~ / -i \S+\.rc /) {
# compilation
handleCompileLine($text, $line);
@@ -263,16 +263,15 @@ sub handleCompileLine
if ("$part" eq "-o") {
# ignore object file
shift @parts;
- } elsif ("$part" eq "-c") {
+ } elsif ("$part" eq "-c" || "$part" eq "-i" || "$part" =~ /^-fno-/) {
# ignore compile flag
- } elsif ("$part" eq "-c") {
} elsif ($part =~ /^.?-I/) {
push(@incpaths, $part);
} elsif ($part =~ /^.?-D/) {
push(@defines, $part);
} elsif ($part =~ /^-/) {
push(@cflags, $part);
- } elsif ($part =~ /\.(c|cc|cpp)$/) {
+ } elsif ($part =~ /\.(c|cc|cpp|rc)$/) {
$sourcefile = $part;
} else {
die "Unhandled compiler option @ line $lineno: $part";
@@ -359,7 +358,7 @@ sub handleLinkLine
push(@libs, $part);
} elsif ($part eq 'invalidcontinue.obj') {
# ignore - known to MSVC
- } elsif ($part =~ /\.o$/) {
+ } elsif ($part =~ /\.(o|res)$/) {
push(@objfiles, $part);
} elsif ($part =~ /\.obj$/) {
# do nothing, 'make' should not be producing .obj, only .o files
@@ -371,7 +370,9 @@ sub handleLinkLine
# exit(1);
foreach (@objfiles) {
my $sourcefile = $_;
+ $sourcefile =~ s/^headless-git\.o$/compat\/win32\/headless.c/;
$sourcefile =~ s/\.o$/.c/;
+ $sourcefile =~ s/\.res$/.rc/;
push(@sources, $sourcefile);
push(@cflags, @{$compile_options{"${sourcefile}_CFLAGS"}});
push(@defines, @{$compile_options{"${sourcefile}_DEFINES"}});