Skip to content

Commit da500cc

Browse files
committed
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.
1 parent 3f0caa1 commit da500cc

File tree

4 files changed

+151
-66
lines changed

4 files changed

+151
-66
lines changed

tests/checkout/index.c

Lines changed: 67 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
#include "fileops.h"
66
#include "repository.h"
77
#include "remote.h"
8+
#include "repo/repo_helpers.h"
89

910
static git_repository *g_repo;
11+
static git_buf g_global_path = GIT_BUF_INIT;
1012

1113
void test_checkout_index__initialize(void)
1214
{
@@ -22,21 +24,29 @@ void test_checkout_index__initialize(void)
2224
cl_git_rewritefile(
2325
"./testrepo/.gitattributes",
2426
"* text eol=lf\n");
27+
28+
git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL,
29+
&g_global_path);
2530
}
2631

2732
void test_checkout_index__cleanup(void)
2833
{
34+
git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL,
35+
g_global_path.ptr);
36+
git_buf_dispose(&g_global_path);
37+
2938
cl_git_sandbox_cleanup();
3039

31-
/* try to remove alternative dir */
32-
if (git_path_isdir("alternative"))
33-
git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES);
40+
/* try to remove directories created by tests */
41+
cl_fixture_cleanup("alternative");
42+
cl_fixture_cleanup("symlink");
43+
cl_fixture_cleanup("symlink.git");
44+
cl_fixture_cleanup("tmp_global_path");
3445
}
3546

3647
void test_checkout_index__cannot_checkout_a_bare_repository(void)
3748
{
38-
test_checkout_index__cleanup();
39-
49+
cl_git_sandbox_cleanup();
4050
g_repo = cl_git_sandbox_init("testrepo.git");
4151

4252
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)
136146
#endif
137147
}
138148

139-
void test_checkout_index__honor_coresymlinks_default(void)
149+
static void populate_symlink_workdir(void)
140150
{
141151
git_repository *repo;
142152
git_remote *origin;
143153
git_object *target;
144-
char cwd[GIT_PATH_MAX];
145154

146155
const char *url = git_repository_path(g_repo);
147156

148-
cl_assert(getcwd(cwd, sizeof(cwd)) != NULL);
149-
cl_assert_equal_i(0, p_mkdir("readonly", 0555)); /* Read-only directory */
150-
cl_assert_equal_i(0, chdir("readonly"));
151157
cl_git_pass(git_repository_init(&repo, "../symlink.git", true));
152-
cl_assert_equal_i(0, chdir(cwd));
153-
cl_assert_equal_i(0, p_mkdir("symlink", 0777));
154158
cl_git_pass(git_repository_set_workdir(repo, "symlink", 1));
155159

160+
/* Delete the `origin` repo (if it exists) so we can recreate it. */
161+
git_remote_delete(repo, GIT_REMOTE_ORIGIN);
162+
156163
cl_git_pass(git_remote_create(&origin, repo, GIT_REMOTE_ORIGIN, url));
157164
cl_git_pass(git_remote_fetch(origin, NULL, NULL, NULL));
158165
git_remote_free(origin);
@@ -161,23 +168,54 @@ void test_checkout_index__honor_coresymlinks_default(void)
161168
cl_git_pass(git_reset(repo, target, GIT_RESET_HARD, NULL));
162169
git_object_free(target);
163170
git_repository_free(repo);
171+
}
164172

165-
if (!filesystem_supports_symlinks("symlink/test")) {
166-
check_file_contents("./symlink/link_to_new.txt", "new.txt");
167-
} else {
168-
char link_data[1024];
169-
int link_size = 1024;
173+
void test_checkout_index__honor_coresymlinks_default_true(void)
174+
{
175+
char link_data[GIT_PATH_MAX];
176+
int link_size = GIT_PATH_MAX;
170177

171-
link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size);
172-
cl_assert(link_size >= 0);
178+
cl_must_pass(p_mkdir("symlink", 0777));
173179

174-
link_data[link_size] = '\0';
175-
cl_assert_equal_i(link_size, strlen("new.txt"));
176-
cl_assert_equal_s(link_data, "new.txt");
177-
check_file_contents("./symlink/link_to_new.txt", "my new file\n");
178-
}
180+
if (!filesystem_supports_symlinks("symlink/test"))
181+
cl_skip();
179182

