From: Johannes Schindelin Date: Wed, 17 Apr 2024 09:39:09 +0000 (+0200) Subject: Sync with 2.41.1 X-Git-Tag: v2.42.2~1 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=be348e9815285636789dccd4aef85b5bab5f8557;p=thirdparty%2Fgit.git Sync with 2.41.1 * maint-2.41: (38 commits) Git 2.41.1 Git 2.40.2 Git 2.39.4 fsck: warn about symlink pointing inside a gitdir core.hooksPath: add some protection while cloning init.templateDir: consider this config setting protected clone: prevent hooks from running during a clone Add a helper function to compare file contents init: refactor the template directory discovery into its own function find_hook(): refactor the `STRIP_EXTENSION` logic clone: when symbolic links collide with directories, keep the latter entry: report more colliding paths t5510: verify that D/F confusion cannot lead to an RCE submodule: require the submodule path to contain directories only clone_submodule: avoid using `access()` on directories submodules: submodule paths must not contain symlinks clone: prevent clashing git dirs when cloning submodule in parallel t7423: add tests for symlinked submodule directories has_dir_name(): do not get confused by characters < '/' docs: document security issues around untrusted .git dirs ... --- be348e9815285636789dccd4aef85b5bab5f8557 diff --cc builtin/clone.c index c6357af949,d6545d036f..72e89c309a --- a/builtin/clone.c +++ b/builtin/clone.c @@@ -930,7 -959,8 +959,9 @@@ int cmd_clone(int argc, const char **ar int submodule_progress; int filter_submodules = 0; int hash_algo; + const int do_not_override_repo_unix_permissions = -1; + const char *template_dir; + char *template_dir_dup = NULL; struct transport_ls_refs_options transport_ls_refs_options = TRANSPORT_LS_REFS_OPTIONS_INIT; @@@ -1097,8 -1134,8 +1135,8 @@@ } } - init_db(git_dir, real_git_dir, option_template, GIT_HASH_UNKNOWN, NULL, + init_db(git_dir, real_git_dir, template_dir, GIT_HASH_UNKNOWN, NULL, - INIT_DB_QUIET); + do_not_override_repo_unix_permissions, INIT_DB_QUIET); if (real_git_dir) { free((char *)git_dir); diff --cc builtin/upload-pack.c index 9b021ef026,edb01aab70..272cddaafd --- a/builtin/upload-pack.c +++ b/builtin/upload-pack.c @@@ -36,7 -36,9 +36,9 @@@ int cmd_upload_pack(int argc, const cha }; packet_trace_identity("upload-pack"); - read_replace_refs = 0; + disable_replace_refs(); + /* TODO: This should use NO_LAZY_FETCH_ENVIRONMENT */ + xsetenv("GIT_NO_LAZY_FETCH", "1", 0); argc = parse_options(argc, argv, prefix, options, upload_pack_usage, 0); diff --cc config.c index 3846a37be9,66fc088bd8..cccda75a78 --- a/config.c +++ b/config.c @@@ -1558,8 -1596,19 +1558,19 @@@ static int git_default_core_config(cons if (!strcmp(var, "core.attributesfile")) return git_config_pathname(&git_attributes_file, var, value); - if (!strcmp(var, "core.hookspath")) + if (!strcmp(var, "core.hookspath")) { - if (current_config_scope() == CONFIG_SCOPE_LOCAL && ++ if (ctx->kvi && ctx->kvi->scope == CONFIG_SCOPE_LOCAL && + git_env_bool("GIT_CLONE_PROTECTION_ACTIVE", 0)) + die(_("active `core.hooksPath` found in the local " + "repository config:\n\t%s\nFor security " + "reasons, this is disallowed by default.\nIf " + "this is intentional and the hook should " + "actually be run, please\nrun the command " + "again with " + "`GIT_CLONE_PROTECTION_ACTIVE=false`"), + value); return git_config_pathname(&git_hooks_path, var, value); + } if (!strcmp(var, "core.bare")) { is_bare_repository_cfg = git_config_bool(var, value); diff --cc copy.c index 23d84c6c1d,1b4069cfea..3df156f6ce --- a/copy.c +++ b/copy.c @@@ -1,6 -1,10 +1,9 @@@ #include "git-compat-util.h" #include "copy.h" #include "path.h" -#include "wrapper.h" + #include "gettext.h" + #include "strbuf.h" + #include "abspath.h" int copy_fd(int ifd, int ofd) { diff --cc remote-curl.c index ef05752ca5,a0777e3f7a..dad241e89d --- a/remote-curl.c +++ b/remote-curl.c @@@ -1,4 -1,6 +1,5 @@@ #include "git-compat-util.h" + #include "git-curl-compat.h" -#include "alloc.h" #include "config.h" #include "environment.h" #include "gettext.h" diff --cc setup.c index 2e607632db,84324e35c6..881935f095 --- a/setup.c +++ b/setup.c @@@ -16,7 -12,8 +16,8 @@@ #include "promisor-remote.h" #include "quote.h" #include "trace2.h" -#include "wrapper.h" +#include "worktree.h" + #include "exec-cmd.h" static int inside_git_dir = -1; static int inside_work_tree = -1; @@@ -1179,9 -1173,28 +1180,30 @@@ static int ensure_valid_ownership(cons return data.is_safe; } + void die_upon_dubious_ownership(const char *gitfile, const char *worktree, + const char *gitdir) + { + struct strbuf report = STRBUF_INIT, quoted = STRBUF_INIT; + const char *path; + + if (ensure_valid_ownership(gitfile, worktree, gitdir, &report)) + return; + + strbuf_complete(&report, '\n'); + path = gitfile ? gitfile : gitdir; + sq_quote_buf_pretty("ed, path); + + die(_("detected dubious ownership in repository at '%s'\n" + "%s" + "To add an exception for this directory, call:\n" + "\n" + "\tgit config --global --add safe.directory %s"), + path, report.buf, quoted.buf); + } + -static int allowed_bare_repo_cb(const char *key, const char *value, void *d) +static int allowed_bare_repo_cb(const char *key, const char *value, + const struct config_context *ctx UNUSED, + void *d) { enum allowed_bare_repo *allowed_bare_repo = d; @@@ -1706,490 -1730,56 +1728,531 @@@ int daemonize(void #endif } -#ifndef DEFAULT_GIT_TEMPLATE_DIR -#define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates" -#endif - + struct template_dir_cb_data { + char *path; + int initialized; + }; + -static int template_dir_cb(const char *key, const char *value, void *d) ++static int template_dir_cb(const char *key, const char *value, ++ const struct config_context *ctx, void *d) + { + struct template_dir_cb_data *data = d; + + if (strcmp(key, "init.templatedir")) + return 0; + + if (!value) { + data->path = NULL; + } else { + char *path = NULL; + + FREE_AND_NULL(data->path); + if (!git_config_pathname((const char **)&path, key, value)) + data->path = path ? path : xstrdup(value); + } + + return 0; + } + + const char *get_template_dir(const char *option_template) + { + const char *template_dir = option_template; + + if (!template_dir) + template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); + if (!template_dir) { + static struct template_dir_cb_data data; + + if (!data.initialized) { + git_protected_config(template_dir_cb, &data); + data.initialized = 1; + } + template_dir = data.path; + } + if (!template_dir) { + static char *dir; + + if (!dir) + dir = system_path(DEFAULT_GIT_TEMPLATE_DIR); + template_dir = dir; + } + return template_dir; + } ++ +#ifdef NO_TRUSTABLE_FILEMODE +#define TEST_FILEMODE 0 +#else +#define TEST_FILEMODE 1 +#endif + +#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH" + +static void copy_templates_1(struct strbuf *path, struct strbuf *template_path, + DIR *dir) +{ + size_t path_baselen = path->len; + size_t template_baselen = template_path->len; + struct dirent *de; + + /* Note: if ".git/hooks" file exists in the repository being + * re-initialized, /etc/core-git/templates/hooks/update would + * cause "git init" to fail here. I think this is sane but + * it means that the set of templates we ship by default, along + * with the way the namespace under .git/ is organized, should + * be really carefully chosen. + */ + safe_create_dir(path->buf, 1); + while ((de = readdir(dir)) != NULL) { + struct stat st_git, st_template; + int exists = 0; + + strbuf_setlen(path, path_baselen); + strbuf_setlen(template_path, template_baselen); + + if (de->d_name[0] == '.') + continue; + strbuf_addstr(path, de->d_name); + strbuf_addstr(template_path, de->d_name); + if (lstat(path->buf, &st_git)) { + if (errno != ENOENT) + die_errno(_("cannot stat '%s'"), path->buf); + } + else + exists = 1; + + if (lstat(template_path->buf, &st_template)) + die_errno(_("cannot stat template '%s'"), template_path->buf); + + if (S_ISDIR(st_template.st_mode)) { + DIR *subdir = opendir(template_path->buf); + if (!subdir) + die_errno(_("cannot opendir '%s'"), template_path->buf); + strbuf_addch(path, '/'); + strbuf_addch(template_path, '/'); + copy_templates_1(path, template_path, subdir); + closedir(subdir); + } + else if (exists) + continue; + else if (S_ISLNK(st_template.st_mode)) { + struct strbuf lnk = STRBUF_INIT; + if (strbuf_readlink(&lnk, template_path->buf, + st_template.st_size) < 0) + die_errno(_("cannot readlink '%s'"), template_path->buf); + if (symlink(lnk.buf, path->buf)) + die_errno(_("cannot symlink '%s' '%s'"), + lnk.buf, path->buf); + strbuf_release(&lnk); + } + else if (S_ISREG(st_template.st_mode)) { + if (copy_file(path->buf, template_path->buf, st_template.st_mode)) + die_errno(_("cannot copy '%s' to '%s'"), + template_path->buf, path->buf); + } + else + error(_("ignoring template %s"), template_path->buf); + } +} + - static void copy_templates(const char *template_dir, const char *init_template_dir) ++static void copy_templates(const char *option_template) +{ ++ const char *template_dir = get_template_dir(option_template); + struct strbuf path = STRBUF_INIT; + struct strbuf template_path = STRBUF_INIT; + size_t template_len; + struct repository_format template_format = REPOSITORY_FORMAT_INIT; + struct strbuf err = STRBUF_INIT; + DIR *dir; + char *to_free = NULL; + - if (!template_dir) - template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT); - if (!template_dir) - template_dir = init_template_dir; - if (!template_dir) - template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR); - if (!template_dir[0]) { - free(to_free); ++ if (!template_dir || !*template_dir) + return; - } + + strbuf_addstr(&template_path, template_dir); + strbuf_complete(&template_path, '/'); + template_len = template_path.len; + + dir = opendir(template_path.buf); + if (!dir) { + warning(_("templates not found in %s"), template_dir); + goto free_return; + } + + /* Make sure that template is from the correct vintage */ + strbuf_addstr(&template_path, "config"); + read_repository_format(&template_format, template_path.buf); + strbuf_setlen(&template_path, template_len); + + /* + * No mention of version at all is OK, but anything else should be + * verified. + */ + if (template_format.version >= 0 && + verify_repository_format(&template_format, &err) < 0) { + warning(_("not copying templates from '%s': %s"), + template_dir, err.buf); + strbuf_release(&err); + goto close_free_return; + } + + strbuf_addstr(&path, get_git_common_dir()); + strbuf_complete(&path, '/'); + copy_templates_1(&path, &template_path, dir); +close_free_return: + closedir(dir); +free_return: + free(to_free); + strbuf_release(&path); + strbuf_release(&template_path); + clear_repository_format(&template_format); +} + +/* + * If the git_dir is not directly inside the working tree, then git will not + * find it by default, and we need to set the worktree explicitly. + */ +static int needs_work_tree_config(const char *git_dir, const char *work_tree) +{ + if (!strcmp(work_tree, "/") && !strcmp(git_dir, "/.git")) + return 0; + if (skip_prefix(git_dir, work_tree, &git_dir) && + !strcmp(git_dir, "/.git")) + return 0; + return 1; +} + +void initialize_repository_version(int hash_algo, int reinit) +{ + char repo_version_string[10]; + int repo_version = GIT_REPO_VERSION; + + if (hash_algo != GIT_HASH_SHA1) + repo_version = GIT_REPO_VERSION_READ; + + /* This forces creation of new config file */ + xsnprintf(repo_version_string, sizeof(repo_version_string), + "%d", repo_version); + git_config_set("core.repositoryformatversion", repo_version_string); + + if (hash_algo != GIT_HASH_SHA1) + git_config_set("extensions.objectformat", + hash_algos[hash_algo].name); + else if (reinit) + git_config_set_gently("extensions.objectformat", NULL); +} + +static int create_default_files(const char *template_path, + const char *original_git_dir, + const char *initial_branch, + const struct repository_format *fmt, + int prev_bare_repository, + int init_shared_repository, + int quiet) +{ + struct stat st1; + struct strbuf buf = STRBUF_INIT; + char *path; + char junk[2]; + int reinit; + int filemode; + struct strbuf err = STRBUF_INIT; - const char *init_template_dir = NULL; + const char *work_tree = get_git_work_tree(); + + /* + * First copy the templates -- we might have the default + * config file there, in which case we would want to read + * from it after installing. + * + * Before reading that config, we also need to clear out any cached + * values (since we've just potentially changed what's available on + * disk). + */ - git_config_get_pathname("init.templatedir", &init_template_dir); - copy_templates(template_path, init_template_dir); - free((char *)init_template_dir); ++ copy_templates(template_path); + git_config_clear(); + reset_shared_repository(); + git_config(git_default_config, NULL); + + /* + * We must make sure command-line options continue to override any + * values we might have just re-read from the config. + */ + if (init_shared_repository != -1) + set_shared_repository(init_shared_repository); + /* + * TODO: heed core.bare from config file in templates if no + * command-line override given + */ + is_bare_repository_cfg = prev_bare_repository || !work_tree; + /* TODO (continued): + * + * Unfortunately, the line above is equivalent to + * is_bare_repository_cfg = !work_tree; + * which ignores the config entirely even if no `--[no-]bare` + * command line option was present. + * + * To see why, note that before this function, there was this call: + * prev_bare_repository = is_bare_repository() + * expanding the right hand side: + * = is_bare_repository_cfg && !get_git_work_tree() + * = is_bare_repository_cfg && !work_tree + * note that the last simplification above is valid because nothing + * calls repo_init() or set_git_work_tree() between any of the + * relevant calls in the code, and thus the !get_git_work_tree() + * calls will return the same result each time. So, what we are + * interested in computing is the right hand side of the line of + * code just above this comment: + * prev_bare_repository || !work_tree + * = is_bare_repository_cfg && !work_tree || !work_tree + * = !work_tree + * because "A && !B || !B == !B" for all boolean values of A & B. + */ + + /* + * We would have created the above under user's umask -- under + * shared-repository settings, we would need to fix them up. + */ + if (get_shared_repository()) { + adjust_shared_perm(get_git_dir()); + } + + /* + * We need to create a "refs" dir in any case so that older + * versions of git can tell that this is a repository. + */ + safe_create_dir(git_path("refs"), 1); + adjust_shared_perm(git_path("refs")); + + if (refs_init_db(&err)) + die("failed to set up refs db: %s", err.buf); + + /* + * Point the HEAD symref to the initial branch with if HEAD does + * not yet exist. + */ + path = git_path_buf(&buf, "HEAD"); + reinit = (!access(path, R_OK) + || readlink(path, junk, sizeof(junk)-1) != -1); + if (!reinit) { + char *ref; + + if (!initial_branch) + initial_branch = git_default_branch_name(quiet); + + ref = xstrfmt("refs/heads/%s", initial_branch); + if (check_refname_format(ref, 0) < 0) + die(_("invalid initial branch name: '%s'"), + initial_branch); + + if (create_symref("HEAD", ref, NULL) < 0) + exit(1); + free(ref); + } + + initialize_repository_version(fmt->hash_algo, 0); + + /* Check filemode trustability */ + path = git_path_buf(&buf, "config"); + filemode = TEST_FILEMODE; + if (TEST_FILEMODE && !lstat(path, &st1)) { + struct stat st2; + filemode = (!chmod(path, st1.st_mode ^ S_IXUSR) && + !lstat(path, &st2) && + st1.st_mode != st2.st_mode && + !chmod(path, st1.st_mode)); + if (filemode && !reinit && (st1.st_mode & S_IXUSR)) + filemode = 0; + } + git_config_set("core.filemode", filemode ? "true" : "false"); + + if (is_bare_repository()) + git_config_set("core.bare", "true"); + else { + git_config_set("core.bare", "false"); + /* allow template config file to override the default */ + if (log_all_ref_updates == LOG_REFS_UNSET) + git_config_set("core.logallrefupdates", "true"); + if (needs_work_tree_config(original_git_dir, work_tree)) + git_config_set("core.worktree", work_tree); + } + + if (!reinit) { + /* Check if symlink is supported in the work tree */ + path = git_path_buf(&buf, "tXXXXXX"); + if (!close(xmkstemp(path)) && + !unlink(path) && + !symlink("testing", path) && + !lstat(path, &st1) && + S_ISLNK(st1.st_mode)) + unlink(path); /* good */ + else + git_config_set("core.symlinks", "false"); + + /* Check if the filesystem is case-insensitive */ + path = git_path_buf(&buf, "CoNfIg"); + if (!access(path, F_OK)) + git_config_set("core.ignorecase", "true"); + probe_utf8_pathname_composition(); + } + + strbuf_release(&buf); + return reinit; +} + +static void create_object_directory(void) +{ + struct strbuf path = STRBUF_INIT; + size_t baselen; + + strbuf_addstr(&path, get_object_directory()); + baselen = path.len; + + safe_create_dir(path.buf, 1); + + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/pack"); + safe_create_dir(path.buf, 1); + + strbuf_setlen(&path, baselen); + strbuf_addstr(&path, "/info"); + safe_create_dir(path.buf, 1); + + strbuf_release(&path); +} + +static void separate_git_dir(const char *git_dir, const char *git_link) +{ + struct stat st; + + if (!stat(git_link, &st)) { + const char *src; + + if (S_ISREG(st.st_mode)) + src = read_gitfile(git_link); + else if (S_ISDIR(st.st_mode)) + src = git_link; + else + die(_("unable to handle file type %d"), (int)st.st_mode); + + if (rename(src, git_dir)) + die_errno(_("unable to move %s to %s"), src, git_dir); + repair_worktrees(NULL, NULL); + } + + write_file(git_link, "gitdir: %s", git_dir); +} + +static void validate_hash_algorithm(struct repository_format *repo_fmt, int hash) +{ + const char *env = getenv(GIT_DEFAULT_HASH_ENVIRONMENT); + /* + * If we already have an initialized repo, don't allow the user to + * specify a different algorithm, as that could cause corruption. + * Otherwise, if the user has specified one on the command line, use it. + */ + if (repo_fmt->version >= 0 && hash != GIT_HASH_UNKNOWN && hash != repo_fmt->hash_algo) + die(_("attempt to reinitialize repository with different hash")); + else if (hash != GIT_HASH_UNKNOWN) + repo_fmt->hash_algo = hash; + else if (env) { + int env_algo = hash_algo_by_name(env); + if (env_algo == GIT_HASH_UNKNOWN) + die(_("unknown hash algorithm '%s'"), env); + repo_fmt->hash_algo = env_algo; + } +} + +int init_db(const char *git_dir, const char *real_git_dir, + const char *template_dir, int hash, const char *initial_branch, + int init_shared_repository, unsigned int flags) +{ + int reinit; + int exist_ok = flags & INIT_DB_EXIST_OK; + char *original_git_dir = real_pathdup(git_dir, 1); + struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT; + int prev_bare_repository; + + if (real_git_dir) { + struct stat st; + + if (!exist_ok && !stat(git_dir, &st)) + die(_("%s already exists"), git_dir); + + if (!exist_ok && !stat(real_git_dir, &st)) + die(_("%s already exists"), real_git_dir); + + set_git_dir(real_git_dir, 1); + git_dir = get_git_dir(); + separate_git_dir(git_dir, original_git_dir); + } + else { + set_git_dir(git_dir, 1); + git_dir = get_git_dir(); + } + startup_info->have_repository = 1; + + /* Ensure `core.hidedotfiles` is processed */ + git_config(platform_core_config, NULL); + + safe_create_dir(git_dir, 0); + + prev_bare_repository = is_bare_repository(); + + /* Check to see if the repository version is right. + * Note that a newly created repository does not have + * config file, so this will not fail. What we are catching + * is an attempt to reinitialize new repository with an old tool. + */ + check_repository_format(&repo_fmt); + + validate_hash_algorithm(&repo_fmt, hash); + + reinit = create_default_files(template_dir, original_git_dir, + initial_branch, &repo_fmt, + prev_bare_repository, + init_shared_repository, + flags & INIT_DB_QUIET); + if (reinit && initial_branch) + warning(_("re-init: ignored --initial-branch=%s"), + initial_branch); + + create_object_directory(); + + if (get_shared_repository()) { + char buf[10]; + /* We do not spell "group" and such, so that + * the configuration can be read by older version + * of git. Note, we use octal numbers for new share modes, + * and compatibility values for PERM_GROUP and + * PERM_EVERYBODY. + */ + if (get_shared_repository() < 0) + /* force to the mode value */ + xsnprintf(buf, sizeof(buf), "0%o", -get_shared_repository()); + else if (get_shared_repository() == PERM_GROUP) + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_GROUP); + else if (get_shared_repository() == PERM_EVERYBODY) + xsnprintf(buf, sizeof(buf), "%d", OLD_PERM_EVERYBODY); + else + BUG("invalid value for shared_repository"); + git_config_set("core.sharedrepository", buf); + git_config_set("receive.denyNonFastforwards", "true"); + } + + if (!(flags & INIT_DB_QUIET)) { + int len = strlen(git_dir); + + if (reinit) + printf(get_shared_repository() + ? _("Reinitialized existing shared Git repository in %s%s\n") + : _("Reinitialized existing Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + else + printf(get_shared_repository() + ? _("Initialized empty shared Git repository in %s%s\n") + : _("Initialized empty Git repository in %s%s\n"), + git_dir, len && git_dir[len-1] != '/' ? "/" : ""); + } + + free(original_git_dir); + return 0; +} diff --cc setup.h index b48cf1c43b,e7708f5739..01a6ad7735 --- a/setup.h +++ b/setup.h @@@ -41,30 -41,19 +41,42 @@@ const char *read_gitfile_gently(const c const char *resolve_gitdir_gently(const char *suspect, int *return_error_code); #define resolve_gitdir(path) resolve_gitdir_gently((path), NULL) + /* + * Check if a repository is safe and die if it is not, by verifying the + * ownership of the worktree (if any), the git directory, and the gitfile (if + * any). + * + * Exemptions for known-safe repositories can be added via `safe.directory` + * config settings; for non-bare repositories, their worktree needs to be + * added, for bare ones their git directory. + */ + void die_upon_dubious_ownership(const char *gitfile, const char *worktree, + const char *gitdir); + void setup_work_tree(void); + +/* + * discover_git_directory_reason() is similar to discover_git_directory(), + * except it returns an enum value instead. It is important to note that + * a zero-valued return here is actually GIT_DIR_NONE, which is different + * from discover_git_directory. + */ +enum discovery_result { + GIT_DIR_EXPLICIT = 1, + GIT_DIR_DISCOVERED = 2, + GIT_DIR_BARE = 3, + /* these are errors */ + GIT_DIR_HIT_CEILING = -1, + GIT_DIR_HIT_MOUNT_POINT = -2, + GIT_DIR_INVALID_GITFILE = -3, + GIT_DIR_INVALID_OWNERSHIP = -4, + GIT_DIR_DISALLOWED_BARE = -5, + GIT_DIR_INVALID_FORMAT = -6, + GIT_DIR_CWD_FAILURE = -7, +}; +enum discovery_result discover_git_directory_reason(struct strbuf *commondir, + struct strbuf *gitdir); + /* * Find the commondir and gitdir of the repository that contains the current * working directory, without changing the working directory or other global @@@ -169,15 -152,8 +181,17 @@@ int verify_repository_format(const stru */ void check_repository_format(struct repository_format *fmt); + const char *get_template_dir(const char *option_template); + +#define INIT_DB_QUIET 0x0001 +#define INIT_DB_EXIST_OK 0x0002 + +int init_db(const char *git_dir, const char *real_git_dir, + const char *template_dir, int hash_algo, + const char *initial_branch, int init_shared_repository, + unsigned int flags); +void initialize_repository_version(int hash_algo, int reinit); + /* * NOTE NOTE NOTE!! * diff --cc t/t7406-submodule-update.sh index 00651c25cb,dae87090e0..fb82f760c3 --- a/t/t7406-submodule-update.sh +++ b/t/t7406-submodule-update.sh @@@ -1179,27 -1179,52 +1179,75 @@@ test_expect_success 'submodule update - test_cmp expect.err actual.err ' +add_submodule_commit_and_validate () { + HASH=$(git rev-parse HEAD) && + git update-index --add --cacheinfo 160000,$HASH,sub && + git commit -m "create submodule" && + echo "160000 commit $HASH sub" >expect && + git ls-tree HEAD -- sub >actual && + test_cmp expect actual +} + +test_expect_success 'commit with staged submodule change' ' + add_submodule_commit_and_validate +' + +test_expect_success 'commit with staged submodule change with ignoreSubmodules dirty' ' + test_config diff.ignoreSubmodules dirty && + add_submodule_commit_and_validate +' + +test_expect_success 'commit with staged submodule change with ignoreSubmodules all' ' + test_config diff.ignoreSubmodules all && + add_submodule_commit_and_validate +' + + test_expect_success CASE_INSENSITIVE_FS,SYMLINKS \ + 'submodule paths must not follow symlinks' ' + + # This is only needed because we want to run this in a self-contained + # test without having to spin up an HTTP server; However, it would not + # be needed in a real-world scenario where the submodule is simply + # hosted on a public site. + test_config_global protocol.file.allow always && + + # Make sure that Git tries to use symlinks on Windows + test_config_global core.symlinks true && + + tell_tale_path="$PWD/tell.tale" && + git init hook && + ( + cd hook && + mkdir -p y/hooks && + write_script y/hooks/post-checkout <<-EOF && + echo HOOK-RUN >&2 + echo hook-run >"$tell_tale_path" + EOF + git add y/hooks/post-checkout && + test_tick && + git commit -m post-checkout + ) && + + hook_repo_path="$(pwd)/hook" && + git init captain && + ( + cd captain && + git submodule add --name x/y "$hook_repo_path" A/modules/x && + test_tick && + git commit -m add-submodule && + + printf .git >dotgit.txt && + git hash-object -w --stdin dot-git.hash && + printf "120000 %s 0\ta\n" "$(cat dot-git.hash)" >index.info && + git update-index --index-info err && + ! grep HOOK-RUN err && + test_path_is_missing "$tell_tale_path" + ' + test_done