template.c 8.13 KB
Newer Older
1 2
#include "clar_libgit2.h"

3
#include "futils.h"
4 5 6 7 8 9
#include "repo/repo_helpers.h"

#define CLEAR_FOR_CORE_FILEMODE(M) ((M) &= ~0177)

static git_repository *_repo = NULL;
static mode_t g_umask = 0;
10
static git_str _global_path = GIT_STR_INIT;
11

12 13 14
static const char *fixture_repo;
static const char *fixture_templates;

15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
void test_repo_template__initialize(void)
{
	_repo = NULL;

	/* load umask if not already loaded */
	if (!g_umask) {
		g_umask = p_umask(022);
		(void)p_umask(g_umask);
	}
}

void test_repo_template__cleanup(void)
{
	git_libgit2_opts(GIT_OPT_SET_SEARCH_PATH, GIT_CONFIG_LEVEL_GLOBAL,
		_global_path.ptr);
30
	git_str_dispose(&_global_path);
31 32 33

	cl_fixture_cleanup("tmp_global_path");

34 35 36 37 38 39 40 41 42 43
	if (fixture_repo) {
		cl_fixture_cleanup(fixture_repo);
		fixture_repo = NULL;
	}

	if (fixture_templates) {
		cl_fixture_cleanup(fixture_templates);
		fixture_templates = NULL;
	}

44 45 46 47 48 49 50 51 52 53
	git_repository_free(_repo);
	_repo = NULL;
}

static void assert_hooks_match(
	const char *template_dir,
	const char *repo_dir,
	const char *hook_path,
	bool core_filemode)
{
54 55
	git_str expected = GIT_STR_INIT;
	git_str actual = GIT_STR_INIT;
56 57
	struct stat expected_st, st;

58
	cl_git_pass(git_str_joinpath(&expected, template_dir, hook_path));
59
	cl_git_pass(git_fs_path_lstat(expected.ptr, &expected_st));
60

61
	cl_git_pass(git_str_joinpath(&actual, repo_dir, hook_path));
62
	cl_git_pass(git_fs_path_lstat(actual.ptr, &st));
63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78

	cl_assert(expected_st.st_size == st.st_size);

	if (GIT_MODE_TYPE(expected_st.st_mode) != GIT_FILEMODE_LINK) {
		mode_t expected_mode =
			GIT_MODE_TYPE(expected_st.st_mode) |
			(GIT_PERMS_FOR_WRITE(expected_st.st_mode) & ~g_umask);

		if (!core_filemode) {
			CLEAR_FOR_CORE_FILEMODE(expected_mode);
			CLEAR_FOR_CORE_FILEMODE(st.st_mode);
		}

		cl_assert_equal_i_fmt(expected_mode, st.st_mode, "%07o");
	}

79 80
	git_str_dispose(&expected);
	git_str_dispose(&actual);
81 82 83 84 85 86
}

static void assert_mode_seems_okay(
	const char *base, const char *path,
	git_filemode_t expect_mode, bool expect_setgid, bool core_filemode)
{
87
	git_str full = GIT_STR_INIT;
88 89
	struct stat st;

90
	cl_git_pass(git_str_joinpath(&full, base, path));
91
	cl_git_pass(git_fs_path_lstat(full.ptr, &st));
92
	git_str_dispose(&full);
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109

	if (!core_filemode) {
		CLEAR_FOR_CORE_FILEMODE(expect_mode);
		CLEAR_FOR_CORE_FILEMODE(st.st_mode);
		expect_setgid = false;
	}

	if (S_ISGID != 0)
		cl_assert_equal_b(expect_setgid, (st.st_mode & S_ISGID) != 0);

	cl_assert_equal_b(
		GIT_PERMS_IS_EXEC(expect_mode), GIT_PERMS_IS_EXEC(st.st_mode));

	cl_assert_equal_i_fmt(
		GIT_MODE_TYPE(expect_mode), GIT_MODE_TYPE(st.st_mode), "%07o");
}

110 111 112 113 114 115 116
static void setup_repo(const char *name, git_repository_init_options *opts)
{
	cl_git_pass(git_repository_init_ext(&_repo, name, opts));
	fixture_repo = name;
}

static void setup_templates(const char *name, bool setup_globally)
117
{
118
	git_str path = GIT_STR_INIT;
119

120 121 122 123 124
	cl_fixture_sandbox("template");
	if (strcmp(name, "template"))
		cl_must_pass(p_rename("template", name));

	fixture_templates = name;
125

126 127
	/*
	 * Create a symlink from link.sample to update.sample if the filesystem
128 129
	 * supports it.
	 */
130
	cl_git_pass(git_str_join3(&path, '/', name, "hooks", "link.sample"));
131
#ifdef GIT_WIN32
132
	cl_git_mkfile(path.ptr, "#!/bin/sh\necho hello, world\n");
133
#else
134
	cl_must_pass(p_symlink("update.sample", path.ptr));
135 136
#endif

137
	git_str_clear(&path);
138

139
	/* Create a file starting with a dot */
140
	cl_git_pass(git_str_join3(&path, '/', name, "hooks", ".dotfile"));
141
	cl_git_mkfile(path.ptr, "something\n");
142

143
	git_str_clear(&path);
144

145
	if (setup_globally) {
146
		cl_git_pass(git_str_joinpath(&path, clar_sandbox_path(), name));
147 148 149
		create_tmp_global_config("tmp_global_path", "init.templatedir", path.ptr);
	}

150
	git_str_dispose(&path);
151 152 153 154
}

