]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jc/safe-implicit-bare'
authorJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2024 21:55:13 +0000 (14:55 -0700)
committerJunio C Hamano <gitster@pobox.com>
Thu, 21 Mar 2024 21:55:13 +0000 (14:55 -0700)
Users with safe.bareRepository=explicit can still work from within
$GIT_DIR of a seconary worktree (which resides at .git/worktrees/$name/)
of the primary worktree without explicitly specifying the $GIT_DIR
environment variable or the --git-dir=<path> option.

* jc/safe-implicit-bare:
  setup: notice more types of implicit bare repositories

1  2 
setup.c

diff --combined setup.c
index b2d9371ddc229f9999121f12d8cd9096a8d554d6,25d98ee6dd035382eaf9ee358f8a8b9b56ad01fe..0b798591c0c5c2b23285ce1c4f8fcfb71caddb14
+++ b/setup.c
@@@ -13,6 -13,7 +13,6 @@@
  #include "string-list.h"
  #include "chdir-notify.h"
  #include "path.h"
 -#include "promisor-remote.h"
  #include "quote.h"
  #include "trace2.h"
  #include "worktree.h"
@@@ -558,8 -559,6 +558,8 @@@ static enum extension_result handle_ext
                        data->precious_objects = git_config_bool(var, value);
                        return EXTENSION_OK;
                } else if (!strcmp(ext, "partialclone")) {
 +                      if (!value)
 +                              return config_error_nonbool(var);
                        data->partial_clone = xstrdup(value);
                        return EXTENSION_OK;
                } else if (!strcmp(ext, "worktreeconfig")) {
@@@ -591,17 -590,6 +591,17 @@@ static enum extension_result handle_ext
                                     "extensions.objectformat", value);
                data->hash_algo = format;
                return EXTENSION_OK;
 +      } else if (!strcmp(ext, "refstorage")) {
 +              unsigned int format;
 +
 +              if (!value)
 +                      return config_error_nonbool(var);
 +              format = ref_storage_format_by_name(value);
 +              if (format == REF_STORAGE_FORMAT_UNKNOWN)
 +                      return error(_("invalid value for '%s': '%s'"),
 +                                   "extensions.refstorage", value);
 +              data->ref_storage_format = format;
 +              return EXTENSION_OK;
        }
        return EXTENSION_UNKNOWN;
  }
@@@ -1243,6 -1231,32 +1243,32 @@@ static const char *allowed_bare_repo_to
        return NULL;
  }
  
+ static int is_implicit_bare_repo(const char *path)
+ {
+       /*
+        * what we found is a ".git" directory at the root of
+        * the working tree.
+        */
+       if (ends_with_path_components(path, ".git"))
+               return 1;
+       /*
+        * we are inside $GIT_DIR of a secondary worktree of a
+        * non-bare repository.
+        */
+       if (strstr(path, "/.git/worktrees/"))
+               return 1;
+       /*
+        * we are inside $GIT_DIR of a worktree of a non-embedded
+        * submodule, whose superproject is not a bare repository.
+        */
+       if (strstr(path, "/.git/modules/"))
+               return 1;
+       return 0;
+ }
  /*
   * We cannot decide in this function whether we are in the work tree or
   * not, since the config can only be read _after_ this function was called.
@@@ -1372,7 -1386,7 +1398,7 @@@ static enum discovery_result setup_git_
                if (is_git_directory(dir->buf)) {
                        trace2_data_string("setup", NULL, "implicit-bare-repository", dir->buf);
                        if (get_allowed_bare_repo() == ALLOWED_BARE_REPO_EXPLICIT &&
-                           !ends_with_path_components(dir->buf, ".git"))
+                           !is_implicit_bare_repo(dir->buf))
                                return GIT_DIR_DISALLOWED_BARE;
                        if (!ensure_valid_ownership(NULL, NULL, dir->buf, report))
                                return GIT_DIR_INVALID_OWNERSHIP;
@@@ -1577,8 -1591,6 +1603,8 @@@ const char *setup_git_directory_gently(
                }
                if (startup_info->have_repository) {
                        repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
 +                      repo_set_ref_storage_format(the_repository,
 +                                                  repo_fmt.ref_storage_format);
                        the_repository->repository_format_worktree_config =
                                repo_fmt.worktree_config;
                        /* take ownership of repo_fmt.partial_clone */
@@@ -1672,8 -1684,6 +1698,8 @@@ void check_repository_format(struct rep
        check_repository_format_gently(get_git_dir(), fmt, NULL);
        startup_info->have_repository = 1;
        repo_set_hash_algo(the_repository, fmt->hash_algo);
 +      repo_set_ref_storage_format(the_repository,
 +                                  fmt->ref_storage_format);
        the_repository->repository_format_worktree_config =
                fmt->worktree_config;
        the_repository->repository_format_partial_clone =
@@@ -1882,22 -1892,12 +1908,22 @@@ static int needs_work_tree_config(cons
        return 1;
  }
  
 -void initialize_repository_version(int hash_algo, int reinit)
 +void initialize_repository_version(int hash_algo,
 +                                 unsigned int ref_storage_format,
 +                                 int reinit)
  {
        char repo_version_string[10];
        int repo_version = GIT_REPO_VERSION;
  
 -      if (hash_algo != GIT_HASH_SHA1)
 +      /*
 +       * Note that we initialize the repository version to 1 when the ref
 +       * storage format is unknown. This is on purpose so that we can add the
 +       * correct object format to the config during git-clone(1). The format
 +       * version will get adjusted by git-clone(1) once it has learned about
 +       * the remote repository's format.
 +       */
 +      if (hash_algo != GIT_HASH_SHA1 ||
 +          ref_storage_format != REF_STORAGE_FORMAT_FILES)
                repo_version = GIT_REPO_VERSION_READ;
  
        /* This forces creation of new config file */
                  "%d", repo_version);
        git_config_set("core.repositoryformatversion", repo_version_string);
  
 -      if (hash_algo != GIT_HASH_SHA1)
 +      if (hash_algo != GIT_HASH_SHA1 && hash_algo != GIT_HASH_UNKNOWN)
                git_config_set("extensions.objectformat",
                               hash_algos[hash_algo].name);
        else if (reinit)
                git_config_set_gently("extensions.objectformat", NULL);
 +
 +      if (ref_storage_format != REF_STORAGE_FORMAT_FILES)
 +              git_config_set("extensions.refstorage",
 +                             ref_storage_format_to_name(ref_storage_format));
 +}
 +
 +static int is_reinit(void)
 +{
 +      struct strbuf buf = STRBUF_INIT;
 +      char junk[2];
 +      int ret;
 +
 +      git_path_buf(&buf, "HEAD");
 +      ret = !access(buf.buf, R_OK) || readlink(buf.buf, junk, sizeof(junk) - 1) != -1;
 +      strbuf_release(&buf);
 +      return ret;
 +}
 +
 +void create_reference_database(unsigned int ref_storage_format,
 +                             const char *initial_branch, int quiet)
 +{
 +      struct strbuf err = STRBUF_INIT;
 +      int reinit = is_reinit();
 +
 +      repo_set_ref_storage_format(the_repository, ref_storage_format);
 +      if (refs_init_db(get_main_ref_store(the_repository), 0, &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.
 +       */
 +      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);
 +      }
 +
 +      if (reinit && initial_branch)
 +              warning(_("re-init: ignored --initial-branch=%s"),
 +                      initial_branch);
 +
 +      strbuf_release(&err);
  }
  
  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)
 +                              int init_shared_repository)
  {
        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();
  
        reset_shared_repository();
        git_config(git_default_config, NULL);
  
 +      reinit = is_reinit();
 +
        /*
         * 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.
 -       */
 +
 +      is_bare_repository_cfg = !work_tree;
  
        /*
         * We would have created the above under user's umask -- under
                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);
 +      initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0);
  
        /* Check filemode trustability */
        path = git_path_buf(&buf, "config");
@@@ -2126,35 -2135,15 +2152,35 @@@ static void validate_hash_algorithm(str
        }
  }
  
 +static void validate_ref_storage_format(struct repository_format *repo_fmt,
 +                                      unsigned int format)
 +{
 +      const char *name = getenv("GIT_DEFAULT_REF_FORMAT");
 +
 +      if (repo_fmt->version >= 0 &&
 +          format != REF_STORAGE_FORMAT_UNKNOWN &&
 +          format != repo_fmt->ref_storage_format) {
 +              die(_("attempt to reinitialize repository with different reference storage format"));
 +      } else if (format != REF_STORAGE_FORMAT_UNKNOWN) {
 +              repo_fmt->ref_storage_format = format;
 +      } else if (name) {
 +              format = ref_storage_format_by_name(name);
 +              if (format == REF_STORAGE_FORMAT_UNKNOWN)
 +                      die(_("unknown ref storage format '%s'"), name);
 +              repo_fmt->ref_storage_format = format;
 +      }
 +}
 +
  int init_db(const char *git_dir, const char *real_git_dir,
 -          const char *template_dir, int hash, const char *initial_branch,
 +          const char *template_dir, int hash,
 +          unsigned int ref_storage_format,
 +          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;
  
        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
        check_repository_format(&repo_fmt);
  
        validate_hash_algorithm(&repo_fmt, hash);
 +      validate_ref_storage_format(&repo_fmt, ref_storage_format);
  
        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);
 +                                    &repo_fmt, init_shared_repository);
 +
 +      /*
 +       * Now that we have set up both the hash algorithm and the ref storage
 +       * format we can update the repository's settings accordingly.
 +       */
 +      repo_set_hash_algo(the_repository, repo_fmt.hash_algo);
 +      repo_set_ref_storage_format(the_repository, repo_fmt.ref_storage_format);
  
 +      if (!(flags & INIT_DB_SKIP_REFDB))
 +              create_reference_database(repo_fmt.ref_storage_format,
 +                                        initial_branch, flags & INIT_DB_QUIET);
        create_object_directory();
  
        if (get_shared_repository()) {