Skip to content
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

Fix error: "fatal: '$GIT_DIR' too big" when checking out in a long path with core.longpaths = true. #3877

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

kevin-worm
Copy link

This is a fix for issue: #3372

Use case:
Allow git to checkout a repository or submodule in a directory with a long
path when core.longpaths = true.

Example:

./git.exe config --global core.longpaths true
./git.exe clone https://github.com/git/git.git --recurse-submodules
/c/eval/git_test/loooooooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo
oooooooong

Context:
$ sh --version
GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
$ ./git.exe --version --build-options
git version 2.36.1.windows.1
cpu: x86_64
built from commit: e2ff68a
sizeof-long: 4
sizeof-size_t: 8
shell-path: /bin/sh
feature: fsmonitor--daemon

Error:
fatal: '$GIT_DIR' too big.

Problem analysis:
setup_explicit_git_dir in setup.c uses PATH_MAX to check if the git dir is
to long. On Windows PATH_MAX is set by limit.h to 260 and
setup_explicit_git_dir ignores core.longpaths.

Solution:
The implementation is based on the solution proposed by Johannes
Schindelin, see:
#3372 (comment)

  • Refactor the part of trace2_initialize() that reads the config.
  • Make tr2_sysenv_cb() a public function.
  • No longer calling it from trace2_initialize(), but from a static callback
    function in common-main.c.
  • Calling read_very_early_config() explicitly in main(), with that static
    callback function that calls into tr2_sysenv_cb().
  • Extend the static callback function for Windows, and parse core.longPaths
    in that function.
  • Extend startup_info struct in cache.h with 'int max_long_path' so we can
    use it in setup_explicit_git_dir instead of PATH_MAX.

Signed-off-by: Kevin Worm kevin.worm_1@signify.com

kevin-worm and others added 3 commits May 30, 2022 11:47
The Trace2 machinery wishes to parse the system and the global config
early. To this end, it taps into the `common-main` framework to run
first thing.

There are more Git features that could benefit from such a handling,
most notably the Windows-specific `core.longPaths` setting: If a user
has worktrees whose path already requires long paths support, we cannot
wait until we parse the the config settings in the usual way because
the gitdir discovery needs to happen first and would fail because any
`core.longPaths` setting in, say, `~/.gitconfig` would not have been
parsed yet.

To that end, let's refactor Trace2's early config parsing so that other
users can tap into it, too.

Signed-off-by: Kevin Worm <kevin.worm_1@signify.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Use case:
Allow git to checkout a repository or submodule in a directory with a long
path when core.longpaths = true.

Example:
> ./git.exe config --global core.longpaths true
> ./git.exe clone https://github.com/git/git.git --recurse-submodules \
/c/eval/git_test/loooooooooooooooooooooooooooooooooooooooooooooooooooooooo\
oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\
oooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo\
oooooooong

Context:
$ sh --version
	GNU bash, version 4.4.23(1)-release (x86_64-pc-msys)
$ ./git.exe --version --build-options
	git version 2.36.1.windows.1
	cpu: x86_64
	built from commit: e2ff68a
	sizeof-long: 4
	sizeof-size_t: 8
	shell-path: /bin/sh
	feature: fsmonitor--daemon

Error:
fatal: '$GIT_DIR' too big.

Problem analysis:
setup_explicit_git_dir in setup.c uses PATH_MAX to check if the git dir is
to long. On Windows PATH_MAX is set by limit.h to 260 and
setup_explicit_git_dir ignores core.longpaths.

Solution:
The implementation is based on the solution proposed by Johannes
Schindelin, see:
git-for-windows#3372 (comment)

* Refactor the part of trace2_initialize() that reads the config.
* Make tr2_sysenv_cb() a public function.
* No longer calling it from trace2_initialize(), but from a static callback
  function in common-main.c.
* Calling read_very_early_config() explicitly in main(), with that static
  callback function that calls into tr2_sysenv_cb().
* Extend the static callback function for Windows, and parse core.longPaths
  in that function.
* Extend startup_info struct in cache.h with 'int max_long_path' so we can
  use it in setup_explicit_git_dir instead of PATH_MAX.

This fixes git-for-windows#3372

Signed-off-by: Kevin Worm <kevin.worm_1@signify.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
This currently fails, with:

	++ p=/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef
	++ p=y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef
	++ test_config_global core.longpaths true
	++ test_when_finished 'test_unconfig --global '\''core.longpaths'\'''
	++ test 0 = 0
	++ test_cleanup='{ test_unconfig --global '\''core.longpaths'\''
			} && (exit "$eval_ret"); eval_ret=$?; :'
	++ git config --global core.longpaths true
	++ git init y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef
	fatal: cannot chdir to y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef: No such file or directory
	error: last command exited with $?=128
	not ok 7 - init with long path

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
@dscho
Copy link
Member

dscho commented May 30, 2022

@kevin-worm thank you for working on this!

I figured out the failures in 0210 (the early config must be parsed before initializing Trace2), split the commit in two and added a regression test.

Please find the updated branch in your fork (thank you for allowing to update this PR that way, that makes things much more convenient than it would be otherwise).

However, the test case I added fails because the chdir() fails with:

[...]
++ p=/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef
++ p=y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789
abcdef/123456789abc/ef
++ test_config_global core.longpaths true
++ test_when_finished 'test_unconfig --global '\''core.longpaths'\'''
++ test 0 = 0
++ test_cleanup='{ test_unconfig --global '\''core.longpaths'\''
                } && (exit "$eval_ret"); eval_ret=$?; :'
++ git config --global core.longpaths true
++ git init y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789a
bcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/12
3456789abcdef/123456789abc/ef
fatal: cannot chdir to y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef
/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/1234567
89abcdef/123456789abcdef/123456789abc/ef: No such file or directory
error: last command exited with $?=128
not ok 7 - init with long path
#
#               p=/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef &&
#               p=y$p$p$p$p &&
#               test_config_global core.longpaths true &&
#               git init $p
#

I see that the directory exists, but as git.exe is a regular native Win32 application, it cannot change the current working directory to such a long path.

Can you verify this behavior?

@kevin-worm
Copy link
Author

kevin-worm commented May 30, 2022

@dscho thanks for your quick response and edits!

I see the same behavior.

$ git config --global core.longpaths true

$ ./git.exe init /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef
fatal: cannot chdir to C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef: No such file or directory

@kevin-worm
Copy link
Author

kevin-worm commented May 30, 2022

The issue is caused by _wchdir in mingw_chdir which does not support the '\\?\' extended path-length prefix.

I have tried to replace it with SetCurrentDirectory, but it requires the application to opt-in to remove the MAX_PATH limitation and I have not been able to get that to work.

I tried updating the manifest 'compat/win32/git.manifest' to make the executable longPathAware, but that does not seem to do anything.

<application xmlns="urn:schemas-microsoft-com:asm.v3">
	<windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
		<ws2:longPathAware>true</ws2:longPathAware>
	</windowsSettings>
</application>

@dscho
Copy link
Member

dscho commented May 30, 2022

I tried updating the manifest 'compat/win32/git.manifest' to make the executable longPathAware, but that does not seem to do anything.

As far as I remember, you also have to set Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled.

Plus, you have to ensure that the code does not use PATH_MAX (or MAX_PATH) when it should use a larger value, but I think that this particular chdir() does not need to be changed.

@kevin-worm
Copy link
Author

kevin-worm commented May 31, 2022

That's correct, I have set the register entry to 1.

According to the Microsoft documentation the following is required to circumvent the MAX_PATH limitation:

  • Computer\HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\FileSystem\LongPathsEnabled set to 1.
  • The system needs to be at least Windows 10, version 1607.
  • The application manifest must also include the longPathAware element.
  • One of the following directory management functions needs to be used: CreateDirectoryW, CreateDirectoryExW GetCurrentDirectoryW RemoveDirectoryW SetCurrentDirectoryW.
  • To specify an extended-length path, use the "\\?\" prefix.
  • Because you cannot use the "\\?\" prefix with a relative path, relative paths are always limited to a total of MAX_PATH characters.

See: https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation

I have added some printf statements to mingw_chdir so I could locate the issue:

$ ./git.exe init /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
mingw_chdir, dirname=C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
mingw_chdir, core_long_paths=1
mingw_chdir, wdirname=\\?\C:\eval\y\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\
mingw_chdir, result=-1
mingw_chdir, dirname=C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
mingw_chdir, core_long_paths=1
mingw_chdir, wdirname=\\?\C:\eval\y\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\
mingw_chdir, result=-1
fatal: cannot chdir to C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/: No such file or directory

The result from _wchdir is -1 which indicates a failure. When I retry my test with a shorter path it returns '0' so it seems to be the cause of the error.

I suspect that the reason why SetCurrentDirectoryW does not work as intended might be related to the msbuild / Windows SDK version.

@dscho
Copy link
Member

dscho commented May 31, 2022

Maybe https://github.com/git-for-windows/git/blob/HEAD/compat/win32/git.manifest is incomplete? I do not see Windows 11 there, that's most likely an oversight on my part.

@kevin-worm
Copy link
Author

Windows 11 shares the same supportedOS GUID as Windows 10 (https://docs.microsoft.com/en-us/windows/win32/sbscs/application-manifests#supportedos) so that should be fine, but we could update the comment for clarity.

I have created a small C test app in visual studio 2022 (17.2.2) and in the test app it works with the updated manifest:

#include <stdio.h>
#include <Windows.h>

int main() {
	wchar_t* wdirname = L"\\\\?\\C:\\eval\\y\\123456789abcdef\\123456789abcdef\\123456789abcdef\\123456789abc\\ef\\123456789abcdef\\123456789abcdef\\123456789abcdef\\123456789abc\\ef\\123456789abcdef\\123456789abcdef\\123456789abcdef\\123456789abc\\ef\\123456789abcdef\\123456789abcdef\\123456789abcdef\\123456789abc\\ef\\";
	int result = 0;

	printf("mingw_chdir, wdirname=%ls\n", wdirname);
	if (!SetCurrentDirectoryW(wdirname)) {
		result = -1;
	}

	printf("mingw_chdir, GetLastError()=%d\n", GetLastError());
	printf("mingw_chdir, result=%d\n", result);

	return 0;
}

Manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly
	xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"
	xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
	<trustInfo
		xmlns="urn:schemas-microsoft-com:asm.v3">
		<security>
			<requestedPrivileges>
				<requestedExecutionLevel level="asInvoker" uiAccess="false"></requestedExecutionLevel>
			</requestedPrivileges>
		</security>
	</trustInfo>
	<application
		xmlns="urn:schemas-microsoft-com:asm.v3">
		<windowsSettings
			xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
			<ws2:longPathAware>true</ws2:longPathAware>
		</windowsSettings>
	</application>
</assembly>

Project file .vcxproj:

...
<PlatformToolset>v143</PlatformToolset>
...
<WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion>
...

Result:

$ ./ConsoleApplication1.exe
mingw_chdir, wdirname=\\?\C:\eval\y\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\
mingw_chdir, GetLastError()=0
mingw_chdir, result=0

Removing longPathAware from the manifest causes the same error code that I also see in git:

$ ./ConsoleApplication1.exe
mingw_chdir, wdirname=\\?\C:\eval\y\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\
mingw_chdir, GetLastError()=206
mingw_chdir, result=-1

When I look for the manifest in git.exe I find 2 different manifests.

  • git.exe\.rsrc\0\MANIFEST\1
  • git.exe\.rsrc\1033\RT_MANIFEST\1

The second manifest is the updated manifest (compat/win32/git.manifest), but the first one is the old unchanged version. In my test project I only have .rsrc\0\MANIFEST\1 so I suspect this might be the issue.

Only I have no idea where the first manifest is coming from as ./compat/win32/git.manifest is the only manifest in the project.

$ grep -rnwI . -e "{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"
./compat/win32/git.manifest:27:                 <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>

compat/win32/git.manifest:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
	<assemblyIdentity type="win32" name="Git" version="0.0.0.1" />
	<asmv3:application>
		<asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
			<ws2:longPathAware>true</ws2:longPathAware>
		</asmv3:windowsSettings>
	</asmv3:application>
	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
		<security>
			<requestedPrivileges>
				<requestedExecutionLevel level="asInvoker" uiAccess="false" />
			</requestedPrivileges>
		</security>
	</trustInfo>
	<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
		<application>
			<!-- Windows Vista -->
			<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
			<!-- Windows 7 -->
			<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
			<!-- Windows 8 -->
			<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
			<!-- Windows 8.1 -->
			<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
			<!-- Windows 10 and Windows 11 -->
			<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
		</application>
	</compatibility>
</assembly>

git.exe\.rsrc\0\MANIFEST\1:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
  <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
    <security>
      <requestedPrivileges>
        <requestedExecutionLevel level="asInvoker"/>
      </requestedPrivileges>
    </security>
  </trustInfo>
  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
    <application>
      <!--The ID below indicates application support for Windows Vista -->
      <supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
      <!--The ID below indicates application support for Windows 7 -->
      <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
      <!--The ID below indicates application support for Windows 8 -->
      <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
      <!--The ID below indicates application support for Windows 8.1 -->
      <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> 
      <!--The ID below indicates application support for Windows 10 -->
      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> 
    </application>
  </compatibility>
</assembly>

git.exe\.rsrc\1033\RT_MANIFEST\1:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
	<assemblyIdentity type="win32" name="Git" version="0.0.0.1" />
	<asmv3:application>
		<asmv3:windowsSettings xmlns:ws2="http://schemas.microsoft.com/SMI/2016/WindowsSettings">
			<ws2:longPathAware>true</ws2:longPathAware>
		</asmv3:windowsSettings>
	</asmv3:application>
	<trustInfo xmlns="urn:schemas-microsoft-com:asm.v2">
		<security>
			<requestedPrivileges>
				<requestedExecutionLevel level="asInvoker" uiAccess="false" />
			</requestedPrivileges>
		</security>
	</trustInfo>
	<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
		<application>
			<!-- Windows Vista -->
			<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
			<!-- Windows 7 -->
			<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
			<!-- Windows 8 -->
			<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
			<!-- Windows 8.1 -->
			<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
			<!-- Windows 10 and Windows 11 -->
			<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
		</application>
	</compatibility>
</assembly>

@dscho
Copy link
Member

dscho commented Jun 1, 2022

Maybe it's coming from here via mingw-w64-windows-default-manifest?

@kevin-worm
Copy link
Author

I got it working with a small change in git.rc and longPathAware enabled in the manifest, but another error popped up.

diff --git a/git.rc b/git.rc
index cc3fdc6cc6..e0b38ee5f5 100644
--- a/git.rc
+++ b/git.rc
@@ -21,4 +21,4 @@ BEGIN
   END
 END

-1 RT_MANIFEST "compat/win32/git.manifest"
+1 MANIFEST "compat/win32/git.manifest"

$ ./git.exe init /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
fatal: unable to get current working directory: Filename too long

So I guess the next step is going through compat/mingw.c and replacing all the api calls that don't support long paths.

@dscho
Copy link
Member

dscho commented Jun 1, 2022

unable to get current working directory: Filename too long

This one might be as easy as replacing MAX_PATH with MAX_LONG_PATH in https://github.com/git/git/blob/v2.36.1/compat/mingw.c#L1122

@dscho
Copy link
Member

dscho commented Jun 1, 2022

-1 RT_MANIFEST "compat/win32/git.manifest"
+1 MANIFEST "compat/win32/git.manifest"

Wow, that must be a bug introduced by me. Sorry about that!

Would you happen to know off the back of your head what the difference is between MANIFEST and RT_MANIFEST?

@kevin-worm
Copy link
Author

kevin-worm commented Jun 1, 2022

This one might be as easy as replacing MAX_PATH with MAX_LONG_PATH in https://github.com/git/git/blob/v2.36.1/compat/mingw.c#L1122

That is one more mole whacked, that fixes mingw_getcwd. The next issue is mingw_mktemp:

$ ./git.exe init /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
fatal: Unable to create temporary file 'C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789': Filename too long

Would you happen to know off the back of your head what the difference is between MANIFEST and RT_MANIFEST?

To be honest I'm not sure. In a fresh Visual Studio 2022 C project only a MANIFEST is added and the windows-default-manifest from MINGW also only uses the MANIFEST file with the same xml schema. So I think that's the only one that's required, but it's a bit of an assumption.

https://docs.microsoft.com/en-us/cpp/build/reference/manifest-create-side-by-side-assembly-manifest?view=msvc-170 mentions RT_MANIFEST, but does not explain why one would use it.

@dscho
Copy link
Member

dscho commented Jun 1, 2022

Would you happen to know off the back of your head what the difference is between MANIFEST and RT_MANIFEST?

To be honest I'm not sure. In a fresh Visual Studio 2022 C project only a MANIFEST is added and the windows-default-manifest from MINGW also only uses the MANIFEST file with the same xml schema. So I think that's the only one that's required, but it's a bit of an assumption.

https://docs.microsoft.com/en-us/cpp/build/reference/manifest-create-side-by-side-assembly-manifest?view=msvc-170 mentions RT_MANIFEST, but does not explain why one would use it.

https://docs.microsoft.com/en-us/previous-versions/bb756929(v=msdn.10) also mentions RT_MANIFEST, and to be honest, I am having trouble finding any resource that would recommend MANIFEST instead...

mingw_mktemp

Indeed. However, this comment gives me pause:

/* we need to return the path, thus no long paths here! */

And indeed, the template we expect to be passed into that function is supposedly of maximal size MAX_PATH... Having said that, this line suggests to me that we might have passed a buffer large enough to hold the result.

In git init's case, it seems to be this call which passes in a buffer from an strbuf, so it should be safe.

I have not performed the same analysis for all callers, but the fact that the xwcstoutf() call in mingw_mktemp() does not use MAX_PATH but instead the strlen(template) makes me optimistic about replacing the MAX_PATH by a MAX_LONG_PATH in that function.

@kevin-worm
Copy link
Author

kevin-worm commented Jun 1, 2022

I think I understand what's going on with the manifest file.

For the manifest to work it should be at git.exe\.rsrc\MANIFEST\1 in the executable. The way to do this according to al the documentation is by adding 1 RT_MANIFEST "compat/win32/git.manifest". RT_MANIFEST is a define that resolves to 24, that matches what we see in default-manifest.rc (1 24 MOVEABLE PURE DISCARDABLE), they just skipped the define.

I also found the header files:

$ grep -rnwI /mingw64/include/ -e "RT_MANIFEST"
/mingw64/include/ruby-3.0.0/x64-mingw32/rb_mjit_min_header-3.0.3.h:69271:#define RT_MANIFEST MAKEINTRESOURCE(24)
/mingw64/include/winuser.h:146:#define RT_MANIFEST 24
/mingw64/include/winuser.h:154:#define RT_MANIFEST MAKEINTRESOURCE(24)
/mingw64/include/winuser.rh:6:#define RT_MANIFEST 24

if I replace 1 MANIFEST "compat/win32/git.manifest" with 1 ABCD "compat/win32/git.manifest" the result is git.manifest at git.exe\.rsrc\1033\ABCD\1 and the windows-default-manifest at git.exe\.rsrc\MANIFEST\1.

If I replace 1 MANIFEST "compat/win32/git.manifest" with 1 24 "compat/win32/git.manifest" the result is git.manifest at git.exe\.rsrc\MANIFEST\1.

So it looks like RT_MANIFEST is correct, but for some reason unresolved. The correct workaround is replacing it with 24. I was just lucky that you can use any string and MANIFEST also causes the manifest to end up in the right location.

@kevin-worm
Copy link
Author

I managed to get a bit further by fixing mingw_mktemp and mkstemp.

char *mingw_mktemp(char *template)
{
	wchar_t wtemplate[MAX_LONG_PATH];
	int offset = 0;

	if (xutftowcs_path_ex(wtemplate, template, MAX_LONG_PATH, -1, 248,
			      core_long_paths) < 0)
		return NULL;

	if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) &&
	    iswalpha(wtemplate[0]) && wtemplate[1] == L':') {
		/* We have an absolute path missing the drive prefix */
		offset = 2;
	}
	if (!_wmktemp(wtemplate))
		return NULL;
	if (xwcstoutf(template, wtemplate + offset, strlen(template) + 1) < 0)
		return NULL;
	return template;
}

