]> git.ipfire.org Git - thirdparty/git.git/commitdiff
Merge branch 'jt/no-abuse-alternate-odb-for-submodules'
authorJunio C Hamano <gitster@pobox.com>
Mon, 25 Oct 2021 23:06:56 +0000 (16:06 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 25 Oct 2021 23:06:56 +0000 (16:06 -0700)
Follow through the work to use the repo interface to access
submodule objects in-process, instead of abusing the alternate
object database interface.

* jt/no-abuse-alternate-odb-for-submodules:
  submodule: trace adding submodule ODB as alternate
  submodule: pass repo to check_has_commit()
  object-file: only register submodule ODB if needed
  merge-{ort,recursive}: remove add_submodule_odb()
  refs: peeling non-the_repository iterators is BUG
  refs: teach arbitrary repo support to iterators
  refs: plumb repo into ref stores

12 files changed:
1  2 
merge-ort.c
merge-recursive.c
object-file.c
refs.c
refs/files-backend.c
refs/packed-backend.c
refs/ref-cache.c
refs/ref-cache.h
refs/refs-internal.h
strbuf.h
submodule.c
t/README

diff --combined merge-ort.c
index e5456f4722894a20f09c6255d921306ab5884ccd,fbc5c204c1cd5fd2a51ff7beae9d06c25eecd70c..0342f104836b69a7889b3fa686c7c359c27e5dd6
@@@ -609,6 -609,7 +609,7 @@@ static int err(struct merge_options *op
  
  static void format_commit(struct strbuf *sb,
                          int indent,
+                         struct repository *repo,
                          struct commit *commit)
  {
        struct merge_remote_desc *desc;
                return;
        }
  
-       format_commit_message(commit, "%h %s", sb, &ctx);
+       repo_format_commit_message(repo, commit, "%h %s", sb, &ctx);
        strbuf_addch(sb, '\n');
  }
  
@@@ -1578,17 -1579,6 +1579,6 @@@ static int merge_submodule(struct merge
        if (is_null_oid(b))
                return 0;
  
-       /*
-        * NEEDSWORK: Remove this when all submodule object accesses are
-        * through explicitly specified repositores.
-        */
-       if (add_submodule_odb(path)) {
-               path_msg(opt, path, 0,
-                        _("Failed to merge submodule %s (not checked out)"),
-                        path);
-               return 0;
-       }
        if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
                path_msg(opt, path, 0,
                                _("Failed to merge submodule %s (not checked out)"),
                break;
  
        case 1:
-               format_commit(&sb, 4,
+               format_commit(&sb, 4, &subrepo,
                              (struct commit *)merges.objects[0].item);
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s, but a possible merge "
                break;
        default:
                for (i = 0; i < merges.nr; i++)
-                       format_commit(&sb, 4,
+                       format_commit(&sb, 4, &subrepo,
                                      (struct commit *)merges.objects[i].item);
                path_msg(opt, path, 0,
                         _("Failed to merge submodule %s, but multiple "
@@@ -4062,7 -4052,11 +4052,7 @@@ static int checkout(struct merge_option
        unpack_opts.quiet = 0; /* FIXME: sequencer might want quiet? */
        unpack_opts.verbose_update = (opt->verbosity > 2);
        unpack_opts.fn = twoway_merge;
 -      if (1/* FIXME: opts->overwrite_ignore*/) {
 -              CALLOC_ARRAY(unpack_opts.dir, 1);
 -              unpack_opts.dir->flags |= DIR_SHOW_IGNORED;
 -              setup_standard_excludes(unpack_opts.dir);
 -      }
 +      unpack_opts.preserve_ignored = 0; /* FIXME: !opts->overwrite_ignore */
        parse_tree(prev);
        init_tree_desc(&trees[0], prev->buffer, prev->size);
        parse_tree(next);
  
        ret = unpack_trees(2, trees, &unpack_opts);
        clear_unpack_trees_porcelain(&unpack_opts);
 -      dir_clear(unpack_opts.dir);
 -      FREE_AND_NULL(unpack_opts.dir);
        return ret;
  }
  
@@@ -4107,7 -4103,7 +4097,7 @@@ static int record_conflicted_index_entr
        state.istate = index;
        original_cache_nr = index->cache_nr;
  
 -      /* Put every entry from paths into plist, then sort */
 +      /* Append every entry from conflicted into index, then sort */
        strmap_for_each_entry(&opt->priv->conflicted, &iter, e) {
                const char *path = e->key;
                struct conflict_info *ci = e->value;
diff --combined merge-recursive.c
index c5537518899684b2fa4809211fee4309b9b57290,80594153f13d8520c25d19d52e6fafd9d99975c1..d9457797dbb73bfed720ac9ca0b9bcf56575c62a
@@@ -334,7 -334,9 +334,9 @@@ static void output(struct merge_option
                flush_output(opt);
  }
  
- static void output_commit_title(struct merge_options *opt, struct commit *commit)
+ static void repo_output_commit_title(struct merge_options *opt,
+                                    struct repository *repo,
+                                    struct commit *commit)
  {
        struct merge_remote_desc *desc;
  
        if (desc)
                strbuf_addf(&opt->obuf, "virtual %s\n", desc->name);
        else {
-               strbuf_add_unique_abbrev(&opt->obuf, &commit->object.oid,
-                                        DEFAULT_ABBREV);
+               strbuf_repo_add_unique_abbrev(&opt->obuf, repo,
+                                             &commit->object.oid,
+                                             DEFAULT_ABBREV);
                strbuf_addch(&opt->obuf, ' ');
-               if (parse_commit(commit) != 0)
+               if (repo_parse_commit(repo, commit) != 0)
                        strbuf_addstr(&opt->obuf, _("(bad commit)\n"));
                else {
                        const char *title;
-                       const char *msg = get_commit_buffer(commit, NULL);
+                       const char *msg = repo_get_commit_buffer(repo, commit, NULL);
                        int len = find_commit_subject(msg, &title);
                        if (len)
                                strbuf_addf(&opt->obuf, "%.*s\n", len, title);
-                       unuse_commit_buffer(commit, msg);
+                       repo_unuse_commit_buffer(repo, commit, msg);
                }
        }
        flush_output(opt);
  }
  
+ static void output_commit_title(struct merge_options *opt, struct commit *commit)
+ {
+       repo_output_commit_title(opt, the_repository, commit);
+ }
  static int add_cacheinfo(struct merge_options *opt,
                         const struct diff_filespec *blob,
                         const char *path, int stage, int refresh, int options)
@@@ -409,11 -417,8 +417,11 @@@ static int unpack_trees_start(struct me
        memset(&opt->priv->unpack_opts, 0, sizeof(opt->priv->unpack_opts));
        if (opt->priv->call_depth)
                opt->priv->unpack_opts.index_only = 1;
 -      else
 +      else {
                opt->priv->unpack_opts.update = 1;
 +              /* FIXME: should only do this if !overwrite_ignore */
 +              opt->priv->unpack_opts.preserve_ignored = 0;
 +      }
        opt->priv->unpack_opts.merge = 1;
        opt->priv->unpack_opts.head_idx = 2;
        opt->priv->unpack_opts.fn = threeway_merge;
@@@ -1152,14 -1157,14 +1160,14 @@@ static int find_first_merges(struct rep
        return result->nr;
  }
  
- static void print_commit(struct commit *commit)
+ static void print_commit(struct repository *repo, struct commit *commit)
  {
        struct strbuf sb = STRBUF_INIT;
        struct pretty_print_context ctx = {0};
        ctx.date_mode.type = DATE_NORMAL;
        /* FIXME: Merge this with output_commit_title() */
        assert(!merge_remote_util(commit));
-       format_commit_message(commit, " %h: %m %s", &sb, &ctx);
+       repo_format_commit_message(repo, commit, " %h: %m %s", &sb, &ctx);
        fprintf(stderr, "%s\n", sb.buf);
        strbuf_release(&sb);
  }
@@@ -1199,15 -1204,6 +1207,6 @@@ static int merge_submodule(struct merge
        if (is_null_oid(b))
                return 0;
  
-       /*
-        * NEEDSWORK: Remove this when all submodule object accesses are
-        * through explicitly specified repositores.
-        */
-       if (add_submodule_odb(path)) {
-               output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
-               return 0;
-       }
        if (repo_submodule_init(&subrepo, opt->repo, path, null_oid())) {
                output(opt, 1, _("Failed to merge submodule %s (not checked out)"), path);
                return 0;
                oidcpy(result, b);
                if (show(opt, 3)) {
                        output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
-                       output_commit_title(opt, commit_b);
+                       repo_output_commit_title(opt, &subrepo, commit_b);
                } else if (show(opt, 2))
                        output(opt, 2, _("Fast-forwarding submodule %s"), path);
                else
                oidcpy(result, a);
                if (show(opt, 3)) {
                        output(opt, 3, _("Fast-forwarding submodule %s to the following commit:"), path);
-                       output_commit_title(opt, commit_a);
+                       repo_output_commit_title(opt, &subrepo, commit_a);
                } else if (show(opt, 2))
                        output(opt, 2, _("Fast-forwarding submodule %s"), path);
                else
        case 1:
                output(opt, 1, _("Failed to merge submodule %s (not fast-forward)"), path);
                output(opt, 2, _("Found a possible merge resolution for the submodule:\n"));
-               print_commit((struct commit *) merges.objects[0].item);
+               print_commit(&subrepo, (struct commit *) merges.objects[0].item);
                output(opt, 2, _(
                       "If this is correct simply add it to the index "
                       "for example\n"
        default:
                output(opt, 1, _("Failed to merge submodule %s (multiple merges found)"), path);
                for (i = 0; i < merges.nr; i++)
-                       print_commit((struct commit *) merges.objects[i].item);
+                       print_commit(&subrepo, (struct commit *) merges.objects[i].item);
        }
  
        object_array_clear(&merges);
diff --combined object-file.c
index 8b35fd85bf3390679d9892c49e3ba9ede5b06f8f,0a1835fe309accc86569239e2d842096741f386e..b3e1718885a7cc09a412329724f7b8c82103f65e
@@@ -415,6 -415,74 +415,6 @@@ enum scld_error safe_create_leading_dir
        return result;
  }
  
 -int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
 -{
 -      /*
 -       * The number of times we will try to remove empty directories
 -       * in the way of path. This is only 1 because if another
 -       * process is racily creating directories that conflict with
 -       * us, we don't want to fight against them.
 -       */
 -      int remove_directories_remaining = 1;
 -
 -      /*
 -       * The number of times that we will try to create the
 -       * directories containing path. We are willing to attempt this
 -       * more than once, because another process could be trying to
 -       * clean up empty directories at the same time as we are
 -       * trying to create them.
 -       */
 -      int create_directories_remaining = 3;
 -
 -      /* A scratch copy of path, filled lazily if we need it: */
 -      struct strbuf path_copy = STRBUF_INIT;
 -
 -      int ret, save_errno;
 -
 -      /* Sanity check: */
 -      assert(*path);
 -
 -retry_fn:
 -      ret = fn(path, cb);
 -      save_errno = errno;
 -      if (!ret)
 -              goto out;
 -
 -      if (errno == EISDIR && remove_directories_remaining-- > 0) {
 -              /*
 -               * A directory is in the way. Maybe it is empty; try
 -               * to remove it:
 -               */
 -              if (!path_copy.len)
 -                      strbuf_addstr(&path_copy, path);
 -
 -              if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
 -                      goto retry_fn;
 -      } else if (errno == ENOENT && create_directories_remaining-- > 0) {
 -              /*
 -               * Maybe the containing directory didn't exist, or
 -               * maybe it was just deleted by a process that is
 -               * racing with us to clean up empty directories. Try
 -               * to create it:
 -               */
 -              enum scld_error scld_result;
 -
 -              if (!path_copy.len)
 -                      strbuf_addstr(&path_copy, path);
 -
 -              do {
 -                      scld_result = safe_create_leading_directories(path_copy.buf);
 -                      if (scld_result == SCLD_OK)
 -                              goto retry_fn;
 -              } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
 -      }
 -
 -out:
 -      strbuf_release(&path_copy);
 -      errno = save_errno;
 -      return ret;
 -}
 -
  static void fill_loose_path(struct strbuf *buf, const struct object_id *oid)
  {
        int i;
@@@ -1016,11 -1084,9 +1016,11 @@@ void *xmmap(void *start, size_t length
   * the streaming interface and rehash it to do the same.
   */
  int check_object_signature(struct repository *r, const struct object_id *oid,
 -                         void *map, unsigned long size, const char *type)
 +                         void *map, unsigned long size, const char *type,
 +                         struct object_id *real_oidp)
  {
 -      struct object_id real_oid;
 +      struct object_id tmp;
 +      struct object_id *real_oid = real_oidp ? real_oidp : &tmp;
        enum object_type obj_type;
        struct git_istream *st;
        git_hash_ctx c;
        int hdrlen;
  
        if (map) {
 -              hash_object_file(r->hash_algo, map, size, type, &real_oid);
 -              return !oideq(oid, &real_oid) ? -1 : 0;
 +              hash_object_file(r->hash_algo, map, size, type, real_oid);
 +              return !oideq(oid, real_oid) ? -1 : 0;
        }
  
        st = open_istream(r, oid, &obj_type, &size, NULL);
                        break;
                r->hash_algo->update_fn(&c, buf, readlen);
        }
 -      r->hash_algo->final_oid_fn(&real_oid, &c);
 +      r->hash_algo->final_oid_fn(real_oid, &c);
        close_istream(st);
 -      return !oideq(oid, &real_oid) ? -1 : 0;
 +      return !oideq(oid, real_oid) ? -1 : 0;
  }
  
  int git_open_cloexec(const char *name, int flags)
@@@ -1189,14 -1255,11 +1189,14 @@@ void *map_loose_object(struct repositor
        return map_loose_object_1(r, NULL, oid, size);
  }
  
 -static int unpack_loose_short_header(git_zstream *stream,
 -                                   unsigned char *map, unsigned long mapsize,
 -                                   void *buffer, unsigned long bufsiz)
 +enum unpack_loose_header_result unpack_loose_header(git_zstream *stream,
 +                                                  unsigned char *map,
 +                                                  unsigned long mapsize,
 +                                                  void *buffer,
 +                                                  unsigned long bufsiz,
 +                                                  struct strbuf *header)
  {
 -      int ret;
 +      int status;
  
        /* Get the data stream */
        memset(stream, 0, sizeof(*stream));
  
        git_inflate_init(stream);
        obj_read_unlock();
 -      ret = git_inflate(stream, 0);
 +      status = git_inflate(stream, 0);
        obj_read_lock();
 -
 -      return ret;
 -}
 -
 -int unpack_loose_header(git_zstream *stream,
 -                      unsigned char *map, unsigned long mapsize,
 -                      void *buffer, unsigned long bufsiz)
 -{
 -      int status = unpack_loose_short_header(stream, map, mapsize,
 -                                             buffer, bufsiz);
 -
        if (status < Z_OK)
 -              return status;
 -
 -      /* Make sure we have the terminating NUL */
 -      if (!memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
 -              return -1;
 -      return 0;
 -}
 -
 -static int unpack_loose_header_to_strbuf(git_zstream *stream, unsigned char *map,
 -                                       unsigned long mapsize, void *buffer,
 -                                       unsigned long bufsiz, struct strbuf *header)
 -{
 -      int status;
 -
 -      status = unpack_loose_short_header(stream, map, mapsize, buffer, bufsiz);
 -      if (status < Z_OK)
 -              return -1;
 +              return ULHR_BAD;
  
        /*
         * Check if entire header is unpacked in the first iteration.
         */
        if (memchr(buffer, '\0', stream->next_out - (unsigned char *)buffer))
 -              return 0;
 +              return ULHR_OK;
 +
 +      /*
 +       * We have a header longer than MAX_HEADER_LEN. The "header"
 +       * here is only non-NULL when we run "cat-file
 +       * --allow-unknown-type".
 +       */
 +      if (!header)
 +              return ULHR_TOO_LONG;
  
        /*
         * buffer[0..bufsiz] was not large enough.  Copy the partial
                stream->next_out = buffer;
                stream->avail_out = bufsiz;
        } while (status != Z_STREAM_END);
 -      return -1;
 +      return ULHR_TOO_LONG;
  }
  
  static void *unpack_loose_rest(git_zstream *stream,
   * too permissive for what we want to check. So do an anal
   * object header parse by hand.
   */
 -static int parse_loose_header_extended(const char *hdr, struct object_info *oi,
 -                                     unsigned int flags)
 +int parse_loose_header(const char *hdr, struct object_info *oi)
  {
        const char *type_buf = hdr;
        unsigned long size;
        type = type_from_string_gently(type_buf, type_len, 1);
        if (oi->type_name)
                strbuf_add(oi->type_name, type_buf, type_len);
 -      /*
 -       * Set type to 0 if its an unknown object and
 -       * we're obtaining the type using '--allow-unknown-type'
 -       * option.
 -       */
 -      if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE) && (type < 0))
 -              type = 0;
 -      else if (type < 0)
 -              die(_("invalid object type"));
        if (oi->typep)
                *oi->typep = type;
  
        /*
         * The length must be followed by a zero byte
         */
 -      return *hdr ? -1 : type;
 -}
 -
 -int parse_loose_header(const char *hdr, unsigned long *sizep)
 -{
 -      struct object_info oi = OBJECT_INFO_INIT;
 +      if (*hdr)
 +              return -1;
  
 -      oi.sizep = sizep;
 -      return parse_loose_header_extended(hdr, &oi, 0);
 +      /*
 +       * The format is valid, but the type may still be bogus. The
 +       * Caller needs to check its oi->typep.
 +       */
 +      return 0;
  }
  
  static int loose_object_info(struct repository *r,
        char hdr[MAX_HEADER_LEN];
        struct strbuf hdrbuf = STRBUF_INIT;
        unsigned long size_scratch;
 +      enum object_type type_scratch;
 +      int allow_unknown = flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE;
  
        if (oi->delta_base_oid)
                oidclr(oi->delta_base_oid);
  
        if (!oi->sizep)
                oi->sizep = &size_scratch;
 +      if (!oi->typep)
 +              oi->typep = &type_scratch;
  
        if (oi->disk_sizep)
                *oi->disk_sizep = mapsize;
 -      if ((flags & OBJECT_INFO_ALLOW_UNKNOWN_TYPE)) {
 -              if (unpack_loose_header_to_strbuf(&stream, map, mapsize, hdr, sizeof(hdr), &hdrbuf) < 0)
 -                      status = error(_("unable to unpack %s header with --allow-unknown-type"),
 -                                     oid_to_hex(oid));
 -      } else if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0)
 +
 +      switch (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
 +                                  allow_unknown ? &hdrbuf : NULL)) {
 +      case ULHR_OK:
 +              if (parse_loose_header(hdrbuf.len ? hdrbuf.buf : hdr, oi) < 0)
 +                      status = error(_("unable to parse %s header"), oid_to_hex(oid));
 +              else if (!allow_unknown && *oi->typep < 0)
 +                      die(_("invalid object type"));
 +
 +              if (!oi->contentp)
 +                      break;
 +              *oi->contentp = unpack_loose_rest(&stream, hdr, *oi->sizep, oid);
 +              if (*oi->contentp)
 +                      goto cleanup;
 +
 +              status = -1;
 +              break;
 +      case ULHR_BAD:
                status = error(_("unable to unpack %s header"),
                               oid_to_hex(oid));
 -      if (status < 0)
 -              ; /* Do nothing */
 -      else if (hdrbuf.len) {
 -              if ((status = parse_loose_header_extended(hdrbuf.buf, oi, flags)) < 0)
 -                      status = error(_("unable to parse %s header with --allow-unknown-type"),
 -                                     oid_to_hex(oid));
 -      } else if ((status = parse_loose_header_extended(hdr, oi, flags)) < 0)
 -              status = error(_("unable to parse %s header"), oid_to_hex(oid));
 -
 -      if (status >= 0 && oi->contentp) {
 -              *oi->contentp = unpack_loose_rest(&stream, hdr,
 -                                                *oi->sizep, oid);
 -              if (!*oi->contentp) {
 -                      git_inflate_end(&stream);
 -                      status = -1;
 -              }
 -      } else
 -              git_inflate_end(&stream);
 +              break;
 +      case ULHR_TOO_LONG:
 +              status = error(_("header for %s too long, exceeds %d bytes"),
 +                             oid_to_hex(oid), MAX_HEADER_LEN);
 +              break;
 +      }
  
 +      git_inflate_end(&stream);
 +cleanup:
        munmap(map, mapsize);
 -      if (status && oi->typep)
 -              *oi->typep = status;
        if (oi->sizep == &size_scratch)
                oi->sizep = NULL;
        strbuf_release(&hdrbuf);
 +      if (oi->typep == &type_scratch)
 +              oi->typep = NULL;
        oi->whence = OI_LOOSE;
 -      return (status < 0) ? status : 0;
 +      return status;
  }
  
  int obj_read_use_lock = 0;
@@@ -1528,7 -1614,14 +1528,14 @@@ static int do_oid_object_info_extended(
                                break;
                }
  
-               if (register_all_submodule_odb_as_alternates())
+               /*
+                * If r is the_repository, this might be an attempt at
+                * accessing a submodule object as if it were in the_repository
+                * (having called add_submodule_odb() on that submodule's ODB).
+                * If any such ODBs exist, register them and try again.
+                */
+               if (r == the_repository &&
+                   register_all_submodule_odb_as_alternates())
                        /* We added some alternates; retry */
                        continue;
  
@@@ -2506,16 -2599,17 +2513,16 @@@ static int check_stream_oid(git_zstrea
  
  int read_loose_object(const char *path,
                      const struct object_id *expected_oid,
 -                    enum object_type *type,
 -                    unsigned long *size,
 -                    void **contents)
 +                    struct object_id *real_oid,
 +                    void **contents,
 +                    struct object_info *oi)
  {
        int ret = -1;
        void *map = NULL;
        unsigned long mapsize;
        git_zstream stream;
        char hdr[MAX_HEADER_LEN];
 -
 -      *contents = NULL;
 +      unsigned long *size = oi->sizep;
  
        map = map_loose_object_1(the_repository, path, NULL, &mapsize);
        if (!map) {
                goto out;
        }
  
 -      if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr)) < 0) {
 +      if (unpack_loose_header(&stream, map, mapsize, hdr, sizeof(hdr),
 +                              NULL) < 0) {
                error(_("unable to unpack header of %s"), path);
                goto out;
        }
  
 -      *type = parse_loose_header(hdr, size);
 -      if (*type < 0) {
 +      if (parse_loose_header(hdr, oi) < 0) {
                error(_("unable to parse header of %s"), path);
                git_inflate_end(&stream);
                goto out;
        }
  
 -      if (*type == OBJ_BLOB && *size > big_file_threshold) {
 +      if (*oi->typep == OBJ_BLOB && *size > big_file_threshold) {
                if (check_stream_oid(&stream, hdr, *size, path, expected_oid) < 0)
                        goto out;
        } else {
                        goto out;
                }
                if (check_object_signature(the_repository, expected_oid,
 -                                         *contents, *size,
 -                                         type_name(*type))) {
 -                      error(_("hash mismatch for %s (expected %s)"), path,
 -                            oid_to_hex(expected_oid));
 +                                         *contents, *size, oi->type_name->buf, real_oid)) {
                        free(*contents);
                        goto out;
                }
diff --combined refs.c
index 7f019c2377effa49a90427ffec0451c400941e0c,c07aeff6f4e36b1db579c6a53db5b79ef2338c79..d7cc0a23a3b65502242650cce698179e3cca27d4
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -10,7 -10,6 +10,7 @@@
  #include "refs.h"
  #include "refs/refs-internal.h"
  #include "run-command.h"
 +#include "hook.h"
  #include "object-store.h"
  #include "object.h"
  #include "tag.h"
@@@ -34,6 -33,11 +34,6 @@@ static struct ref_storage_be *find_ref_
        return NULL;
  }
  
 -int ref_storage_backend_exists(const char *name)
 -{
 -      return find_ref_storage_backend(name) != NULL;
 -}
 -
  /*
   * How to handle various characters in refnames:
   * 0: An acceptable character for refs
@@@ -251,12 -255,13 +251,13 @@@ int refname_is_safe(const char *refname
   * does not exist, emit a warning and return false.
   */
  int ref_resolves_to_object(const char *refname,
+                          struct repository *repo,
                           const struct object_id *oid,
                           unsigned int flags)
  {
        if (flags & REF_ISBROKEN)
                return 0;
-       if (!has_object_file(oid)) {
+       if (!repo_has_object_file(repo, oid)) {
                error(_("%s does not point to a valid object!"), refname);
                return 0;
        }
@@@ -694,7 -699,7 +695,7 @@@ int repo_dwim_log(struct repository *r
                strbuf_addf(&path, *p, len, str);
                ref = refs_resolve_ref_unsafe(refs, path.buf,
                                              RESOLVE_REF_READING,
 -                                            &hash, NULL);
 +                                            oid ? &hash : NULL, NULL);
                if (!ref)
                        continue;
                if (refs_reflog_exists(refs, path.buf))
                        continue;
                if (!logs_found++) {
                        *log = xstrdup(it);
 -                      oidcpy(oid, &hash);
 +                      if (oid)
 +                              oidcpy(oid, &hash);
                }
                if (!warn_ambiguous_refs)
                        break;
@@@ -1676,7 -1680,7 +1677,7 @@@ int refs_read_raw_ref(struct ref_store 
        }
  
        return ref_store->be->read_raw_ref(ref_store, refname, oid, referent,
 -                                         type);
 +                                         type, &errno);
  }
  
  /* This function needs to return a meaningful errno on failure */
@@@ -1870,7 -1874,8 +1871,8 @@@ static struct ref_store *lookup_ref_sto
   * Create, record, and return a ref_store instance for the specified
   * gitdir.
   */
- static struct ref_store *ref_store_init(const char *gitdir,
+ static struct ref_store *ref_store_init(struct repository *repo,
+                                       const char *gitdir,
                                        unsigned int flags)
  {
        const char *be_name = "files";
        if (!be)
                BUG("reference backend %s is unknown", be_name);
  
-       refs = be->init(gitdir, flags);
+       refs = be->init(repo, gitdir, flags);
        return refs;
  }
  
@@@ -1892,7 -1897,7 +1894,7 @@@ struct ref_store *get_main_ref_store(st
        if (!r->gitdir)
                BUG("attempting to get main_ref_store outside of repository");
  
-       r->refs_private = ref_store_init(r->gitdir, REF_STORE_ALL_CAPS);
+       r->refs_private = ref_store_init(r, r->gitdir, REF_STORE_ALL_CAPS);
        r->refs_private = maybe_debug_wrap_ref_store(r->gitdir, r->refs_private);
        return r->refs_private;
  }
@@@ -1922,6 -1927,7 +1924,7 @@@ struct ref_store *get_submodule_ref_sto
        struct ref_store *refs;
        char *to_free = NULL;
        size_t len;
+       struct repository *subrepo;
  
        if (!submodule)
                return NULL;
        if (submodule_to_gitdir(&submodule_sb, submodule))
                goto done;
  
-       /* assume that add_submodule_odb() has been called */
-       refs = ref_store_init(submodule_sb.buf,
+       subrepo = xmalloc(sizeof(*subrepo));
+       /*
+        * NEEDSWORK: Make get_submodule_ref_store() work with arbitrary
+        * superprojects other than the_repository. This probably should be
+        * done by making it take a struct repository * parameter instead of a
+        * submodule path.
+        */
+       if (repo_submodule_init(subrepo, the_repository, submodule,
+                               null_oid())) {
+               free(subrepo);
+               goto done;
+       }
+       refs = ref_store_init(subrepo, submodule_sb.buf,
                              REF_STORE_READ | REF_STORE_ODB);
        register_ref_store_map(&submodule_ref_stores, "submodule",
                               refs, submodule);
@@@ -1974,10 -1991,12 +1988,12 @@@ struct ref_store *get_worktree_ref_stor
                return refs;
  
        if (wt->id)
-               refs = ref_store_init(git_common_path("worktrees/%s", wt->id),
+               refs = ref_store_init(the_repository,
+                                     git_common_path("worktrees/%s", wt->id),
                                      REF_STORE_ALL_CAPS);
        else
-               refs = ref_store_init(get_git_common_dir(),
+               refs = ref_store_init(the_repository,
+                                     get_git_common_dir(),
                                      REF_STORE_ALL_CAPS);
  
        if (refs)
@@@ -2365,19 -2384,19 +2381,19 @@@ int delete_reflog(const char *refname
  }
  
  int refs_reflog_expire(struct ref_store *refs,
 -                     const char *refname, const struct object_id *oid,
 +                     const char *refname,
                       unsigned int flags,
                       reflog_expiry_prepare_fn prepare_fn,
                       reflog_expiry_should_prune_fn should_prune_fn,
                       reflog_expiry_cleanup_fn cleanup_fn,
                       void *policy_cb_data)
  {
 -      return refs->be->reflog_expire(refs, refname, oid, flags,
 +      return refs->be->reflog_expire(refs, refname, flags,
                                       prepare_fn, should_prune_fn,
                                       cleanup_fn, policy_cb_data);
  }
  
 -int reflog_expire(const char *refname, const struct object_id *oid,
 +int reflog_expire(const char *refname,
                  unsigned int flags,
                  reflog_expiry_prepare_fn prepare_fn,
                  reflog_expiry_should_prune_fn should_prune_fn,
                  void *policy_cb_data)
  {
        return refs_reflog_expire(get_main_ref_store(the_repository),
 -                                refname, oid, flags,
 +                                refname, flags,
                                  prepare_fn, should_prune_fn,
                                  cleanup_fn, policy_cb_data);
  }
diff --combined refs/files-backend.c
index 6a6ead0b99bbd8b0326081516e3b6cac45366012,8ee6ac210365c2fe407c423f0caa1a4cb18e7adf..151b0056fe57d41acdacdcf4e8dfc16a43d82b96
@@@ -79,13 -79,15 +79,15 @@@ static void clear_loose_ref_cache(struc
   * Create a new submodule ref cache and add it to the internal
   * set of caches.
   */
- static struct ref_store *files_ref_store_create(const char *gitdir,
+ static struct ref_store *files_ref_store_create(struct repository *repo,
+                                               const char *gitdir,
                                                unsigned int flags)
  {
        struct files_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
        struct strbuf sb = STRBUF_INIT;
  
+       ref_store->repo = repo;
        ref_store->gitdir = xstrdup(gitdir);
        base_ref_store_init(ref_store, &refs_be_files);
        refs->store_flags = flags;
@@@ -93,7 -95,7 +95,7 @@@
        get_common_dir_noenv(&sb, gitdir);
        refs->gitcommondir = strbuf_detach(&sb, NULL);
        strbuf_addf(&sb, "%s/packed-refs", refs->gitcommondir);
-       refs->packed_ref_store = packed_ref_store_create(sb.buf, flags);
+       refs->packed_ref_store = packed_ref_store_create(repo, sb.buf, flags);
        strbuf_release(&sb);
  
        chdir_notify_reparent("files-backend $GIT_DIR", &refs->base.gitdir);
@@@ -227,7 -229,7 +229,7 @@@ static void add_per_worktree_entries_to
                pos = search_ref_dir(dir, prefix, prefix_len);
                if (pos >= 0)
                        continue;
 -              child_entry = create_dir_entry(dir->cache, prefix, prefix_len, 1);
 +              child_entry = create_dir_entry(dir->cache, prefix, prefix_len);
                add_entry_to_dir(dir, child_entry);
        }
  }
@@@ -278,7 -280,7 +280,7 @@@ static void loose_fill_ref_dir(struct r
                        strbuf_addch(&refname, '/');
                        add_entry_to_dir(dir,
                                         create_dir_entry(dir->cache, refname.buf,
 -                                                        refname.len, 1));
 +                                                        refname.len));
                } else {
                        if (!refs_resolve_ref_unsafe(&refs->base,
                                                     refname.buf,
@@@ -336,14 -338,14 +338,14 @@@ static struct ref_cache *get_loose_ref_
                 * lazily):
                 */
                add_entry_to_dir(get_ref_dir(refs->loose->root),
 -                               create_dir_entry(refs->loose, "refs/", 5, 1));
 +                               create_dir_entry(refs->loose, "refs/", 5));
        }
        return refs->loose;
  }
  
 -static int files_read_raw_ref(struct ref_store *ref_store,
 -                            const char *refname, struct object_id *oid,
 -                            struct strbuf *referent, unsigned int *type)
 +static int files_read_raw_ref(struct ref_store *ref_store, const char *refname,
 +                            struct object_id *oid, struct strbuf *referent,
 +                            unsigned int *type, int *failure_errno)
  {
        struct files_ref_store *refs =
                files_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
        struct stat st;
        int fd;
        int ret = -1;
 -      int save_errno;
        int remaining_retries = 3;
  
        *type = 0;
@@@ -458,9 -461,10 +460,9 @@@ stat_ref
        ret = parse_loose_ref_contents(buf, oid, referent, type);
  
  out:
 -      save_errno = errno;
 +      *failure_errno = errno;
        strbuf_release(&sb_path);
        strbuf_release(&sb_contents);
 -      errno = save_errno;
        return ret;
  }
  
@@@ -529,6 -533,7 +531,6 @@@ static void unlock_ref(struct ref_lock 
  static int lock_raw_ref(struct files_ref_store *refs,
                        const char *refname, int mustexist,
                        const struct string_list *extras,
 -                      const struct string_list *skip,
                        struct ref_lock **lock_p,
                        struct strbuf *referent,
                        unsigned int *type,
        struct strbuf ref_file = STRBUF_INIT;
        int attempts_remaining = 3;
        int ret = TRANSACTION_GENERIC_ERROR;
 +      int failure_errno;
  
        assert(err);
        files_assert_main_repository(refs, "lock_raw_ref");
@@@ -566,7 -570,7 +568,7 @@@ retry
                 * reason to expect this error to be transitory.
                 */
                if (refs_verify_refname_available(&refs->base, refname,
 -                                                extras, skip, err)) {
 +                                                extras, NULL, err)) {
                        if (mustexist) {
                                /*
                                 * To the user the relevant error is
        if (hold_lock_file_for_update_timeout(
                            &lock->lk, ref_file.buf, LOCK_NO_DEREF,
                            get_files_ref_lock_timeout_ms()) < 0) {
 -              if (errno == ENOENT && --attempts_remaining > 0) {
 +              int myerr = errno;
 +              errno = 0;
 +              if (myerr == ENOENT && --attempts_remaining > 0) {
                        /*
                         * Maybe somebody just deleted one of the
                         * directories leading to ref_file.  Try
                         */
                        goto retry;
                } else {
 -                      unable_to_lock_message(ref_file.buf, errno, err);
 +                      unable_to_lock_message(ref_file.buf, myerr, err);
                        goto error_return;
                }
        }
         * fear that its value will change.
         */
  
 -      if (files_read_raw_ref(&refs->base, refname,
 -                             &lock->old_oid, referent, type)) {
 -              if (errno == ENOENT) {
 +      if (files_read_raw_ref(&refs->base, refname, &lock->old_oid, referent,
 +                             type, &failure_errno)) {
 +              if (failure_errno == ENOENT) {
                        if (mustexist) {
                                /* Garden variety missing reference. */
                                strbuf_addf(err, "unable to resolve reference '%s'",
                                 *   reference named "refs/foo/bar/baz".
                                 */
                        }
 -              } else if (errno == EISDIR) {
 +              } else if (failure_errno == EISDIR) {
                        /*
                         * There is a directory in the way. It might have
                         * contained references that have been deleted. If
                                                          REMOVE_DIR_EMPTY_ONLY)) {
                                if (refs_verify_refname_available(
                                                    &refs->base, refname,
 -                                                  extras, skip, err)) {
 +                                                  extras, NULL, err)) {
                                        /*
                                         * The error message set by
                                         * verify_refname_available() is OK.
                                        goto error_return;
                                }
                        }
 -              } else if (errno == EINVAL && (*type & REF_ISBROKEN)) {
 +              } else if (failure_errno == EINVAL && (*type & REF_ISBROKEN)) {
                        strbuf_addf(err, "unable to resolve reference '%s': "
                                    "reference broken", refname);
                        goto error_return;
                } else {
                        strbuf_addf(err, "unable to resolve reference '%s': %s",
 -                                  refname, strerror(errno));
 +                                  refname, strerror(failure_errno));
                        goto error_return;
                }
  
                 */
                if (refs_verify_refname_available(
                                    refs->packed_ref_store, refname,
 -                                  extras, skip, err))
 +                                  extras, NULL, err))
                        goto error_return;
        }
  
@@@ -730,6 -732,7 +732,7 @@@ struct files_ref_iterator 
        struct ref_iterator base;
  
        struct ref_iterator *iter0;
+       struct repository *repo;
        unsigned int flags;
  };
  
@@@ -751,6 -754,7 +754,7 @@@ static int files_ref_iterator_advance(s
  
                if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
                    !ref_resolves_to_object(iter->iter0->refname,
+                                           iter->repo,
                                            iter->iter0->oid,
                                            iter->iter0->flags))
                        continue;
@@@ -829,7 -833,7 +833,7 @@@ static struct ref_iterator *files_ref_i
         */
  
        loose_iter = cache_ref_iterator_begin(get_loose_ref_cache(refs),
-                                             prefix, 1);
+                                             prefix, ref_store->repo, 1);
  
        /*
         * The packed-refs file might contain broken references, for
        base_ref_iterator_init(ref_iterator, &files_ref_iterator_vtable,
                               overlay_iter->ordered);
        iter->iter0 = overlay_iter;
+       iter->repo = ref_store->repo;
        iter->flags = flags;
  
        return ref_iterator;
  }
  
  /*
 - * Verify that the reference locked by lock has the value old_oid
 - * (unless it is NULL).  Fail if the reference doesn't exist and
 - * mustexist is set. Return 0 on success. On error, write an error
 - * message to err, set errno, and return a negative value.
 + * 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.
   */
 -static int verify_lock(struct ref_store *ref_store, struct ref_lock *lock,
 -                     const struct object_id *old_oid, int mustexist,
 -                     struct strbuf *err)
 +static int raceproof_create_file(const char *path, create_file_fn fn, void *cb)
  {
 -      assert(err);
 +      /*
 +       * The number of times we will try to remove empty directories
 +       * in the way of path. This is only 1 because if another
 +       * process is racily creating directories that conflict with
 +       * us, we don't want to fight against them.
 +       */
 +      int remove_directories_remaining = 1;
  
 -      if (refs_read_ref_full(ref_store, lock->ref_name,
 -                             mustexist ? RESOLVE_REF_READING : 0,
 -                             &lock->old_oid, NULL)) {
 -              if (old_oid) {
 -                      int save_errno = errno;
 -                      strbuf_addf(err, "can't verify ref '%s'", lock->ref_name);
 -                      errno = save_errno;
 -                      return -1;
 -              } else {
 -                      oidclr(&lock->old_oid);
 -                      return 0;
 -              }
 -      }
 -      if (old_oid && !oideq(&lock->old_oid, old_oid)) {
 -              strbuf_addf(err, "ref '%s' is at %s but expected %s",
 -                          lock->ref_name,
 -                          oid_to_hex(&lock->old_oid),
 -                          oid_to_hex(old_oid));
 -              errno = EBUSY;
 -              return -1;
 +      /*
 +       * The number of times that we will try to create the
 +       * directories containing path. We are willing to attempt this
 +       * more than once, because another process could be trying to
 +       * clean up empty directories at the same time as we are
 +       * trying to create them.
 +       */
 +      int create_directories_remaining = 3;
 +
 +      /* A scratch copy of path, filled lazily if we need it: */
 +      struct strbuf path_copy = STRBUF_INIT;
 +
 +      int ret, save_errno;
 +
 +      /* Sanity check: */
 +      assert(*path);
 +
 +retry_fn:
 +      ret = fn(path, cb);
 +      save_errno = errno;
 +      if (!ret)
 +              goto out;
 +
 +      if (errno == EISDIR && remove_directories_remaining-- > 0) {
 +              /*
 +               * A directory is in the way. Maybe it is empty; try
 +               * to remove it:
 +               */
 +              if (!path_copy.len)
 +                      strbuf_addstr(&path_copy, path);
 +
 +              if (!remove_dir_recursively(&path_copy, REMOVE_DIR_EMPTY_ONLY))
 +                      goto retry_fn;
 +      } else if (errno == ENOENT && create_directories_remaining-- > 0) {
 +              /*
 +               * Maybe the containing directory didn't exist, or
 +               * maybe it was just deleted by a process that is
 +               * racing with us to clean up empty directories. Try
 +               * to create it:
 +               */
 +              enum scld_error scld_result;
 +
 +              if (!path_copy.len)
 +                      strbuf_addstr(&path_copy, path);
 +
 +              do {
 +                      scld_result = safe_create_leading_directories(path_copy.buf);
 +                      if (scld_result == SCLD_OK)
 +                              goto retry_fn;
 +              } while (scld_result == SCLD_VANISHED && create_directories_remaining-- > 0);
        }
 -      return 0;
 +
 +out:
 +      strbuf_release(&path_copy);
 +      errno = save_errno;
 +      return ret;
  }
  
  static int remove_empty_directories(struct strbuf *path)
@@@ -988,27 -920,64 +993,27 @@@ static int create_reflock(const char *p
  
  /*
   * Locks a ref returning the lock on success and NULL on failure.
 - * On failure errno is set to something meaningful.
   */
  static struct ref_lock *lock_ref_oid_basic(struct files_ref_store *refs,
 -                                         const char *refname,
 -                                         const struct object_id *old_oid,
 -                                         const struct string_list *extras,
 -                                         const struct string_list *skip,
 -                                         unsigned int flags, int *type,
 +                                         const char *refname, int *type,
                                           struct strbuf *err)
  {
        struct strbuf ref_file = STRBUF_INIT;
        struct ref_lock *lock;
 -      int last_errno = 0;
 -      int mustexist = (old_oid && !is_null_oid(old_oid));
 -      int resolve_flags = RESOLVE_REF_NO_RECURSE;
 -      int resolved;
  
        files_assert_main_repository(refs, "lock_ref_oid_basic");
        assert(err);
  
        CALLOC_ARRAY(lock, 1);
  
 -      if (mustexist)
 -              resolve_flags |= RESOLVE_REF_READING;
 -      if (flags & REF_DELETING)
 -              resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
 -
        files_ref_path(refs, &ref_file, refname);
 -      resolved = !!refs_resolve_ref_unsafe(&refs->base,
 -                                           refname, resolve_flags,
 -                                           &lock->old_oid, type);
 -      if (!resolved && errno == EISDIR) {
 -              /*
 -               * we are trying to lock foo but we used to
 -               * have foo/bar which now does not exist;
 -               * it is normal for the empty directory 'foo'
 -               * to remain.
 -               */
 -              if (remove_empty_directories(&ref_file)) {
 -                      last_errno = errno;
 -                      if (!refs_verify_refname_available(
 -                                          &refs->base,
 -                                          refname, extras, skip, err))
 -                              strbuf_addf(err, "there are still refs under '%s'",
 -                                          refname);
 -                      goto error_return;
 -              }
 -              resolved = !!refs_resolve_ref_unsafe(&refs->base,
 -                                                   refname, resolve_flags,
 -                                                   &lock->old_oid, type);
 -      }
 -      if (!resolved) {
 -              last_errno = errno;
 -              if (last_errno != ENOTDIR ||
 -                  !refs_verify_refname_available(&refs->base, refname,
 -                                                 extras, skip, err))
 +      if (!refs_resolve_ref_unsafe(&refs->base, refname,
 +                                   RESOLVE_REF_NO_RECURSE,
 +                                   &lock->old_oid, type)) {
 +              if (!refs_verify_refname_available(&refs->base, refname,
 +                                                 NULL, NULL, err))
                        strbuf_addf(err, "unable to resolve reference '%s': %s",
 -                                  refname, strerror(last_errno));
 +                                  refname, strerror(errno));
  
                goto error_return;
        }
         */
        if (is_null_oid(&lock->old_oid) &&
            refs_verify_refname_available(refs->packed_ref_store, refname,
 -                                        extras, skip, err)) {
 -              last_errno = ENOTDIR;
 +                                        NULL, NULL, err))
                goto error_return;
 -      }
  
        lock->ref_name = xstrdup(refname);
  
        if (raceproof_create_file(ref_file.buf, create_reflock, &lock->lk)) {
 -              last_errno = errno;
                unable_to_lock_message(ref_file.buf, errno, err);
                goto error_return;
        }
  
 -      if (verify_lock(&refs->base, lock, old_oid, mustexist, err)) {
 -              last_errno = errno;
 -              goto error_return;
 -      }
 +      if (refs_read_ref_full(&refs->base, lock->ref_name,
 +                             0,
 +                             &lock->old_oid, NULL))
 +              oidclr(&lock->old_oid);
        goto out;
  
   error_return:
  
   out:
        strbuf_release(&ref_file);
 -      errno = last_errno;
        return lock;
  }
  
@@@ -1169,7 -1142,7 +1174,7 @@@ static int should_pack_ref(const char *
                return 0;
  
        /* Do not pack broken refs: */
-       if (!ref_resolves_to_object(refname, oid, ref_flags))
+       if (!ref_resolves_to_object(refname, the_repository, oid, ref_flags))
                return 0;
  
        return 1;
@@@ -1192,7 -1165,8 +1197,8 @@@ static int files_pack_refs(struct ref_s
  
        packed_refs_lock(refs->packed_ref_store, LOCK_DIE_ON_ERROR, &err);
  
-       iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL, 0);
+       iter = cache_ref_iterator_begin(get_loose_ref_cache(refs), NULL,
+                                       the_repository, 0);
        while ((ok = ref_iterator_advance(iter)) == ITER_OK) {
                /*
                 * If the loose reference can be packed, add an entry
@@@ -1452,7 -1426,8 +1458,7 @@@ static int files_copy_or_rename_ref(str
  
        logmoved = log;
  
 -      lock = lock_ref_oid_basic(refs, newrefname, NULL, NULL, NULL,
 -                                REF_NO_DEREF, NULL, &err);
 +      lock = lock_ref_oid_basic(refs, newrefname, NULL, &err);
        if (!lock) {
                if (copy)
                        error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
        goto out;
  
   rollback:
 -      lock = lock_ref_oid_basic(refs, oldrefname, NULL, NULL, NULL,
 -                                REF_NO_DEREF, NULL, &err);
 +      lock = lock_ref_oid_basic(refs, oldrefname, NULL, &err);
        if (!lock) {
                error("unable to lock %s for rollback: %s", oldrefname, err.buf);
                strbuf_release(&err);
@@@ -1881,7 -1857,9 +1887,7 @@@ static int files_create_symref(struct r
        struct ref_lock *lock;
        int ret;
  
 -      lock = lock_ref_oid_basic(refs, refname, NULL,
 -                                NULL, NULL, REF_NO_DEREF, NULL,
 -                                &err);
 +      lock = lock_ref_oid_basic(refs, refname, NULL, &err);
        if (!lock) {
                error("%s", err.buf);
                strbuf_release(&err);
@@@ -2449,7 -2427,7 +2455,7 @@@ static int lock_ref_for_update(struct f
        }
  
        ret = lock_raw_ref(refs, update->refname, mustexist,
 -                         affected_refnames, NULL,
 +                         affected_refnames,
                           &lock, &referent,
                           &update->type, err);
        if (ret) {
@@@ -3070,7 -3048,7 +3076,7 @@@ static int expire_reflog_ent(struct obj
  }
  
  static int files_reflog_expire(struct ref_store *ref_store,
 -                             const char *refname, const struct object_id *oid,
 +                             const char *refname,
                               unsigned int flags,
                               reflog_expiry_prepare_fn prepare_fn,
                               reflog_expiry_should_prune_fn should_prune_fn,
        int status = 0;
        int type;
        struct strbuf err = STRBUF_INIT;
 +      const struct object_id *oid;
  
        memset(&cb, 0, sizeof(cb));
        cb.flags = flags;
         * reference itself, plus we might need to update the
         * reference if --updateref was specified:
         */
 -      lock = lock_ref_oid_basic(refs, refname, oid,
 -                                NULL, NULL, REF_NO_DEREF,
 -                                &type, &err);
 +      lock = lock_ref_oid_basic(refs, refname, &type, &err);
        if (!lock) {
                error("cannot lock ref '%s': %s", refname, err.buf);
                strbuf_release(&err);
                return -1;
        }
 +      oid = &lock->old_oid;
 +
 +      /*
 +       * When refs are deleted, their reflog is deleted before the
 +       * ref itself is deleted. This is because there is no separate
 +       * lock for reflog; instead we take a lock on the ref with
 +       * lock_ref_oid_basic().
 +       *
 +       * If a race happens and the reflog doesn't exist after we've
 +       * acquired the lock that's OK. We've got nothing more to do;
 +       * We were asked to delete the reflog, but someone else
 +       * deleted it! The caller doesn't care that we deleted it,
 +       * just that it is deleted. So we can return successfully.
 +       */
        if (!refs_reflog_exists(ref_store, refname)) {
                unlock_ref(lock);
                return 0;
diff --combined refs/packed-backend.c
index 47247a149178f364423f84572c5b2912772d10be,216121871962e4eeccabc6030cc22f2fdb65c27f..1c5211b03e48cf23fd3a4d02e8aa6278b24ce26f
@@@ -193,13 -193,15 +193,15 @@@ static int release_snapshot(struct snap
        }
  }
  
- struct ref_store *packed_ref_store_create(const char *path,
+ struct ref_store *packed_ref_store_create(struct repository *repo,
+                                         const char *path,
                                          unsigned int store_flags)
  {
        struct packed_ref_store *refs = xcalloc(1, sizeof(*refs));
        struct ref_store *ref_store = (struct ref_store *)refs;
  
        base_ref_store_init(ref_store, &refs_be_packed);
+       ref_store->repo = repo;
        ref_store->gitdir = xstrdup(path);
        refs->store_flags = store_flags;
  
@@@ -724,9 -726,9 +726,9 @@@ static struct snapshot *get_snapshot(st
        return refs->snapshot;
  }
  
 -static int packed_read_raw_ref(struct ref_store *ref_store,
 -                             const char *refname, struct object_id *oid,
 -                             struct strbuf *referent, unsigned int *type)
 +static int packed_read_raw_ref(struct ref_store *ref_store, const char *refname,
 +                             struct object_id *oid, struct strbuf *referent,
 +                             unsigned int *type, int *failure_errno)
  {
        struct packed_ref_store *refs =
                packed_downcast(ref_store, REF_STORE_READ, "read_raw_ref");
  
        if (!rec) {
                /* refname is not a packed reference. */
 -              errno = ENOENT;
 +              *failure_errno = ENOENT;
                return -1;
        }
  
@@@ -776,6 -778,7 +778,7 @@@ struct packed_ref_iterator 
        struct object_id oid, peeled;
        struct strbuf refname_buf;
  
+       struct repository *repo;
        unsigned int flags;
  };
  
@@@ -864,8 -867,8 +867,8 @@@ static int packed_ref_iterator_advance(
                        continue;
  
                if (!(iter->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
-                   !ref_resolves_to_object(iter->base.refname, &iter->oid,
-                                           iter->flags))
+                   !ref_resolves_to_object(iter->base.refname, iter->repo,
+                                           &iter->oid, iter->flags))
                        continue;
  
                return ITER_OK;
@@@ -883,6 -886,9 +886,9 @@@ static int packed_ref_iterator_peel(str
        struct packed_ref_iterator *iter =
                (struct packed_ref_iterator *)ref_iterator;
  
+       if (iter->repo != the_repository)
+               BUG("peeling for non-the_repository is not supported");
        if ((iter->base.flags & REF_KNOWS_PEELED)) {
                oidcpy(peeled, &iter->peeled);
                return is_null_oid(&iter->peeled) ? -1 : 0;
@@@ -954,6 -960,7 +960,7 @@@ static struct ref_iterator *packed_ref_
  
        iter->base.oid = &iter->oid;
  
+       iter->repo = ref_store->repo;
        iter->flags = flags;
  
        if (prefix && *prefix)
@@@ -1600,7 -1607,6 +1607,7 @@@ static int packed_for_each_reflog_ent(s
                                      const char *refname,
                                      each_reflog_ent_fn fn, void *cb_data)
  {
 +      BUG("packed reference store does not support reflogs");
        return 0;
  }
  
@@@ -1609,14 -1615,12 +1616,14 @@@ static int packed_for_each_reflog_ent_r
                                              each_reflog_ent_fn fn,
                                              void *cb_data)
  {
 +      BUG("packed reference store does not support reflogs");
        return 0;
  }
  
  static int packed_reflog_exists(struct ref_store *ref_store,
                               const char *refname)
  {
 +      BUG("packed reference store does not support reflogs");
        return 0;
  }
  
@@@ -1630,19 -1634,17 +1637,19 @@@ static int packed_create_reflog(struct 
  static int packed_delete_reflog(struct ref_store *ref_store,
                               const char *refname)
  {
 +      BUG("packed reference store does not support reflogs");
        return 0;
  }
  
  static int packed_reflog_expire(struct ref_store *ref_store,
 -                              const char *refname, const struct object_id *oid,
 +                              const char *refname,
                                unsigned int flags,
                                reflog_expiry_prepare_fn prepare_fn,
                                reflog_expiry_should_prune_fn should_prune_fn,
                                reflog_expiry_cleanup_fn cleanup_fn,
                                void *policy_cb_data)
  {
 +      BUG("packed reference store does not support reflogs");
        return 0;
  }
  
diff --combined refs/ref-cache.c
index a5ad8a39fb405430f7d5f9ffd18af6472c3b215f,97a6ac349ee314178ee690b0b742d6cadbb78141..be4aa5e09818fad082a7c15303746edd8a5ddcaf
@@@ -49,7 -49,7 +49,7 @@@ struct ref_cache *create_ref_cache(stru
  
        ret->ref_store = refs;
        ret->fill_ref_dir = fill_ref_dir;
 -      ret->root = create_dir_entry(ret, "", 0, 1);
 +      ret->root = create_dir_entry(ret, "", 0);
        return ret;
  }
  
@@@ -86,13 -86,14 +86,13 @@@ static void clear_ref_dir(struct ref_di
  }
  
  struct ref_entry *create_dir_entry(struct ref_cache *cache,
 -                                 const char *dirname, size_t len,
 -                                 int incomplete)
 +                                 const char *dirname, size_t len)
  {
        struct ref_entry *direntry;
  
        FLEX_ALLOC_MEM(direntry, name, dirname, len);
        direntry->u.subdir.cache = cache;
 -      direntry->flag = REF_DIR | (incomplete ? REF_INCOMPLETE : 0);
 +      direntry->flag = REF_DIR | REF_INCOMPLETE;
        return direntry;
  }
  
@@@ -143,19 -144,30 +143,19 @@@ int search_ref_dir(struct ref_dir *dir
  /*
   * Search for a directory entry directly within dir (without
   * recursing).  Sort dir if necessary.  subdirname must be a directory
 - * name (i.e., end in '/').  If mkdir is set, then create the
 - * directory if it is missing; otherwise, return NULL if the desired
 + * name (i.e., end in '/'). Returns NULL if the desired
   * directory cannot be found.  dir must already be complete.
   */
  static struct ref_dir *search_for_subdir(struct ref_dir *dir,
 -                                       const char *subdirname, size_t len,
 -                                       int mkdir)
 +                                       const char *subdirname, size_t len)
  {
        int entry_index = search_ref_dir(dir, subdirname, len);
        struct ref_entry *entry;
 -      if (entry_index == -1) {
 -              if (!mkdir)
 -                      return NULL;
 -              /*
 -               * Since dir is complete, the absence of a subdir
 -               * means that the subdir really doesn't exist;
 -               * therefore, create an empty record for it but mark
 -               * the record complete.
 -               */
 -              entry = create_dir_entry(dir->cache, subdirname, len, 0);
 -              add_entry_to_dir(dir, entry);
 -      } else {
 -              entry = dir->entries[entry_index];
 -      }
 +
 +      if (entry_index == -1)
 +              return NULL;
 +
 +      entry = dir->entries[entry_index];
        return get_ref_dir(entry);
  }
  
   * tree that should hold refname. If refname is a directory name
   * (i.e., it ends in '/'), then return that ref_dir itself. dir must
   * represent the top-level directory and must already be complete.
 - * Sort ref_dirs and recurse into subdirectories as necessary. If
 - * mkdir is set, then create any missing directories; otherwise,
 + * Sort ref_dirs and recurse into subdirectories as necessary. Will
   * return NULL if the desired directory cannot be found.
   */
  static struct ref_dir *find_containing_dir(struct ref_dir *dir,
 -                                         const char *refname, int mkdir)
 +                                         const char *refname)
  {
        const char *slash;
        for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
                size_t dirnamelen = slash - refname + 1;
                struct ref_dir *subdir;
 -              subdir = search_for_subdir(dir, refname, dirnamelen, mkdir);
 +              subdir = search_for_subdir(dir, refname, dirnamelen);
                if (!subdir) {
                        dir = NULL;
                        break;
@@@ -189,7 -202,7 +189,7 @@@ struct ref_entry *find_ref_entry(struc
  {
        int entry_index;
        struct ref_entry *entry;
 -      dir = find_containing_dir(dir, refname, 0);
 +      dir = find_containing_dir(dir, refname);
        if (!dir)
                return NULL;
        entry_index = search_ref_dir(dir, refname, strlen(refname));
        return (entry->flag & REF_DIR) ? NULL : entry;
  }
  
 -int remove_entry_from_dir(struct ref_dir *dir, const char *refname)
 -{
 -      int refname_len = strlen(refname);
 -      int entry_index;
 -      struct ref_entry *entry;
 -      int is_dir = refname[refname_len - 1] == '/';
 -      if (is_dir) {
 -              /*
 -               * refname represents a reference directory.  Remove
 -               * the trailing slash; otherwise we will get the
 -               * directory *representing* refname rather than the
 -               * one *containing* it.
 -               */
 -              char *dirname = xmemdupz(refname, refname_len - 1);
 -              dir = find_containing_dir(dir, dirname, 0);
 -              free(dirname);
 -      } else {
 -              dir = find_containing_dir(dir, refname, 0);
 -      }
 -      if (!dir)
 -              return -1;
 -      entry_index = search_ref_dir(dir, refname, refname_len);
 -      if (entry_index == -1)
 -              return -1;
 -      entry = dir->entries[entry_index];
 -
 -      MOVE_ARRAY(&dir->entries[entry_index],
 -                 &dir->entries[entry_index + 1], dir->nr - entry_index - 1);
 -      dir->nr--;
 -      if (dir->sorted > entry_index)
 -              dir->sorted--;
 -      free_ref_entry(entry);
 -      return dir->nr;
 -}
 -
 -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref)
 -{
 -      dir = find_containing_dir(dir, ref->name, 1);
 -      if (!dir)
 -              return -1;
 -      add_entry_to_dir(dir, ref);
 -      return 0;
 -}
 -
  /*
   * Emit a warning and return true iff ref1 and ref2 have the same name
   * and the same oid. Die if they have the same name but different
@@@ -378,6 -435,8 +378,8 @@@ struct cache_ref_iterator 
         * on from there.)
         */
        struct cache_ref_iterator_level *levels;
+       struct repository *repo;
  };
  
  static int cache_ref_iterator_advance(struct ref_iterator *ref_iterator)
  static int cache_ref_iterator_peel(struct ref_iterator *ref_iterator,
                                   struct object_id *peeled)
  {
+       struct cache_ref_iterator *iter =
+               (struct cache_ref_iterator *)ref_iterator;
+       if (iter->repo != the_repository)
+               BUG("peeling for non-the_repository is not supported");
        return peel_object(ref_iterator->oid, peeled) ? -1 : 0;
  }
  
@@@ -456,6 -520,7 +463,7 @@@ static struct ref_iterator_vtable cache
  
  struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
                                              const char *prefix,
+                                             struct repository *repo,
                                              int prime_dir)
  {
        struct ref_dir *dir;
  
        dir = get_ref_dir(cache->root);
        if (prefix && *prefix)
 -              dir = find_containing_dir(dir, prefix, 0);
 +              dir = find_containing_dir(dir, prefix);
        if (!dir)
                /* There's nothing to iterate over. */
                return empty_ref_iterator_begin();
                level->prefix_state = PREFIX_CONTAINS_DIR;
        }
  
+       iter->repo = repo;
        return ref_iterator;
  }
diff --combined refs/ref-cache.h
index 5c042ae718cb370ddba39e27f9f972636384bace,7877bf86edb26ab1218372044e7bf4876542d183..850d9d3744e94270fb5cd445b4f6b7b18aac8128
@@@ -169,7 -169,8 +169,7 @@@ struct ref_dir *get_ref_dir(struct ref_
   * "refs/heads/") or "" for the top-level directory.
   */
  struct ref_entry *create_dir_entry(struct ref_cache *cache,
 -                                 const char *dirname, size_t len,
 -                                 int incomplete);
 +                                 const char *dirname, size_t len);
  
  struct ref_entry *create_ref_entry(const char *refname,
                                   const struct object_id *oid, int flag);
@@@ -198,6 -199,29 +198,6 @@@ void free_ref_cache(struct ref_cache *c
   */
  void add_entry_to_dir(struct ref_dir *dir, struct ref_entry *entry);
  
 -/*
 - * Remove the entry with the given name from dir, recursing into
 - * subdirectories as necessary.  If refname is the name of a directory
 - * (i.e., ends with '/'), then remove the directory and its contents.
 - * If the removal was successful, return the number of entries
 - * remaining in the directory entry that contained the deleted entry.
 - * If the name was not found, return -1.  Please note that this
 - * function only deletes the entry from the cache; it does not delete
 - * it from the filesystem or ensure that other cache entries (which
 - * might be symbolic references to the removed entry) are updated.
 - * Nor does it remove any containing dir entries that might be made
 - * empty by the removal.  dir must represent the top-level directory
 - * and must already be complete.
 - */
 -int remove_entry_from_dir(struct ref_dir *dir, const char *refname);
 -
 -/*
 - * Add a ref_entry to the ref_dir (unsorted), recursing into
 - * subdirectories as necessary.  dir must represent the top-level
 - * directory.  Return 0 on success.
 - */
 -int add_ref_entry(struct ref_dir *dir, struct ref_entry *ref);
 -
  /*
   * Find the value entry with the given name in dir, sorting ref_dirs
   * and recursing into subdirectories as necessary.  If the name is not
@@@ -214,6 -238,7 +214,7 @@@ struct ref_entry *find_ref_entry(struc
   */
  struct ref_iterator *cache_ref_iterator_begin(struct ref_cache *cache,
                                              const char *prefix,
+                                             struct repository *repo,
                                              int prime_dir);
  
  #endif /* REFS_REF_CACHE_H */
diff --combined refs/refs-internal.h
index 72746407fc3dd4df57d17894967cece6804bf0d5,500d77864d896741890c92d030e058892db5e057..12224742ede8f01d4f8d88ceaaa2e326413bf674
@@@ -66,6 -66,7 +66,7 @@@ int refname_is_safe(const char *refname
   * referred-to object does not exist, emit a warning and return false.
   */
  int ref_resolves_to_object(const char *refname,
+                          struct repository *repo,
                           const struct object_id *oid,
                           unsigned int flags);
  
@@@ -539,7 -540,8 +540,8 @@@ struct ref_store
   * should call base_ref_store_init() to initialize the shared part of
   * the ref_store and to record the ref_store for later lookup.
   */
- typedef struct ref_store *ref_store_init_fn(const char *gitdir,
+ typedef struct ref_store *ref_store_init_fn(struct repository *repo,
+                                           const char *gitdir,
                                            unsigned int flags);
  
  typedef int ref_init_db_fn(struct ref_store *refs, struct strbuf *err);
@@@ -607,7 -609,7 +609,7 @@@ typedef int create_reflog_fn(struct ref
                             int force_create, struct strbuf *err);
  typedef int delete_reflog_fn(struct ref_store *ref_store, const char *refname);
  typedef int reflog_expire_fn(struct ref_store *ref_store,
 -                           const char *refname, const struct object_id *oid,
 +                           const char *refname,
                             unsigned int flags,
                             reflog_expiry_prepare_fn prepare_fn,
                             reflog_expiry_should_prune_fn should_prune_fn,
   * properly-formatted or even safe reference name. NEITHER INPUT NOR
   * OUTPUT REFERENCE NAMES ARE VALIDATED WITHIN THIS FUNCTION.
   *
 - * Return 0 on success. If the ref doesn't exist, set errno to ENOENT
 - * and return -1. If the ref exists but is neither a symbolic ref nor
 - * an object ID, it is broken; set REF_ISBROKEN in type, set errno to
 - * EINVAL, and return -1. If there is another error reading the ref,
 - * set errno appropriately and return -1.
 + * Return 0 on success, or -1 on failure. If the ref exists but is neither a
 + * symbolic ref nor an object ID, it is broken. In this case set REF_ISBROKEN in
 + * type, and return -1 (failure_errno should not be ENOENT)
 + *
 + * failure_errno provides errno codes that are interpreted beyond error
 + * reporting. The following error codes have special meaning:
 + *    * ENOENT: the ref doesn't exist
 + *    * EISDIR: ref name is a directory
 + *    * ENOTDIR: ref prefix is not a directory
   *
   * Backend-specific flags might be set in type as well, regardless of
   * outcome.
   * - in all other cases, referent will be untouched, and therefore
   *   refname will still be valid and unchanged.
   */
 -typedef int read_raw_ref_fn(struct ref_store *ref_store,
 -                          const char *refname, struct object_id *oid,
 -                          struct strbuf *referent, unsigned int *type);
 +typedef int read_raw_ref_fn(struct ref_store *ref_store, const char *refname,
 +                          struct object_id *oid, struct strbuf *referent,
 +                          unsigned int *type, int *failure_errno);
  
  struct ref_storage_be {
        struct ref_storage_be *next;
@@@ -701,7 -699,12 +703,12 @@@ struct ref_store 
        /* The backend describing this ref_store's storage scheme: */
        const struct ref_storage_be *be;
  
-       /* The gitdir that this ref_store applies to: */
+       struct repository *repo;
+       /*
+        * The gitdir that this ref_store applies to. Note that this is not
+        * necessarily repo->gitdir if the repo has multiple worktrees.
+        */
        char *gitdir;
  };
  
diff --combined strbuf.h
index 3b36bbc49f08b2fa38fb97994053ed74b44ef766,2d9e01c16f72083bcc7072aab440eae385a42083..96512f85b316e7f3b56df94cd1e7912dbb6020f0
+++ b/strbuf.h
@@@ -70,7 -70,7 +70,7 @@@ struct strbuf 
  };
  
  extern char strbuf_slopbuf[];
 -#define STRBUF_INIT  { .alloc = 0, .len = 0, .buf = strbuf_slopbuf }
 +#define STRBUF_INIT  { .buf = strbuf_slopbuf }
  
  /*
   * Predeclare this here, since cache.h includes this file before it defines the
@@@ -634,8 -634,10 +634,10 @@@ void strbuf_list_free(struct strbuf **l
   * Add the abbreviation, as generated by find_unique_abbrev, of `sha1` to
   * the strbuf `sb`.
   */
- void strbuf_add_unique_abbrev(struct strbuf *sb,
-                             const struct object_id *oid,
+ struct repository;
+ void strbuf_repo_add_unique_abbrev(struct strbuf *sb, struct repository *repo,
+                                  const struct object_id *oid, int abbrev_len);
+ void strbuf_add_unique_abbrev(struct strbuf *sb, const struct object_id *oid,
                              int abbrev_len);
  
  /**
diff --combined submodule.c
index f3c99634a974a5e3f2f4b68836782ad74a2fbd1d,61575e5a5607b02fa0e940f676ae7fea14210e0a..c689070524171b8e6cfae3349cb317c18f693664
@@@ -201,6 -201,8 +201,8 @@@ int register_all_submodule_odb_as_alter
                add_to_alternates_memory(added_submodule_odb_paths.items[i].string);
        if (ret) {
                string_list_clear(&added_submodule_odb_paths, 0);
+               trace2_data_intmax("submodule", the_repository,
+                                  "register_all_submodule_odb_as_alternates/registered", ret);
                if (git_env_bool("GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB", 0))
                        BUG("register_all_submodule_odb_as_alternates() called");
        }
@@@ -928,23 -930,33 +930,33 @@@ struct has_commit_data 
  static int check_has_commit(const struct object_id *oid, void *data)
  {
        struct has_commit_data *cb = data;
+       struct repository subrepo;
+       enum object_type type;
  
-       enum object_type type = oid_object_info(cb->repo, oid, NULL);
+       if (repo_submodule_init(&subrepo, cb->repo, cb->path, null_oid())) {
+               cb->result = 0;
+               goto cleanup;
+       }
+       type = oid_object_info(&subrepo, oid, NULL);
  
        switch (type) {
        case OBJ_COMMIT:
-               return 0;
+               goto cleanup;
        case OBJ_BAD:
                /*
                 * Object is missing or invalid. If invalid, an error message
                 * has already been printed.
                 */
                cb->result = 0;
-               return 0;
+               goto cleanup;
        default:
                die(_("submodule entry '%s' (%s) is a %s, not a commit"),
                    cb->path, oid_to_hex(oid), type_name(type));
        }
+ cleanup:
+       repo_clear(&subrepo);
+       return 0;
  }
  
  static int submodule_has_commits(struct repository *r,
@@@ -1318,11 -1330,9 +1330,11 @@@ struct submodule_parallel_fetch 
  
        struct strbuf submodules_with_errors;
  };
 -#define SPF_INIT {0, STRVEC_INIT, NULL, NULL, 0, 0, 0, 0, \
 -                STRING_LIST_INIT_DUP, \
 -                NULL, 0, 0, STRBUF_INIT}
 +#define SPF_INIT { \
 +      .args = STRVEC_INIT, \
 +      .changed_submodule_names = STRING_LIST_INIT_DUP, \
 +      .submodules_with_errors = STRBUF_INIT, \
 +}
  
  static int get_fetch_recurse_config(const struct submodule *submodule,
                                    struct submodule_parallel_fetch *spf)
@@@ -1896,7 -1906,6 +1908,7 @@@ static void submodule_reset_index(cons
  
        strvec_pushf(&cp.args, "--super-prefix=%s%s/",
                     get_super_prefix_or_empty(), path);
 +      /* TODO: determine if this might overwright untracked files */
        strvec_pushl(&cp.args, "read-tree", "-u", "--reset", NULL);
  
        strvec_push(&cp.args, empty_tree_oid_hex());
diff --combined t/README
index b92155a822ea962cc4d7c899e89681884f25914d,b677caaf686ff4e5e9e3cc51053b22d5a93bad6d..29f72354bf17e959abcc2e14243816b1a0262e52
+++ b/t/README
@@@ -366,13 -366,6 +366,13 @@@ excluded as so much relies on it, but t
  GIT_TEST_SPLIT_INDEX=<boolean> forces split-index mode on the whole
  test suite. Accept any boolean values that are accepted by git-config.
  
 +GIT_TEST_PASSING_SANITIZE_LEAK=<boolean> when compiled with
 +SANITIZE=leak will run only those tests that have whitelisted
 +themselves as passing with no memory leaks. Tests can be whitelisted
 +by setting "TEST_PASSES_SANITIZE_LEAK=true" before sourcing
 +"test-lib.sh" itself at the top of the test script. This test mode is
 +used by the "linux-leaks" CI target.
 +
  GIT_TEST_PROTOCOL_VERSION=<n>, when set, makes 'protocol.version'
  default to n.
  
@@@ -463,11 -456,8 +463,8 @@@ GIT_TEST_FATAL_REGISTER_SUBMODULE_ODB=<
  registering submodule ODBs as alternates a fatal action. Support for
  this environment variable can be removed once the migration to
  explicitly providing repositories when accessing submodule objects is
- complete (in which case we might want to replace this with a trace2
- call so that users can make it visible if accessing submodule objects
- without an explicit repository still happens) or needs to be abandoned
- for whatever reason (in which case the migrated codepaths still retain
- their performance benefits).
+ complete or needs to be abandoned for whatever reason (in which case the
+ migrated codepaths still retain their performance benefits).
  
  Naming Tests
  ------------