From b1e9fbfa2affbec086c5ef44fc3538b0115fdc04 Mon Sep 17 00:00:00 2001 From: vgm Date: Thu, 22 Oct 2026 23:23:54 +0800 Subject: [PATCH] mingw: use the full buffer when reading reparse points read_reparse_point() limits its UTF-16 to UTF-8 conversion to MAX_PATH bytes, although both callers provide a MAX_LONG_PATH-sized buffer. Since the conversion includes the terminating NUL, symlink targets of 260 or more UTF-8 bytes fail with ERANGE. This affects both readlink() and mingw_lstat(), even for short link paths and with core.longpaths enabled. Use MAX_LONG_PATH for the conversion, matching the buffers allocated by the callers. Add Windows regression coverage for ASCII and multibyte targets of 259, 260 and 261 bytes. Check the decoded target as well as the diff output and stderr, since git diff can report the conversion error without failing its exit status. Disable the filesystem cache so the tests also exercise mingw_lstat(). Fixes: https://github.com/git-for-windows/git/issues/6498 Co-authored-by: GPT-6 Signed-off-by: vgm --- compat/mingw.c | 2 +- t/t2031-checkout-long-paths.sh | 42 ++++++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/compat/mingw.c b/compat/mingw.c index 9c5a5140790161..b09eabd12d8cae 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -1341,7 +1341,7 @@ static int read_reparse_point(const WCHAR *wpath, BOOL fail_on_unknown_tag, } if ((*plen = - xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_PATH)) < 0) + xwcstoutf(tmpbuf, normalize_ntpath(wbuf), MAX_LONG_PATH)) < 0) return -1; return 0; } diff --git a/t/t2031-checkout-long-paths.sh b/t/t2031-checkout-long-paths.sh index 4278a602cbd09e..390497c0f8a859 100755 --- a/t/t2031-checkout-long-paths.sh +++ b/t/t2031-checkout-long-paths.sh @@ -108,4 +108,46 @@ test_expect_success SYMLINKS_WINDOWS 'leave drive-less, short paths intact' ' test_grep " *PF *\\[\\\\Program Files\\]" actual ' +test_expect_success SYMLINKS_WINDOWS 'set up long symlink targets' ' + ascii_target=$(printf "%0128d" 0) && + ascii_target=$ascii_target/$ascii_target && + utf8_char=$(printf "\344\270\255") && + utf8_target=$(test_seq 1 85 | sed "s/.*/$utf8_char/" | tr -d "\n") && + utf8_target=$utf8_target/a +' + +for encoding in ascii utf8 +do + for suffix in aa aaa aaaa + do + # Both prefixes are 257 UTF-8 bytes: test 259, 260 and 261. + bytes=$((257 + ${#suffix})) + test_expect_success SYMLINKS_WINDOWS \ + "read $encoding symlink target of $bytes bytes" ' + test_config core.symlinks true && + test_config core.longpaths false && + test_config core.fscache false && + test_when_finished "rm -f long-link" && + test_when_finished \ + "git update-index --force-remove long-link" && + case $encoding in + ascii) target=$ascii_target$suffix ;; + utf8) target=$utf8_target$suffix ;; + esac && + oid=$(printf "%s" "$target" | + git hash-object -w --stdin) && + git update-index --add \ + --cacheinfo 120000,$oid,long-link && + win_target=$(printf "%s" "$target" | tr / "\\\\") && + cmd //c "mklink long-link $win_target" && + printf "%s\n" "$target" >expect && + test-tool path-utils readlink long-link >actual && + test_cmp expect actual && + git diff --exit-code -- long-link >actual 2>err && + test_must_be_empty actual && + test_must_be_empty err + ' + done +done + test_done