Merge branch 'jk/for-each-reflog-ent-reverse' into maint
authorJunio C Hamano <gitster@pobox.com>
Mon, 12 Jan 2015 20:19:17 +0000 (12:19 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 12 Jan 2015 20:19:17 +0000 (12:19 -0800)
* jk/for-each-reflog-ent-reverse:
for_each_reflog_ent_reverse: turn leftover check into assertion
for_each_reflog_ent_reverse: fix newlines on block boundaries

1  2 
refs.c
t/t1410-reflog.sh
diff --combined refs.c
index 5ff457ebfce7aba9cd470f3e2e1f3f5dbe9d9741,a421d43211f9d610c8b81ed83893ee4dde02520f..1f77fa6a2400a29819a65d189d0b25cf217af7df
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -1,5 -1,4 +1,5 @@@
  #include "cache.h"
 +#include "lockfile.h"
  #include "refs.h"
  #include "object.h"
  #include "tag.h"
@@@ -7,34 -6,8 +7,34 @@@
  #include "string-list.h"
  
  /*
 - * Make sure "ref" is something reasonable to have under ".git/refs/";
 - * We do not like it if:
 + * How to handle various characters in refnames:
 + * 0: An acceptable character for refs
 + * 1: End-of-component
 + * 2: ., look for a preceding . to reject .. in refs
 + * 3: {, look for a preceding @ to reject @{ in refs
 + * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
 + */
 +static unsigned char refname_disposition[256] = {
 +      1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
 +      4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
 +      4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 4, 4
 +};
 +
 +/*
 + * Used as a flag to ref_transaction_delete when a loose ref is being
 + * pruned.
 + */
 +#define REF_ISPRUNING 0x0100
 +/*
 + * Try to read one refname component from the front of refname.
 + * Return the length of the component found, or -1 if the component is
 + * not legal.  It is legal if it is something reasonable to have under
 + * ".git/refs/"; We do not like it if:
   *
   * - any path component of it begins with ".", or
   * - it has double dots "..", or
   * - it ends with ".lock"
   * - it contains a "\" (backslash)
   */
 -
 -/* Return true iff ch is not allowed in reference names. */
 -static inline int bad_ref_char(int ch)
 -{
 -      if (((unsigned) ch) <= ' ' || ch == 0x7f ||
 -          ch == '~' || ch == '^' || ch == ':' || ch == '\\')
 -              return 1;
 -      /* 2.13 Pattern Matching Notation */
 -      if (ch == '*' || ch == '?' || ch == '[') /* Unsupported */
 -              return 1;
 -      return 0;
 -}
 -
 -/*
 - * Try to read one refname component from the front of refname.  Return
 - * the length of the component found, or -1 if the component is not
 - * legal.
 - */
  static int check_refname_component(const char *refname, int flags)
  {
        const char *cp;
        char last = '\0';
  
        for (cp = refname; ; cp++) {
 -              char ch = *cp;
 -              if (ch == '\0' || ch == '/')
 +              int ch = *cp & 255;
 +              unsigned char disp = refname_disposition[ch];
 +              switch (disp) {
 +              case 1:
 +                      goto out;
 +              case 2:
 +                      if (last == '.')
 +                              return -1; /* Refname contains "..". */
                        break;
 -              if (bad_ref_char(ch))
 -                      return -1; /* Illegal character in refname. */
 -              if (last == '.' && ch == '.')
 -                      return -1; /* Refname contains "..". */
 -              if (last == '@' && ch == '{')
 -                      return -1; /* Refname contains "@{". */
 +              case 3:
 +                      if (last == '@')
 +                              return -1; /* Refname contains "@{". */
 +                      break;
 +              case 4:
 +                      return -1;
 +              }
                last = ch;
        }
 +out:
        if (cp == refname)
                return 0; /* Component has zero length. */
 -      if (refname[0] == '.') {
 -              if (!(flags & REFNAME_DOT_COMPONENT))
 -                      return -1; /* Component starts with '.'. */
 -              /*
 -               * Even if leading dots are allowed, don't allow "."
 -               * as a component (".." is prevented by a rule above).
 -               */
 -              if (refname[1] == '\0')
 -                      return -1; /* Component equals ".". */
 -      }
 -      if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
 +      if (refname[0] == '.')
 +              return -1; /* Component starts with '.'. */
 +      if (cp - refname >= LOCK_SUFFIX_LEN &&
 +          !memcmp(cp - LOCK_SUFFIX_LEN, LOCK_SUFFIX, LOCK_SUFFIX_LEN))
                return -1; /* Refname ends with ".lock". */
        return cp - refname;
  }
@@@ -187,8 -177,8 +187,8 @@@ struct ref_dir 
  
  /*
   * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,
 - * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
 - * refs.h.
 + * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
 + * public values; see refs.h.
   */
  
  /*
   * the correct peeled value for the reference, which might be
   * null_sha1 if the reference is not a tag or if it is broken.
   */
 -#define REF_KNOWS_PEELED 0x08
 +#define REF_KNOWS_PEELED 0x10
  
  /* ref_entry represents a directory of references */
 -#define REF_DIR 0x10
 +#define REF_DIR 0x20
  
  /*
   * Entry has not yet been read from disk (used only for REF_DIR
   * entries representing loose references)
   */
 -#define REF_INCOMPLETE 0x20
 +#define REF_INCOMPLETE 0x40
  
  /*
   * A ref_entry represents either a reference or a "subdirectory" of
@@@ -274,39 -264,6 +274,39 @@@ static struct ref_dir *get_ref_dir(stru
        return dir;
  }
  
 +/*
 + * Check if a refname is safe.
 + * For refs that start with "refs/" we consider it safe as long they do
 + * not try to resolve to outside of refs/.
 + *
 + * For all other refs we only consider them safe iff they only contain
 + * upper case characters and '_' (like "HEAD" AND "MERGE_HEAD", and not like
 + * "config").
 + */
 +static int refname_is_safe(const char *refname)
 +{
 +      if (starts_with(refname, "refs/")) {
 +              char *buf;
 +              int result;
 +
 +              buf = xmalloc(strlen(refname) + 1);
 +              /*
 +               * Does the refname try to escape refs/?
 +               * For example: refs/foo/../bar is safe but refs/foo/../../bar
 +               * is not.
 +               */
 +              result = !normalize_path_copy(buf, refname + strlen("refs/"));
 +              free(buf);
 +              return result;
 +      }
 +      while (*refname) {
 +              if (!isupper(*refname) && *refname != '_')
 +                      return 0;
 +              refname++;
 +      }
 +      return 1;
 +}
 +
  static struct ref_entry *create_ref_entry(const char *refname,
                                          const unsigned char *sha1, int flag,
                                          int check_name)
        struct ref_entry *ref;
  
        if (check_name &&
 -          check_refname_format(refname, REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
 +          check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                die("Reference has invalid format: '%s'", refname);
 +      if (!check_name && !refname_is_safe(refname))
 +              die("Reference has invalid name: '%s'", refname);
        len = strlen(refname) + 1;
        ref = xmalloc(sizeof(struct ref_entry) + len);
        hashcpy(ref->u.value.sha1, sha1);
@@@ -682,7 -637,7 +682,7 @@@ static int do_one_ref(struct ref_entry 
        struct ref_entry *old_current_ref;
        int retval;
  
 -      if (prefixcmp(entry->name, data->base))
 +      if (!starts_with(entry->name, data->base))
                return 0;
  
        if (!(data->flags & DO_FOR_EACH_INCLUDE_BROKEN) &&
@@@ -813,121 -768,60 +813,121 @@@ static void prime_ref_dir(struct ref_di
                        prime_ref_dir(get_ref_dir(entry));
        }
  }
 -/*
 - * Return true iff refname1 and refname2 conflict with each other.
 - * Two reference names conflict if one of them exactly matches the
 - * leading components of the other; e.g., "foo/bar" conflicts with
 - * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
 - * "foo/barbados".
 - */
 -static int names_conflict(const char *refname1, const char *refname2)
 +
 +static int entry_matches(struct ref_entry *entry, const struct string_list *list)
  {
 -      for (; *refname1 && *refname1 == *refname2; refname1++, refname2++)
 -              ;
 -      return (*refname1 == '\0' && *refname2 == '/')
 -              || (*refname1 == '/' && *refname2 == '\0');
 +      return list && string_list_has_string(list, entry->name);
  }
  
 -struct name_conflict_cb {
 -      const char *refname;
 -      const char *oldrefname;
 -      const char *conflicting_refname;
 +struct nonmatching_ref_data {
 +      const struct string_list *skip;
 +      struct ref_entry *found;
  };
  
 -static int name_conflict_fn(struct ref_entry *entry, void *cb_data)
 +static int nonmatching_ref_fn(struct ref_entry *entry, void *vdata)
  {
 -      struct name_conflict_cb *data = (struct name_conflict_cb *)cb_data;
 -      if (data->oldrefname && !strcmp(data->oldrefname, entry->name))
 +      struct nonmatching_ref_data *data = vdata;
 +
 +      if (entry_matches(entry, data->skip))
                return 0;
 -      if (names_conflict(data->refname, entry->name)) {
 -              data->conflicting_refname = entry->name;
 -              return 1;
 -      }
 -      return 0;
 +
 +      data->found = entry;
 +      return 1;
 +}
 +
 +static void report_refname_conflict(struct ref_entry *entry,
 +                                  const char *refname)
 +{
 +      error("'%s' exists; cannot create '%s'", entry->name, refname);
  }
  
  /*
   * Return true iff a reference named refname could be created without
   * conflicting with the name of an existing reference in dir.  If
 - * oldrefname is non-NULL, ignore potential conflicts with oldrefname
 - * (e.g., because oldrefname is scheduled for deletion in the same
 + * skip is non-NULL, ignore potential conflicts with refs in skip
 + * (e.g., because they are scheduled for deletion in the same
   * operation).
 + *
 + * Two reference names conflict if one of them exactly matches the
 + * leading components of the other; e.g., "foo/bar" conflicts with
 + * both "foo" and with "foo/bar/baz" but not with "foo/bar" or
 + * "foo/barbados".
 + *
 + * skip must be sorted.
   */
 -static int is_refname_available(const char *refname, const char *oldrefname,
 +static int is_refname_available(const char *refname,
 +                              const struct string_list *skip,
                                struct ref_dir *dir)
  {
 -      struct name_conflict_cb data;
 -      data.refname = refname;
 -      data.oldrefname = oldrefname;
 -      data.conflicting_refname = NULL;
 +      const char *slash;
 +      size_t len;
 +      int pos;
 +      char *dirname;
  
 -      sort_ref_dir(dir);
 -      if (do_for_each_entry_in_dir(dir, 0, name_conflict_fn, &data)) {
 -              error("'%s' exists; cannot create '%s'",
 -                    data.conflicting_refname, refname);
 +      for (slash = strchr(refname, '/'); slash; slash = strchr(slash + 1, '/')) {
 +              /*
 +               * We are still at a leading dir of the refname; we are
 +               * looking for a conflict with a leaf entry.
 +               *
 +               * If we find one, we still must make sure it is
 +               * not in "skip".
 +               */
 +              pos = search_ref_dir(dir, refname, slash - refname);
 +              if (pos >= 0) {
 +                      struct ref_entry *entry = dir->entries[pos];
 +                      if (entry_matches(entry, skip))
 +                              return 1;
 +                      report_refname_conflict(entry, refname);
 +                      return 0;
 +              }
 +
 +
 +              /*
 +               * Otherwise, we can try to continue our search with
 +               * the next component; if we come up empty, we know
 +               * there is nothing under this whole prefix.
 +               */
 +              pos = search_ref_dir(dir, refname, slash + 1 - refname);
 +              if (pos < 0)
 +                      return 1;
 +
 +              dir = get_ref_dir(dir->entries[pos]);
 +      }
 +
 +      /*
 +       * We are at the leaf of our refname; we want to
 +       * make sure there are no directories which match it.
 +       */
 +      len = strlen(refname);
 +      dirname = xmallocz(len + 1);
 +      sprintf(dirname, "%s/", refname);
 +      pos = search_ref_dir(dir, dirname, len + 1);
 +      free(dirname);
 +
 +      if (pos >= 0) {
 +              /*
 +               * We found a directory named "refname". It is a
 +               * problem iff it contains any ref that is not
 +               * in "skip".
 +               */
 +              struct ref_entry *entry = dir->entries[pos];
 +              struct ref_dir *dir = get_ref_dir(entry);
 +              struct nonmatching_ref_data data;
 +
 +              data.skip = skip;
 +              sort_ref_dir(dir);
 +              if (!do_for_each_entry_in_dir(dir, 0, nonmatching_ref_fn, &data))
 +                      return 1;
 +
 +              report_refname_conflict(data.found, refname);
                return 0;
        }
 +
 +      /*
 +       * There is no point in searching for another leaf
 +       * node which matches it; such an entry would be the
 +       * ref we are looking for, not a conflict.
 +       */
        return 1;
  }
  
@@@ -1146,15 -1040,9 +1146,15 @@@ static void read_packed_refs(FILE *f, s
  
                refname = parse_ref_line(refline, sha1);
                if (refname) {
 -                      last = create_ref_entry(refname, sha1, REF_ISPACKED, 1);
 +                      int flag = REF_ISPACKED;
 +
 +                      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +                              hashclr(sha1);
 +                              flag |= REF_BAD_NAME | REF_ISBROKEN;
 +                      }
 +                      last = create_ref_entry(refname, sha1, flag, 0);
                        if (peeled == PEELED_FULLY ||
 -                          (peeled == PEELED_TAGS && !prefixcmp(refname, "refs/tags/")))
 +                          (peeled == PEELED_TAGS && starts_with(refname, "refs/tags/")))
                                last->flag |= REF_KNOWS_PEELED;
                        add_ref(dir, last);
                        continue;
@@@ -1263,7 -1151,7 +1263,7 @@@ static void read_loose_refs(const char 
  
                if (de->d_name[0] == '.')
                        continue;
 -              if (has_extension(de->d_name, ".lock"))
 +              if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(&refname, de->d_name);
                refdir = *refs->name
                                        hashclr(sha1);
                                        flag |= REF_ISBROKEN;
                                }
 -                      } else if (read_ref_full(refname.buf, sha1, 1, &flag)) {
 +                      } else if (read_ref_full(refname.buf,
 +                                               RESOLVE_REF_READING,
 +                                               sha1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
 +                      if (check_refname_format(refname.buf,
 +                                               REFNAME_ALLOW_ONELEVEL)) {
 +                              hashclr(sha1);
 +                              flag |= REF_BAD_NAME | REF_ISBROKEN;
 +                      }
                        add_entry_to_dir(dir,
 -                                       create_ref_entry(refname.buf, sha1, flag, 1));
 +                                       create_ref_entry(refname.buf, sha1, flag, 0));
                }
                strbuf_setlen(&refname, dirnamelen);
        }
@@@ -1341,7 -1222,7 +1341,7 @@@ static int resolve_gitlink_packed_ref(s
        if (ref == NULL)
                return -1;
  
 -      memcpy(sha1, ref->u.value.sha1, 20);
 +      hashcpy(sha1, ref->u.value.sha1);
        return 0;
  }
  
@@@ -1415,10 -1296,10 +1415,10 @@@ static struct ref_entry *get_packed_ref
   * A loose ref file doesn't exist; check for a packed ref.  The
   * options are forwarded from resolve_safe_unsafe().
   */
 -static const char *handle_missing_loose_ref(const char *refname,
 -                                          unsigned char *sha1,
 -                                          int reading,
 -                                          int *flag)
 +static int resolve_missing_loose_ref(const char *refname,
 +                                   int resolve_flags,
 +                                   unsigned char *sha1,
 +                                   int *flags)
  {
        struct ref_entry *entry;
  
        entry = get_packed_ref(refname);
        if (entry) {
                hashcpy(sha1, entry->u.value.sha1);
 -              if (flag)
 -                      *flag |= REF_ISPACKED;
 -              return refname;
 +              if (flags)
 +                      *flags |= REF_ISPACKED;
 +              return 0;
        }
        /* The reference is not a packed reference, either. */
 -      if (reading) {
 -              return NULL;
 +      if (resolve_flags & RESOLVE_REF_READING) {
 +              errno = ENOENT;
 +              return -1;
        } else {
                hashclr(sha1);
 -              return refname;
 +              return 0;
        }
  }
  
 -const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 +/* This function needs to return a meaningful errno on failure */
 +const char *resolve_ref_unsafe(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
  {
        int depth = MAXDEPTH;
        ssize_t len;
        char buffer[256];
        static char refname_buffer[256];
 +      int bad_name = 0;
  
 -      if (flag)
 -              *flag = 0;
 +      if (flags)
 +              *flags = 0;
  
 -      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
 -              return NULL;
 +      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +              if (flags)
 +                      *flags |= REF_BAD_NAME;
  
 +              if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
 +                  !refname_is_safe(refname)) {
 +                      errno = EINVAL;
 +                      return NULL;
 +              }
 +              /*
 +               * dwim_ref() uses REF_ISBROKEN to distinguish between
 +               * missing refs and refs that were present but invalid,
 +               * to complain about the latter to stderr.
 +               *
 +               * We don't know whether the ref exists, so don't set
 +               * REF_ISBROKEN yet.
 +               */
 +              bad_name = 1;
 +      }
        for (;;) {
                char path[PATH_MAX];
                struct stat st;
                char *buf;
                int fd;
  
 -              if (--depth < 0)
 +              if (--depth < 0) {
 +                      errno = ELOOP;
                        return NULL;
 +              }
  
                git_snpath(path, sizeof(path), "%s", refname);
  
                 */
        stat_ref:
                if (lstat(path, &st) < 0) {
 -                      if (errno == ENOENT)
 -                              return handle_missing_loose_ref(refname, sha1,
 -                                                              reading, flag);
 -                      else
 +                      if (errno != ENOENT)
                                return NULL;
 +                      if (resolve_missing_loose_ref(refname, resolve_flags,
 +                                                    sha1, flags))
 +                              return NULL;
 +                      if (bad_name) {
 +                              hashclr(sha1);
 +                              if (flags)
 +                                      *flags |= REF_ISBROKEN;
 +                      }
 +                      return refname;
                }
  
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
                                        return NULL;
                        }
                        buffer[len] = 0;
 -                      if (!prefixcmp(buffer, "refs/") &&
 +                      if (starts_with(buffer, "refs/") &&
                                        !check_refname_format(buffer, 0)) {
                                strcpy(refname_buffer, buffer);
                                refname = refname_buffer;
 -                              if (flag)
 -                                      *flag |= REF_ISSYMREF;
 +                              if (flags)
 +                                      *flags |= REF_ISSYMREF;
 +                              if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
 +                                      hashclr(sha1);
 +                                      return refname;
 +                              }
                                continue;
                        }
                }
                                return NULL;
                }
                len = read_in_full(fd, buffer, sizeof(buffer)-1);
 -              close(fd);
 -              if (len < 0)
 +              if (len < 0) {
 +                      int save_errno = errno;
 +                      close(fd);
 +                      errno = save_errno;
                        return NULL;
 +              }
 +              close(fd);
                while (len && isspace(buffer[len-1]))
                        len--;
                buffer[len] = '\0';
                /*
                 * Is it a symbolic ref?
                 */
 -              if (prefixcmp(buffer, "ref:")) {
 +              if (!starts_with(buffer, "ref:")) {
                        /*
                         * Please note that FETCH_HEAD has a second
                         * line containing other data.
                         */
                        if (get_sha1_hex(buffer, sha1) ||
                            (buffer[40] != '\0' && !isspace(buffer[40]))) {
 -                              if (flag)
 -                                      *flag |= REF_ISBROKEN;
 +                              if (flags)
 +                                      *flags |= REF_ISBROKEN;
 +                              errno = EINVAL;
                                return NULL;
                        }
 +                      if (bad_name) {
 +                              hashclr(sha1);
 +                              if (flags)
 +                                      *flags |= REF_ISBROKEN;
 +                      }
                        return refname;
                }
 -              if (flag)
 -                      *flag |= REF_ISSYMREF;
 +              if (flags)
 +                      *flags |= REF_ISSYMREF;
                buf = buffer + 4;
                while (isspace(*buf))
                        buf++;
 +              refname = strcpy(refname_buffer, buf);
 +              if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
 +                      hashclr(sha1);
 +                      return refname;
 +              }
                if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
 -                      if (flag)
 -                              *flag |= REF_ISBROKEN;
 -                      return NULL;
 +                      if (flags)
 +                              *flags |= REF_ISBROKEN;
 +
 +                      if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
 +                          !refname_is_safe(buf)) {
 +                              errno = EINVAL;
 +                              return NULL;
 +                      }
 +                      bad_name = 1;
                }
 -              refname = strcpy(refname_buffer, buf);
        }
  }
  
 -char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag)
 +char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
  {
 -      const char *ret = resolve_ref_unsafe(ref, sha1, reading, flag);
 +      const char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags);
        return ret ? xstrdup(ret) : NULL;
  }
  
