summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2018-11-15 09:17:51 +0000
committerGitHub <noreply@github.com>2018-11-15 09:17:51 +0000
commit7321cff05df927c8d00755ef21289ec00d125c9c (patch)
treecbc8f816d196c6e5642886bb632d524eecbfa137
parent9189a66a9eb99f13ee81da5913ade3a1ff64262a (diff)
parentda500cc607f1f30cea822087f3aaeb6b6727ff74 (diff)
downloadlibgit2-7321cff05df927c8d00755ef21289ec00d125c9c.tar.gz
Merge pull request #4713 from libgit2/ethomson/win_symlinks
Support symlinks on Windows when core.symlinks=true
-rw-r--r--src/CMakeLists.txt2
-rw-r--r--src/repository.c66
-rw-r--r--src/win32/posix_w32.c54
-rw-r--r--tests/checkout/conflict.c104
-rw-r--r--tests/checkout/icase.c45
-rw-r--r--tests/checkout/index.c137
-rw-r--r--tests/repo/init.c89
-rw-r--r--tests/repo/repo_helpers.c30
-rw-r--r--tests/repo/repo_helpers.h2
9 files changed, 331 insertions, 198 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 157ce8000..8ba3aa590 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -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")
diff --git a/src/repository.c b/src/repository.c
index a2f88a283..490388051 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -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)
diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c
index 8c321ef3c..5d144936b 100644
--- a/src/win32/posix_w32.c
+++ b/src/win32/posix_w32.c
@@ -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)
diff --git a/tests/checkout/conflict.c b/tests/checkout/conflict.c
index 90603d4b7..6670b2ec7 100644
--- a/tests/checkout/conflict.c
+++ b/tests/checkout/conflict.c
@@ -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");
}
diff --git a/tests/checkout/icase.c b/tests/checkout/icase.c
index 560c1ad86..4aa78a869 100644
--- a/tests/checkout/icase.c
+++ b/tests/checkout/icase.c
@@ -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));
diff --git a/tests/checkout/index.c b/tests/checkout/index.c
index 69252e3d9..65a121d76 100644
--- a/tests/checkout/index.c
+++ b/tests/checkout/index.c
@@ -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));
diff --git a/tests/repo/init.c b/tests/repo/init.c
index 2ac09e422..2611f05c7 100644
--- a/tests/repo/init.c
+++ b/tests/repo/init.c
@@ -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)
diff --git a/tests/repo/repo_helpers.c b/tests/repo/repo_helpers.c
index 4dc5bfc6f..4256314f1 100644
--- a/tests/repo/repo_helpers.c
+++ b/tests/repo/repo_helpers.c
@@ -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);
+}
diff --git a/tests/repo/repo_helpers.h b/tests/repo/repo_helpers.h
index 6783d5701..2c9aeabee 100644
--- a/tests/repo/repo_helpers.h
+++ b/tests/repo/repo_helpers.h
@@ -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);