diff --git a/src/fileops.c b/src/fileops.c index 6429e55b4..c1824e812 100644 --- a/src/fileops.c +++ b/src/fileops.c @@ -272,6 +272,10 @@ int git_futils_mkdir( } /* if we are not supposed to made the last element, truncate it */ + if ((flags & GIT_MKDIR_SKIP_LAST2) != 0) { + git_buf_rtruncate_at_char(&make_path, '/'); + flags |= GIT_MKDIR_SKIP_LAST; + } if ((flags & GIT_MKDIR_SKIP_LAST) != 0) git_buf_rtruncate_at_char(&make_path, '/'); @@ -303,34 +307,34 @@ int git_futils_mkdir( int already_exists = 0; switch (errno) { - case EEXIST: - if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 && - !git_path_isdir(make_path.ptr)) { - giterr_set( - GITERR_OS, "Existing path is not a directory '%s'", - make_path.ptr); - error = GIT_ENOTFOUND; - goto fail; - } + case EEXIST: + if (!lastch && (flags & GIT_MKDIR_VERIFY_DIR) != 0 && + !git_path_isdir(make_path.ptr)) { + giterr_set( + GITERR_OS, "Existing path is not a directory '%s'", + make_path.ptr); + error = GIT_ENOTFOUND; + goto fail; + } + already_exists = 1; + break; + case ENOSYS: + /* Solaris can generate this error if you try to mkdir + * a path which is already a mount point. In that case, + * the path does already exist; but it's not implied by + * the definition of the error, so let's recheck */ + if (git_path_isdir(make_path.ptr)) { already_exists = 1; break; - case ENOSYS: - /* Solaris can generate this error if you try to mkdir - * a path which is already a mount point. In that case, - * the path does already exist; but it's not implied by - * the definition of the error, so let's recheck */ - if (git_path_isdir(make_path.ptr)) { - already_exists = 1; - break; - } + } - /* Fall through */ - errno = ENOSYS; - default: - giterr_set(GITERR_OS, "Failed to make directory '%s'", - make_path.ptr); - goto fail; + /* Fall through */ + errno = ENOSYS; + default: + giterr_set(GITERR_OS, "Failed to make directory '%s'", + make_path.ptr); + goto fail; } if (already_exists && (flags & GIT_MKDIR_EXCL) != 0) { @@ -714,8 +718,33 @@ typedef struct { mode_t dirmode; } cp_r_info; +#define GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT (1u << 10) + +static int _cp_r_mkdir(cp_r_info *info, git_buf *from) +{ + int error = 0; + + /* create root directory the first time we need to create a directory */ + if ((info->flags & GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT) == 0) { + error = git_futils_mkdir( + info->to_root, NULL, info->dirmode, + (info->flags & GIT_CPDIR_CHMOD_DIRS) ? GIT_MKDIR_CHMOD : 0); + + info->flags |= GIT_CPDIR__MKDIR_DONE_FOR_TO_ROOT; + } + + /* create directory with root as base to prevent excess chmods */ + if (!error) + error = git_futils_mkdir( + from->ptr + info->from_prefix, info->to_root, + info->dirmode, info->mkdir_flags); + + return error; +} + static int _cp_r_callback(void *ref, git_buf *from) { + int error = 0; cp_r_info *info = ref; struct stat from_st, to_st; bool exists = false; @@ -737,24 +766,22 @@ static int _cp_r_callback(void *ref, git_buf *from) } else exists = true; - if (git_path_lstat(from->ptr, &from_st) < 0) - return -1; + if ((error = git_path_lstat(from->ptr, &from_st)) < 0) + return error; if (S_ISDIR(from_st.st_mode)) { - int error = 0; mode_t oldmode = info->dirmode; /* if we are not chmod'ing, then overwrite dirmode */ - if ((info->flags & GIT_CPDIR_CHMOD) == 0) + if ((info->flags & GIT_CPDIR_CHMOD_DIRS) == 0) info->dirmode = from_st.st_mode; /* make directory now if CREATE_EMPTY_DIRS is requested and needed */ if (!exists && (info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) != 0) - error = git_futils_mkdir( - info->to.ptr, NULL, info->dirmode, info->mkdir_flags); + error = _cp_r_mkdir(info, from); /* recurse onto target directory */ - if (!exists || S_ISDIR(to_st.st_mode)) + if (!error && (!exists || S_ISDIR(to_st.st_mode))) error = git_path_direach(from, _cp_r_callback, info); if (oldmode != 0) @@ -782,15 +809,22 @@ static int _cp_r_callback(void *ref, git_buf *from) /* Make container directory on demand if needed */ if ((info->flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0 && - git_futils_mkdir( - info->to.ptr, NULL, info->dirmode, info->mkdir_flags) < 0) - return -1; + (error = _cp_r_mkdir(info, from)) < 0) + return error; /* make symlink or regular file */ if (S_ISLNK(from_st.st_mode)) - return cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); - else - return git_futils_cp(from->ptr, info->to.ptr, from_st.st_mode); + error = cp_link(from->ptr, info->to.ptr, (size_t)from_st.st_size); + else { + mode_t usemode = from_st.st_mode; + + if ((info->flags & GIT_CPDIR_SIMPLE_TO_MODE) != 0) + usemode = (usemode & 0111) ? 0777 : 0666; + + error = git_futils_cp(from->ptr, info->to.ptr, usemode); + } + + return error; } int git_futils_cp_r( @@ -803,7 +837,7 @@ int git_futils_cp_r( git_buf path = GIT_BUF_INIT; cp_r_info info; - if (git_buf_sets(&path, from) < 0) + if (git_buf_joinpath(&path, from, "") < 0) /* ensure trailing slash */ return -1; info.to_root = to; @@ -814,12 +848,16 @@ int git_futils_cp_r( /* precalculate mkdir flags */ if ((flags & GIT_CPDIR_CREATE_EMPTY_DIRS) == 0) { + /* if not creating empty dirs, then use mkdir to create the path on + * demand right before files are copied. + */ info.mkdir_flags = GIT_MKDIR_PATH | GIT_MKDIR_SKIP_LAST; - if ((flags & GIT_CPDIR_CHMOD) != 0) + if ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) info.mkdir_flags |= GIT_MKDIR_CHMOD_PATH; } else { + /* otherwise, we will do simple mkdir as directories are encountered */ info.mkdir_flags = - ((flags & GIT_CPDIR_CHMOD) != 0) ? GIT_MKDIR_CHMOD : 0; + ((flags & GIT_CPDIR_CHMOD_DIRS) != 0) ? GIT_MKDIR_CHMOD : 0; } error = _cp_r_callback(&info, &path); diff --git a/src/fileops.h b/src/fileops.h index f01f22706..7ba99d3d9 100644 --- a/src/fileops.h +++ b/src/fileops.h @@ -65,6 +65,7 @@ extern int git_futils_mkdir_r(const char *path, const char *base, const mode_t m * * GIT_MKDIR_CHMOD says to chmod the final directory entry after creation * * GIT_MKDIR_CHMOD_PATH says to chmod each directory component in the path * * GIT_MKDIR_SKIP_LAST says to leave off the last element of the path + * * GIT_MKDIR_SKIP_LAST2 says to leave off the last 2 elements of the path * * GIT_MKDIR_VERIFY_DIR says confirm final item is a dir, not just EEXIST * * Note that the chmod options will be executed even if the directory already @@ -76,7 +77,8 @@ typedef enum { GIT_MKDIR_CHMOD = 4, GIT_MKDIR_CHMOD_PATH = 8, GIT_MKDIR_SKIP_LAST = 16, - GIT_MKDIR_VERIFY_DIR = 32, + GIT_MKDIR_SKIP_LAST2 = 32, + GIT_MKDIR_VERIFY_DIR = 64, } git_futils_mkdir_flags; /** @@ -168,13 +170,26 @@ extern int git_futils_cp( /** * Flags that can be passed to `git_futils_cp_r`. + * + * - GIT_CPDIR_CREATE_EMPTY_DIRS: create directories even if there are no + * files under them (otherwise directories will only be created lazily + * when a file inside them is copied). + * - GIT_CPDIR_COPY_SYMLINKS: copy symlinks, otherwise they are ignored. + * - GIT_CPDIR_COPY_DOTFILES: copy files with leading '.', otherwise ignored. + * - GIT_CPDIR_OVERWRITE: overwrite pre-existing files with source content, + * otherwise they are silently skipped. + * - GIT_CPDIR_CHMOD_DIRS: explicitly chmod directories to `dirmode` + * - GIT_CPDIR_SIMPLE_TO_MODE: default tries to replicate the mode of the + * source file to the target; with this flag, always use 0666 (or 0777 if + * source has exec bits set) for target. */ typedef enum { - GIT_CPDIR_CREATE_EMPTY_DIRS = 1, - GIT_CPDIR_COPY_SYMLINKS = 2, - GIT_CPDIR_COPY_DOTFILES = 4, - GIT_CPDIR_OVERWRITE = 8, - GIT_CPDIR_CHMOD = 16 + GIT_CPDIR_CREATE_EMPTY_DIRS = (1u << 0), + GIT_CPDIR_COPY_SYMLINKS = (1u << 1), + GIT_CPDIR_COPY_DOTFILES = (1u << 2), + GIT_CPDIR_OVERWRITE = (1u << 3), + GIT_CPDIR_CHMOD_DIRS = (1u << 4), + GIT_CPDIR_SIMPLE_TO_MODE = (1u << 5), } git_futils_cpdir_flags; /** diff --git a/src/refs.c b/src/refs.c index cca3f3ec8..113cadad5 100644 --- a/src/refs.c +++ b/src/refs.c @@ -1599,7 +1599,8 @@ static int ensure_segment_validity(const char *name) { const char *current = name; char prev = '\0'; - int lock_len = strlen(GIT_FILELOCK_EXTENSION); + const int lock_len = (int)strlen(GIT_FILELOCK_EXTENSION); + int segment_len; if (*current == '.') return -1; /* Refname starts with "." */ @@ -1620,12 +1621,14 @@ static int ensure_segment_validity(const char *name) prev = *current; } + segment_len = (int)(current - name); + /* A refname component can not end with ".lock" */ - if (current - name >= lock_len && + if (segment_len >= lock_len && !memcmp(current - lock_len, GIT_FILELOCK_EXTENSION, lock_len)) return -1; - return (int)(current - name); + return segment_len; } static bool is_all_caps_and_underscore(const char *name, size_t len) @@ -1700,7 +1703,7 @@ int git_reference__normalize_name( /* No empty segment is allowed when not normalizing */ if (segment_len == 0 && !normalize) goto cleanup; - + if (current[segment_len] == '\0') break; diff --git a/src/repo_template.h b/src/repo_template.h index 90ffe851b..099279aa7 100644 --- a/src/repo_template.h +++ b/src/repo_template.h @@ -11,10 +11,10 @@ #define GIT_OBJECTS_PACK_DIR GIT_OBJECTS_DIR "pack/" #define GIT_HOOKS_DIR "hooks/" -#define GIT_HOOKS_DIR_MODE 0755 +#define GIT_HOOKS_DIR_MODE 0777 #define GIT_HOOKS_README_FILE GIT_HOOKS_DIR "README.sample" -#define GIT_HOOKS_README_MODE 0755 +#define GIT_HOOKS_README_MODE 0777 #define GIT_HOOKS_README_CONTENT \ "#!/bin/sh\n"\ "#\n"\ @@ -23,16 +23,16 @@ "# more information.\n" #define GIT_INFO_DIR "info/" -#define GIT_INFO_DIR_MODE 0755 +#define GIT_INFO_DIR_MODE 0777 #define GIT_INFO_EXCLUDE_FILE GIT_INFO_DIR "exclude" -#define GIT_INFO_EXCLUDE_MODE 0644 +#define GIT_INFO_EXCLUDE_MODE 0666 #define GIT_INFO_EXCLUDE_CONTENT \ "# File patterns to ignore; see `git help ignore` for more information.\n"\ "# Lines that start with '#' are comments.\n" #define GIT_DESC_FILE "description" -#define GIT_DESC_MODE 0644 +#define GIT_DESC_MODE 0666 #define GIT_DESC_CONTENT \ "Unnamed repository; edit this file 'description' to name the repository.\n" diff --git a/src/repository.c b/src/repository.c index c28a0882d..278abfaf2 100644 --- a/src/repository.c +++ b/src/repository.c @@ -935,7 +935,7 @@ static int repo_write_gitlink( error = git_buf_printf(&buf, "%s %s", GIT_FILE_CONTENT_PREFIX, to_repo); if (!error) - error = repo_write_template(in_dir, true, DOT_GIT, 0644, true, buf.ptr); + error = repo_write_template(in_dir, true, DOT_GIT, 0666, true, buf.ptr); cleanup: git_buf_free(&buf); @@ -945,7 +945,7 @@ cleanup: static mode_t pick_dir_mode(git_repository_init_options *opts) { if (opts->mode == GIT_REPOSITORY_INIT_SHARED_UMASK) - return 0755; + return 0777; if (opts->mode == GIT_REPOSITORY_INIT_SHARED_GROUP) return (0775 | S_ISGID); if (opts->mode == GIT_REPOSITORY_INIT_SHARED_ALL) @@ -1006,17 +1006,9 @@ static int repo_init_structure( tdir = GIT_TEMPLATE_DIR; } - /* FIXME: GIT_CPDIR_CHMOD cannot applied here as an attempt - * would be made to chmod() all directories up to the last - * component of repo_dir, e.g., also /home etc. Recall that - * repo_dir is prettified at this point. - * - * Best probably would be to have the same logic as in - * git_futils_mkdir(), i.e., to separate the base from - * the path. - */ error = git_futils_cp_r(tdir, repo_dir, - GIT_CPDIR_COPY_SYMLINKS /*| GIT_CPDIR_CHMOD*/, dmode); + GIT_CPDIR_COPY_SYMLINKS | GIT_CPDIR_CHMOD_DIRS | + GIT_CPDIR_SIMPLE_TO_MODE, dmode); if (error < 0) { if (strcmp(tdir, GIT_TEMPLATE_DIR) != 0) @@ -1051,6 +1043,17 @@ static int repo_init_structure( return error; } +static int mkdir_parent(git_buf *buf, uint32_t mode, bool skip2) +{ + /* When making parent directories during repository initialization + * don't try to set gid or grant world write access + */ + return git_futils_mkdir( + buf->ptr, NULL, mode & ~(S_ISGID | 0002), + GIT_MKDIR_PATH | GIT_MKDIR_VERIFY_DIR | + (skip2 ? GIT_MKDIR_SKIP_LAST2 : GIT_MKDIR_SKIP_LAST)); +} + static int repo_init_directories( git_buf *repo_path, git_buf *wd_path, @@ -1058,14 +1061,36 @@ static int repo_init_directories( git_repository_init_options *opts) { int error = 0; - bool add_dotgit, has_dotgit, natural_wd; + bool is_bare, add_dotgit, has_dotgit, natural_wd; mode_t dirmode; + /* There are three possible rules for what we are allowed to create: + * - MKPATH means anything we need + * - MKDIR means just the .git directory and its parent and the workdir + * - Neither means only the .git directory can be created + * + * There are 5 "segments" of path that we might need to deal with: + * 1. The .git directory + * 2. The parent of the .git directory + * 3. Everything above the parent of the .git directory + * 4. The working directory (often the same as #2) + * 5. Everything above the working directory (often the same as #3) + * + * For all directories created, we start with the init_mode value for + * permissions and then strip off bits in some cases: + * + * For MKPATH, we create #3 (and #5) paths without S_ISGID or S_IWOTH + * For MKPATH and MKDIR, we create #2 (and #4) without S_ISGID + * For all rules, we create #1 using the untouched init_mode + */ + /* set up repo path */ + is_bare = ((opts->flags & GIT_REPOSITORY_INIT_BARE) != 0); + add_dotgit = (opts->flags & GIT_REPOSITORY_INIT_NO_DOTGIT_DIR) == 0 && - (opts->flags & GIT_REPOSITORY_INIT_BARE) == 0 && + !is_bare && git__suffixcmp(given_repo, "/" DOT_GIT) != 0 && git__suffixcmp(given_repo, "/" GIT_DIR) != 0; @@ -1078,7 +1103,7 @@ static int repo_init_directories( /* set up workdir path */ - if ((opts->flags & GIT_REPOSITORY_INIT_BARE) == 0) { + if (!is_bare) { if (opts->workdir_path) { if (git_path_join_unrooted( wd_path, opts->workdir_path, repo_path->ptr, NULL) < 0) @@ -1110,30 +1135,45 @@ static int repo_init_directories( dirmode = pick_dir_mode(opts); - if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 && has_dotgit) { - git_buf p = GIT_BUF_INIT; - if ((error = git_path_dirname_r(&p, repo_path->ptr)) >= 0) - error = git_futils_mkdir(p.ptr, NULL, dirmode, 0); - git_buf_free(&p); + if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) { + /* create path #5 */ + if (wd_path->size > 0 && + (error = mkdir_parent(wd_path, dirmode, false)) < 0) + return error; + + /* create path #3 (if not the same as #5) */ + if (!natural_wd && + (error = mkdir_parent(repo_path, dirmode, has_dotgit)) < 0) + return error; + } + + if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || + (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) + { + /* create path #4 */ + if (wd_path->size > 0 && + (error = git_futils_mkdir( + wd_path->ptr, NULL, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR)) < 0) + return error; + + /* create path #2 (if not the same as #4) */ + if (!natural_wd && + (error = git_futils_mkdir( + repo_path->ptr, NULL, dirmode & ~S_ISGID, + GIT_MKDIR_VERIFY_DIR | GIT_MKDIR_SKIP_LAST)) < 0) + return error; } if ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0 || has_dotgit) { - uint32_t mkflag = GIT_MKDIR_CHMOD; - if ((opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0) - mkflag |= GIT_MKDIR_PATH; - error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, mkflag); + /* create path #1 */ + error = git_futils_mkdir(repo_path->ptr, NULL, dirmode, + GIT_MKDIR_VERIFY_DIR | ((dirmode & S_ISGID) ? GIT_MKDIR_CHMOD : 0)); } - if (wd_path->size > 0 && - !natural_wd && - ((opts->flags & GIT_REPOSITORY_INIT_MKDIR) != 0 || - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) != 0)) - error = git_futils_mkdir(wd_path->ptr, NULL, dirmode & ~S_ISGID, - (opts->flags & GIT_REPOSITORY_INIT_MKPATH) ? GIT_MKDIR_PATH : 0); - /* prettify both directories now that they are created */ if (!error) { diff --git a/tests-clar/refs/branches/remote.c b/tests-clar/refs/branches/remote.c index 145c3182f..5272d1236 100644 --- a/tests-clar/refs/branches/remote.c +++ b/tests-clar/refs/branches/remote.c @@ -11,7 +11,7 @@ void test_refs_branches_remote__initialize(void) { g_repo = cl_git_sandbox_init("testrepo"); - expected_remote_name_length = strlen(expected_remote_name) + 1; + expected_remote_name_length = (int)strlen(expected_remote_name) + 1; } void test_refs_branches_remote__cleanup(void) diff --git a/tests-clar/repo/init.c b/tests-clar/repo/init.c index 97a5ff62a..e6f53083b 100644 --- a/tests-clar/repo/init.c +++ b/tests-clar/repo/init.c @@ -9,7 +9,7 @@ enum repo_mode { BARE_REPOSITORY = 1 }; -static git_repository *_repo; +static git_repository *_repo = NULL; void test_repo_init__initialize(void) { @@ -363,36 +363,166 @@ void test_repo_init__extended_1(void) cl_fixture_cleanup("root"); } +static void assert_hooks_match( + const char *template_dir, + const char *repo_dir, + const char *hook_path, + bool core_filemode) +{ + 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 (!core_filemode) { + expected_st.st_mode = expected_st.st_mode & ~0111; + st.st_mode = st.st_mode & ~0111; + } + + cl_assert_equal_i((int)expected_st.st_mode, (int)st.st_mode); + + git_buf_free(&expected); + git_buf_free(&actual); +} + +static void assert_mode_seems_okay( + const char *base, const char *path, + git_filemode_t expect_mode, bool expect_setgid, bool core_filemode) +{ + 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); + + if (!core_filemode) { + expect_mode = expect_mode & ~0111; + st.st_mode = st.st_mode & ~0111; + expect_setgid = false; + } + + if (S_ISGID != 0) { + if (expect_setgid) + cl_assert((st.st_mode & S_ISGID) != 0); + else + cl_assert((st.st_mode & S_ISGID) == 0); + } + + if ((expect_mode & 0111) != 0) + cl_assert((st.st_mode & 0111) != 0); + else + cl_assert((st.st_mode & 0111) == 0); + + cl_assert((expect_mode & 0170000) == (st.st_mode & 0170000)); +} + 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; - opts.flags = GIT_REPOSITORY_INIT_MKPATH | GIT_REPOSITORY_INIT_BARE | GIT_REPOSITORY_INIT_EXTERNAL_TEMPLATE; + 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_assert(git_futils_readbuffer(&expected,cl_fixture("template/description")) == GIT_OK); - cl_assert(git_futils_readbuffer(&actual,"templated.git/description") == GIT_OK); + cl_git_pass(git_futils_readbuffer( + &expected, cl_fixture("template/description"))); + cl_git_pass(git_futils_readbuffer( + &actual, "templated.git/description")); - cl_assert(!git_buf_cmp(&expected,&actual)); + cl_assert_equal_s(expected.ptr, actual.ptr); git_buf_free(&expected); git_buf_free(&actual); - cleanup_repository("templated.git"); + assert_hooks_match( + cl_fixture("template"), git_repository_path(_repo), + "hooks/update.sample", true); + + assert_hooks_match( + cl_fixture("template"), git_repository_path(_repo), + "hooks/link.sample", true); +} + +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; + git_config *config; + int filemode = true; + const char *repo_path = NULL; + + 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_repository_config(&config, _repo)); + cl_git_pass(git_config_get_bool(&filemode, config, "core.filemode")); + git_config_free(config); + + 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); + + 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); + + /* for a non-symlinked hook, it should have shared permissions now */ + assert_hooks_match( + cl_fixture("template"), git_repository_path(_repo), + "hooks/update.sample", filemode); + + /* 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", filemode); } 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)); @@ -401,5 +531,4 @@ void test_repo_init__can_reinit_an_initialized_repository(void) cl_assert_equal_s(git_repository_path(_repo), git_repository_path(reinit)); git_repository_free(reinit); - cleanup_repository("extended"); } diff --git a/tests-clar/resources/template/hooks/link.sample b/tests-clar/resources/template/hooks/link.sample new file mode 120000 index 000000000..771acc43b --- /dev/null +++ b/tests-clar/resources/template/hooks/link.sample @@ -0,0 +1 @@ +update.sample \ No newline at end of file diff --git a/tests-clar/resources/template/hooks/update.sample b/tests-clar/resources/template/hooks/update.sample new file mode 100755 index 000000000..3b5f41202 --- /dev/null +++ b/tests-clar/resources/template/hooks/update.sample @@ -0,0 +1,9 @@ +#!/bin/sh +# +# A sample hook to make sure that the `git_repository_init_ext()` function +# can correctly copy a hook over and set it up with the correct permissions. +# +# To enable a hook, you copy the file and remove the ".sample" suffix, but +# in this case, we're just making sure it gets copied correctly. + +echo "$GIT_DIR"