]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/init-db.c
Merge branch 'en/ort-perf-batch-9'
[thirdparty/git.git] / builtin / init-db.c
index 12ddda7e7bac51f74b2f7ca8a2b64ffe90fd8683..c19b35f1e6919e53c85b05a4f5a85f5cec5cbe15 100644 (file)
@@ -9,6 +9,7 @@
 #include "builtin.h"
 #include "exec-cmd.h"
 #include "parse-options.h"
+#include "worktree.h"
 
 #ifndef DEFAULT_GIT_TEMPLATE_DIR
 #define DEFAULT_GIT_TEMPLATE_DIR "/usr/share/git-core/templates"
 #define TEST_FILEMODE 1
 #endif
 
+#define GIT_DEFAULT_HASH_ENVIRONMENT "GIT_DEFAULT_HASH"
+
 static int init_is_bare_repository = 0;
 static int init_shared_repository = -1;
-static const char *init_db_template_dir;
 
 static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
                             DIR *dir)
@@ -91,12 +93,12 @@ static void copy_templates_1(struct strbuf *path, struct strbuf *template_path,
        }
 }
 
-static void copy_templates(const char *template_dir)
+static void copy_templates(const char *template_dir, const char *init_template_dir)
 {
        struct strbuf path = STRBUF_INIT;
        struct strbuf template_path = STRBUF_INIT;
        size_t template_len;
-       struct repository_format template_format;
+       struct repository_format template_format = REPOSITORY_FORMAT_INIT;
        struct strbuf err = STRBUF_INIT;
        DIR *dir;
        char *to_free = NULL;
@@ -104,7 +106,7 @@ static void copy_templates(const char *template_dir)
        if (!template_dir)
                template_dir = getenv(TEMPLATE_DIR_ENVIRONMENT);
        if (!template_dir)
-               template_dir = init_db_template_dir;
+               template_dir = init_template_dir;
        if (!template_dir)
                template_dir = to_free = system_path(DEFAULT_GIT_TEMPLATE_DIR);
        if (!template_dir[0]) {
@@ -148,14 +150,7 @@ free_return:
        free(to_free);
        strbuf_release(&path);
        strbuf_release(&template_path);
-}
-
-static int git_init_db_config(const char *k, const char *v, void *cb)
-{
-       if (!strcmp(k, "init.templatedir"))
-               return git_config_pathname(&init_db_template_dir, k, v);
-
-       return 0;
+       clear_repository_format(&template_format);
 }
 
 /*
@@ -172,20 +167,41 @@ static int needs_work_tree_config(const char *git_dir, const char *work_tree)
        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 *original_git_dir,
+                               const char *initial_branch,
+                               const struct repository_format *fmt,
+                               int quiet)
 {
        struct stat st1;
        struct strbuf buf = STRBUF_INIT;
        char *path;
-       char repo_version_string[10];
        char junk[2];
        int reinit;
        int filemode;
        struct strbuf err = STRBUF_INIT;
-
-       /* Just look for `init.templatedir` */
-       git_config(git_init_db_config, NULL);
+       const char *init_template_dir = NULL;
+       const char *work_tree = get_git_work_tree();
 
        /*
         * First copy the templates -- we might have the default
@@ -196,7 +212,8 @@ static int create_default_files(const char *template_path,
         * values (since we've just potentially changed what's available on
         * disk).
         */
-       copy_templates(template_path);
+       git_config_get_value("init.templatedir", &init_template_dir);
+       copy_templates(template_path, init_template_dir);
        git_config_clear();
        reset_shared_repository();
        git_config(git_default_config, NULL);
@@ -205,7 +222,7 @@ static int create_default_files(const char *template_path,
         * We must make sure command-line options continue to override any
         * values we might have just re-read from the config.
         */
-       is_bare_repository_cfg = init_is_bare_repository;
+       is_bare_repository_cfg = init_is_bare_repository || !work_tree;
        if (init_shared_repository != -1)
                set_shared_repository(init_shared_repository);
 
@@ -228,21 +245,29 @@ static int create_default_files(const char *template_path,
                die("failed to set up refs db: %s", err.buf);
 
        /*
-        * Create the default symlink from ".git/HEAD" to the "master"
-        * branch, if it does not exist yet.
+        * 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) {
-               if (create_symref("HEAD", "refs/heads/master", NULL) < 0)
+               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);
        }
 
-       /* This forces creation of new config file */
-       xsnprintf(repo_version_string, sizeof(repo_version_string),
-                 "%d", GIT_REPO_VERSION);
-       git_config_set("core.repositoryformatversion", repo_version_string);
+       initialize_repository_version(fmt->hash_algo, 0);
 
        /* Check filemode trustability */
        path = git_path_buf(&buf, "config");
@@ -261,7 +286,6 @@ static int create_default_files(const char *template_path,
        if (is_bare_repository())
                git_config_set("core.bare", "true");
        else {
-               const char *work_tree = get_git_work_tree();
                git_config_set("core.bare", "false");
                /* allow template config file to override the default */
                if (log_all_ref_updates == LOG_REFS_UNSET)
@@ -330,17 +354,40 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
 
                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, unsigned int flags)
+           const char *template_dir, int hash, const char *initial_branch,
+           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;
 
        if (real_git_dir) {
                struct stat st;
@@ -351,16 +398,19 @@ int init_db(const char *git_dir, const char *real_git_dir,
                if (!exist_ok && !stat(real_git_dir, &st))
                        die(_("%s already exists"), real_git_dir);
 
-               set_git_dir(real_path(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(real_path(git_dir));
+               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);
 
        init_is_bare_repository = is_bare_repository();
@@ -370,9 +420,16 @@ int init_db(const char *git_dir, const char *real_git_dir,
         * 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();
+       check_repository_format(&repo_fmt);
 
-       reinit = create_default_files(template_dir, original_git_dir);
+       validate_hash_algorithm(&repo_fmt, hash);
+
+       reinit = create_default_files(template_dir, original_git_dir,
+                                     initial_branch, &repo_fmt,
+                                     flags & INIT_DB_QUIET);
+       if (reinit && initial_branch)
+               warning(_("re-init: ignored --initial-branch=%s"),
+                       initial_branch);
 
        create_object_directory();
 
@@ -451,6 +508,7 @@ static int guess_repository_type(const char *git_dir)
 
 static int shared_callback(const struct option *opt, const char *arg, int unset)
 {
+       BUG_ON_OPT_NEG(unset);
        *((int *) opt->value) = (arg) ? git_config_perm("arg", arg) : PERM_GROUP;
        return 0;
 }
@@ -473,6 +531,9 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        const char *work_tree;
        const char *template_dir = NULL;
        unsigned int flags = 0;
+       const char *object_format = NULL;
+       const char *initial_branch = NULL;
+       int hash_algo = GIT_HASH_UNKNOWN;
        const struct option init_db_options[] = {
                OPT_STRING(0, "template", &template_dir, N_("template-directory"),
                                N_("directory from which templates will be used")),
@@ -485,14 +546,26 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                OPT_BIT('q', "quiet", &flags, N_("be quiet"), INIT_DB_QUIET),
                OPT_STRING(0, "separate-git-dir", &real_git_dir, N_("gitdir"),
                           N_("separate git dir from working tree")),
+               OPT_STRING('b', "initial-branch", &initial_branch, N_("name"),
+                          N_("override the name of the initial branch")),
+               OPT_STRING(0, "object-format", &object_format, N_("hash"),
+                          N_("specify the hash algorithm to use")),
                OPT_END()
        };
 
        argc = parse_options(argc, argv, prefix, init_db_options, init_db_usage, 0);
 
+       if (real_git_dir && is_bare_repository_cfg == 1)
+               die(_("--separate-git-dir and --bare are mutually exclusive"));
+
        if (real_git_dir && !is_absolute_path(real_git_dir))
                real_git_dir = real_pathdup(real_git_dir, 1);
 
+       if (template_dir && *template_dir && !is_absolute_path(template_dir)) {
+               template_dir = absolute_pathdup(template_dir);
+               UNLEAK(template_dir);
+       }
+
        if (argc == 1) {
                int mkdir_tried = 0;
        retry:
@@ -534,6 +607,12 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                free(cwd);
        }
 
+       if (object_format) {
+               hash_algo = hash_algo_by_name(object_format);
+               if (hash_algo == GIT_HASH_UNKNOWN)
+                       die(_("unknown hash algorithm '%s'"), object_format);
+       }
+
        if (init_shared_repository != -1)
                set_shared_repository(init_shared_repository);
 
@@ -541,8 +620,8 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
         * GIT_WORK_TREE makes sense only in conjunction with GIT_DIR
         * without --bare.  Catch the error early.
         */
-       git_dir = getenv(GIT_DIR_ENVIRONMENT);
-       work_tree = getenv(GIT_WORK_TREE_ENVIRONMENT);
+       git_dir = xstrdup_or_null(getenv(GIT_DIR_ENVIRONMENT));
+       work_tree = xstrdup_or_null(getenv(GIT_WORK_TREE_ENVIRONMENT));
        if ((!git_dir || is_bare_repository_cfg == 1) && work_tree)
                die(_("%s (or --work-tree=<directory>) not allowed without "
                          "specifying %s (or --git-dir=<directory>)"),
@@ -555,6 +634,30 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
        if (!git_dir)
                git_dir = DEFAULT_GIT_DIR_ENVIRONMENT;
 
+       /*
+        * When --separate-git-dir is used inside a linked worktree, take
+        * care to ensure that the common .git/ directory is relocated, not
+        * the worktree-specific .git/worktrees/<id>/ directory.
+        */
+       if (real_git_dir) {
+               int err;
+               const char *p;
+               struct strbuf sb = STRBUF_INIT;
+
+               p = read_gitfile_gently(git_dir, &err);
+               if (p && get_common_dir(&sb, p)) {
+                       struct strbuf mainwt = STRBUF_INIT;
+
+                       strbuf_addbuf(&mainwt, &sb);
+                       strbuf_strip_suffix(&mainwt, "/.git");
+                       if (chdir(mainwt.buf) < 0)
+                               die_errno(_("cannot chdir to %s"), mainwt.buf);
+                       strbuf_release(&mainwt);
+                       git_dir = strbuf_detach(&sb, NULL);
+               }
+               strbuf_release(&sb);
+       }
+
        if (is_bare_repository_cfg < 0)
                is_bare_repository_cfg = guess_repository_type(git_dir);
 
@@ -576,12 +679,17 @@ int cmd_init_db(int argc, const char **argv, const char *prefix)
                                   get_git_work_tree());
        }
        else {
+               if (real_git_dir)
+                       die(_("--separate-git-dir incompatible with bare repository"));
                if (work_tree)
                        set_git_work_tree(work_tree);
        }
 
        UNLEAK(real_git_dir);
+       UNLEAK(git_dir);
+       UNLEAK(work_tree);
 
        flags |= INIT_DB_EXIST_OK;
-       return init_db(git_dir, real_git_dir, template_dir, flags);
+       return init_db(git_dir, real_git_dir, template_dir, hash_algo,
+                      initial_branch, flags);
 }