Skip to content

Commit 880470d

Browse files
committed
Merge 'long-paths' into HEAD
2 parents bf971c4 + 7bbbaf5 commit 880470d

File tree

10 files changed

+421
-49
lines changed

10 files changed

+421
-49
lines changed

Documentation/config.txt

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

722+
core.longpaths::
723+
Enable long path (> 260) support for builtin commands in Git for
724+
Windows. This is disabled by default, as long paths are not supported
725+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
726+
(msys, bash, tcl, perl...). Only enable this if you know what you're
727+
doing and are prepared to live with a few quirks.
728+
722729
core.createObject::
723730
You can set this to 'link', in which case a hardlink followed by
724731
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
@@ -685,6 +685,8 @@ extern enum hide_dotfiles_type hide_dotfiles;
685685

686686
extern int core_fscache;
687687

688+
extern int core_long_paths;
689+
688690
enum branch_track {
689691
BRANCH_TRACK_UNSPECIFIED = -1,
690692
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);
@@ -470,25 +473,32 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
470473

471474
int mingw_access(const char *filename, int mode)
472475
{
473-
wchar_t wfilename[MAX_PATH];
474-
if (xutftowcs_path(wfilename, filename) < 0)
476+
wchar_t wfilename[MAX_LONG_PATH];
477+
if (xutftowcs_long_path(wfilename, filename) < 0)
475478
return -1;
476479
/* X_OK is not supported by the MSVCRT version */
477480
return _waccess(wfilename, mode & ~X_OK);
478481
}
479482

483+
/* cached length of current directory for handle_long_path */
484+
static int current_directory_len = 0;
485+
480486
int mingw_chdir(const char *dirname)
481487
{
488+
int result;
482489
wchar_t wdirname[MAX_PATH];
490+
/* SetCurrentDirectoryW doesn't support long paths */
483491
if (xutftowcs_path(wdirname, dirname) < 0)
484492
return -1;
485-
return _wchdir(wdirname);
493+
result = _wchdir(wdirname);
494+
current_directory_len = GetCurrentDirectoryW(0, NULL);
495+
return result;
486496
}
487497

488498
int mingw_chmod(const char *filename, int mode)
489499
{
490-
wchar_t wfilename[MAX_PATH];
491-
if (xutftowcs_path(wfilename, filename) < 0)
500+
wchar_t wfilename[MAX_LONG_PATH];
501+
if (xutftowcs_long_path(wfilename, filename) < 0)
492502
return -1;
493503
return _wchmod(wfilename, mode);
494504
}
@@ -536,8 +546,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
536546
static int do_lstat(int follow, const char *file_name, struct stat *buf)
537547
{
538548
WIN32_FILE_ATTRIBUTE_DATA fdata;
539-
wchar_t wfilename[MAX_PATH];
540-
if (xutftowcs_path(wfilename, file_name) < 0)
549+
wchar_t wfilename[MAX_LONG_PATH];
550+
if (xutftowcs_long_path(wfilename, file_name) < 0)
541551
return -1;
542552

543553
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -608,7 +618,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
608618
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
609619
{
610620
int namelen;
611-
char alt_name[PATH_MAX];
621+
char alt_name[MAX_LONG_PATH];
612622

613623
if (!do_lstat(follow, file_name, buf))
614624
return 0;
@@ -624,7 +634,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
624634
return -1;
625635
while (namelen && file_name[namelen-1] == '/')
626636
--namelen;
627-
if (!namelen || namelen >= PATH_MAX)
637+
if (!namelen || namelen >= MAX_LONG_PATH)
628638
return -1;
629639

630640
memcpy(alt_name, file_name, namelen);
@@ -686,8 +696,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
686696
FILETIME mft, aft;
687697
int fh, rc;
688698
DWORD attrs;
689-
wchar_t wfilename[MAX_PATH];
690-
if (xutftowcs_path(wfilename, file_name) < 0)
699+
wchar_t wfilename[MAX_LONG_PATH];
700+
if (xutftowcs_long_path(wfilename, file_name) < 0)
691701
return -1;
692702

693703
/* must have write permission */
@@ -735,6 +745,7 @@ unsigned int sleep (unsigned int seconds)
735745
char *mingw_mktemp(char *template)
736746
{
737747
wchar_t wtemplate[MAX_PATH];
748+
/* we need to return the path, thus no long paths here! */
738749
if (xutftowcs_path(wtemplate, template) < 0)
739750
return NULL;
740751
if (!_wmktemp(wtemplate))
@@ -1088,6 +1099,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
10881099
si.hStdOutput = winansi_get_osfhandle(fhout);
10891100
si.hStdError = winansi_get_osfhandle(fherr);
10901101

1102+
/* executables and the current directory don't support long paths */
10911103
if (xutftowcs_path(wcmd, cmd) < 0)
10921104
return -1;
10931105
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1663,8 +1675,9 @@ int mingw_rename(const char *pold, const char *pnew)
16631675
{
16641676
DWORD attrs, gle;
16651677
int tries = 0;
1666-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1667-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1678+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1679+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1680+
xutftowcs_long_path(wpnew, pnew) < 0)
16681681
return -1;
16691682

16701683
/*
@@ -1946,9 +1959,9 @@ int link(const char *oldpath, const char *newpath)
19461959
{
19471960
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19481961
static T create_hard_link = NULL;
1949-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1950-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1951-
xutftowcs_path(wnewpath, newpath) < 0)
1962+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1963+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1964+
xutftowcs_long_path(wnewpath, newpath) < 0)
19521965
return -1;
19531966

19541967
if (!create_hard_link) {
@@ -2160,6 +2173,68 @@ static void setup_windows_environment()
21602173
setenv("TERM", "cygwin", 1);
21612174
}
21622175

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

22522327
/* initialize Unicode console */
22532328
winansi_init();
2329+
2330+
/* init length of current directory for handle_long_path */
2331+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22542332
}
22552333

22562334
int uname(struct utsname *buf)

0 commit comments

Comments
 (0)