Skip to content

Commit 89969b4

Browse files
author
Git for Windows Build Agent
committed
Merge 'long-paths' into HEAD
2 parents 548cba8 + a52969e commit 89969b4

File tree

10 files changed

+422
-49
lines changed

10 files changed

+422
-49
lines changed

Documentation/config.txt

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

769+
core.longpaths::
770+
Enable long path (> 260) support for builtin commands in Git for
771+
Windows. This is disabled by default, as long paths are not supported
772+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
773+
(msys, bash, tcl, perl...). Only enable this if you know what you're
774+
doing and are prepared to live with a few quirks.
775+
769776
core.createObject::
770777
You can set this to 'link', in which case a hardlink followed by
771778
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
@@ -731,6 +731,8 @@ extern enum hide_dotfiles_type hide_dotfiles;
731731

732732
extern int core_fscache;
733733

734+
extern int core_long_paths;
735+
734736
enum branch_track {
735737
BRANCH_TRACK_UNSPECIFIED = -1,
736738
BRANCH_TRACK_NEVER = 0,

compat/mingw.c

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

212212
/* read-only files cannot be removed */
@@ -235,7 +235,7 @@ static int is_dir_empty(const wchar_t *wpath)
235235
{
236236
WIN32_FIND_DATAW findbuf;
237237
HANDLE handle;
238-
wchar_t wbuf[MAX_PATH + 2];
238+
wchar_t wbuf[MAX_LONG_PATH + 2];
239239
wcscpy(wbuf, wpath);
240240
wcscat(wbuf, L"\\*");
241241
handle = FindFirstFileW(wbuf, &findbuf);
@@ -256,8 +256,8 @@ static int is_dir_empty(const wchar_t *wpath)
256256
int mingw_rmdir(const char *pathname)
257257
{
258258
int ret, tries = 0;
259-
wchar_t wpathname[MAX_PATH];
260-
if (xutftowcs_path(wpathname, pathname) < 0)
259+
wchar_t wpathname[MAX_LONG_PATH];
260+
if (xutftowcs_long_path(wpathname, pathname) < 0)
261261
return -1;
262262

263263
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -332,9 +332,12 @@ static int set_hidden_flag(const wchar_t *path, int set)
332332
int mingw_mkdir(const char *path, int mode)
333333
{
334334
int ret;
335-
wchar_t wpath[MAX_PATH];
336-
if (xutftowcs_path(wpath, path) < 0)
335+
wchar_t wpath[MAX_LONG_PATH];
336+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
337+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
338+
core_long_paths) < 0)
337339
return -1;
340+
338341
ret = _wmkdir(wpath);
339342
if (!ret && needs_hiding(path))
340343
return set_hidden_flag(wpath, 1);
@@ -346,7 +349,7 @@ int mingw_open (const char *filename, int oflags, ...)
346349
va_list args;
347350
unsigned mode;
348351
int fd;
349-
wchar_t wfilename[MAX_PATH];
352+
wchar_t wfilename[MAX_LONG_PATH];
350353

351354
va_start(args, oflags);
352355
mode = va_arg(args, int);
@@ -355,7 +358,7 @@ int mingw_open (const char *filename, int oflags, ...)
355358
if (filename && !strcmp(filename, "/dev/null"))
356359
filename = "nul";
357360

358-
if (xutftowcs_path(wfilename, filename) < 0)
361+
if (xutftowcs_long_path(wfilename, filename) < 0)
359362
return -1;
360363
fd = _wopen(wfilename, oflags, mode);
361364

@@ -412,10 +415,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
412415
{
413416
int hide = needs_hiding(filename);
414417
FILE *file;
415-
wchar_t wfilename[MAX_PATH], wotype[4];
418+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
416419
if (filename && !strcmp(filename, "/dev/null"))
417420
filename = "nul";
418-
if (xutftowcs_path(wfilename, filename) < 0 ||
421+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
419422
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
420423
return NULL;
421424
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -432,10 +435,10 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
432435
{
433436
int hide = needs_hiding(filename);
434437
FILE *file;
435-
wchar_t wfilename[MAX_PATH], wotype[4];
438+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
436439
if (filename && !strcmp(filename, "/dev/null"))
437440
filename = "nul";
438-
if (xutftowcs_path(wfilename, filename) < 0 ||
441+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
439442
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
440443
return NULL;
441444
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -489,25 +492,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
489492

490493
int mingw_access(const char *filename, int mode)
491494
{
492-
wchar_t wfilename[MAX_PATH];
493-
if (xutftowcs_path(wfilename, filename) < 0)
495+
wchar_t wfilename[MAX_LONG_PATH];
496+
if (xutftowcs_long_path(wfilename, filename) < 0)
494497
return -1;
495498
/* X_OK is not supported by the MSVCRT version */
496499
return _waccess(wfilename, mode & ~X_OK);
497500
}
498501

502+
/* cached length of current directory for handle_long_path */
503+
static int current_directory_len = 0;
504+
499505
int mingw_chdir(const char *dirname)
500506
{
501-
wchar_t wdirname[MAX_PATH];
502-
if (xutftowcs_path(wdirname, dirname) < 0)
507+
int result;
508+
wchar_t wdirname[MAX_LONG_PATH];
509+
if (xutftowcs_long_path(wdirname, dirname) < 0)
503510
return -1;
504-
return _wchdir(wdirname);
511+
result = _wchdir(wdirname);
512+
current_directory_len = GetCurrentDirectoryW(0, NULL);
513+
return result;
505514
}
506515

507516
int mingw_chmod(const char *filename, int mode)
508517
{
509-
wchar_t wfilename[MAX_PATH];
510-
if (xutftowcs_path(wfilename, filename) < 0)
518+
wchar_t wfilename[MAX_LONG_PATH];
519+
if (xutftowcs_long_path(wfilename, filename) < 0)
511520
return -1;
512521
return _wchmod(wfilename, mode);
513522
}
@@ -555,8 +564,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
555564
static int do_lstat(int follow, const char *file_name, struct stat *buf)
556565
{
557566
WIN32_FILE_ATTRIBUTE_DATA fdata;
558-
wchar_t wfilename[MAX_PATH];
559-
if (xutftowcs_path(wfilename, file_name) < 0)
567+
wchar_t wfilename[MAX_LONG_PATH];
568+
if (xutftowcs_long_path(wfilename, file_name) < 0)
560569
return -1;
561570

562571
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -627,7 +636,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
627636
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
628637
{
629638
int namelen;
630-
char alt_name[PATH_MAX];
639+
char alt_name[MAX_LONG_PATH];
631640

632641
if (!do_lstat(follow, file_name, buf))
633642
return 0;
@@ -643,7 +652,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
643652
return -1;
644653
while (namelen && file_name[namelen-1] == '/')
645654
--namelen;
646-
if (!namelen || namelen >= PATH_MAX)
655+
if (!namelen || namelen >= MAX_LONG_PATH)
647656
return -1;
648657

649658
memcpy(alt_name, file_name, namelen);
@@ -705,8 +714,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
705714
FILETIME mft, aft;
706715
int fh, rc;
707716
DWORD attrs;
708-
wchar_t wfilename[MAX_PATH];
709-
if (xutftowcs_path(wfilename, file_name) < 0)
717+
wchar_t wfilename[MAX_LONG_PATH];
718+
if (xutftowcs_long_path(wfilename, file_name) < 0)
710719
return -1;
711720

712721
/* must have write permission */
@@ -754,6 +763,7 @@ unsigned int sleep (unsigned int seconds)
754763
char *mingw_mktemp(char *template)
755764
{
756765
wchar_t wtemplate[MAX_PATH];
766+
/* we need to return the path, thus no long paths here! */
757767
if (xutftowcs_path(wtemplate, template) < 0)
758768
return NULL;
759769
if (!_wmktemp(wtemplate))
@@ -1104,6 +1114,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
11041114
si.hStdOutput = winansi_get_osfhandle(fhout);
11051115
si.hStdError = winansi_get_osfhandle(fherr);
11061116

1117+
/* executables and the current directory don't support long paths */
11071118
if (xutftowcs_path(wcmd, cmd) < 0)
11081119
return -1;
11091120
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1679,8 +1690,9 @@ int mingw_rename(const char *pold, const char *pnew)
16791690
{
16801691
DWORD attrs, gle;
16811692
int tries = 0;
1682-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1683-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1693+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1694+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1695+
xutftowcs_long_path(wpnew, pnew) < 0)
16841696
return -1;
16851697

16861698
/*
@@ -1920,9 +1932,9 @@ int link(const char *oldpath, const char *newpath)
19201932
{
19211933
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19221934
static T create_hard_link = NULL;
1923-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1924-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1925-
xutftowcs_path(wnewpath, newpath) < 0)
1935+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1936+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1937+
xutftowcs_long_path(wnewpath, newpath) < 0)
19261938
return -1;
19271939

19281940
if (!create_hard_link) {
@@ -2135,6 +2147,68 @@ static void setup_windows_environment(void)
21352147
setenv("TERM", "cygwin", 1);
21362148
}
21372149

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

22272301
/* initialize Unicode console */
22282302
winansi_init();
2303+
2304+
/* init length of current directory for handle_long_path */
2305+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22292306
}
22302307

22312308
int uname(struct utsname *buf)

0 commit comments

Comments
 (0)