@@@ -1625,29 -1455,29 +1625,29 @@@ struct ref_filter 
        void *cb_data;
  };
  
 -int read_ref_full(const char *refname, unsigned char *sha1, int reading, int *flags)
 +int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, int *flags)
  {
 -      if (resolve_ref_unsafe(refname, sha1, reading, flags))
 +      if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
                return 0;
        return -1;
  }
  
  int read_ref(const char *refname, unsigned char *sha1)
  {
 -      return read_ref_full(refname, sha1, 1, NULL);
 +      return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
  }
  
  int ref_exists(const char *refname)
  {
        unsigned char sha1[20];
 -      return !!resolve_ref_unsafe(refname, sha1, 1, NULL);
 +      return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
  }
  
  static int filter_refs(const char *refname, const unsigned char *sha1, int flags,
                       void *data)
  {
        struct ref_filter *filter = (struct ref_filter *)data;
 -      if (fnmatch(filter->pattern, refname, 0))
 +      if (wildmatch(filter->pattern, refname, 0, NULL))
                return 0;
        return filter->fn(refname, sha1, flags, filter->cb_data);
  }
@@@ -1690,8 -1520,9 +1690,8 @@@ static enum peel_status peel_object(con
  
        if (o->type == OBJ_NONE) {
                int type = sha1_object_info(name, NULL);
 -              if (type < 0)
 +              if (type < 0 || !object_as_type(o, type, 0))
                        return PEEL_INVALID;
 -              o->type = type;
        }
  
        if (o->type != OBJ_TAG)
@@@ -1753,7 -1584,7 +1753,7 @@@ int peel_ref(const char *refname, unsig
                return 0;
        }
  
 -      if (read_ref_full(refname, base, 1, &flag))
 +      if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))
                return -1;
  
        /*
  struct warn_if_dangling_data {
        FILE *fp;
        const char *refname;
 +      const struct string_list *refnames;
        const char *msg_fmt;
  };
  
@@@ -1794,13 -1624,9 +1794,13 @@@ static int warn_if_dangling_symref(cons
        if (!(flags & REF_ISSYMREF))
                return 0;
  
 -      resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
 -      if (!resolves_to || strcmp(resolves_to, d->refname))
 +      resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);
 +      if (!resolves_to
 +          || (d->refname
 +              ? strcmp(resolves_to, d->refname)
 +              : !string_list_has_string(d->refnames, resolves_to))) {
                return 0;
 +      }
  
        fprintf(d->fp, d->msg_fmt, refname);
        fputc('\n', d->fp);
@@@ -1813,18 -1639,6 +1813,18 @@@ void warn_dangling_symref(FILE *fp, con
  
        data.fp = fp;
        data.refname = refname;
 +      data.refnames = NULL;
 +      data.msg_fmt = msg_fmt;
 +      for_each_rawref(warn_if_dangling_symref, &data);
 +}
 +
 +void warn_dangling_symrefs(FILE *fp, const char *msg_fmt, const struct string_list *refnames)
 +{
 +      struct warn_if_dangling_data data;
 +
 +      data.fp = fp;
 +      data.refname = NULL;
 +      data.refnames = refnames;
        data.msg_fmt = msg_fmt;
        for_each_rawref(warn_if_dangling_symref, &data);
  }
@@@ -1919,7 -1733,7 +1919,7 @@@ static int do_head_ref(const char *subm
                return 0;
        }
  
 -      if (!read_ref_full("HEAD", sha1, 1, &flag))
 +      if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag))
                return fn("HEAD", sha1, flag, cb_data);
  
        return 0;
@@@ -1999,7 -1813,7 +1999,7 @@@ int head_ref_namespaced(each_ref_fn fn
        int flag;
  
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
 -      if (!read_ref_full(buf.buf, sha1, 1, &flag))
 +      if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))
                ret = fn(buf.buf, sha1, flag, cb_data);
        strbuf_release(&buf);
  
@@@ -2023,7 -1837,7 +2023,7 @@@ int for_each_glob_ref_in(each_ref_fn fn
        struct ref_filter filter;
        int ret;
  
 -      if (!prefix && prefixcmp(pattern, "refs/"))
 +      if (!prefix && !starts_with(pattern, "refs/"))
                strbuf_addstr(&real_pattern, "refs/");
        else if (prefix)
                strbuf_addstr(&real_pattern, prefix);
@@@ -2060,13 -1874,13 +2060,13 @@@ int for_each_rawref(each_ref_fn fn, voi
  const char *prettify_refname(const char *name)
  {
        return name + (
 -              !prefixcmp(name, "refs/heads/") ? 11 :
 -              !prefixcmp(name, "refs/tags/") ? 10 :
 -              !prefixcmp(name, "refs/remotes/") ? 13 :
 +              starts_with(name, "refs/heads/") ? 11 :
 +              starts_with(name, "refs/tags/") ? 10 :
 +              starts_with(name, "refs/remotes/") ? 13 :
                0);
  }
  
 -const char *ref_rev_parse_rules[] = {
 +static const char *ref_rev_parse_rules[] = {
        "%.*s",
        "refs/%.*s",
        "refs/tags/%.*s",
        NULL
  };
  
 -int refname_match(const char *abbrev_name, const char *full_name, const char **rules)
 +int refname_match(const char *abbrev_name, const char *full_name)
  {
        const char **p;
        const int abbrev_name_len = strlen(abbrev_name);
  
 -      for (p = rules; *p; p++) {
 +      for (p = ref_rev_parse_rules; *p; p++) {
                if (!strcmp(full_name, mkpath(*p, abbrev_name_len, abbrev_name))) {
                        return 1;
                }
        return 0;
  }
  
 +/* This function should make sure errno is meaningful on error */
  static struct ref_lock *verify_lock(struct ref_lock *lock,
        const unsigned char *old_sha1, int mustexist)
  {
 -      if (read_ref_full(lock->ref_name, lock->old_sha1, mustexist, NULL)) {
 +      if (read_ref_full(lock->ref_name,
 +                        mustexist ? RESOLVE_REF_READING : 0,
 +                        lock->old_sha1, NULL)) {
 +              int save_errno = errno;
                error("Can't verify ref %s", lock->ref_name);
                unlock_ref(lock);
 +              errno = save_errno;
                return NULL;
        }
        if (hashcmp(lock->old_sha1, old_sha1)) {
                error("Ref %s is at %s but expected %s", lock->ref_name,
                        sha1_to_hex(lock->old_sha1), sha1_to_hex(old_sha1));
                unlock_ref(lock);
 +              errno = EBUSY;
                return NULL;
        }
        return lock;
@@@ -2120,16 -1928,14 +2120,16 @@@ static int remove_empty_directories(con
         * only empty directories), remove them.
         */
        struct strbuf path;
 -      int result;
 +      int result, save_errno;
  
        strbuf_init(&path, 20);
        strbuf_addstr(&path, file);
  
        result = remove_dir_recursively(&path, REMOVE_DIR_EMPTY_ONLY);
 +      save_errno = errno;
  
        strbuf_release(&path);
 +      errno = save_errno;
  
        return result;
  }
@@@ -2169,8 -1975,7 +2169,8 @@@ int dwim_ref(const char *str, int len, 
  
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
 -              r = resolve_ref_unsafe(fullref, this_result, 1, &flag);
 +              r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
 +                                     this_result, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
@@@ -2194,18 -1999,21 +2194,18 @@@ int dwim_log(const char *str, int len, 
  
        *log = NULL;
        for (p = ref_rev_parse_rules; *p; p++) {
 -              struct stat st;
                unsigned char hash[20];
                char path[PATH_MAX];
                const char *ref, *it;
  
                mksnpath(path, sizeof(path), *p, len, str);
 -              ref = resolve_ref_unsafe(path, hash, 1, NULL);
 +              ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
 +                                       hash, NULL);
                if (!ref)
                        continue;
 -              if (!stat(git_path("logs/%s", path), &st) &&
 -                  S_ISREG(st.st_mode))
 +              if (reflog_exists(path))
                        it = path;
 -              else if (strcmp(ref, path) &&
 -                       !stat(git_path("logs/%s", ref), &st) &&
 -                       S_ISREG(st.st_mode))
 +              else if (strcmp(ref, path) && reflog_exists(ref))
                        it = ref;
                else
                        continue;
        return logs_found;
  }
  
 +/*
 + * Locks a ref returning the lock on success and NULL on failure.
 + * On failure errno is set to something meaningful.
 + */
  static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            const unsigned char *old_sha1,
 +                                          const struct string_list *skip,
                                            int flags, int *type_p)
  {
        char *ref_file;
        int last_errno = 0;
        int type, lflags;
        int mustexist = (old_sha1 && !is_null_sha1(old_sha1));
 +      int resolve_flags = 0;
        int missing = 0;
 +      int attempts_remaining = 3;
  
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
  
 -      refname = resolve_ref_unsafe(refname, lock->old_sha1, mustexist, &type);
 +      if (mustexist)
 +              resolve_flags |= RESOLVE_REF_READING;
 +      if (flags & REF_DELETING) {
 +              resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
 +              if (flags & REF_NODEREF)
 +                      resolve_flags |= RESOLVE_REF_NO_RECURSE;
 +      }
 +
 +      refname = resolve_ref_unsafe(refname, resolve_flags,
 +                                   lock->old_sha1, &type);
        if (!refname && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
                        error("there are still refs under '%s'", orig_refname);
                        goto error_return;
                }
 -              refname = resolve_ref_unsafe(orig_refname, lock->old_sha1, mustexist, &type);
 +              refname = resolve_ref_unsafe(orig_refname, resolve_flags,
 +                                           lock->old_sha1, &type);
        }
        if (type_p)
            *type_p = type;
         * name is a proper prefix of our refname.
         */
        if (missing &&
 -           !is_refname_available(refname, NULL, get_packed_refs(&ref_cache))) {
 +           !is_refname_available(refname, skip, get_packed_refs(&ref_cache))) {
                last_errno = ENOTDIR;
                goto error_return;
        }
  
        lock->lk = xcalloc(1, sizeof(struct lock_file));
  
 -      lflags = LOCK_DIE_ON_ERROR;
 +      lflags = 0;
        if (flags & REF_NODEREF) {
                refname = orig_refname;
 -              lflags |= LOCK_NODEREF;
 +              lflags |= LOCK_NO_DEREF;
        }
        lock->ref_name = xstrdup(refname);
        lock->orig_ref_name = xstrdup(orig_refname);
        if ((flags & REF_NODEREF) && (type & REF_ISSYMREF))
                lock->force_write = 1;
  
 -      if (safe_create_leading_directories(ref_file)) {
 + retry:
 +      switch (safe_create_leading_directories(ref_file)) {
 +      case SCLD_OK:
 +              break; /* success */
 +      case SCLD_VANISHED:
 +              if (--attempts_remaining > 0)
 +                      goto retry;
 +              /* fall through */
 +      default:
                last_errno = errno;
                error("unable to create directory for %s", ref_file);
                goto error_return;
        }
  
        lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
 +      if (lock->lock_fd < 0) {
 +              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_die(ref_file, errno);
 +      }
        return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
  
   error_return:
        return NULL;
  }
  
 -struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1)
 -{
 -      char refpath[PATH_MAX];
 -      if (check_refname_format(refname, 0))
 -              return NULL;
 -      strcpy(refpath, mkpath("refs/%s", refname));
 -      return lock_ref_sha1_basic(refpath, old_sha1, 0, NULL);
 -}
 -
  struct ref_lock *lock_any_ref_for_update(const char *refname,
                                         const unsigned char *old_sha1,
                                         int flags, int *type_p)
  {
 -      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
 -              return NULL;
 -      return lock_ref_sha1_basic(refname, old_sha1, flags, type_p);
 +      return lock_ref_sha1_basic(refname, old_sha1, NULL, flags, type_p);
  }
  
  /*
   * Write an entry to the packed-refs file for the specified refname.
   * If peeled is non-NULL, write it as the entry's peeled value.
   */
 -static void write_packed_entry(int fd, char *refname, unsigned char *sha1,
 +static void write_packed_entry(FILE *fh, char *refname, unsigned char *sha1,
                               unsigned char *peeled)
  {
 -      char line[PATH_MAX + 100];
 -      int len;
 -
 -      len = snprintf(line, sizeof(line), "%s %s\n",
 -                     sha1_to_hex(sha1), refname);
 -      /* this should not happen but just being defensive */
 -      if (len > sizeof(line))
 -              die("too long a refname '%s'", refname);
 -      write_or_die(fd, line, len);
 -
 -      if (peeled) {
 -              if (snprintf(line, sizeof(line), "^%s\n",
 -                           sha1_to_hex(peeled)) != PEELED_LINE_LENGTH)
 -                      die("internal error");
 -              write_or_die(fd, line, PEELED_LINE_LENGTH);
 -      }
 +      fprintf_or_die(fh, "%s %s\n", sha1_to_hex(sha1), refname);
 +      if (peeled)
 +              fprintf_or_die(fh, "^%s\n", sha1_to_hex(peeled));
  }
  
  /*
   */
  static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
  {
 -      int *fd = cb_data;
        enum peel_status peel_status = peel_entry(entry, 0);
  
        if (peel_status != PEEL_PEELED && peel_status != PEEL_NON_TAG)
                error("internal error: %s is not a valid packed reference!",
                      entry->name);
 -      write_packed_entry(*fd, entry->name, entry->u.value.sha1,
 +      write_packed_entry(cb_data, entry->name, entry->u.value.sha1,
                           peel_status == PEEL_PEELED ?
                           entry->u.value.peeled : NULL);
        return 0;
  }
  
 +/* This should return a meaningful errno on failure */
  int lock_packed_refs(int flags)
  {
        struct packed_ref_cache *packed_ref_cache;
        return 0;
  }
  
 +/*
 + * Commit the packed refs changes.
 + * On error we must make sure that errno contains a meaningful value.
 + */
  int commit_packed_refs(void)
  {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
        int error = 0;
 +      int save_errno = 0;
 +      FILE *out;
  
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
 -      write_or_die(packed_ref_cache->lock->fd,
 -                   PACKED_REFS_HEADER, strlen(PACKED_REFS_HEADER));
  
 +      out = fdopen_lock_file(packed_ref_cache->lock, "w");
 +      if (!out)
 +              die_errno("unable to fdopen packed-refs descriptor");
 +
 +      fprintf_or_die(out, "%s", PACKED_REFS_HEADER);
        do_for_each_entry_in_dir(get_packed_ref_dir(packed_ref_cache),
 -                               0, write_packed_entry_fn,
 -                               &packed_ref_cache->lock->fd);
 -      if (commit_lock_file(packed_ref_cache->lock))
 +                               0, write_packed_entry_fn, out);
 +
 +      if (commit_lock_file(packed_ref_cache->lock)) {
 +              save_errno = errno;
                error = -1;
 +      }
        packed_ref_cache->lock = NULL;
        release_packed_ref_cache(packed_ref_cache);
 +      errno = save_errno;
        return error;
  }
  
@@@ -2460,7 -2244,7 +2460,7 @@@ static int pack_if_possible_fn(struct r
        struct pack_refs_cb_data *cb = cb_data;
        enum peel_status peel_status;
        struct ref_entry *packed_entry;
 -      int is_tag_ref = !prefixcmp(entry->name, "refs/tags/");
 +      int is_tag_ref = starts_with(entry->name, "refs/tags/");
  
        /* ALWAYS pack tags */
        if (!(cb->flags & PACK_REFS_ALL) && !is_tag_ref)
@@@ -2533,25 -2317,13 +2533,25 @@@ static void try_remove_empty_parents(ch
  /* make sure nobody touched the ref, and unlink */
  static void prune_ref(struct ref_to_prune *r)
  {
 -      struct ref_lock *lock = lock_ref_sha1(r->name + 5, r->sha1);
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
  
 -      if (lock) {
 -              unlink_or_warn(git_path("%s", r->name));
 -              unlock_ref(lock);
 -              try_remove_empty_parents(r->name);
 +      if (check_refname_format(r->name, 0))
 +              return;
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_delete(transaction, r->name, r->sha1,
 +                                 REF_ISPRUNING, 1, NULL, &err) ||
 +          ref_transaction_commit(transaction, &err)) {
 +              ref_transaction_free(transaction);
 +              error("%s", err.buf);
 +              strbuf_release(&err);
 +              return;
        }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      try_remove_empty_parents(r->name);
  }
  
  static void prune_refs(struct ref_to_prune *r)
@@@ -2607,7 -2379,7 +2607,7 @@@ static int curate_packed_ref_fn(struct 
                unsigned char sha1[20];
                int flags;
  
 -              if (read_ref_full(entry->name, sha1, 0, &flags))
 +              if (read_ref_full(entry->name, 0, sha1, &flags))
                        /* We should at least have found the packed ref. */
                        die("Internal error");
                if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
        return 0;
  }
  
 -static int repack_without_refs(const char **refnames, int n)
 +int repack_without_refs(const char **refnames, int n, struct strbuf *err)
  {
        struct ref_dir *packed;
        struct string_list refs_to_delete = STRING_LIST_INIT_DUP;
        struct string_list_item *ref_to_delete;
 -      int i, removed = 0;
 +      int i, ret, removed = 0;
 +
 +      assert(err);
  
        /* Look for a packed ref */
        for (i = 0; i < n; i++)
                return 0; /* no refname exists in packed refs */
  
        if (lock_packed_refs(0)) {
 -              unable_to_lock_error(git_path("packed-refs"), errno);
 -              return error("cannot delete '%s' from packed refs", refnames[i]);
 +              unable_to_lock_message(git_path("packed-refs"), errno, err);
 +              return -1;
        }
        packed = get_packed_refs(&ref_cache);
  
        }
  
        /* Write what remains */
 -      return commit_packed_refs();
 +      ret = commit_packed_refs();
 +      if (ret)
 +              strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
 +                          strerror(errno));
 +      return ret;
  }
  
 -static int repack_without_ref(const char *refname)
 +static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
  {
 -      return repack_without_refs(&refname, 1);
 -}
 +      assert(err);
  
 -static int delete_ref_loose(struct ref_lock *lock, int flag)
 -{
        if (!(flag & REF_ISPACKED) || flag & REF_ISSYMREF) {
 -              /* loose */
 -              int err, i = strlen(lock->lk->filename) - 5; /* .lock */
 -
 -              lock->lk->filename[i] = 0;
 -              err = unlink_or_warn(lock->lk->filename);
 -              lock->lk->filename[i] = '.';
 -              if (err && errno != ENOENT)
 +              /*
 +               * 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;
  
  int delete_ref(const char *refname, const unsigned char *sha1, int delopt)
  {
 -      struct ref_lock *lock;
 -      int ret = 0, flag = 0;
 -
 -      lock = lock_ref_sha1_basic(refname, sha1, delopt, &flag);
 -      if (!lock)
 +      struct ref_transaction *transaction;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      transaction = ref_transaction_begin(&err);
 +      if (!transaction ||
 +          ref_transaction_delete(transaction, refname, sha1, delopt,
 +                                 sha1 && !is_null_sha1(sha1), NULL, &err) ||
 +          ref_transaction_commit(transaction, &err)) {
 +              error("%s", err.buf);
 +              ref_transaction_free(transaction);
 +              strbuf_release(&err);
                return 1;
 -      ret |= delete_ref_loose(lock, flag);
 -
 -      /* removing the loose one could have resurrected an earlier
 -       * packed one.  Also, if it was not loose we need to repack
 -       * without it.
 -       */
 -      ret |= repack_without_ref(lock->ref_name);
 -
 -      unlink_or_warn(git_path("logs/%s", lock->ref_name));
 -      clear_loose_ref_cache(&ref_cache);
 -      unlock_ref(lock);
 -      return ret;
 +      }
 +      ref_transaction_free(transaction);
 +      strbuf_release(&err);
 +      return 0;
  }
  
  /*
   */
  #define TMP_RENAMED_LOG  "logs/refs/.tmp-renamed-log"
  
 +static int rename_tmp_log(const char *newrefname)
 +{
 +      int attempts_remaining = 4;
 +
 + retry:
 +      switch (safe_create_leading_directories(git_path("logs/%s", newrefname))) {
 +      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);
 +              return -1;
 +      }
 +
 +      if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
 +              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(git_path("logs/%s", newrefname))) {
 +                              error("Directory not empty: logs/%s", newrefname);
 +                              return -1;
 +                      }
 +                      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));
 +                      return -1;
 +              }
 +      }
 +      return 0;
 +}
 +
 +static int rename_ref_available(const char *oldname, const char *newname)
 +{
 +      struct string_list skip = STRING_LIST_INIT_NODUP;
 +      int ret;
 +
 +      string_list_insert(&skip, oldname);
 +      ret = is_refname_available(newname, &skip, get_packed_refs(&ref_cache))
 +          && is_refname_available(newname, &skip, get_loose_refs(&ref_cache));
 +      string_list_clear(&skip, 0);
 +      return ret;
 +}
 +
 +static int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1,
 +                        const char *logmsg);
 +
  int rename_ref(const char *oldrefname, const char *newrefname, const char *logmsg)
  {
        unsigned char sha1[20], orig_sha1[20];
        if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldrefname);
  
 -      symref = resolve_ref_unsafe(oldrefname, orig_sha1, 1, &flag);
 +      symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
 +                                  orig_sha1, &flag);
        if (flag & REF_ISSYMREF)
                return error("refname %s is a symbolic ref, renaming it is not supported",
                        oldrefname);
        if (!symref)
                return error("refname %s not found", oldrefname);
  
 -      if (!is_refname_available(newrefname, oldrefname, get_packed_refs(&ref_cache)))
 -              return 1;
 -
 -      if (!is_refname_available(newrefname, oldrefname, get_loose_refs(&ref_cache)))
 +      if (!rename_ref_available(oldrefname, newrefname))
                return 1;
  
        if (log && rename(git_path("logs/%s", oldrefname), git_path(TMP_RENAMED_LOG)))
                goto rollback;
        }
  
 -      if (!read_ref_full(newrefname, sha1, 1, &flag) &&
 +      if (!read_ref_full(newrefname, RESOLVE_REF_READING, sha1, NULL) &&
            delete_ref(newrefname, sha1, REF_NODEREF)) {
                if (errno==EISDIR) {
                        if (remove_empty_directories(git_path("%s", newrefname))) {
                }
        }
  
 -      if (log && safe_create_leading_directories(git_path("logs/%s", newrefname))) {
 -              error("unable to create directory for %s", newrefname);
 +      if (log && rename_tmp_log(newrefname))
                goto rollback;
 -      }
  
 - retry:
 -      if (log && rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
 -              if (errno==EISDIR || errno==ENOTDIR) {
 -                      /*
 -                       * 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(git_path("logs/%s", newrefname))) {
 -                              error("Directory not empty: logs/%s", newrefname);
 -                              goto rollback;
 -                      }
 -                      goto retry;
 -              } else {
 -                      error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
 -                              newrefname, strerror(errno));
 -                      goto rollback;
 -              }
 -      }
        logmoved = log;
  
 -      lock = lock_ref_sha1_basic(newrefname, NULL, 0, NULL);
 +      lock = lock_ref_sha1_basic(newrefname, NULL, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for update", newrefname);
                goto rollback;
        return 0;
  
   rollback:
 -      lock = lock_ref_sha1_basic(oldrefname, NULL, 0, NULL);
 +      lock = lock_ref_sha1_basic(oldrefname, NULL, NULL, 0, NULL);
        if (!lock) {
                error("unable to lock %s for rollback", oldrefname);
                goto rollbacklog;
@@@ -2940,49 -2673,38 +2940,49 @@@ static int copy_msg(char *buf, const ch
        return cp - buf;
  }
  
 +/* This function must set a meaningful errno on failure */
  int log_ref_setup(const char *refname, char *logfile, int bufsize)
  {
        int logfd, oflags = O_APPEND | O_WRONLY;
  
        git_snpath(logfile, bufsize, "logs/%s", refname);
        if (log_all_ref_updates &&
 -          (!prefixcmp(refname, "refs/heads/") ||
 -           !prefixcmp(refname, "refs/remotes/") ||
 -           !prefixcmp(refname, "refs/notes/") ||
 +          (starts_with(refname, "refs/heads/") ||
 +           starts_with(refname, "refs/remotes/") ||
 +           starts_with(refname, "refs/notes/") ||
             !strcmp(refname, "HEAD"))) {
 -              if (safe_create_leading_directories(logfile) < 0)
 -                      return error("unable to create directory for %s",
 -                                   logfile);
 +              if (safe_create_leading_directories(logfile) < 0) {
 +                      int save_errno = errno;
 +                      error("unable to create directory for %s", logfile);
 +                      errno = save_errno;
 +                      return -1;
 +              }
                oflags |= O_CREAT;
        }
  
        logfd = open(logfile, oflags, 0666);
        if (logfd < 0) {
 -              if (!(oflags & O_CREAT) && errno == ENOENT)
 +              if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
                        return 0;
  
 -              if ((oflags & O_CREAT) && errno == EISDIR) {
 +              if (errno == EISDIR) {
                        if (remove_empty_directories(logfile)) {
 -                              return error("There are still logs under '%s'",
 -                                           logfile);
 +                              int save_errno = errno;
 +                              error("There are still logs under '%s'",
 +                                    logfile);
 +                              errno = save_errno;
 +                              return -1;
                        }
                        logfd = open(logfile, oflags, 0666);
                }
  
 -              if (logfd < 0)
 -                      return error("Unable to append to %s: %s",
 -                                   logfile, strerror(errno));
 +              if (logfd < 0) {
 +                      int save_errno = errno;
 +                      error("Unable to append to %s: %s", logfile,
 +                            strerror(errno));
 +                      errno = save_errno;
 +                      return -1;
 +              }
        }
  
        adjust_shared_perm(logfile);
@@@ -3022,41 -2744,24 +3022,41 @@@ static int log_ref_write(const char *re
                len += copy_msg(logrec + len - 1, msg) - 1;
        written = len <= maxlen ? write_in_full(logfd, logrec, len) : -1;
        free(logrec);
 -      if (close(logfd) != 0 || written != len)
 -              return error("Unable to append to %s", log_file);
 +      if (written != len) {
 +              int save_errno = errno;
 +              close(logfd);
 +              error("Unable to append to %s", log_file);
 +              errno = save_errno;
 +              return -1;
 +      }
 +      if (close(logfd)) {
 +              int save_errno = errno;
 +              error("Unable to append to %s", log_file);
 +              errno = save_errno;
 +              return -1;
 +      }
        return 0;
  }
  
 -static int is_branch(const char *refname)
 +int is_branch(const char *refname)
  {
 -      return !strcmp(refname, "HEAD") || !prefixcmp(refname, "refs/heads/");
 +      return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
  }
  
 -int write_ref_sha1(struct ref_lock *lock,
 +/*
 + * Write sha1 into the ref specified by the lock. Make sure that errno
 + * is sane on error.
 + */
 +static int write_ref_sha1(struct ref_lock *lock,
        const unsigned char *sha1, const char *logmsg)
  {
        static char term = '\n';
        struct object *o;
  
 -      if (!lock)
 +      if (!lock) {
 +              errno = EINVAL;
                return -1;
 +      }
        if (!lock->force_write && !hashcmp(lock->old_sha1, sha1)) {
                unlock_ref(lock);
                return 0;
                error("Trying to write ref %s with nonexistent object %s",
                        lock->ref_name, sha1_to_hex(sha1));
                unlock_ref(lock);
 +              errno = EINVAL;
                return -1;
        }
        if (o->type != OBJ_COMMIT && is_branch(lock->ref_name)) {
                error("Trying to write non-commit object %s to branch %s",
                        sha1_to_hex(sha1), lock->ref_name);
                unlock_ref(lock);
 +              errno = EINVAL;
                return -1;
        }
        if (write_in_full(lock->lock_fd, sha1_to_hex(sha1), 40) != 40 ||
 -          write_in_full(lock->lock_fd, &term, 1) != 1
 -              || close_ref(lock) < 0) {
 -              error("Couldn't write %s", lock->lk->filename);
 +          write_in_full(lock->lock_fd, &term, 1) != 1 ||
 +          close_ref(lock) < 0) {
 +              int save_errno = errno;
 +              error("Couldn't write %s", lock->lk->filename.buf);
                unlock_ref(lock);
 +              errno = save_errno;
                return -1;
        }
        clear_loose_ref_cache(&ref_cache);
                unsigned char head_sha1[20];
                int head_flag;
                const char *head_ref;
 -              head_ref = resolve_ref_unsafe("HEAD", head_sha1, 1, &head_flag);
 +              head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
 +                                            head_sha1, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name))
                        log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@@ -3186,137 -2886,122 +3186,137 @@@ int create_symref(const char *ref_targe
        return 0;
  }
  
 -static char *ref_msg(const char *line, const char *endp)
 -{
 -      const char *ep;
 -      line += 82;
 -      ep = memchr(line, '\n', endp - line);
 -      if (!ep)
 -              ep = endp;
 -      return xmemdupz(line, ep - line);
 +struct read_ref_at_cb {
 +      const char *refname;
 +      unsigned long at_time;
 +      int cnt;
 +      int reccnt;
 +      unsigned char *sha1;
 +      int found_it;
 +
 +      unsigned char osha1[20];
 +      unsigned char nsha1[20];
 +      int tz;
 +      unsigned long date;
 +      char **msg;
 +      unsigned long *cutoff_time;
 +      int *cutoff_tz;
 +      int *cutoff_cnt;
 +};
 +
 +static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
 +              const char *email, unsigned long timestamp, int tz,
 +              const char *message, void *cb_data)
 +{
 +      struct read_ref_at_cb *cb = cb_data;
 +
 +      cb->reccnt++;
 +      cb->tz = tz;
 +      cb->date = timestamp;
 +
 +      if (timestamp <= cb->at_time || cb->cnt == 0) {
 +              if (cb->msg)
 +                      *cb->msg = xstrdup(message);
 +              if (cb->cutoff_time)
 +                      *cb->cutoff_time = timestamp;
 +              if (cb->cutoff_tz)
 +                      *cb->cutoff_tz = tz;
 +              if (cb->cutoff_cnt)
 +                      *cb->cutoff_cnt = cb->reccnt - 1;
 +              /*
 +               * we have not yet updated cb->[n|o]sha1 so they still
 +               * hold the values for the previous record.
 +               */
 +              if (!is_null_sha1(cb->osha1)) {
 +                      hashcpy(cb->sha1, nsha1);
 +                      if (hashcmp(cb->osha1, nsha1))
 +                              warning("Log for ref %s has gap after %s.",
 +                                      cb->refname, show_date(cb->date, cb->tz, DATE_RFC2822));
 +              }
 +              else if (cb->date == cb->at_time)
 +                      hashcpy(cb->sha1, nsha1);
 +              else if (hashcmp(nsha1, cb->sha1))
 +                      warning("Log for ref %s unexpectedly ended on %s.",
 +                              cb->refname, show_date(cb->date, cb->tz,
 +                                                 DATE_RFC2822));
 +              hashcpy(cb->osha1, osha1);
 +              hashcpy(cb->nsha1, nsha1);
 +              cb->found_it = 1;
 +              return 1;
 +      }
 +      hashcpy(cb->osha1, osha1);
 +      hashcpy(cb->nsha1, nsha1);
 +      if (cb->cnt > 0)
 +              cb->cnt--;
 +      return 0;
 +}
 +
 +static int read_ref_at_ent_oldest(unsigned char *osha1, unsigned char *nsha1,
 +                                const char *email, unsigned long timestamp,
 +                                int tz, const char *message, void *cb_data)
 +{
 +      struct read_ref_at_cb *cb = cb_data;
 +
 +      if (cb->msg)
 +              *cb->msg = xstrdup(message);
 +      if (cb->cutoff_time)
 +              *cb->cutoff_time = timestamp;
 +      if (cb->cutoff_tz)
 +              *cb->cutoff_tz = tz;
 +      if (cb->cutoff_cnt)
 +              *cb->cutoff_cnt = cb->reccnt;
 +      hashcpy(cb->sha1, osha1);
 +      if (is_null_sha1(cb->sha1))
 +              hashcpy(cb->sha1, nsha1);
 +      /* We just want the first entry */
 +      return 1;
  }
  
 -int read_ref_at(const char *refname, unsigned long at_time, int cnt,
 +int read_ref_at(const char *refname, unsigned int flags, unsigned long at_time, int cnt,
                unsigned char *sha1, char **msg,
                unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt)
  {
 -      const char *logfile, *logdata, *logend, *rec, *lastgt, *lastrec;
 -      char *tz_c;
 -      int logfd, tz, reccnt = 0;
 -      struct stat st;
 -      unsigned long date;
 -      unsigned char logged_sha1[20];
 -      void *log_mapped;
 -      size_t mapsz;
 +      struct read_ref_at_cb cb;
  
 -      logfile = git_path("logs/%s", refname);
 -      logfd = open(logfile, O_RDONLY, 0);
 -      if (logfd < 0)
 -              die_errno("Unable to read log '%s'", logfile);
 -      fstat(logfd, &st);
 -      if (!st.st_size)
 -              die("Log %s is empty.", logfile);
 -      mapsz = xsize_t(st.st_size);
 -      log_mapped = xmmap(NULL, mapsz, PROT_READ, MAP_PRIVATE, logfd, 0);
 -      logdata = log_mapped;
 -      close(logfd);
 +      memset(&cb, 0, sizeof(cb));
 +      cb.refname = refname;
 +      cb.at_time = at_time;
 +      cb.cnt = cnt;
 +      cb.msg = msg;
 +      cb.cutoff_time = cutoff_time;
 +      cb.cutoff_tz = cutoff_tz;
 +      cb.cutoff_cnt = cutoff_cnt;
 +      cb.sha1 = sha1;
  
 -      lastrec = NULL;
 -      rec = logend = logdata + st.st_size;
 -      while (logdata < rec) {
 -              reccnt++;
 -              if (logdata < rec && *(rec-1) == '\n')
 -                      rec--;
 -              lastgt = NULL;
 -              while (logdata < rec && *(rec-1) != '\n') {
 -                      rec--;
 -                      if (*rec == '>')
 -                              lastgt = rec;
 -              }
 -              if (!lastgt)
 -                      die("Log %s is corrupt.", logfile);
 -              date = strtoul(lastgt + 1, &tz_c, 10);
 -              if (date <= at_time || cnt == 0) {
 -                      tz = strtoul(tz_c, NULL, 10);
 -                      if (msg)
 -                              *msg = ref_msg(rec, logend);
 -                      if (cutoff_time)
 -                              *cutoff_time = date;
 -                      if (cutoff_tz)
 -                              *cutoff_tz = tz;
 -                      if (cutoff_cnt)
 -                              *cutoff_cnt = reccnt - 1;
 -                      if (lastrec) {
 -                              if (get_sha1_hex(lastrec, logged_sha1))
 -                                      die("Log %s is corrupt.", logfile);
 -                              if (get_sha1_hex(rec + 41, sha1))
 -                                      die("Log %s is corrupt.", logfile);
 -                              if (hashcmp(logged_sha1, sha1)) {
 -                                      warning("Log %s has gap after %s.",
 -                                              logfile, show_date(date, tz, DATE_RFC2822));
 -                              }
 -                      }
 -                      else if (date == at_time) {
 -                              if (get_sha1_hex(rec + 41, sha1))
 -                                      die("Log %s is corrupt.", logfile);
 -                      }
 -                      else {
 -                              if (get_sha1_hex(rec + 41, logged_sha1))
 -                                      die("Log %s is corrupt.", logfile);
 -                              if (hashcmp(logged_sha1, sha1)) {
 -                                      warning("Log %s unexpectedly ended on %s.",
 -                                              logfile, show_date(date, tz, DATE_RFC2822));
 -                              }
 -                      }
 -                      munmap(log_mapped, mapsz);
 -                      return 0;
 -              }
 -              lastrec = rec;
 -              if (cnt > 0)
 -                      cnt--;
 -      }
 -
 -      rec = logdata;
 -      while (rec < logend && *rec != '>' && *rec != '\n')
 -              rec++;
 -      if (rec == logend || *rec == '\n')
 -              die("Log %s is corrupt.", logfile);
 -      date = strtoul(rec + 1, &tz_c, 10);
 -      tz = strtoul(tz_c, NULL, 10);
 -      if (get_sha1_hex(logdata, sha1))
 -              die("Log %s is corrupt.", logfile);
 -      if (is_null_sha1(sha1)) {
 -              if (get_sha1_hex(logdata + 41, sha1))
 -                      die("Log %s is corrupt.", logfile);
 +      for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
 +
 +      if (!cb.reccnt) {
 +              if (flags & GET_SHA1_QUIETLY)
 +                      exit(128);
 +              else
 +                      die("Log for %s is empty.", refname);
        }
 -      if (msg)
 -              *msg = ref_msg(logdata, logend);
 -      munmap(log_mapped, mapsz);
 -
 -      if (cutoff_time)
 -              *cutoff_time = date;
 -      if (cutoff_tz)
 -              *cutoff_tz = tz;
 -      if (cutoff_cnt)
 -              *cutoff_cnt = reccnt;
 +      if (cb.found_it)
 +              return 0;
 +
 +      for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
 +
        return 1;
  }
  
 +int reflog_exists(const char *refname)
 +{
 +      struct stat st;
 +
 +      return !lstat(git_path("logs/%s", refname), &st) &&
 +              S_ISREG(st.st_mode);
 +}
 +
 +int delete_reflog(const char *refname)
 +{
 +      return remove_path(git_path("logs/%s", refname));
 +}
 +
  static int show_one_reflog_ent(struct strbuf *sb, each_reflog_ent_fn fn, void *cb_data)
  {
        unsigned char osha1[20], nsha1[20];
@@@ -3404,29 -3089,54 +3404,54 @@@ int for_each_reflog_ent_reverse(const c
  
                        bp = find_beginning_of_line(buf, scanp);
  
-                       if (*bp != '\n') {
-                               strbuf_splice(&sb, 0, 0, buf, endp - buf);
-                               if (pos)
-                                       break; /* need to fill another block */
-                               scanp = buf - 1; /* leave loop */
-                       } else {
+                       if (*bp == '\n') {
                                /*
-                                * (bp + 1) thru endp is the beginning of the
-                                * current line we have in sb
+                                * The newline is the end of the previous line,
+                                * so we know we have complete line starting
+                                * at (bp + 1). Prefix it onto any prior data
+                                * we collected for the line and process it.
                                 */
                                strbuf_splice(&sb, 0, 0, bp + 1, endp - (bp + 1));
                                scanp = bp;
                                endp = bp + 1;
+                               ret = show_one_reflog_ent(&sb, fn, cb_data);
+                               strbuf_reset(&sb);
+                               if (ret)
+                                       break;
+                       } else if (!pos) {
+                               /*
+                                * We are at the start of the buffer, and the
+                                * start of the file; there is no previous
+                                * line, and we have everything for this one.
+                                * Process it, and we can end the loop.
+                                */
+                               strbuf_splice(&sb, 0, 0, buf, endp - buf);
+                               ret = show_one_reflog_ent(&sb, fn, cb_data);
+                               strbuf_reset(&sb);
+                               break;
                        }
-                       ret = show_one_reflog_ent(&sb, fn, cb_data);
-                       strbuf_reset(&sb);
-                       if (ret)
+                       if (bp == buf) {
+                               /*
+                                * We are at the start of the buffer, and there
+                                * is more file to read backwards. Which means
+                                * we are in the middle of a line. Note that we
+                                * may get here even if *bp was a newline; that
+                                * just means we are at the exact end of the
+                                * previous line, rather than some spot in the
+                                * middle.
+                                *
+                                * Save away what we have to be combined with
+                                * the data from the next read.
+                                */
+                               strbuf_splice(&sb, 0, 0, buf, endp - buf);
                                break;
+                       }
                }
  
        }
        if (!ret && sb.len)
-               ret = show_one_reflog_ent(&sb, fn, cb_data);
+               die("BUG: reverse reflog parser had leftover data");
  
        fclose(logfp);
        strbuf_release(&sb);
@@@ -3469,7 -3179,7 +3494,7 @@@ static int do_for_each_reflog(struct st
  
                if (de->d_name[0] == '.')
                        continue;
 -              if (has_extension(de->d_name, ".lock"))
 +              if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(name, de->d_name);
                if (stat(git_path("logs/%s", name->buf), &st) < 0) {
                                retval = do_for_each_reflog(name, fn, cb_data);
                        } else {
                                unsigned char sha1[20];
 -                              if (read_ref_full(name->buf, sha1, 0, NULL))
 +                              if (read_ref_full(name->buf, 0, sha1, NULL))
                                        retval = error("bad ref for %s", name->buf);
                                else
                                        retval = fn(name->buf, sha1, 0, cb_data);
@@@ -3504,177 -3214,37 +3529,177 @@@ int for_each_reflog(each_ref_fn fn, voi
        return retval;
  }
  
 -static struct ref_lock *update_ref_lock(const char *refname,
 -                                      const unsigned char *oldval,
 -                                      int flags, int *type_p,
 -                                      enum action_on_err onerr)
 -{
 +/**
 + * Information needed for a single ref update.  Set new_sha1 to the
 + * new value or to zero to delete the ref.  To check the old value
 + * while locking the ref, set have_old to 1 and set old_sha1 to the
 + * value or to zero to ensure the ref does not exist before update.
 + */
 +struct ref_update {
 +      unsigned char new_sha1[20];
 +      unsigned char old_sha1[20];
 +      int flags; /* REF_NODEREF? */
 +      int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
        struct ref_lock *lock;
 -      lock = lock_any_ref_for_update(refname, oldval, flags, type_p);
 -      if (!lock) {
 -              const char *str = "Cannot lock the ref '%s'.";
 -              switch (onerr) {
 -              case MSG_ON_ERR: error(str, refname); break;
 -              case DIE_ON_ERR: die(str, refname); break;
 -              case QUIET_ON_ERR: break;
 -              }
 +      int type;
 +      char *msg;
 +      const char refname[FLEX_ARRAY];
 +};
 +
 +/*
 + * Transaction states.
 + * OPEN:   The transaction is in a valid state and can accept new updates.
 + *         An OPEN transaction can be committed.
 + * CLOSED: A closed transaction is no longer active and no other operations
 + *         than free can be used on it in this state.
 + *         A transaction can either become closed by successfully committing
 + *         an active transaction or if there is a failure while building
 + *         the transaction thus rendering it failed/inactive.
 + */
 +enum ref_transaction_state {
 +      REF_TRANSACTION_OPEN   = 0,
 +      REF_TRANSACTION_CLOSED = 1
 +};
 +
 +/*
 + * Data structure for holding a reference transaction, which can
 + * consist of checks and updates to multiple references, carried out
 + * as atomically as possible.  This structure is opaque to callers.
 + */
 +struct ref_transaction {
 +      struct ref_update **updates;
 +      size_t alloc;
 +      size_t nr;
 +      enum ref_transaction_state state;
 +};
 +
 +struct ref_transaction *ref_transaction_begin(struct strbuf *err)
 +{
 +      assert(err);
 +
 +      return xcalloc(1, sizeof(struct ref_transaction));
 +}
 +
 +void ref_transaction_free(struct ref_transaction *transaction)
 +{
 +      int i;
 +
 +      if (!transaction)
 +              return;
 +
 +      for (i = 0; i < transaction->nr; i++) {
 +              free(transaction->updates[i]->msg);
 +              free(transaction->updates[i]);
        }
 -      return lock;
 +      free(transaction->updates);
 +      free(transaction);
  }
  
 -static int update_ref_write(const char *action, const char *refname,
 -                          const unsigned char *sha1, struct ref_lock *lock,
 -                          enum action_on_err onerr)
 +static struct ref_update *add_update(struct ref_transaction *transaction,
 +                                   const char *refname)
  {
 -      if (write_ref_sha1(lock, sha1, action) < 0) {
 -              const char *str = "Cannot update the ref '%s'.";
 -              switch (onerr) {
 -              case MSG_ON_ERR: error(str, refname); break;
 -              case DIE_ON_ERR: die(str, refname); break;
 -              case QUIET_ON_ERR: break;
 -              }
 -              return 1;
 +      size_t len = strlen(refname);
 +      struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
 +
 +      strcpy((char *)update->refname, refname);
 +      ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
 +      transaction->updates[transaction->nr++] = update;
 +      return update;
 +}
 +
 +int ref_transaction_update(struct ref_transaction *transaction,
 +                         const char *refname,
 +                         const unsigned char *new_sha1,
 +                         const unsigned char *old_sha1,
 +                         int flags, int have_old, const char *msg,
 +                         struct strbuf *err)
 +{
 +      struct ref_update *update;
 +
 +      assert(err);
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: update called for transaction that is not open");
 +
 +      if (have_old && !old_sha1)
 +              die("BUG: have_old is true but old_sha1 is NULL");
 +
 +      if (!is_null_sha1(new_sha1) &&
 +          check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +              strbuf_addf(err, "refusing to update ref with bad name %s",
 +                          refname);
 +              return -1;
        }
 +
 +      update = add_update(transaction, refname);
 +      hashcpy(update->new_sha1, new_sha1);
 +      update->flags = flags;
 +      update->have_old = have_old;
 +      if (have_old)
 +              hashcpy(update->old_sha1, old_sha1);
 +      if (msg)
 +              update->msg = xstrdup(msg);
 +      return 0;
 +}
 +
 +int ref_transaction_create(struct ref_transaction *transaction,
 +                         const char *refname,
 +                         const unsigned char *new_sha1,
 +                         int flags, const char *msg,
 +                         struct strbuf *err)
 +{
 +      struct ref_update *update;
 +
 +      assert(err);
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: create called for transaction that is not open");
 +
 +      if (!new_sha1 || is_null_sha1(new_sha1))
 +              die("BUG: create ref with null new_sha1");
 +
 +      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +              strbuf_addf(err, "refusing to create ref with bad name %s",
 +                          refname);
 +              return -1;
 +      }
 +
 +      update = add_update(transaction, refname);
 +
 +      hashcpy(update->new_sha1, new_sha1);
 +      hashclr(update->old_sha1);
 +      update->flags = flags;
 +      update->have_old = 1;
 +      if (msg)
 +              update->msg = xstrdup(msg);
 +      return 0;
 +}
 +
 +int ref_transaction_delete(struct ref_transaction *transaction,
 +                         const char *refname,
 +                         const unsigned char *old_sha1,
 +                         int flags, int have_old, const char *msg,
 +                         struct strbuf *err)
 +{
 +      struct ref_update *update;
 +
 +      assert(err);
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: delete called for transaction that is not open");
 +
 +      if (have_old && !old_sha1)
 +              die("BUG: have_old is true but old_sha1 is NULL");
 +
 +      update = add_update(transaction, refname);
 +      update->flags = flags;
 +      update->have_old = have_old;
 +      if (have_old) {
 +              assert(!is_null_sha1(old_sha1));
 +              hashcpy(update->old_sha1, old_sha1);
 +      }
 +      if (msg)
 +              update->msg = xstrdup(msg);
        return 0;
  }
  
@@@ -3682,161 -3252,136 +3707,161 @@@ int update_ref(const char *action, cons
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
  {
 -      struct ref_lock *lock;
 -      lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
 -      if (!lock)
 +      struct ref_transaction *t;
 +      struct strbuf err = STRBUF_INIT;
 +
 +      t = ref_transaction_begin(&err);
 +      if (!t ||
 +          ref_transaction_update(t, refname, sha1, oldval, flags,
 +                                 !!oldval, action, &err) ||
 +          ref_transaction_commit(t, &err)) {
 +              const char *str = "update_ref failed for ref '%s': %s";
 +
 +              ref_transaction_free(t);
 +              switch (onerr) {
 +              case UPDATE_REFS_MSG_ON_ERR:
 +                      error(str, refname, err.buf);
 +                      break;
 +              case UPDATE_REFS_DIE_ON_ERR:
 +                      die(str, refname, err.buf);
 +                      break;
 +              case UPDATE_REFS_QUIET_ON_ERR:
 +                      break;
 +              }
 +              strbuf_release(&err);
                return 1;
 -      return update_ref_write(action, refname, sha1, lock, onerr);
 +      }
 +      strbuf_release(&err);
 +      ref_transaction_free(t);
 +      return 0;
  }
  
  static int ref_update_compare(const void *r1, const void *r2)
  {
        const struct ref_update * const *u1 = r1;
        const struct ref_update * const *u2 = r2;
 -      return strcmp((*u1)->ref_name, (*u2)->ref_name);
 +      return strcmp((*u1)->refname, (*u2)->refname);
  }
  
  static int ref_update_reject_duplicates(struct ref_update **updates, int n,
 -                                      enum action_on_err onerr)
 +                                      struct strbuf *err)
  {
        int i;
 +
 +      assert(err);
 +
        for (i = 1; i < n; i++)
 -              if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
 -                      const char *str =
 -                              "Multiple updates for ref '%s' not allowed.";
 -                      switch (onerr) {
 -                      case MSG_ON_ERR:
 -                              error(str, updates[i]->ref_name); break;
 -                      case DIE_ON_ERR:
 -                              die(str, updates[i]->ref_name); break;
 -                      case QUIET_ON_ERR:
 -                              break;
 -                      }
 +              if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
 +                      strbuf_addf(err,
 +                                  "Multiple updates for ref '%s' not allowed.",
 +                                  updates[i]->refname);
                        return 1;
                }
        return 0;
  }
  
 -int update_refs(const char *action, const struct ref_update **updates_orig,
 -              int n, enum action_on_err onerr)
 +int ref_transaction_commit(struct ref_transaction *transaction,
 +                         struct strbuf *err)
  {
        int ret = 0, delnum = 0, i;
 -      struct ref_update **updates;
 -      int *types;
 -      struct ref_lock **locks;
        const char **delnames;
 +      int n = transaction->nr;
 +      struct ref_update **updates = transaction->updates;
 +
 +      assert(err);
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: commit called for transaction that is not open");
  
 -      if (!updates_orig || !n)
 +      if (!n) {
 +              transaction->state = REF_TRANSACTION_CLOSED;
                return 0;
 +      }
  
        /* Allocate work space */
 -      updates = xmalloc(sizeof(*updates) * n);
 -      types = xmalloc(sizeof(*types) * n);
 -      locks = xcalloc(n, sizeof(*locks));
        delnames = xmalloc(sizeof(*delnames) * n);
  
        /* Copy, sort, and reject duplicate refs */
 -      memcpy(updates, updates_orig, sizeof(*updates) * n);
        qsort(updates, n, sizeof(*updates), ref_update_compare);
 -      ret = ref_update_reject_duplicates(updates, n, onerr);
 -      if (ret)
 +      if (ref_update_reject_duplicates(updates, n, err)) {
 +              ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
 +      }
  
        /* Acquire all locks while verifying old values */
        for (i = 0; i < n; i++) {
 -              locks[i] = update_ref_lock(updates[i]->ref_name,
 -                                         (updates[i]->have_old ?
 -                                          updates[i]->old_sha1 : NULL),
 -                                         updates[i]->flags,
 -                                         &types[i], onerr);
 -              if (!locks[i]) {
 -                      ret = 1;
 +              struct ref_update *update = updates[i];
 +              int flags = update->flags;
 +
 +              if (is_null_sha1(update->new_sha1))
 +                      flags |= REF_DELETING;
 +              update->lock = lock_ref_sha1_basic(update->refname,
 +                                                 (update->have_old ?
 +                                                  update->old_sha1 :
 +                                                  NULL),
 +                                                 NULL,
 +                                                 flags,
 +                                                 &update->type);
 +              if (!update->lock) {
 +                      ret = (errno == ENOTDIR)
 +                              ? TRANSACTION_NAME_CONFLICT
 +                              : TRANSACTION_GENERIC_ERROR;
 +                      strbuf_addf(err, "Cannot lock the ref '%s'.",
 +                                  update->refname);
                        goto cleanup;
                }
        }
  
        /* Perform updates first so live commits remain referenced */
 -      for (i = 0; i < n; i++)
 -              if (!is_null_sha1(updates[i]->new_sha1)) {
 -                      ret = update_ref_write(action,
 -                                             updates[i]->ref_name,
 -                                             updates[i]->new_sha1,
 -                                             locks[i], onerr);
 -                      locks[i] = NULL; /* freed by update_ref_write */
 -                      if (ret)
 +      for (i = 0; i < n; i++) {
 +              struct ref_update *update = updates[i];
 +
 +              if (!is_null_sha1(update->new_sha1)) {
 +                      if (write_ref_sha1(update->lock, update->new_sha1,
 +                                         update->msg)) {
 +                              update->lock = NULL; /* freed by write_ref_sha1 */
 +                              strbuf_addf(err, "Cannot update the ref '%s'.",
 +                                          update->refname);
 +                              ret = TRANSACTION_GENERIC_ERROR;
                                goto cleanup;
 +                      }
 +                      update->lock = NULL; /* freed by write_ref_sha1 */
                }
 +      }
  
        /* Perform deletes now that updates are safely completed */
 -      for (i = 0; i < n; i++)
 -              if (locks[i]) {
 -                      delnames[delnum++] = locks[i]->ref_name;
 -                      ret |= delete_ref_loose(locks[i], types[i]);
 +      for (i = 0; i < n; i++) {
 +              struct ref_update *update = updates[i];
 +
 +              if (update->lock) {
 +                      if (delete_ref_loose(update->lock, update->type, err)) {
 +                              ret = TRANSACTION_GENERIC_ERROR;
 +                              goto cleanup;
 +                      }
 +
 +                      if (!(update->flags & REF_ISPRUNING))
 +                              delnames[delnum++] = update->lock->ref_name;
                }
 -      ret |= repack_without_refs(delnames, delnum);
 +      }
 +
 +      if (repack_without_refs(delnames, delnum, err)) {
 +              ret = TRANSACTION_GENERIC_ERROR;
 +              goto cleanup;
 +      }
        for (i = 0; i < delnum; i++)
                unlink_or_warn(git_path("logs/%s", delnames[i]));
        clear_loose_ref_cache(&ref_cache);
  
  cleanup:
 +      transaction->state = REF_TRANSACTION_CLOSED;
 +
        for (i = 0; i < n; i++)
 -              if (locks[i])
 -                      unlock_ref(locks[i]);
 -      free(updates);
 -      free(types);
 -      free(locks);
 +              if (updates[i]->lock)
 +                      unlock_ref(updates[i]->lock);
        free(delnames);
        return ret;
  }
  
 -/*
 - * generate a format suitable for scanf from a ref_rev_parse_rules
 - * rule, that is replace the "%.*s" spec with a "%s" spec
 - */
 -static void gen_scanf_fmt(char *scanf_fmt, const char *rule)
 -{
 -      char *spec;
 -
 -      spec = strstr(rule, "%.*s");
 -      if (!spec || strstr(spec + 4, "%.*s"))
 -              die("invalid rule in ref_rev_parse_rules: %s", rule);
 -
 -      /* copy all until spec */
 -      strncpy(scanf_fmt, rule, spec - rule);
 -      scanf_fmt[spec - rule] = '\0';
 -      /* copy new spec */
 -      strcat(scanf_fmt, "%s");
 -      /* copy remaining rule */
 -      strcat(scanf_fmt, spec + 4);
 -
 -      return;
 -}
 -
  char *shorten_unambiguous_ref(const char *refname, int strict)
  {
        int i;
        static int nr_rules;
        char *short_name;
  
 -      /* pre generate scanf formats from ref_rev_parse_rules[] */
        if (!nr_rules) {
 +              /*
 +               * Pre-generate scanf formats from ref_rev_parse_rules[].
 +               * Generate a format suitable for scanf from a
 +               * ref_rev_parse_rules rule by interpolating "%s" at the
 +               * location of the "%.*s".
 +               */
                size_t total_len = 0;
 +              size_t offset = 0;
  
                /* the rule list is NULL terminated, count them first */
                for (nr_rules = 0; ref_rev_parse_rules[nr_rules]; nr_rules++)
 -                      /* no +1 because strlen("%s") < strlen("%.*s") */
 -                      total_len += strlen(ref_rev_parse_rules[nr_rules]);
 +                      /* -2 for strlen("%.*s") - strlen("%s"); +1 for NUL */
 +                      total_len += strlen(ref_rev_parse_rules[nr_rules]) - 2 + 1;
  
                scanf_fmts = xmalloc(nr_rules * sizeof(char *) + total_len);
  
 -              total_len = 0;
 +              offset = 0;
                for (i = 0; i < nr_rules; i++) {
 -                      scanf_fmts[i] = (char *)&scanf_fmts[nr_rules]
 -                                      + total_len;
 -                      gen_scanf_fmt(scanf_fmts[i], ref_rev_parse_rules[i]);
 -                      total_len += strlen(ref_rev_parse_rules[i]);
 +                      assert(offset < total_len);
 +                      scanf_fmts[i] = (char *)&scanf_fmts[nr_rules] + offset;
 +                      offset += snprintf(scanf_fmts[i], total_len - offset,
 +                                         ref_rev_parse_rules[i], 2, "%s") + 1;
                }
        }
  
@@@ -3936,7 -3475,7 +3961,7 @@@ int parse_hide_refs_config(const char *
  {
        if (!strcmp("transfer.hiderefs", var) ||
            /* NEEDSWORK: use parse_config_key() once both are merged */
 -          (!prefixcmp(var, section) && var[strlen(section)] == '.' &&
 +          (starts_with(var, section) && var[strlen(section)] == '.' &&
             !strcmp(var + strlen(section), ".hiderefs"))) {
                char *ref;
                int len;
@@@ -3964,7 -3503,7 +3989,7 @@@ int ref_is_hidden(const char *refname
                return 0;
        for_each_string_list_item(item, hide_refs) {
                int len;
 -              if (prefixcmp(refname, item->string))
 +              if (!starts_with(refname, item->string))
                        continue;
                len = strlen(item->string);
                if (!refname[len] || refname[len] == '/')
diff --combined t/t1410-reflog.sh
index 8cf446165eff431106dd86d01754633fbd3ce246,e582c0182bba81316352dc69e1cc7c6b664c68e9..779d4e3829b29d7092d54f6e342f923f0d239b96
@@@ -245,46 -245,34 +245,76 @@@ test_expect_success 'gc.reflogexpire=fa
  
  '
  
 +test_expect_success 'checkout should not delete log for packed ref' '
 +      test $(git reflog master | wc -l) = 4 &&
 +      git branch foo &&
 +      git pack-refs --all &&
 +      git checkout foo &&
 +      test $(git reflog master | wc -l) = 4
 +'
 +
 +test_expect_success 'stale dirs do not cause d/f conflicts (reflogs on)' '
 +      test_when_finished "git branch -d one || git branch -d one/two" &&
 +
 +      git branch one/two master &&
 +      echo "one/two@{0} branch: Created from master" >expect &&
 +      git log -g --format="%gd %gs" one/two >actual &&
 +      test_cmp expect actual &&
 +      git branch -d one/two &&
 +
 +      # now logs/refs/heads/one is a stale directory, but
 +      # we should move it out of the way to create "one" reflog
 +      git branch one master &&
 +      echo "one@{0} branch: Created from master" >expect &&
 +      git log -g --format="%gd %gs" one >actual &&
 +      test_cmp expect actual
 +'
 +
 +test_expect_success 'stale dirs do not cause d/f conflicts (reflogs off)' '
 +      test_when_finished "git branch -d one || git branch -d one/two" &&
 +
 +      git branch one/two master &&
 +      echo "one/two@{0} branch: Created from master" >expect &&
 +      git log -g --format="%gd %gs" one/two >actual &&
 +      test_cmp expect actual &&
 +      git branch -d one/two &&
 +
 +      # same as before, but we only create a reflog for "one" if
 +      # it already exists, which it does not
 +      git -c core.logallrefupdates=false branch one master &&
 +      : >expect &&
 +      git log -g --format="%gd %gs" one >actual &&
 +      test_cmp expect actual
 +'
 +
+ # Triggering the bug detected by this test requires a newline to fall
+ # exactly BUFSIZ-1 bytes from the end of the file. We don't know
+ # what that value is, since it's platform dependent. However, if
+ # we choose some value N, we also catch any D which divides N evenly
+ # (since we will read backwards in chunks of D). So we choose 8K,
+ # which catches glibc (with an 8K BUFSIZ) and *BSD (1K).
+ #
+ # Each line is 114 characters, so we need 75 to still have a few before the
+ # last 8K. The 89-character padding on the final entry lines up our
+ # newline exactly.
+ test_expect_success 'parsing reverse reflogs at BUFSIZ boundaries' '
+       git checkout -b reflogskip &&
+       z38=00000000000000000000000000000000000000 &&
+       ident="abc <xyz> 0000000001 +0000" &&
+       for i in $(test_seq 1 75); do
+               printf "$z38%02d $z38%02d %s\t" $i $(($i+1)) "$ident" &&
+               if test $i = 75; then
+                       for j in $(test_seq 1 89); do
+                               printf X
+                       done
+               else
+                       printf X
+               fi &&
+               printf "\n"
+       done >.git/logs/refs/heads/reflogskip &&
+       git rev-parse reflogskip@{73} >actual &&
+       echo ${z38}03 >expect &&
+       test_cmp expect actual
+ '
  test_done