diff options
| author | Edward Thomson <ethomson@edwardthomson.com> | 2018-11-15 09:17:51 +0000 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2018-11-15 09:17:51 +0000 |
| commit | 7321cff05df927c8d00755ef21289ec00d125c9c (patch) | |
| tree | cbc8f816d196c6e5642886bb632d524eecbfa137 | |
| parent | 9189a66a9eb99f13ee81da5913ade3a1ff64262a (diff) | |
| parent | da500cc607f1f30cea822087f3aaeb6b6727ff74 (diff) | |
| download | libgit2-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.txt | 2 | ||||
| -rw-r--r-- | src/repository.c | 66 | ||||
| -rw-r--r-- | src/win32/posix_w32.c | 54 | ||||
| -rw-r--r-- | tests/checkout/conflict.c | 104 | ||||
| -rw-r--r-- | tests/checkout/icase.c | 45 | ||||
| -rw-r--r-- | tests/checkout/index.c | 137 | ||||
| -rw-r--r-- | tests/repo/init.c | 89 | ||||
| -rw-r--r-- | tests/repo/repo_helpers.c | 30 | ||||
| -rw-r--r-- | tests/repo/repo_helpers.h | 2 |
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); |
