Skip to content

Commit

Permalink
Merge pull request #2504 from dscho/access-repo-via-junction
Browse files Browse the repository at this point in the history
Handle `git add <file>` where <file> traverses an NTFS junction
  • Loading branch information
dscho committed Sep 26, 2024
2 parents 3906d6c + 0162275 commit 122f7a8
Show file tree
Hide file tree
Showing 7 changed files with 112 additions and 0 deletions.
3 changes: 3 additions & 0 deletions abspath.c
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,9 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
goto error_out;
}

if (platform_strbuf_realpath(resolved, path))
return resolved->buf;

strbuf_addstr(&remaining, path);
get_root_part(resolved, &remaining);

Expand Down
76 changes: 76 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -1148,6 +1148,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];
Expand Down
3 changes: 3 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,9 @@ int mingw_is_mount_point(struct strbuf *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"
Expand Down
4 changes: 4 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -646,6 +646,10 @@ static inline int git_has_dir_sep(const char *path)
#define query_user_email() NULL
#endif

#ifndef platform_strbuf_realpath
#define platform_strbuf_realpath(resolved, path) NULL
#endif

#ifdef __TANDEM
#include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)>
#include <floss.h(floss_getpwuid)>
Expand Down
8 changes: 8 additions & 0 deletions t/t0060-path-utils.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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 &&
Expand Down
11 changes: 11 additions & 0 deletions t/t3700-add.sh
Original file line number Diff line number Diff line change
Expand Up @@ -549,4 +549,15 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
git add "$downcased"
'

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" &&
>target/via-junction &&
git -C junction add "$(pwd)/junction/via-junction" &&
echo via-junction >expect &&
git -C target diff --cached --name-only >actual &&
test_cmp expect actual
'

test_done
7 changes: 7 additions & 0 deletions t/t5601-clone.sh
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,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 &&
Expand Down

0 comments on commit 122f7a8

Please sign in to comment.