mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-09 07:20:07 +00:00

When PR #1359 removed the hooks from the test resources/template directory, it made me realize that the tests for git_repository_init_ext using templates must be pretty shabby because we could not have been testing if the hooks were getting created correctly. So, this started with me recreating a couple of hooks, including a sample and symlink, and adding tests that they got created correctly in the various circumstances, including with the SHARED modes, etc. Unfortunately this uncovered some issues with how directories and symlinks were copied and chmod'ed. Also, there was a FIXME in the code related to the chmod behavior as well. Going back over the directory creation logic for setting up a repository, I found it was a little difficult to read and could result in creating and/or chmod'ing directories that the user almost certainly didn't intend. So that let to this work which makes repo initialization much more careful (and hopefully easier to follow). It required a couple of extensions / changes to core fileops utilities, but I also think those are for the better, at least for git_futils_cp_r in terms of being careful about what actions it takes.
507 lines
14 KiB
C
507 lines
14 KiB
C
#include "clar_libgit2.h"
|
|
#include "fileops.h"
|
|
#include "repository.h"
|
|
#include "config.h"
|
|
#include "path.h"
|
|
|
|
enum repo_mode {
|
|
STANDARD_REPOSITORY = 0,
|
|
BARE_REPOSITORY = 1
|
|
};
|
|
|
|
static git_repository *_repo = NULL;
|
|
|
|
void test_repo_init__initialize(void)
|
|
{
|
|
_repo = NULL;
|
|
}
|
|
|
|
static void cleanup_repository(void *path)
|
|
{
|
|
git_repository_free(_repo);
|
|
_repo = NULL;
|
|
|
|
cl_fixture_cleanup((const char *)path);
|
|
}
|
|
|
|
static void ensure_repository_init(
|
|
const char *working_directory,
|
|
int is_bare,
|
|
const char *expected_path_repository,
|
|
const char *expected_working_directory)
|
|
{
|
|
const char *workdir;
|
|
|
|
cl_assert(!git_path_isdir(working_directory));
|
|
|
|
cl_git_pass(git_repository_init(&_repo, working_directory, is_bare));
|
|
|
|
workdir = git_repository_workdir(_repo);
|
|
if (workdir != NULL || expected_working_directory != NULL) {
|
|
cl_assert(
|
|
git__suffixcmp(workdir, expected_working_directory) == 0
|
|
);
|
|
}
|
|
|
|
cl_assert(
|
|
git__suffixcmp(git_repository_path(_repo), expected_path_repository) == 0
|
|
);
|
|
|
|
cl_assert(git_repository_is_bare(_repo) == is_bare);
|
|
|
|
#ifdef GIT_WIN32
|
|
if (!is_bare) {
|
|
DWORD fattrs = GetFileAttributes(git_repository_path(_repo));
|
|
cl_assert((fattrs & FILE_ATTRIBUTE_HIDDEN) != 0);
|
|
}
|
|
#endif
|
|
|
|
cl_assert(git_repository_is_empty(_repo));
|
|
}
|
|
|
|
void test_repo_init__standard_repo(void)
|
|
{
|
|
cl_set_cleanup(&cleanup_repository, "testrepo");
|
|
ensure_repository_init("testrepo/", 0, "testrepo/.git/", "testrepo/");
|
|
}
|
|
|
|
void test_repo_init__standard_repo_noslash(void)
|
|
{
|
|
cl_set_cleanup(&cleanup_repository, "testrepo");
|
|
ensure_repository_init("testrepo", 0, "testrepo/.git/", "testrepo/");
|
|
}
|
|
|
|
void test_repo_init__bare_repo(void)
|
|
{
|
|
cl_set_cleanup(&cleanup_repository, "testrepo.git");
|
|
ensure_repository_init("testrepo.git/", 1, "testrepo.git/", NULL);
|
|
}
|
|
|
|
void test_repo_init__bare_repo_noslash(void)
|
|
{
|
|
cl_set_cleanup(&cleanup_repository, "testrepo.git");
|
|
ensure_repository_init("testrepo.git", 1, "testrepo.git/", NULL);
|
|
}
|
|
|
|
void test_repo_init__bare_repo_escaping_current_workdir(void)
|
|
{
|
|
git_buf path_repository = GIT_BUF_INIT;
|
|
git_buf path_current_workdir = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_path_prettify_dir(&path_current_workdir, ".", NULL));
|
|
|
|
cl_git_pass(git_buf_joinpath(&path_repository, git_buf_cstr(&path_current_workdir), "a/b/c"));
|
|
cl_git_pass(git_futils_mkdir_r(git_buf_cstr(&path_repository), NULL, GIT_DIR_MODE));
|
|
|
|
/* Change the current working directory */
|
|
cl_git_pass(chdir(git_buf_cstr(&path_repository)));
|
|
|
|
/* Initialize a bare repo with a relative path escaping out of the current working directory */
|
|
cl_git_pass(git_repository_init(&_repo, "../d/e.git", 1));
|
|
cl_git_pass(git__suffixcmp(git_repository_path(_repo), "/a/b/d/e.git/"));
|
|
|
|
git_repository_free(_repo);
|
|
|
|
/* Open a bare repo with a relative path escaping out of the current working directory */
|
|
cl_git_pass(git_repository_open(&_repo, "../d/e.git"));
|
|
|
|
cl_git_pass(chdir(git_buf_cstr(&path_current_workdir)));
|
|
|
|
git_buf_free(&path_current_workdir);
|
|
git_buf_free(&path_repository);
|
|
|
|
cleanup_repository("a");
|
|
}
|
|
|
|
void test_repo_init__reinit_bare_repo(void)
|
|
{
|
|
cl_set_cleanup(&cleanup_repository, "reinit.git");
|
|
|
|
/* Initialize the repository */
|
|
cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
|
|
git_repository_free(_repo);
|
|
|
|
/* Reinitialize the repository */
|
|
cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
|
|
}
|
|
|
|
void test_repo_init__reinit_too_recent_bare_repo(void)
|
|
{
|
|
git_config *config;
|
|
|
|
/* Initialize the repository */
|
|
cl_git_pass(git_repository_init(&_repo, "reinit.git", 1));
|
|
git_repository_config(&config, _repo);
|
|
|
|
/*
|
|
* Hack the config of the repository to make it look like it has
|
|
* been created by a recenter version of git/libgit2
|
|
*/
|
|
cl_git_pass(git_config_set_int32(config, "core.repositoryformatversion", 42));
|
|
|
|
git_config_free(config);
|
|
git_repository_free(_repo);
|
|
|
|
/* Try to reinitialize the repository */
|
|
cl_git_fail(git_repository_init(&_repo, "reinit.git", 1));
|
|
|
|
cl_fixture_cleanup("reinit.git");
|
|
}
|
|
|
|
void test_repo_init__additional_templates(void)
|
|
{
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
cl_set_cleanup(&cleanup_repository, "tester");
|
|
|
|
ensure_repository_init("tester", 0, "tester/.git/", "tester/");
|
|
|
|
cl_git_pass(
|
|
git_buf_joinpath(&path, git_repository_path(_repo), "description"));
|
|
cl_assert(git_path_isfile(git_buf_cstr(&path)));
|
|
|
|
cl_git_pass(
|
|
git_buf_joinpath(&path, git_repository_path(_repo), "info/exclude"));
|
|
cl_assert(git_path_isfile(git_buf_cstr(&path)));
|
|
|
|
cl_git_pass(
|
|
git_buf_joinpath(&path, git_repository_path(_repo), "hooks"));
|
|
cl_assert(git_path_isdir(git_buf_cstr(&path)));
|
|
/* won't confirm specific contents of hooks dir since it may vary */
|
|
|
|
git_buf_free(&path);
|
|
}
|
|
|
|
static void assert_config_entry_on_init_bytype(const char *config_key, int expected_value, bool is_bare)
|
|
{
|
|
git_config *config;
|
|
int current_value;
|
|
git_buf repo_path = GIT_BUF_INIT;
|
|
|
|
cl_set_cleanup(&cleanup_repository, "config_entry");
|
|
|
|
cl_git_pass(git_buf_puts(&repo_path, "config_entry/test."));
|
|
|
|
if (!is_bare)
|
|
cl_git_pass(git_buf_puts(&repo_path, "non."));
|
|
|
|
cl_git_pass(git_buf_puts(&repo_path, "bare.git"));
|
|
|
|
cl_git_pass(git_repository_init(&_repo, git_buf_cstr(&repo_path), is_bare));
|
|
|
|
git_buf_free(&repo_path);
|
|
|
|
git_repository_config(&config, _repo);
|
|
|
|
if (expected_value >= 0) {
|
|
cl_git_pass(git_config_get_bool(¤t_value, config, config_key));
|
|
|
|
cl_assert_equal_i(expected_value, current_value);
|
|
} else {
|
|
int error = git_config_get_bool(¤t_value, config, config_key);
|
|
|
|
cl_assert_equal_i(expected_value, error);
|
|
}
|
|
|
|
git_config_free(config);
|
|
}
|
|
|
|
static void assert_config_entry_on_init(const char *config_key, int expected_value)
|
|
{
|
|
assert_config_entry_on_init_bytype(config_key, expected_value, true);
|
|
git_repository_free(_repo);
|
|
|
|
assert_config_entry_on_init_bytype(config_key, expected_value, false);
|
|
}
|
|
|
|
void test_repo_init__detect_filemode(void)
|
|
{
|
|
#ifdef GIT_WIN32
|
|
assert_config_entry_on_init("core.filemode", false);
|
|
#else
|
|
assert_config_entry_on_init("core.filemode", true);
|
|
#endif
|
|
}
|
|
|
|
#define CASE_INSENSITIVE_FILESYSTEM (defined GIT_WIN32 || defined __APPLE__)
|
|
|
|
void test_repo_init__detect_ignorecase(void)
|
|
{
|
|
#if CASE_INSENSITIVE_FILESYSTEM
|
|
assert_config_entry_on_init("core.ignorecase", true);
|
|
#else
|
|
assert_config_entry_on_init("core.ignorecase", GIT_ENOTFOUND);
|
|
#endif
|
|
}
|
|
|
|
void test_repo_init__reinit_doesnot_overwrite_ignorecase(void)
|
|
{
|
|
git_config *config;
|
|
int current_value;
|
|
|
|
/* Init a new repo */
|
|
cl_set_cleanup(&cleanup_repository, "not.overwrite.git");
|
|
cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1));
|
|
|
|
/* Change the "core.ignorecase" config value to something unlikely */
|
|
git_repository_config(&config, _repo);
|
|
git_config_set_int32(config, "core.ignorecase", 42);
|
|
git_config_free(config);
|
|
git_repository_free(_repo);
|
|
_repo = NULL;
|
|
|
|
/* Reinit the repository */
|
|
cl_git_pass(git_repository_init(&_repo, "not.overwrite.git", 1));
|
|
git_repository_config(&config, _repo);
|
|
|
|
/* Ensure the "core.ignorecase" config value hasn't been updated */
|
|
cl_git_pass(git_config_get_int32(¤t_value, config, "core.ignorecase"));
|
|
cl_assert_equal_i(42, current_value);
|
|
|
|
git_config_free(config);
|
|
}
|
|
|
|
void test_repo_init__reinit_overwrites_filemode(void)
|
|
{
|
|
git_config *config;
|
|
int expected, current_value;
|
|
|
|
#ifdef GIT_WIN32
|
|
expected = false;
|
|
#else
|
|
expected = true;
|
|
#endif
|
|
|
|
/* Init a new repo */
|
|
cl_set_cleanup(&cleanup_repository, "overwrite.git");
|
|
cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1));
|
|
|
|
|
|
/* Change the "core.filemode" config value to something unlikely */
|
|
git_repository_config(&config, _repo);
|
|
git_config_set_bool(config, "core.filemode", !expected);
|
|
git_config_free(config);
|
|
git_repository_free(_repo);
|
|
_repo = NULL;
|
|
|
|
/* Reinit the repository */
|
|
cl_git_pass(git_repository_init(&_repo, "overwrite.git", 1));
|
|
git_repository_config(&config, _repo);
|
|
|
|
/* Ensure the "core.filemode" config value has been reset */
|
|
cl_git_pass(git_config_get_bool(¤t_value, config, "core.filemode"));
|
|
cl_assert_equal_i(expected, current_value);
|
|
|
|
git_config_free(config);
|
|
}
|
|
|
|
void test_repo_init__sets_logAllRefUpdates_according_to_type_of_repository(void)
|
|
{
|
|
assert_config_entry_on_init_bytype("core.logallrefupdates", GIT_ENOTFOUND, true);
|
|
git_repository_free(_repo);
|
|
assert_config_entry_on_init_bytype("core.logallrefupdates", true, false);
|
|
}
|
|
|
|
void test_repo_init__extended_0(void)
|
|
{
|
|
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
|
|
/* without MKDIR this should fail */
|
|
cl_git_fail(git_repository_init_ext(&_repo, "extended", &opts));
|
|
|
|
/* make the directory first, then it should succeed */
|
|
cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0));
|
|
cl_git_pass(git_repository_init_ext(&_repo, "extended", &opts));
|
|
|
|
cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/extended/"));
|
|
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/extended/.git/"));
|
|
cl_assert(!git_repository_is_bare(_repo));
|
|
cl_assert(git_repository_is_empty(_repo));
|
|
|
|
cleanup_repository("extended");
|
|
}
|
|
|
|
void test_repo_init__extended_1(void)
|
|
{
|
|
git_reference *ref;
|
|
git_remote *remote;
|
|
struct stat st;
|
|
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
|
|
opts.flags = GIT_REPOSITORY_INIT_MKPATH |
|
|
GIT_REPOSITORY_INIT_NO_DOTGIT_DIR;
|
|
opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;
|
|
opts.workdir_path = "../c_wd";
|
|
opts.description = "Awesomest test repository evah";
|
|
opts.initial_head = "development";
|
|
opts.origin_url = "https://github.com/libgit2/libgit2.git";
|
|
|
|
cl_git_pass(git_repository_init_ext(&_repo, "root/b/c.git", &opts));
|
|
|
|
cl_assert(!git__suffixcmp(git_repository_workdir(_repo), "/c_wd/"));
|
|
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/c.git/"));
|
|
cl_assert(git_path_isfile("root/b/c_wd/.git"));
|
|
cl_assert(!git_repository_is_bare(_repo));
|
|
/* repo will not be counted as empty because we set head to "development" */
|
|
cl_assert(!git_repository_is_empty(_repo));
|
|
|
|
cl_git_pass(git_path_lstat(git_repository_path(_repo), &st));
|
|
cl_assert(S_ISDIR(st.st_mode));
|
|
cl_assert((S_ISGID & st.st_mode) == S_ISGID);
|
|
|
|
cl_git_pass(git_reference_lookup(&ref, _repo, "HEAD"));
|
|
cl_assert(git_reference_type(ref) == GIT_REF_SYMBOLIC);
|
|
cl_assert_equal_s("refs/heads/development", git_reference_symbolic_target(ref));
|
|
git_reference_free(ref);
|
|
|
|
cl_git_pass(git_remote_load(&remote, _repo, "origin"));
|
|
cl_assert_equal_s("origin", git_remote_name(remote));
|
|
cl_assert_equal_s(opts.origin_url, git_remote_url(remote));
|
|
git_remote_free(remote);
|
|
|
|
git_repository_free(_repo);
|
|
cl_fixture_cleanup("root");
|
|
}
|
|
|
|
static void assert_hooks_match(
|
|
const char *template_dir,
|
|
const char *repo_dir,
|
|
const char *hook_path,
|
|
uint32_t expected_mode)
|
|
{
|
|
git_buf expected = GIT_BUF_INIT;
|
|
git_buf actual = GIT_BUF_INIT;
|
|
struct stat expected_st, st;
|
|
|
|
cl_git_pass(git_buf_joinpath(&expected, template_dir, hook_path));
|
|
cl_git_pass(git_path_lstat(expected.ptr, &expected_st));
|
|
|
|
cl_git_pass(git_buf_joinpath(&actual, repo_dir, hook_path));
|
|
cl_git_pass(git_path_lstat(actual.ptr, &st));
|
|
|
|
cl_assert(expected_st.st_size == st.st_size);
|
|
|
|
if (!expected_mode)
|
|
expected_mode = (uint32_t)expected_st.st_mode;
|
|
|
|
cl_assert_equal_i((int)expected_mode, (int)st.st_mode);
|
|
|
|
git_buf_free(&expected);
|
|
git_buf_free(&actual);
|
|
}
|
|
|
|
static void assert_has_mode(
|
|
const char *base, const char *path, uint32_t expected)
|
|
{
|
|
git_buf full = GIT_BUF_INIT;
|
|
struct stat st;
|
|
|
|
cl_git_pass(git_buf_joinpath(&full, base, path));
|
|
cl_git_pass(git_path_lstat(full.ptr, &st));
|
|
git_buf_free(&full);
|
|
|
|
cl_assert_equal_i((int)expected, (int)st.st_mode);
|
|
}
|
|
|
|
void test_repo_init__extended_with_template(void)
|
|
{
|
|
git_buf expected = GIT_BUF_INIT;
|
|
git_buf actual = GIT_BUF_INIT;
|
|
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
|
|
cl_set_cleanup(&cleanup_repository, "templated.git");
|
|
|
|
opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE |
|
|
GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
|
|
opts.template_path = cl_fixture("template");
|
|
|
|
cl_git_pass(git_repository_init_ext(&_repo, "templated.git", &opts));
|
|
|
|
cl_assert(git_repository_is_bare(_repo));
|
|
|
|
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/templated.git/"));
|
|
|
|
cl_git_pass(git_futils_readbuffer(
|
|
&expected, cl_fixture("template/description")));
|
|
cl_git_pass(git_futils_readbuffer(
|
|
&actual, "templated.git/description"));
|
|
|
|
cl_assert_equal_s(expected.ptr, actual.ptr);
|
|
|
|
git_buf_free(&expected);
|
|
git_buf_free(&actual);
|
|
|
|
assert_hooks_match(
|
|
cl_fixture("template"), git_repository_path(_repo),
|
|
"hooks/update.sample", 0);
|
|
|
|
assert_hooks_match(
|
|
cl_fixture("template"), git_repository_path(_repo),
|
|
"hooks/link.sample", 0);
|
|
}
|
|
|
|
void test_repo_init__extended_with_template_and_shared_mode(void)
|
|
{
|
|
git_buf expected = GIT_BUF_INIT;
|
|
git_buf actual = GIT_BUF_INIT;
|
|
git_repository_init_options opts = GIT_REPOSITORY_INIT_OPTIONS_INIT;
|
|
|
|
cl_set_cleanup(&cleanup_repository, "init_shared_from_tpl");
|
|
|
|
opts.flags = GIT_REPOSITORY_INIT_MKPATH |
|
|
GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE;
|
|
opts.template_path = cl_fixture("template");
|
|
opts.mode = GIT_REPOSITORY_INIT_SHARED_GROUP;
|
|
|
|
cl_git_pass(git_repository_init_ext(&_repo, "init_shared_from_tpl", &opts));
|
|
|
|
cl_assert(!git_repository_is_bare(_repo));
|
|
cl_assert(!git__suffixcmp(git_repository_path(_repo), "/init_shared_from_tpl/.git/"));
|
|
|
|
cl_git_pass(git_futils_readbuffer(
|
|
&expected, cl_fixture("template/description")));
|
|
cl_git_pass(git_futils_readbuffer(
|
|
&actual, "init_shared_from_tpl/.git/description"));
|
|
|
|
cl_assert_equal_s(expected.ptr, actual.ptr);
|
|
|
|
git_buf_free(&expected);
|
|
git_buf_free(&actual);
|
|
|
|
assert_has_mode(git_repository_path(_repo), "hooks",
|
|
GIT_REPOSITORY_INIT_SHARED_GROUP | S_IFDIR);
|
|
assert_has_mode(git_repository_path(_repo), "info",
|
|
GIT_REPOSITORY_INIT_SHARED_GROUP | S_IFDIR);
|
|
assert_has_mode(git_repository_path(_repo), "description",
|
|
(GIT_REPOSITORY_INIT_SHARED_GROUP | S_IFREG) & ~(S_ISGID | 0111));
|
|
|
|
/* for a non-symlinked hook, it should have shared permissions now */
|
|
assert_hooks_match(
|
|
cl_fixture("template"), git_repository_path(_repo),
|
|
"hooks/update.sample",
|
|
(GIT_REPOSITORY_INIT_SHARED_GROUP | S_IFREG) & ~S_ISGID);
|
|
|
|
/* for a symlinked hook, the permissions still should match the
|
|
* source link, not the GIT_REPOSITORY_INIT_SHARED_GROUP value
|
|
*/
|
|
assert_hooks_match(
|
|
cl_fixture("template"), git_repository_path(_repo),
|
|
"hooks/link.sample", 0);
|
|
}
|
|
|
|
void test_repo_init__can_reinit_an_initialized_repository(void)
|
|
{
|
|
git_repository *reinit;
|
|
|
|
cl_set_cleanup(&cleanup_repository, "extended");
|
|
|
|
cl_git_pass(git_futils_mkdir("extended", NULL, 0775, 0));
|
|
cl_git_pass(git_repository_init(&_repo, "extended", false));
|
|
|
|
cl_git_pass(git_repository_init(&reinit, "extended", false));
|
|
|
|
cl_assert_equal_s(git_repository_path(_repo), git_repository_path(reinit));
|
|
|
|
git_repository_free(reinit);
|
|
}
|