Commit da500cc607f1f30cea822087f3aaeb6b6727ff74

Edward Thomson 2018-10-20T05:43:40

symlink tests: test symbolic links on windows Test updated symbolic link creation on Windows. Ensure that we emulate Git for Windows behavior. Ensure that when `core.symlinks=true` is set in a global configuration that new repositories are created without a `core.symlinks` setting, and that when `core.symlinks` is unset that `core.symlinks=false` in set in the repository. Further ensure that checkout honors the expected `core.symlinks` defaults on Windows.

diff --git a/tests/checkout/index.c b/tests/checkout/index.c
index 4d86ba1..65a121d 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,23 +168,54 @@ 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);
+}
 
-	if (!filesystem_supports_symlinks("symlink/test")) {
-		check_file_contents("./symlink/link_to_new.txt", "new.txt");
-	} else {
-		char link_data[1024];
-		int link_size = 1024;
+void test_checkout_index__honor_coresymlinks_default_true(void)
+{
+	char link_data[GIT_PATH_MAX];
+	int link_size = GIT_PATH_MAX;
 
-		link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size);
-		cl_assert(link_size >= 0);
+	cl_must_pass(p_mkdir("symlink", 0777));
 
-		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");
-	}
+	if (!filesystem_supports_symlinks("symlink/test"))
+		cl_skip();
 
-	cl_fixture_cleanup("symlink");
+#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");
 }
 
 void test_checkout_index__coresymlinks_set_to_true_fails_when_unsupported(void)
@@ -558,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));
@@ -570,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);
@@ -624,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 6818bf6..2611f05 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)
@@ -48,19 +46,6 @@ static void cleanup_repository(void *path)
 	cl_fixture_cleanup((const char *)path);
 }
 
-static void configure_tmp_global_path(git_buf *out)
-{
-	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(out, _tmp_path.ptr, ".gitconfig"));
-}
-
 static void ensure_repository_init(
 	const char *working_directory,
 	int is_bare,
@@ -260,10 +245,66 @@ void test_repo_init__detect_ignorecase(void)
 		"core.ignorecase", found_without_match ? true : GIT_ENOTFOUND);
 }
 
-void test_repo_init__detect_symlinks(void)
+/*
+ * 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)
@@ -582,18 +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;
-
-	configure_tmp_global_path(&config_path);
-
-	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 50a201e..4256314 100644
--- a/tests/repo/repo_helpers.c
+++ b/tests/repo/repo_helpers.c
@@ -24,11 +24,29 @@ void delete_head(git_repository* repo)
 int filesystem_supports_symlinks(const char *path)
 {
 	struct stat st;
+	bool support = 0;
 
-	if (p_symlink("target", path) < 0 ||
-		p_lstat(path, &st) < 0 ||
-		!(S_ISLNK(st.st_mode)))
-		return 0;
+	if (p_symlink("target", path) == 0) {
+		if (p_lstat(path, &st) == 0 && S_ISLNK(st.st_mode))
+			support = 1;
 
-	return 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 f184865..2c9aeab 100644
--- a/tests/repo/repo_helpers.h
+++ b/tests/repo/repo_helpers.h
@@ -5,3 +5,4 @@
 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);