Merge branch 'mh/submodule-hash'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:15 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:15 +0000 (13:57 -0800)
Code and design clean-up for the refs API.

* mh/submodule-hash:
read_loose_refs(): read refs using resolve_ref_recursively()
files_ref_store::submodule: use NULL for the main repository
base_ref_store_init(): remove submodule argument
refs: push the submodule attribute down
refs: store submodule ref stores in a hashmap
register_ref_store(): new function
refs: remove some unnecessary handling of submodule == ""
refs: make some ref_store lookup functions private
refs: reorder some function definitions

1  2 
refs.c
refs/files-backend.c
refs/refs-internal.h
diff --combined refs.c
index cd36b64ed93f821936fd7ffb68e60ce384119898,4f845798bad0fd1fc348d49592fb83e86679f09d..81b64b4ed51d58603598f719097eba9c4ef17ab4
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3,6 -3,7 +3,7 @@@
   */
  
  #include "cache.h"
+ #include "hashmap.h"
  #include "lockfile.h"
  #include "refs.h"
  #include "refs/refs-internal.h"
@@@ -638,17 -639,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)
@@@ -1234,10 -1230,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;
@@@ -1357,62 -1353,102 +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 */
diff --combined refs/files-backend.c
index 21e116d4e620b9de77def18bf89054399477cf62,cdb6b8ff572a41d8a10cbc7a49bfa64dffe045ee..db3bd42a91d1935471a93e3149165705ec55f8ec
@@@ -697,7 -697,7 +697,7 @@@ static int cache_ref_iterator_peel(stru
  
        if (peel_entry(entry, 0))
                return -1;
 -      hashcpy(peeled->hash, entry->u.value.peeled.hash);
 +      oidcpy(peeled, &entry->u.value.peeled);
        return 0;
  }
  
@@@ -912,6 -912,14 +912,14 @@@ struct packed_ref_cache 
   */
  struct files_ref_store {
        struct ref_store base;
+       /*
+        * The name of the submodule represented by this object, or
+        * NULL if it represents the main repository's reference
+        * store:
+        */
+       const char *submodule;
        struct ref_entry *loose;
        struct packed_ref_cache *packed;
  };
@@@ -972,11 -980,24 +980,24 @@@ static struct ref_store *files_ref_stor
        struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
  
-       base_ref_store_init(ref_store, &refs_be_files, submodule);
+       base_ref_store_init(ref_store, &refs_be_files);
+       refs->submodule = xstrdup_or_null(submodule);
  
        return ref_store;
  }
  
+ /*
+  * Die if refs is for a submodule (i.e., not for the main repository).
+  * caller is used in any necessary error messages.
+  */
+ static void files_assert_main_repository(struct files_ref_store *refs,
+                                        const char *caller)
+ {
+       if (refs->submodule)
+               die("BUG: %s called for a submodule", caller);
+ }
  /*
   * Downcast ref_store to files_ref_store. Die if ref_store is not a
   * files_ref_store. If submodule_allowed is not true, then also die if
@@@ -987,14 -1008,18 +1008,18 @@@ static struct files_ref_store *files_do
                struct ref_store *ref_store, int submodule_allowed,
                const char *caller)
  {
+       struct files_ref_store *refs;
        if (ref_store->be != &refs_be_files)
                die("BUG: ref_store is type \"%s\" not \"files\" in %s",
                    ref_store->be->name, caller);
  
+       refs = (struct files_ref_store *)ref_store;
        if (!submodule_allowed)
-               assert_main_repository(ref_store, caller);
+               files_assert_main_repository(refs, caller);
  
-       return (struct files_ref_store *)ref_store;
+       return refs;
  }
  
  /* The length of a peeled reference line in packed-refs, including EOL: */
@@@ -1133,8 -1158,8 +1158,8 @@@ static struct packed_ref_cache *get_pac
  {
        char *packed_refs_file;
  
-       if (*refs->base.submodule)
-               packed_refs_file = git_pathdup_submodule(refs->base.submodule,
+       if (refs->submodule)
+               packed_refs_file = git_pathdup_submodule(refs->submodule,
                                                         "packed-refs");
        else
                packed_refs_file = git_pathdup("packed-refs");
@@@ -1203,8 -1228,8 +1228,8 @@@ static void read_loose_refs(const char 
        size_t path_baselen;
        int err = 0;
  
-       if (*refs->base.submodule)
-               err = strbuf_git_path_submodule(&path, refs->base.submodule, "%s", dirname);
+       if (refs->submodule)
+               err = strbuf_git_path_submodule(&path, refs->submodule, "%s", dirname);
        else
                strbuf_git_path(&path, "%s", dirname);
        path_baselen = path.len;
                                         create_dir_entry(refs, refname.buf,
                                                          refname.len, 1));
                } else {
-                       int read_ok;
-                       if (*refs->base.submodule) {
-                               hashclr(sha1);
-                               flag = 0;
-                               read_ok = !resolve_gitlink_ref(refs->base.submodule,
-                                                              refname.buf, sha1);
-                       } else {
-                               read_ok = !read_ref_full(refname.buf,
-                                                        RESOLVE_REF_READING,
-                                                        sha1, &flag);
-                       }
-                       if (!read_ok) {
+                       if (!resolve_ref_recursively(&refs->base,
+                                                    refname.buf,
+                                                    RESOLVE_REF_READING,
+                                                    sha1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        } else if (is_null_sha1(sha1)) {
@@@ -1358,8 -1373,8 +1373,8 @@@ static int files_read_raw_ref(struct re
        *type = 0;
        strbuf_reset(&sb_path);
  
-       if (*refs->base.submodule)
-               strbuf_git_path_submodule(&sb_path, refs->base.submodule, "%s", refname);
+       if (refs->submodule)
+               strbuf_git_path_submodule(&sb_path, refs->submodule, "%s", refname);
        else
                strbuf_git_path(&sb_path, "%s", refname);
  
@@@ -1540,7 -1555,7 +1555,7 @@@ static int lock_raw_ref(struct files_re
        int ret = TRANSACTION_GENERIC_ERROR;
  
        assert(err);
-       assert_main_repository(&refs->base, "lock_raw_ref");
+       files_assert_main_repository(refs, "lock_raw_ref");
  
        *type = 0;
  
@@@ -1985,13 -2000,6 +2000,13 @@@ static int remove_empty_directories(str
        return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
  }
  
 +static int create_reflock(const char *path, void *cb)
 +{
 +      struct lock_file *lk = cb;
 +
 +      return hold_lock_file_for_update(lk, path, LOCK_NO_DEREF) < 0 ? -1 : 0;
 +}
 +
  /*
   * Locks a ref returning the lock on success and NULL on failure.
   * On failure errno is set to something meaningful.
@@@ -2007,11 -2015,13 +2022,11 @@@ static struct ref_lock *lock_ref_sha1_b
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
        int last_errno = 0;
 -      int lflags = LOCK_NO_DEREF;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
        int resolve_flags = RESOLVE_REF_NO_RECURSE;
 -      int attempts_remaining = 3;
        int resolved;
  
-       assert_main_repository(&refs->base, "lock_ref_sha1_basic");
+       files_assert_main_repository(refs, "lock_ref_sha1_basic");
        assert(err);
  
        lock = xcalloc(1, sizeof(struct ref_lock));
  
        lock->ref_name = xstrdup(refname);
  
 - retry:
 -      switch (safe_create_leading_directories_const(ref_file.buf)) {
 -      case SCLD_OK:
 -              break; /* success */
 -      case SCLD_VANISHED:
 -              if (--attempts_remaining > 0)
 -                      goto retry;
 -              /* fall through */
 -      default:
 +      if (raceproof_create_file(ref_file.buf, create_reflock, lock->lk)) {
                last_errno = errno;
 -              strbuf_addf(err, "unable to create directory for '%s'",
 -                          ref_file.buf);
 +              unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
        }
  
 -      if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
 -              last_errno = errno;
 -              if (errno == ENOENT && --attempts_remaining > 0)
 -                      /*
 -                       * Maybe somebody just deleted one of the
 -                       * directories leading to ref_file.  Try
 -                       * again:
 -                       */
 -                      goto retry;
 -              else {
 -                      unable_to_lock_message(ref_file.buf, errno, err);
 -                      goto error_return;
 -              }
 -      }
        if (verify_lock(lock, old_sha1, mustexist, err)) {
                last_errno = errno;
                goto error_return;
@@@ -2134,7 -2167,7 +2149,7 @@@ static int lock_packed_refs(struct file
        static int timeout_value = 1000;
        struct packed_ref_cache *packed_ref_cache;
  
-       assert_main_repository(&refs->base, "lock_packed_refs");
+       files_assert_main_repository(refs, "lock_packed_refs");
  
        if (!timeout_configured) {
                git_config_get_int("core.packedrefstimeout", &timeout_value);
@@@ -2172,7 -2205,7 +2187,7 @@@ static int commit_packed_refs(struct fi
        int save_errno = 0;
        FILE *out;
  
-       assert_main_repository(&refs->base, "commit_packed_refs");
+       files_assert_main_repository(refs, "commit_packed_refs");
  
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
@@@ -2205,7 -2238,7 +2220,7 @@@ static void rollback_packed_refs(struc
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(refs);
  
-       assert_main_repository(&refs->base, "rollback_packed_refs");
+       files_assert_main_repository(refs, "rollback_packed_refs");
  
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
@@@ -2280,25 -2313,15 +2295,25 @@@ static int pack_if_possible_fn(struct r
        return 0;
  }
  
 +enum {
 +      REMOVE_EMPTY_PARENTS_REF = 0x01,
 +      REMOVE_EMPTY_PARENTS_REFLOG = 0x02
 +};
 +
  /*
 - * Remove empty parents, but spare refs/ and immediate subdirs.
 - * Note: munges *name.
 + * Remove empty parent directories associated with the specified
 + * reference and/or its reflog, but spare [logs/]refs/ and immediate
 + * subdirs. flags is a combination of REMOVE_EMPTY_PARENTS_REF and/or
 + * REMOVE_EMPTY_PARENTS_REFLOG.
   */
 -static void try_remove_empty_parents(char *name)
 +static void try_remove_empty_parents(const char *refname, unsigned int flags)
  {
 +      struct strbuf buf = STRBUF_INIT;
        char *p, *q;
        int i;
 -      p = name;
 +
 +      strbuf_addstr(&buf, refname);
 +      p = buf.buf;
        for (i = 0; i < 2; i++) { /* refs/{heads,tags,...}/ */
                while (*p && *p != '/')
                        p++;
                while (*p == '/')
                        p++;
        }
 -      for (q = p; *q; q++)
 -              ;
 -      while (1) {
 +      q = buf.buf + buf.len;
 +      while (flags & (REMOVE_EMPTY_PARENTS_REF | REMOVE_EMPTY_PARENTS_REFLOG)) {
                while (q > p && *q != '/')
                        q--;
                while (q > p && *(q-1) == '/')
                        q--;
                if (q == p)
                        break;
 -              *q = '\0';
 -              if (rmdir(git_path("%s", name)))
 -                      break;
 +              strbuf_setlen(&buf, q - buf.buf);
 +              if ((flags & REMOVE_EMPTY_PARENTS_REF) &&
 +                  rmdir(git_path("%s", buf.buf)))
 +                      flags &= ~REMOVE_EMPTY_PARENTS_REF;
 +              if ((flags & REMOVE_EMPTY_PARENTS_REFLOG) &&
 +                  rmdir(git_path("logs/%s", buf.buf)))
 +                      flags &= ~REMOVE_EMPTY_PARENTS_REFLOG;
        }
 +      strbuf_release(&buf);
  }
  
  /* make sure nobody touched the ref, and unlink */
@@@ -2346,6 -2365,7 +2361,6 @@@ static void prune_ref(struct ref_to_pru
        }
        ref_transaction_free(transaction);
        strbuf_release(&err);
 -      try_remove_empty_parents(r->name);
  }
  
  static void prune_refs(struct ref_to_prune *r)
@@@ -2392,7 -2412,7 +2407,7 @@@ static int repack_without_refs(struct f
        struct string_list_item *refname;
        int ret, needs_repacking = 0, removed = 0;
  
-       assert_main_repository(&refs->base, "repack_without_refs");
+       files_assert_main_repository(refs, "repack_without_refs");
        assert(err);
  
        /* Look for a packed ref */
        return ret;
  }
  
 -static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
 -{
 -      assert(err);
 -
 -      if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
 -              /*
 -               * loose.  The loose file name is the same as the
 -               * lockfile name, minus ".lock":
 -               */
 -              char *loose_filename = get_locked_file_path(lock->lk);
 -              int res = unlink_or_msg(loose_filename, err);
 -              free(loose_filename);
 -              if (res)
 -                      return 1;
 -      }
 -      return 0;
 -}
 -
  static int files_delete_refs(struct ref_store *ref_store,
                             struct string_list *refnames, unsigned int flags)
  {
   */
  #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
  
 -static int rename_tmp_log(const char *newrefname)
 +static int rename_tmp_log_callback(const char *path, void *cb)
  {
 -      int attempts_remaining = 4;
 -      struct strbuf path = STRBUF_INIT;
 -      int ret = -1;
 +      int *true_errno = cb;
  
 - retry:
 -      strbuf_reset(&path);
 -      strbuf_git_path(&path, "logs/%s", newrefname);
 -      switch (safe_create_leading_directories_const(path.buf)) {
 -      case SCLD_OK:
 -              break; /* success */
 -      case SCLD_VANISHED:
 -              if (--attempts_remaining > 0)
 -                      goto retry;
 -              /* fall through */
 -      default:
 -              error("unable to create directory for %s", newrefname);
 -              goto out;
 +      if (rename(git_path(TMP_RENAMED_LOG), path)) {
 +              /*
 +               * rename(a, b) when b is an existing directory ought
 +               * to result in ISDIR, but Solaris 5.8 gives ENOTDIR.
 +               * Sheesh. Record the true errno for error reporting,
 +               * but report EISDIR to raceproof_create_file() so
 +               * that it knows to retry.
 +               */
 +              *true_errno = errno;
 +              if (errno == ENOTDIR)
 +                      errno = EISDIR;
 +              return -1;
 +      } else {
 +              return 0;
        }
 +}
  
 -      if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
 -              if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
 -                      /*
 -                       * rename(a, b) when b is an existing
 -                       * directory ought to result in ISDIR, but
 -                       * Solaris 5.8 gives ENOTDIR.  Sheesh.
 -                       */
 -                      if (remove_empty_directories(&path)) {
 -                              error("Directory not empty: logs/%s", newrefname);
 -                              goto out;
 -                      }
 -                      goto retry;
 -              } else if (errno == ENOENT && --attempts_remaining > 0) {
 -                      /*
 -                       * Maybe another process just deleted one of
 -                       * the directories in the path to newrefname.
 -                       * Try again from the beginning.
 -                       */
 -                      goto retry;
 -              } else {
 -                      error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
 -                              newrefname, strerror(errno));
 -                      goto out;
 -              }
 +static int rename_tmp_log(const char *newrefname)
 +{
 +      char *path = git_pathdup("logs/%s", newrefname);
 +      int ret, true_errno;
 +
 +      ret = raceproof_create_file(path, rename_tmp_log_callback, &true_errno);
 +      if (ret) {
 +              if (errno == EISDIR)
 +                      error("directory not empty: %s", path);
 +              else
 +                      error("unable to move logfile %s to %s: %s",
 +                            git_path(TMP_RENAMED_LOG), path,
 +                            strerror(true_errno));
        }
 -      ret = 0;
 -out:
 -      strbuf_release(&path);
 +
 +      free(path);
        return ret;
  }
  
@@@ -2596,7 -2646,7 +2611,7 @@@ static int files_rename_ref(struct ref_
        if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
                           sha1, NULL) &&
            delete_ref(newrefname, NULL, REF_NODEREF)) {
 -              if (errno==EISDIR) {
 +              if (errno == EISDIR) {
                        struct strbuf path = STRBUF_INIT;
                        int result;
  
        }
  
        flag = log_all_ref_updates;
 -      log_all_ref_updates = 0;
 +      log_all_ref_updates = LOG_REFS_NONE;
        if (write_ref_to_lockfile(lock, orig_sha1, &err) ||
            commit_ref_update(refs, lock, orig_sha1, NULL, &err)) {
                error("unable to write current sha1 into %s: %s", oldrefname, err.buf);
@@@ -2705,89 -2755,66 +2720,89 @@@ static int commit_ref(struct ref_lock *
        return 0;
  }
  
 +static int open_or_create_logfile(const char *path, void *cb)
 +{
 +      int *fd = cb;
 +
 +      *fd = open(path, O_APPEND | O_WRONLY | O_CREAT, 0666);
 +      return (*fd < 0) ? -1 : 0;
 +}
 +
  /*
 - * Create a reflog for a ref.  If force_create = 0, the reflog will
 - * only be created for certain refs (those for which
 - * should_autocreate_reflog returns non-zero.  Otherwise, create it
 - * regardless of the ref name.  Fill in *err and return -1 on failure.
 + * Create a reflog for a ref. If force_create = 0, only create the
 + * reflog for certain refs (those for which should_autocreate_reflog
 + * returns non-zero). Otherwise, create it regardless of the reference
 + * name. If the logfile already existed or was created, return 0 and
 + * set *logfd to the file descriptor opened for appending to the file.
 + * If no logfile exists and we decided not to create one, return 0 and
 + * set *logfd to -1. On failure, fill in *err, set *logfd to -1, and
 + * return -1.
   */
 -static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
 +static int log_ref_setup(const char *refname, int force_create,
 +                       int *logfd, struct strbuf *err)
  {
 -      int logfd, oflags = O_APPEND | O_WRONLY;
 +      char *logfile = git_pathdup("logs/%s", refname);
  
 -      strbuf_git_path(logfile, "logs/%s", refname);
        if (force_create || should_autocreate_reflog(refname)) {
 -              if (safe_create_leading_directories(logfile->buf) < 0) {
 -                      strbuf_addf(err, "unable to create directory for '%s': "
 -                                  "%s", logfile->buf, strerror(errno));
 -                      return -1;
 -              }
 -              oflags |= O_CREAT;
 -      }
 -
 -      logfd = open(logfile->buf, oflags, 0666);
 -      if (logfd < 0) {
 -              if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
 -                      return 0;
 +              if (raceproof_create_file(logfile, open_or_create_logfile, logfd)) {
 +                      if (errno == ENOENT)
 +                              strbuf_addf(err, "unable to create directory for '%s': "
 +                                          "%s", logfile, strerror(errno));
 +                      else if (errno == EISDIR)
 +                              strbuf_addf(err, "there are still logs under '%s'",
 +                                          logfile);
 +                      else
 +                              strbuf_addf(err, "unable to append to '%s': %s",
 +                                          logfile, strerror(errno));
  
 -              if (errno == EISDIR) {
 -                      if (remove_empty_directories(logfile)) {
 -                              strbuf_addf(err, "there are still logs under "
 -                                          "'%s'", logfile->buf);
 -                              return -1;
 -                      }
 -                      logfd = open(logfile->buf, oflags, 0666);
 +                      goto error;
                }
 -
 -              if (logfd < 0) {
 -                      strbuf_addf(err, "unable to append to '%s': %s",
 -                                  logfile->buf, strerror(errno));
 -                      return -1;
 +      } else {
 +              *logfd = open(logfile, O_APPEND | O_WRONLY, 0666);
 +              if (*logfd < 0) {
 +                      if (errno == ENOENT || errno == EISDIR) {
 +                              /*
 +                               * The logfile doesn't already exist,
 +                               * but that is not an error; it only
 +                               * means that we won't write log
 +                               * entries to it.
 +                               */
 +                              ;
 +                      } else {
 +                              strbuf_addf(err, "unable to append to '%s': %s",
 +                                          logfile, strerror(errno));
 +                              goto error;
 +                      }
                }
        }
  
 -      adjust_shared_perm(logfile->buf);
 -      close(logfd);
 +      if (*logfd >= 0)
 +              adjust_shared_perm(logfile);
 +
 +      free(logfile);
        return 0;
 -}
  
 +error:
 +      free(logfile);
 +      return -1;
 +}
  
  static int files_create_reflog(struct ref_store *ref_store,
                               const char *refname, int force_create,
                               struct strbuf *err)
  {
 -      int ret;
 -      struct strbuf sb = STRBUF_INIT;
 +      int fd;
  
        /* Check validity (but we don't need the result): */
        files_downcast(ref_store, 0, "create_reflog");
  
 -      ret = log_ref_setup(refname, &sb, err, force_create);
 -      strbuf_release(&sb);
 -      return ret;
 +      if (log_ref_setup(refname, force_create, &fd, err))
 +              return -1;
 +
 +      if (fd >= 0)
 +              close(fd);
 +
 +      return 0;
  }
  
  static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
        return 0;
  }
  
 -static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
 -                         const unsigned char *new_sha1, const char *msg,
 -                         struct strbuf *logfile, int flags,
 -                         struct strbuf *err)
 +int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
 +                      const unsigned char *new_sha1, const char *msg,
 +                      int flags, struct strbuf *err)
  {
 -      int logfd, result, oflags = O_APPEND | O_WRONLY;
 +      int logfd, result;
  
 -      if (log_all_ref_updates < 0)
 -              log_all_ref_updates = !is_bare_repository();
 +      if (log_all_ref_updates == LOG_REFS_UNSET)
 +              log_all_ref_updates = is_bare_repository() ? LOG_REFS_NONE : LOG_REFS_NORMAL;
  
 -      result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
 +      result = log_ref_setup(refname, flags & REF_FORCE_CREATE_REFLOG,
 +                             &logfd, err);
  
        if (result)
                return result;
  
 -      logfd = open(logfile->buf, oflags);
        if (logfd < 0)
                return 0;
        result = log_ref_write_fd(logfd, old_sha1, new_sha1,
                                  git_committer_info(0), msg);
        if (result) {
 -              strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
 -                          strerror(errno));
 +              int save_errno = errno;
 +
 +              strbuf_addf(err, "unable to append to '%s': %s",
 +                          git_path("logs/%s", refname), strerror(save_errno));
                close(logfd);
                return -1;
        }
        if (close(logfd)) {
 -              strbuf_addf(err, "unable to append to '%s': %s", logfile->buf,
 -                          strerror(errno));
 +              int save_errno = errno;
 +
 +              strbuf_addf(err, "unable to append to '%s': %s",
 +                          git_path("logs/%s", refname), strerror(save_errno));
                return -1;
        }
        return 0;
  }
  
 -static int log_ref_write(const char *refname, const unsigned char *old_sha1,
 -                       const unsigned char *new_sha1, const char *msg,
 -                       int flags, struct strbuf *err)
 -{
 -      return files_log_ref_write(refname, old_sha1, new_sha1, msg, flags,
 -                                 err);
 -}
 -
 -int files_log_ref_write(const char *refname, const unsigned char *old_sha1,
 -                      const unsigned char *new_sha1, const char *msg,
 -                      int flags, struct strbuf *err)
 -{
 -      struct strbuf sb = STRBUF_INIT;
 -      int ret = log_ref_write_1(refname, old_sha1, new_sha1, msg, &sb, flags,
 -                                err);
 -      strbuf_release(&sb);
 -      return ret;
 -}
 -
  /*
   * Write sha1 into the open lockfile, then close the lockfile. On
   * errors, rollback the lockfile, fill in *err and
@@@ -2902,11 -2945,10 +2917,11 @@@ static int commit_ref_update(struct fil
                             const unsigned char *sha1, const char *logmsg,
                             struct strbuf *err)
  {
-       assert_main_repository(&refs->base, "commit_ref_update");
+       files_assert_main_repository(refs, "commit_ref_update");
  
        clear_loose_ref_cache(refs);
 -      if (log_ref_write(lock->ref_name, lock->old_oid.hash, sha1, logmsg, 0, err)) {
 +      if (files_log_ref_write(lock->ref_name, lock->old_oid.hash, sha1,
 +                              logmsg, 0, err)) {
                char *old_msg = strbuf_detach(err, NULL);
                strbuf_addf(err, "cannot update the ref '%s': %s",
                            lock->ref_name, old_msg);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name)) {
                        struct strbuf log_err = STRBUF_INIT;
 -                      if (log_ref_write("HEAD", lock->old_oid.hash, sha1,
 +                      if (files_log_ref_write("HEAD", lock->old_oid.hash, sha1,
                                          logmsg, 0, &log_err)) {
                                error("%s", log_err.buf);
                                strbuf_release(&log_err);
@@@ -2976,8 -3018,7 +2991,8 @@@ static void update_symref_reflog(struc
        struct strbuf err = STRBUF_INIT;
        unsigned char new_sha1[20];
        if (logmsg && !read_ref(target, new_sha1) &&
 -          log_ref_write(refname, lock->old_oid.hash, new_sha1, logmsg, 0, &err)) {
 +          files_log_ref_write(refname, lock->old_oid.hash, new_sha1,
 +                              logmsg, 0, &err)) {
                error("%s", err.buf);
                strbuf_release(&err);
        }
@@@ -3534,7 -3575,7 +3549,7 @@@ static int lock_ref_for_update(struct f
        int ret;
        struct ref_lock *lock;
  
-       assert_main_repository(&refs->base, "lock_ref_for_update");
+       files_assert_main_repository(refs, "lock_ref_for_update");
  
        if ((update->flags & REF_HAVE_NEW) && is_null_sha1(update->new_sha1))
                update->flags |= REF_DELETING;
@@@ -3752,11 -3793,9 +3767,11 @@@ static int files_transaction_commit(str
  
                if (update->flags & REF_NEEDS_COMMIT ||
                    update->flags & REF_LOG_ONLY) {
 -                      if (log_ref_write(lock->ref_name, lock->old_oid.hash,
 -                                        update->new_sha1,
 -                                        update->msg, update->flags, err)) {
 +                      if (files_log_ref_write(lock->ref_name,
 +                                              lock->old_oid.hash,
 +                                              update->new_sha1,
 +                                              update->msg, update->flags,
 +                                              err)) {
                                char *old_msg = strbuf_detach(err, NULL);
  
                                strbuf_addf(err, "cannot update the ref '%s': %s",
  
                if (update->flags & REF_DELETING &&
                    !(update->flags & REF_LOG_ONLY)) {
 -                      if (delete_ref_loose(lock, update->type, err)) {
 -                              ret = TRANSACTION_GENERIC_ERROR;
 -                              goto cleanup;
 +                      if (!(update->type & REF_ISPACKED) ||
 +                          update->type & REF_ISSYMREF) {
 +                              /* It is a loose reference. */
 +                              if (unlink_or_msg(git_path("%s", lock->ref_name), err)) {
 +                                      ret = TRANSACTION_GENERIC_ERROR;
 +                                      goto cleanup;
 +                              }
 +                              update->flags |= REF_DELETED_LOOSE;
                        }
  
                        if (!(update->flags & REF_ISPRUNING))
                ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 -      for_each_string_list_item(ref_to_delete, &refs_to_delete)
 -              unlink_or_warn(git_path("logs/%s", ref_to_delete->string));
 +
 +      /* Delete the reflogs of any references that were deleted: */
 +      for_each_string_list_item(ref_to_delete, &refs_to_delete) {
 +              if (!unlink_or_warn(git_path("logs/%s", ref_to_delete->string)))
 +                      try_remove_empty_parents(ref_to_delete->string,
 +                                               REMOVE_EMPTY_PARENTS_REFLOG);
 +      }
 +
        clear_loose_ref_cache(refs);
  
  cleanup:
        transaction->state = REF_TRANSACTION_CLOSED;
  
 -      for (i = 0; i < transaction->nr; i++)
 -              if (transaction->updates[i]->backend_data)
 -                      unlock_ref(transaction->updates[i]->backend_data);
 +      for (i = 0; i < transaction->nr; i++) {
 +              struct ref_update *update = transaction->updates[i];
 +              struct ref_lock *lock = update->backend_data;
 +
 +              if (lock)
 +                      unlock_ref(lock);
 +
 +              if (update->flags & REF_DELETED_LOOSE) {
 +                      /*
 +                       * The loose reference was deleted. Delete any
 +                       * empty parent directories. (Note that this
 +                       * can only work because we have already
 +                       * removed the lockfile.)
 +                       */
 +                      try_remove_empty_parents(update->refname,
 +                                               REMOVE_EMPTY_PARENTS_REF);
 +              }
 +      }
 +
        string_list_clear(&refs_to_delete, 0);
        free(head_ref);
        string_list_clear(&affected_refnames, 0);
diff --combined refs/refs-internal.h
index 611ab5b1d1ca250fca54749119489dbaba6dcc22,33adbf93b564ce33ecfb2cdde92c02c9393dcb48..fa93c9a32ec4c52b9cd6597b9c2ebb4b4501206b
   */
  #define REF_UPDATE_VIA_HEAD 0x100
  
 +/*
 + * Used as a flag in ref_update::flags when the loose reference has
 + * been deleted.
 + */
 +#define REF_DELETED_LOOSE 0x200
 +
  /*
   * Return true iff refname is minimally safe. "Safe" here means that
   * deleting a loose reference by this name will not do any damage, for
   * This function does not check that the reference name is legal; for
   * that, use check_refname_format().
   *
 - * We consider a refname that starts with "refs/" to be safe as long
 - * as any ".." components that it might contain do not escape "refs/".
 - * Names that do not start with "refs/" are considered safe iff they
 - * consist entirely of upper case characters and '_' (like "HEAD" and
 - * "MERGE_HEAD" but not "config" or "FOO/BAR").
 + * A refname that starts with "refs/" is considered safe iff it
 + * doesn't contain any "." or ".." components or consecutive '/'
 + * characters, end with '/', or (on Windows) contain any '\'
 + * characters. Names that do not start with "refs/" are considered
 + * safe iff they consist entirely of upper case characters and '_'
 + * (like "HEAD" and "MERGE_HEAD" but not "config" or "FOO/BAR").
   */
  int refname_is_safe(const char *refname);
  
@@@ -140,6 -133,8 +140,6 @@@ int verify_refname_available(const cha
   */
  int copy_reflog_msg(char *buf, const char *msg);
  
 -int should_autocreate_reflog(const char *refname);
 -
  /**
   * Information needed for a single ref update. Set new_sha1 to the new
   * value or to null_sha1 to delete the ref. To check the old value
@@@ -162,9 -157,8 +162,9 @@@ struct ref_update 
  
        /*
         * One or more of REF_HAVE_NEW, REF_HAVE_OLD, REF_NODEREF,
 -       * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY, and
 -       * REF_UPDATE_VIA_HEAD:
 +       * REF_DELETING, REF_ISPRUNING, REF_LOG_ONLY,
 +       * REF_UPDATE_VIA_HEAD, REF_NEEDS_COMMIT, and
 +       * REF_DELETED_LOOSE:
         */
        unsigned int flags;
  
@@@ -635,47 -629,14 +635,14 @@@ extern struct ref_storage_be refs_be_fi
  struct ref_store {
        /* The backend describing this ref_store's storage scheme: */
        const struct ref_storage_be *be;
-       /*
-        * The name of the submodule represented by this object, or
-        * the empty string if it represents the main repository's
-        * reference store:
-        */
-       const char *submodule;
-       /*
-        * Submodule reference store instances are stored in a linked
-        * list using this pointer.
-        */
-       struct ref_store *next;
  };
  
  /*
-  * Fill in the generic part of refs for the specified submodule and
-  * add it to our collection of reference stores.
+  * Fill in the generic part of refs and add it to our collection of
+  * reference stores.
   */
  void base_ref_store_init(struct ref_store *refs,
-                        const struct ref_storage_be *be,
-                        const char *submodule);
- /*
-  * Create, record, and return a ref_store instance for the specified
-  * submodule (or the main repository if submodule is NULL).
-  *
-  * For backwards compatibility, submodule=="" is treated the same as
-  * submodule==NULL.
-  */
- struct ref_store *ref_store_init(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.
-  *
-  * For backwards compatibility, submodule=="" is treated the same as
-  * submodule==NULL.
-  */
- struct ref_store *lookup_ref_store(const char *submodule);
+                        const struct ref_storage_be *be);
  
  /*
   * Return the ref_store instance for the specified submodule. For the
   */
  struct ref_store *get_ref_store(const char *submodule);
  
- /*
-  * Die if refs is for a submodule (i.e., not for the main repository).
-  * caller is used in any necessary error messages.
-  */
- void assert_main_repository(struct ref_store *refs, const char *caller);
+ const char *resolve_ref_recursively(struct ref_store *refs,
+                                   const char *refname,
+                                   int resolve_flags,
+                                   unsigned char *sha1, int *flags);
  
  #endif /* REFS_REFS_INTERNAL_H */