static void validate_templates(git_repository *repo, const char *template_path)
{
155
	git_str path = GIT_STR_INIT, expected = GIT_STR_INIT, actual = GIT_STR_INIT;
156 157
	int filemode;

158
	cl_git_pass(git_str_joinpath(&path, template_path, "description"));
159
	cl_git_pass(git_futils_readbuffer(&expected, path.ptr));
160

161
	git_str_clear(&path);
162

163
	cl_git_pass(git_str_joinpath(&path, git_repository_path(repo), "description"));
164
	cl_git_pass(git_futils_readbuffer(&actual, path.ptr));
165 166 167 168 169 170 171 172 173 174 175 176 177 178 179

	cl_assert_equal_s(expected.ptr, actual.ptr);

	filemode = cl_repo_get_bool(repo, "core.filemode");

	assert_hooks_match(
		template_path, git_repository_path(repo),
		"hooks/update.sample", filemode);
	assert_hooks_match(
		template_path, git_repository_path(repo),
		"hooks/link.sample", filemode);
	assert_hooks_match(
		template_path, git_repository_path(repo),
		"hooks/.dotfile", filemode);

180 181 182
	git_str_dispose(&expected);
	git_str_dispose(&actual);
	git_str_dispose(&path);
183 184 185 186 187 188 189 190 191 192
}

void test_repo_template__external_templates_specified_in_options(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE |
		GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
	opts.template_path = "template";

193 194
	setup_templates("template", false);
	setup_repo("templated.git", &opts);
195 196 197 198 199 200 201 202 203 204 205

	validate_templates(_repo, "template");
}

void test_repo_template__external_templates_specified_in_config(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE |
		GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;

206 207
	setup_templates("template", true);
	setup_repo("templated.git", &opts);
208 209 210 211 212 213 214 215 216 217 218

	validate_templates(_repo, "template");
}

void test_repo_template__external_templates_with_leading_dot(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE |
		GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;

219 220
	setup_templates(".template_with_leading_dot", true);
	setup_repo("templated.git", &opts);
221 222 223 224 225 226 227

	validate_templates(_repo, ".template_with_leading_dot");
}

void test_repo_template__extended_with_template_and_shared_mode(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
228 229
	const char *repo_path;
	int filemode;
230 231 232 233 234 235

	opts.flags = GIT_REPOSITORY_INIT_MKPATH |
		GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
	opts.template_path = "template";
	opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;

236 237
	setup_templates("template", false);
	setup_repo("init_shared_from_tpl", &opts);
238 239 240 241 242 243 244 245 246 247 248 249 250 251

	filemode = cl_repo_get_bool(_repo, "core.filemode");

	repo_path = git_repository_path(_repo);
	assert_mode_seems_okay(repo_path, "hooks",
		GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode);
	assert_mode_seems_okay(repo_path, "info",
		GIT_FILEMODE_TREE | GIT_REPOSITORY_INIT_SHARED_GROUP, true, filemode);
	assert_mode_seems_okay(repo_path, "description",
		GIT_FILEMODE_BLOB, false, filemode);

	validate_templates(_repo, "template");
}

252 253 254
void test_repo_template__templated_head_is_used(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
255
	git_str head = GIT_STR_INIT;
256 257 258 259 260 261 262 263 264 265

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;

	setup_templates("template", true);
	cl_git_mkfile("template/HEAD", "foobar\n");
	setup_repo("repo", &opts);

	cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD"));
	cl_assert_equal_s("foobar\n", head.ptr);

266
	git_str_dispose(&head);
267 268 269 270 271
}

void test_repo_template__initial_head_option_overrides_template_head(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
272
	git_str head = GIT_STR_INIT;
273 274 275 276 277 278 279 280 281 282 283

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
	opts.initial_head = "manual";

	setup_templates("template", true);
	cl_git_mkfile("template/HEAD", "foobar\n");
	setup_repo("repo", &opts);

	cl_git_pass(git_futils_readbuffer(&head, "repo/.git/HEAD"));
	cl_assert_equal_s("ref: refs/heads/manual\n", head.ptr);

284
	git_str_dispose(&head);
285 286
}

287 288 289 290
void test_repo_template__empty_template_path(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;

291 292
	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
	opts.template_path = "";
293

294
	setup_repo("foo", &opts);
295
}
296 297 298 299 300 301 302 303 304 305

void test_repo_template__nonexistent_template_path(void)
{
	git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;

	opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
	opts.template_path = "/tmp/path/that/does/not/exist/for/libgit2/test";

	setup_repo("bar", &opts);
}