Merge branch 'jk/parse-config-key-cleanup'
authorJunio C Hamano <gitster@pobox.com>
Fri, 10 Mar 2017 21:24:22 +0000 (13:24 -0800)
committerJunio C Hamano <gitster@pobox.com>
Fri, 10 Mar 2017 21:24:22 +0000 (13:24 -0800)
The "parse_config_key()" API function has been cleaned up.

* jk/parse-config-key-cleanup:
parse_hide_refs_config: tell parse_config_key we don't want a subsection
parse_config_key: allow matching single-level config
parse_config_key: use skip_prefix instead of starts_with

1  2 
cache.h
config.c
refs.c
diff --combined cache.h
index 80b6372cf765155b4ea34720e6d65684600c7c12,e7b0e6be951369c4c46a6b6ad3d33f72345d0f8d..1046dfc9345e31c75086978207f6b2e0685b8402
+++ b/cache.h
@@@ -507,16 -507,14 +507,16 @@@ extern int is_nonbare_repository_dir(st
  #define READ_GITFILE_ERR_NO_PATH 6
  #define READ_GITFILE_ERR_NOT_A_REPO 7
  #define READ_GITFILE_ERR_TOO_LARGE 8
 +extern void read_gitfile_error_die(int error_code, const char *path, const char *dir);
  extern const char *read_gitfile_gently(const char *path, int *return_error_code);
  #define read_gitfile(path) read_gitfile_gently((path), NULL)
 -extern const char *resolve_gitdir(const char *suspect);
 +extern const char *resolve_gitdir_gently(const char *suspect, int *return_error_code);
 +#define resolve_gitdir(path) resolve_gitdir_gently((path), NULL)
 +
  extern void set_git_work_tree(const char *tree);
  
  #define ALTERNATE_DB_ENVIRONMENT "GIT_ALTERNATE_OBJECT_DIRECTORIES"
  
 -extern const char **get_pathspec(const char *prefix, const char **pathspec);
  extern void setup_work_tree(void);
  extern const char *setup_git_directory_gently(int *);
  extern const char *setup_git_directory(void);
@@@ -633,7 -631,7 +633,7 @@@ extern int chmod_index_entry(struct ind
  extern int ce_same_name(const struct cache_entry *a, const struct cache_entry *b);
  extern void set_object_name_for_intent_to_add_entry(struct cache_entry *ce);
  extern int index_name_is_other(const struct index_state *, const char *, int);
 -extern void *read_blob_data_from_index(struct index_state *, const char *, unsigned long *);
 +extern void *read_blob_data_from_index(const struct index_state *, const char *, unsigned long *);
  
  /* do stat comparison even if CE_VALID is true */
  #define CE_MATCH_IGNORE_VALID         01
@@@ -695,6 -693,7 +695,6 @@@ extern int minimum_abbrev, default_abbr
  extern int ignore_case;
  extern int assume_unchanged;
  extern int prefer_symlink_refs;
 -extern int log_all_ref_updates;
  extern int warn_ambiguous_refs;
  extern int warn_on_object_refname_ambiguity;
  extern const char *apply_default_whitespace;
@@@ -762,14 -761,6 +762,14 @@@ enum hide_dotfiles_type 
  };
  extern enum hide_dotfiles_type hide_dotfiles;
  
 +enum log_refs_config {
 +      LOG_REFS_UNSET = -1,
 +      LOG_REFS_NONE = 0,
 +      LOG_REFS_NORMAL,
 +      LOG_REFS_ALWAYS
 +};
 +extern enum log_refs_config log_all_ref_updates;
 +
  enum branch_track {
        BRANCH_TRACK_UNSPECIFIED = -1,
        BRANCH_TRACK_NEVER = 0,
@@@ -1072,9 -1063,8 +1072,9 @@@ int adjust_shared_perm(const char *path
  
  /*
   * Create the directory containing the named path, using care to be
 - * somewhat safe against races.  Return one of the scld_error values
 - * to indicate success/failure.
 + * somewhat safe against races. Return one of the scld_error values to
 + * indicate success/failure. On error, set errno to describe the
 + * problem.
   *
   * SCLD_VANISHED indicates that one of the ancestor directories of the
   * path existed at one point during the function call and then
@@@ -1098,49 -1088,6 +1098,49 @@@ enum scld_error 
  enum scld_error safe_create_leading_directories(char *path);
  enum scld_error safe_create_leading_directories_const(const char *path);
  
 +/*
 + * Callback function for raceproof_create_file(). This function is
 + * expected to do something that makes dirname(path) permanent despite
 + * the fact that other processes might be cleaning up empty
 + * directories at the same time. Usually it will create a file named
 + * path, but alternatively it could create another file in that
 + * directory, or even chdir() into that directory. The function should
 + * return 0 if the action was completed successfully. On error, it
 + * should return a nonzero result and set errno.
 + * raceproof_create_file() treats two errno values specially:
 + *
 + * - ENOENT -- dirname(path) does not exist. In this case,
 + *             raceproof_create_file() tries creating dirname(path)
 + *             (and any parent directories, if necessary) and calls
 + *             the function again.
 + *
 + * - EISDIR -- the file already exists and is a directory. In this
 + *             case, raceproof_create_file() removes the directory if
 + *             it is empty (and recursively any empty directories that
 + *             it contains) and calls the function again.
 + *
 + * Any other errno causes raceproof_create_file() to fail with the
 + * callback's return value and errno.
 + *
 + * Obviously, this function should be OK with being called again if it
 + * fails with ENOENT or EISDIR. In other scenarios it will not be
 + * called again.
 + */
 +typedef int create_file_fn(const char *path, void *cb);
 +
 +/*
 + * Create a file in dirname(path) by calling fn, creating leading
 + * directories if necessary. Retry a few times in case we are racing
 + * with another process that is trying to clean up the directory that
 + * contains path. See the documentation for create_file_fn for more
 + * details.
 + *
 + * Return the value and set the errno that resulted from the most
 + * recent call of fn. fn is always called at least once, and will be
 + * called more than once if it returns ENOENT or EISDIR.
 + */
 +int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
 +
  int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
@@@ -1149,13 -1096,9 +1149,13 @@@ static inline int is_absolute_path(cons
        return is_dir_sep(path[0]) || has_dos_drive_prefix(path);
  }
  int is_directory(const char *);
 +char *strbuf_realpath(struct strbuf *resolved, const char *path,
 +                    int die_on_error);
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
 +char *real_pathdup(const char *path);
  const char *absolute_path(const char *path);
 +char *absolute_pathdup(const char *path);
  const char *remove_leading_path(const char *in, const char *prefix);
  const char *relative_path(const char *in, const char *prefix, struct strbuf *sb);
  int normalize_path_copy_len(char *dst, const char *src, int *prefix_len);
@@@ -1214,8 -1157,7 +1214,8 @@@ extern int write_sha1_file(const void *
  extern int hash_sha1_file_literally(const void *buf, unsigned long len, const char *type, unsigned char *sha1, unsigned flags);
  extern int pretend_sha1_file(void *, unsigned long, enum object_type, unsigned char *);
  extern int force_object_loose(const unsigned char *sha1, time_t mtime);
 -extern int git_open(const char *name);
 +extern int git_open_cloexec(const char *name, int flags);
 +#define git_open(name) git_open_cloexec(name, O_RDONLY)
  extern void *map_sha1_file(const unsigned char *sha1, unsigned long *size);
  extern int unpack_sha1_header(git_zstream *stream, unsigned char *map, unsigned long mapsize, void *buffer, unsigned long bufsiz);
  extern int parse_sha1_header(const char *hdr, unsigned long *sizep);
@@@ -1229,19 -1171,6 +1229,19 @@@ extern int finalize_object_file(const c
  
  extern int has_sha1_pack(const unsigned char *sha1);
  
 +/*
 + * Open the loose object at path, check its sha1, and return the contents,
 + * type, and size. If the object is a blob, then "contents" may return NULL,
 + * to allow streaming of large blobs.
 + *
 + * Returns 0 on success, negative on error (details may be written to stderr).
 + */
 +int read_loose_object(const char *path,
 +                    const unsigned char *expected_sha1,
 +                    enum object_type *type,
 +                    unsigned long *size,
 +                    void **contents);
 +
  /*
   * Return true iff we have an object named sha1, whether local or in
   * an alternate object database, and whether packed or loose.  This
@@@ -1793,8 -1722,6 +1793,8 @@@ extern int git_default_config(const cha
  extern int git_config_from_file(config_fn_t fn, const char *, void *);
  extern int git_config_from_mem(config_fn_t fn, const enum config_origin_type,
                                        const char *name, const char *buf, size_t len, void *data);
 +extern int git_config_from_blob_sha1(config_fn_t fn, const char *name,
 +                                   const unsigned char *sha1, void *data);
  extern void git_config_push_parameter(const char *text);
  extern int git_config_from_parameters(config_fn_t fn, void *data);
  extern void git_config(config_fn_t fn, void *);
@@@ -1863,8 -1790,11 +1863,11 @@@ extern int git_config_include(const cha
   *
   * (i.e., what gets handed to a config_fn_t). The caller provides the section;
   * we return -1 if it does not match, 0 otherwise. The subsection and key
-  * out-parameters are filled by the function (and subsection is NULL if it is
+  * out-parameters are filled by the function (and *subsection is NULL if it is
   * missing).
+  *
+  * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
+  * there is no subsection at all.
   */
  extern int parse_config_key(const char *var,
                            const char *section,
diff --combined config.c
index 48edc6a3846334f7aa3224a8c15a0029f4f8ab39,fd92738fbaf163c1e9019dfc05b5c41e9f079cc9..0e9e1ebefc0a6771de7bf20615e41176ed986179
+++ b/config.c
@@@ -201,105 -201,11 +201,105 @@@ void git_config_push_parameter(const ch
        strbuf_release(&env);
  }
  
 +static inline int iskeychar(int c)
 +{
 +      return isalnum(c) || c == '-';
 +}
 +
 +/*
 + * Auxiliary function to sanity-check and split the key into the section
 + * identifier and variable name.
 + *
 + * Returns 0 on success, -1 when there is an invalid character in the key and
 + * -2 if there is no section name in the key.
 + *
 + * store_key - pointer to char* which will hold a copy of the key with
 + *             lowercase section and variable name
 + * baselen - pointer to int which will hold the length of the
 + *           section + subsection part, can be NULL
 + */
 +static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 +{
 +      int i, dot, baselen;
 +      const char *last_dot = strrchr(key, '.');
 +
 +      /*
 +       * Since "key" actually contains the section name and the real
 +       * key name separated by a dot, we have to know where the dot is.
 +       */
 +
 +      if (last_dot == NULL || last_dot == key) {
 +              if (!quiet)
 +                      error("key does not contain a section: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      if (!last_dot[1]) {
 +              if (!quiet)
 +                      error("key does not contain variable name: %s", key);
 +              return -CONFIG_NO_SECTION_OR_NAME;
 +      }
 +
 +      baselen = last_dot - key;
 +      if (baselen_)
 +              *baselen_ = baselen;
 +
 +      /*
 +       * Validate the key and while at it, lower case it for matching.
 +       */
 +      if (store_key)
 +              *store_key = xmallocz(strlen(key));
 +
 +      dot = 0;
 +      for (i = 0; key[i]; i++) {
 +              unsigned char c = key[i];
 +              if (c == '.')
 +                      dot = 1;
 +              /* Leave the extended basename untouched.. */
 +              if (!dot || i > baselen) {
 +                      if (!iskeychar(c) ||
 +                          (i == baselen + 1 && !isalpha(c))) {
 +                              if (!quiet)
 +                                      error("invalid key: %s", key);
 +                              goto out_free_ret_1;
 +                      }
 +                      c = tolower(c);
 +              } else if (c == '\n') {
 +                      if (!quiet)
 +                              error("invalid key (newline): %s", key);
 +                      goto out_free_ret_1;
 +              }
 +              if (store_key)
 +                      (*store_key)[i] = c;
 +      }
 +
 +      return 0;
 +
 +out_free_ret_1:
 +      if (store_key) {
 +              free(*store_key);
 +              *store_key = NULL;
 +      }
 +      return -CONFIG_INVALID_KEY;
 +}
 +
 +int git_config_parse_key(const char *key, char **store_key, int *baselen)
 +{
 +      return git_config_parse_key_1(key, store_key, baselen, 0);
 +}
 +
 +int git_config_key_is_valid(const char *key)
 +{
 +      return !git_config_parse_key_1(key, NULL, NULL, 1);
 +}
 +
  int git_config_parse_parameter(const char *text,
                               config_fn_t fn, void *data)
  {
        const char *value;
 +      char *canonical_name;
        struct strbuf **pair;
 +      int ret;
  
        pair = strbuf_split_str(text, '=', 2);
        if (!pair[0])
                strbuf_list_free(pair);
                return error("bogus config parameter: %s", text);
        }
 -      strbuf_tolower(pair[0]);
 -      if (fn(pair[0]->buf, value, data) < 0) {
 -              strbuf_list_free(pair);
 -              return -1;
 +
 +      if (git_config_parse_key(pair[0]->buf, &canonical_name, NULL)) {
 +              ret = -1;
 +      } else {
 +              ret = (fn(canonical_name, value, data) < 0) ? -1 : 0;
 +              free(canonical_name);
        }
        strbuf_list_free(pair);
 -      return 0;
 +      return ret;
  }
  
  int git_config_from_parameters(config_fn_t fn, void *data)
@@@ -452,6 -356,11 +452,6 @@@ static char *parse_value(void
        }
  }
  
 -static inline int iskeychar(int c)
 -{
 -      return isalnum(c) || c == '-';
 -}
 -
  static int get_value(config_fn_t fn, void *data, struct strbuf *name)
  {
        int c;
@@@ -917,12 -826,7 +917,12 @@@ static int git_default_core_config(cons
        }
  
        if (!strcmp(var, "core.logallrefupdates")) {
 -              log_all_ref_updates = git_config_bool(var, value);
 +              if (value && !strcasecmp(value, "always"))
 +                      log_all_ref_updates = LOG_REFS_ALWAYS;
 +              else if (git_config_bool(var, value))
 +                      log_all_ref_updates = LOG_REFS_NORMAL;
 +              else
 +                      log_all_ref_updates = LOG_REFS_NONE;
                return 0;
        }
  
@@@ -1332,10 -1236,10 +1332,10 @@@ int git_config_from_mem(config_fn_t fn
        return do_config_from(&top, fn, data);
  }
  
 -static int git_config_from_blob_sha1(config_fn_t fn,
 -                                   const char *name,
 -                                   const unsigned char *sha1,
 -                                   void *data)
 +int git_config_from_blob_sha1(config_fn_t fn,
 +                            const char *name,
 +                            const unsigned char *sha1,
 +                            void *data)
  {
        enum object_type type;
        char *buf;
@@@ -2080,6 -1984,93 +2080,6 @@@ void git_config_set(const char *key, co
        git_config_set_multivar(key, value, NULL, 0);
  }
  
 -/*
 - * Auxiliary function to sanity-check and split the key into the section
 - * identifier and variable name.
 - *
 - * Returns 0 on success, -1 when there is an invalid character in the key and
 - * -2 if there is no section name in the key.
 - *
 - * store_key - pointer to char* which will hold a copy of the key with
 - *             lowercase section and variable name
 - * baselen - pointer to int which will hold the length of the
 - *           section + subsection part, can be NULL
 - */
 -static int git_config_parse_key_1(const char *key, char **store_key, int *baselen_, int quiet)
 -{
 -      int i, dot, baselen;
 -      const char *last_dot = strrchr(key, '.');
 -
 -      /*
 -       * Since "key" actually contains the section name and the real
 -       * key name separated by a dot, we have to know where the dot is.
 -       */
 -
 -      if (last_dot == NULL || last_dot == key) {
 -              if (!quiet)
 -                      error("key does not contain a section: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      if (!last_dot[1]) {
 -              if (!quiet)
 -                      error("key does not contain variable name: %s", key);
 -              return -CONFIG_NO_SECTION_OR_NAME;
 -      }
 -
 -      baselen = last_dot - key;
 -      if (baselen_)
 -              *baselen_ = baselen;
 -
 -      /*
 -       * Validate the key and while at it, lower case it for matching.
 -       */
 -      if (store_key)
 -              *store_key = xmallocz(strlen(key));
 -
 -      dot = 0;
 -      for (i = 0; key[i]; i++) {
 -              unsigned char c = key[i];
 -              if (c == '.')
 -                      dot = 1;
 -              /* Leave the extended basename untouched.. */
 -              if (!dot || i > baselen) {
 -                      if (!iskeychar(c) ||
 -                          (i == baselen + 1 && !isalpha(c))) {
 -                              if (!quiet)
 -                                      error("invalid key: %s", key);
 -                              goto out_free_ret_1;
 -                      }
 -                      c = tolower(c);
 -              } else if (c == '\n') {
 -                      if (!quiet)
 -                              error("invalid key (newline): %s", key);
 -                      goto out_free_ret_1;
 -              }
 -              if (store_key)
 -                      (*store_key)[i] = c;
 -      }
 -
 -      return 0;
 -
 -out_free_ret_1:
 -      if (store_key) {
 -              free(*store_key);
 -              *store_key = NULL;
 -      }
 -      return -CONFIG_INVALID_KEY;
 -}
 -
 -int git_config_parse_key(const char *key, char **store_key, int *baselen)
 -{
 -      return git_config_parse_key_1(key, store_key, baselen, 0);
 -}
 -
 -int git_config_key_is_valid(const char *key)
 -{
 -      return !git_config_parse_key_1(key, NULL, NULL, 1);
 -}
 -
  /*
   * If value==NULL, unset in (remove from) config,
   * if value_regex!=NULL, disregard key/value pairs where value does not match.
@@@ -2540,11 -2531,10 +2540,10 @@@ int parse_config_key(const char *var
                     const char **subsection, int *subsection_len,
                     const char **key)
  {
-       int section_len = strlen(section);
        const char *dot;
  
        /* Does it start with "section." ? */
-       if (!starts_with(var, section) || var[section_len] != '.')
+       if (!skip_prefix(var, section, &var) || *var != '.')
                return -1;
  
        /*
        *key = dot + 1;
  
        /* Did we have a subsection at all? */
-       if (dot == var + section_len) {
-               *subsection = NULL;
-               *subsection_len = 0;
+       if (dot == var) {
+               if (subsection) {
+                       *subsection = NULL;
+                       *subsection_len = 0;
+               }
        }
        else {
-               *subsection = var + section_len + 1;
+               if (!subsection)
+                       return -1;
+               *subsection = var + 1;
                *subsection_len = dot - *subsection;
        }
  
diff --combined refs.c
index eec36a2a94910ecb1e798f4243289aec4eee93b7,cb812455791ab66f2a3d726d5a609726d338dadf..4d6bf9237b5947c93dd366ccc1511f2a73649ae6
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3,7 -3,6 +3,7 @@@
   */
  
  #include "cache.h"
 +#include "hashmap.h"
  #include "lockfile.h"
  #include "refs.h"
  #include "refs/refs-internal.h"
@@@ -592,8 -591,8 +592,8 @@@ static int delete_pseudoref(const char 
        return 0;
  }
  
 -int delete_ref(const char *refname, const unsigned char *old_sha1,
 -             unsigned int flags)
 +int delete_ref(const char *msg, const char *refname,
 +             const unsigned char *old_sha1, unsigned int flags)
  {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_sha1,
 -                                 flags, NULL, &err) ||
 +                                 flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
                ref_transaction_free(transaction);
@@@ -639,17 -638,12 +639,17 @@@ int copy_reflog_msg(char *buf, const ch
  
  int should_autocreate_reflog(const char *refname)
  {
 -      if (!log_all_ref_updates)
 +      switch (log_all_ref_updates) {
 +      case LOG_REFS_ALWAYS:
 +              return 1;
 +      case LOG_REFS_NORMAL:
 +              return starts_with(refname, "refs/heads/") ||
 +                      starts_with(refname, "refs/remotes/") ||
 +                      starts_with(refname, "refs/notes/") ||
 +                      !strcmp(refname, "HEAD");
 +      default:
                return 0;
 -      return starts_with(refname, "refs/heads/") ||
 -              starts_with(refname, "refs/remotes/") ||
 -              starts_with(refname, "refs/notes/") ||
 -              !strcmp(refname, "HEAD");
 +      }
  }
  
  int is_branch(const char *refname)
@@@ -1035,11 -1029,10 +1035,10 @@@ static struct string_list *hide_refs
  
  int parse_hide_refs_config(const char *var, const char *value, const char *section)
  {
-       const char *subsection, *key;
-       int subsection_len;
+       const char *key;
        if (!strcmp("transfer.hiderefs", var) ||
-           (!parse_config_key(var, section, &subsection, &subsection_len, &key)
-           && !subsection && !strcmp(key, "hiderefs"))) {
+           (!parse_config_key(var, section, NULL, NULL, &key) &&
+            !strcmp(key, "hiderefs"))) {
                char *ref;
                int len;
  
@@@ -1236,10 -1229,10 +1235,10 @@@ int for_each_rawref(each_ref_fn fn, voi
  }
  
  /* This function needs to return a meaningful errno on failure */
 -static const char *resolve_ref_recursively(struct ref_store *refs,
 -                                         const char *refname,
 -                                         int resolve_flags,
 -                                         unsigned char *sha1, int *flags)
 +const char *resolve_ref_recursively(struct ref_store *refs,
 +                                  const char *refname,
 +                                  int resolve_flags,
 +                                  unsigned char *sha1, int *flags)
  {
        static struct strbuf sb_refname = STRBUF_INIT;
        int unused_flags;
@@@ -1359,102 -1352,62 +1358,102 @@@ int resolve_gitlink_ref(const char *sub
        return 0;
  }
  
 +struct submodule_hash_entry
 +{
 +      struct hashmap_entry ent; /* must be the first member! */
 +
 +      struct ref_store *refs;
 +
 +      /* NUL-terminated name of submodule: */
 +      char submodule[FLEX_ARRAY];
 +};
 +
 +static int submodule_hash_cmp(const void *entry, const void *entry_or_key,
 +                            const void *keydata)
 +{
 +      const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key;
 +      const char *submodule = keydata ? keydata : e2->submodule;
 +
 +      return strcmp(e1->submodule, submodule);
 +}
 +
 +static struct submodule_hash_entry *alloc_submodule_hash_entry(
 +              const char *submodule, struct ref_store *refs)
 +{
 +      struct submodule_hash_entry *entry;
 +
 +      FLEX_ALLOC_STR(entry, submodule, submodule);
 +      hashmap_entry_init(entry, strhash(submodule));
 +      entry->refs = refs;
 +      return entry;
 +}
 +
  /* A pointer to the ref_store for the main repository: */
  static struct ref_store *main_ref_store;
  
 -/* A linked list of ref_stores for submodules: */
 -static struct ref_store *submodule_ref_stores;
 +/* A hashmap of ref_stores, stored by submodule name: */
 +static struct hashmap submodule_ref_stores;
  
 -void base_ref_store_init(struct ref_store *refs,
 -                       const struct ref_storage_be *be,
 -                       const char *submodule)
 +/*
 + * Return the ref_store instance for the specified submodule (or the
 + * main repository if submodule is NULL). If that ref_store hasn't
 + * been initialized yet, return NULL.
 + */
 +static struct ref_store *lookup_ref_store(const char *submodule)
 +{
 +      struct submodule_hash_entry *entry;
 +
 +      if (!submodule)
 +              return main_ref_store;
 +
 +      if (!submodule_ref_stores.tablesize)
 +              /* It's initialized on demand in register_ref_store(). */
 +              return NULL;
 +
 +      entry = hashmap_get_from_hash(&submodule_ref_stores,
 +                                    strhash(submodule), submodule);
 +      return entry ? entry->refs : NULL;
 +}
 +
 +/*
 + * Register the specified ref_store to be the one that should be used
 + * for submodule (or the main repository if submodule is NULL). It is
 + * a fatal error to call this function twice for the same submodule.
 + */
 +static void register_ref_store(struct ref_store *refs, const char *submodule)
  {
 -      refs->be = be;
        if (!submodule) {
                if (main_ref_store)
                        die("BUG: main_ref_store initialized twice");
  
 -              refs->submodule = "";
 -              refs->next = NULL;
                main_ref_store = refs;
        } else {
 -              if (lookup_ref_store(submodule))
 +              if (!submodule_ref_stores.tablesize)
 +                      hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0);
 +
 +              if (hashmap_put(&submodule_ref_stores,
 +                              alloc_submodule_hash_entry(submodule, refs)))
                        die("BUG: ref_store for submodule '%s' initialized twice",
                            submodule);
 -
 -              refs->submodule = xstrdup(submodule);
 -              refs->next = submodule_ref_stores;
 -              submodule_ref_stores = refs;
        }
  }
  
 -struct ref_store *ref_store_init(const char *submodule)
 +/*
 + * Create, record, and return a ref_store instance for the specified
 + * submodule (or the main repository if submodule is NULL).
 + */
 +static struct ref_store *ref_store_init(const char *submodule)
  {
        const char *be_name = "files";
        struct ref_storage_be *be = find_ref_storage_backend(be_name);
 +      struct ref_store *refs;
  
        if (!be)
                die("BUG: reference backend %s is unknown", be_name);
  
 -      if (!submodule || !*submodule)
 -              return be->init(NULL);
 -      else
 -              return be->init(submodule);
 -}
 -
 -struct ref_store *lookup_ref_store(const char *submodule)
 -{
 -      struct ref_store *refs;
 -
 -      if (!submodule || !*submodule)
 -              return main_ref_store;
 -
 -      for (refs = submodule_ref_stores; refs; refs = refs->next) {
 -              if (!strcmp(submodule, refs->submodule))
 -                      return refs;
 -      }
 -
 -      return NULL;
 +      refs = be->init(submodule);
 +      register_ref_store(refs, submodule);
 +      return refs;
  }
  
  struct ref_store *get_ref_store(const char *submodule)
        return refs;
  }
  
 -void assert_main_repository(struct ref_store *refs, const char *caller)
 +void base_ref_store_init(struct ref_store *refs,
 +                       const struct ref_storage_be *be)
  {
 -      if (*refs->submodule)
 -              die("BUG: %s called for a submodule", caller);
 +      refs->be = be;
  }
  
  /* backend functions */