Unverified Commit 7321cff0 by Edward Thomson Committed by GitHub

Merge pull request #4713 from libgit2/ethomson/win_symlinks

Support symlinks on Windows when core.symlinks=true
parents 9189a66a da500cc6
......@@ -423,7 +423,7 @@ FILE(GLOB SRC_H
# On Windows use specific platform sources
IF (WIN32 AND NOT CYGWIN)
ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0501)
ADD_DEFINITIONS(-DWIN32 -D_WIN32_WINNT=0x0600)
IF(MSVC)
SET(WIN_RC "win32/git2.rc")
......
......@@ -944,18 +944,20 @@ static int load_config(
git_buf config_path = GIT_BUF_INIT;
git_config *cfg = NULL;
assert(repo && out);
assert(out);
if ((error = git_config_new(&cfg)) < 0)
return error;
if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0)
error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0);
if (repo) {
if ((error = git_repository_item_path(&config_path, repo, GIT_REPOSITORY_ITEM_CONFIG)) == 0)
error = git_config_add_file_ondisk(cfg, config_path.ptr, GIT_CONFIG_LEVEL_LOCAL, repo, 0);
if (error && error != GIT_ENOTFOUND)
goto on_error;
if (error && error != GIT_ENOTFOUND)
goto on_error;
git_buf_dispose(&config_path);
git_buf_dispose(&config_path);
}
if (global_config_path != NULL &&
(error = git_config_add_file_ondisk(
......@@ -1411,24 +1413,56 @@ static bool is_filesystem_case_insensitive(const char *gitdir_path)
static bool are_symlinks_supported(const char *wd_path)
{
git_config *config = NULL;
git_buf path = GIT_BUF_INIT;
int fd;
struct stat st;
int symlinks_supported = -1;
bool symlinks = false;
/*
* To emulate Git for Windows, symlinks on Windows must be explicitly
* opted-in. We examine the system configuration for a core.symlinks
* set to true. If found, we then examine the filesystem to see if
* symlinks are _actually_ supported by the current user. If that is
* _not_ set, then we do not test or enable symlink support.
*/
#ifdef GIT_WIN32
git_buf global_buf = GIT_BUF_INIT;
git_buf xdg_buf = GIT_BUF_INIT;
git_buf system_buf = GIT_BUF_INIT;
git_buf programdata_buf = GIT_BUF_INIT;
git_config_find_global(&global_buf);
git_config_find_xdg(&xdg_buf);
git_config_find_system(&system_buf);
git_config_find_programdata(&programdata_buf);
if (load_config(&config, NULL,
path_unless_empty(&global_buf),
path_unless_empty(&xdg_buf),
path_unless_empty(&system_buf),
path_unless_empty(&programdata_buf)) < 0)
goto done;
if (git_config_get_bool(&symlinks, config, "core.symlinks") < 0 || !symlinks)
goto done;
#endif
if ((fd = git_futils_mktmp(&path, wd_path, 0666)) < 0 ||
p_close(fd) < 0 ||
p_unlink(path.ptr) < 0 ||
p_symlink("testing", path.ptr) < 0 ||
p_lstat(path.ptr, &st) < 0)
symlinks_supported = false;
else
symlinks_supported = (S_ISLNK(st.st_mode) != 0);
p_close(fd) < 0 ||
p_unlink(path.ptr) < 0 ||
p_symlink("testing", path.ptr) < 0 ||
p_lstat(path.ptr, &st) < 0)
goto done;
symlinks = (S_ISLNK(st.st_mode) != 0);
(void)p_unlink(path.ptr);
git_buf_dispose(&path);
return symlinks_supported;
done:
git_buf_dispose(&path);
git_config_free(config);
return symlinks;
}
static int create_empty_file(const char *path, mode_t mode)
......
......@@ -29,15 +29,16 @@
#define IO_REPARSE_TAG_SYMLINK (0xA000000CL)
#endif
#ifndef SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE
# define SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE 0x02
#endif
/* Allowable mode bits on Win32. Using mode bits that are not supported on
* Win32 (eg S_IRWXU) is generally ignored, but Wine warns loudly about it
* so we simply remove them.
*/
#define WIN32_MODE_MASK (_S_IREAD | _S_IWRITE)
/* GetFinalPathNameByHandleW signature */
typedef DWORD(WINAPI *PFGetFinalPathNameByHandleW)(HANDLE, LPWSTR, DWORD, DWORD);
unsigned long git_win32__createfile_sharemode =
FILE_SHARE_READ | FILE_SHARE_WRITE;
int git_win32__retries = 10;
......@@ -393,12 +394,20 @@ int p_readlink(const char *path, char *buf, size_t bufsiz)
return (int)bufsiz;
}
int p_symlink(const char *old, const char *new)
int p_symlink(const char *target, const char *path)
{
/* Real symlinks on NTFS require admin privileges. Until this changes,
* libgit2 just creates a text file with the link target in the contents.
*/
return git_futils_fake_symlink(old, new);
git_win32_path target_w, path_w;
wchar_t *target_p;
if (git_win32_path_from_utf8(path_w, path) < 0 ||
git__utf8_to_16(target_w, MAX_PATH, target) < 0)
return -1;
if (!CreateSymbolicLinkW(path_w, target_w,
SYMBOLIC_LINK_FLAG_ALLOW_UNPRIVILEGED_CREATE))
return -1;
return 0;
}
struct open_opts {
......@@ -598,40 +607,13 @@ int p_getcwd(char *buffer_out, size_t size)
return 0;
}
/*
* Returns the address of the GetFinalPathNameByHandleW function.
* This function is available on Windows Vista and higher.
*/
static PFGetFinalPathNameByHandleW get_fpnbyhandle(void)
{
static PFGetFinalPathNameByHandleW pFunc = NULL;
PFGetFinalPathNameByHandleW toReturn = pFunc;
if (!toReturn) {
HMODULE hModule = GetModuleHandleW(L"kernel32");
if (hModule)
toReturn = (PFGetFinalPathNameByHandleW)GetProcAddress(hModule, "GetFinalPathNameByHandleW");
pFunc = toReturn;
}
assert(toReturn);
return toReturn;
}
static int getfinalpath_w(
git_win32_path dest,
const wchar_t *path)
{
PFGetFinalPathNameByHandleW pgfp = get_fpnbyhandle();
HANDLE hFile;
DWORD dwChars;
if (!pgfp)
return -1;
/* Use FILE_FLAG_BACKUP_SEMANTICS so we can open a directory. Do not
* specify FILE_FLAG_OPEN_REPARSE_POINT; we want to open a handle to the
* target of the link. */
......@@ -642,7 +624,7 @@ static int getfinalpath_w(
return -1;
/* Call GetFinalPathNameByHandle */
dwChars = pgfp(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
dwChars = GetFinalPathNameByHandleW(hFile, dest, GIT_WIN_PATH_UTF16, FILE_NAME_NORMALIZED);
CloseHandle(hFile);
if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16)
......
......@@ -2,6 +2,7 @@
#include "git2/repository.h"
#include "git2/sys/index.h"
#include "fileops.h"
#include "repository.h"
static git_repository *g_repo;
static git_index *g_index;
......@@ -184,28 +185,35 @@ static void ensure_workdir(const char *path, int mode, const char *oid_str)
ensure_workdir_oid(path, oid_str);
}
static void ensure_workdir_link(const char *path, const char *target)
static void ensure_workdir_link(
git_repository *repo,
const char *path,
const char *target)
{
#ifdef GIT_WIN32
ensure_workdir_contents(path, target);
#else
git_buf fullpath = GIT_BUF_INIT;
char actual[1024];
struct stat st;
int len;
int symlinks;
cl_git_pass(
git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
cl_git_pass(git_repository__cvar(&symlinks, repo, GIT_CVAR_SYMLINKS));
cl_git_pass(p_lstat(git_buf_cstr(&fullpath), &st));
cl_assert(S_ISLNK(st.st_mode));
if (!symlinks) {
ensure_workdir_contents(path, target);
} else {
git_buf fullpath = GIT_BUF_INIT;
char actual[1024];
struct stat st;
int len;
cl_assert((len = p_readlink(git_buf_cstr(&fullpath), actual, 1024)) > 0);
actual[len] = '\0';
cl_assert(strcmp(actual, target) == 0);
cl_git_pass(
git_buf_joinpath(&fullpath, git_repository_workdir(g_repo), path));
git_buf_dispose(&fullpath);
#endif
cl_git_pass(p_lstat(git_buf_cstr(&fullpath), &st));
cl_assert(S_ISLNK(st.st_mode));
cl_assert((len = p_readlink(git_buf_cstr(&fullpath), actual, 1024)) > 0);
actual[len] = '\0';
cl_assert(strcmp(actual, target) == 0);
git_buf_dispose(&fullpath);
}
}
void test_checkout_conflict__ignored(void)
......@@ -415,8 +423,8 @@ void test_checkout_conflict__links(void)
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
/* Conflicts with links always keep the ours side (even with -Xtheirs) */
ensure_workdir_link("link-1", LINK_OURS_TARGET);
ensure_workdir_link("link-2", LINK_OURS_TARGET);
ensure_workdir_link(g_repo, "link-1", LINK_OURS_TARGET);
ensure_workdir_link(g_repo, "link-2", LINK_OURS_TARGET);
}
void test_checkout_conflict__add_add(void)
......@@ -684,7 +692,7 @@ void test_checkout_conflict__renames(void)
void test_checkout_conflict__rename_keep_ours(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
struct checkout_index_entry checkout_index_entries[] = {
{ 0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e", 0, "0a-no-change.txt" },
{ 0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6", 0, "0b-duplicated-in-ours.txt" },
......@@ -728,122 +736,122 @@ void test_checkout_conflict__rename_keep_ours(void)
{ 0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11", 2, "7-both-renamed.txt" },
{ 0100644, "b69fe837e4cecfd4c9a40cdca7c138468687df07", 3, "7-both-renamed.txt" }
};
struct checkout_name_entry checkout_name_entries[] = {
{
"3a-renamed-in-ours-deleted-in-theirs.txt",
"3a-newname-in-ours-deleted-in-theirs.txt",
""
},
{
"3b-renamed-in-theirs-deleted-in-ours.txt",
"",
"3b-newname-in-theirs-deleted-in-ours.txt"
},
{
"4a-renamed-in-ours-added-in-theirs.txt",
"4a-newname-in-ours-added-in-theirs.txt",
""
},
{
"4b-renamed-in-theirs-added-in-ours.txt",
"",
"4b-newname-in-theirs-added-in-ours.txt"
},
{
"5a-renamed-in-ours-added-in-theirs.txt",
"5a-newname-in-ours-added-in-theirs.txt",
"5a-renamed-in-ours-added-in-theirs.txt"
},
{
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-renamed-in-theirs-added-in-ours.txt",
"5b-newname-in-theirs-added-in-ours.txt"
},
{
"6-both-renamed-1-to-2.txt",
"6-both-renamed-1-to-2-ours.txt",
"6-both-renamed-1-to-2-theirs.txt"
},
{
"7-both-renamed-side-1.txt",
"7-both-renamed.txt",
"7-both-renamed-side-1.txt"
},
{
"7-both-renamed-side-2.txt",
"7-both-renamed-side-2.txt",
"7-both-renamed.txt"
}
};
opts.checkout_strategy |= GIT_CHECKOUT_SAFE | GIT_CHECKOUT_USE_OURS;
create_index(checkout_index_entries, 41);
create_index_names(checkout_name_entries, 9);
cl_git_pass(git_index_write(g_index));
cl_git_pass(git_checkout_index(g_repo, g_index, &opts));
ensure_workdir("0a-no-change.txt",
0100644, "68c6c84b091926c7d90aa6a79b2bc3bb6adccd8e");
ensure_workdir("0b-duplicated-in-ours.txt",
0100644, "f0ce2b8e4986084d9b308fb72709e414c23eb5e6");
ensure_workdir("0b-rewritten-in-ours.txt",
0100644, "e376fbdd06ebf021c92724da9f26f44212734e3e");
ensure_workdir("0c-duplicated-in-theirs.txt",
0100644, "2f56120107d680129a5d9791b521cb1e73a2ed31");
ensure_workdir("0c-rewritten-in-theirs.txt",
0100644, "efc9121fdedaf08ba180b53ebfbcf71bd488ed09");
ensure_workdir("1a-newname-in-ours-edited-in-theirs.txt",
0100644, "0d872f8e871a30208305978ecbf9e66d864f1638");
ensure_workdir("1a-newname-in-ours.txt",
0100644, "d0d4594e16f2e19107e3fa7ea63e7aaaff305ffb");
ensure_workdir("1b-newname-in-theirs-edited-in-ours.txt",
0100644, "ed9523e62e453e50dd9be1606af19399b96e397a");
ensure_workdir("1b-newname-in-theirs.txt",
0100644, "2b5f1f181ee3b58ea751f5dd5d8f9b445520a136");
ensure_workdir("2-newname-in-both.txt",
0100644, "178940b450f238a56c0d75b7955cb57b38191982");
ensure_workdir("3a-newname-in-ours-deleted-in-theirs.txt",
0100644, "18cb316b1cefa0f8a6946f0e201a8e1a6f845ab9");
ensure_workdir("3b-newname-in-theirs-deleted-in-ours.txt",
0100644, "36219b49367146cb2e6a1555b5a9ebd4d0328495");
ensure_workdir("4a-newname-in-ours-added-in-theirs.txt",
0100644, "227792b52aaa0b238bea00ec7e509b02623f168c");
ensure_workdir("4b-newname-in-theirs-added-in-ours.txt",
0100644, "de872ee3618b894992e9d1e18ba2ebe256a112f9");
ensure_workdir("5a-newname-in-ours-added-in-theirs.txt",
0100644, "d3719a5ae8e4d92276b5313ce976f6ee5af2b436");
ensure_workdir("5b-newname-in-theirs-added-in-ours.txt",
0100644, "385c8a0f26ddf79e9041e15e17dc352ed2c4cced");
ensure_workdir("6-both-renamed-1-to-2-ours.txt",
0100644, "d8fa77b6833082c1ea36b7828a582d4c43882450");
ensure_workdir("7-both-renamed.txt",
0100644, "b42712cfe99a1a500b2a51fe984e0b8a7702ba11");
}
......
......@@ -3,6 +3,7 @@
#include "git2/checkout.h"
#include "refs.h"
#include "path.h"
#include "repository.h"
#ifdef GIT_WIN32
# include <windows.h>
......@@ -44,29 +45,6 @@ void test_checkout_icase__cleanup(void)
static char *get_filename(const char *in)
{
#ifdef GIT_WIN32
HANDLE fh;
HMODULE kerneldll;
char *filename;
typedef DWORD (__stdcall *getfinalpathname)(HANDLE, LPSTR, DWORD, DWORD);
getfinalpathname getfinalpathfn;
cl_assert(filename = malloc(MAX_PATH));
cl_assert(kerneldll = LoadLibrary("kernel32.dll"));
cl_assert(getfinalpathfn = (getfinalpathname)GetProcAddress(kerneldll, "GetFinalPathNameByHandleA"));
cl_assert(fh = CreateFileA(in, FILE_READ_ATTRIBUTES | STANDARD_RIGHTS_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL));
cl_win32_pass(getfinalpathfn(fh, filename, MAX_PATH, VOLUME_NAME_DOS));
CloseHandle(fh);
git_path_mkposix(filename);
return filename;
#else
char *search_dirname, *search_filename, *filename = NULL;
git_buf out = GIT_BUF_INIT;
DIR *dir;
......@@ -92,7 +70,6 @@ static char *get_filename(const char *in)
git_buf_dispose(&out);
return filename;
#endif
}
static void assert_name_is(const char *expected)
......@@ -115,6 +92,18 @@ static void assert_name_is(const char *expected)
free(actual);
}
static int symlink_or_fake(git_repository *repo, const char *a, const char *b)
{
int symlinks;
cl_git_pass(git_repository__cvar(&symlinks, repo, GIT_CVAR_SYMLINKS));
if (symlinks)
return p_symlink(a, b);
else
return git_futils_fake_symlink(a, b);
}
void test_checkout_icase__refuses_to_overwrite_files_for_files(void)
{
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING;
......@@ -141,7 +130,7 @@ void test_checkout_icase__refuses_to_overwrite_links_for_files(void)
{
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING;
cl_must_pass(p_symlink("../tmp", "testrepo/BRANCH_FILE.txt"));
cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/BRANCH_FILE.txt"));
cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts));
......@@ -153,7 +142,7 @@ void test_checkout_icase__overwrites_links_for_files_when_forced(void)
{
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_must_pass(p_symlink("../tmp", "testrepo/NEW.txt"));
cl_must_pass(symlink_or_fake(repo, "../tmp", "testrepo/NEW.txt"));
cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts));
......@@ -229,7 +218,7 @@ void test_checkout_icase__refuses_to_overwrite_links_for_folders(void)
{
checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE|GIT_CHECKOUT_RECREATE_MISSING;
cl_must_pass(p_symlink("..", "testrepo/A"));
cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A"));
cl_git_fail(git_checkout_tree(repo, obj, &checkout_opts));
......@@ -241,7 +230,7 @@ void test_checkout_icase__overwrites_links_for_folders_when_forced(void)
{
checkout_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
cl_must_pass(p_symlink("..", "testrepo/A"));
cl_must_pass(symlink_or_fake(repo, "..", "testrepo/A"));
cl_git_pass(git_checkout_tree(repo, obj, &checkout_opts));
......
......@@ -5,8 +5,10 @@
#include "fileops.h"
#include "repository.h"
#include "remote.h"
#include "repo/repo_helpers.h"
static git_repository *g_repo;
static git_buf g_global_path = GIT_BUF_INIT;
void test_checkout_index__initialize(void)
{
......@@ -22,21 +24,29 @@ void test_checkout_index__initialize(void)
cl_git_rewritefile(
"./testrepo/.gitattributes",
"* text eol=lf\n");
git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL,
&g_global_path);
}
void test_checkout_index__cleanup(void)
{
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL,
g_global_path.ptr);
git_buf_dispose(&g_global_path);
cl_git_sandbox_cleanup();
/* try to remove alternative dir */
if (git_path_isdir("alternative"))
git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES);
/* try to remove directories created by tests */
cl_fixture_cleanup("alternative");
cl_fixture_cleanup("symlink");
cl_fixture_cleanup("symlink.git");
cl_fixture_cleanup("tmp_global_path");
}
void test_checkout_index__cannot_checkout_a_bare_repository(void)
{
test_checkout_index__cleanup();
cl_git_sandbox_cleanup();
g_repo = cl_git_sandbox_init("testrepo.git");
cl_git_fail(git_checkout_index(g_repo, NULL, NULL));
......@@ -136,23 +146,20 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void)
#endif
}
void test_checkout_index__honor_coresymlinks_default(void)
static void populate_symlink_workdir(void)
{
git_repository *repo;
git_remote *origin;
git_object *target;
char cwd[GIT_PATH_MAX];
const char *url = git_repository_path(g_repo);
cl_assert(getcwd(cwd, sizeof(cwd)) != NULL);
cl_assert_equal_i(0, p_mkdir("readonly", 0555)); /* Read-only directory */
cl_assert_equal_i(0, chdir("readonly"));
cl_git_pass(git_repository_init(&repo, "../symlink.git", true));
cl_assert_equal_i(0, chdir(cwd));
cl_assert_equal_i(0, p_mkdir("symlink", 0777));
cl_git_pass(git_repository_set_workdir(repo, "symlink", 1));
/* Delete the `origin` repo (if it exists) so we can recreate it. */
git_remote_delete(repo, GIT_REMOTE_ORIGIN);
cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url));
cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL));
git_remote_free(origin);
......@@ -161,28 +168,79 @@ void test_checkout_index__honor_coresymlinks_default(void)
cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL));
git_object_free(target);
git_repository_free(repo);
}
void test_checkout_index__honor_coresymlinks_default_true(void)
{
char link_data[GIT_PATH_MAX];
int link_size = GIT_PATH_MAX;
cl_must_pass(p_mkdir("symlink", 0777));
if (!filesystem_supports_symlinks("symlink/test"))
cl_skip();
#ifdef GIT_WIN32
/*
* Windows explicitly requires the global configuration to have
* core.symlinks=true in addition to actual filesystem support.
*/
create_tmp_global_config("tmp_global_path", "core.symlinks", "true");
#endif
populate_symlink_workdir();
link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size);
cl_assert(link_size >= 0);
link_data[link_size] = '\0';
cl_assert_equal_i(link_size, strlen("new.txt"));
cl_assert_equal_s(link_data, "new.txt");
check_file_contents("./symlink/link_to_new.txt", "my new file\n");
}
void test_checkout_index__honor_coresymlinks_default_false(void)
{
cl_must_pass(p_mkdir("symlink", 0777));
#ifndef GIT_WIN32
/*
* This test is largely for Windows platforms to ensure that
* we respect an unset core.symlinks even when the platform
* supports symlinks. Bail entirely on POSIX platforms that
* do support symlinks.
*/
if (filesystem_supports_symlinks("symlink/test"))
cl_skip();
#endif
populate_symlink_workdir();
check_file_contents("./symlink/link_to_new.txt", "new.txt");
#else
{
char link_data[1024];
size_t link_size = 1024;
link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size);
link_data[link_size] = '\0';
cl_assert_equal_i(link_size, strlen("new.txt"));
cl_assert_equal_s(link_data, "new.txt");
check_file_contents("./symlink/link_to_new.txt", "my new file\n");
}
void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
if (filesystem_supports_symlinks("testrepo/test")) {
cl_skip();
}
#endif
cl_fixture_cleanup("symlink");
cl_repo_set_bool(g_repo, "core.symlinks", true);
opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_RECREATE_MISSING;
cl_git_fail(git_checkout_index(g_repo, NULL, &opts));
}
void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
char link_data[GIT_PATH_MAX];
size_t link_size = GIT_PATH_MAX;
if (!filesystem_supports_symlinks("testrepo/test")) {
cl_skip();
}
cl_repo_set_bool(g_repo, "core.symlinks", true);
......@@ -190,20 +248,11 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void)
cl_git_pass(git_checkout_index(g_repo, NULL, &opts));
#ifdef GIT_WIN32
check_file_contents("./testrepo/link_to_new.txt", "new.txt");
#else
{
char link_data[1024];
size_t link_size = 1024;
link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
link_data[link_size] = '\0';
cl_assert_equal_i(link_size, strlen("new.txt"));
cl_assert_equal_s(link_data, "new.txt");
check_file_contents("./testrepo/link_to_new.txt", "my new file\n");
}
#endif
link_size = p_readlink("./testrepo/link_to_new.txt", link_data, link_size);
link_data[link_size] = '\0';
cl_assert_equal_i(link_size, strlen("new.txt"));
cl_assert_equal_s(link_data, "new.txt");
check_file_contents("./testrepo/link_to_new.txt", "my new file\n");
}
void test_checkout_index__honor_coresymlinks_setting_set_to_false(void)
......@@ -474,7 +523,7 @@ void test_checkout_index__can_overcome_name_clashes(void)
cl_assert(git_path_isfile("./testrepo/path0/file0"));
opts.checkout_strategy =
GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_SAFE |
GIT_CHECKOUT_RECREATE_MISSING |
GIT_CHECKOUT_ALLOW_CONFLICTS;
cl_git_pass(git_checkout_index(g_repo, index, &opts));
......@@ -547,9 +596,9 @@ void test_checkout_index__can_update_prefixed_files(void)
void test_checkout_index__can_checkout_a_newly_initialized_repository(void)
{
test_checkout_index__cleanup();
cl_git_sandbox_cleanup();
g_repo = cl_git_sandbox_init("empty_standard_repo");
cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt");
cl_git_pass(git_checkout_index(g_repo, NULL, NULL));
......@@ -559,8 +608,7 @@ void test_checkout_index__issue_1397(void)
{
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
test_checkout_index__cleanup();
cl_git_sandbox_cleanup();
g_repo = cl_git_sandbox_init("issue_1397");
cl_repo_set_bool(g_repo, "core.autocrlf", true);
......@@ -613,8 +661,7 @@ void test_checkout_index__target_directory_from_bare(void)
checkout_counts cts;
memset(&cts, 0, sizeof(cts));
test_checkout_index__cleanup();
cl_git_sandbox_cleanup();
g_repo = cl_git_sandbox_init("testrepo.git");
cl_assert(git_repository_is_bare(g_repo));
......
......@@ -4,6 +4,7 @@
#include "config.h"
#include "path.h"
#include "config/config_helpers.h"
#include "repo/repo_helpers.h"
enum repo_mode {
STANDARD_REPOSITORY = 0,
......@@ -12,7 +13,6 @@ enum repo_mode {
static git_repository *_repo = NULL;
static git_buf _global_path = GIT_BUF_INIT;
static git_buf _tmp_path = GIT_BUF_INIT;
static mode_t g_umask = 0;
void test_repo_init__initialize(void)
......@@ -35,9 +35,7 @@ void test_repo_init__cleanup(void)
_global_path.ptr);
git_buf_dispose(&_global_path);
if (_tmp_path.size > 0 && git_path_isdir(_tmp_path.ptr))
git_futils_rmdir_r(_tmp_path.ptr, NULL, GIT_RMDIR_REMOVE_FILES);
git_buf_dispose(&_tmp_path);
cl_fixture_cleanup("tmp_global_path");
}
static void cleanup_repository(void *path)
......@@ -247,6 +245,68 @@ void test_repo_init__detect_ignorecase(void)
"core.ignorecase", found_without_match ? true : GIT_ENOTFOUND);
}
/*
* Windows: if the filesystem supports symlinks (because we're running
* as administrator, or because the user has opted into it for normal
* users) then we can also opt-in explicitly by settings `core.symlinks`
* in the global config. Symlinks remain off by default.
*/
void test_repo_init__symlinks_win32_enabled_by_global_config(void)
{
#ifndef GIT_WIN32
cl_skip();
#else
git_config *config, *repo_config;
int val;
if (!filesystem_supports_symlinks("link"))
cl_skip();
create_tmp_global_config("tmp_global_config", "core.symlinks", "true");
/*
* Create a new repository (can't use `assert_config_on_init` since we
* want to examine configuration levels with more granularity.)
*/
cl_git_pass(git_repository_init(&_repo, "config_entry/test.non.bare.git", false));
/* Ensure that core.symlinks remains set (via the global config). */
cl_git_pass(git_repository_config(&config, _repo));
cl_git_pass(git_config_get_bool(&val, config, "core.symlinks"));
cl_assert_equal_i(1, val);
/*
* Ensure that the repository config does not set core.symlinks.
* It should remain inherited.
*/
cl_git_pass(git_config_open_level(&repo_config, config, GIT_CONFIG_LEVEL_LOCAL));
cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&val, repo_config, "core.symlinks"));
git_config_free(repo_config);
git_config_free(config);
#endif
}
void test_repo_init__symlinks_win32_off_by_default(void)
{
#ifndef GIT_WIN32
cl_skip();
#else
assert_config_entry_on_init("core.symlinks", false);
#endif
}
void test_repo_init__symlinks_posix_detected(void)
{
#ifdef GIT_WIN32
cl_skip();
#else
assert_config_entry_on_init(
"core.symlinks", filesystem_supports_symlinks("link") ? GIT_ENOTFOUND : false);
#endif
}
void test_repo_init__detect_precompose_unicode_required(void)
{
#ifdef GIT_USE_ICONV
......@@ -563,26 +623,7 @@ static const char *template_sandbox(const char *name)
static void configure_templatedir(const char *template_path)
{
git_buf config_path = GIT_BUF_INIT;
git_buf config_data = GIT_BUF_INIT;
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH,
GIT_CONFIG_LEVEL_GLOBAL, &_tmp_path));
cl_git_pass(git_buf_puts(&_tmp_path, ".tmp"));
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH,
GIT_CONFIG_LEVEL_GLOBAL, _tmp_path.ptr));
cl_must_pass(p_mkdir(_tmp_path.ptr, 0777));
cl_git_pass(git_buf_joinpath(&config_path, _tmp_path.ptr, ".gitconfig"));
cl_git_pass(git_buf_printf(&config_data,
"[init]\n\ttemplatedir = \"%s\"\n", template_path));
cl_git_mkfile(config_path.ptr, config_data.ptr);
git_buf_dispose(&config_path);
git_buf_dispose(&config_data);
create_tmp_global_config("tmp_global_path", "init.templatedir", template_path);
}
static void validate_templates(git_repository *repo, const char *template_path)
......
......@@ -20,3 +20,33 @@ void delete_head(git_repository* repo)
git_buf_dispose(&head_path);
}
int filesystem_supports_symlinks(const char *path)
{
struct stat st;
bool support = 0;
if (p_symlink("target", path) == 0) {
if (p_lstat(path, &st) == 0 && S_ISLNK(st.st_mode))
support = 1;
p_unlink(path);
}
return support;
}
void create_tmp_global_config(const char *dirname, const char *key, const char *val)
{
git_buf path = GIT_BUF_INIT;
git_config *config;
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH,
GIT_CONFIG_LEVEL_GLOBAL, dirname));
cl_must_pass(p_mkdir(dirname, 0777));
cl_git_pass(git_buf_joinpath(&path, dirname, ".gitconfig"));
cl_git_pass(git_config_open_ondisk(&config, path.ptr));
cl_git_pass(git_config_set_string(config, key, val));
git_config_free(config);
git_buf_dispose(&path);
}
......@@ -4,3 +4,5 @@
extern void make_head_unborn(git_repository* repo, const char *target);
extern void delete_head(git_repository* repo);
extern int filesystem_supports_symlinks(const char *path);
extern void create_tmp_global_config(const char *path, const char *key, const char *val);
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment