Merge branch 'km/delete-ref-reflog-message'
authorJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:18 +0000 (13:57 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 27 Feb 2017 21:57:18 +0000 (13:57 -0800)
"git update-ref -d" and other operations to delete references did
not leave any entry in HEAD's reflog when the reference being
deleted was the current branch. This is not a problem in practice
because you do not want to delete the branch you are currently on,
but caused renaming of the current branch to something else not to
be logged in a useful way.

* km/delete-ref-reflog-message:
branch: record creation of renamed branch in HEAD's log
rename_ref: replace empty message in HEAD's log
update-ref: pass reflog message to delete_ref()
delete_ref: accept a reflog message argument

1  2 
builtin/branch.c
builtin/tag.c
refs.c
refs/files-backend.c
t/t1400-update-ref.sh
transport.c
diff --combined builtin/branch.c
index cbaa6d03c047975ac88ccb4833a28ea5982b0b4f,e1f97dcfc4ea9ebe0a7e1b47a51e056930daa032..94f7de7fa5da4a27dab99c582c5dfbbf25169048
@@@ -28,7 -28,6 +28,7 @@@ static const char * const builtin_branc
        N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."),
        N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"),
        N_("git branch [<options>] [-r | -a] [--points-at]"),
 +      N_("git branch [<options>] [-r | -a] [--format]"),
        NULL
  };
  
@@@ -38,11 -37,11 +38,11 @@@ static unsigned char head_sha1[20]
  static int branch_use_color = -1;
  static char branch_colors[][COLOR_MAXLEN] = {
        GIT_COLOR_RESET,
 -      GIT_COLOR_NORMAL,       /* PLAIN */
 -      GIT_COLOR_RED,          /* REMOTE */
 -      GIT_COLOR_NORMAL,       /* LOCAL */
 -      GIT_COLOR_GREEN,        /* CURRENT */
 -      GIT_COLOR_BLUE,         /* UPSTREAM */
 +      GIT_COLOR_NORMAL,       /* PLAIN */
 +      GIT_COLOR_RED,          /* REMOTE */
 +      GIT_COLOR_NORMAL,       /* LOCAL */
 +      GIT_COLOR_GREEN,        /* CURRENT */
 +      GIT_COLOR_BLUE,         /* UPSTREAM */
  };
  enum color_branch {
        BRANCH_COLOR_RESET = 0,
@@@ -252,7 -251,7 +252,7 @@@ static int delete_branches(int argc, co
                        goto next;
                }
  
-               if (delete_ref(name, is_null_sha1(sha1) ? NULL : sha1,
+               if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1,
                               REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
        return(ret);
  }
  
 -static void fill_tracking_info(struct strbuf *stat, const char *branch_name,
 -              int show_upstream_ref)
 +static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
  {
 -      int ours, theirs;
 -      char *ref = NULL;
 -      struct branch *branch = branch_get(branch_name);
 -      const char *upstream;
 -      struct strbuf fancy = STRBUF_INIT;
 -      int upstream_is_gone = 0;
 -      int added_decoration = 1;
 -
 -      if (stat_tracking_info(branch, &ours, &theirs, &upstream) < 0) {
 -              if (!upstream)
 -                      return;
 -              upstream_is_gone = 1;
 -      }
 -
 -      if (show_upstream_ref) {
 -              ref = shorten_unambiguous_ref(upstream, 0);
 -              if (want_color(branch_use_color))
 -                      strbuf_addf(&fancy, "%s%s%s",
 -                                      branch_get_color(BRANCH_COLOR_UPSTREAM),
 -                                      ref, branch_get_color(BRANCH_COLOR_RESET));
 -              else
 -                      strbuf_addstr(&fancy, ref);
 -      }
 +      int i, max = 0;
 +      for (i = 0; i < refs->nr; i++) {
 +              struct ref_array_item *it = refs->items[i];
 +              const char *desc = it->refname;
 +              int w;
  
 -      if (upstream_is_gone) {
 -              if (show_upstream_ref)
 -                      strbuf_addf(stat, _("[%s: gone]"), fancy.buf);
 -              else
 -                      added_decoration = 0;
 -      } else if (!ours && !theirs) {
 -              if (show_upstream_ref)
 -                      strbuf_addf(stat, _("[%s]"), fancy.buf);
 -              else
 -                      added_decoration = 0;
 -      } else if (!ours) {
 -              if (show_upstream_ref)
 -                      strbuf_addf(stat, _("[%s: behind %d]"), fancy.buf, theirs);
 -              else
 -                      strbuf_addf(stat, _("[behind %d]"), theirs);
 +              skip_prefix(it->refname, "refs/heads/", &desc);
 +              skip_prefix(it->refname, "refs/remotes/", &desc);
 +              if (it->kind == FILTER_REFS_DETACHED_HEAD) {
 +                      char *head_desc = get_head_description();
 +                      w = utf8_strwidth(head_desc);
 +                      free(head_desc);
 +              } else
 +                      w = utf8_strwidth(desc);
  
 -      } else if (!theirs) {
 -              if (show_upstream_ref)
 -                      strbuf_addf(stat, _("[%s: ahead %d]"), fancy.buf, ours);
 -              else
 -                      strbuf_addf(stat, _("[ahead %d]"), ours);
 -      } else {
 -              if (show_upstream_ref)
 -                      strbuf_addf(stat, _("[%s: ahead %d, behind %d]"),
 -                                  fancy.buf, ours, theirs);
 -              else
 -                      strbuf_addf(stat, _("[ahead %d, behind %d]"),
 -                                  ours, theirs);
 +              if (it->kind == FILTER_REFS_REMOTES)
 +                      w += remote_bonus;
 +              if (w > max)
 +                      max = w;
        }
 -      strbuf_release(&fancy);
 -      if (added_decoration)
 -              strbuf_addch(stat, ' ');
 -      free(ref);
 +      return max;
  }
  
 -static void add_verbose_info(struct strbuf *out, struct ref_array_item *item,
 -                           struct ref_filter *filter, const char *refname)
 +static const char *quote_literal_for_format(const char *s)
  {
 -      struct strbuf subject = STRBUF_INIT, stat = STRBUF_INIT;
 -      const char *sub = _(" **** invalid ref ****");
 -      struct commit *commit = item->commit;
 +      static struct strbuf buf = STRBUF_INIT;
  
 -      if (!parse_commit(commit)) {
 -              pp_commit_easy(CMIT_FMT_ONELINE, commit, &subject);
 -              sub = subject.buf;
 +      strbuf_reset(&buf);
 +      while (*s) {
 +              const char *ep = strchrnul(s, '%');
 +              if (s < ep)
 +                      strbuf_add(&buf, s, ep - s);
 +              if (*ep == '%') {
 +                      strbuf_addstr(&buf, "%%");
 +                      s = ep + 1;
 +              } else {
 +                      s = ep;
 +              }
        }
 -
 -      if (item->kind == FILTER_REFS_BRANCHES)
 -              fill_tracking_info(&stat, refname, filter->verbose > 1);
 -
 -      strbuf_addf(out, " %s %s%s",
 -              find_unique_abbrev(item->commit->object.oid.hash, filter->abbrev),
 -              stat.buf, sub);
 -      strbuf_release(&stat);
 -      strbuf_release(&subject);
 +      return buf.buf;
  }
  
 -static char *get_head_description(void)
 +static char *build_format(struct ref_filter *filter, int maxwidth, const char *remote_prefix)
  {
 -      struct strbuf desc = STRBUF_INIT;
 -      struct wt_status_state state;
 -      memset(&state, 0, sizeof(state));
 -      wt_status_get_state(&state, 1);
 -      if (state.rebase_in_progress ||
 -          state.rebase_interactive_in_progress)
 -              strbuf_addf(&desc, _("(no branch, rebasing %s)"),
 -                          state.branch);
 -      else if (state.bisect_in_progress)
 -              strbuf_addf(&desc, _("(no branch, bisect started on %s)"),
 -                          state.branch);
 -      else if (state.detached_from) {
 -              if (state.detached_at)
 -                      /* TRANSLATORS: make sure this matches
 -                         "HEAD detached at " in wt-status.c */
 -                      strbuf_addf(&desc, _("(HEAD detached at %s)"),
 -                              state.detached_from);
 -              else
 -                      /* TRANSLATORS: make sure this matches
 -                         "HEAD detached from " in wt-status.c */
 -                      strbuf_addf(&desc, _("(HEAD detached from %s)"),
 -                              state.detached_from);
 -      }
 -      else
 -              strbuf_addstr(&desc, _("(no branch)"));
 -      free(state.branch);
 -      free(state.onto);
 -      free(state.detached_from);
 -      return strbuf_detach(&desc, NULL);
 -}
 +      struct strbuf fmt = STRBUF_INIT;
 +      struct strbuf local = STRBUF_INIT;
 +      struct strbuf remote = STRBUF_INIT;
  
 -static void format_and_print_ref_item(struct ref_array_item *item, int maxwidth,
 -                                    struct ref_filter *filter, const char *remote_prefix)
 -{
 -      char c;
 -      int current = 0;
 -      int color;
 -      struct strbuf out = STRBUF_INIT, name = STRBUF_INIT;
 -      const char *prefix_to_show = "";
 -      const char *prefix_to_skip = NULL;
 -      const char *desc = item->refname;
 -      char *to_free = NULL;
 +      strbuf_addf(&fmt, "%%(if)%%(HEAD)%%(then)* %s%%(else)  %%(end)",
 +                  branch_get_color(BRANCH_COLOR_CURRENT));
  
 -      switch (item->kind) {
 -      case FILTER_REFS_BRANCHES:
 -              prefix_to_skip = "refs/heads/";
 -              skip_prefix(desc, prefix_to_skip, &desc);
 -              if (!filter->detached && !strcmp(desc, head))
 -                      current = 1;
 +      if (filter->verbose) {
 +              strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
 +              strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
 +              strbuf_addf(&local, " %%(objectname:short=7) ");
 +
 +              if (filter->verbose > 1)
 +                      strbuf_addf(&local, "%%(if)%%(upstream)%%(then)[%s%%(upstream:short)%s%%(if)%%(upstream:track)"
 +                                  "%%(then): %%(upstream:track,nobracket)%%(end)] %%(end)%%(contents:subject)",
 +                                  branch_get_color(BRANCH_COLOR_UPSTREAM), branch_get_color(BRANCH_COLOR_RESET));
                else
 -                      color = BRANCH_COLOR_LOCAL;
 -              break;
 -      case FILTER_REFS_REMOTES:
 -              prefix_to_skip = "refs/remotes/";
 -              skip_prefix(desc, prefix_to_skip, &desc);
 -              color = BRANCH_COLOR_REMOTE;
 -              prefix_to_show = remote_prefix;
 -              break;
 -      case FILTER_REFS_DETACHED_HEAD:
 -              desc = to_free = get_head_description();
 -              current = 1;
 -              break;
 -      default:
 -              color = BRANCH_COLOR_PLAIN;
 -              break;
 -      }
 +                      strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
  
 -      c = ' ';
 -      if (current) {
 -              c = '*';
 -              color = BRANCH_COLOR_CURRENT;
 -      }
 -
 -      strbuf_addf(&name, "%s%s", prefix_to_show, desc);
 -      if (filter->verbose) {
 -              int utf8_compensation = strlen(name.buf) - utf8_strwidth(name.buf);
 -              strbuf_addf(&out, "%c %s%-*s%s", c, branch_get_color(color),
 -                          maxwidth + utf8_compensation, name.buf,
 +              strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s%%(if)%%(symref)%%(then) -> %%(symref:short)"
 +                          "%%(else) %%(objectname:short=7) %%(contents:subject)%%(end)",
 +                          branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
                            branch_get_color(BRANCH_COLOR_RESET));
 -      } else
 -              strbuf_addf(&out, "%c %s%s%s", c, branch_get_color(color),
 -                          name.buf, branch_get_color(BRANCH_COLOR_RESET));
 -
 -      if (item->symref) {
 -              const char *symref = item->symref;
 -              if (prefix_to_skip)
 -                      skip_prefix(symref, prefix_to_skip, &symref);
 -              strbuf_addf(&out, " -> %s", symref);
 -      }
 -      else if (filter->verbose)
 -              /* " f7c0c00 [ahead 58, behind 197] vcs-svn: drop obj_pool.h" */
 -              add_verbose_info(&out, item, filter, desc);
 -      if (column_active(colopts)) {
 -              assert(!filter->verbose && "--column and --verbose are incompatible");
 -              string_list_append(&output, out.buf);
        } else {
 -              printf("%s\n", out.buf);
 +              strbuf_addf(&local, "%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
 +                          branch_get_color(BRANCH_COLOR_RESET));
 +              strbuf_addf(&remote, "%s%s%%(refname:lstrip=2)%s%%(if)%%(symref)%%(then) -> %%(symref:short)%%(end)",
 +                          branch_get_color(BRANCH_COLOR_REMOTE), quote_literal_for_format(remote_prefix),
 +                          branch_get_color(BRANCH_COLOR_RESET));
        }
 -      strbuf_release(&name);
 -      strbuf_release(&out);
 -      free(to_free);
 -}
 -
 -static int calc_maxwidth(struct ref_array *refs, int remote_bonus)
 -{
 -      int i, max = 0;
 -      for (i = 0; i < refs->nr; i++) {
 -              struct ref_array_item *it = refs->items[i];
 -              const char *desc = it->refname;
 -              int w;
  
 -              skip_prefix(it->refname, "refs/heads/", &desc);
 -              skip_prefix(it->refname, "refs/remotes/", &desc);
 -              w = utf8_strwidth(desc);
 +      strbuf_addf(&fmt, "%%(if:notequals=refs/remotes)%%(refname:rstrip=-2)%%(then)%s%%(else)%s%%(end)", local.buf, remote.buf);
  
 -              if (it->kind == FILTER_REFS_REMOTES)
 -                      w += remote_bonus;
 -              if (w > max)
 -                      max = w;
 -      }
 -      return max;
 +      strbuf_release(&local);
 +      strbuf_release(&remote);
 +      return strbuf_detach(&fmt, NULL);
  }
  
 -static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting)
 +static void print_ref_list(struct ref_filter *filter, struct ref_sorting *sorting, const char *format)
  {
        int i;
        struct ref_array array;
        int maxwidth = 0;
        const char *remote_prefix = "";
 +      struct strbuf out = STRBUF_INIT;
 +      char *to_free = NULL;
  
        /*
         * If we are listing more than just remote branches,
  
        memset(&array, 0, sizeof(array));
  
 -      verify_ref_format("%(refname)%(symref)");
        filter_refs(&array, filter, filter->kind | FILTER_REFS_INCLUDE_BROKEN);
  
        if (filter->verbose)
                maxwidth = calc_maxwidth(&array, strlen(remote_prefix));
  
 +      if (!format)
 +              format = to_free = build_format(filter, maxwidth, remote_prefix);
 +      verify_ref_format(format);
 +
        ref_array_sort(sorting, &array);
  
 -      for (i = 0; i < array.nr; i++)
 -              format_and_print_ref_item(array.items[i], maxwidth, filter, remote_prefix);
 +      for (i = 0; i < array.nr; i++) {
 +              format_ref_array_item(array.items[i], format, 0, &out);
 +              if (column_active(colopts)) {
 +                      assert(!filter->verbose && "--column and --verbose are incompatible");
 +                       /* format to a string_list to let print_columns() do its job */
 +                      string_list_append(&output, out.buf);
 +              } else {
 +                      fwrite(out.buf, 1, out.len, stdout);
 +                      putchar('\n');
 +              }
 +              strbuf_release(&out);
 +      }
  
        ref_array_clear(&array);
 +      free(to_free);
  }
  
  static void reject_rebase_or_bisect_branch(const char *target)
@@@ -471,14 -579,15 +471,15 @@@ static void rename_branch(const char *o
  
        if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch rename failed"));
-       strbuf_release(&logmsg);
  
        if (recovery)
                warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
  
-       if (replace_each_worktree_head_symref(oldref.buf, newref.buf))
+       if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
                die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
  
+       strbuf_release(&logmsg);
        strbuf_addf(&oldsection, "branch.%s", oldref.buf + 11);
        strbuf_release(&oldref);
        strbuf_addf(&newsection, "branch.%s", newref.buf + 11);
@@@ -530,7 -639,6 +531,7 @@@ int cmd_branch(int argc, const char **a
        struct ref_filter filter;
        int icase = 0;
        static struct ref_sorting *sorting = NULL, **sorting_tail = &sorting;
 +      const char *format = NULL;
  
        struct option options[] = {
                OPT_GROUP(N_("Generic options")),
                        N_("print only branches of the object"), 0, parse_opt_object_name
                },
                OPT_BOOL('i', "ignore-case", &icase, N_("sorting and filtering are case insensitive")),
 +              OPT_STRING(  0 , "format", &format, N_("format"), N_("format to use for the output")),
                OPT_END(),
        };
  
 +      setup_ref_filter_porcelain_msg();
 +
        memset(&filter, 0, sizeof(filter));
        filter.kind = FILTER_REFS_BRANCHES;
        filter.abbrev = -1;
                if (!sorting)
                        sorting = ref_default_sorting();
                sorting->ignore_case = icase;
 -              print_ref_list(&filter, sorting);
 +              print_ref_list(&filter, sorting, format);
                print_columns(&output, colopts, NULL);
                string_list_clear(&output, 0);
                return 0;
diff --combined builtin/tag.c
index e5e2c6a446261f5e4543ae0ead0cbadb8ed7c6cb,c23d6d4bc7d0dd65fa44ed6748209446bd0dd104..ad29be692384454ffd2f35f134d49c227ae1a67b
@@@ -45,11 -45,11 +45,11 @@@ static int list_tags(struct ref_filter 
        if (!format) {
                if (filter->lines) {
                        to_free = xstrfmt("%s %%(contents:lines=%d)",
 -                                        "%(align:15)%(refname:strip=2)%(end)",
 +                                        "%(align:15)%(refname:lstrip=2)%(end)",
                                          filter->lines);
                        format = to_free;
                } else
 -                      format = "%(refname:strip=2)";
 +                      format = "%(refname:lstrip=2)";
        }
  
        verify_ref_format(format);
@@@ -97,7 -97,7 +97,7 @@@ static int for_each_tag_name(const cha
  static int delete_tag(const char *name, const char *ref,
                      const unsigned char *sha1, const void *cb_data)
  {
-       if (delete_ref(ref, sha1, 0))
+       if (delete_ref(NULL, ref, sha1, 0))
                return 1;
        printf(_("Deleted tag '%s' (was %s)\n"), name, find_unique_abbrev(sha1, DEFAULT_ABBREV));
        return 0;
@@@ -302,54 -302,6 +302,54 @@@ static void create_tag(const unsigned c
        }
  }
  
 +static void create_reflog_msg(const unsigned char *sha1, struct strbuf *sb)
 +{
 +      enum object_type type;
 +      struct commit *c;
 +      char *buf;
 +      unsigned long size;
 +      int subject_len = 0;
 +      const char *subject_start;
 +
 +      char *rla = getenv("GIT_REFLOG_ACTION");
 +      if (rla) {
 +              strbuf_addstr(sb, rla);
 +      } else {
 +              strbuf_addstr(sb, _("tag: tagging "));
 +              strbuf_add_unique_abbrev(sb, sha1, DEFAULT_ABBREV);
 +      }
 +
 +      strbuf_addstr(sb, " (");
 +      type = sha1_object_info(sha1, NULL);
 +      switch (type) {
 +      default:
 +              strbuf_addstr(sb, _("object of unknown type"));
 +              break;
 +      case OBJ_COMMIT:
 +              if ((buf = read_sha1_file(sha1, &type, &size)) != NULL) {
 +                      subject_len = find_commit_subject(buf, &subject_start);
 +                      strbuf_insert(sb, sb->len, subject_start, subject_len);
 +              } else {
 +                      strbuf_addstr(sb, _("commit object"));
 +              }
 +              free(buf);
 +
 +              if ((c = lookup_commit_reference(sha1)) != NULL)
 +                      strbuf_addf(sb, ", %s", show_date(c->date, 0, DATE_MODE(SHORT)));
 +              break;
 +      case OBJ_TREE:
 +              strbuf_addstr(sb, _("tree object"));
 +              break;
 +      case OBJ_BLOB:
 +              strbuf_addstr(sb, _("blob object"));
 +              break;
 +      case OBJ_TAG:
 +              strbuf_addstr(sb, _("other tag object"));
 +              break;
 +      }
 +      strbuf_addch(sb, ')');
 +}
 +
  struct msg_arg {
        int given;
        struct strbuf buf;
@@@ -383,7 -335,6 +383,7 @@@ int cmd_tag(int argc, const char **argv
  {
        struct strbuf buf = STRBUF_INIT;
        struct strbuf ref = STRBUF_INIT;
 +      struct strbuf reflog_msg = STRBUF_INIT;
        unsigned char object[20], prev[20];
        const char *object_ref, *tag;
        struct create_tag_options opt;
                OPT_END()
        };
  
 +      setup_ref_filter_porcelain_msg();
 +
        git_config(git_tag_config, sorting_tail);
  
        memset(&opt, 0, sizeof(opt));
        else
                die(_("Invalid cleanup mode %s"), cleanup_arg);
  
 +      create_reflog_msg(object, &reflog_msg);
 +
        if (create_tag_object) {
                if (force_sign_annotate && !annotate)
                        opt.sign = 1;
        if (!transaction ||
            ref_transaction_update(transaction, ref.buf, object, prev,
                                   create_reflog ? REF_FORCE_CREATE_REFLOG : 0,
 -                                 NULL, &err) ||
 +                                 reflog_msg.buf, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
        ref_transaction_free(transaction);
        strbuf_release(&err);
        strbuf_release(&buf);
        strbuf_release(&ref);
 +      strbuf_release(&reflog_msg);
        return 0;
  }
diff --combined refs.c
index 81b64b4ed51d58603598f719097eba9c4ef17ab4,053d23a90df023bc43fb00b0489f161d220d904e..6d0961921b25d2296195bddb3666b6537b0e0fb2
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -3,7 -3,6 +3,7 @@@
   */
  
  #include "cache.h"
 +#include "hashmap.h"
  #include "lockfile.h"
  #include "refs.h"
  #include "refs/refs-internal.h"
@@@ -592,8 -591,8 +592,8 @@@ static int delete_pseudoref(const char 
        return 0;
  }
  
- int delete_ref(const char *refname, const unsigned char *old_sha1,
-              unsigned int flags)
+ int delete_ref(const char *msg, const char *refname,
+              const unsigned char *old_sha1, unsigned int flags)
  {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
            ref_transaction_delete(transaction, refname, old_sha1,
-                                  flags, NULL, &err) ||
+                                  flags, msg, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
                ref_transaction_free(transaction);
@@@ -1235,10 -1234,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;
@@@ -1358,102 -1357,62 +1358,102 @@@ int resolve_gitlink_ref(const char *sub
        return 0;
  }
  
 +struct submodule_hash_entry
 +{
 +      struct hashmap_entry ent; /* must be the first member! */
 +
 +      struct ref_store *refs;
 +
 +      /* NUL-terminated name of submodule: */
 +      char submodule[FLEX_ARRAY];
 +};
 +
 +static int submodule_hash_cmp(const void *entry, const void *entry_or_key,
 +                            const void *keydata)
 +{
 +      const struct submodule_hash_entry *e1 = entry, *e2 = entry_or_key;
 +      const char *submodule = keydata ? keydata : e2->submodule;
 +
 +      return strcmp(e1->submodule, submodule);
 +}
 +
 +static struct submodule_hash_entry *alloc_submodule_hash_entry(
 +              const char *submodule, struct ref_store *refs)
 +{
 +      struct submodule_hash_entry *entry;
 +
 +      FLEX_ALLOC_STR(entry, submodule, submodule);
 +      hashmap_entry_init(entry, strhash(submodule));
 +      entry->refs = refs;
 +      return entry;
 +}
 +
  /* A pointer to the ref_store for the main repository: */
  static struct ref_store *main_ref_store;
  
 -/* A linked list of ref_stores for submodules: */
 -static struct ref_store *submodule_ref_stores;
 +/* A hashmap of ref_stores, stored by submodule name: */
 +static struct hashmap submodule_ref_stores;
  
 -void base_ref_store_init(struct ref_store *refs,
 -                       const struct ref_storage_be *be,
 -                       const char *submodule)
 +/*
 + * Return the ref_store instance for the specified submodule (or the
 + * main repository if submodule is NULL). If that ref_store hasn't
 + * been initialized yet, return NULL.
 + */
 +static struct ref_store *lookup_ref_store(const char *submodule)
 +{
 +      struct submodule_hash_entry *entry;
 +
 +      if (!submodule)
 +              return main_ref_store;
 +
 +      if (!submodule_ref_stores.tablesize)
 +              /* It's initialized on demand in register_ref_store(). */
 +              return NULL;
 +
 +      entry = hashmap_get_from_hash(&submodule_ref_stores,
 +                                    strhash(submodule), submodule);
 +      return entry ? entry->refs : NULL;
 +}
 +
 +/*
 + * Register the specified ref_store to be the one that should be used
 + * for submodule (or the main repository if submodule is NULL). It is
 + * a fatal error to call this function twice for the same submodule.
 + */
 +static void register_ref_store(struct ref_store *refs, const char *submodule)
  {
 -      refs->be = be;
        if (!submodule) {
                if (main_ref_store)
                        die("BUG: main_ref_store initialized twice");
  
 -              refs->submodule = "";
 -              refs->next = NULL;
                main_ref_store = refs;
        } else {
 -              if (lookup_ref_store(submodule))
 +              if (!submodule_ref_stores.tablesize)
 +                      hashmap_init(&submodule_ref_stores, submodule_hash_cmp, 0);
 +
 +              if (hashmap_put(&submodule_ref_stores,
 +                              alloc_submodule_hash_entry(submodule, refs)))
                        die("BUG: ref_store for submodule '%s' initialized twice",
                            submodule);
 -
 -              refs->submodule = xstrdup(submodule);
 -              refs->next = submodule_ref_stores;
 -              submodule_ref_stores = refs;
        }
  }
  
 -struct ref_store *ref_store_init(const char *submodule)
 +/*
 + * Create, record, and return a ref_store instance for the specified
 + * submodule (or the main repository if submodule is NULL).
 + */
 +static struct ref_store *ref_store_init(const char *submodule)
  {
        const char *be_name = "files";
        struct ref_storage_be *be = find_ref_storage_backend(be_name);
 +      struct ref_store *refs;
  
        if (!be)
                die("BUG: reference backend %s is unknown", be_name);
  
 -      if (!submodule || !*submodule)
 -              return be->init(NULL);
 -      else
 -              return be->init(submodule);
 -}
 -
 -struct ref_store *lookup_ref_store(const char *submodule)
 -{
 -      struct ref_store *refs;
 -
 -      if (!submodule || !*submodule)
 -              return main_ref_store;
 -
 -      for (refs = submodule_ref_stores; refs; refs = refs->next) {
 -              if (!strcmp(submodule, refs->submodule))
 -                      return refs;
 -      }
 -
 -      return NULL;
 +      refs = be->init(submodule);
 +      register_ref_store(refs, submodule);
 +      return refs;
  }
  
  struct ref_store *get_ref_store(const char *submodule)
        return refs;
  }
  
 -void assert_main_repository(struct ref_store *refs, const char *caller)
 +void base_ref_store_init(struct ref_store *refs,
 +                       const struct ref_storage_be *be)
  {
 -      if (*refs->submodule)
 -              die("BUG: %s called for a submodule", caller);
 +      refs->be = be;
  }
  
  /* backend functions */
diff --combined refs/files-backend.c
index db3bd42a91d1935471a93e3149165705ec55f8ec,42b137bb1c274c353b7206ae8652648bc6dc9b45..b42df147c9c4335b0e47201f751e7e05204e4b01
@@@ -912,14 -912,6 +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;
  };
@@@ -980,24 -972,11 +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
@@@ -1008,18 -987,14 +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: */
@@@ -1158,8 -1133,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");
@@@ -1228,8 -1203,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)) {
@@@ -1373,8 -1358,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);
  
@@@ -1555,7 -1540,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;
  
@@@ -2000,13 -1985,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.
@@@ -2022,11 -2000,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;
@@@ -2149,7 -2152,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);
@@@ -2187,7 -2190,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");
@@@ -2220,7 -2223,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");
@@@ -2295,25 -2298,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 */
@@@ -2361,6 -2350,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)
@@@ -2407,7 -2397,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)
  {
        for (i = 0; i < refnames->nr; i++) {
                const char *refname = refnames->items[i].string;
  
-               if (delete_ref(refname, NULL, flags))
+               if (delete_ref(NULL, refname, NULL, flags))
                        result |= error(_("could not remove reference %s"), refname);
        }
  
   */
  #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 -2616,7 +2596,7 @@@ static int files_rename_ref(struct ref_
                return error("unable to move logfile logs/%s to "TMP_RENAMED_LOG": %s",
                        oldrefname, strerror(errno));
  
-       if (delete_ref(oldrefname, orig_sha1, REF_NODEREF)) {
+       if (delete_ref(logmsg, oldrefname, orig_sha1, REF_NODEREF)) {
                error("unable to delete old %s", oldrefname);
                goto rollback;
        }
         */
        if (!read_ref_full(newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE,
                           sha1, NULL) &&
-           delete_ref(newrefname, NULL, REF_NODEREF)) {
+           delete_ref(NULL, newrefname, NULL, REF_NODEREF)) {
 -              if (errno==EISDIR) {
 +              if (errno == EISDIR) {
                        struct strbuf path = STRBUF_INIT;
                        int result;
  
@@@ -2720,89 -2740,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 == 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
@@@ -2917,11 -2930,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);
@@@ -2991,8 -3003,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);
        }
@@@ -3044,7 -3055,7 +3044,7 @@@ static int files_create_symref(struct r
        return ret;
  }
  
- int set_worktree_head_symref(const char *gitdir, const char *target)
+ int set_worktree_head_symref(const char *gitdir, const char *target, const char *logmsg)
  {
        static struct lock_file head_lock;
        struct ref_lock *lock;
        lock->lk = &head_lock;
        lock->ref_name = xstrdup(head_rel);
  
-       ret = create_symref_locked(lock, head_rel, target, NULL);
+       ret = create_symref_locked(lock, head_rel, target, logmsg);
  
        unlock_ref(lock); /* will free lock */
        strbuf_release(&head_path);
@@@ -3549,7 -3560,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;
@@@ -3767,11 -3778,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 t/t1400-update-ref.sh
index e208009906f13fa48a404ea9e84cf5b037ce0837,6e112fb5f9c66162b87355f06769ae6a1078f0c6..825422341de5d80247d4a2ac689d6fc799f9a9a0
@@@ -85,6 -85,24 +85,24 @@@ test_expect_success "delete $m (by HEAD
  '
  rm -f .git/$m
  
+ test_expect_success "deleting current branch adds message to HEAD's log" '
+       git update-ref $m $A &&
+       git symbolic-ref HEAD $m &&
+       git update-ref -m delete-$m -d $m &&
+       ! test -f .git/$m &&
+       grep "delete-$m$" .git/logs/HEAD
+ '
+ rm -f .git/$m
+ test_expect_success "deleting by HEAD adds message to HEAD's log" '
+       git update-ref $m $A &&
+       git symbolic-ref HEAD $m &&
+       git update-ref -m delete-by-head -d HEAD &&
+       ! test -f .git/$m &&
+       grep "delete-by-head$" .git/logs/HEAD
+ '
+ rm -f .git/$m
  test_expect_success 'update-ref does not create reflogs by default' '
        test_when_finished "git update-ref -d $outside" &&
        git update-ref $outside $A &&
@@@ -256,33 -274,6 +274,33 @@@ test_expect_success 
         git update-ref HEAD'" $A &&
         test $A"' = $(cat .git/'"$m"')'
  
 +test_expect_success "empty directory removal" '
 +      git branch d1/d2/r1 HEAD &&
 +      git branch d1/r2 HEAD &&
 +      test -f .git/refs/heads/d1/d2/r1 &&
 +      test -f .git/logs/refs/heads/d1/d2/r1 &&
 +      git branch -d d1/d2/r1 &&
 +      ! test -e .git/refs/heads/d1/d2 &&
 +      ! test -e .git/logs/refs/heads/d1/d2 &&
 +      test -f .git/refs/heads/d1/r2 &&
 +      test -f .git/logs/refs/heads/d1/r2
 +'
 +
 +test_expect_success "symref empty directory removal" '
 +      git branch e1/e2/r1 HEAD &&
 +      git branch e1/r2 HEAD &&
 +      git checkout e1/e2/r1 &&
 +      test_when_finished "git checkout master" &&
 +      test -f .git/refs/heads/e1/e2/r1 &&
 +      test -f .git/logs/refs/heads/e1/e2/r1 &&
 +      git update-ref -d HEAD &&
 +      ! test -e .git/refs/heads/e1/e2 &&
 +      ! test -e .git/logs/refs/heads/e1/e2 &&
 +      test -f .git/refs/heads/e1/r2 &&
 +      test -f .git/logs/refs/heads/e1/r2 &&
 +      test -f .git/logs/HEAD
 +'
 +
  cat >expect <<EOF
  $Z $A $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150200 +0000     Initial Creation
  $A $B $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 1117150260 +0000     Switch
diff --combined transport.c
index 48864b3a9e66ce42abc7e2350a0afb424df1e93f,269352b22396ce722408fe46f65139046f056b80..5828e06afd670fc9c563da8bf233c34778872e9c
@@@ -299,7 -299,7 +299,7 @@@ void transport_update_tracking_ref(stru
                if (verbose)
                        fprintf(stderr, "updating local tracking ref '%s'\n", rs.dst);
                if (ref->deletion) {
-                       delete_ref(rs.dst, NULL, 0);
+                       delete_ref(NULL, rs.dst, NULL, 0);
                } else
                        update_ref("update by push", rs.dst,
                                        ref->new_oid.hash, NULL, 0, 0);
@@@ -1206,42 -1206,6 +1206,42 @@@ literal_copy
        return xstrdup(url);
  }
  
 +static void read_alternate_refs(const char *path,
 +                              alternate_ref_fn *cb,
 +                              void *data)
 +{
 +      struct child_process cmd = CHILD_PROCESS_INIT;
 +      struct strbuf line = STRBUF_INIT;
 +      FILE *fh;
 +
 +      cmd.git_cmd = 1;
 +      argv_array_pushf(&cmd.args, "--git-dir=%s", path);
 +      argv_array_push(&cmd.args, "for-each-ref");
 +      argv_array_push(&cmd.args, "--format=%(objectname) %(refname)");
 +      cmd.env = local_repo_env;
 +      cmd.out = -1;
 +
 +      if (start_command(&cmd))
 +              return;
 +
 +      fh = xfdopen(cmd.out, "r");
 +      while (strbuf_getline_lf(&line, fh) != EOF) {
 +              struct object_id oid;
 +
 +              if (get_oid_hex(line.buf, &oid) ||
 +                  line.buf[GIT_SHA1_HEXSZ] != ' ') {
 +                      warning("invalid line while parsing alternate refs: %s",
 +                              line.buf);
 +                      break;
 +              }
 +
 +              cb(line.buf + GIT_SHA1_HEXSZ + 1, &oid, data);
 +      }
 +
 +      fclose(fh);
 +      finish_command(&cmd);
 +}
 +
  struct alternate_refs_data {
        alternate_ref_fn *fn;
        void *data;
  static int refs_from_alternate_cb(struct alternate_object_database *e,
                                  void *data)
  {
 -      char *other;
 -      size_t len;
 -      struct remote *remote;
 -      struct transport *transport;
 -      const struct ref *extra;
 +      struct strbuf path = STRBUF_INIT;
 +      size_t base_len;
        struct alternate_refs_data *cb = data;
  
 -      other = real_pathdup(e->path);
 -      len = strlen(other);
 -
 -      while (other[len-1] == '/')
 -              other[--len] = '\0';
 -      if (len < 8 || memcmp(other + len - 8, "/objects", 8))
 +      if (!strbuf_realpath(&path, e->path, 0))
                goto out;
 +      if (!strbuf_strip_suffix(&path, "/objects"))
 +              goto out;
 +      base_len = path.len;
 +
        /* Is this a git repository with refs? */
 -      memcpy(other + len - 8, "/refs", 6);
 -      if (!is_directory(other))
 +      strbuf_addstr(&path, "/refs");
 +      if (!is_directory(path.buf))
                goto out;
 -      other[len - 8] = '\0';
 -      remote = remote_get(other);
 -      transport = transport_get(remote, other);
 -      for (extra = transport_get_remote_refs(transport);
 -           extra;
 -           extra = extra->next)
 -              cb->fn(extra, cb->data);
 -      transport_disconnect(transport);
 +      strbuf_setlen(&path, base_len);
 +
 +      read_alternate_refs(path.buf, cb->fn, cb->data);
 +
  out:
 -      free(other);
 +      strbuf_release(&path);
        return 0;
  }