Skip to content

Commit 7321cff

Browse files
authored
Merge pull request libgit2#4713 from libgit2/ethomson/win_symlinks
Support symlinks on Windows when core.symlinks=true
2 parents 9189a66 + da500cc commit 7321cff

File tree

9 files changed

+331
-198
lines changed

9 files changed

+331
-198
lines changed

src/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ FILE(GLOB SRC_H
423423

424424
# On Windows use specific platform sources
425425
IF (WIN32 AND NOT CYGWIN)
426-
ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501)
426+
ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0600)
427427

428428
IF(MSVC)
429429
SET(WIN_RC "win32/git2.rc")

src/repository.c

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -944,18 +944,20 @@ static int load_config(
944944
git_buf config_path = GIT_BUF_INIT;
945945
git_config *cfg = NULL;
946946

947-
assert(repo && out);
947+
assert(out);
948948

949949
if ((error = git_config_new(&cfg)) < 0)
950950
return error;
951951

952-
if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0)
953-
error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0);
952+
if (repo) {
953+
if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0)
954+
error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0);
954955

955-
if (error && error != GIT_ENOTFOUND)
956-
goto on_error;
956+
if (error && error != GIT_ENOTFOUND)
957+
goto on_error;
957958

958-
git_buf_dispose(&config_path);
959+
git_buf_dispose(&config_path);
960+
}
959961

960962
if (global_config_path != NULL &&
961963
(error = git_config_add_file_ondisk(
@@ -1411,24 +1413,56 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
14111413

14121414
static bool are_symlinks_supported(const char *wd_path)
14131415
{
1416+
git_config *config = NULL;
14141417
git_buf path = GIT_BUF_INIT;
14151418
int fd;
14161419
struct stat st;
1417-
int symlinks_supported = -1;
1420+
bool symlinks = false;
1421+
1422+
/*
1423+
* To emulate Git for Windows, symlinks on Windows must be explicitly
1424+
* opted-in. We examine the system configuration for a core.symlinks
1425+
* set to true. If found, we then examine the filesystem to see if
1426+
* symlinks are _actually_ supported by the current user. If that is
1427+
* _not_ set, then we do not test or enable symlink support.
1428+
*/
1429+
#ifdef GIT_WIN32
1430+
git_buf global_buf = GIT_BUF_INIT;
1431+
git_buf xdg_buf = GIT_BUF_INIT;
1432+
git_buf system_buf = GIT_BUF_INIT;
1433+
git_buf programdata_buf = GIT_BUF_INIT;
1434+
1435+
git_config_find_global(&global_buf);
1436+
git_config_find_xdg(&xdg_buf);
1437+
git_config_find_system(&system_buf);
1438+
git_config_find_programdata(&programdata_buf);
1439+
1440+
if (load_config(&config, NULL,
1441+
path_unless_empty(&global_buf),
1442+
path_unless_empty(&xdg_buf),
1443+
path_unless_empty(&system_buf),
1444+
path_unless_empty(&programdata_buf)) < 0)
1445+
goto done;
1446+
1447+
if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks)
1448+
goto done;
1449+
#endif
14181450

14191451
if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 ||
1420-
p_close(fd) < 0 ||
1421-
p_unlink(path.ptr) < 0 ||
1422-
p_symlink("testing", path.ptr) < 0 ||
1423-
p_lstat(path.ptr, &st) < 0)
1424-
symlinks_supported = false;
1425-
else
1426-
symlinks_supported = (S_ISLNK(st.st_mode) != 0);
1452+
p_close(fd) < 0 ||
1453+
p_unlink(path.ptr) < 0 ||
1454+
p_symlink("testing", path.ptr) < 0 ||
1455+
p_lstat(path.ptr, &st) < 0)
1456+
goto done;
1457+
1458+
symlinks = (S_ISLNK(st.st_mode) != 0);
14271459

14281460
(void)p_unlink(path.ptr);
1429-
git_buf_dispose(&path);
14301461

1431-
return symlinks_supported;
1462+
done:
1463+
git_buf_dispose(&path);
1464+
git_config_free(config);
1465+
return symlinks;
14321466
}
14331467

14341468
static int create_empty_file(const char *path, mode_t mode)

src/win32/posix_w32.c

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,16 @@
2929
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
3030
#endif
3131

32+
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
33+
# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02
34+
#endif
35+
3236
/* Allowable mode bits on Win32. Using mode bits that are not supported on
3337
* Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
3438
* so we simply remove them.
3539
*/
3640
#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
3741

38-
/* GetFinalPathNameByHandleW signature */
39-
typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
40-
4142
unsigned long git_win32__createfile_sharemode =
4243
FILE_SHARE_READ | FILE_SHARE_WRITE;
4344
int git_win32__retries = 10;
@@ -393,12 +394,20 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
393394
return (int)bufsiz;
394395
}
395396

396-
int p_symlink(const char *old, const char *new)
397+
int p_symlink(const char *target, const char *path)
397398
{
398-
/* Real symlinks on NTFS require admin privileges. Until this changes,
399-
* libgit2 just creates a text file with the link target in the contents.
400-
*/
401-
return git_futils_fake_symlink(old, new);
399+
git_win32_path target_w, path_w;
400+
wchar_t *target_p;
401+
402+
if (git_win32_path_from_utf8(path_w, path) < 0 ||
403+
git__utf8_to_16(target_w, MAX_PATH, target) < 0)
404+
return -1;
405+
406+
if (!CreateSymbolicLinkW(path_w, target_w,
407+
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
408+
return -1;
409+
410+
return 0;
402411
}
403412

404413
struct open_opts {
@@ -598,40 +607,13 @@ int p_getcwd(char *buffer_out, size_t size)
598607
return 0;
599608
}
600609

601-
/*
602-
* Returns the address of the GetFinalPathNameByHandleW function.
603-
* This function is available on Windows Vista and higher.
604-
*/
605-
static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
606-
{
607-
static PFGetFinalPathNameByHandleW pFunc = NULL;
608-
PFGetFinalPathNameByHandleW toReturn = pFunc;
609-
610-
if (!toReturn) {
611-
HMODULE hModule = GetModuleHandleW(L"kernel32");
612-
613-
if (hModule)
614-
toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
615-
616-
pFunc = toReturn;
617-
}
618-
619-
assert(toReturn);
620-
621-
return toReturn;
622-
}
623-
624610
static int getfinalpath_w(
625611
git_win32_path dest,
626612
const wchar_t *path)
627613
{
628-
PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
629614
HANDLE hFile;
630615
DWORD dwChars;
631616

632-
if (!pgfp)
633-
return -1;
634-
635617
/* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
636618
* specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
637619
* target of the link. */
@@ -642,7 +624,7 @@ static int getfinalpath_w(
642624
return -1;
643625

644626
/* Call GetFinalPathNameByHandle */
645-
dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
627+
dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
646628
CloseHandle(hFile);
647629

648630
if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)

0 commit comments

Comments
 (0)