Skip to content

Commit 586446c

Browse files
committed
Merge branch 'long-paths'
2 parents cb1b350 + de6c51e commit 586446c

File tree

8 files changed

+440
-68
lines changed

8 files changed

+440
-68
lines changed

Documentation/config.txt

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

918+
core.longpaths::
919+
Enable long path (> 260) support for builtin commands in Git for
920+
Windows. This is disabled by default, as long paths are not supported
921+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
922+
(msys, bash, tcl, perl...). Only enable this if you know what you're
923+
doing and are prepared to live with a few quirks.
924+
918925
core.unsetenvvars::
919926
EXPERIMENTAL, Windows-only: comma-separated list of environment
920927
variables' names that need to be unset before spawning any other

compat/mingw.c

Lines changed: 124 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ enum hide_dotfiles_type {
213213
static enum hide_dotfiles_type hide_dotfiles = HIDE_DOTFILES_DOTGITONLY;
214214
static char *unset_environment_variables;
215215
int core_fscache;
216+
int core_long_paths;
216217

217218
int mingw_core_config(const char *var, const char *value, void *cb)
218219
{
@@ -229,6 +230,11 @@ int mingw_core_config(const char *var, const char *value, void *cb)
229230
return 0;
230231
}
231232

233+
if (!strcmp(var, "core.longpaths")) {
234+
core_long_paths = git_config_bool(var, value);
235+
return 0;
236+
}
237+
232238
if (!strcmp(var, "core.unsetenvvars")) {
233239
free(unset_environment_variables);
234240
unset_environment_variables = xstrdup(value);
@@ -266,8 +272,8 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)
266272
int mingw_unlink(const char *pathname)
267273
{
268274
int ret, tries = 0;
269-
wchar_t wpathname[MAX_PATH];
270-
if (xutftowcs_path(wpathname, pathname) < 0)
275+
wchar_t wpathname[MAX_LONG_PATH];
276+
if (xutftowcs_long_path(wpathname, pathname) < 0)
271277
return -1;
272278

273279
/* read-only files cannot be removed */
@@ -296,7 +302,7 @@ static int is_dir_empty(const wchar_t *wpath)
296302
{
297303
WIN32_FIND_DATAW findbuf;
298304
HANDLE handle;
299-
wchar_t wbuf[MAX_PATH + 2];
305+
wchar_t wbuf[MAX_LONG_PATH + 2];
300306
wcscpy(wbuf, wpath);
301307
wcscat(wbuf, L"\\*");
302308
handle = FindFirstFileW(wbuf, &findbuf);
@@ -317,8 +323,8 @@ static int is_dir_empty(const wchar_t *wpath)
317323
int mingw_rmdir(const char *pathname)
318324
{
319325
int ret, tries = 0;
320-
wchar_t wpathname[MAX_PATH];
321-
if (xutftowcs_path(wpathname, pathname) < 0)
326+
wchar_t wpathname[MAX_LONG_PATH];
327+
if (xutftowcs_long_path(wpathname, pathname) < 0)
322328
return -1;
323329

324330
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -393,9 +399,12 @@ static int set_hidden_flag(const wchar_t *path, int set)
393399
int mingw_mkdir(const char *path, int mode)
394400
{
395401
int ret;
396-
wchar_t wpath[MAX_PATH];
397-
if (xutftowcs_path(wpath, path) < 0)
402+
wchar_t wpath[MAX_LONG_PATH];
403+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
404+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
405+
core_long_paths) < 0)
398406
return -1;
407+
399408
ret = _wmkdir(wpath);
400409
if (!ret && needs_hiding(path))
401410
return set_hidden_flag(wpath, 1);
@@ -438,7 +447,7 @@ int mingw_open (const char *filename, int oflags, ...)
438447
va_list args;
439448
unsigned mode;
440449
int fd;
441-
wchar_t wfilename[MAX_PATH];
450+
wchar_t wfilename[MAX_LONG_PATH];
442451
open_fn_t open_fn;
443452

444453
va_start(args, oflags);
@@ -453,7 +462,7 @@ int mingw_open (const char *filename, int oflags, ...)
453462
else
454463
open_fn = _wopen;
455464

456-
if (xutftowcs_path(wfilename, filename) < 0)
465+
if (xutftowcs_long_path(wfilename, filename) < 0)
457466
return -1;
458467
fd = open_fn(wfilename, oflags, mode);
459468

@@ -510,10 +519,10 @@ FILE *mingw_fopen (const char *filename, const char *otype)
510519
{
511520
int hide = needs_hiding(filename);
512521
FILE *file;
513-
wchar_t wfilename[MAX_PATH], wotype[4];
522+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
514523
if (filename && !strcmp(filename, "/dev/null"))
515524
filename = "nul";
516-
if (xutftowcs_path(wfilename, filename) < 0 ||
525+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
517526
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
518527
return NULL;
519528
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -532,10 +541,10 @@ FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
532541
{
533542
int hide = needs_hiding(filename);
534543
FILE *file;
535-
wchar_t wfilename[MAX_PATH], wotype[4];
544+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
536545
if (filename && !strcmp(filename, "/dev/null"))
537546
filename = "nul";
538-
if (xutftowcs_path(wfilename, filename) < 0 ||
547+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
539548
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
540549
return NULL;
541550
if (hide && !access(filename, F_OK) && set_hidden_flag(wfilename, 0)) {
@@ -589,25 +598,31 @@ ssize_t mingw_write(int fd, const void *buf, size_t len)
589598

590599
int mingw_access(const char *filename, int mode)
591600
{
592-
wchar_t wfilename[MAX_PATH];
593-
if (xutftowcs_path(wfilename, filename) < 0)
601+
wchar_t wfilename[MAX_LONG_PATH];
602+
if (xutftowcs_long_path(wfilename, filename) < 0)
594603
return -1;
595604
/* X_OK is not supported by the MSVCRT version */
596605
return _waccess(wfilename, mode & ~X_OK);
597606
}
598607

608+
/* cached length of current directory for handle_long_path */
609+
static int current_directory_len = 0;
610+
599611
int mingw_chdir(const char *dirname)
600612
{
601-
wchar_t wdirname[MAX_PATH];
602-
if (xutftowcs_path(wdirname, dirname) < 0)
613+
int result;
614+
wchar_t wdirname[MAX_LONG_PATH];
615+
if (xutftowcs_long_path(wdirname, dirname) < 0)
603616
return -1;
604-
return _wchdir(wdirname);
617+
result = _wchdir(wdirname);
618+
current_directory_len = GetCurrentDirectoryW(0, NULL);
619+
return result;
605620
}
606621

607622
int mingw_chmod(const char *filename, int mode)
608623
{
609-
wchar_t wfilename[MAX_PATH];
610-
if (xutftowcs_path(wfilename, filename) < 0)
624+
wchar_t wfilename[MAX_LONG_PATH];
625+
if (xutftowcs_long_path(wfilename, filename) < 0)
611626
return -1;
612627
return _wchmod(wfilename, mode);
613628
}
@@ -655,8 +670,8 @@ static int has_valid_directory_prefix(wchar_t *wfilename)
655670
static int do_lstat(int follow, const char *file_name, struct stat *buf)
656671
{
657672
WIN32_FILE_ATTRIBUTE_DATA fdata;
658-
wchar_t wfilename[MAX_PATH];
659-
if (xutftowcs_path(wfilename, file_name) < 0)
673+
wchar_t wfilename[MAX_LONG_PATH];
674+
if (xutftowcs_long_path(wfilename, file_name) < 0)
660675
return -1;
661676

662677
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -727,7 +742,7 @@ static int do_lstat(int follow, const char *file_name, struct stat *buf)
727742
static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
728743
{
729744
int namelen;
730-
char alt_name[PATH_MAX];
745+
char alt_name[MAX_LONG_PATH];
731746

732747
if (!do_lstat(follow, file_name, buf))
733748
return 0;
@@ -743,7 +758,7 @@ static int do_stat_internal(int follow, const char *file_name, struct stat *buf)
743758
return -1;
744759
while (namelen && file_name[namelen-1] == '/')
745760
--namelen;
746-
if (!namelen || namelen >= PATH_MAX)
761+
if (!namelen || namelen >= MAX_LONG_PATH)
747762
return -1;
748763

749764
memcpy(alt_name, file_name, namelen);
@@ -827,8 +842,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
827842
FILETIME mft, aft;
828843
int fh, rc;
829844
DWORD attrs;
830-
wchar_t wfilename[MAX_PATH];
831-
if (xutftowcs_path(wfilename, file_name) < 0)
845+
wchar_t wfilename[MAX_LONG_PATH];
846+
if (xutftowcs_long_path(wfilename, file_name) < 0)
832847
return -1;
833848

834849
/* must have write permission */
@@ -887,11 +902,20 @@ unsigned int sleep (unsigned int seconds)
887902
char *mingw_mktemp(char *template)
888903
{
889904
wchar_t wtemplate[MAX_PATH];
905+
int offset = 0;
906+
907+
/* we need to return the path, thus no long paths here! */
890908
if (xutftowcs_path(wtemplate, template) < 0)
891909
return NULL;
910+
911+
if (is_dir_sep(template[0]) && !is_dir_sep(template[1]) &&
912+
iswalpha(wtemplate[0]) && wtemplate[1] == L':') {
913+
/* We have an absolute path missing the drive prefix */
914+
offset = 2;
915+
}
892916
if (!_wmktemp(wtemplate))
893917
return NULL;
894-
if (xwcstoutf(template, wtemplate, strlen(template) + 1) < 0)
918+
if (xwcstoutf(template, wtemplate + offset, strlen(template) + 1) < 0)
895919
return NULL;
896920
return template;
897921
}
@@ -1321,6 +1345,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
13211345
si.hStdOutput = winansi_get_osfhandle(fhout);
13221346
si.hStdError = winansi_get_osfhandle(fherr);
13231347

1348+
/* executables and the current directory don't support long paths */
13241349
if (xutftowcs_path(wcmd, cmd) < 0)
13251350
return -1;
13261351
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1888,8 +1913,9 @@ int mingw_rename(const char *pold, const char *pnew)
18881913
{
18891914
DWORD attrs, gle;
18901915
int tries = 0;
1891-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1892-
if (xutftowcs_path(wpold, pold) < 0 || xutftowcs_path(wpnew, pnew) < 0)
1916+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1917+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1918+
xutftowcs_long_path(wpnew, pnew) < 0)
18931919
return -1;
18941920

18951921
/*
@@ -2153,24 +2179,12 @@ int mingw_raise(int sig)
21532179

21542180
int link(const char *oldpath, const char *newpath)
21552181
{
2156-
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
2157-
static T create_hard_link = NULL;
2158-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
2159-
if (xutftowcs_path(woldpath, oldpath) < 0 ||
2160-
xutftowcs_path(wnewpath, newpath) < 0)
2182+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
2183+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
2184+
xutftowcs_long_path(wnewpath, newpath) < 0)
21612185
return -1;
21622186

2163-
if (!create_hard_link) {
2164-
create_hard_link = (T) GetProcAddress(
2165-
GetModuleHandle("kernel32.dll"), "CreateHardLinkW");
2166-
if (!create_hard_link)
2167-
create_hard_link = (T)-1;
2168-
}
2169-
if (create_hard_link == (T)-1) {
2170-
errno = ENOSYS;
2171-
return -1;
2172-
}
2173-
if (!create_hard_link(wnewpath, woldpath, NULL)) {
2187+
if (!CreateHardLinkW(wnewpath, woldpath, NULL)) {
21742188
errno = err_win_to_posix(GetLastError());
21752189
return -1;
21762190
}
@@ -2370,6 +2384,68 @@ static void setup_windows_environment(void)
23702384
setenv("TERM", "cygwin", 1);
23712385
}
23722386

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

2613+
/* init length of current directory for handle_long_path */
2614+
current_directory_len = GetCurrentDirectoryW(0, NULL);
2615+
25372616
/* invoke the real main() using our utf8 version of argv. */
25382617
exit_status = main(argc, argv);
25392618

0 commit comments

Comments
 (0)