int mkstemp(char *template)
{
	wchar_t wtemplate[MAX_LONG_PATH];

	if (xutftowcs_path_ex(wtemplate, template, MAX_LONG_PATH, -1, 248,
			      core_long_paths) < 0)
		return -1;
	if (!_wmktemp(wtemplate))
		return -1;

	return _wopen(wtemplate, O_RDWR | O_CREAT, 0600);
}

Init now works with long paths:

$ ./git.exe init /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>
Initialized empty Git repository in C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/.git/

But cloning still has some issues:

$ ./git.exe clone https://github.com/git/git.git --recurse-submodules /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
Cloning into 'C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef'...
remote: Enumerating objects: 327280, done.
remote: Counting objects: 100% (273/273), done.
remote: Compressing objects: 100% (128/128), done.
remote: Total 327280 (delta 171), reused 190 (delta 145), pack-reused 327007
Receiving objects: 100% (327280/327280), 190.64 MiB | 9.28 MiB/s, done.
Resolving deltas: 100% (244650/244650), done.
Updating files: 100% (4138/4138), done.
error: cannot spawn git: Invalid argument

@kevin-worm
Copy link
Author

kevin-worm commented Jun 2, 2022

The issue is caused by CreateProcessW in mingw_spawnve_fd. With submodules the current directory is changed to the git root dir before starting the process and CreateProcessW throws error code 87 - ERROR_INVALID_PARAMETER when the current dir is a long path.

