Skip to content

Commit 1ae7d91

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 fb80522 + 93b9b6f commit 1ae7d91

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
@@ -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))
@@ -1046,6 +1057,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
10461057
si.hStdOutput = winansi_get_osfhandle(fhout);
10471058
si.hStdError = winansi_get_osfhandle(fherr);
10481059

1060+
/* executables and the current directory don't support long paths */
10491061
if (xutftowcs_path(wcmd, cmd) < 0)
10501062
return -1;
10511063
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1675,8 +1687,9 @@ int mingw_rename(const char *pold, const char *pnew)
16751687
{
16761688
DWORD attrs, gle;
16771689
int tries = 0;
1678-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1679-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1690+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1691+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1692+
xutftowcs_long_path(wpnew, pnew) < 0)
16801693
return -1;
16811694

16821695
/*
@@ -1958,9 +1971,9 @@ int link(const char *oldpath, const char *newpath)
19581971
{
19591972
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19601973
static T create_hard_link = NULL;
1961-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1962-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1963-
xutftowcs_path(wnewpath, newpath) < 0)
1974+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1975+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1976+
xutftowcs_long_path(wnewpath, newpath) < 0)
19641977
return -1;
19651978

19661979
if (!create_hard_link) {
@@ -2141,6 +2154,68 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
21412154
return -1;
21422155
}
21432156

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

23002375
/* initialize Unicode console */
23012376
winansi_init();
2377+
2378+
/* init length of current directory for handle_long_path */
2379+
current_directory_len = GetCurrentDirectoryW(0, NULL);
23022380
}

0 commit comments

Comments
 (0)