Skip to content
This repository was archived by the owner on Nov 9, 2017. It is now read-only.

Commit 942ba3e

Browse files
committed
Merge pull request #122 from kbless:kb/long-paths-v2
Signed-off-by: Johannes Schindelin <[email protected]>
2 parents 7baee59 + c5f9845 commit 942ba3e

File tree

10 files changed

+367
-126
lines changed

10 files changed

+367
-126
lines changed

Documentation/config.txt

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

629+
core.longpaths::
630+
Enable long path (> 260) support for builtin commands in Git for
631+
Windows. This is disabled by default, as long paths are not supported
632+
by Windows Explorer, cmd.exe and the Git for Windows tool chain
633+
(msys, bash, tcl, perl...). Only enable this if you know what you're
634+
doing and are prepared to live with a few quirks.
635+
629636
core.createObject::
630637
You can set this to 'link', in which case a hardlink followed by
631638
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
@@ -600,6 +600,8 @@ extern enum hide_dotfiles_type hide_dotfiles;
600600

601601
extern int core_fscache;
602602

603+
extern int core_long_paths;
604+
603605
enum branch_track {
604606
BRANCH_TRACK_UNSPECIFIED = -1,
605607
BRANCH_TRACK_NEVER = 0,

compat/mingw.c

Lines changed: 108 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
#include "../strbuf.h"
66
#include "../run-command.h"
77
#include "../cache.h"
8-
#include "../string-list.h"
98

109
static const int delay[] = { 0, 1, 10, 20, 40 };
1110
unsigned int _CRT_fmode = _O_BINARY;
@@ -205,8 +204,8 @@ static int ask_yes_no_if_possible(const char *format, ...)
205204
int mingw_unlink(const char *pathname)
206205
{
207206
int ret, tries = 0;
208-
wchar_t wpathname[MAX_PATH];
209-
if (xutftowcs_canonical_path(wpathname, pathname) < 0)
207+
wchar_t wpathname[MAX_LONG_PATH];
208+
if (xutftowcs_long_path(wpathname, pathname) < 0)
210209
return -1;
211210

212211
/* read-only files cannot be removed */
@@ -235,7 +234,7 @@ static int is_dir_empty(const wchar_t *wpath)
235234
{
236235
WIN32_FIND_DATAW findbuf;
237236
HANDLE handle;
238-
wchar_t wbuf[MAX_PATH + 2];
237+
wchar_t wbuf[MAX_LONG_PATH + 2];
239238
wcscpy(wbuf, wpath);
240239
wcscat(wbuf, L"\\*");
241240
handle = FindFirstFileW(wbuf, &findbuf);
@@ -256,8 +255,8 @@ static int is_dir_empty(const wchar_t *wpath)
256255
int mingw_rmdir(const char *pathname)
257256
{
258257
int ret, tries = 0;
259-
wchar_t wpathname[MAX_PATH];
260-
if (xutftowcs_canonical_path(wpathname, pathname) < 0)
258+
wchar_t wpathname[MAX_LONG_PATH];
259+
if (xutftowcs_long_path(wpathname, pathname) < 0)
261260
return -1;
262261

263262
while ((ret = _wrmdir(wpathname)) == -1 && tries < ARRAY_SIZE(delay)) {
@@ -297,9 +296,9 @@ static int make_hidden(const wchar_t *path)
297296

298297
void mingw_mark_as_git_dir(const char *dir)
299298
{
300-
wchar_t wdir[MAX_PATH];
299+
wchar_t wdir[MAX_LONG_PATH];
301300
if (hide_dotfiles != HIDE_DOTFILES_FALSE && !is_bare_repository())
302-
if (xutftowcs_canonical_path(wdir, dir) < 0 || make_hidden(wdir))
301+
if (xutftowcs_long_path(wdir, dir) < 0 || make_hidden(wdir))
303302
warning("Failed to make '%s' hidden", dir);
304303
git_config_set("core.hideDotFiles",
305304
hide_dotfiles == HIDE_DOTFILES_FALSE ? "false" :
@@ -310,9 +309,12 @@ void mingw_mark_as_git_dir(const char *dir)
310309
int mingw_mkdir(const char *path, int mode)
311310
{
312311
int ret;
313-
wchar_t wpath[MAX_PATH];
314-
if (xutftowcs_canonical_path(wpath, path) < 0)
312+
wchar_t wpath[MAX_LONG_PATH];
313+
/* CreateDirectoryW path limit is 248 (MAX_PATH - 8.3 file name) */
314+
if (xutftowcs_path_ex(wpath, path, MAX_LONG_PATH, -1, 248,
315+
core_long_paths) < 0)
315316
return -1;
317+
316318
ret = _wmkdir(wpath);
317319
if (!ret && hide_dotfiles == HIDE_DOTFILES_TRUE) {
318320
/*
@@ -332,7 +334,7 @@ int mingw_open (const char *filename, int oflags, ...)
332334
va_list args;
333335
unsigned mode;
334336
int fd;
335-
wchar_t wfilename[MAX_PATH];
337+
wchar_t wfilename[MAX_LONG_PATH];
336338

337339
va_start(args, oflags);
338340
mode = va_arg(args, int);
@@ -341,7 +343,7 @@ int mingw_open (const char *filename, int oflags, ...)
341343
if (filename && !strcmp(filename, "/dev/null"))
342344
filename = "nul";
343345

344-
if (xutftowcs_canonical_path(wfilename, filename) < 0)
346+
if (xutftowcs_long_path(wfilename, filename) < 0)
345347
return -1;
346348
fd = _wopen(wfilename, oflags, mode);
347349

@@ -406,66 +408,18 @@ int mingw_fgetc(FILE *stream)
406408
return ch;
407409
}
408410

409-
static struct string_list_item dos_device_names_items[] = {
410-
{ "AUX", NULL},
411-
{ "CLOCK$", NULL},
412-
{ "COM1", NULL},
413-
{ "COM2", NULL},
414-
{ "COM3", NULL},
415-
{ "COM4", NULL},
416-
{ "CON", NULL},
417-
{ "CONERR$", NULL},
418-
{ "CONIN$", NULL},
419-
{ "CONOUT$", NULL},
420-
{ "LPT1", NULL},
421-
{ "LPT2", NULL},
422-
{ "LPT3", NULL},
423-
{ "NUL", NULL},
424-
{ "PRN", NULL},
425-
{ "aux", NULL},
426-
{ "clock$", NULL},
427-
{ "com1", NULL},
428-
{ "com2", NULL},
429-
{ "com3", NULL},
430-
{ "com4", NULL},
431-
{ "con", NULL},
432-
{ "conerr$", NULL},
433-
{ "conin$", NULL},
434-
{ "conout$", NULL},
435-
{ "lpt1", NULL},
436-
{ "lpt2", NULL},
437-
{ "lpt3", NULL},
438-
{ "nul", NULL},
439-
{ "prn", NULL}
440-
};
441-
442-
static struct string_list dos_device_names = {
443-
&dos_device_names_items[0], ARRAY_SIZE(dos_device_names_items),
444-
0, 0, NULL
445-
};
446-
447-
static int is_dos_device_name(const char *filename)
448-
{
449-
return filename && !strchr(filename, '/') &&
450-
string_list_has_string(&dos_device_names, filename);
451-
}
452-
453411
#undef fopen
454412
FILE *mingw_fopen (const char *filename, const char *otype)
455413
{
456414
int hide = 0;
457415
FILE *file;
458-
wchar_t wfilename[MAX_PATH], wotype[4];
459-
460-
if (is_dos_device_name(filename))
461-
return fopen(filename, otype);
462-
416+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
463417
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
464418
basename((char*)filename)[0] == '.')
465419
hide = access(filename, F_OK);
466420
if (filename && !strcmp(filename, "/dev/null"))
467421
filename = "nul";
468-
if (xutftowcs_canonical_path(wfilename, filename) < 0 ||
422+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
469423
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
470424
return NULL;
471425
file = _wfopen(wfilename, wotype);
@@ -474,22 +428,17 @@ FILE *mingw_fopen (const char *filename, const char *otype)
474428
return file;
475429
}
476430

477-
#undef freopen
478431
FILE *mingw_freopen (const char *filename, const char *otype, FILE *stream)
479432
{
480433
int hide = 0;
481434
FILE *file;
482-
wchar_t wfilename[MAX_PATH], wotype[4];
483-
484-
if (is_dos_device_name(filename))
485-
return freopen(filename, otype, stream);
486-
435+
wchar_t wfilename[MAX_LONG_PATH], wotype[4];
487436
if (hide_dotfiles == HIDE_DOTFILES_TRUE &&
488437
basename((char*)filename)[0] == '.')
489438
hide = access(filename, F_OK);
490439
if (filename && !strcmp(filename, "/dev/null"))
491440
filename = "nul";
492-
if (xutftowcs_canonical_path(wfilename, filename) < 0 ||
441+
if (xutftowcs_long_path(wfilename, filename) < 0 ||
493442
xutftowcs(wotype, otype, ARRAY_SIZE(wotype)) < 0)
494443
return NULL;
495444
file = _wfreopen(wfilename, wotype, stream);
@@ -522,25 +471,32 @@ int mingw_fflush(FILE *stream)
522471

523472
int mingw_access(const char *filename, int mode)
524473
{
525-
wchar_t wfilename[MAX_PATH];
526-
if (xutftowcs_canonical_path(wfilename, filename) < 0)
474+
wchar_t wfilename[MAX_LONG_PATH];
475+
if (xutftowcs_long_path(wfilename, filename) < 0)
527476
return -1;
528477
/* X_OK is not supported by the MSVCRT version */
529478
return _waccess(wfilename, mode & ~X_OK);
530479
}
531480

481+
/* cached length of current directory for handle_long_path */
482+
static int current_directory_len = 0;
483+
532484
int mingw_chdir(const char *dirname)
533485
{
486+
int result;
534487
wchar_t wdirname[MAX_PATH];
535-
if (xutftowcs_canonical_path(wdirname, dirname) < 0)
488+
/* SetCurrentDirectoryW doesn't support long paths */
489+
if (xutftowcs_path(wdirname, dirname) < 0)
536490
return -1;
537-
return _wchdir(wdirname);
491+
result = _wchdir(wdirname);
492+
current_directory_len = GetCurrentDirectoryW(0, NULL);
493+
return result;
538494
}
539495

540496
int mingw_chmod(const char *filename, int mode)
541497
{
542-
wchar_t wfilename[MAX_PATH];
543-
if (xutftowcs_canonical_path(wfilename, filename) < 0)
498+
wchar_t wfilename[MAX_LONG_PATH];
499+
if (xutftowcs_long_path(wfilename, filename) < 0)
544500
return -1;
545501
return _wchmod(wfilename, mode);
546502
}
@@ -555,8 +511,8 @@ int mingw_chmod(const char *filename, int mode)
555511
static int do_lstat(int follow, const char *file_name, struct stat *buf)
556512
{
557513
WIN32_FILE_ATTRIBUTE_DATA fdata;
558-
wchar_t wfilename[MAX_PATH];
559-
if (xutftowcs_canonical_path(wfilename, file_name) < 0)
514+
wchar_t wfilename[MAX_LONG_PATH];
515+
if (xutftowcs_long_path(wfilename, file_name) < 0)
560516
return -1;
561517

562518
if (GetFileAttributesExW(wfilename, GetFileExInfoStandard, &fdata)) {
@@ -699,8 +655,8 @@ int mingw_utime (const char *file_name, const struct utimbuf *times)
699655
FILETIME mft, aft;
700656
int fh, rc;
701657
DWORD attrs;
702-
wchar_t wfilename[MAX_PATH];
703-
if (xutftowcs_canonical_path(wfilename, file_name) < 0)
658+
wchar_t wfilename[MAX_LONG_PATH];
659+
if (xutftowcs_long_path(wfilename, file_name) < 0)
704660
return -1;
705661

706662
/* must have write permission */
@@ -748,6 +704,7 @@ unsigned int sleep (unsigned int seconds)
748704
char *mingw_mktemp(char *template)
749705
{
750706
wchar_t wtemplate[MAX_PATH];
707+
/* we need to return the path, thus no long paths here! */
751708
if (xutftowcs_path(wtemplate, template) < 0)
752709
return NULL;
753710
if (!_wmktemp(wtemplate))
@@ -1190,6 +1147,7 @@ static pid_t mingw_spawnve_fd(const char *cmd, const char **argv, char **deltaen
11901147
si.hStdOutput = winansi_get_osfhandle(fhout);
11911148
si.hStdError = winansi_get_osfhandle(fherr);
11921149

1150+
/* executables and the current directory don't support long paths */
11931151
if (xutftowcs_path(wcmd, cmd) < 0)
11941152
return -1;
11951153
if (dir && xutftowcs_path(wdir, dir) < 0)
@@ -1682,9 +1640,9 @@ int mingw_rename(const char *pold, const char *pnew)
16821640
{
16831641
DWORD attrs, gle;
16841642
int tries = 0;
1685-
wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
1686-
if (xutftowcs_canonical_path(wpold, pold) < 0 ||
1687-
xutftowcs_canonical_path(wpnew, pnew) < 0)
1643+
wchar_t wpold[MAX_LONG_PATH], wpnew[MAX_LONG_PATH];
1644+
if (xutftowcs_long_path(wpold, pold) < 0 ||
1645+
xutftowcs_long_path(wpnew, pnew) < 0)
16881646
return -1;
16891647

16901648
/*
@@ -1960,9 +1918,9 @@ int link(const char *oldpath, const char *newpath)
19601918
{
19611919
typedef BOOL (WINAPI *T)(LPCWSTR, LPCWSTR, LPSECURITY_ATTRIBUTES);
19621920
static T create_hard_link = NULL;
1963-
wchar_t woldpath[MAX_PATH], wnewpath[MAX_PATH];
1964-
if (xutftowcs_canonical_path(woldpath, oldpath) < 0 ||
1965-
xutftowcs_canonical_path(wnewpath, newpath) < 0)
1921+
wchar_t woldpath[MAX_LONG_PATH], wnewpath[MAX_LONG_PATH];
1922+
if (xutftowcs_long_path(woldpath, oldpath) < 0 ||
1923+
xutftowcs_long_path(wnewpath, newpath) < 0)
19661924
return -1;
19671925

19681926
if (!create_hard_link) {
@@ -2161,6 +2119,68 @@ int xwcstoutf(char *utf, const wchar_t *wcs, size_t utflen)
21612119
return -1;
21622120
}
21632121

2122+
int handle_long_path(wchar_t *path, int len, int max_path, int expand)
2123+
{
2124+
int result;
2125+
wchar_t buf[MAX_LONG_PATH];
2126+
2127+
/*
2128+
* we don't need special handling if path is relative to the current
2129+
* directory, and current directory + path don't exceed the desired
2130+
* max_path limit. This should cover > 99 % of cases with minimal
2131+
* performance impact (git almost always uses relative paths).
2132+
*/
2133+
if ((len < 2 || (!is_dir_sep(path[0]) && path[1] != ':')) &&
2134+
(current_directory_len + len < max_path))
2135+
return len;
2136+
2137+
/*
2138+
* handle everything else:
2139+
* - absolute paths: "C:\dir\file"
2140+
* - absolute UNC paths: "\\server\share\dir\file"
2141+
* - absolute paths on current drive: "\dir\file"
2142+
* - relative paths on other drive: "X:file"
2143+
* - prefixed paths: "\\?\...", "\\.\..."
2144+
*/
2145+
2146+
/* convert to absolute path using GetFullPathNameW */
2147+
result = GetFullPathNameW(path, MAX_LONG_PATH, buf, NULL);
2148+
if (!result) {
2149+
errno = err_win_to_posix(GetLastError());
2150+
return -1;
2151+
}
2152+
2153+
/*
2154+
* return absolute path if it fits within max_path (even if
2155+
* "cwd + path" doesn't due to '..' components)
2156+
*/
2157+
if (result < max_path) {
2158+
wcscpy(path, buf);
2159+
return result;
2160+
}
2161+
2162+
/* error out if we shouldn't expand the path or buf is too small */
2163+
if (!expand || result >= MAX_LONG_PATH - 6) {
2164+
errno = ENAMETOOLONG;
2165+
return -1;
2166+
}
2167+
2168+
/* prefix full path with "\\?\" or "\\?\UNC\" */
2169+
if (buf[0] == '\\') {
2170+
/* ...unless already prefixed */
2171+
if (buf[1] == '\\' && (buf[2] == '?' || buf[2] == '.'))
2172+
return len;
2173+
2174+
wcscpy(path, L"\\\\?\\UNC\\");
2175+
wcscpy(path + 8, buf + 2);
2176+
return result + 6;
2177+
} else {
2178+
wcscpy(path, L"\\\\?\\");
2179+
wcscpy(path + 4, buf);
2180+
return result + 4;
2181+
}
2182+
}
2183+
21642184
/*
21652185
* Disable MSVCRT command line wildcard expansion (__getmainargs called from
21662186
* mingw startup code, see init.c in mingw runtime).
@@ -2264,4 +2284,7 @@ void mingw_startup()
22642284

22652285
/* initialize Unicode console */
22662286
winansi_init();
2287+
2288+
/* init length of current directory for handle_long_path */
2289+
current_directory_len = GetCurrentDirectoryW(0, NULL);
22672290
}

0 commit comments

Comments
 (0)