Skip to content

Commit c329204

Browse files
author
Git for Windows Build Agent
committed
Merge 'long-paths' into HEAD
2 parents 7e16101 + dec7e87 commit c329204

File tree

7 files changed

+418
-49
lines changed

7 files changed

+418
-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.unsetenvvars::
770777
EXPERIMENTAL, Windows-only: comma-separated list of environment
771778
variables' names that need to be unset before spawning any other

compat/mingw.c

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ enum hide_dotfiles_type {
212212
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
213213
static char *unset_environment_variables;
214214
int core_fscache;
215+
int core_long_paths;
215216

216217
int mingw_core_config(const char *var, const char *value)
217218
{
@@ -228,6 +229,11 @@ int mingw_core_config(const char *var, const char *value)
228229
return 0;
229230
}
230231

232+
if (!strcmp(var, "core.longpaths")) {
233+
core_long_paths = git_config_bool(var, value);
234+
return 0;
235+
}
236+
231237
if (!strcmp(var, "core.unsetenvvars")) {
232238
free(unset_environment_variables);
233239
unset_environment_variables = xstrdup(value);
@@ -240,8 +246,8 @@ int mingw_core_config(const char *var, const char *value)
240246
int mingw_unlink(const char *pathname)
241247
{
242248
int ret, tries = 0;
243-
wchar_t wpathname[MAX_PATH];
244-
if (xutftowcs_path(wpathname, pathname) < 0)
249+
wchar_t wpathname[MAX_LONG_PATH];
250+
if (xutftowcs_long_path(wpathname, pathname) < 0)
245251
return -1;
246252

247253
/* read-only files cannot be removed */
@@ -270,7 +276,7 @@ static int is_dir_empty(const wchar_t *wpath)
270276
{
271277
WIN32_FIND_DATAW findbuf;
272278
HANDLE handle;
273-
wchar_t wbuf[MAX_PATH + 2];
279+
wchar_t wbuf[MAX_LONG_PATH + 2];
274280
wcscpy(wbuf, wpath);
275281
wcscat(wbuf, L"\\*");
276282
handle = FindFirstFileW(wbuf, &findbuf);
@@ -291,8 +297,8 @@ static int is_dir_empty(const wchar_t *wpath)
291297
int mingw_rmdir(const char *pathname)
292298
{
293299
int ret, tries = 0;
294-
wchar_t wpathname[MAX_PATH];
295-
if (xutftowcs_path(wpathname, pathname) < 0)
300+
wchar_t wpathname[MAX_LONG_PATH];
301+
if (xutftowcs_long_path(wpathname, pathname) < 0)
296302
return -1;
297303

298304
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -367,9 +373,12 @@ static int set_hidden_flag(const wchar_t *path, int set)
367373
int mingw_mkdir(const char *path, int mode)
368374
{
369375
int ret;
370-
wchar_t wpath[MAX_PATH];
371-
if (xutftowcs_path(wpath, path) < 0)
376+
wchar_t wpath[MAX_LONG_PATH];
377+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
378+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
379+
core_long_paths) < 0)
372380
return -1;
381+
373382
ret = _wmkdir(wpath);
374383
if (!ret && needs_hiding(path))
375384
return set_hidden_flag(wpath, 1);
@@ -381,7 +390,7 @@ int mingw_open (const char *filename, int oflags, ...)
381390
va_list args;
382391
unsigned mode;
383392
int fd;
384-
wchar_t wfilename[MAX_PATH];
393+
wchar_t wfilename[MAX_LONG_PATH];
385394

386395
va_start(args, oflags);
387396
mode = va_arg(args, int);
@@ -390,7 +399,7 @@ int mingw_open (const char *filename, int oflags, ...)
390399
if (filename && !strcmp(filename, "/dev/null"))
391400
filename = "nul";
392401

393-
if (xutftowcs_path(wfilename, filename) < 0)
402+
if (xutftowcs_long_path(wfilename, filename) < 0)
394403
return -1;
395404
fd = _wopen(wfilename, oflags, mode);
396405

@@ -447,10 +456,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
447456
{
448457
int hide = needs_hiding(filename);
449458
FILE *file;
450-
wchar_t wfilename[MAX_PATH], wotype[4];
459+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
451460
if (filename && !strcmp(filename, "/dev/null"))
452461
filename = "nul";
453-
if (xutftowcs_path(wfilename, filename) < 0 ||
462+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
454463
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
455464
return NULL;
456465
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -467,10 +476,10 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
467476
{
468477
int hide = needs_hiding(filename);
469478
FILE *file;
470-
wchar_t wfilename[MAX_PATH], wotype[4];
479+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
471480
if (filename && !strcmp(filename, "/dev/null"))
472481
filename = "nul";
473-
if (xutftowcs_path(wfilename, filename) < 0 ||
482+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
474483
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
475484
return NULL;
476485
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -524,25 +533,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
524533

525534
int mingw_access(const char *filename, int mode)
526535
{
527-
wchar_t wfilename[MAX_PATH];
528-
if (xutftowcs_path(wfilename, filename) < 0)
536+
wchar_t wfilename[MAX_LONG_PATH];
537+
if (xutftowcs_long_path(wfilename, filename) < 0)
529538
return -1;
530539
/* X_OK is not supported by the MSVCRT version */
531540
return _waccess(wfilename, mode & ~X_OK);
532541
}
533542

543+
/* cached length of current directory for handle_long_path */
544+
static int current_directory_len = 0;
545+
534546
int mingw_chdir(const char *dirname)
535547
{
536-
wchar_t wdirname[MAX_PATH];
537-
if (xutftowcs_path(wdirname, dirname) < 0)
548+
int result;
549+
wchar_t wdirname[MAX_LONG_PATH];
550+
if (xutftowcs_long_path(wdirname, dirname) < 0)
538551
return -1;
539-
return _wchdir(wdirname);
552+
result = _wchdir(wdirname);
553+
current_directory_len = GetCurrentDirectoryW(0, NULL);
554+
return result;
540555
}
541556

542557
int mingw_chmod(const char *filename, int mode)
543558
{
544-
wchar_t wfilename[MAX_PATH];
545-
if (xutftowcs_path(wfilename, filename) < 0)
559+
wchar_t wfilename[MAX_LONG_PATH];
560+
if (xutftowcs_long_path(wfilename, filename) < 0)
546561
return -1;
547562
return _wchmod(wfilename, mode);
548563
}
@@ -590,8 +605,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
590605
static int do_lstat(int follow, const char *file_name, struct stat *buf)
591606
{
592607
WIN32_FILE_ATTRIBUTE_DATA fdata;
593-
wchar_t wfilename[MAX_PATH];
594-
if (xutftowcs_path(wfilename, file_name) < 0)
608+
wchar_t wfilename[MAX_LONG_PATH];
609+
if (xutftowcs_long_path(wfilename, file_name) < 0)
595610
return -1;
596611

597612
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -662,7 +677,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
662677
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
663678
{
664679
int namelen;
665-
char alt_name[PATH_MAX];
680+
char alt_name[MAX_LONG_PATH];
666681

667682
if (!do_lstat(follow, file_name, buf))
668683
return 0;
@@ -678,7 +693,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
678693
return -1;
679694
while (namelen && file_name[namelen-1] == '/')
680695
--namelen;
681-
if (!namelen || namelen >= PATH_MAX)
696+
if (!namelen || namelen >= MAX_LONG_PATH)
682697
return -1;
683698

684699
memcpy(alt_name, file_name, namelen);
@@ -740,8 +755,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
740755
FILETIME mft, aft;
741756
int fh, rc;
742757
DWORD attrs;
743-
wchar_t wfilename[MAX_PATH];
744-
if (xutftowcs_path(wfilename, file_name) < 0)
758+
wchar_t wfilename[MAX_LONG_PATH];
759+
if (xutftowcs_long_path(wfilename, file_name) < 0)
745760
return -1;
746761

747762
/* must have write permission */
@@ -789,6 +804,7 @@ unsigned int sleep (unsigned int seconds)
789804
char *mingw_mktemp(char *template)
790805
{
791806
wchar_t wtemplate[MAX_PATH];
807+
/* we need to return the path, thus no long paths here! */
792808
if (xutftowcs_path(wtemplate, template) < 0)
793809
return NULL;
794810
if (!_wmktemp(wtemplate))
@@ -1163,6 +1179,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
11631179
si.hStdOutput = winansi_get_osfhandle(fhout);
11641180
si.hStdError = winansi_get_osfhandle(fherr);
11651181

1182+
/* executables and the current directory don't support long paths */
11661183
if (xutftowcs_path(wcmd, cmd) < 0)
11671184
return -1;
11681185
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1738,8 +1755,9 @@ int mingw_rename(const char *pold, const char *pnew)
17381755
{
17391756
DWORD attrs, gle;
17401757
int tries = 0;
1741-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1742-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1758+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1759+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1760+
xutftowcs_long_path(wpnew, pnew) < 0)
17431761
return -1;
17441762

17451763
/*
@@ -1979,9 +1997,9 @@ int link(const char *oldpath, const char *newpath)
19791997
{
19801998
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19811999
static T create_hard_link = NULL;
1982-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1983-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
1984-
xutftowcs_path(wnewpath, newpath) < 0)
2000+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
2001+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
2002+
xutftowcs_long_path(wnewpath, newpath) < 0)
19852003
return -1;
19862004

19872005
if (!create_hard_link) {
@@ -2194,6 +2212,68 @@ static void setup_windows_environment(void)
21942212
setenv("TERM", "cygwin", 1);
21952213
}
21962214

2215+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2216+
{
2217+
int result;
2218+
wchar_t buf[MAX_LONG_PATH];
2219+
2220+
/*
2221+
* we don't need special handling if path is relative to the current
2222+
* directory, and current directory + path don't exceed the desired
2223+
* max_path limit. This should cover > 99 % of cases with minimal
2224+
* performance impact (git almost always uses relative paths).
2225+
*/
2226+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2227+
(current_directory_len + len < max_path))
2228+
return len;
2229+
2230+
/*
2231+
* handle everything else:
2232+
* - absolute paths: "C:\dir\file"
2233+
* - absolute UNC paths: "\\server\share\dir\file"
2234+
* - absolute paths on current drive: "\dir\file"
2235+
* - relative paths on other drive: "X:file"
2236+
* - prefixed paths: "\\?\...", "\\.\..."
2237+
*/
2238+
2239+
/* convert to absolute path using GetFullPathNameW */
2240+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2241+
if (!result) {
2242+
errno = err_win_to_posix(GetLastError());
2243+
return -1;
2244+
}
2245+
2246+
/*
2247+
* return absolute path if it fits within max_path (even if
2248+
* "cwd + path" doesn't due to '..' components)
2249+
*/
2250+
if (result < max_path) {
2251+
wcscpy(path, buf);
2252+
return result;
2253+
}
2254+
2255+
/* error out if we shouldn't expand the path or buf is too small */
2256+
if (!expand || result >= MAX_LONG_PATH - 6) {
2257+
errno = ENAMETOOLONG;
2258+
return -1;
2259+
}
2260+
2261+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2262+
if (buf[0] == '\\') {
2263+
/* ...unless already prefixed */
2264+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2265+
return len;
2266+
2267+
wcscpy(path, L"\\\\?\\UNC\\");
2268+
wcscpy(path + 8, buf + 2);
2269+
return result + 6;
2270+
} else {
2271+
wcscpy(path, L"\\\\?\\");
2272+
wcscpy(path + 4, buf);
2273+
return result + 4;
2274+
}
2275+
}
2276+
21972277
/*
21982278
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
21992279
* mingw startup code, see init.c in mingw runtime).
@@ -2287,6 +2367,9 @@ void mingw_startup(void)
22872367

22882368
/* initialize Unicode console */
22892369
winansi_init();
2370+
2371+
/* init length of current directory for handle_long_path */
2372+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22902373
}
22912374

22922375
int uname(struct utsname *buf)

0 commit comments

Comments
 (0)