git clone and git init work from outside of the git project dir so they don't cause the same issue. Maybe we could change the way the git-submodule is started so it's not executed from within the git project dir?

What I have seen so far indicates that there are no issues when changing the current dir to a long path after the process has started. The challenge seems to be that git-submodule does not expose a parameter like -C so there is no build in way to communicate the folder the proces needs to change to.

I will commit the changes I made so far.

`t2031-checkout-long-paths.sh` runs successfully, but there are still
issues remaining preventing submodules from working in a long path dir.

Signed-off-by: Kevin Worm <kevin.worm_1@signify.com>
@dscho
Copy link
Member

dscho commented Jun 3, 2022

The issue is caused by CreateProcessW in mingw_spawnve_fd.

Did you see that wdir is still only MAX_PATH long?

@kevin-worm
Copy link
Author

kevin-worm commented Jun 3, 2022

Yes, but I when I traced the origin I found that dir is set to NULL in every call that I could find. Only an example in tmp-objdir.h shows a call to run_command_v_opt_cd_env that would set it (run_command_v_opt_cd_env(cmd, 0, NULL, tmp_objdir_env(t)).

When dir is NULL lpCurrentDirectory is set to NULL as well.

CreateProcessW(..., lpCurrentDirectory: dir ? wdir : NULL, ...)

https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw

[in, optional] lpCurrentDirectory

The full path to the current directory for the process. The string can also specify a UNC path.

If this parameter is NULL, the new process will have the same current drive and directory as the calling process. (This feature is provided primarily for shells that need to start an application and specify its initial drive and working directory.)

@dscho
Copy link
Member

dscho commented Jun 8, 2022

Yes, but I when I traced the origin I found that dir is set to NULL in every call that I could find.

Hrm. That's odd.

Can you pinpoint which call it is that is failing? E.g. by running the command with GIT_TRACE=1?

@kevin-worm
Copy link
Author

GIT_TRACE=1 ./git.exe clone https://github.com/git/git.git --recurse-submodules /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
15:57:11.546520 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
15:57:11.549728 git.c:459               trace: built-in: git clone https://github.com/git/git.git --recurse-submodules C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
Cloning into 'C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef'...
15:57:11.741833 run-command.c:654       trace: run_command: git remote-https origin https://github.com/git/git.git
15:57:11.768220 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
15:57:11.772062 git.c:748               trace: exec: git-remote-https origin https://github.com/git/git.git
15:57:11.772062 run-command.c:654       trace: run_command: git-remote-https origin https://github.com/git/git.git
15:57:12.061086 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
15:57:13.104446 run-command.c:654       trace: run_command: git index-pack --stdin -v --fix-thin '--keep=fetch-pack 14308 on DESKTOP-X' --check-self-contained-and-connected
15:57:13.126416 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
15:57:13.130756 git.c:459               trace: built-in: git index-pack --stdin -v --fix-thin '--keep=fetch-pack 14308 on DESKTOP-X' --check-self-contained-and-connected
remote: Enumerating objects: 327806, done.
remote: Counting objects: 100% (281/281), done.
remote: Compressing objects: 100% (151/151), done.
remote: Total 327806 (delta 165), reused 218 (delta 130), pack-reused 327525
Receiving objects: 100% (327806/327806), 191.19 MiB | 10.97 MiB/s, done.
Resolving deltas: 100% (245057/245057), done.
15:57:52.279441 run-command.c:654       trace: run_command: git rev-list --objects --stdin --not --all --quiet --alternate-refs '--progress=Checking connectivity'
15:57:52.331494 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
15:57:52.336958 git.c:459               trace: built-in: git rev-list --objects --stdin --not --all --quiet --alternate-refs '--progress=Checking connectivity'
Updating files: 100% (4146/4146), done.
15:57:58.916787 run-command.c:654       trace: run_command: git submodule update --require-init --recursive --progress --no-single-branch
error: cannot spawn git: Invalid argument

Earlier I had added some printf statements to check the current dir with GetCurrentDirectoryW before CreateProcessW was called and the current dir of the main process was changed to the long path when the error occurred. So with dir and lpCurrentDirectory set to NULL the new process will have the same current drive and directory as the calling process.

@dscho
Copy link
Member

dscho commented Jun 8, 2022

So with dir and lpCurrentDirectory set to NULL the new process will have the same current drive and directory as the calling process.

One particular problem, I think, might be that git submodule is actually a shell script, and I could imagine that it is bash.exe that is simply not long-paths-aware. But the funny thing is that if that was the culprit, I would have expected to see a trace: exec: git-submodule [...] line in the output, and I do not see that.

@kevin-worm
Copy link
Author

The second-to-last log line does mention `git submodule':

...
15:57:58.916787 run-command.c:654       trace: run_command: git submodule update --require-init --recursive --progress --no-single-branch
error: cannot spawn git: Invalid argument

The call fails when git submodule is executed with CreateProcessW in mingw_spawnve_fd. I have added a few printf statements to show this.

Diff:

diff --git a/compat/mingw.c b/compat/mingw.c
index 25293452d8..e8c2d6e456 100644
--- a/compat/mingw.c
+++ b/compat/mingw.c
@@ -2036,11 +2036,19 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
                flags |= EXTENDED_STARTUPINFO_PRESENT;
        }

+       wchar_t current_dir[MAX_LONG_PATH];
+       GetCurrentDirectoryW(ARRAY_SIZE(current_dir), current_dir);
+
+       fprintf(stderr, "mingw_spawnve_fd, GetCurrentDirectoryW() = %ls\n", current_dir);
+
+
        ret = CreateProcessW(*wcmd ? wcmd : NULL, wargs, NULL, NULL,
                             stdhandles_count ? TRUE : FALSE,
                             flags, wenvblk, dir ? wdir : NULL,
                             &si.StartupInfo, &pi);

+       fprintf(stderr, "mingw_spawnve_fd, CreateProcessW(lpApplicationName=%ls, lpCommandLine=%ls, ..., lpCurrentDirectory=%ls, ...) = %d\n", *wcmd ? wcmd : NULL, wargs, dir ? wdir : NULL, ret);
+
        /*
         * On Windows 2008 R2, it seems that specifying certain types of handles
         * (such as FILE_TYPE_CHAR or FILE_TYPE_PIPE) will always produce an

Log:

GIT_TRACE=1 ./git.exe clone https://github.com/git/git.git --recurse-submodules /c/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
14:07:24.430808 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
14:07:24.432500 git.c:459               trace: built-in: git clone https://github.com/git/git.git --recurse-submodules C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/
Cloning into 'C:/eval/y/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef/123456789abcdef/123456789abcdef/123456789abcdef/123456789abc/ef'...
14:07:24.604375 run-command.c:654       trace: run_command: git remote-https origin https://github.com/git/git.git
mingw_spawnve_fd, GetCurrentDirectoryW() = C:\Users\600019538\Documents\projects\git
mingw_spawnve_fd, CreateProcessW(lpApplicationName=C:\Users\600019538\Documents\projects\git\git.exe, lpCommandLine=git remote-https origin https://github.com/git/git.git, ..., lpCurrentDirectory=(NULL), ...) = 1
14:07:24.627139 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
14:07:24.629841 git.c:748               trace: exec: git-remote-https origin https://github.com/git/git.git
14:07:24.629841 run-command.c:654       trace: run_command: git-remote-https origin https://github.com/git/git.git
mingw_spawnve_fd, GetCurrentDirectoryW() = C:\Users\600019538\Documents\projects\git
mingw_spawnve_fd, CreateProcessW(lpApplicationName=C:\Users\600019538\Documents\projects\git\git-remote-https.exe, lpCommandLine=git-remote-https origin https://github.com/git/git.git, ..., lpCurrentDirectory=(NULL), ...) = 1
14:07:24.964107 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
14:07:25.872314 run-command.c:654       trace: run_command: git index-pack --stdin -v --fix-thin '--keep=fetch-pack 18396 on DESKTOP-X' --check-self-contained-and-connected
mingw_spawnve_fd, GetCurrentDirectoryW() = C:\Users\600019538\Documents\projects\git
mingw_spawnve_fd, CreateProcessW(lpApplicationName=C:\Users\600019538\Documents\projects\git\git.exe, lpCommandLine=git index-pack --stdin -v --fix-thin "--keep=fetch-pack 18396 on DESKTOP-2VTCABB" --check-self-contained-and-connected, ..., lpCurrentDirectory=(NULL), ...) = 1
14:07:25.949906 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
14:07:25.954780 git.c:459               trace: built-in: git index-pack --stdin -v --fix-thin '--keep=fetch-pack 18396 on DESKTOP-X' --check-self-contained-and-connected
remote: Enumerating objects: 327889, done.
remote: Counting objects: 100% (385/385), done.
remote: Compressing objects: 100% (184/184), done.
remote: Total 327889 (delta 240), reused 295 (delta 200), pack-reused 327504
Receiving objects: 100% (327889/327889), 190.83 MiB | 10.59 MiB/s, done.
Resolving deltas: 100% (245127/245127), done.
14:08:03.724848 run-command.c:654       trace: run_command: git rev-list --objects --stdin --not --all --quiet --alternate-refs '--progress=Checking connectivity'
mingw_spawnve_fd, GetCurrentDirectoryW() = C:\Users\600019538\Documents\projects\git
mingw_spawnve_fd, CreateProcessW(lpApplicationName=C:\Users\600019538\Documents\projects\git\git.exe, lpCommandLine=git rev-list --objects --stdin --not --all --quiet --alternate-refs "--progress=Checking connectivity", ..., lpCurrentDirectory=(NULL), ...) = 1
14:08:03.803054 exec-cmd.c:237          trace: resolved executable dir: C:/Users/600019538/Documents/projects/git
14:08:03.809854 git.c:459               trace: built-in: git rev-list --objects --stdin --not --all --quiet --alternate-refs '--progress=Checking connectivity'
Updating files: 100% (4147/4147), done.
14:08:10.528448 run-command.c:654       trace: run_command: git submodule update --require-init --recursive --progress --no-single-branch
mingw_spawnve_fd, GetCurrentDirectoryW() = \\?\C:\eval\y\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef\123456789abcdef\123456789abcdef\123456789abcdef\123456789abc\ef
mingw_spawnve_fd, CreateProcessW(lpApplicationName=C:\Users\600019538\Documents\projects\git\git.exe, lpCommandLine=git submodule update --require-init --recursive --progress --no-single-branch, ..., lpCurrentDirectory=(NULL), ...) = 0
error: cannot spawn git: Invalid argument

Only when it tries to call git submodule update --require-init --recursive --progress --no-single-branch the main process current dir changes to the long path and this causes CreateProcessW to fail.

@dscho
Copy link
Member

dscho commented Jun 10, 2022

C:\Users\600019538\Documents\projects\git\git.exe

Is this really legitimate?

@dscho
Copy link
Member

dscho commented Jun 10, 2022

C:\Users\600019538\Documents\projects\git\git.exe

Is this really legitimate?

Heh, I mistook this path to refer to the worktree you're checking out, but this is probably just your checkout of Git itself.

In any case, I think we're hitting the issue here that is covered by https://stackoverflow.com/questions/51524910/subprocess-cwd-too-long-builtins-notadirectoryerror-winerror-267...

@kevin-worm
Copy link
Author

Heh, I mistook this path to refer to the worktree you're checking out, but this is probably just your checkout of Git itself.

Indeed that is my checkout of git itself.

In any case, I think we're hitting the issue here that is covered by https://stackoverflow.com/questions/51524910/subprocess-cwd-too-long-builtins-notadirectoryerror-winerror-267...

I agree, I think it's the same issue.

I think we have a few options to work around this issue:

  • Create a symbolic link / junction of the long path project dir to a shorter path in tmp.
  • Start the child process in a shorter path, but set the env variable GIT_WORK_TREE.
  • Start the child process in a shorter path and switch the current dir after it has started. (e.g .git -C $LONG_PATH submodule update)

@dscho
Copy link
Member

dscho commented Jun 13, 2022

  • Create a symbolic link / junction of the long path project dir to a shorter path in tmp.

  • Start the child process in a shorter path, but set the env variable GIT_WORK_TREE.

  • Start the child process in a shorter path and switch the current dir after it has started. (e.g .git -C $LONG_PATH submodule update)

To be honest, none of these really sound appealing ;-)

The lease bad might be the third option, where we detect a too-long current working directory path and temporarily switch to the root directory of the corresponding drive.

However, that will most likely not fix anything for git submodule, which still is a shell script as I've mentioned earlier. The git.exe process might work, but then it will fail to launch the "dashed external".

@rimrul
Copy link
Member

rimrul commented Jun 15, 2022

  • Start the child process in a shorter path and switch the current dir after it has started. (e.g .git -C $LONG_PATH submodule update)

To be honest, none of these really sound appealing ;-)

The lease bad might be the third option, where we detect a too-long current working directory path and temporarily switch to the root directory of the corresponding drive.

However, that will most likely not fix anything for git submodule, which still is a shell script as I've mentioned earlier. The git.exe process might work, but then it will fail to launch the "dashed external".

One (admittedly nasty) approach that could get us slightly further along the call chain is to create the process in a suspended state in some short directory and then modify it's current directory before resuming, but that would then need similar patches to msys2-runtime to allow bash to run non-builtins from the dashed external in such a long working directory. This would also lead us deep into undocumented NTAPI territory and potentially increase our chance of being falsely detected as malware.

@dscho
Copy link
Member

dscho commented Jun 15, 2022

Another potential solution would be to teach the MSYS2 runtime the long paths trick, e.g. by introducing a configure knob that includes the manifest entry and increases MAX_PATH to something large, say, 4096 (as Git for Windows does).

The biggest problem with that is that some POSIX functions assume an implicit MAX_PATH "parameter", i.e. they think that buffers passed in as pointers have that much space available, and if any MSYS2 program is compiled with a smaller value and calls such a function the MSYS2 runtime will cause memory corruption.

So we would need to require programs to be recompiled, and to indicate in some fashion that they use a longer MAX_PATH than the current MSYS2 runtime. That idea gets tricky pretty quickly.

@skdsp
Copy link

skdsp commented Jun 5, 2024

Hi,

@kevin-worm, @dscho, @rimrul first of all, thank you very much for diving into this topic.

We want to migrate to git, but this issue is still of great concern for us. Unfortunately, there are tools involved, where we have no influence on the naming of files...and than comes git with its Metadata files :) (especially if LFS is involved).

Is there any chance that this issue can be resolved (with our help)? Maybe something changed already in the past years for MSYS2?

@dscho
Copy link
Member

dscho commented Jun 5, 2024

@skdsp could you provide a concrete example repository you need to work?

@skdsp
Copy link

skdsp commented Jun 5, 2024

@dscho Unfortunately, the concrete repositories are on our own servers. But the first example in the linked issue still shows the behavior:

git clone -c core.longpaths=true https://github.com/git/git.git --recurse-submodules //?/D:/eval/Git_Test/looooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong

@dscho
Copy link
Member

dscho commented Jun 5, 2024

@skdsp And there is no chance to get off of submodules, right? I frequently quip that: Friends don't let friends use submodules. There's more than a grain of truth behind that jab, though: Submodules pose many, many problems, and this here issue is just one of them.

And no, nothing has changed in the MSYS2 runtime because it needs to be backwards-compatible to Windows 8.1, still (Windows 7 and 8 support was dropped recently, with minor efforts to keep those setups working in most scenarios, in some form or other).

The best bet you'd have would be to convert the last remaining bits and pieces of git-submodule.sh to C. But even that might not be enough because:

  • Git uses many multi-process strategies where native Win32 applications would prefer multi-threading. This makes Git susceptible to this important limitation:

    ⓘ Important

    Setting a current directory longer than MAX_PATH causes CreateProcessW() to fail.

  • Submodules are still regular repositories. Therefore, if any hook, alias or script is called either in a specific submodule or recursively in the super-project, you run into the very same bash.exe problem we've run into earlier in this here ticket.

tl;dr there might not be any quick fix in sight.

@skdsp
Copy link

skdsp commented Jun 6, 2024

We are used to work with an integrated configuration management in our current VCS. Thus it would be convenient for us to work with submodules, especially since there is good tool support (e.g. using Git Extensions).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants