Skip to content

Commit f5bf87d

Browse files
committed
Merge pull request #2504 from dscho/access-repo-via-junction
Handle `git add <file>` where <file> traverses an NTFS junction
2 parents 8a05bcf + 1134979 commit f5bf87d

File tree

7 files changed

+110
-0
lines changed

7 files changed

+110
-0
lines changed

abspath.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,9 @@ static char *strbuf_realpath_1(struct strbuf *resolved, const char *path,
9191
goto error_out;
9292
}
9393

94+
if (platform_strbuf_realpath(resolved, path))
95+
return resolved->buf;
96+
9497
strbuf_addstr(&remaining, path);
9598
get_root_part(resolved, &remaining);
9699

compat/mingw.c

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

1129+
char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path)
1130+
{
1131+
wchar_t wpath[MAX_PATH];
1132+
HANDLE h;
1133+
DWORD ret;
1134+
int len;
1135+
const char *last_component = NULL;
1136+
char *append = NULL;
1137+
1138+
if (xutftowcs_path(wpath, path) < 0)
1139+
return NULL;
1140+
1141+
h = CreateFileW(wpath, 0,
1142+
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
1143+
OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
1144+
1145+
/*
1146+
* strbuf_realpath() allows the last path component to not exist. If
1147+
* that is the case, now it's time to try without last component.
1148+
*/
1149+
if (h == INVALID_HANDLE_VALUE &&
1150+
GetLastError() == ERROR_FILE_NOT_FOUND) {
1151+
/* cut last component off of `wpath` */
1152+
wchar_t *p = wpath + wcslen(wpath);
1153+
1154+
while (p != wpath)
1155+
if (*(--p) == L'/' || *p == L'\\')
1156+
break; /* found start of last component */
1157+
1158+
if (p != wpath && (last_component = find_last_dir_sep(path))) {
1159+
append = xstrdup(last_component + 1); /* skip directory separator */
1160+
/*
1161+
* Do not strip the trailing slash at the drive root, otherwise
1162+
* the path would be e.g. `C:` (which resolves to the
1163+
* _current_ directory on that drive).
1164+
*/
1165+
if (p[-1] == L':')
1166+
p[1] = L'\0';
1167+
else
1168+
*p = L'\0';
1169+
h = CreateFileW(wpath, 0, FILE_SHARE_READ |
1170+
FILE_SHARE_WRITE | FILE_SHARE_DELETE,
1171+
NULL, OPEN_EXISTING,
1172+
FILE_FLAG_BACKUP_SEMANTICS, NULL);
1173+
}
1174+
}
1175+
1176+
if (h == INVALID_HANDLE_VALUE) {
1177+
realpath_failed:
1178+
FREE_AND_NULL(append);
1179+
return NULL;
1180+
}
1181+
1182+
ret = GetFinalPathNameByHandleW(h, wpath, ARRAY_SIZE(wpath), 0);
1183+
CloseHandle(h);
1184+
if (!ret || ret >= ARRAY_SIZE(wpath))
1185+
goto realpath_failed;
1186+
1187+
len = wcslen(wpath) * 3;
1188+
strbuf_grow(resolved, len);
1189+
len = xwcstoutf(resolved->buf, normalize_ntpath(wpath), len);
1190+
if (len < 0)
1191+
goto realpath_failed;
1192+
resolved->len = len;
1193+
1194+
if (append) {
1195+
/* Use forward-slash, like `normalize_ntpath()` */
1196+
strbuf_complete(resolved, '/');
1197+
strbuf_addstr(resolved, append);
1198+
FREE_AND_NULL(append);
1199+
}
1200+
1201+
return resolved->buf;
1202+
1203+
}
1204+
11291205
char *mingw_getcwd(char *pointer, int len)
11301206
{
11311207
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
@@ -456,6 +456,9 @@ int mingw_is_mount_point(struct strbuf *path);
456456
#define PATH_SEP ';'
457457
char *mingw_query_user_email(void);
458458
#define query_user_email mingw_query_user_email
459+
struct strbuf;
460+
char *mingw_strbuf_realpath(struct strbuf *resolved, const char *path);
461+
#define platform_strbuf_realpath mingw_strbuf_realpath
459462
#if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800)
460463
#define PRIuMAX "I64u"
461464
#define PRId64 "I64d"

git-compat-util.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -576,6 +576,10 @@ static inline int git_has_dir_sep(const char *path)
576576
#define query_user_email() NULL
577577
#endif
578578

579+
#ifndef platform_strbuf_realpath
580+
#define platform_strbuf_realpath(resolved, path) NULL
581+
#endif
582+
579583
#ifdef __TANDEM
580584
#include <floss.h(floss_execl,floss_execlp,floss_execv,floss_execvp)>
581585
#include <floss.h(floss_getpwuid)>

t/t0060-path-utils.sh

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,12 @@ test_expect_success SYMLINKS 'real path works on symlinks' '
237237
test "$sym" = "$(test-tool path-utils real_path "$dir2/syml")"
238238
'
239239

240+
test_expect_success MINGW 'real path works near drive root' '
241+
# we need a non-existing path at the drive root; simply skip if C:/xyz exists
242+
test -e C:/xyz ||
243+
test C:/xyz = $(test-tool path-utils real_path C:/xyz)
244+
'
245+
240246
test_expect_success SYMLINKS 'prefix_path works with absolute paths to work tree symlinks' '
241247
ln -s target symlink &&
242248
test "$(test-tool path-utils prefix_path prefix "$(pwd)/symlink")" = "symlink"

t/t3700-add.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,15 @@ test_expect_success CASE_INSENSITIVE_FS 'path is case-insensitive' '
498498
git add "$downcased"
499499
'
500500

501+
test_expect_success MINGW 'can add files via NTFS junctions' '
502+
test_when_finished "cmd //c rmdir junction && rm -rf target" &&
503+
test_create_repo target &&
504+
cmd //c "mklink /j junction target" &&
505+
>target/via-junction &&
506+
git -C junction add "$(pwd)/junction/via-junction" &&
507+
echo via-junction >expect &&
508+
git -C target diff --cached --name-only >actual &&
509+
test_cmp expect actual
510+
'
511+
501512
test_done

t/t5601-clone.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,13 @@ test_expect_success 'clone respects GIT_WORK_TREE' '
7171
7272
'
7373

74+
test_expect_success CASE_INSENSITIVE_FS 'core.worktree is not added due to path case' '
75+
76+
mkdir UPPERCASE &&
77+
git clone src "$(pwd)/uppercase" &&
78+
test "unset" = "$(git -C UPPERCASE config --default unset core.worktree)"
79+
'
80+
7481
test_expect_success 'clone from hooks' '
7582
7683
test_create_repo r0 &&

0 commit comments

Comments
 (0)