Skip to content

Commit bb7c644

Browse files
committed
Merge branch 'long-paths'
2 parents 0a16804 + 21af3a5 commit bb7c644

File tree

7 files changed

+419
-49
lines changed

7 files changed

+419
-49
lines changed

Documentation/config/core.txt

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

560+
core.longpaths::
561+
Enable long path (> 260) support for builtin commands in Git for
562+
Windows. This is disabled by default, as long paths are not supported
563+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
564+
(msys, bash, tcl, perl...). Only enable this if you know what you're
565+
doing and are prepared to live with a few quirks.
566+
560567
core.unsetenvvars::
561568
Windows-only: comma-separated list of environment variables'
562569
names that need to be unset before spawning any other process.

compat/mingw.c

Lines changed: 117 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ enum hide_dotfiles_type {
228228
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
229229
static char *unset_environment_variables;
230230
int core_fscache;
231+
int core_long_paths;
231232

232233
int mingw_core_config(const char *var, const char *value, void *cb)
233234
{
@@ -244,6 +245,11 @@ int mingw_core_config(const char *var, const char *value, void *cb)
244245
return 0;
245246
}
246247

248+
if (!strcmp(var, "core.longpaths")) {
249+
core_long_paths = git_config_bool(var, value);
250+
return 0;
251+
}
252+
247253
if (!strcmp(var, "core.unsetenvvars")) {
248254
free(unset_environment_variables);
249255
unset_environment_variables = xstrdup(value);
@@ -281,8 +287,8 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
281287
int mingw_unlink(const char *pathname)
282288
{
283289
int ret, tries = 0;
284-
wchar_t wpathname[MAX_PATH];
285-
if (xutftowcs_path(wpathname, pathname) < 0)
290+
wchar_t wpathname[MAX_LONG_PATH];
291+
if (xutftowcs_long_path(wpathname, pathname) < 0)
286292
return -1;
287293

288294
/* read-only files cannot be removed */
@@ -311,7 +317,7 @@ static int is_dir_empty(const wchar_t *wpath)
311317
{
312318
WIN32_FIND_DATAW findbuf;
313319
HANDLE handle;
314-
wchar_t wbuf[MAX_PATH + 2];
320+
wchar_t wbuf[MAX_LONG_PATH + 2];
315321
wcscpy(wbuf, wpath);
316322
wcscat(wbuf, L"\\*");
317323
handle = FindFirstFileW(wbuf, &findbuf);
@@ -332,8 +338,8 @@ static int is_dir_empty(const wchar_t *wpath)
332338
int mingw_rmdir(const char *pathname)
333339
{
334340
int ret, tries = 0;
335-
wchar_t wpathname[MAX_PATH];
336-
if (xutftowcs_path(wpathname, pathname) < 0)
341+
wchar_t wpathname[MAX_LONG_PATH];
342+
if (xutftowcs_long_path(wpathname, pathname) < 0)
337343
return -1;
338344

339345
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -408,9 +414,12 @@ static int set_hidden_flag(const wchar_t *path, int set)
408414
int mingw_mkdir(const char *path, int mode)
409415
{
410416
int ret;
411-
wchar_t wpath[MAX_PATH];
412-
if (xutftowcs_path(wpath, path) < 0)
417+
wchar_t wpath[MAX_LONG_PATH];
418+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
419+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
420+
core_long_paths) < 0)
413421
return -1;
422+
414423
ret = _wmkdir(wpath);
415424
if (!ret && needs_hiding(path))
416425
return set_hidden_flag(wpath, 1);
@@ -483,7 +492,7 @@ int mingw_open (const char *filename, int oflags, ...)
483492
va_list args;
484493
unsigned mode;
485494
int fd;
486-
wchar_t wfilename[MAX_PATH];
495+
wchar_t wfilename[MAX_LONG_PATH];
487496
open_fn_t open_fn;
488497

489498
va_start(args, oflags);
@@ -498,7 +507,7 @@ int mingw_open (const char *filename, int oflags, ...)
498507
else
499508
open_fn = _wopen;
500509

501-
if (xutftowcs_path(wfilename, filename) < 0)
510+
if (xutftowcs_long_path(wfilename, filename) < 0)
502511
return -1;
503512
fd = open_fn(wfilename, oflags, mode);
504513

@@ -555,10 +564,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
555564
{
556565
int hide = needs_hiding(filename);
557566
FILE *file;
558-
wchar_t wfilename[MAX_PATH], wotype[4];
567+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
559568
if (filename && !strcmp(filename, "/dev/null"))
560569
filename = "nul";
561-
if (xutftowcs_path(wfilename, filename) < 0 ||
570+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
562571
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
563572
return NULL;
564573
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -577,10 +586,10 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
577586
{
578587
int hide = needs_hiding(filename);
579588
FILE *file;
580-
wchar_t wfilename[MAX_PATH], wotype[4];
589+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
581590
if (filename && !strcmp(filename, "/dev/null"))
582591
filename = "nul";
583-
if (xutftowcs_path(wfilename, filename) < 0 ||
592+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
584593
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
585594
return NULL;
586595
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -634,25 +643,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
634643

635644
int mingw_access(const char *filename, int mode)
636645
{
637-
wchar_t wfilename[MAX_PATH];
638-
if (xutftowcs_path(wfilename, filename) < 0)
646+
wchar_t wfilename[MAX_LONG_PATH];
647+
if (xutftowcs_long_path(wfilename, filename) < 0)
639648
return -1;
640649
/* X_OK is not supported by the MSVCRT version */
641650
return _waccess(wfilename, mode & ~X_OK);
642651
}
643652

653+
/* cached length of current directory for handle_long_path */
654+
static int current_directory_len = 0;
655+
644656
int mingw_chdir(const char *dirname)
645657
{
646-
wchar_t wdirname[MAX_PATH];
647-
if (xutftowcs_path(wdirname, dirname) < 0)
658+
int result;
659+
wchar_t wdirname[MAX_LONG_PATH];
660+
if (xutftowcs_long_path(wdirname, dirname) < 0)
648661
return -1;
649-
return _wchdir(wdirname);
662+
result = _wchdir(wdirname);
663+
current_directory_len = GetCurrentDirectoryW(0, NULL);
664+
return result;
650665
}
651666

652667
int mingw_chmod(const char *filename, int mode)
653668
{
654-
wchar_t wfilename[MAX_PATH];
655-
if (xutftowcs_path(wfilename, filename) < 0)
669+
wchar_t wfilename[MAX_LONG_PATH];
670+
if (xutftowcs_long_path(wfilename, filename) < 0)
656671
return -1;
657672
return _wchmod(wfilename, mode);
658673
}
@@ -700,8 +715,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
700715
static int do_lstat(int follow, const char *file_name, struct stat *buf)
701716
{
702717
WIN32_FILE_ATTRIBUTE_DATA fdata;
703-
wchar_t wfilename[MAX_PATH];
704-
if (xutftowcs_path(wfilename, file_name) < 0)
718+
wchar_t wfilename[MAX_LONG_PATH];
719+
if (xutftowcs_long_path(wfilename, file_name) < 0)
705720
return -1;
706721

707722
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -772,7 +787,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
772787
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
773788
{
774789
int namelen;
775-
char alt_name[PATH_MAX];
790+
char alt_name[MAX_LONG_PATH];
776791

777792
if (!do_lstat(follow, file_name, buf))
778793
return 0;
@@ -788,7 +803,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
788803
return -1;
789804
while (namelen && file_name[namelen-1] == '/')
790805
--namelen;
791-
if (!namelen || namelen >= PATH_MAX)
806+
if (!namelen || namelen >= MAX_LONG_PATH)
792807
return -1;
793808

794809
memcpy(alt_name, file_name, namelen);
@@ -872,8 +887,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
872887
FILETIME mft, aft;
873888
int fh, rc;
874889
DWORD attrs;
875-
wchar_t wfilename[MAX_PATH];
876-
if (xutftowcs_path(wfilename, file_name) < 0)
890+
wchar_t wfilename[MAX_LONG_PATH];
891+
if (xutftowcs_long_path(wfilename, file_name) < 0)
877892
return -1;
878893

879894
/* must have write permission */
@@ -934,6 +949,7 @@ char *mingw_mktemp(char *template)
934949
wchar_t wtemplate[MAX_PATH];
935950
int offset = 0;
936951

952+
/* we need to return the path, thus no long paths here! */
937953
if (xutftowcs_path(wtemplate, template) < 0)
938954
return NULL;
939955

@@ -1457,6 +1473,10 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
14571473

14581474
if (*argv && !strcmp(cmd, *argv))
14591475
wcmd[0] = L'\0';
1476+
/*
1477+
* Paths to executables and to the current directory do not support
1478+
* long paths, therefore we cannot use xutftowcs_long_path() here.
1479+
*/
14601480
else if (xutftowcs_path(wcmd, cmd) < 0)
14611481
return -1;
14621482
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1855,8 +1875,9 @@ int mingw_rename(const char *pold, const char *pnew)
18551875
{
18561876
DWORD attrs, gle;
18571877
int tries = 0;
1858-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1859-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1878+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1879+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1880+
xutftowcs_long_path(wpnew, pnew) < 0)
18601881
return -1;
18611882

18621883
/*
@@ -2165,9 +2186,9 @@ int mingw_raise(int sig)
21652186

21662187
int link(const char *oldpath, const char *newpath)
21672188
{
2168-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
2169-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
2170-
xutftowcs_path(wnewpath, newpath) < 0)
2189+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
2190+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
2191+
xutftowcs_long_path(wnewpath, newpath) < 0)
21712192
return -1;
21722193

21732194
if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
@@ -2367,6 +2388,68 @@ static void setup_windows_environment(void)
23672388
}
23682389
}
23692390

2391+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2392+
{
2393+
int result;
2394+
wchar_t buf[MAX_LONG_PATH];
2395+
2396+
/*
2397+
* we don't need special handling if path is relative to the current
2398+
* directory, and current directory + path don't exceed the desired
2399+
* max_path limit. This should cover > 99 % of cases with minimal
2400+
* performance impact (git almost always uses relative paths).
2401+
*/
2402+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2403+
(current_directory_len + len < max_path))
2404+
return len;
2405+
2406+
/*
2407+
* handle everything else:
2408+
* - absolute paths: "C:\dir\file"
2409+
* - absolute UNC paths: "\\server\share\dir\file"
2410+
* - absolute paths on current drive: "\dir\file"
2411+
* - relative paths on other drive: "X:file"
2412+
* - prefixed paths: "\\?\...", "\\.\..."
2413+
*/
2414+
2415+
/* convert to absolute path using GetFullPathNameW */
2416+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2417+
if (!result) {
2418+
errno = err_win_to_posix(GetLastError());
2419+
return -1;
2420+
}
2421+
2422+
/*
2423+
* return absolute path if it fits within max_path (even if
2424+
* "cwd + path" doesn't due to '..' components)
2425+
*/
2426+
if (result < max_path) {
2427+
wcscpy(path, buf);
2428+
return result;
2429+
}
2430+
2431+
/* error out if we shouldn't expand the path or buf is too small */
2432+
if (!expand || result >= MAX_LONG_PATH - 6) {
2433+
errno = ENAMETOOLONG;
2434+
return -1;
2435+
}
2436+
2437+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2438+
if (buf[0] == '\\') {
2439+
/* ...unless already prefixed */
2440+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2441+
return len;
2442+
2443+
wcscpy(path, L"\\\\?\\UNC\\");
2444+
wcscpy(path + 8, buf + 2);
2445+
return result + 6;
2446+
} else {
2447+
wcscpy(path, L"\\\\?\\");
2448+
wcscpy(path + 4, buf);
2449+
return result + 4;
2450+
}
2451+
}
2452+
23702453
#if !defined(_MSC_VER)
23712454
/*
23722455
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
@@ -2523,6 +2606,9 @@ int wmain(int argc, const wchar_t **wargv)
25232606
/* initialize Unicode console */
25242607
winansi_init();
25252608

2609+
/* init length of current directory for handle_long_path */
2610+
current_directory_len = GetCurrentDirectoryW(0, NULL);
2611+
25262612
/* invoke the real main() using our utf8 version of argv. */
25272613
exit_status = main(argc, argv);
25282614

0 commit comments

Comments
 (0)