Merge branch 'bc/object-id'
authorJunio C Hamano <gitster@pobox.com>
Fri, 17 Mar 2017 20:50:24 +0000 (13:50 -0700)
committerJunio C Hamano <gitster@pobox.com>
Fri, 17 Mar 2017 20:50:25 +0000 (13:50 -0700)
"uchar [40]" to "struct object_id" conversion continues.

* bc/object-id:
wt-status: convert to struct object_id
builtin/merge-base: convert to struct object_id
Convert object iteration callbacks to struct object_id
sha1_file: introduce an nth_packed_object_oid function
refs: simplify parsing of reflog entries
refs: convert each_reflog_ent_fn to struct object_id
reflog-walk: convert struct reflog_info to struct object_id
builtin/replace: convert to struct object_id
Convert remaining callers of resolve_refdup to object_id
builtin/merge: convert to struct object_id
builtin/clone: convert to struct object_id
builtin/branch: convert to struct object_id
builtin/grep: convert to struct object_id
builtin/fmt-merge-message: convert to struct object_id
builtin/fast-export: convert to struct object_id
builtin/describe: convert to struct object_id
builtin/diff-tree: convert to struct object_id
builtin/commit: convert to struct object_id
hex: introduce parse_oid_hex

18 files changed:
1  2 
builtin/branch.c
builtin/describe.c
builtin/diff-tree.c
builtin/grep.c
builtin/merge.c
builtin/notes.c
builtin/pack-objects.c
builtin/receive-pack.c
builtin/replace.c
cache.h
ref-filter.c
refs.c
refs.h
refs/files-backend.c
revision.c
sha1_file.c
sha1_name.c
transport.c
diff --combined builtin/branch.c
index 5d44e36cba0e030cb77461784a65f77c44cb67cd,faf472ff8f091306ef3ee528662b45bb693393c4..52688f2e1be91024ba6cb170b4b38b393e8ab8ba
@@@ -28,21 -28,20 +28,21 @@@ 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
  };
  
  static const char *head;
- static unsigned char head_sha1[20];
+ static struct object_id head_oid;
  
  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,
@@@ -118,13 -117,13 +118,13 @@@ static int branch_merged(int kind, cons
        if (kind == FILTER_REFS_BRANCHES) {
                struct branch *branch = branch_get(name);
                const char *upstream = branch_get_upstream(branch, NULL);
-               unsigned char sha1[20];
+               struct object_id oid;
  
                if (upstream &&
                    (reference_name = reference_name_to_free =
                     resolve_refdup(upstream, RESOLVE_REF_READING,
-                                   sha1, NULL)) != NULL)
-                       reference_rev = lookup_commit_reference(sha1);
+                                   oid.hash, NULL)) != NULL)
+                       reference_rev = lookup_commit_reference(oid.hash);
        }
        if (!reference_rev)
                reference_rev = head_rev;
  }
  
  static int check_branch_commit(const char *branchname, const char *refname,
-                              const unsigned char *sha1, struct commit *head_rev,
+                              const struct object_id *oid, struct commit *head_rev,
                               int kinds, int force)
  {
-       struct commit *rev = lookup_commit_reference(sha1);
+       struct commit *rev = lookup_commit_reference(oid->hash);
        if (!rev) {
                error(_("Couldn't look up commit object for '%s'"), refname);
                return -1;
@@@ -184,34 -183,31 +184,34 @@@ static int delete_branches(int argc, co
                           int quiet)
  {
        struct commit *head_rev = NULL;
-       unsigned char sha1[20];
+       struct object_id oid;
        char *name = NULL;
        const char *fmt;
        int i;
        int ret = 0;
        int remote_branch = 0;
        struct strbuf bname = STRBUF_INIT;
 +      unsigned allowed_interpret;
  
        switch (kinds) {
        case FILTER_REFS_REMOTES:
                fmt = "refs/remotes/%s";
                /* For subsequent UI messages */
                remote_branch = 1;
 +              allowed_interpret = INTERPRET_BRANCH_REMOTE;
  
                force = 1;
                break;
        case FILTER_REFS_BRANCHES:
                fmt = "refs/heads/%s";
 +              allowed_interpret = INTERPRET_BRANCH_LOCAL;
                break;
        default:
                die(_("cannot use -a with -d"));
        }
  
        if (!force) {
-               head_rev = lookup_commit_reference(head_sha1);
+               head_rev = lookup_commit_reference(head_oid.hash);
                if (!head_rev)
                        die(_("Couldn't look up commit object for HEAD"));
        }
                char *target = NULL;
                int flags = 0;
  
 -              strbuf_branchname(&bname, argv[i]);
 +              strbuf_branchname(&bname, argv[i], allowed_interpret);
                free(name);
                name = mkpathdup(fmt, bname.buf);
  
                                        RESOLVE_REF_READING
                                        | RESOLVE_REF_NO_RECURSE
                                        | RESOLVE_REF_ALLOW_BAD_NAME,
-                                       sha1, &flags);
+                                       oid.hash, &flags);
                if (!target) {
                        error(remote_branch
                              ? _("remote-tracking branch '%s' not found.")
                }
  
                if (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
-                   check_branch_commit(bname.buf, name, sha1, head_rev, kinds,
+                   check_branch_commit(bname.buf, name, &oid, head_rev, kinds,
                                        force)) {
                        ret = 1;
                        goto next;
                }
  
-               if (delete_ref(NULL, name, is_null_sha1(sha1) ? NULL : sha1,
 -              if (delete_ref(name, is_null_oid(&oid) ? NULL : oid.hash,
++              if (delete_ref(NULL, name, is_null_oid(&oid) ? NULL : oid.hash,
                               REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote-tracking branch '%s'")
                               bname.buf,
                               (flags & REF_ISBROKEN) ? "broken"
                               : (flags & REF_ISSYMREF) ? target
-                              : find_unique_abbrev(sha1, DEFAULT_ABBREV));
+                              : find_unique_abbrev(oid.hash, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
  
        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) {
 +              struct strbuf obname = STRBUF_INIT;
 +
 +              if (filter->abbrev < 0)
 +                      strbuf_addf(&obname, "%%(objectname:short)");
 +              else if (!filter->abbrev)
 +                      strbuf_addf(&obname, "%%(objectname)");
                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(&obname, "%%(objectname:short=%d)", filter->abbrev);
  
 -      c = ' ';
 -      if (current) {
 -              c = '*';
 -              color = BRANCH_COLOR_CURRENT;
 -      }
 +              strbuf_addf(&local, "%%(align:%d,left)%%(refname:lstrip=2)%%(end)", maxwidth);
 +              strbuf_addf(&local, "%s", branch_get_color(BRANCH_COLOR_RESET));
 +              strbuf_addf(&local, " %s ", obname.buf);
  
 -      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,
 -                          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);
 +              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
 +                      strbuf_addf(&local, "%%(if)%%(upstream:track)%%(then)%%(upstream:track) %%(end)%%(contents:subject)");
 +
 +              strbuf_addf(&remote, "%s%%(align:%d,left)%s%%(refname:lstrip=2)%%(end)%s"
 +                          "%%(if)%%(symref)%%(then) -> %%(symref:short)"
 +                          "%%(else) %s %%(contents:subject)%%(end)",
 +                          branch_get_color(BRANCH_COLOR_REMOTE), maxwidth, quote_literal_for_format(remote_prefix),
 +                          branch_get_color(BRANCH_COLOR_RESET), obname.buf);
 +              strbuf_release(&obname);
        } 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)
@@@ -485,15 -579,14 +485,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);
@@@ -545,7 -638,6 +545,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;
  
        track = git_branch_track;
  
-       head = resolve_refdup("HEAD", 0, head_sha1, NULL);
+       head = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD"))
                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/describe.c
index 6769446e1f57537879fd1b411f83ba7393bad338,738e68f95b1bccafeff6d1c58f4245dbd8265ad8..76c18059bf7ad059d4cca70712f4a782cc02d0eb
@@@ -28,8 -28,7 +28,8 @@@ static int abbrev = -1; /* unspecified 
  static int max_candidates = 10;
  static struct hashmap names;
  static int have_util;
 -static const char *pattern;
 +static struct string_list patterns = STRING_LIST_INIT_NODUP;
 +static struct string_list exclude_patterns = STRING_LIST_INIT_NODUP;
  static int always;
  static const char *dirty;
  
@@@ -40,11 -39,11 +40,11 @@@ static const char *diff_index_args[] = 
  
  struct commit_name {
        struct hashmap_entry entry;
-       unsigned char peeled[20];
+       struct object_id peeled;
        struct tag *tag;
        unsigned prio:2; /* annotated tag = 2, tag = 1, head = 0 */
        unsigned name_checked:1;
-       unsigned char sha1[20];
+       struct object_id oid;
        char *path;
  };
  
@@@ -55,17 -54,17 +55,17 @@@ static const char *prio_names[] = 
  static int commit_name_cmp(const struct commit_name *cn1,
                const struct commit_name *cn2, const void *peeled)
  {
-       return hashcmp(cn1->peeled, peeled ? peeled : cn2->peeled);
+       return oidcmp(&cn1->peeled, peeled ? peeled : &cn2->peeled);
  }
  
- static inline struct commit_name *find_commit_name(const unsigned char *peeled)
+ static inline struct commit_name *find_commit_name(const struct object_id *peeled)
  {
-       return hashmap_get_from_hash(&names, sha1hash(peeled), peeled);
+       return hashmap_get_from_hash(&names, sha1hash(peeled->hash), peeled->hash);
  }
  
  static int replace_name(struct commit_name *e,
                               int prio,
-                              const unsigned char *sha1,
+                              const struct object_id *oid,
                               struct tag **tag)
  {
        if (!e || e->prio < prio)
                struct tag *t;
  
                if (!e->tag) {
-                       t = lookup_tag(e->sha1);
+                       t = lookup_tag(e->oid.hash);
                        if (!t || parse_tag(t))
                                return 1;
                        e->tag = t;
                }
  
-               t = lookup_tag(sha1);
+               t = lookup_tag(oid->hash);
                if (!t || parse_tag(t))
                        return 0;
                *tag = t;
  }
  
  static void add_to_known_names(const char *path,
-                              const unsigned char *peeled,
+                              const struct object_id *peeled,
                               int prio,
-                              const unsigned char *sha1)
+                              const struct object_id *oid)
  {
        struct commit_name *e = find_commit_name(peeled);
        struct tag *tag = NULL;
-       if (replace_name(e, prio, sha1, &tag)) {
+       if (replace_name(e, prio, oid, &tag)) {
                if (!e) {
                        e = xmalloc(sizeof(struct commit_name));
-                       hashcpy(e->peeled, peeled);
-                       hashmap_entry_init(e, sha1hash(peeled));
+                       oidcpy(&e->peeled, peeled);
+                       hashmap_entry_init(e, sha1hash(peeled->hash));
                        hashmap_add(&names, e);
                        e->path = NULL;
                }
                e->tag = tag;
                e->prio = prio;
                e->name_checked = 0;
-               hashcpy(e->sha1, sha1);
+               oidcpy(&e->oid, oid);
                free(e->path);
                e->path = xstrdup(path);
        }
@@@ -130,40 -129,9 +130,40 @@@ static int get_name(const char *path, c
        if (!all && !is_tag)
                return 0;
  
 -      /* Accept only tags that match the pattern, if given */
 -      if (pattern && (!is_tag || wildmatch(pattern, path + 10, 0, NULL)))
 -              return 0;
 +      /*
 +       * If we're given exclude patterns, first exclude any tag which match
 +       * any of the exclude pattern.
 +       */
 +      if (exclude_patterns.nr) {
 +              struct string_list_item *item;
 +
 +              if (!is_tag)
 +                      return 0;
 +
 +              for_each_string_list_item(item, &exclude_patterns) {
 +                      if (!wildmatch(item->string, path + 10, 0, NULL))
 +                              return 0;
 +              }
 +      }
 +
 +      /*
 +       * If we're given patterns, accept only tags which match at least one
 +       * pattern.
 +       */
 +      if (patterns.nr) {
 +              struct string_list_item *item;
 +
 +              if (!is_tag)
 +                      return 0;
 +
 +              for_each_string_list_item(item, &patterns) {
 +                      if (!wildmatch(item->string, path + 10, 0, NULL))
 +                              break;
 +
 +                      /* If we get here, no pattern matched. */
 +                      return 0;
 +              }
 +      }
  
        /* Is it annotated? */
        if (!peel_ref(path, peeled.hash)) {
        else
                prio = 0;
  
-       add_to_known_names(all ? path + 5 : path + 10, peeled.hash, prio, oid->hash);
+       add_to_known_names(all ? path + 5 : path + 10, &peeled, prio, oid);
        return 0;
  }
  
@@@ -244,7 -212,7 +244,7 @@@ static unsigned long finish_depth_compu
  static void display_name(struct commit_name *n)
  {
        if (n->prio == 2 && !n->tag) {
-               n->tag = lookup_tag(n->sha1);
+               n->tag = lookup_tag(n->oid.hash);
                if (!n->tag || parse_tag(n->tag))
                        die(_("annotated tag %s not available"), n->path);
        }
                printf("%s", n->path);
  }
  
- static void show_suffix(int depth, const unsigned char *sha1)
+ static void show_suffix(int depth, const struct object_id *oid)
  {
-       printf("-%d-g%s", depth, find_unique_abbrev(sha1, abbrev));
+       printf("-%d-g%s", depth, find_unique_abbrev(oid->hash, abbrev));
  }
  
  static void describe(const char *arg, int last_one)
  {
-       unsigned char sha1[20];
+       struct object_id oid;
        struct commit *cmit, *gave_up_on = NULL;
        struct commit_list *list;
        struct commit_name *n;
        unsigned long seen_commits = 0;
        unsigned int unannotated_cnt = 0;
  
-       if (get_sha1(arg, sha1))
+       if (get_oid(arg, &oid))
                die(_("Not a valid object name %s"), arg);
-       cmit = lookup_commit_reference(sha1);
+       cmit = lookup_commit_reference(oid.hash);
        if (!cmit)
                die(_("%s is not a valid '%s' object"), arg, commit_type);
  
-       n = find_commit_name(cmit->object.oid.hash);
+       n = find_commit_name(&cmit->object.oid);
        if (n && (tags || all || n->prio == 2)) {
                /*
                 * Exact match to an existing ref.
                 */
                display_name(n);
                if (longformat)
-                       show_suffix(0, n->tag ? n->tag->tagged->oid.hash : sha1);
+                       show_suffix(0, n->tag ? &n->tag->tagged->oid : &oid);
                if (dirty)
                        printf("%s", dirty);
                printf("\n");
                struct commit *c;
                struct commit_name *n = hashmap_iter_first(&names, &iter);
                for (; n; n = hashmap_iter_next(&iter)) {
-                       c = lookup_commit_reference_gently(n->peeled, 1);
+                       c = lookup_commit_reference_gently(n->peeled.hash, 1);
                        if (c)
                                c->util = n;
                }
  
        display_name(all_matches[0].name);
        if (abbrev)
-               show_suffix(all_matches[0].depth, cmit->object.oid.hash);
+               show_suffix(all_matches[0].depth, &cmit->object.oid);
        if (dirty)
                printf("%s", dirty);
        printf("\n");
@@@ -436,10 -404,8 +436,10 @@@ int cmd_describe(int argc, const char *
                            N_("only output exact matches"), 0),
                OPT_INTEGER(0, "candidates", &max_candidates,
                            N_("consider <n> most recent tags (default: 10)")),
 -              OPT_STRING(0, "match",       &pattern, N_("pattern"),
 +              OPT_STRING_LIST(0, "match", &patterns, N_("pattern"),
                           N_("only consider tags matching <pattern>")),
 +              OPT_STRING_LIST(0, "exclude", &exclude_patterns, N_("pattern"),
 +                         N_("do not consider tags matching <pattern>")),
                OPT_BOOL(0, "always",        &always,
                        N_("show abbreviated commit object as fallback")),
                {OPTION_STRING, 0, "dirty",  &dirty, N_("mark"),
                die(_("--long is incompatible with --abbrev=0"));
  
        if (contains) {
 +              struct string_list_item *item;
                struct argv_array args;
  
                argv_array_init(&args);
                        argv_array_push(&args, "--always");
                if (!all) {
                        argv_array_push(&args, "--tags");
 -                      if (pattern)
 -                              argv_array_pushf(&args, "--refs=refs/tags/%s", pattern);
 +                      for_each_string_list_item(item, &patterns)
 +                              argv_array_pushf(&args, "--refs=refs/tags/%s", item->string);
 +                      for_each_string_list_item(item, &exclude_patterns)
 +                              argv_array_pushf(&args, "--exclude=refs/tags/%s", item->string);
                }
                if (argc)
                        argv_array_pushv(&args, argv);
diff --combined builtin/diff-tree.c
index 8ce00480cd484e10b0b46d931c934fcd3ba7b71e,06ac997976d44a72671278b1d8ce01dd66e560a9..326f88b6576d6c02e9a0bcecb5a0b402bb257372
@@@ -7,46 -7,44 +7,44 @@@
  
  static struct rev_info log_tree_opt;
  
- static int diff_tree_commit_sha1(const unsigned char *sha1)
+ static int diff_tree_commit_sha1(const struct object_id *oid)
  {
-       struct commit *commit = lookup_commit_reference(sha1);
+       struct commit *commit = lookup_commit_reference(oid->hash);
        if (!commit)
                return -1;
        return log_tree_commit(&log_tree_opt, commit);
  }
  
  /* Diff one or more commits. */
- static int stdin_diff_commit(struct commit *commit, char *line, int len)
+ static int stdin_diff_commit(struct commit *commit, const char *p)
  {
-       unsigned char sha1[20];
-       if (isspace(line[40]) && !get_sha1_hex(line+41, sha1)) {
-               /* Graft the fake parents locally to the commit */
-               int pos = 41;
-               struct commit_list **pptr;
-               /* Free the real parent list */
-               free_commit_list(commit->parents);
-               commit->parents = NULL;
-               pptr = &(commit->parents);
-               while (line[pos] && !get_sha1_hex(line + pos, sha1)) {
-                       struct commit *parent = lookup_commit(sha1);
-                       if (parent) {
-                               pptr = &commit_list_insert(parent, pptr)->next;
-                       }
-                       pos += 41;
+       struct object_id oid;
+       struct commit_list **pptr = NULL;
+       /* Graft the fake parents locally to the commit */
+       while (isspace(*p++) && !parse_oid_hex(p, &oid, &p)) {
+               struct commit *parent = lookup_commit(oid.hash);
+               if (!pptr) {
+                       /* Free the real parent list */
+                       free_commit_list(commit->parents);
+                       commit->parents = NULL;
+                       pptr = &(commit->parents);
+               }
+               if (parent) {
+                       pptr = &commit_list_insert(parent, pptr)->next;
                }
        }
        return log_tree_commit(&log_tree_opt, commit);
  }
  
  /* Diff two trees. */
- static int stdin_diff_trees(struct tree *tree1, char *line, int len)
+ static int stdin_diff_trees(struct tree *tree1, const char *p)
  {
-       unsigned char sha1[20];
+       struct object_id oid;
        struct tree *tree2;
-       if (len != 82 || !isspace(line[40]) || get_sha1_hex(line + 41, sha1))
+       if (!isspace(*p++) || parse_oid_hex(p, &oid, &p) || *p)
                return error("Need exactly two trees, separated by a space");
-       tree2 = lookup_tree(sha1);
+       tree2 = lookup_tree(oid.hash);
        if (!tree2 || parse_tree(tree2))
                return -1;
        printf("%s %s\n", oid_to_hex(&tree1->object.oid),
  static int diff_tree_stdin(char *line)
  {
        int len = strlen(line);
-       unsigned char sha1[20];
+       struct object_id oid;
        struct object *obj;
+       const char *p;
  
        if (!len || line[len-1] != '\n')
                return -1;
        line[len-1] = 0;
-       if (get_sha1_hex(line, sha1))
+       if (parse_oid_hex(line, &oid, &p))
                return -1;
-       obj = parse_object(sha1);
+       obj = parse_object(oid.hash);
        if (!obj)
                return -1;
        if (obj->type == OBJ_COMMIT)
-               return stdin_diff_commit((struct commit *)obj, line, len);
+               return stdin_diff_commit((struct commit *)obj, p);
        if (obj->type == OBJ_TREE)
-               return stdin_diff_trees((struct tree *)obj, line, len);
+               return stdin_diff_trees((struct tree *)obj, p);
        error("Object %s is a %s, not a commit or tree",
-             sha1_to_hex(sha1), typename(obj->type));
+             oid_to_hex(&oid), typename(obj->type));
        return -1;
  }
  
@@@ -141,13 -140,15 +140,13 @@@ int cmd_diff_tree(int argc, const char 
                break;
        case 1:
                tree1 = opt->pending.objects[0].item;
-               diff_tree_commit_sha1(tree1->oid.hash);
+               diff_tree_commit_sha1(&tree1->oid);
                break;
        case 2:
                tree1 = opt->pending.objects[0].item;
                tree2 = opt->pending.objects[1].item;
                if (tree2->flags & UNINTERESTING) {
 -                      struct object *tmp = tree2;
 -                      tree2 = tree1;
 -                      tree1 = tmp;
 +                      SWAP(tree2, tree1);
                }
                diff_tree_sha1(tree1->oid.hash,
                               tree2->oid.hash,
                        opt->diffopt.setup |= (DIFF_SETUP_USE_SIZE_CACHE |
                                               DIFF_SETUP_USE_CACHE);
                while (fgets(line, sizeof(line), stdin)) {
-                       unsigned char sha1[20];
+                       struct object_id oid;
  
-                       if (get_sha1_hex(line, sha1)) {
+                       if (get_oid_hex(line, &oid)) {
                                fputs(line, stdout);
                                fflush(stdout);
                        }
diff --combined builtin/grep.c
index 9304c33e75051baa65ee02d1daa03c361e25339e,0393b0fdc47467ddf10acdd830b910b58a1accd7..837836fb3e3bf9cdeb25edf8789428aaf813f49d
@@@ -294,17 -294,17 +294,17 @@@ static int grep_cmd_config(const char *
        return st;
  }
  
- static void *lock_and_read_sha1_file(const unsigned char *sha1, enum object_type *type, unsigned long *size)
+ static void *lock_and_read_oid_file(const struct object_id *oid, enum object_type *type, unsigned long *size)
  {
        void *data;
  
        grep_read_lock();
-       data = read_sha1_file(sha1, type, size);
+       data = read_sha1_file(oid->hash, type, size);
        grep_read_unlock();
        return data;
  }
  
- static int grep_sha1(struct grep_opt *opt, const unsigned char *sha1,
+ static int grep_oid(struct grep_opt *opt, const struct object_id *oid,
                     const char *filename, int tree_name_len,
                     const char *path)
  {
  
  #ifndef NO_PTHREADS
        if (num_threads) {
-               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+               add_work(opt, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                return 0;
        } else
                struct grep_source gs;
                int hit;
  
-               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, sha1);
+               grep_source_init(&gs, GREP_SOURCE_SHA1, pathbuf.buf, path, oid);
                strbuf_release(&pathbuf);
                hit = grep_source(opt, &gs);
  
@@@ -690,7 -690,7 +690,7 @@@ static int grep_cache(struct grep_opt *
                            ce_skip_worktree(ce)) {
                                if (ce_stage(ce) || ce_intent_to_add(ce))
                                        continue;
-                               hit |= grep_sha1(opt, ce->oid.hash, ce->name,
+                               hit |= grep_oid(opt, &ce->oid, ce->name,
                                                 0, ce->name);
                        } else {
                                hit |= grep_file(opt, ce->name);
@@@ -750,7 -750,7 +750,7 @@@ static int grep_tree(struct grep_opt *o
                strbuf_add(base, entry.path, te_len);
  
                if (S_ISREG(entry.mode)) {
-                       hit |= grep_sha1(opt, entry.oid->hash, base->buf, tn_len,
+                       hit |= grep_oid(opt, entry.oid, base->buf, tn_len,
                                         check_attr ? base->buf + tn_len : NULL);
                } else if (S_ISDIR(entry.mode)) {
                        enum object_type type;
                        void *data;
                        unsigned long size;
  
-                       data = lock_and_read_sha1_file(entry.oid->hash, &type, &size);
+                       data = lock_and_read_oid_file(entry.oid, &type, &size);
                        if (!data)
                                die(_("unable to read tree (%s)"),
                                    oid_to_hex(entry.oid));
@@@ -787,7 -787,7 +787,7 @@@ static int grep_object(struct grep_opt 
                       struct object *obj, const char *name, const char *path)
  {
        if (obj->type == OBJ_BLOB)
-               return grep_sha1(opt, obj->oid.hash, name, 0, path);
+               return grep_oid(opt, &obj->oid, name, 0, path);
        if (obj->type == OBJ_COMMIT || obj->type == OBJ_TREE) {
                struct tree_desc tree;
                void *data;
@@@ -967,7 -967,6 +967,7 @@@ int cmd_grep(int argc, const char **arg
        int dummy;
        int use_index = 1;
        int pattern_type_arg = GREP_PATTERN_TYPE_UNSPECIFIED;
 +      int allow_revs;
  
        struct option options[] = {
                OPT_BOOL(0, "cached", &cached,
  
        compile_grep_patterns(&opt);
  
 -      /* Check revs and then paths */
 +      /*
 +       * We have to find "--" in a separate pass, because its presence
 +       * influences how we will parse arguments that come before it.
 +       */
 +      for (i = 0; i < argc; i++) {
 +              if (!strcmp(argv[i], "--")) {
 +                      seen_dashdash = 1;
 +                      break;
 +              }
 +      }
 +
 +      /*
 +       * Resolve any rev arguments. If we have a dashdash, then everything up
 +       * to it must resolve as a rev. If not, then we stop at the first
 +       * non-rev and assume everything else is a path.
 +       */
 +      allow_revs = use_index && !untracked;
        for (i = 0; i < argc; i++) {
                const char *arg = argv[i];
-               unsigned char sha1[20];
+               struct object_id oid;
                struct object_context oc;
 -              /* Is it a rev? */
 -              if (!get_sha1_with_context(arg, 0, oid.hash, &oc)) {
 -                      struct object *object = parse_object_or_die(oid.hash, arg);
 -                      if (!seen_dashdash)
 -                              verify_non_filename(prefix, arg);
 -                      add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
 -                      continue;
 -              }
 +              struct object *object;
 +
                if (!strcmp(arg, "--")) {
                        i++;
 -                      seen_dashdash = 1;
 +                      break;
                }
 -              break;
 +
 +              if (!allow_revs) {
 +                      if (seen_dashdash)
 +                              die(_("--no-index or --untracked cannot be used with revs"));
 +                      break;
 +              }
 +
-               if (get_sha1_with_context(arg, 0, sha1, &oc)) {
++              if (get_sha1_with_context(arg, 0, oid.hash, &oc)) {
 +                      if (seen_dashdash)
 +                              die(_("unable to resolve revision: %s"), arg);
 +                      break;
 +              }
 +
-               object = parse_object_or_die(sha1, arg);
++              object = parse_object_or_die(oid.hash, arg);
 +              if (!seen_dashdash)
 +                      verify_non_filename(prefix, arg);
 +              add_object_array_with_path(object, arg, &list, oc.mode, oc.path);
        }
  
 +      /*
 +       * Anything left over is presumed to be a path. But in the non-dashdash
 +       * "do what I mean" case, we verify and complain when that isn't true.
 +       */
 +      if (!seen_dashdash) {
 +              int j;
 +              for (j = i; j < argc; j++)
 +                      verify_filename(prefix, argv[j], j == i && allow_revs);
 +      }
 +
 +      parse_pathspec(&pathspec, 0,
 +                     PATHSPEC_PREFER_CWD |
 +                     (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
 +                     prefix, argv + i);
 +      pathspec.max_depth = opt.max_depth;
 +      pathspec.recursive = 1;
 +
  #ifndef NO_PTHREADS
        if (list.nr || cached || show_in_pager)
                num_threads = 0;
        }
  #endif
  
 -      /* The rest are paths */
 -      if (!seen_dashdash) {
 -              int j;
 -              for (j = i; j < argc; j++)
 -                      verify_filename(prefix, argv[j], j == i);
 -      }
 -
 -      parse_pathspec(&pathspec, 0,
 -                     PATHSPEC_PREFER_CWD |
 -                     (opt.max_depth != -1 ? PATHSPEC_MAXDEPTH_VALID : 0),
 -                     prefix, argv + i);
 -      pathspec.max_depth = opt.max_depth;
 -      pathspec.recursive = 1;
 -
        if (recurse_submodules) {
                gitmodules_config();
                compile_submodule_options(&opt, &pathspec, cached, untracked,
  
        if (!use_index || untracked) {
                int use_exclude = (opt_exclude < 0) ? use_index : !!opt_exclude;
 -              if (list.nr)
 -                      die(_("--no-index or --untracked cannot be used with revs."));
                hit = grep_directory(&opt, &pathspec, use_exclude, use_index);
        } else if (0 <= opt_exclude) {
                die(_("--[no-]exclude-standard cannot be used for tracked contents."));
diff --combined builtin/merge.c
index 848a29855611d22bf19f3740a577a2b312294257,099cfab447672612892f3c2297d2667f6f79b7b1..7554b8d4127ada8261d1f913c6215e93ca42ef7d
@@@ -244,7 -244,7 +244,7 @@@ static void drop_save(void
        unlink(git_path_merge_mode());
  }
  
- static int save_state(unsigned char *stash)
+ static int save_state(struct object_id *stash)
  {
        int len;
        struct child_process cp = CHILD_PROCESS_INIT;
        else if (!len)          /* no changes */
                return -1;
        strbuf_setlen(&buffer, buffer.len-1);
-       if (get_sha1(buffer.buf, stash))
+       if (get_oid(buffer.buf, stash))
                die(_("not a valid object: %s"), buffer.buf);
        return 0;
  }
@@@ -305,18 -305,18 +305,18 @@@ static void reset_hard(unsigned const c
                die(_("read-tree failed"));
  }
  
- static void restore_state(const unsigned char *head,
-                         const unsigned char *stash)
+ static void restore_state(const struct object_id *head,
+                         const struct object_id *stash)
  {
        struct strbuf sb = STRBUF_INIT;
        const char *args[] = { "stash", "apply", NULL, NULL };
  
-       if (is_null_sha1(stash))
+       if (is_null_oid(stash))
                return;
  
-       reset_hard(head, 1);
+       reset_hard(head->hash, 1);
  
-       args[2] = sha1_to_hex(stash);
+       args[2] = oid_to_hex(stash);
  
        /*
         * It is OK to ignore error here, for example when there was
@@@ -376,10 -376,10 +376,10 @@@ static void squash_message(struct commi
  
  static void finish(struct commit *head_commit,
                   struct commit_list *remoteheads,
-                  const unsigned char *new_head, const char *msg)
+                  const struct object_id *new_head, const char *msg)
  {
        struct strbuf reflog_message = STRBUF_INIT;
-       const unsigned char *head = head_commit->object.oid.hash;
+       const struct object_id *head = &head_commit->object.oid;
  
        if (!msg)
                strbuf_addstr(&reflog_message, getenv("GIT_REFLOG_ACTION"));
                else {
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD",
-                               new_head, head, 0,
+                               new_head->hash, head->hash, 0,
                                UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
                        DIFF_FORMAT_SUMMARY | DIFF_FORMAT_DIFFSTAT;
                opts.detect_rename = DIFF_DETECT_RENAME;
                diff_setup_done(&opts);
-               diff_tree_sha1(head, new_head, "", &opts);
+               diff_tree_sha1(head->hash, new_head->hash, "", &opts);
                diffcore_std(&opts);
                diff_flush(&opts);
        }
  static void merge_name(const char *remote, struct strbuf *msg)
  {
        struct commit *remote_head;
-       unsigned char branch_head[20];
+       struct object_id branch_head;
        struct strbuf buf = STRBUF_INIT;
        struct strbuf bname = STRBUF_INIT;
        const char *ptr;
        char *found_ref;
        int len, early;
  
 -      strbuf_branchname(&bname, remote);
 +      strbuf_branchname(&bname, remote, 0);
        remote = bname.buf;
  
-       memset(branch_head, 0, sizeof(branch_head));
+       oidclr(&branch_head);
        remote_head = get_merge_parent(remote);
        if (!remote_head)
                die(_("'%s' does not point to a commit"), remote);
  
-       if (dwim_ref(remote, strlen(remote), branch_head, &found_ref) > 0) {
+       if (dwim_ref(remote, strlen(remote), branch_head.hash, &found_ref) > 0) {
                if (starts_with(found_ref, "refs/heads/")) {
                        strbuf_addf(msg, "%s\t\tbranch '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/tags/")) {
                        strbuf_addf(msg, "%s\t\ttag '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
                if (starts_with(found_ref, "refs/remotes/")) {
                        strbuf_addf(msg, "%s\t\tremote-tracking branch '%s' of .\n",
-                                   sha1_to_hex(branch_head), remote);
+                                   oid_to_hex(&branch_head), remote);
                        goto cleanup;
                }
        }
@@@ -590,8 -590,8 +590,8 @@@ static int git_merge_config(const char 
        return git_diff_ui_config(k, v, cb);
  }
  
- static int read_tree_trivial(unsigned char *common, unsigned char *head,
-                            unsigned char *one)
+ static int read_tree_trivial(struct object_id *common, struct object_id *head,
+                            struct object_id *one)
  {
        int i, nr_trees = 0;
        struct tree *trees[MAX_UNPACK_TREES];
        opts.verbose_update = 1;
        opts.trivial_merges_only = 1;
        opts.merge = 1;
-       trees[nr_trees] = parse_tree_indirect(common);
+       trees[nr_trees] = parse_tree_indirect(common->hash);
        if (!trees[nr_trees++])
                return -1;
-       trees[nr_trees] = parse_tree_indirect(head);
+       trees[nr_trees] = parse_tree_indirect(head->hash);
        if (!trees[nr_trees++])
                return -1;
-       trees[nr_trees] = parse_tree_indirect(one);
+       trees[nr_trees] = parse_tree_indirect(one->hash);
        if (!trees[nr_trees++])
                return -1;
        opts.fn = threeway_merge;
        return 0;
  }
  
- static void write_tree_trivial(unsigned char *sha1)
+ static void write_tree_trivial(struct object_id *oid)
  {
-       if (write_cache_as_tree(sha1, 0, NULL))
+       if (write_cache_as_tree(oid->hash, 0, NULL))
                die(_("git write-tree failed to write a tree"));
  }
  
@@@ -781,7 -781,7 +781,7 @@@ static void prepare_to_commit(struct co
  
  static int merge_trivial(struct commit *head, struct commit_list *remoteheads)
  {
-       unsigned char result_tree[20], result_commit[20];
+       struct object_id result_tree, result_commit;
        struct commit_list *parents, **pptr = &parents;
        static struct lock_file lock;
  
                return error(_("Unable to write index."));
        rollback_lock_file(&lock);
  
-       write_tree_trivial(result_tree);
+       write_tree_trivial(&result_tree);
        printf(_("Wonderful.\n"));
        pptr = commit_list_append(head, pptr);
        pptr = commit_list_append(remoteheads->item, pptr);
        prepare_to_commit(remoteheads);
-       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
-                       result_commit, NULL, sign_commit))
+       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree.hash, parents,
+                       result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
-       finish(head, remoteheads, result_commit, "In-index merge");
+       finish(head, remoteheads, &result_commit, "In-index merge");
        drop_save();
        return 0;
  }
@@@ -809,12 -809,12 +809,12 @@@ static int finish_automerge(struct comm
                            int head_subsumed,
                            struct commit_list *common,
                            struct commit_list *remoteheads,
-                           unsigned char *result_tree,
+                           struct object_id *result_tree,
                            const char *wt_strategy)
  {
        struct commit_list *parents = NULL;
        struct strbuf buf = STRBUF_INIT;
-       unsigned char result_commit[20];
+       struct object_id result_commit;
  
        free_commit_list(common);
        parents = remoteheads;
                commit_list_insert(head, &parents);
        strbuf_addch(&merge_msg, '\n');
        prepare_to_commit(remoteheads);
-       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree, parents,
-                       result_commit, NULL, sign_commit))
+       if (commit_tree(merge_msg.buf, merge_msg.len, result_tree->hash, parents,
+                       result_commit.hash, NULL, sign_commit))
                die(_("failed to write commit object"));
        strbuf_addf(&buf, "Merge made by the '%s' strategy.", wt_strategy);
-       finish(head, remoteheads, result_commit, buf.buf);
+       finish(head, remoteheads, &result_commit, buf.buf);
        strbuf_release(&buf);
        drop_save();
        return 0;
@@@ -854,18 -854,18 +854,18 @@@ static int suggest_conflicts(void
  }
  
  static struct commit *is_old_style_invocation(int argc, const char **argv,
-                                             const unsigned char *head)
+                                             const struct object_id *head)
  {
        struct commit *second_token = NULL;
        if (argc > 2) {
-               unsigned char second_sha1[20];
+               struct object_id second_oid;
  
-               if (get_sha1(argv[1], second_sha1))
+               if (get_oid(argv[1], &second_oid))
                        return NULL;
-               second_token = lookup_commit_reference_gently(second_sha1, 0);
+               second_token = lookup_commit_reference_gently(second_oid.hash, 0);
                if (!second_token)
                        die(_("'%s' is not a commit"), argv[1]);
-               if (hashcmp(second_token->object.oid.hash, head))
+               if (oidcmp(&second_token->object.oid, head))
                        return NULL;
        }
        return second_token;
@@@ -1038,7 -1038,7 +1038,7 @@@ static void handle_fetch_head(struct co
                die_errno(_("could not close '%s'"), filename);
  
        for (pos = 0; pos < merge_names->len; pos = npos) {
-               unsigned char sha1[20];
+               struct object_id oid;
                char *ptr;
                struct commit *commit;
  
                else
                        npos = merge_names->len;
  
-               if (npos - pos < 40 + 2 ||
-                   get_sha1_hex(merge_names->buf + pos, sha1))
+               if (npos - pos < GIT_SHA1_HEXSZ + 2 ||
+                   get_oid_hex(merge_names->buf + pos, &oid))
                        commit = NULL; /* bad */
-               else if (memcmp(merge_names->buf + pos + 40, "\t\t", 2))
+               else if (memcmp(merge_names->buf + pos + GIT_SHA1_HEXSZ, "\t\t", 2))
                        continue; /* not-for-merge */
                else {
-                       char saved = merge_names->buf[pos + 40];
-                       merge_names->buf[pos + 40] = '\0';
+                       char saved = merge_names->buf[pos + GIT_SHA1_HEXSZ];
+                       merge_names->buf[pos + GIT_SHA1_HEXSZ] = '\0';
                        commit = get_merge_parent(merge_names->buf + pos);
-                       merge_names->buf[pos + 40] = saved;
+                       merge_names->buf[pos + GIT_SHA1_HEXSZ] = saved;
                }
                if (!commit) {
                        if (ptr)
@@@ -1117,9 -1117,7 +1117,7 @@@ static struct commit_list *collect_pare
  
  int cmd_merge(int argc, const char **argv, const char *prefix)
  {
-       unsigned char result_tree[20];
-       unsigned char stash[20];
-       unsigned char head_sha1[20];
+       struct object_id result_tree, stash, head_oid;
        struct commit *head_commit;
        struct strbuf buf = STRBUF_INIT;
        const char *head_arg;
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, NULL);
+       branch = branch_to_free = resolve_refdup("HEAD", 0, head_oid.hash, NULL);
        if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
-       if (!branch || is_null_sha1(head_sha1))
+       if (!branch || is_null_oid(&head_oid))
                head_commit = NULL;
        else
-               head_commit = lookup_commit_or_die(head_sha1, "HEAD");
+               head_commit = lookup_commit_or_die(head_oid.hash, "HEAD");
  
        init_diff_ui_defaults();
        git_config(git_merge_config, NULL);
                 * to forbid "git merge" into a branch yet to be born.
                 * We do the same for "git pull".
                 */
-               unsigned char *remote_head_sha1;
+               struct object_id *remote_head_oid;
                if (squash)
                        die(_("Squash commit into empty head not supported yet"));
                if (fast_forward == FF_NO)
                        die(_("%s - not something we can merge"), argv[0]);
                if (remoteheads->next)
                        die(_("Can merge only exactly one commit into empty head"));
-               remote_head_sha1 = remoteheads->item->object.oid.hash;
-               read_empty(remote_head_sha1, 0);
-               update_ref("initial pull", "HEAD", remote_head_sha1,
+               remote_head_oid = &remoteheads->item->object.oid;
+               read_empty(remote_head_oid->hash, 0);
+               update_ref("initial pull", "HEAD", remote_head_oid->hash,
                           NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        }
         * additional safety measure to check for it.
         */
        if (!have_message &&
-           is_old_style_invocation(argc, argv, head_commit->object.oid.hash)) {
+           is_old_style_invocation(argc, argv, &head_commit->object.oid)) {
                warning("old-style 'git merge <msg> HEAD <commit>' is deprecated.");
                strbuf_addstr(&merge_msg, argv[0]);
                head_arg = argv[1];
                        goto done;
                }
  
-               finish(head_commit, remoteheads, commit->object.oid.hash, msg.buf);
+               finish(head_commit, remoteheads, &commit->object.oid, msg.buf);
                drop_save();
                goto done;
        } else if (!remoteheads->next && common->next)
                        /* See if it is really trivial. */
                        git_committer_info(IDENT_STRICT);
                        printf(_("Trying really trivial in-index merge...\n"));
-                       if (!read_tree_trivial(common->item->object.oid.hash,
-                                              head_commit->object.oid.hash,
-                                              remoteheads->item->object.oid.hash)) {
+                       if (!read_tree_trivial(&common->item->object.oid,
+                                              &head_commit->object.oid,
+                                              &remoteheads->item->object.oid)) {
                                ret = merge_trivial(head_commit, remoteheads);
                                goto done;
                        }
            /*
             * Stash away the local changes so that we can try more than one.
             */
-           save_state(stash))
-               hashclr(stash);
+           save_state(&stash))
+               oidclr(&stash);
  
        for (i = 0; i < use_strategies_nr; i++) {
                int ret;
                if (i) {
                        printf(_("Rewinding the tree to pristine...\n"));
-                       restore_state(head_commit->object.oid.hash, stash);
+                       restore_state(&head_commit->object.oid, &stash);
                }
                if (use_strategies_nr != 1)
                        printf(_("Trying merge strategy %s...\n"),
                }
  
                /* Automerge succeeded. */
-               write_tree_trivial(result_tree);
+               write_tree_trivial(&result_tree);
                automerge_was_ok = 1;
                break;
        }
        if (automerge_was_ok) {
                ret = finish_automerge(head_commit, head_subsumed,
                                       common, remoteheads,
-                                      result_tree, wt_strategy);
+                                      &result_tree, wt_strategy);
                goto done;
        }
  
         * it up.
         */
        if (!best_strategy) {
-               restore_state(head_commit->object.oid.hash, stash);
+               restore_state(&head_commit->object.oid, &stash);
                if (use_strategies_nr > 1)
                        fprintf(stderr,
                                _("No merge strategy handled the merge.\n"));
                ; /* We already have its result in the working tree. */
        else {
                printf(_("Rewinding the tree to pristine...\n"));
-               restore_state(head_commit->object.oid.hash, stash);
+               restore_state(&head_commit->object.oid, &stash);
                printf(_("Using the %s to prepare resolving by hand.\n"),
                        best_strategy);
                try_merge_strategy(best_strategy, common, remoteheads,
diff --combined builtin/notes.c
index 4b492abd417e3520d7ab98725f359087e9fcddd7,8c569a49a0635e163c38351affd7e91bd5fd2135..0513f7455dccd15e6ec1c2c1e1d847e66af027a7
@@@ -681,9 -681,9 +681,9 @@@ static int merge_abort(struct notes_mer
         * notes_merge_abort() to remove .git/NOTES_MERGE_WORKTREE.
         */
  
 -      if (delete_ref("NOTES_MERGE_PARTIAL", NULL, 0))
 +      if (delete_ref(NULL, "NOTES_MERGE_PARTIAL", NULL, 0))
                ret += error(_("failed to delete ref NOTES_MERGE_PARTIAL"));
 -      if (delete_ref("NOTES_MERGE_REF", NULL, REF_NODEREF))
 +      if (delete_ref(NULL, "NOTES_MERGE_REF", NULL, REF_NODEREF))
                ret += error(_("failed to delete ref NOTES_MERGE_REF"));
        if (notes_merge_abort(o))
                ret += error(_("failed to remove 'git notes merge' worktree"));
  static int merge_commit(struct notes_merge_options *o)
  {
        struct strbuf msg = STRBUF_INIT;
-       unsigned char sha1[20], parent_sha1[20];
+       struct object_id oid, parent_oid;
        struct notes_tree *t;
        struct commit *partial;
        struct pretty_print_context pretty_ctx;
         * and target notes ref from .git/NOTES_MERGE_REF.
         */
  
-       if (get_sha1("NOTES_MERGE_PARTIAL", sha1))
+       if (get_oid("NOTES_MERGE_PARTIAL", &oid))
                die(_("failed to read ref NOTES_MERGE_PARTIAL"));
-       else if (!(partial = lookup_commit_reference(sha1)))
+       else if (!(partial = lookup_commit_reference(oid.hash)))
                die(_("could not find commit from NOTES_MERGE_PARTIAL."));
        else if (parse_commit(partial))
                die(_("could not parse commit from NOTES_MERGE_PARTIAL."));
  
        if (partial->parents)
-               hashcpy(parent_sha1, partial->parents->item->object.oid.hash);
+               oidcpy(&parent_oid, &partial->parents->item->object.oid);
        else
-               hashclr(parent_sha1);
+               oidclr(&parent_oid);
  
        t = xcalloc(1, sizeof(struct notes_tree));
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
  
        o->local_ref = local_ref_to_free =
-               resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL);
+               resolve_refdup("NOTES_MERGE_REF", 0, oid.hash, NULL);
        if (!o->local_ref)
                die(_("failed to resolve NOTES_MERGE_REF"));
  
-       if (notes_merge_commit(o, t, partial, sha1))
+       if (notes_merge_commit(o, t, partial, oid.hash))
                die(_("failed to finalize notes merge"));
  
        /* Reuse existing commit message in reflog message */
        format_commit_message(partial, "%s", &msg, &pretty_ctx);
        strbuf_trim(&msg);
        strbuf_insert(&msg, 0, "notes: ", 7);
-       update_ref(msg.buf, o->local_ref, sha1,
-                  is_null_sha1(parent_sha1) ? NULL : parent_sha1,
+       update_ref(msg.buf, o->local_ref, oid.hash,
+                  is_null_oid(&parent_oid) ? NULL : parent_oid.hash,
                   0, UPDATE_REFS_DIE_ON_ERR);
  
        free_notes(t);
diff --combined builtin/pack-objects.c
index f294dcffa90aa3d8c916e448a4de905fa6363d26,6e7c3a6575d345689dc8dbc18a962a9fd5a26f99..16517f26375b1a0ebe58b628b7741c7f5e77bc08
@@@ -894,15 -894,24 +894,15 @@@ static void write_pack_file(void
                        written, nr_result);
  }
  
 -static void setup_delta_attr_check(struct git_attr_check *check)
 -{
 -      static struct git_attr *attr_delta;
 -
 -      if (!attr_delta)
 -              attr_delta = git_attr("delta");
 -
 -      check[0].attr = attr_delta;
 -}
 -
  static int no_try_delta(const char *path)
  {
 -      struct git_attr_check check[1];
 +      static struct attr_check *check;
  
 -      setup_delta_attr_check(check);
 -      if (git_check_attr(path, ARRAY_SIZE(check), check))
 +      if (!check)
 +              check = attr_check_initl("delta", NULL);
 +      if (git_check_attr(path, check))
                return 0;
 -      if (ATTR_FALSE(check->value))
 +      if (ATTR_FALSE(check->items[0].value))
                return 1;
        return 0;
  }
@@@ -1530,8 -1539,6 +1530,8 @@@ static int pack_offset_sort(const void 
   *   2. Updating our size/type to the non-delta representation. These were
   *      either not recorded initially (size) or overwritten with the delta type
   *      (type) when check_object() decided to reuse the delta.
 + *
 + *   3. Resetting our delta depth, as we are now a base object.
   */
  static void drop_reused_delta(struct object_entry *entry)
  {
                        p = &(*p)->delta_sibling;
        }
        entry->delta = NULL;
 +      entry->depth = 0;
  
        oi.sizep = &entry->size;
        oi.typep = &entry->type;
   * Follow the chain of deltas from this entry onward, throwing away any links
   * that cause us to hit a cycle (as determined by the DFS state flags in
   * the entries).
 + *
 + * We also detect too-long reused chains that would violate our --depth
 + * limit.
   */
  static void break_delta_chains(struct object_entry *entry)
  {
 -      /* If it's not a delta, it can't be part of a cycle. */
 -      if (!entry->delta) {
 -              entry->dfs_state = DFS_DONE;
 -              return;
 -      }
 +      /*
 +       * The actual depth of each object we will write is stored as an int,
 +       * as it cannot exceed our int "depth" limit. But before we break
 +       * changes based no that limit, we may potentially go as deep as the
 +       * number of objects, which is elsewhere bounded to a uint32_t.
 +       */
 +      uint32_t total_depth;
 +      struct object_entry *cur, *next;
 +
 +      for (cur = entry, total_depth = 0;
 +           cur;
 +           cur = cur->delta, total_depth++) {
 +              if (cur->dfs_state == DFS_DONE) {
 +                      /*
 +                       * We've already seen this object and know it isn't
 +                       * part of a cycle. We do need to append its depth
 +                       * to our count.
 +                       */
 +                      total_depth += cur->depth;
 +                      break;
 +              }
 +
 +              /*
 +               * We break cycles before looping, so an ACTIVE state (or any
 +               * other cruft which made its way into the state variable)
 +               * is a bug.
 +               */
 +              if (cur->dfs_state != DFS_NONE)
 +                      die("BUG: confusing delta dfs state in first pass: %d",
 +                          cur->dfs_state);
  
 -      switch (entry->dfs_state) {
 -      case DFS_NONE:
                /*
 -               * This is the first time we've seen the object. We mark it as
 -               * part of the active potential cycle and recurse.
 +               * Now we know this is the first time we've seen the object. If
 +               * it's not a delta, we're done traversing, but we'll mark it
 +               * done to save time on future traversals.
                 */
 -              entry->dfs_state = DFS_ACTIVE;
 -              break_delta_chains(entry->delta);
 -              entry->dfs_state = DFS_DONE;
 -              break;
 +              if (!cur->delta) {
 +                      cur->dfs_state = DFS_DONE;
 +                      break;
 +              }
  
 -      case DFS_DONE:
 -              /* object already examined, and not part of a cycle */
 -              break;
 +              /*
 +               * Mark ourselves as active and see if the next step causes
 +               * us to cycle to another active object. It's important to do
 +               * this _before_ we loop, because it impacts where we make the
 +               * cut, and thus how our total_depth counter works.
 +               * E.g., We may see a partial loop like:
 +               *
 +               *   A -> B -> C -> D -> B
 +               *
 +               * Cutting B->C breaks the cycle. But now the depth of A is
 +               * only 1, and our total_depth counter is at 3. The size of the
 +               * error is always one less than the size of the cycle we
 +               * broke. Commits C and D were "lost" from A's chain.
 +               *
 +               * If we instead cut D->B, then the depth of A is correct at 3.
 +               * We keep all commits in the chain that we examined.
 +               */
 +              cur->dfs_state = DFS_ACTIVE;
 +              if (cur->delta->dfs_state == DFS_ACTIVE) {
 +                      drop_reused_delta(cur);
 +                      cur->dfs_state = DFS_DONE;
 +                      break;
 +              }
 +      }
 +
 +      /*
 +       * And now that we've gone all the way to the bottom of the chain, we
 +       * need to clear the active flags and set the depth fields as
 +       * appropriate. Unlike the loop above, which can quit when it drops a
 +       * delta, we need to keep going to look for more depth cuts. So we need
 +       * an extra "next" pointer to keep going after we reset cur->delta.
 +       */
 +      for (cur = entry; cur; cur = next) {
 +              next = cur->delta;
 +
 +              /*
 +               * We should have a chain of zero or more ACTIVE states down to
 +               * a final DONE. We can quit after the DONE, because either it
 +               * has no bases, or we've already handled them in a previous
 +               * call.
 +               */
 +              if (cur->dfs_state == DFS_DONE)
 +                      break;
 +              else if (cur->dfs_state != DFS_ACTIVE)
 +                      die("BUG: confusing delta dfs state in second pass: %d",
 +                          cur->dfs_state);
  
 -      case DFS_ACTIVE:
                /*
 -               * We found a cycle that needs broken. It would be correct to
 -               * break any link in the chain, but it's convenient to
 -               * break this one.
 +               * If the total_depth is more than depth, then we need to snip
 +               * the chain into two or more smaller chains that don't exceed
 +               * the maximum depth. Most of the resulting chains will contain
 +               * (depth + 1) entries (i.e., depth deltas plus one base), and
 +               * the last chain (i.e., the one containing entry) will contain
 +               * whatever entries are left over, namely
 +               * (total_depth % (depth + 1)) of them.
 +               *
 +               * Since we are iterating towards decreasing depth, we need to
 +               * decrement total_depth as we go, and we need to write to the
 +               * entry what its final depth will be after all of the
 +               * snipping. Since we're snipping into chains of length (depth
 +               * + 1) entries, the final depth of an entry will be its
 +               * original depth modulo (depth + 1). Any time we encounter an
 +               * entry whose final depth is supposed to be zero, we snip it
 +               * from its delta base, thereby making it so.
                 */
 -              drop_reused_delta(entry);
 -              entry->dfs_state = DFS_DONE;
 -              break;
 +              cur->depth = (total_depth--) % (depth + 1);
 +              if (!cur->depth)
 +                      drop_reused_delta(cur);
 +
 +              cur->dfs_state = DFS_DONE;
        }
  }
  
@@@ -2612,17 -2534,17 +2612,17 @@@ static void add_objects_in_unpacked_pac
        free(in_pack.array);
  }
  
- static int add_loose_object(const unsigned char *sha1, const char *path,
+ static int add_loose_object(const struct object_id *oid, const char *path,
                            void *data)
  {
-       enum object_type type = sha1_object_info(sha1, NULL);
+       enum object_type type = sha1_object_info(oid->hash, NULL);
  
        if (type < 0) {
                warning("loose object at %s could not be examined", path);
                return 0;
        }
  
-       add_object_entry(sha1, type, "", 0);
+       add_object_entry(oid->hash, type, "", 0);
        return 0;
  }
  
diff --combined builtin/receive-pack.c
index f2c6953a39f6c1369725494cfc36b108cdb097a6,7966f4f4df012d53283cbeca036733c9cd54b6a0..83492af05f2bfd8ca1ec796b7ae8dccc84f0f31b
@@@ -21,7 -21,6 +21,7 @@@
  #include "sigchain.h"
  #include "fsck.h"
  #include "tmp-objdir.h"
 +#include "oidset.h"
  
  static const char * const receive_pack_usage[] = {
        N_("git receive-pack <git-dir>"),
@@@ -251,9 -250,8 +251,9 @@@ static void show_ref(const char *path, 
  }
  
  static int show_ref_cb(const char *path_full, const struct object_id *oid,
 -                     int flag, void *unused)
 +                     int flag, void *data)
  {
 +      struct oidset *seen = data;
        const char *path = strip_namespace(path_full);
  
        if (ref_is_hidden(path, path_full))
        /*
         * Advertise refs outside our current namespace as ".have"
         * refs, so that the client can use them to minimize data
 -       * transfer but will otherwise ignore them. This happens to
 -       * cover ".have" that are thrown in by add_one_alternate_ref()
 -       * to mark histories that are complete in our alternates as
 -       * well.
 +       * transfer but will otherwise ignore them.
         */
 -      if (!path)
 +      if (!path) {
 +              if (oidset_insert(seen, oid))
 +                      return 0;
                path = ".have";
 +      } else {
 +              oidset_insert(seen, oid);
 +      }
        show_ref(path, oid->hash);
        return 0;
  }
  
 -static int show_one_alternate_sha1(const unsigned char sha1[20], void *unused)
 +static void show_one_alternate_ref(const char *refname,
 +                                 const struct object_id *oid,
 +                                 void *data)
  {
 -      show_ref(".have", sha1);
 -      return 0;
 -}
 +      struct oidset *seen = data;
  
 -static void collect_one_alternate_ref(const struct ref *ref, void *data)
 -{
 -      struct sha1_array *sa = data;
 -      sha1_array_append(sa, ref->old_oid.hash);
 +      if (oidset_insert(seen, oid))
 +              return;
 +
 +      show_ref(".have", oid->hash);
  }
  
  static void write_head_info(void)
  {
 -      struct sha1_array sa = SHA1_ARRAY_INIT;
 +      static struct oidset seen = OIDSET_INIT;
  
 -      for_each_alternate_ref(collect_one_alternate_ref, &sa);
 -      sha1_array_for_each_unique(&sa, show_one_alternate_sha1, NULL);
 -      sha1_array_clear(&sa);
 -      for_each_ref(show_ref_cb, NULL);
 +      for_each_ref(show_ref_cb, &seen);
 +      for_each_alternate_ref(show_one_alternate_ref, &seen);
 +      oidset_clear(&seen);
        if (!sent_capabilities)
                show_ref("capabilities^{}", null_sha1);
  
@@@ -1417,7 -1414,7 +1417,7 @@@ static void execute_commands(struct com
  {
        struct check_connected_options opt = CHECK_CONNECTED_INIT;
        struct command *cmd;
-       unsigned char sha1[20];
+       struct object_id oid;
        struct iterate_data data;
        struct async muxer;
        int err_fd = 0;
        check_aliased_updates(commands);
  
        free(head_name_to_free);
-       head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
+       head_name = head_name_to_free = resolve_refdup("HEAD", 0, oid.hash, NULL);
  
        if (use_atomic)
                execute_commands_atomic(commands, si);
@@@ -1667,11 -1664,8 +1667,11 @@@ static const char *unpack(int err_fd, s
        }
  
        tmp_objdir = tmp_objdir_create();
 -      if (!tmp_objdir)
 +      if (!tmp_objdir) {
 +              if (err_fd > 0)
 +                      close(err_fd);
                return "unable to create temporary object directory";
 +      }
        child.env = tmp_objdir_env(tmp_objdir);
  
        /*
diff --combined builtin/replace.c
index 226d0f95236f299a1506beefb5babf1cf52574f0,f7716a547293a58bfb96773acb60692ac86e0efb..f83e7b8fc1758aa837b4dd48708a3d6c31bcd226
@@@ -88,78 -88,78 +88,78 @@@ static int list_replace_refs(const cha
  }
  
  typedef int (*each_replace_name_fn)(const char *name, const char *ref,
-                                   const unsigned char *sha1);
+                                   const struct object_id *oid);
  
  static int for_each_replace_name(const char **argv, each_replace_name_fn fn)
  {
        const char **p, *full_hex;
        char ref[PATH_MAX];
        int had_error = 0;
-       unsigned char sha1[20];
+       struct object_id oid;
  
        for (p = argv; *p; p++) {
-               if (get_sha1(*p, sha1)) {
+               if (get_oid(*p, &oid)) {
                        error("Failed to resolve '%s' as a valid ref.", *p);
                        had_error = 1;
                        continue;
                }
-               full_hex = sha1_to_hex(sha1);
+               full_hex = oid_to_hex(&oid);
                snprintf(ref, sizeof(ref), "%s%s", git_replace_ref_base, full_hex);
                /* read_ref() may reuse the buffer */
                full_hex = ref + strlen(git_replace_ref_base);
-               if (read_ref(ref, sha1)) {
+               if (read_ref(ref, oid.hash)) {
                        error("replace ref '%s' not found.", full_hex);
                        had_error = 1;
                        continue;
                }
-               if (fn(full_hex, ref, sha1))
+               if (fn(full_hex, ref, &oid))
                        had_error = 1;
        }
        return had_error;
  }
  
  static int delete_replace_ref(const char *name, const char *ref,
-                             const unsigned char *sha1)
+                             const struct object_id *oid)
  {
-       if (delete_ref(NULL, ref, sha1, 0))
 -      if (delete_ref(ref, oid->hash, 0))
++      if (delete_ref(NULL, ref, oid->hash, 0))
                return 1;
        printf("Deleted replace ref '%s'\n", name);
        return 0;
  }
  
- static void check_ref_valid(unsigned char object[20],
-                           unsigned char prev[20],
+ static void check_ref_valid(struct object_id *object,
+                           struct object_id *prev,
                            char *ref,
                            int ref_size,
                            int force)
  {
        if (snprintf(ref, ref_size,
                     "%s%s", git_replace_ref_base,
-                    sha1_to_hex(object)) > ref_size - 1)
+                    oid_to_hex(object)) > ref_size - 1)
                die("replace ref name too long: %.*s...", 50, ref);
        if (check_refname_format(ref, 0))
                die("'%s' is not a valid ref name.", ref);
  
-       if (read_ref(ref, prev))
-               hashclr(prev);
+       if (read_ref(ref, prev->hash))
+               oidclr(prev);
        else if (!force)
                die("replace ref '%s' already exists", ref);
  }
  
- static int replace_object_sha1(const char *object_ref,
-                              unsigned char object[20],
+ static int replace_object_oid(const char *object_ref,
+                              struct object_id *object,
                               const char *replace_ref,
-                              unsigned char repl[20],
+                              struct object_id *repl,
                               int force)
  {
-       unsigned char prev[20];
+       struct object_id prev;
        enum object_type obj_type, repl_type;
        char ref[PATH_MAX];
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
  
-       obj_type = sha1_object_info(object, NULL);
-       repl_type = sha1_object_info(repl, NULL);
+       obj_type = sha1_object_info(object->hash, NULL);
+       repl_type = sha1_object_info(repl->hash, NULL);
        if (!force && obj_type != repl_type)
                die("Objects must be of the same type.\n"
                    "'%s' points to a replaced object of type '%s'\n"
                    object_ref, typename(obj_type),
                    replace_ref, typename(repl_type));
  
-       check_ref_valid(object, prev, ref, sizeof(ref), force);
+       check_ref_valid(object, &prev, ref, sizeof(ref), force);
  
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_update(transaction, ref, repl, prev,
+           ref_transaction_update(transaction, ref, repl->hash, prev.hash,
                                   0, NULL, &err) ||
            ref_transaction_commit(transaction, &err))
                die("%s", err.buf);
  
  static int replace_object(const char *object_ref, const char *replace_ref, int force)
  {
-       unsigned char object[20], repl[20];
+       struct object_id object, repl;
  
-       if (get_sha1(object_ref, object))
+       if (get_oid(object_ref, &object))
                die("Failed to resolve '%s' as a valid ref.", object_ref);
-       if (get_sha1(replace_ref, repl))
+       if (get_oid(replace_ref, &repl))
                die("Failed to resolve '%s' as a valid ref.", replace_ref);
  
-       return replace_object_sha1(object_ref, object, replace_ref, repl, force);
+       return replace_object_oid(object_ref, &object, replace_ref, &repl, force);
  }
  
  /*
   * If "raw" is true, then the object's raw contents are printed according to
   * "type". Otherwise, we pretty-print the contents for human editing.
   */
- static void export_object(const unsigned char *sha1, enum object_type type,
+ static void export_object(const struct object_id *oid, enum object_type type,
                          int raw, const char *filename)
  {
        struct child_process cmd = CHILD_PROCESS_INIT;
                argv_array_push(&cmd.args, typename(type));
        else
                argv_array_push(&cmd.args, "-p");
-       argv_array_push(&cmd.args, sha1_to_hex(sha1));
+       argv_array_push(&cmd.args, oid_to_hex(oid));
        cmd.git_cmd = 1;
        cmd.out = fd;
  
   * interpreting it as "type", and writing the result to the object database.
   * The sha1 of the written object is returned via sha1.
   */
- static void import_object(unsigned char *sha1, enum object_type type,
+ static void import_object(struct object_id *oid, enum object_type type,
                          int raw, const char *filename)
  {
        int fd;
  
                if (finish_command(&cmd))
                        die("mktree reported failure");
-               if (get_sha1_hex(result.buf, sha1) < 0)
+               if (get_oid_hex(result.buf, oid) < 0)
                        die("mktree did not return an object name");
  
                strbuf_release(&result);
  
                if (fstat(fd, &st) < 0)
                        die_errno("unable to fstat %s", filename);
-               if (index_fd(sha1, fd, &st, type, NULL, flags) < 0)
+               if (index_fd(oid->hash, fd, &st, type, NULL, flags) < 0)
                        die("unable to write object to database");
                /* index_fd close()s fd for us */
        }
@@@ -279,29 -279,29 +279,29 @@@ static int edit_and_replace(const char 
  {
        char *tmpfile = git_pathdup("REPLACE_EDITOBJ");
        enum object_type type;
-       unsigned char old[20], new[20], prev[20];
+       struct object_id old, new, prev;
        char ref[PATH_MAX];
  
-       if (get_sha1(object_ref, old) < 0)
+       if (get_oid(object_ref, &old) < 0)
                die("Not a valid object name: '%s'", object_ref);
  
-       type = sha1_object_info(old, NULL);
+       type = sha1_object_info(old.hash, NULL);
        if (type < 0)
-               die("unable to get object type for %s", sha1_to_hex(old));
+               die("unable to get object type for %s", oid_to_hex(&old));
  
-       check_ref_valid(old, prev, ref, sizeof(ref), force);
+       check_ref_valid(&old, &prev, ref, sizeof(ref), force);
  
-       export_object(old, type, raw, tmpfile);
+       export_object(&old, type, raw, tmpfile);
        if (launch_editor(tmpfile, NULL, NULL) < 0)
                die("editing object file failed");
-       import_object(new, type, raw, tmpfile);
+       import_object(&new, type, raw, tmpfile);
  
        free(tmpfile);
  
-       if (!hashcmp(old, new))
-               return error("new object is the same as the old one: '%s'", sha1_to_hex(old));
+       if (!oidcmp(&old, &new))
+               return error("new object is the same as the old one: '%s'", oid_to_hex(&old));
  
-       return replace_object_sha1(object_ref, old, "replacement", new, force);
+       return replace_object_oid(object_ref, &old, "replacement", &new, force);
  }
  
  static void replace_parents(struct strbuf *buf, int argc, const char **argv)
  
        /* find existing parents */
        parent_start = buf->buf;
-       parent_start += 46; /* "tree " + "hex sha1" + "\n" */
+       parent_start += GIT_SHA1_HEXSZ + 6; /* "tree " + "hex sha1" + "\n" */
        parent_end = parent_start;
  
        while (starts_with(parent_end, "parent "))
  
        /* prepare new parents */
        for (i = 0; i < argc; i++) {
-               unsigned char sha1[20];
-               if (get_sha1(argv[i], sha1) < 0)
+               struct object_id oid;
+               if (get_oid(argv[i], &oid) < 0)
                        die(_("Not a valid object name: '%s'"), argv[i]);
-               lookup_commit_or_die(sha1, argv[i]);
-               strbuf_addf(&new_parents, "parent %s\n", sha1_to_hex(sha1));
+               lookup_commit_or_die(oid.hash, argv[i]);
+               strbuf_addf(&new_parents, "parent %s\n", oid_to_hex(&oid));
        }
  
        /* replace existing parents with new ones */
@@@ -345,12 -345,12 +345,12 @@@ static void check_one_mergetag(struct c
  {
        struct check_mergetag_data *mergetag_data = (struct check_mergetag_data *)data;
        const char *ref = mergetag_data->argv[0];
-       unsigned char tag_sha1[20];
+       struct object_id tag_oid;
        struct tag *tag;
        int i;
  
-       hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_sha1);
-       tag = lookup_tag(tag_sha1);
+       hash_sha1_file(extra->value, extra->len, typename(OBJ_TAG), tag_oid.hash);
+       tag = lookup_tag(tag_oid.hash);
        if (!tag)
                die(_("bad mergetag in commit '%s'"), ref);
        if (parse_tag_buffer(tag, extra->value, extra->len))
        }
  
        die(_("original commit '%s' contains mergetag '%s' that is discarded; "
-             "use --edit instead of --graft"), ref, sha1_to_hex(tag_sha1));
+             "use --edit instead of --graft"), ref, oid_to_hex(&tag_oid));
  }
  
  static void check_mergetags(struct commit *commit, int argc, const char **argv)
  
  static int create_graft(int argc, const char **argv, int force)
  {
-       unsigned char old[20], new[20];
+       struct object_id old, new;
        const char *old_ref = argv[0];
        struct commit *commit;
        struct strbuf buf = STRBUF_INIT;
        const char *buffer;
        unsigned long size;
  
-       if (get_sha1(old_ref, old) < 0)
+       if (get_oid(old_ref, &old) < 0)
                die(_("Not a valid object name: '%s'"), old_ref);
-       commit = lookup_commit_or_die(old, old_ref);
+       commit = lookup_commit_or_die(old.hash, old_ref);
  
        buffer = get_commit_buffer(commit, &size);
        strbuf_add(&buf, buffer, size);
  
        check_mergetags(commit, argc, argv);
  
-       if (write_sha1_file(buf.buf, buf.len, commit_type, new))
+       if (write_sha1_file(buf.buf, buf.len, commit_type, new.hash))
                die(_("could not write replacement commit for: '%s'"), old_ref);
  
        strbuf_release(&buf);
  
-       if (!hashcmp(old, new))
-               return error("new commit is the same as the old one: '%s'", sha1_to_hex(old));
+       if (!oidcmp(&old, &new))
+               return error("new commit is the same as the old one: '%s'", oid_to_hex(&old));
  
-       return replace_object_sha1(old_ref, old, "replacement", new, force);
+       return replace_object_oid(old_ref, &old, "replacement", &new, force);
  }
  
  int cmd_replace(int argc, const char **argv, const char *prefix)
diff --combined cache.h
index d3265cca1239099f342ffaca3d28fde3d735fdc2,724e905f7a4de5fd60a6698c2cea541d98af50b8..dc261f40dda7c6eb274463a64767671a6c1cae4a
+++ b/cache.h
@@@ -1045,6 -1045,9 +1045,6 @@@ static inline int is_empty_tree_oid(con
        return !hashcmp(oid->hash, EMPTY_TREE_SHA1_BIN);
  }
  
 -
 -int git_mkstemp(char *path, size_t n, const char *template);
 -
  /* set default permissions by passing mode arguments to open(2) */
  int git_mkstemps_mode(char *pattern, int suffix_len, int mode);
  int git_mkstemp_mode(char *pattern, int mode);
@@@ -1069,9 -1072,8 +1069,9 @@@ int adjust_shared_perm(const char *path
  
  /*
   * Create the directory containing the named path, using care to be
 - * somewhat safe against races.  Return one of the scld_error values
 - * to indicate success/failure.
 + * somewhat safe against races. Return one of the scld_error values to
 + * indicate success/failure. On error, set errno to describe the
 + * problem.
   *
   * SCLD_VANISHED indicates that one of the ancestor directories of the
   * path existed at one point during the function call and then
@@@ -1095,49 -1097,6 +1095,49 @@@ enum scld_error 
  enum scld_error safe_create_leading_directories(char *path);
  enum scld_error safe_create_leading_directories_const(const char *path);
  
 +/*
 + * Callback function for raceproof_create_file(). This function is
 + * expected to do something that makes dirname(path) permanent despite
 + * the fact that other processes might be cleaning up empty
 + * directories at the same time. Usually it will create a file named
 + * path, but alternatively it could create another file in that
 + * directory, or even chdir() into that directory. The function should
 + * return 0 if the action was completed successfully. On error, it
 + * should return a nonzero result and set errno.
 + * raceproof_create_file() treats two errno values specially:
 + *
 + * - ENOENT -- dirname(path) does not exist. In this case,
 + *             raceproof_create_file() tries creating dirname(path)
 + *             (and any parent directories, if necessary) and calls
 + *             the function again.
 + *
 + * - EISDIR -- the file already exists and is a directory. In this
 + *             case, raceproof_create_file() removes the directory if
 + *             it is empty (and recursively any empty directories that
 + *             it contains) and calls the function again.
 + *
 + * Any other errno causes raceproof_create_file() to fail with the
 + * callback's return value and errno.
 + *
 + * Obviously, this function should be OK with being called again if it
 + * fails with ENOENT or EISDIR. In other scenarios it will not be
 + * called again.
 + */
 +typedef int create_file_fn(const char *path, void *cb);
 +
 +/*
 + * Create a file in dirname(path) by calling fn, creating leading
 + * directories if necessary. Retry a few times in case we are racing
 + * with another process that is trying to clean up the directory that
 + * contains path. See the documentation for create_file_fn for more
 + * details.
 + *
 + * Return the value and set the errno that resulted from the most
 + * recent call of fn. fn is always called at least once, and will be
 + * called more than once if it returns ENOENT or EISDIR.
 + */
 +int raceproof_create_file(const char *path, create_file_fn fn, void *cb);
 +
  int mkdir_in_gitdir(const char *path);
  extern char *expand_user_path(const char *path);
  const char *enter_repo(const char *path, int strict);
@@@ -1150,7 -1109,7 +1150,7 @@@ char *strbuf_realpath(struct strbuf *re
                      int die_on_error);
  const char *real_path(const char *path);
  const char *real_path_if_valid(const char *path);
 -char *real_pathdup(const char *path);
 +char *real_pathdup(const char *path, int die_on_error);
  const char *absolute_path(const char *path);
  char *absolute_pathdup(const char *path);
  const char *remove_leading_path(const char *in, const char *prefix);
@@@ -1270,9 -1229,6 +1270,9 @@@ extern int has_pack_index(const unsigne
  
  extern void assert_sha1_type(const unsigned char *sha1, enum object_type expect);
  
 +/* Helper to check and "touch" a file */
 +extern int check_and_freshen_file(const char *fn, int freshen);
 +
  extern const signed char hexval_table[256];
  static inline unsigned int hexval(unsigned char c)
  {
@@@ -1363,37 -1319,16 +1363,46 @@@ extern char *oid_to_hex_r(char *out, co
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
  
 -extern int interpret_branch_name(const char *str, int len, struct strbuf *);
+ /*
+  * Parse a 40-character hexadecimal object ID starting from hex, updating the
+  * pointer specified by end when parsing stops.  The resulting object ID is
+  * stored in oid.  Returns 0 on success.  Parsing will stop on the first NUL or
+  * other invalid character.  end is only updated on success; otherwise, it is
+  * unmodified.
+  */
+ extern int parse_oid_hex(const char *hex, struct object_id *oid, const char **end);
 +/*
 + * This reads short-hand syntax that not only evaluates to a commit
 + * object name, but also can act as if the end user spelled the name
 + * of the branch from the command line.
 + *
 + * - "@{-N}" finds the name of the Nth previous branch we were on, and
 + *   places the name of the branch in the given buf and returns the
 + *   number of characters parsed if successful.
 + *
 + * - "<branch>@{upstream}" finds the name of the other ref that
 + *   <branch> is configured to merge with (missing <branch> defaults
 + *   to the current branch), and places the name of the branch in the
 + *   given buf and returns the number of characters parsed if
 + *   successful.
 + *
 + * If the input is not of the accepted format, it returns a negative
 + * number to signal an error.
 + *
 + * If the input was ok but there are not N branch switches in the
 + * reflog, it returns 0.
 + *
 + * If "allowed" is non-zero, it is a treated as a bitfield of allowable
 + * expansions: local branches ("refs/heads/"), remote branches
 + * ("refs/remotes/"), or "HEAD". If no "allowed" bits are set, any expansion is
 + * allowed, even ones to refs outside of those namespaces.
 + */
 +#define INTERPRET_BRANCH_LOCAL (1<<0)
 +#define INTERPRET_BRANCH_REMOTE (1<<1)
 +#define INTERPRET_BRANCH_HEAD (1<<2)
 +extern int interpret_branch_name(const char *str, int len, struct strbuf *,
 +                               unsigned allowed);
  extern int get_oid_mb(const char *str, struct object_id *oid);
  
  extern int validate_headref(const char *ref);
@@@ -1673,6 -1608,12 +1682,12 @@@ extern void check_pack_index_ptr(const 
   * error.
   */
  extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t n);
+ /*
+  * Like nth_packed_object_sha1, but write the data into the object specified by
+  * the the first argument.  Returns the first argument on success, and NULL on
+  * error.
+  */
+ extern const struct object_id *nth_packed_object_oid(struct object_id *, struct packed_git *, uint32_t n);
  
  /*
   * Return the offset of the nth object within the specified packfile.
@@@ -1714,7 -1655,7 +1729,7 @@@ extern int unpack_object_header(struct 
   * scratch buffer, but restored to its original contents before
   * the function returns.
   */
- typedef int each_loose_object_fn(const unsigned char *sha1,
+ typedef int each_loose_object_fn(const struct object_id *oid,
                                 const char *path,
                                 void *data);
  typedef int each_loose_cruft_fn(const char *basename,
@@@ -1740,7 -1681,7 +1755,7 @@@ int for_each_loose_file_in_objdir_buf(s
   * LOCAL_ONLY flag is set).
   */
  #define FOR_EACH_OBJECT_LOCAL_ONLY 0x1
- typedef int each_packed_object_fn(const unsigned char *sha1,
+ typedef int each_packed_object_fn(const struct object_id *oid,
                                  struct packed_git *pack,
                                  uint32_t pos,
                                  void *data);
@@@ -1893,11 -1834,8 +1908,11 @@@ extern int git_config_include(const cha
   *
   * (i.e., what gets handed to a config_fn_t). The caller provides the section;
   * we return -1 if it does not match, 0 otherwise. The subsection and key
 - * out-parameters are filled by the function (and subsection is NULL if it is
 + * out-parameters are filled by the function (and *subsection is NULL if it is
   * missing).
 + *
 + * If the subsection pointer-to-pointer passed in is NULL, returns 0 only if
 + * there is no subsection at all.
   */
  extern int parse_config_key(const char *var,
                            const char *section,
@@@ -1959,11 -1897,6 +1974,11 @@@ extern int git_config_get_bool_or_int(c
  extern int git_config_get_maybe_bool(const char *key, int *dest);
  extern int git_config_get_pathname(const char *key, const char **dest);
  extern int git_config_get_untracked_cache(void);
 +extern int git_config_get_split_index(void);
 +extern int git_config_get_max_percent_split_change(void);
 +
 +/* This dies if the configured or default date is in the future */
 +extern int git_config_get_expiry(const char *key, const char **output);
  
  /*
   * This is a hack for test programs like test-dump-untracked-cache to
diff --combined ref-filter.c
index 1ec0fb8391ba6be716e76c688a0f9e2f0208adc2,f0de30e2efead63ad33815ece61a9ee6ca8a2b85..89798dc59ba75ca3bfc7b7207b41834ace22677b
  #include "git-compat-util.h"
  #include "version.h"
  #include "trailer.h"
 +#include "wt-status.h"
 +
 +static struct ref_msg {
 +      const char *gone;
 +      const char *ahead;
 +      const char *behind;
 +      const char *ahead_behind;
 +} msgs = {
 +       /* Untranslated plumbing messages: */
 +      "gone",
 +      "ahead %d",
 +      "behind %d",
 +      "ahead %d, behind %d"
 +};
 +
 +void setup_ref_filter_porcelain_msg(void)
 +{
 +      msgs.gone = _("gone");
 +      msgs.ahead = _("ahead %d");
 +      msgs.behind = _("behind %d");
 +      msgs.ahead_behind = _("ahead %d, behind %d");
 +}
  
  typedef enum { FIELD_STR, FIELD_ULONG, FIELD_TIME } cmp_type;
 +typedef enum { COMPARE_EQUAL, COMPARE_UNEQUAL, COMPARE_NONE } cmp_status;
  
  struct align {
        align_type position;
        unsigned int width;
  };
  
 +struct if_then_else {
 +      cmp_status cmp_status;
 +      const char *str;
 +      unsigned int then_atom_seen : 1,
 +              else_atom_seen : 1,
 +              condition_satisfied : 1;
 +};
 +
 +struct refname_atom {
 +      enum { R_NORMAL, R_SHORT, R_LSTRIP, R_RSTRIP } option;
 +      int lstrip, rstrip;
 +};
 +
  /*
   * An atom is a valid field atom listed below, possibly prefixed with
   * a "*" to denote deref_tag().
@@@ -74,24 -38,13 +74,24 @@@ static struct used_atom 
        union {
                char color[COLOR_MAXLEN];
                struct align align;
 -              enum { RR_NORMAL, RR_SHORTEN, RR_TRACK, RR_TRACKSHORT }
 -                      remote_ref;
 +              struct {
 +                      enum { RR_REF, RR_TRACK, RR_TRACKSHORT } option;
 +                      struct refname_atom refname;
 +                      unsigned int nobracket : 1;
 +              } remote_ref;
                struct {
                        enum { C_BARE, C_BODY, C_BODY_DEP, C_LINES, C_SIG, C_SUB, C_TRAILERS } option;
                        unsigned int nlines;
                } contents;
 -              enum { O_FULL, O_SHORT } objectname;
 +              struct {
 +                      cmp_status cmp_status;
 +                      const char *str;
 +              } if_then_else;
 +              struct {
 +                      enum { O_FULL, O_LENGTH, O_SHORT } option;
 +                      unsigned int length;
 +              } objectname;
 +              struct refname_atom refname;
        } u;
  } *used_atom;
  static int used_atom_cnt, need_tagged, need_symref;
@@@ -105,58 -58,18 +105,58 @@@ static void color_atom_parser(struct us
                die(_("unrecognized color: %%(color:%s)"), color_value);
  }
  
 -static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
 +static void refname_atom_parser_internal(struct refname_atom *atom,
 +                                       const char *arg, const char *name)
  {
        if (!arg)
 -              atom->u.remote_ref = RR_NORMAL;
 +              atom->option = R_NORMAL;
        else if (!strcmp(arg, "short"))
 -              atom->u.remote_ref = RR_SHORTEN;
 -      else if (!strcmp(arg, "track"))
 -              atom->u.remote_ref = RR_TRACK;
 -      else if (!strcmp(arg, "trackshort"))
 -              atom->u.remote_ref = RR_TRACKSHORT;
 -      else
 -              die(_("unrecognized format: %%(%s)"), atom->name);
 +              atom->option = R_SHORT;
 +      else if (skip_prefix(arg, "lstrip=", &arg) ||
 +               skip_prefix(arg, "strip=", &arg)) {
 +              atom->option = R_LSTRIP;
 +              if (strtol_i(arg, 10, &atom->lstrip))
 +                      die(_("Integer value expected refname:lstrip=%s"), arg);
 +      } else if (skip_prefix(arg, "rstrip=", &arg)) {
 +              atom->option = R_RSTRIP;
 +              if (strtol_i(arg, 10, &atom->rstrip))
 +                      die(_("Integer value expected refname:rstrip=%s"), arg);
 +      } else
 +              die(_("unrecognized %%(%s) argument: %s"), name, arg);
 +}
 +
 +static void remote_ref_atom_parser(struct used_atom *atom, const char *arg)
 +{
 +      struct string_list params = STRING_LIST_INIT_DUP;
 +      int i;
 +
 +      if (!arg) {
 +              atom->u.remote_ref.option = RR_REF;
 +              refname_atom_parser_internal(&atom->u.remote_ref.refname,
 +                                           arg, atom->name);
 +              return;
 +      }
 +
 +      atom->u.remote_ref.nobracket = 0;
 +      string_list_split(&params, arg, ',', -1);
 +
 +      for (i = 0; i < params.nr; i++) {
 +              const char *s = params.items[i].string;
 +
 +              if (!strcmp(s, "track"))
 +                      atom->u.remote_ref.option = RR_TRACK;
 +              else if (!strcmp(s, "trackshort"))
 +                      atom->u.remote_ref.option = RR_TRACKSHORT;
 +              else if (!strcmp(s, "nobracket"))
 +                      atom->u.remote_ref.nobracket = 1;
 +              else {
 +                      atom->u.remote_ref.option = RR_REF;
 +                      refname_atom_parser_internal(&atom->u.remote_ref.refname,
 +                                                   arg, atom->name);
 +              }
 +      }
 +
 +      string_list_clear(&params, 0);
  }
  
  static void body_atom_parser(struct used_atom *atom, const char *arg)
@@@ -203,25 -116,13 +203,25 @@@ static void contents_atom_parser(struc
  static void objectname_atom_parser(struct used_atom *atom, const char *arg)
  {
        if (!arg)
 -              atom->u.objectname = O_FULL;
 +              atom->u.objectname.option = O_FULL;
        else if (!strcmp(arg, "short"))
 -              atom->u.objectname = O_SHORT;
 -      else
 +              atom->u.objectname.option = O_SHORT;
 +      else if (skip_prefix(arg, "short=", &arg)) {
 +              atom->u.objectname.option = O_LENGTH;
 +              if (strtoul_ui(arg, 10, &atom->u.objectname.length) ||
 +                  atom->u.objectname.length == 0)
 +                      die(_("positive value expected objectname:short=%s"), arg);
 +              if (atom->u.objectname.length < MINIMUM_ABBREV)
 +                      atom->u.objectname.length = MINIMUM_ABBREV;
 +      } else
                die(_("unrecognized %%(objectname) argument: %s"), arg);
  }
  
 +static void refname_atom_parser(struct used_atom *atom, const char *arg)
 +{
 +      return refname_atom_parser_internal(&atom->u.refname, arg, atom->name);
 +}
 +
  static align_type parse_align_position(const char *s)
  {
        if (!strcmp(s, "right"))
@@@ -272,27 -173,12 +272,27 @@@ static void align_atom_parser(struct us
        string_list_clear(&params, 0);
  }
  
 +static void if_atom_parser(struct used_atom *atom, const char *arg)
 +{
 +      if (!arg) {
 +              atom->u.if_then_else.cmp_status = COMPARE_NONE;
 +              return;
 +      } else if (skip_prefix(arg, "equals=", &atom->u.if_then_else.str)) {
 +              atom->u.if_then_else.cmp_status = COMPARE_EQUAL;
 +      } else if (skip_prefix(arg, "notequals=", &atom->u.if_then_else.str)) {
 +              atom->u.if_then_else.cmp_status = COMPARE_UNEQUAL;
 +      } else {
 +              die(_("unrecognized %%(if) argument: %s"), arg);
 +      }
 +}
 +
 +
  static struct {
        const char *name;
        cmp_type cmp_type;
        void (*parser)(struct used_atom *atom, const char *arg);
  } valid_atom[] = {
 -      { "refname" },
 +      { "refname" , FIELD_STR, refname_atom_parser },
        { "objecttype" },
        { "objectsize", FIELD_ULONG },
        { "objectname", FIELD_STR, objectname_atom_parser },
        { "contents", FIELD_STR, contents_atom_parser },
        { "upstream", FIELD_STR, remote_ref_atom_parser },
        { "push", FIELD_STR, remote_ref_atom_parser },
 -      { "symref" },
 +      { "symref", FIELD_STR, refname_atom_parser },
        { "flag" },
        { "HEAD" },
        { "color", FIELD_STR, color_atom_parser },
        { "align", FIELD_STR, align_atom_parser },
        { "end" },
 +      { "if", FIELD_STR, if_atom_parser },
 +      { "then" },
 +      { "else" },
  };
  
  #define REF_FORMATTING_STATE_INIT  { 0, NULL }
  struct ref_formatting_stack {
        struct ref_formatting_stack *prev;
        struct strbuf output;
 -      void (*at_end)(struct ref_formatting_stack *stack);
 +      void (*at_end)(struct ref_formatting_stack **stack);
        void *at_end_data;
  };
  
@@@ -349,9 -232,11 +349,9 @@@ struct ref_formatting_state 
  
  struct atom_value {
        const char *s;
 -      union {
 -              struct align align;
 -      } u;
        void (*handler)(struct atom_value *atomv, struct ref_formatting_state *state);
        unsigned long ul; /* used for sorting when not FIELD_STR */
 +      struct used_atom *atom;
  };
  
  /*
@@@ -408,7 -293,7 +408,7 @@@ int parse_ref_filter_atom(const char *a
                valid_atom[i].parser(&used_atom[at], arg);
        if (*atom == '*')
                need_tagged = 1;
 -      if (!strcmp(used_atom[at].name, "symref"))
 +      if (!strcmp(valid_atom[i].name, "symref"))
                need_symref = 1;
        return at;
  }
@@@ -469,14 -354,13 +469,14 @@@ static void pop_stack_element(struct re
        *stack = prev;
  }
  
 -static void end_align_handler(struct ref_formatting_stack *stack)
 +static void end_align_handler(struct ref_formatting_stack **stack)
  {
 -      struct align *align = (struct align *)stack->at_end_data;
 +      struct ref_formatting_stack *cur = *stack;
 +      struct align *align = (struct align *)cur->at_end_data;
        struct strbuf s = STRBUF_INIT;
  
 -      strbuf_utf8_align(&s, align->position, align->width, stack->output.buf);
 -      strbuf_swap(&stack->output, &s);
 +      strbuf_utf8_align(&s, align->position, align->width, cur->output.buf);
 +      strbuf_swap(&cur->output, &s);
        strbuf_release(&s);
  }
  
@@@ -487,115 -371,7 +487,115 @@@ static void align_atom_handler(struct a
        push_stack_element(&state->stack);
        new = state->stack;
        new->at_end = end_align_handler;
 -      new->at_end_data = &atomv->u.align;
 +      new->at_end_data = &atomv->atom->u.align;
 +}
 +
 +static void if_then_else_handler(struct ref_formatting_stack **stack)
 +{
 +      struct ref_formatting_stack *cur = *stack;
 +      struct ref_formatting_stack *prev = cur->prev;
 +      struct if_then_else *if_then_else = (struct if_then_else *)cur->at_end_data;
 +
 +      if (!if_then_else->then_atom_seen)
 +              die(_("format: %%(if) atom used without a %%(then) atom"));
 +
 +      if (if_then_else->else_atom_seen) {
 +              /*
 +               * There is an %(else) atom: we need to drop one state from the
 +               * stack, either the %(else) branch if the condition is satisfied, or
 +               * the %(then) branch if it isn't.
 +               */
 +              if (if_then_else->condition_satisfied) {
 +                      strbuf_reset(&cur->output);
 +                      pop_stack_element(&cur);
 +              } else {
 +                      strbuf_swap(&cur->output, &prev->output);
 +                      strbuf_reset(&cur->output);
 +                      pop_stack_element(&cur);
 +              }
 +      } else if (!if_then_else->condition_satisfied) {
 +              /*
 +               * No %(else) atom: just drop the %(then) branch if the
 +               * condition is not satisfied.
 +               */
 +              strbuf_reset(&cur->output);
 +      }
 +
 +      *stack = cur;
 +      free(if_then_else);
 +}
 +
 +static void if_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 +{
 +      struct ref_formatting_stack *new;
 +      struct if_then_else *if_then_else = xcalloc(sizeof(struct if_then_else), 1);
 +
 +      if_then_else->str = atomv->atom->u.if_then_else.str;
 +      if_then_else->cmp_status = atomv->atom->u.if_then_else.cmp_status;
 +
 +      push_stack_element(&state->stack);
 +      new = state->stack;
 +      new->at_end = if_then_else_handler;
 +      new->at_end_data = if_then_else;
 +}
 +
 +static int is_empty(const char *s)
 +{
 +      while (*s != '\0') {
 +              if (!isspace(*s))
 +                      return 0;
 +              s++;
 +      }
 +      return 1;
 +}
 +
 +static void then_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 +{
 +      struct ref_formatting_stack *cur = state->stack;
 +      struct if_then_else *if_then_else = NULL;
 +
 +      if (cur->at_end == if_then_else_handler)
 +              if_then_else = (struct if_then_else *)cur->at_end_data;
 +      if (!if_then_else)
 +              die(_("format: %%(then) atom used without an %%(if) atom"));
 +      if (if_then_else->then_atom_seen)
 +              die(_("format: %%(then) atom used more than once"));
 +      if (if_then_else->else_atom_seen)
 +              die(_("format: %%(then) atom used after %%(else)"));
 +      if_then_else->then_atom_seen = 1;
 +      /*
 +       * If the 'equals' or 'notequals' attribute is used then
 +       * perform the required comparison. If not, only non-empty
 +       * strings satisfy the 'if' condition.
 +       */
 +      if (if_then_else->cmp_status == COMPARE_EQUAL) {
 +              if (!strcmp(if_then_else->str, cur->output.buf))
 +                      if_then_else->condition_satisfied = 1;
 +      } else if (if_then_else->cmp_status == COMPARE_UNEQUAL) {
 +              if (strcmp(if_then_else->str, cur->output.buf))
 +                      if_then_else->condition_satisfied = 1;
 +      } else if (cur->output.len && !is_empty(cur->output.buf))
 +              if_then_else->condition_satisfied = 1;
 +      strbuf_reset(&cur->output);
 +}
 +
 +static void else_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
 +{
 +      struct ref_formatting_stack *prev = state->stack;
 +      struct if_then_else *if_then_else = NULL;
 +
 +      if (prev->at_end == if_then_else_handler)
 +              if_then_else = (struct if_then_else *)prev->at_end_data;
 +      if (!if_then_else)
 +              die(_("format: %%(else) atom used without an %%(if) atom"));
 +      if (!if_then_else->then_atom_seen)
 +              die(_("format: %%(else) atom used without a %%(then) atom"));
 +      if (if_then_else->else_atom_seen)
 +              die(_("format: %%(else) atom used more than once"));
 +      if_then_else->else_atom_seen = 1;
 +      push_stack_element(&state->stack);
 +      state->stack->at_end_data = prev->at_end_data;
 +      state->stack->at_end = prev->at_end;
  }
  
  static void end_atom_handler(struct atom_value *atomv, struct ref_formatting_state *state)
  
        if (!current->at_end)
                die(_("format: %%(end) atom used without corresponding atom"));
 -      current->at_end(current);
 +      current->at_end(&state->stack);
 +
 +      /*  Stack may have been popped within at_end(), hence reset the current pointer */
 +      current = state->stack;
  
        /*
         * Perform quote formatting when the stack element is that of
         * a supporting atom. If nested then perform quote formatting
         * only on the topmost supporting atom.
         */
 -      if (!state->stack->prev->prev) {
 +      if (!current->prev->prev) {
                quote_formatting(&s, current->output.buf, state->quote_style);
                strbuf_swap(&current->output, &s);
        }
@@@ -692,15 -465,12 +692,15 @@@ static int grab_objectname(const char *
                           struct atom_value *v, struct used_atom *atom)
  {
        if (starts_with(name, "objectname")) {
 -              if (atom->u.objectname == O_SHORT) {
 +              if (atom->u.objectname.option == O_SHORT) {
                        v->s = xstrdup(find_unique_abbrev(sha1, DEFAULT_ABBREV));
                        return 1;
 -              } else if (atom->u.objectname == O_FULL) {
 +              } else if (atom->u.objectname.option == O_FULL) {
                        v->s = xstrdup(sha1_to_hex(sha1));
                        return 1;
 +              } else if (atom->u.objectname.option == O_LENGTH) {
 +                      v->s = xstrdup(find_unique_abbrev(sha1, atom->u.objectname.length));
 +                      return 1;
                } else
                        die("BUG: unknown %%(objectname) option");
        }
@@@ -1117,108 -887,50 +1117,108 @@@ static inline char *copy_advance(char *
        return dst;
  }
  
 -static const char *strip_ref_components(const char *refname, const char *nr_arg)
 +static const char *lstrip_ref_components(const char *refname, int len)
  {
 -      char *end;
 -      long nr = strtol(nr_arg, &end, 10);
 -      long remaining = nr;
 +      long remaining = len;
        const char *start = refname;
  
 -      if (nr < 1 || *end != '\0')
 -              die(_(":strip= requires a positive integer argument"));
 +      if (len < 0) {
 +              int i;
 +              const char *p = refname;
 +
 +              /* Find total no of '/' separated path-components */
 +              for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
 +                      ;
 +              /*
 +               * The number of components we need to strip is now
 +               * the total minus the components to be left (Plus one
 +               * because we count the number of '/', but the number
 +               * of components is one more than the no of '/').
 +               */
 +              remaining = i + len + 1;
 +      }
  
 -      while (remaining) {
 +      while (remaining > 0) {
                switch (*start++) {
                case '\0':
 -                      die(_("ref '%s' does not have %ld components to :strip"),
 -                          refname, nr);
 +                      return "";
                case '/':
                        remaining--;
                        break;
                }
        }
 +
        return start;
  }
  
 +static const char *rstrip_ref_components(const char *refname, int len)
 +{
 +      long remaining = len;
 +      char *start = xstrdup(refname);
 +
 +      if (len < 0) {
 +              int i;
 +              const char *p = refname;
 +
 +              /* Find total no of '/' separated path-components */
 +              for (i = 0; p[i]; p[i] == '/' ? i++ : *p++)
 +                      ;
 +              /*
 +               * The number of components we need to strip is now
 +               * the total minus the components to be left (Plus one
 +               * because we count the number of '/', but the number
 +               * of components is one more than the no of '/').
 +               */
 +              remaining = i + len + 1;
 +      }
 +
 +      while (remaining-- > 0) {
 +              char *p = strrchr(start, '/');
 +              if (p == NULL)
 +                      return "";
 +              else
 +                      p[0] = '\0';
 +      }
 +      return start;
 +}
 +
 +static const char *show_ref(struct refname_atom *atom, const char *refname)
 +{
 +      if (atom->option == R_SHORT)
 +              return shorten_unambiguous_ref(refname, warn_ambiguous_refs);
 +      else if (atom->option == R_LSTRIP)
 +              return lstrip_ref_components(refname, atom->lstrip);
 +      else if (atom->option == R_RSTRIP)
 +              return rstrip_ref_components(refname, atom->rstrip);
 +      else
 +              return refname;
 +}
 +
  static void fill_remote_ref_details(struct used_atom *atom, const char *refname,
                                    struct branch *branch, const char **s)
  {
        int num_ours, num_theirs;
 -      if (atom->u.remote_ref == RR_SHORTEN)
 -              *s = shorten_unambiguous_ref(refname, warn_ambiguous_refs);
 -      else if (atom->u.remote_ref == RR_TRACK) {
 +      if (atom->u.remote_ref.option == RR_REF)
 +              *s = show_ref(&atom->u.remote_ref.refname, refname);
 +      else if (atom->u.remote_ref.option == RR_TRACK) {
                if (stat_tracking_info(branch, &num_ours,
 -                                     &num_theirs, NULL))
 -                      return;
 -
 -              if (!num_ours && !num_theirs)
 +                                     &num_theirs, NULL)) {
 +                      *s = xstrdup(msgs.gone);
 +              } else if (!num_ours && !num_theirs)
                        *s = "";
                else if (!num_ours)
 -                      *s = xstrfmt("[behind %d]", num_theirs);
 +                      *s = xstrfmt(msgs.behind, num_theirs);
                else if (!num_theirs)
 -                      *s = xstrfmt("[ahead %d]", num_ours);
 +                      *s = xstrfmt(msgs.ahead, num_ours);
                else
 -                      *s = xstrfmt("[ahead %d, behind %d]",
 +                      *s = xstrfmt(msgs.ahead_behind,
                                     num_ours, num_theirs);
 -      } else if (atom->u.remote_ref == RR_TRACKSHORT) {
 +              if (!atom->u.remote_ref.nobracket && *s[0]) {
 +                      const char *to_free = *s;
 +                      *s = xstrfmt("[%s]", *s);
 +                      free((void *)to_free);
 +              }
 +      } else if (atom->u.remote_ref.option == RR_TRACKSHORT) {
                if (stat_tracking_info(branch, &num_ours,
                                       &num_theirs, NULL))
                        return;
                        *s = ">";
                else
                        *s = "<>";
 -      } else /* RR_NORMAL */
 -              *s = refname;
 +      } else
 +              die("BUG: unhandled RR_* enum");
 +}
 +
 +char *get_head_description(void)
 +{
 +      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);
 +}
 +
 +static const char *get_symref(struct used_atom *atom, struct ref_array_item *ref)
 +{
 +      if (!ref->symref)
 +              return "";
 +      else
 +              return show_ref(&atom->u.refname, ref->symref);
 +}
 +
 +static const char *get_refname(struct used_atom *atom, struct ref_array_item *ref)
 +{
 +      if (ref->kind & FILTER_REFS_DETACHED_HEAD)
 +              return get_head_description();
 +      return show_ref(&atom->u.refname, ref->refname);
  }
  
  /*
@@@ -1297,9 -961,9 +1297,9 @@@ static void populate_value(struct ref_a
        ref->value = xcalloc(used_atom_cnt, sizeof(struct atom_value));
  
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
-               unsigned char unused1[20];
+               struct object_id unused1;
                ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
-                                            unused1, NULL);
+                                            unused1.hash, NULL);
                if (!ref->symref)
                        ref->symref = "";
        }
                struct atom_value *v = &ref->value[i];
                int deref = 0;
                const char *refname;
 -              const char *formatp;
                struct branch *branch = NULL;
  
                v->handler = append_atom;
 +              v->atom = atom;
  
                if (*name == '*') {
                        deref = 1;
                }
  
                if (starts_with(name, "refname"))
 -                      refname = ref->refname;
 +                      refname = get_refname(atom, ref);
                else if (starts_with(name, "symref"))
 -                      refname = ref->symref ? ref->symref : "";
 +                      refname = get_symref(atom, ref);
                else if (starts_with(name, "upstream")) {
                        const char *branch_name;
                        /* only local branches may have an upstream */
                                v->s = " ";
                        continue;
                } else if (starts_with(name, "align")) {
 -                      v->u.align = atom->u.align;
                        v->handler = align_atom_handler;
                        continue;
                } else if (!strcmp(name, "end")) {
                        v->handler = end_atom_handler;
                        continue;
 +              } else if (starts_with(name, "if")) {
 +                      const char *s;
 +
 +                      if (skip_prefix(name, "if:", &s))
 +                              v->s = xstrdup(s);
 +                      v->handler = if_atom_handler;
 +                      continue;
 +              } else if (!strcmp(name, "then")) {
 +                      v->handler = then_atom_handler;
 +                      continue;
 +              } else if (!strcmp(name, "else")) {
 +                      v->handler = else_atom_handler;
 +                      continue;
                } else
                        continue;
  
 -              formatp = strchr(name, ':');
 -              if (formatp) {
 -                      const char *arg;
 -
 -                      formatp++;
 -                      if (!strcmp(formatp, "short"))
 -                              refname = shorten_unambiguous_ref(refname,
 -                                                    warn_ambiguous_refs);
 -                      else if (skip_prefix(formatp, "strip=", &arg))
 -                              refname = strip_ref_components(refname, arg);
 -                      else
 -                              die(_("unknown %.*s format %s"),
 -                                  (int)(formatp - name), name, formatp);
 -              }
 -
                if (!deref)
                        v->s = refname;
                else
@@@ -1968,10 -1635,10 +1968,10 @@@ static void append_literal(const char *
        }
  }
  
 -void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 +void format_ref_array_item(struct ref_array_item *info, const char *format,
 +                         int quote_style, struct strbuf *final_buf)
  {
        const char *cp, *sp, *ep;
 -      struct strbuf *final_buf;
        struct ref_formatting_state state = REF_FORMATTING_STATE_INIT;
  
        state.quote_style = quote_style;
        }
        if (state.stack->prev)
                die(_("format: %%(end) atom missing"));
 -      final_buf = &state.stack->output;
 -      fwrite(final_buf->buf, 1, final_buf->len, stdout);
 +      strbuf_addbuf(final_buf, &state.stack->output);
        pop_stack_element(&state.stack);
 +}
 +
 +void show_ref_array_item(struct ref_array_item *info, const char *format, int quote_style)
 +{
 +      struct strbuf final_buf = STRBUF_INIT;
 +
 +      format_ref_array_item(info, format, quote_style, &final_buf);
 +      fwrite(final_buf.buf, 1, final_buf.len, stdout);
 +      strbuf_release(&final_buf);
        putchar('\n');
  }
  
diff --combined refs.c
index b0d58948a5b5bb1a96c43f0363408aab070bd1a9,b900626d3f9f4151e7efe1c6e107e6ee3bf2abca..e7606716ddfff47b29f90e4d46cb2d59633cb455
--- 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"
@@@ -405,7 -404,7 +405,7 @@@ int refname_match(const char *abbrev_na
  static char *substitute_branch_name(const char **string, int *len)
  {
        struct strbuf buf = STRBUF_INIT;
 -      int ret = interpret_branch_name(*string, *len, &buf);
 +      int ret = interpret_branch_name(*string, *len, &buf, 0);
  
        if (ret == *len) {
                size_t size;
@@@ -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);
@@@ -675,7 -674,7 +675,7 @@@ struct read_ref_at_cb 
        int *cutoff_cnt;
  };
  
- static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
+ static int read_ref_at_ent(struct object_id *ooid, struct object_id *noid,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
  {
                 * hold the values for the previous record.
                 */
                if (!is_null_sha1(cb->osha1)) {
-                       hashcpy(cb->sha1, nsha1);
-                       if (hashcmp(cb->osha1, nsha1))
+                       hashcpy(cb->sha1, noid->hash);
+                       if (hashcmp(cb->osha1, noid->hash))
                                warning("Log for ref %s has gap after %s.",
                                        cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
-                       hashcpy(cb->sha1, nsha1);
-               else if (hashcmp(nsha1, cb->sha1))
+                       hashcpy(cb->sha1, noid->hash);
+               else if (hashcmp(noid->hash, cb->sha1))
                        warning("Log for ref %s unexpectedly ended on %s.",
                                cb->refname, show_date(cb->date, cb->tz,
                                                       DATE_MODE(RFC2822)));
-               hashcpy(cb->osha1, osha1);
-               hashcpy(cb->nsha1, nsha1);
+               hashcpy(cb->osha1, ooid->hash);
+               hashcpy(cb->nsha1, noid->hash);
                cb->found_it = 1;
                return 1;
        }
-       hashcpy(cb->osha1, osha1);
-       hashcpy(cb->nsha1, nsha1);
+       hashcpy(cb->osha1, ooid->hash);
+       hashcpy(cb->nsha1, noid->hash);
        if (cb->cnt > 0)
                cb->cnt--;
        return 0;
  }
  
- static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
+ static int read_ref_at_ent_oldest(struct object_id *ooid, struct object_id *noid,
                                  const char *email, unsigned long timestamp,
                                  int tz, const char *message, void *cb_data)
  {
                *cb->cutoff_tz = tz;
        if (cb->cutoff_cnt)
                *cb->cutoff_cnt = cb->reccnt;
-       hashcpy(cb->sha1, osha1);
+       hashcpy(cb->sha1, ooid->hash);
        if (is_null_sha1(cb->sha1))
-               hashcpy(cb->sha1, nsha1);
+               hashcpy(cb->sha1, noid->hash);
        /* We just want the first entry */
        return 1;
  }
@@@ -1035,10 -1034,10 +1035,10 @@@ static struct string_list *hide_refs
  
  int parse_hide_refs_config(const char *var, const char *value, const char *section)
  {
 +      const char *key;
        if (!strcmp("transfer.hiderefs", var) ||
 -          /* NEEDSWORK: use parse_config_key() once both are merged */
 -          (starts_with(var, section) && var[strlen(section)] == '.' &&
 -           !strcmp(var + strlen(section), ".hiderefs"))) {
 +          (!parse_config_key(var, section, NULL, NULL, &key) &&
 +           !strcmp(key, "hiderefs"))) {
                char *ref;
                int len;
  
@@@ -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.h
index e529f4c3a88a79488df4b610b65a1c9d9c18c4a0,a075117a2a94f79485dc8becd2df1e0118f732f5..3df0d45ebb6bb5900254edd40383d9de8e539a31
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
@@@ -276,8 -276,8 +276,8 @@@ int reflog_exists(const char *refname)
   * exists, regardless of its old value. It is an error for old_sha1 to
   * be NULL_SHA1. flags is passed through to ref_transaction_delete().
   */
 -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);
  
  /*
   * Delete the specified references. If there are any problems, emit
@@@ -292,7 -292,7 +292,7 @@@ int delete_reflog(const char *refname)
  
  /* iterate over reflog entries */
  typedef int each_reflog_ent_fn(
-               unsigned char *old_sha1, unsigned char *new_sha1,
+               struct object_id *old_oid, struct object_id *new_oid,
                const char *committer, unsigned long timestamp,
                int tz, const char *msg, void *cb_data);
  
@@@ -334,8 -334,7 +334,8 @@@ int create_symref(const char *refname, 
   * $GIT_DIR points to.
   * Return 0 if successful, non-zero otherwise.
   * */
 -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);
  
  enum action_on_err {
        UPDATE_REFS_MSG_ON_ERR,
diff --combined refs/files-backend.c
index b42df147c9c4335b0e47201f751e7e05204e4b01,fea20e99fe1d2f0b68d318eafc4c3b807722c232..50188e92f9fd90ca52d8257f3618678d632eb38a
@@@ -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)) {
 -              if (errno==EISDIR) {
 +          delete_ref(NULL, newrefname, NULL, REF_NODEREF)) {
 +              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);
@@@ -3102,16 -3113,17 +3102,17 @@@ static int files_delete_reflog(struct r
  
  static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
  {
-       unsigned char osha1[20], nsha1[20];
+       struct object_id ooid, noid;
        char *email_end, *message;
        unsigned long timestamp;
        int tz;
+       const char *p = sb->buf;
  
        /* old SP new SP name <email> SP time TAB msg LF */
-       if (sb->len < 83 || sb->buf[sb->len - 1] != '\n' ||
-           get_sha1_hex(sb->buf, osha1) || sb->buf[40] != ' ' ||
-           get_sha1_hex(sb->buf + 41, nsha1) || sb->buf[81] != ' ' ||
-           !(email_end = strchr(sb->buf + 82, '>')) ||
+       if (!sb->len || sb->buf[sb->len - 1] != '\n' ||
+           parse_oid_hex(p, &ooid, &p) || *p++ != ' ' ||
+           parse_oid_hex(p, &noid, &p) || *p++ != ' ' ||
+           !(email_end = strchr(p, '>')) ||
            email_end[1] != ' ' ||
            !(timestamp = strtoul(email_end + 2, &message, 10)) ||
            !message || message[0] != ' ' ||
                message += 6;
        else
                message += 7;
-       return fn(osha1, nsha1, sb->buf + 82, timestamp, tz, message, cb_data);
+       return fn(&ooid, &noid, p, timestamp, tz, message, cb_data);
  }
  
  static char *find_beginning_of_line(char *bob, char *scan)
@@@ -3549,7 -3561,7 +3550,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 -3779,9 +3768,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);
@@@ -3954,10 -3937,10 +3955,10 @@@ struct expire_reflog_cb 
        reflog_expiry_should_prune_fn *should_prune_fn;
        void *policy_cb;
        FILE *newlog;
-       unsigned char last_kept_sha1[20];
+       struct object_id last_kept_oid;
  };
  
- static int expire_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ static int expire_reflog_ent(struct object_id *ooid, struct object_id *noid,
                             const char *email, unsigned long timestamp, int tz,
                             const char *message, void *cb_data)
  {
        struct expire_reflog_policy_cb *policy_cb = cb->policy_cb;
  
        if (cb->flags & EXPIRE_REFLOGS_REWRITE)
-               osha1 = cb->last_kept_sha1;
+               ooid = &cb->last_kept_oid;
  
-       if ((*cb->should_prune_fn)(osha1, nsha1, email, timestamp, tz,
+       if ((*cb->should_prune_fn)(ooid->hash, noid->hash, email, timestamp, tz,
                                   message, policy_cb)) {
                if (!cb->newlog)
                        printf("would prune %s", message);
        } else {
                if (cb->newlog) {
                        fprintf(cb->newlog, "%s %s %s %lu %+05d\t%s",
-                               sha1_to_hex(osha1), sha1_to_hex(nsha1),
+                               oid_to_hex(ooid), oid_to_hex(noid),
                                email, timestamp, tz, message);
-                       hashcpy(cb->last_kept_sha1, nsha1);
+                       oidcpy(&cb->last_kept_oid, noid);
                }
                if (cb->flags & EXPIRE_REFLOGS_VERBOSE)
                        printf("keep %s", message);
@@@ -4065,14 -4048,14 +4066,14 @@@ static int files_reflog_expire(struct r
                 */
                int update = (flags & EXPIRE_REFLOGS_UPDATE_REF) &&
                        !(type & REF_ISSYMREF) &&
-                       !is_null_sha1(cb.last_kept_sha1);
+                       !is_null_oid(&cb.last_kept_oid);
  
                if (close_lock_file(&reflog_lock)) {
                        status |= error("couldn't write %s: %s", log_file,
                                        strerror(errno));
                } else if (update &&
                           (write_in_full(get_lock_file_fd(lock->lk),
-                               sha1_to_hex(cb.last_kept_sha1), 40) != 40 ||
+                               oid_to_hex(&cb.last_kept_oid), GIT_SHA1_HEXSZ) != GIT_SHA1_HEXSZ ||
                            write_str_in_full(get_lock_file_fd(lock->lk), "\n") != 1 ||
                            close_ref(lock) < 0)) {
                        status |= error("couldn't write %s",
diff --combined revision.c
index 771d079f6edca39f8fa0d28db9bf697a99deeea6,d9fe73318acde03f55cb0a1081bfcd4c87a7d6b2..7ff61ff5f73db07af522ac57d62ed9e699aedf72
@@@ -147,7 -147,7 +147,7 @@@ static void add_pending_object_with_pat
                revs->no_walk = 0;
        if (revs->reflog_info && obj->type == OBJ_COMMIT) {
                struct strbuf buf = STRBUF_INIT;
 -              int len = interpret_branch_name(name, 0, &buf);
 +              int len = interpret_branch_name(name, 0, &buf, 0);
                int st;
  
                if (0 < len && name[len] && buf.len)
@@@ -1196,11 -1196,11 +1196,11 @@@ static void handle_refs(const char *sub
        for_each(submodule, handle_one_ref, &cb);
  }
  
- static void handle_one_reflog_commit(unsigned char *sha1, void *cb_data)
+ static void handle_one_reflog_commit(struct object_id *oid, void *cb_data)
  {
        struct all_refs_cb *cb = cb_data;
-       if (!is_null_sha1(sha1)) {
-               struct object *o = parse_object(sha1);
+       if (!is_null_oid(oid)) {
+               struct object *o = parse_object(oid->hash);
                if (o) {
                        o->flags |= cb->all_flags;
                        /* ??? CMDLINEFLAGS ??? */
        }
  }
  
- static int handle_one_reflog_ent(unsigned char *osha1, unsigned char *nsha1,
+ static int handle_one_reflog_ent(struct object_id *ooid, struct object_id *noid,
                const char *email, unsigned long timestamp, int tz,
                const char *message, void *cb_data)
  {
-       handle_one_reflog_commit(osha1, cb_data);
-       handle_one_reflog_commit(nsha1, cb_data);
+       handle_one_reflog_commit(ooid, cb_data);
+       handle_one_reflog_commit(noid, cb_data);
        return 0;
  }
  
diff --combined sha1_file.c
index f1c83a92487818e40af3b0abd032d448d1838760,6b74c7d3bce8f8c7099f14dfc7ce3366e7017f6d..29bbc5f427c9fe390f15876775fa7e27bc15e4fb
@@@ -129,10 -129,8 +129,10 @@@ enum scld_error safe_create_leading_dir
                *slash = '\0';
                if (!stat(path, &st)) {
                        /* path exists */
 -                      if (!S_ISDIR(st.st_mode))
 +                      if (!S_ISDIR(st.st_mode)) {
 +                              errno = ENOTDIR;
                                ret = SCLD_EXISTS;
 +                      }
                } else if (mkdir(path, 0777)) {
                        if (errno == EEXIST &&
                            !stat(path, &st) && S_ISDIR(st.st_mode))
  
  enum scld_error safe_create_leading_directories_const(const char *path)
  {
 +      int save_errno;
        /* path points to cache entries, so xstrdup before messing with it */
        char *buf = xstrdup(path);
        enum scld_error result = safe_create_leading_directories(buf);
 +
 +      save_errno = errno;
        free(buf);
 +      errno = save_errno;
        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_sha1_path(struct strbuf *buf, const unsigned char *sha1)
  {
        int i;
@@@ -667,7 -593,7 +667,7 @@@ static int freshen_file(const char *fn
   * either does not exist on disk, or has a stale mtime and may be subject to
   * pruning).
   */
 -static int check_and_freshen_file(const char *fn, int freshen)
 +int check_and_freshen_file(const char *fn, int freshen)
  {
        if (access(fn, F_OK))
                return 0;
@@@ -2606,7 -2532,6 +2606,7 @@@ void *unpack_entry(struct packed_git *p
        while (delta_stack_nr) {
                void *delta_data;
                void *base = data;
 +              void *external_base = NULL;
                unsigned long delta_size, base_size = size;
                int i;
  
                                      p->pack_name);
                                mark_bad_packed_object(p, base_sha1);
                                base = read_object(base_sha1, &type, &base_size);
 +                              external_base = base;
                        }
                }
  
                              "at offset %"PRIuMAX" from %s",
                              (uintmax_t)curpos, p->pack_name);
                        data = NULL;
 +                      free(external_base);
                        continue;
                }
  
                        error("failed to apply delta");
  
                free(delta_data);
 +              free(external_base);
        }
  
        *final_type = type;
@@@ -2706,6 -2628,17 +2706,17 @@@ const unsigned char *nth_packed_object_
        }
  }
  
+ const struct object_id *nth_packed_object_oid(struct object_id *oid,
+                                             struct packed_git *p,
+                                             uint32_t n)
+ {
+       const unsigned char *hash = nth_packed_object_sha1(p, n);
+       if (!hash)
+               return NULL;
+       hashcpy(oid->hash, hash);
+       return oid;
+ }
  void check_pack_index_ptr(const struct packed_git *p, const void *vptr)
  {
        const unsigned char *ptr = vptr;
@@@ -3752,15 -3685,15 +3763,15 @@@ static int for_each_file_in_obj_subdir(
                strbuf_setlen(path, baselen);
                strbuf_addf(path, "/%s", de->d_name);
  
-               if (strlen(de->d_name) == 38)  {
-                       char hex[41];
-                       unsigned char sha1[20];
+               if (strlen(de->d_name) == GIT_SHA1_HEXSZ - 2)  {
+                       char hex[GIT_SHA1_HEXSZ+1];
+                       struct object_id oid;
  
                        snprintf(hex, sizeof(hex), "%02x%s",
                                 subdir_nr, de->d_name);
-                       if (!get_sha1_hex(hex, sha1)) {
+                       if (!get_oid_hex(hex, &oid)) {
                                if (obj_cb) {
-                                       r = obj_cb(sha1, path->buf, data);
+                                       r = obj_cb(&oid, path->buf, data);
                                        if (r)
                                                break;
                                }
@@@ -3866,13 -3799,13 +3877,13 @@@ static int for_each_object_in_pack(stru
        int r = 0;
  
        for (i = 0; i < p->num_objects; i++) {
-               const unsigned char *sha1 = nth_packed_object_sha1(p, i);
+               struct object_id oid;
  
-               if (!sha1)
+               if (!nth_packed_object_oid(&oid, p, i))
                        return error("unable to get sha1 of object %u in %s",
                                     i, p->pack_name);
  
-               r = cb(sha1, p, i, data);
+               r = cb(&oid, p, i, data);
                if (r)
                        break;
        }
diff --combined sha1_name.c
index 26ceec1d799afad6c6831cba00ca4d0ffab7979f,744e9f884a736d6a41188c295e48d0b98afb4927..cda9e49b12e7128dd22a8ccbaff437cb7db34d92
@@@ -1051,7 -1051,7 +1051,7 @@@ struct grab_nth_branch_switch_cbdata 
        struct strbuf buf;
  };
  
- static int grab_nth_branch_switch(unsigned char *osha1, unsigned char *nsha1,
+ static int grab_nth_branch_switch(struct object_id *ooid, struct object_id *noid,
                                  const char *email, unsigned long timestamp, int tz,
                                  const char *message, void *cb_data)
  {
@@@ -1176,8 -1176,7 +1176,8 @@@ static int interpret_empty_at(const cha
        return 1;
  }
  
 -static int reinterpret(const char *name, int namelen, int len, struct strbuf *buf)
 +static int reinterpret(const char *name, int namelen, int len,
 +                     struct strbuf *buf, unsigned allowed)
  {
        /* we have extra data, which might need further processing */
        struct strbuf tmp = STRBUF_INIT;
        int ret;
  
        strbuf_add(buf, name + len, namelen - len);
 -      ret = interpret_branch_name(buf->buf, buf->len, &tmp);
 +      ret = interpret_branch_name(buf->buf, buf->len, &tmp, allowed);
        /* that data was not interpreted, remove our cruft */
        if (ret < 0) {
                strbuf_setlen(buf, used);
@@@ -1206,27 -1205,11 +1206,27 @@@ static void set_shortened_ref(struct st
        free(s);
  }
  
 +static int branch_interpret_allowed(const char *refname, unsigned allowed)
 +{
 +      if (!allowed)
 +              return 1;
 +
 +      if ((allowed & INTERPRET_BRANCH_LOCAL) &&
 +          starts_with(refname, "refs/heads/"))
 +              return 1;
 +      if ((allowed & INTERPRET_BRANCH_REMOTE) &&
 +          starts_with(refname, "refs/remotes/"))
 +              return 1;
 +
 +      return 0;
 +}
 +
  static int interpret_branch_mark(const char *name, int namelen,
                                 int at, struct strbuf *buf,
                                 int (*get_mark)(const char *, int),
                                 const char *(*get_data)(struct branch *,
 -                                                       struct strbuf *))
 +                                                       struct strbuf *),
 +                               unsigned allowed)
  {
        int len;
        struct branch *branch;
        if (!value)
                die("%s", err.buf);
  
 +      if (!branch_interpret_allowed(value, allowed))
 +              return -1;
 +
        set_shortened_ref(buf, value);
        return len + at;
  }
  
 -/*
 - * This reads short-hand syntax that not only evaluates to a commit
 - * object name, but also can act as if the end user spelled the name
 - * of the branch from the command line.
 - *
 - * - "@{-N}" finds the name of the Nth previous branch we were on, and
 - *   places the name of the branch in the given buf and returns the
 - *   number of characters parsed if successful.
 - *
 - * - "<branch>@{upstream}" finds the name of the other ref that
 - *   <branch> is configured to merge with (missing <branch> defaults
 - *   to the current branch), and places the name of the branch in the
 - *   given buf and returns the number of characters parsed if
 - *   successful.
 - *
 - * If the input is not of the accepted format, it returns a negative
 - * number to signal an error.
 - *
 - * If the input was ok but there are not N branch switches in the
 - * reflog, it returns 0.
 - */
 -int interpret_branch_name(const char *name, int namelen, struct strbuf *buf)
 +int interpret_branch_name(const char *name, int namelen, struct strbuf *buf,
 +                        unsigned allowed)
  {
        char *at;
        const char *start;
 -      int len = interpret_nth_prior_checkout(name, namelen, buf);
 +      int len;
  
        if (!namelen)
                namelen = strlen(name);
  
 -      if (!len) {
 -              return len; /* syntax Ok, not enough switches */
 -      } else if (len > 0) {
 -              if (len == namelen)
 -                      return len; /* consumed all */
 -              else
 -                      return reinterpret(name, namelen, len, buf);
 +      if (!allowed || (allowed & INTERPRET_BRANCH_LOCAL)) {
 +              len = interpret_nth_prior_checkout(name, namelen, buf);
 +              if (!len) {
 +                      return len; /* syntax Ok, not enough switches */
 +              } else if (len > 0) {
 +                      if (len == namelen)
 +                              return len; /* consumed all */
 +                      else
 +                              return reinterpret(name, namelen, len, buf, allowed);
 +              }
        }
  
        for (start = name;
             (at = memchr(start, '@', namelen - (start - name)));
             start = at + 1) {
  
 -              len = interpret_empty_at(name, namelen, at - name, buf);
 -              if (len > 0)
 -                      return reinterpret(name, namelen, len, buf);
 +              if (!allowed || (allowed & INTERPRET_BRANCH_HEAD)) {
 +                      len = interpret_empty_at(name, namelen, at - name, buf);
 +                      if (len > 0)
 +                              return reinterpret(name, namelen, len, buf,
 +                                                 allowed);
 +              }
  
                len = interpret_branch_mark(name, namelen, at - name, buf,
 -                                          upstream_mark, branch_get_upstream);
 +                                          upstream_mark, branch_get_upstream,
 +                                          allowed);
                if (len > 0)
                        return len;
  
                len = interpret_branch_mark(name, namelen, at - name, buf,
 -                                          push_mark, branch_get_push);
 +                                          push_mark, branch_get_push,
 +                                          allowed);
                if (len > 0)
                        return len;
        }
        return -1;
  }
  
 -int strbuf_branchname(struct strbuf *sb, const char *name)
 +void strbuf_branchname(struct strbuf *sb, const char *name, unsigned allowed)
  {
        int len = strlen(name);
 -      int used = interpret_branch_name(name, len, sb);
 +      int used = interpret_branch_name(name, len, sb, allowed);
  
 -      if (used == len)
 -              return 0;
        if (used < 0)
                used = 0;
        strbuf_add(sb, name + used, len - used);
 -      return len;
  }
  
  int strbuf_check_branch_ref(struct strbuf *sb, const char *name)
  {
 -      strbuf_branchname(sb, name);
 +      strbuf_branchname(sb, name, INTERPRET_BRANCH_LOCAL);
        if (name[0] == '-')
                return -1;
        strbuf_splice(sb, 0, 0, "refs/heads/", 11);
diff --combined transport.c
index ea1feac9dcd83dac05cf0e2c35cf5f7a022810aa,141af31e8e314858217f986df0363372a7a7b99d..417ed7f19f5fcec5fdf9828749e1e40b207a44e7
@@@ -204,7 -204,6 +204,7 @@@ static struct ref *get_refs_via_connect
  static int fetch_refs_via_pack(struct transport *transport,
                               int nr_heads, struct ref **to_fetch)
  {
 +      int ret = 0;
        struct git_transport_data *data = transport->data;
        struct ref *refs;
        char *dest = xstrdup(transport->url);
                          &transport->pack_lockfile);
        close(data->fd[0]);
        close(data->fd[1]);
 -      if (finish_connect(data->conn)) {
 -              free_refs(refs);
 -              refs = NULL;
 -      }
 +      if (finish_connect(data->conn))
 +              ret = -1;
        data->conn = NULL;
        data->got_remote_heads = 0;
        data->options.self_contained_and_connected =
                args.self_contained_and_connected;
  
 +      if (refs == NULL)
 +              ret = -1;
 +      if (report_unmatched_refs(to_fetch, nr_heads))
 +              ret = -1;
 +
        free_refs(refs_tmp);
        free_refs(refs);
        free(dest);
 -      return (refs ? 0 : -1);
 +      return ret;
  }
  
  static int push_had_errors(struct ref *ref)
@@@ -303,7 -299,7 +303,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);
@@@ -471,11 -467,11 +471,11 @@@ void transport_print_push_status(const 
  {
        struct ref *ref;
        int n = 0;
-       unsigned char head_sha1[20];
+       struct object_id head_oid;
        char *head;
        int summary_width = transport_summary_width(refs);
  
-       head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_oid.hash, NULL);
  
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
@@@ -1210,42 -1206,6 +1210,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;
  }