Skip to content

Commit b680f09

Browse files
committed
mingw: implement a platform-specific strbuf_realpath()
There is a Win32 API function to resolve symbolic links, and we can use that instead of resolving them manually. Even better, this function also resolves NTFS junction points (which are somewhat similar to bind mounts). This fixes #2481. Signed-off-by: Johannes Schindelin <[email protected]>
1 parent ae71902 commit b680f09

File tree

5 files changed

+95
-1
lines changed

5 files changed

+95
-1
lines changed

compat/mingw.c

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,82 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
11401140
}
11411141
#endif
11421142

1143+
char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path)
1144+
{
1145+
wchar_t wpath[MAX_PATH];
1146+
HANDLE h;
1147+
DWORD ret;
1148+
int len;
1149+
const char *last_component = NULL;
1150+
char *append = NULL;
1151+
1152+
if (xutftowcs_path(wpath, path) < 0)
1153+
return NULL;
1154+
1155+
h = CreateFileW(wpath, 0,
1156+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
1157+
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
1158+
1159+
/*
1160+
* strbuf_realpath() allows the last path component to not exist. If
1161+
* that is the case, now it's time to try without last component.
1162+
*/
1163+
if (h == INVALID_HANDLE_VALUE &&
1164+
GetLastError() == ERROR_FILE_NOT_FOUND) {
1165+
/* cut last component off of `wpath` */
1166+
wchar_t *p = wpath + wcslen(wpath);
1167+
1168+
while (p != wpath)
1169+
if (*(--p) == L'/' || *p == L'\\')
1170+
break; /* found start of last component */
1171+
1172+
if (p != wpath && (last_component = find_last_dir_sep(path))) {
1173+
append = xstrdup(last_component + 1); /* skip directory separator */
1174+
/*
1175+
* Do not strip the trailing slash at the drive root, otherwise
1176+
* the path would be e.g. `C:` (which resolves to the
1177+
* _current_ directory on that drive).
1178+
*/
1179+
if (p[-1] == L':')
1180+
p[1] = L'\0';
1181+
else
1182+
*p = L'\0';
1183+
h = CreateFileW(wpath, 0, FILE_SHARE_READ |
1184+
FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1185+
NULL, OPEN_EXISTING,
1186+
FILE_FLAG_BACKUP_SEMANTICS, NULL);
1187+
}
1188+
}
1189+
1190+
if (h == INVALID_HANDLE_VALUE) {
1191+
realpath_failed:
1192+
FREE_AND_NULL(append);
1193+
return NULL;
1194+
}
1195+
1196+
ret = GetFinalPathNameByHandleW(h, wpath, ARRAY_SIZE(wpath), 0);
1197+
CloseHandle(h);
1198+
if (!ret || ret >= ARRAY_SIZE(wpath))
1199+
goto realpath_failed;
1200+
1201+
len = wcslen(wpath) * 3;
1202+
strbuf_grow(resolved, len);
1203+
len = xwcstoutf(resolved->buf, normalize_ntpath(wpath), len);
1204+
if (len < 0)
1205+
goto realpath_failed;
1206+
resolved->len = len;
1207+
1208+
if (append) {
1209+
/* Use forward-slash, like `normalize_ntpath()` */
1210+
strbuf_complete(resolved, '/');
1211+
strbuf_addstr(resolved, append);
1212+
FREE_AND_NULL(append);
1213+
}
1214+
1215+
return resolved->buf;
1216+
1217+
}
1218+
11431219
char *mingw_getcwd(char *pointer, int len)
11441220
{
11451221
wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];

compat/mingw.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -457,6 +457,9 @@ static inline void convert_slashes(char *path)
457457
#define PATH_SEP ';'
458458
char *mingw_query_user_email(void);
459459
#define query_user_email mingw_query_user_email
460+
struct strbuf;
461+
char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path);
462+
#define platform_strbuf_realpath mingw_strbuf_realpath
460463
#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
461464
#define PRIuMAX "I64u"
462465
#define PRId64 "I64d"

t/t0060-path-utils.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,14 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
282282
test_cmp expect actual
283283
'
284284

285+
test_expect_success MINGW 'real path works near drive root' '
286+
# we need a non-existing path at the drive root; simply skip if C:/xyz exists
287+
if test ! -e C:/xyz
288+
then
289+
test C:/xyz = $(test-tool path-utils real_path C:/xyz)
290+
fi
291+
'
292+
285293
test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' '
286294
ln -s target symlink &&
287295
echo "symlink" >expect &&

t/t3700-add.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -549,7 +549,7 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
549549
git add "$downcased"
550550
'
551551

552-
test_expect_failure MINGW 'can add files via NTFS junctions' '
552+
test_expect_success MINGW 'can add files via NTFS junctions' '
553553
test_when_finished "cmd //c rmdir junction && rm -rf target" &&
554554
test_create_repo target &&
555555
cmd //c "mklink /j junction target" &&

t/t5601-clone.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
7878
7979
'
8080

81+
test_expect_success CASE_INSENSITIVE_FS 'core.worktree is not added due to path case' '
82+
83+
mkdir UPPERCASE &&
84+
git clone src "$(pwd)/uppercase" &&
85+
test "unset" = "$(git -C UPPERCASE config --default unset core.worktree)"
86+
'
87+
8188
test_expect_success 'clone from hooks' '
8289
8390
test_create_repo r0 &&

0 commit comments

Comments
 (0)