Skip to content

Commit f77a0e6

Browse files
committed
Merge 'long-paths' into HEAD
This works around path length limitations by using the \\?\* trick. Signed-off-by: Johannes Schindelin <[email protected]>
2 parents f786037 + c0874a4 commit f77a0e6

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
@@ -712,6 +712,13 @@ core.fscache::
712712
Git for Windows uses this to bulk-read and cache lstat data of entire
713713
directories (instead of doing lstat file by file).
714714

715+
core.longpaths::
716+
Enable long path (> 260) support for builtin commands in Git for
717+
Windows. This is disabled by default, as long paths are not supported
718+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
719+
(msys, bash, tcl, perl...). Only enable this if you know what you're
720+
doing and are prepared to live with a few quirks.
721+
715722
core.createObject::
716723
You can set this to 'link', in which case a hardlink followed by
717724
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
@@ -637,6 +637,8 @@ extern enum hide_dotfiles_type hide_dotfiles;
637637

638638
extern int core_fscache;
639639

640+
extern int core_long_paths;
641+
640642
enum branch_track {
641643
BRANCH_TRACK_UNSPECIFIED = -1,
642644
BRANCH_TRACK_NEVER = 0,

compat/mingw.c

Lines changed: 109 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,8 @@ static int ask_yes_no_if_possible(const char *format, ...)
208208
int mingw_unlink(const char *pathname)
209209
{
210210
int ret, tries = 0;
211-
wchar_t wpathname[MAX_PATH];
212-
if (xutftowcs_path(wpathname, pathname) < 0)
211+
wchar_t wpathname[MAX_LONG_PATH];
212+
if (xutftowcs_long_path(wpathname, pathname) < 0)
213213
return -1;
214214

215215
/* read-only files cannot be removed */
@@ -238,7 +238,7 @@ static int is_dir_empty(const wchar_t *wpath)
238238
{
239239
WIN32_FIND_DATAW findbuf;
240240
HANDLE handle;
241-
wchar_t wbuf[MAX_PATH + 2];
241+
wchar_t wbuf[MAX_LONG_PATH + 2];
242242
wcscpy(wbuf, wpath);
243243
wcscat(wbuf, L"\\*");
244244
handle = FindFirstFileW(wbuf, &findbuf);
@@ -259,8 +259,8 @@ static int is_dir_empty(const wchar_t *wpath)
259259
int mingw_rmdir(const char *pathname)
260260
{
261261
int ret, tries = 0;
262-
wchar_t wpathname[MAX_PATH];
263-
if (xutftowcs_path(wpathname, pathname) < 0)
262+
wchar_t wpathname[MAX_LONG_PATH];
263+
if (xutftowcs_long_path(wpathname, pathname) < 0)
264264
return -1;
265265

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

301301
void mingw_mark_as_git_dir(const char *dir)
302302
{
303-
wchar_t wdir[MAX_PATH];
303+
wchar_t wdir[MAX_LONG_PATH];
304304
if (hide_dotfiles != HIDE_DOTFILES_FALSE && !is_bare_repository())
305-
if (xutftowcs_path(wdir, dir) < 0 || make_hidden(wdir))
305+
if (xutftowcs_long_path(wdir, dir) < 0 || make_hidden(wdir))
306306
warning("Failed to make '%s' hidden", dir);
307307
git_config_set("core.hideDotFiles",
308308
hide_dotfiles == HIDE_DOTFILES_FALSE ? "false" :
@@ -313,9 +313,12 @@ void mingw_mark_as_git_dir(const char *dir)
313313
int mingw_mkdir(const char *path, int mode)
314314
{
315315
int ret;
316-
wchar_t wpath[MAX_PATH];
317-
if (xutftowcs_path(wpath, path) < 0)
316+
wchar_t wpath[MAX_LONG_PATH];
317+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
318+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
319+
core_long_paths) < 0)
318320
return -1;
321+
319322
ret = _wmkdir(wpath);
320323
if (!ret && hide_dotfiles == HIDE_DOTFILES_TRUE) {
321324
/*
@@ -335,7 +338,7 @@ int mingw_open (const char *filename, int oflags, ...)
335338
va_list args;
336339
unsigned mode;
337340
int fd;
338-
wchar_t wfilename[MAX_PATH];
341+
wchar_t wfilename[MAX_LONG_PATH];
339342

340343
va_start(args, oflags);
341344
mode = va_arg(args, int);
@@ -344,7 +347,7 @@ int mingw_open (const char *filename, int oflags, ...)
344347
if (filename && !strcmp(filename, "/dev/null"))
345348
filename = "nul";
346349

347-
if (xutftowcs_path(wfilename, filename) < 0)
350+
if (xutftowcs_long_path(wfilename, filename) < 0)
348351
return -1;
349352
fd = _wopen(wfilename, oflags, mode);
350353

@@ -397,13 +400,13 @@ FILE *mingw_fopen (const char *filename, const char *otype)
397400
{
398401
int hide = 0;
399402
FILE *file;
400-
wchar_t wfilename[MAX_PATH], wotype[4];
403+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
401404
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
402405
basename((char*)filename)[0] == '.')
403406
hide = access(filename, F_OK);
404407
if (filename && !strcmp(filename, "/dev/null"))
405408
filename = "nul";
406-
if (xutftowcs_path(wfilename, filename) < 0 ||
409+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
407410
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
408411
return NULL;
409412
file = _wfopen(wfilename, wotype);
@@ -416,13 +419,13 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
416419
{
417420
int hide = 0;
418421
FILE *file;
419-
wchar_t wfilename[MAX_PATH], wotype[4];
422+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
420423
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
421424
basename((char*)filename)[0] == '.')
422425
hide = access(filename, F_OK);
423426
if (filename && !strcmp(filename, "/dev/null"))
424427
filename = "nul";
425-
if (xutftowcs_path(wfilename, filename) < 0 ||
428+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
426429
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
427430
return NULL;
428431
file = _wfreopen(wfilename, wotype, stream);
@@ -455,25 +458,32 @@ int mingw_fflush(FILE *stream)
455458

456459
int mingw_access(const char *filename, int mode)
457460
{
458-
wchar_t wfilename[MAX_PATH];
459-
if (xutftowcs_path(wfilename, filename) < 0)
461+
wchar_t wfilename[MAX_LONG_PATH];
462+
if (xutftowcs_long_path(wfilename, filename) < 0)
460463
return -1;
461464
/* X_OK is not supported by the MSVCRT version */
462465
return _waccess(wfilename, mode & ~X_OK);
463466
}
464467

468+
/* cached length of current directory for handle_long_path */
469+
static int current_directory_len = 0;
470+
465471
int mingw_chdir(const char *dirname)
466472
{
473+
int result;
467474
wchar_t wdirname[MAX_PATH];
475+
/* SetCurrentDirectoryW doesn't support long paths */
468476
if (xutftowcs_path(wdirname, dirname) < 0)
469477
return -1;
470-
return _wchdir(wdirname);
478+
result = _wchdir(wdirname);
479+
current_directory_len = GetCurrentDirectoryW(0, NULL);
480+
return result;
471481
}
472482

473483
int mingw_chmod(const char *filename, int mode)
474484
{
475-
wchar_t wfilename[MAX_PATH];
476-
if (xutftowcs_path(wfilename, filename) < 0)
485+
wchar_t wfilename[MAX_LONG_PATH];
486+
if (xutftowcs_long_path(wfilename, filename) < 0)
477487
return -1;
478488
return _wchmod(wfilename, mode);
479489
}
@@ -488,8 +498,8 @@ int mingw_chmod(const char *filename, int mode)
488498
static int do_lstat(int follow, const char *file_name, struct stat *buf)
489499
{
490500
WIN32_FILE_ATTRIBUTE_DATA fdata;
491-
wchar_t wfilename[MAX_PATH];
492-
if (xutftowcs_path(wfilename, file_name) < 0)
501+
wchar_t wfilename[MAX_LONG_PATH];
502+
if (xutftowcs_long_path(wfilename, file_name) < 0)
493503
return -1;
494504

495505
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -554,7 +564,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
554564
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
555565
{
556566
int namelen;
557-
char alt_name[PATH_MAX];
567+
char alt_name[MAX_LONG_PATH];
558568

559569
if (!do_lstat(follow, file_name, buf))
560570
return 0;
@@ -570,7 +580,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
570580
return -1;
571581
while (namelen && file_name[namelen-1] == '/')
572582
--namelen;
573-
if (!namelen || namelen >= PATH_MAX)
583+
if (!namelen || namelen >= MAX_LONG_PATH)
574584
return -1;
575585

576586
memcpy(alt_name, file_name, namelen);
@@ -632,8 +642,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
632642
FILETIME mft, aft;
633643
int fh, rc;
634644
DWORD attrs;
635-
wchar_t wfilename[MAX_PATH];
636-
if (xutftowcs_path(wfilename, file_name) < 0)
645+
wchar_t wfilename[MAX_LONG_PATH];
646+
if (xutftowcs_long_path(wfilename, file_name) < 0)
637647
return -1;
638648

639649
/* must have write permission */
@@ -681,6 +691,7 @@ unsigned int sleep (unsigned int seconds)
681691
char *mingw_mktemp(char *template)
682692
{
683693
wchar_t wtemplate[MAX_PATH];
694+
/* we need to return the path, thus no long paths here! */
684695
if (xutftowcs_path(wtemplate, template) < 0)
685696
return NULL;
686697
if (!_wmktemp(wtemplate))
@@ -1048,6 +1059,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
10481059
si.hStdOutput = winansi_get_osfhandle(fhout);
10491060
si.hStdError = winansi_get_osfhandle(fherr);
10501061

1062+
/* executables and the current directory don't support long paths */
10511063
if (xutftowcs_path(wcmd, cmd) < 0)
10521064
return -1;
10531065
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1677,8 +1689,9 @@ int mingw_rename(const char *pold, const char *pnew)
16771689
{
16781690
DWORD attrs, gle;
16791691
int tries = 0;
1680-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1681-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1692+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1693+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1694+
xutftowcs_long_path(wpnew, pnew) < 0)
16821695
return -1;
16831696

16841697
/*
@@ -1960,9 +1973,9 @@ int link(const char *oldpath, const char *newpath)
19601973
{
19611974
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19621975
static T create_hard_link = NULL;
1963-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1964-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1965-
xutftowcs_path(wnewpath, newpath) < 0)
1976+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1977+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1978+
xutftowcs_long_path(wnewpath, newpath) < 0)
19661979
return -1;
19671980

19681981
if (!create_hard_link) {
@@ -2143,6 +2156,68 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
21432156
return -1;
21442157
}
21452158

2159+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2160+
{
2161+
int result;
2162+
wchar_t buf[MAX_LONG_PATH];
2163+
2164+
/*
2165+
* we don't need special handling if path is relative to the current
2166+
* directory, and current directory + path don't exceed the desired
2167+
* max_path limit. This should cover > 99 % of cases with minimal
2168+
* performance impact (git almost always uses relative paths).
2169+
*/
2170+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2171+
(current_directory_len + len < max_path))
2172+
return len;
2173+
2174+
/*
2175+
* handle everything else:
2176+
* - absolute paths: "C:\dir\file"
2177+
* - absolute UNC paths: "\\server\share\dir\file"
2178+
* - absolute paths on current drive: "\dir\file"
2179+
* - relative paths on other drive: "X:file"
2180+
* - prefixed paths: "\\?\...", "\\.\..."
2181+
*/
2182+
2183+
/* convert to absolute path using GetFullPathNameW */
2184+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2185+
if (!result) {
2186+
errno = err_win_to_posix(GetLastError());
2187+
return -1;
2188+
}
2189+
2190+
/*
2191+
* return absolute path if it fits within max_path (even if
2192+
* "cwd + path" doesn't due to '..' components)
2193+
*/
2194+
if (result < max_path) {
2195+
wcscpy(path, buf);
2196+
return result;
2197+
}
2198+
2199+
/* error out if we shouldn't expand the path or buf is too small */
2200+
if (!expand || result >= MAX_LONG_PATH - 6) {
2201+
errno = ENAMETOOLONG;
2202+
return -1;
2203+
}
2204+
2205+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2206+
if (buf[0] == '\\') {
2207+
/* ...unless already prefixed */
2208+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2209+
return len;
2210+
2211+
wcscpy(path, L"\\\\?\\UNC\\");
2212+
wcscpy(path + 8, buf + 2);
2213+
return result + 6;
2214+
} else {
2215+
wcscpy(path, L"\\\\?\\");
2216+
wcscpy(path + 4, buf);
2217+
return result + 4;
2218+
}
2219+
}
2220+
21462221
/*
21472222
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
21482223
* mingw startup code, see init.c in mingw runtime).
@@ -2264,6 +2339,9 @@ void mingw_startup()
22642339

22652340
/* initialize Unicode console */
22662341
winansi_init();
2342+
2343+
/* init length of current directory for handle_long_path */
2344+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22672345
}
22682346

22692347
int mingw_isatty(int fd) {

0 commit comments

Comments
 (0)