180-
cl_fixture_cleanup("symlink");
183+
#ifdef GIT_WIN32
184+
/*
185+
* Windows explicitly requires the global configuration to have
186+
* core.symlinks=true in addition to actual filesystem support.
187+
*/
188+
create_tmp_global_config("tmp_global_path", "core.symlinks", "true");
189+
#endif
190+
191+
populate_symlink_workdir();
192+
193+
link_size = p_readlink("./symlink/link_to_new.txt", link_data, link_size);
194+
cl_assert(link_size >= 0);
195+
196+
link_data[link_size] = '\0';
197+
cl_assert_equal_i(link_size, strlen("new.txt"));
198+
cl_assert_equal_s(link_data, "new.txt");
199+
check_file_contents("./symlink/link_to_new.txt", "my new file\n");
200+
}
201+
202+
void test_checkout_index__honor_coresymlinks_default_false(void)
203+
{
204+
cl_must_pass(p_mkdir("symlink", 0777));
205+
206+
#ifndef GIT_WIN32
207+
/*
208+
* This test is largely for Windows platforms to ensure that
209+
* we respect an unset core.symlinks even when the platform
210+
* supports symlinks. Bail entirely on POSIX platforms that
211+
* do support symlinks.
212+
*/
213+
if (filesystem_supports_symlinks("symlink/test"))
214+
cl_skip();
215+
#endif
216+
217+
populate_symlink_workdir();
218+
check_file_contents("./symlink/link_to_new.txt", "new.txt");
181219
}
182220

183221
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)
558596

559597
void test_checkout_index__can_checkout_a_newly_initialized_repository(void)
560598
{
561-
test_checkout_index__cleanup();
562-
599+
cl_git_sandbox_cleanup();
563600
g_repo = cl_git_sandbox_init("empty_standard_repo");
601+
564602
cl_git_remove_placeholders(git_repository_path(g_repo), "dummy-marker.txt");
565603

566604
cl_git_pass(git_checkout_index(g_repo, NULL, NULL));
@@ -570,8 +608,7 @@ void test_checkout_index__issue_1397(void)
570608
{
571609
git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
572610

573-
test_checkout_index__cleanup();
574-
611+
cl_git_sandbox_cleanup();
575612
g_repo = cl_git_sandbox_init("issue_1397");
576613

577614
cl_repo_set_bool(g_repo, "core.autocrlf", true);
@@ -624,8 +661,7 @@ void test_checkout_index__target_directory_from_bare(void)
624661
checkout_counts cts;
625662
memset(&cts, 0, sizeof(cts));
626663

627-
test_checkout_index__cleanup();
628-
664+
cl_git_sandbox_cleanup();
629665
g_repo = cl_git_sandbox_init("testrepo.git");
630666
cl_assert(git_repository_is_bare(g_repo));
631667

tests/repo/init.c

Lines changed: 60 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "config.h"
55
#include "path.h"
66
#include "config/config_helpers.h"
7+
#include "repo/repo_helpers.h"
78

89
enum repo_mode {
910
STANDARD_REPOSITORY = 0,
@@ -12,7 +13,6 @@ enum repo_mode {
1213

1314
static git_repository *_repo = NULL;
1415
static git_buf _global_path = GIT_BUF_INIT;
15-
static git_buf _tmp_path = GIT_BUF_INIT;
1616
static mode_t g_umask = 0;
1717

1818
void test_repo_init__initialize(void)
@@ -35,9 +35,7 @@ void test_repo_init__cleanup(void)
3535
_global_path.ptr);
3636
git_buf_dispose(&_global_path);
3737

38-
if (_tmp_path.size > 0 && git_path_isdir(_tmp_path.ptr))
39-
git_futils_rmdir_r(_tmp_path.ptr, NULL, GIT_RMDIR_REMOVE_FILES);
40-
git_buf_dispose(&_tmp_path);
38+
cl_fixture_cleanup("tmp_global_path");
4139
}
4240

4341
static void cleanup_repository(void *path)
@@ -48,19 +46,6 @@ static void cleanup_repository(void *path)
4846
cl_fixture_cleanup((const char *)path);
4947
}
5048

51-
static void configure_tmp_global_path(git_buf *out)
52-
{
53-
cl_git_pass(git_libgit2_opts(GIT_OPT_GET_SEARCH_PATH,
54-
GIT_CONFIG_LEVEL_GLOBAL, &_tmp_path));
55-
cl_git_pass(git_buf_puts(&_tmp_path, ".tmp"));
56-
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH,
57-
GIT_CONFIG_LEVEL_GLOBAL, _tmp_path.ptr));
58-
59-
cl_must_pass(p_mkdir(_tmp_path.ptr, 0777));
60-
61-
cl_git_pass(git_buf_joinpath(out, _tmp_path.ptr, ".gitconfig"));
62-
}
63-
6449
static void ensure_repository_init(
6550
const char *working_directory,
6651
int is_bare,
@@ -260,10 +245,66 @@ void test_repo_init__detect_ignorecase(void)
260245
"core.ignorecase", found_without_match ? true : GIT_ENOTFOUND);
261246
}
262247

