Skip to content

Commit d818c04

Browse files
committed
Merge pull request #122 from kblees/kb/long-paths-v2
Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 931ec5d + 71463a0 commit d818c04

File tree

10 files changed

+418
-49
lines changed

10 files changed

+418
-49
lines changed

Documentation/config.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -667,6 +667,13 @@ core.fscache::
667667
Git for Windows uses this to bulk-read and cache lstat data of entire
668668
directories (instead of doing lstat file by file).
669669

670+
core.longpaths::
671+
Enable long path (> 260) support for builtin commands in Git for
672+
Windows. This is disabled by default, as long paths are not supported
673+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
674+
(msys, bash, tcl, perl...). Only enable this if you know what you're
675+
doing and are prepared to live with a few quirks.
676+
670677
core.createObject::
671678
You can set this to 'link', in which case a hardlink followed by
672679
a delete of the source are used to make sure that object creation

cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,8 @@ extern enum hide_dotfiles_type hide_dotfiles;
629629

630630
extern int core_fscache;
631631

632+
extern int core_long_paths;
633+
632634
enum branch_track {
633635
BRANCH_TRACK_UNSPECIFIED = -1,
634636
BRANCH_TRACK_NEVER = 0,

compat/mingw.c

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -206,8 +206,8 @@ static int ask_yes_no_if_possible(const char *format, ...)
206206
int mingw_unlink(const char *pathname)
207207
{
208208
int ret, tries = 0;
209-
wchar_t wpathname[MAX_PATH];
210-
if (xutftowcs_path(wpathname, pathname) < 0)
209+
wchar_t wpathname[MAX_LONG_PATH];
210+
if (xutftowcs_long_path(wpathname, pathname) < 0)
211211
return -1;
212212

213213
/* read-only files cannot be removed */
@@ -236,7 +236,7 @@ static int is_dir_empty(const wchar_t *wpath)
236236
{
237237
WIN32_FIND_DATAW findbuf;
238238
HANDLE handle;
239-
wchar_t wbuf[MAX_PATH + 2];
239+
wchar_t wbuf[MAX_LONG_PATH + 2];
240240
wcscpy(wbuf, wpath);
241241
wcscat(wbuf, L"\\*");
242242
handle = FindFirstFileW(wbuf, &findbuf);
@@ -257,8 +257,8 @@ static int is_dir_empty(const wchar_t *wpath)
257257
int mingw_rmdir(const char *pathname)
258258
{
259259
int ret, tries = 0;
260-
wchar_t wpathname[MAX_PATH];
261-
if (xutftowcs_path(wpathname, pathname) < 0)
260+
wchar_t wpathname[MAX_LONG_PATH];
261+
if (xutftowcs_long_path(wpathname, pathname) < 0)
262262
return -1;
263263

264264
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -298,9 +298,9 @@ static int make_hidden(const wchar_t *path)
298298

299299
void mingw_mark_as_git_dir(const char *dir)
300300
{
301-
wchar_t wdir[MAX_PATH];
301+
wchar_t wdir[MAX_LONG_PATH];
302302
if (hide_dotfiles != HIDE_DOTFILES_FALSE && !is_bare_repository())
303-
if (xutftowcs_path(wdir, dir) < 0 || make_hidden(wdir))
303+
if (xutftowcs_long_path(wdir, dir) < 0 || make_hidden(wdir))
304304
warning("Failed to make '%s' hidden", dir);
305305
git_config_set("core.hideDotFiles",
306306
hide_dotfiles == HIDE_DOTFILES_FALSE ? "false" :
@@ -311,9 +311,12 @@ void mingw_mark_as_git_dir(const char *dir)
311311
int mingw_mkdir(const char *path, int mode)
312312
{
313313
int ret;
314-
wchar_t wpath[MAX_PATH];
315-
if (xutftowcs_path(wpath, path) < 0)
314+
wchar_t wpath[MAX_LONG_PATH];
315+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
316+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
317+
core_long_paths) < 0)
316318
return -1;
319+
317320
ret = _wmkdir(wpath);
318321
if (!ret && hide_dotfiles == HIDE_DOTFILES_TRUE) {
319322
/*
@@ -333,7 +336,7 @@ int mingw_open (const char *filename, int oflags, ...)
333336
va_list args;
334337
unsigned mode;
335338
int fd;
336-
wchar_t wfilename[MAX_PATH];
339+
wchar_t wfilename[MAX_LONG_PATH];
337340

338341
va_start(args, oflags);
339342
mode = va_arg(args, int);
@@ -342,7 +345,7 @@ int mingw_open (const char *filename, int oflags, ...)
342345
if (filename && !strcmp(filename, "/dev/null"))
343346
filename = "nul";
344347

345-
if (xutftowcs_path(wfilename, filename) < 0)
348+
if (xutftowcs_long_path(wfilename, filename) < 0)
346349
return -1;
347350
fd = _wopen(wfilename, oflags, mode);
348351

@@ -395,13 +398,13 @@ FILE *mingw_fopen (const char *filename, const char *otype)
395398
{
396399
int hide = 0;
397400
FILE *file;
398-
wchar_t wfilename[MAX_PATH], wotype[4];
401+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
399402
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
400403
basename((char*)filename)[0] == '.')
401404
hide = access(filename, F_OK);
402405
if (filename && !strcmp(filename, "/dev/null"))
403406
filename = "nul";
404-
if (xutftowcs_path(wfilename, filename) < 0 ||
407+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
405408
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
406409
return NULL;
407410
file = _wfopen(wfilename, wotype);
@@ -414,13 +417,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
414417
{
415418
int hide = 0;
416419
FILE *file;
417-
wchar_t wfilename[MAX_PATH], wotype[4];
420+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
418421
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
419422
basename((char*)filename)[0] == '.')
420423
hide = access(filename, F_OK);
421424
if (filename && !strcmp(filename, "/dev/null"))
422425
filename = "nul";
423-
if (xutftowcs_path(wfilename, filename) < 0 ||
426+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
424427
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
425428
return NULL;
426429
file = _wfreopen(wfilename, wotype, stream);
@@ -453,25 +456,32 @@ int mingw_fflush(FILE *stream)
453456

454457
int mingw_access(const char *filename, int mode)
455458
{
456-
wchar_t wfilename[MAX_PATH];
457-
if (xutftowcs_path(wfilename, filename) < 0)
459+
wchar_t wfilename[MAX_LONG_PATH];
460+
if (xutftowcs_long_path(wfilename, filename) < 0)
458461
return -1;
459462
/* X_OK is not supported by the MSVCRT version */
460463
return _waccess(wfilename, mode & ~X_OK);
461464
}
462465

466+
/* cached length of current directory for handle_long_path */
467+
static int current_directory_len = 0;
468+
463469
int mingw_chdir(const char *dirname)
464470
{
471+
int result;
465472
wchar_t wdirname[MAX_PATH];
473+
/* SetCurrentDirectoryW doesn't support long paths */
466474
if (xutftowcs_path(wdirname, dirname) < 0)
467475
return -1;
468-
return _wchdir(wdirname);
476+
result = _wchdir(wdirname);
477+
current_directory_len = GetCurrentDirectoryW(0, NULL);
478+
return result;
469479
}
470480

471481
int mingw_chmod(const char *filename, int mode)
472482
{
473-
wchar_t wfilename[MAX_PATH];
474-
if (xutftowcs_path(wfilename, filename) < 0)
483+
wchar_t wfilename[MAX_LONG_PATH];
484+
if (xutftowcs_long_path(wfilename, filename) < 0)
475485
return -1;
476486
return _wchmod(wfilename, mode);
477487
}
@@ -486,8 +496,8 @@ int mingw_chmod(const char *filename, int mode)
486496
static int do_lstat(int follow, const char *file_name, struct stat *buf)
487497
{
488498
WIN32_FILE_ATTRIBUTE_DATA fdata;
489-
wchar_t wfilename[MAX_PATH];
490-
if (xutftowcs_path(wfilename, file_name) < 0)
499+
wchar_t wfilename[MAX_LONG_PATH];
500+
if (xutftowcs_long_path(wfilename, file_name) < 0)
491501
return -1;
492502

493503
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -552,7 +562,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
552562
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
553563
{
554564
int namelen;
555-
char alt_name[PATH_MAX];
565+
char alt_name[MAX_LONG_PATH];
556566

557567
if (!do_lstat(follow, file_name, buf))
558568
return 0;
@@ -568,7 +578,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
568578
return -1;
569579
while (namelen && file_name[namelen-1] == '/')
570580
--namelen;
571-
if (!namelen || namelen >= PATH_MAX)
581+
if (!namelen || namelen >= MAX_LONG_PATH)
572582
return -1;
573583

574584
memcpy(alt_name, file_name, namelen);
@@ -630,8 +640,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
630640
FILETIME mft, aft;
631641
int fh, rc;
632642
DWORD attrs;
633-
wchar_t wfilename[MAX_PATH];
634-
if (xutftowcs_path(wfilename, file_name) < 0)
643+
wchar_t wfilename[MAX_LONG_PATH];
644+
if (xutftowcs_long_path(wfilename, file_name) < 0)
635645
return -1;
636646

637647
/* must have write permission */
@@ -679,6 +689,7 @@ unsigned int sleep (unsigned int seconds)
679689
char *mingw_mktemp(char *template)
680690
{
681691
wchar_t wtemplate[MAX_PATH];
692+
/* we need to return the path, thus no long paths here! */
682693
if (xutftowcs_path(wtemplate, template) < 0)
683694
return NULL;
684695
if (!_wmktemp(wtemplate))
@@ -1039,6 +1050,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
10391050
si.hStdOutput = winansi_get_osfhandle(fhout);
10401051
si.hStdError = winansi_get_osfhandle(fherr);
10411052

1053+
/* executables and the current directory don't support long paths */
10421054
if (xutftowcs_path(wcmd, cmd) < 0)
10431055
return -1;
10441056
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1624,8 +1636,9 @@ int mingw_rename(const char *pold, const char *pnew)
16241636
{
16251637
DWORD attrs, gle;
16261638
int tries = 0;
1627-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1628-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1639+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1640+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1641+
xutftowcs_long_path(wpnew, pnew) < 0)
16291642
return -1;
16301643

16311644
/*
@@ -1906,9 +1919,9 @@ int link(const char *oldpath, const char *newpath)
19061919
{
19071920
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19081921
static T create_hard_link = NULL;
1909-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1910-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1911-
xutftowcs_path(wnewpath, newpath) < 0)
1922+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1923+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1924+
xutftowcs_long_path(wnewpath, newpath) < 0)
19121925
return -1;
19131926

19141927
if (!create_hard_link) {
@@ -2089,6 +2102,68 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
20892102
return -1;
20902103
}
20912104

2105+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2106+
{
2107+
int result;
2108+
wchar_t buf[MAX_LONG_PATH];
2109+
2110+
/*
2111+
* we don't need special handling if path is relative to the current
2112+
* directory, and current directory + path don't exceed the desired
2113+
* max_path limit. This should cover > 99 % of cases with minimal
2114+
* performance impact (git almost always uses relative paths).
2115+
*/
2116+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2117+
(current_directory_len + len < max_path))
2118+
return len;
2119+
2120+
/*
2121+
* handle everything else:
2122+
* - absolute paths: "C:\dir\file"
2123+
* - absolute UNC paths: "\\server\share\dir\file"
2124+
* - absolute paths on current drive: "\dir\file"
2125+
* - relative paths on other drive: "X:file"
2126+
* - prefixed paths: "\\?\...", "\\.\..."
2127+
*/
2128+
2129+
/* convert to absolute path using GetFullPathNameW */
2130+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2131+
if (!result) {
2132+
errno = err_win_to_posix(GetLastError());
2133+
return -1;
2134+
}
2135+
2136+
/*
2137+
* return absolute path if it fits within max_path (even if
2138+
* "cwd + path" doesn't due to '..' components)
2139+
*/
2140+
if (result < max_path) {
2141+
wcscpy(path, buf);
2142+
return result;
2143+
}
2144+
2145+
/* error out if we shouldn't expand the path or buf is too small */
2146+
if (!expand || result >= MAX_LONG_PATH - 6) {
2147+
errno = ENAMETOOLONG;
2148+
return -1;
2149+
}
2150+
2151+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2152+
if (buf[0] == '\\') {
2153+
/* ...unless already prefixed */
2154+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2155+
return len;
2156+
2157+
wcscpy(path, L"\\\\?\\UNC\\");
2158+
wcscpy(path + 8, buf + 2);
2159+
return result + 6;
2160+
} else {
2161+
wcscpy(path, L"\\\\?\\");
2162+
wcscpy(path + 4, buf);
2163+
return result + 4;
2164+
}
2165+
}
2166+
20922167
/*
20932168
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
20942169
* mingw startup code, see init.c in mingw runtime).
@@ -2210,6 +2285,9 @@ void mingw_startup()
22102285

22112286
/* initialize Unicode console */
22122287
winansi_init();
2288+
2289+
/* init length of current directory for handle_long_path */
2290+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22132291
}
22142292

22152293
int mingw_isatty(int fd) {

0 commit comments

Comments
 (0)