Skip to content

Commit

Permalink
mingw: implement a platform-specific strbuf_realpath()
Browse files Browse the repository at this point in the history
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 <johannes.schindelin@gmx.de>
dscho committed Nov 20, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent cdb733e commit 2b8d16d
Showing 5 changed files with 95 additions and 1 deletion.
76 changes: 76 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
@@ -1125,6 +1125,82 @@ struct tm *localtime_r(const time_t *timep, struct tm *result)
}
#endif

char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path)
{
wchar_t wpath[MAX_PATH];
HANDLE h;
DWORD ret;
int len;
const char *last_component = NULL;
char *append = NULL;

if (xutftowcs_path(wpath, path) < 0)
return NULL;

h = CreateFileW(wpath, 0,
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

/*
* strbuf_realpath() allows the last path component to not exist. If
* that is the case, now it's time to try without last component.
*/
if (h == INVALID_HANDLE_VALUE &&
GetLastError() == ERROR_FILE_NOT_FOUND) {
/* cut last component off of `wpath` */
wchar_t *p = wpath + wcslen(wpath);

while (p != wpath)
if (*(--p) == L'/' || *p == L'\\')
break; /* found start of last component */

if (p != wpath && (last_component = find_last_dir_sep(path))) {
append = xstrdup(last_component + 1); /* skip directory separator */
/*
* Do not strip the trailing slash at the drive root, otherwise
* the path would be e.g. `C:` (which resolves to the
* _current_ directory on that drive).
*/
if (p[-1] == L':')
p[1] = L'\0';
else
*p = L'\0';
h = CreateFileW(wpath, 0, FILE_SHARE_READ |
FILE_SHARE_WRITE | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING,
FILE_FLAG_BACKUP_SEMANTICS, NULL);
}
}

if (h == INVALID_HANDLE_VALUE) {
realpath_failed:
FREE_AND_NULL(append);
return NULL;
}

ret = GetFinalPathNameByHandleW(h, wpath, ARRAY_SIZE(wpath), 0);
CloseHandle(h);
if (!ret || ret >= ARRAY_SIZE(wpath))
goto realpath_failed;

len = wcslen(wpath) * 3;
strbuf_grow(resolved, len);
len = xwcstoutf(resolved->buf, normalize_ntpath(wpath), len);
if (len < 0)
goto realpath_failed;
resolved->len = len;

if (append) {
/* Use forward-slash, like `normalize_ntpath()` */
strbuf_complete(resolved, '/');
strbuf_addstr(resolved, append);
FREE_AND_NULL(append);
}

return resolved->buf;

}

char *mingw_getcwd(char *pointer, int len)
{
wchar_t cwd[MAX_PATH], wpointer[MAX_PATH];
3 changes: 3 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
@@ -457,6 +457,9 @@ static inline void convert_slashes(char *path)
#define PATH_SEP ';'
char *mingw_query_user_email(void);
#define query_user_email mingw_query_user_email
struct strbuf;
char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path);
#define platform_strbuf_realpath mingw_strbuf_realpath
#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
#define PRIuMAX "I64u"
#define PRId64 "I64d"
8 changes: 8 additions & 0 deletions t/t0060-path-utils.sh
Original file line number Diff line number Diff line change
@@ -282,6 +282,14 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
test_cmp expect actual
'

test_expect_success MINGW 'real path works near drive root' '
# we need a non-existing path at the drive root; simply skip if C:/xyz exists
if test ! -e C:/xyz
then
test C:/xyz = $(test-tool path-utils real_path C:/xyz)
fi
'

test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' '
ln -s target symlink &&
echo "symlink" >expect &&
2 changes: 1 addition & 1 deletion t/t3700-add.sh
Original file line number Diff line number Diff line change
@@ -506,7 +506,7 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
git add "$downcased"
'

test_expect_failure MINGW 'can add files via NTFS junctions' '
test_expect_success MINGW 'can add files via NTFS junctions' '
test_when_finished "cmd //c rmdir junction && rm -rf target" &&
test_create_repo target &&
cmd //c "mklink /j junction target" &&
7 changes: 7 additions & 0 deletions t/t5601-clone.sh
Original file line number Diff line number Diff line change
@@ -71,6 +71,13 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
'

test_expect_success CASE_INSENSITIVE_FS 'core.worktree is not added due to path case' '
mkdir UPPERCASE &&
git clone src "$(pwd)/uppercase" &&
test "unset" = "$(git -C UPPERCASE config --default unset core.worktree)"
'

test_expect_success 'clone from hooks' '
test_create_repo r0 &&

0 comments on commit 2b8d16d

Please sign in to comment.