263-
void test_repo_init__detect_symlinks(void)
248+
/*
249+
* Windows: if the filesystem supports symlinks (because we're running
250+
* as administrator, or because the user has opted into it for normal
251+
* users) then we can also opt-in explicitly by settings `core.symlinks`
252+
* in the global config. Symlinks remain off by default.
253+
*/
254+
255+
void test_repo_init__symlinks_win32_enabled_by_global_config(void)
256+
{
257+
#ifndef GIT_WIN32
258+
cl_skip();
259+
#else
260+
git_config *config, *repo_config;
261+
int val;
262+
263+
if (!filesystem_supports_symlinks("link"))
264+
cl_skip();
265+
266+
create_tmp_global_config("tmp_global_config", "core.symlinks", "true");
267+
268+
/*
269+
* Create a new repository (can't use `assert_config_on_init` since we
270+
* want to examine configuration levels with more granularity.)
271+
*/
272+
cl_git_pass(git_repository_init(&_repo, "config_entry/test.non.bare.git", false));
273+
274+
/* Ensure that core.symlinks remains set (via the global config). */
275+
cl_git_pass(git_repository_config(&config, _repo));
276+
cl_git_pass(git_config_get_bool(&val, config, "core.symlinks"));
277+
cl_assert_equal_i(1, val);
278+
279+
/*
280+
* Ensure that the repository config does not set core.symlinks.
281+
* It should remain inherited.
282+
*/
283+
cl_git_pass(git_config_open_level(&repo_config, config, GIT_CONFIG_LEVEL_LOCAL));
284+
cl_git_fail_with(GIT_ENOTFOUND, git_config_get_bool(&val, repo_config, "core.symlinks"));
285+
git_config_free(repo_config);
286+
287+
git_config_free(config);
288+
#endif
289+
}
290+
291+
void test_repo_init__symlinks_win32_off_by_default(void)
292+
{
293+
#ifndef GIT_WIN32
294+
cl_skip();
295+
#else
296+
assert_config_entry_on_init("core.symlinks", false);
297+
#endif
298+
}
299+
300+
void test_repo_init__symlinks_posix_detected(void)
264301
{
302+
#ifdef GIT_WIN32
303+
cl_skip();
304+
#else
265305
assert_config_entry_on_init(
266306
"core.symlinks", filesystem_supports_symlinks("link") ? GIT_ENOTFOUND : false);
307+
#endif
267308
}
268309

269310
void test_repo_init__detect_precompose_unicode_required(void)
@@ -582,18 +623,7 @@ static const char *template_sandbox(const char *name)
582623

583624
static void configure_templatedir(const char *template_path)
584625
{
585-
git_buf config_path = GIT_BUF_INIT;
586-
git_buf config_data = GIT_BUF_INIT;
587-
588-
configure_tmp_global_path(&config_path);
589-
590-
cl_git_pass(git_buf_printf(&config_data,
591-
"[init]\n\ttemplatedir = \"%s\"\n", template_path));
592-
593-
cl_git_mkfile(config_path.ptr, config_data.ptr);
594-
595-
git_buf_dispose(&config_path);
596-
git_buf_dispose(&config_data);
626+
create_tmp_global_config("tmp_global_path", "init.templatedir", template_path);
597627
}
598628

599629
static void validate_templates(git_repository *repo, const char *template_path)

tests/repo/repo_helpers.c

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,29 @@ void delete_head(git_repository* repo)
2424
int filesystem_supports_symlinks(const char *path)
2525
{
2626
struct stat st;
27+
bool support = 0;
2728

28-
if (p_symlink("target", path) < 0 ||
29-
p_lstat(path, &st) < 0 ||
30-
!(S_ISLNK(st.st_mode)))
31-
return 0;
29+
if (p_symlink("target", path) == 0) {
30+
if (p_lstat(path, &st) == 0 && S_ISLNK(st.st_mode))
31+
support = 1;
3232

33-
return 1;
33+
p_unlink(path);
34+
}
35+
36+
return support;
37+
}
38+
39+
void create_tmp_global_config(const char *dirname, const char *key, const char *val)
40+
{
41+
git_buf path = GIT_BUF_INIT;
42+
git_config *config;
43+
44+
cl_git_pass(git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH,
45+
GIT_CONFIG_LEVEL_GLOBAL, dirname));
46+
cl_must_pass(p_mkdir(dirname, 0777));
47+
cl_git_pass(git_buf_joinpath(&path, dirname, ".gitconfig"));
48+
cl_git_pass(git_config_open_ondisk(&config, path.ptr));
49+
cl_git_pass(git_config_set_string(config, key, val));
50+
git_config_free(config);
51+
git_buf_dispose(&path);
3452
}

tests/repo/repo_helpers.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@
55
extern void make_head_unborn(git_repository* repo, const char *target);
66
extern void delete_head(git_repository* repo);
77
extern int filesystem_supports_symlinks(const char *path);
8+
extern void create_tmp_global_config(const char *path, const char *key, const char *val);

0 commit comments

Comments
 (0)