Merge branch 'jk/read-packed-refs-without-path-max'
authorJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:28:04 +0000 (12:28 -0800)
committerJunio C Hamano <gitster@pobox.com>
Mon, 22 Dec 2014 20:28:04 +0000 (12:28 -0800)
Git did not correctly read an overlong refname from a packed refs
file.

* jk/read-packed-refs-without-path-max:
read_packed_refs: use skip_prefix instead of static array
read_packed_refs: pass strbuf to parse_ref_line
read_packed_refs: use a strbuf for reading lines

1  2 
refs.c
diff --combined refs.c
index bfe4ba4213d445189b564726a9acbaa0924e1370,ba3003ffd2bd63a1741771e63ed296de69f4e607..5fcacc6c41e6c7033367cc9c4cf7c18b33eea286
--- 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"
@@@ -25,11 -24,6 +25,11 @@@ static unsigned char refname_dispositio
        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
@@@ -70,10 -64,17 +70,10 @@@ static int check_refname_component(cons
  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 -188,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 -275,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);
@@@ -813,121 -779,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;
  }
  
@@@ -1068,8 -973,10 +1068,10 @@@ static const char PACKED_REFS_HEADER[] 
   * Return a pointer to the refname within the line (null-terminated),
   * or NULL if there was a problem.
   */
- static const char *parse_ref_line(char *line, unsigned char *sha1)
+ static const char *parse_ref_line(struct strbuf *line, unsigned char *sha1)
  {
+       const char *ref;
        /*
         * 42: the answer to everything.
         *
         *  +1 (space in between hex and name)
         *  +1 (newline at the end of the line)
         */
-       int len = strlen(line) - 42;
-       if (len <= 0)
+       if (line->len <= 42)
                return NULL;
-       if (get_sha1_hex(line, sha1) < 0)
+       if (get_sha1_hex(line->buf, sha1) < 0)
                return NULL;
-       if (!isspace(line[40]))
+       if (!isspace(line->buf[40]))
                return NULL;
-       line += 41;
-       if (isspace(*line))
+       ref = line->buf + 41;
+       if (isspace(*ref))
                return NULL;
-       if (line[len] != '\n')
+       if (line->buf[line->len - 1] != '\n')
                return NULL;
-       line[len] = 0;
+       line->buf[--line->len] = 0;
  
-       return line;
+       return ref;
  }
  
  /*
  static void read_packed_refs(FILE *f, struct ref_dir *dir)
  {
        struct ref_entry *last = NULL;
-       char refline[PATH_MAX];
+       struct strbuf line = STRBUF_INIT;
        enum { PEELED_NONE, PEELED_TAGS, PEELED_FULLY } peeled = PEELED_NONE;
  
-       while (fgets(refline, sizeof(refline), f)) {
+       while (strbuf_getwholeline(&line, f, '\n') != EOF) {
                unsigned char sha1[20];
                const char *refname;
-               static const char header[] = "# pack-refs with:";
+               const char *traits;
  
-               if (!strncmp(refline, header, sizeof(header)-1)) {
-                       const char *traits = refline + sizeof(header) - 1;
+               if (skip_prefix(line.buf, "# pack-refs with:", &traits)) {
                        if (strstr(traits, " fully-peeled "))
                                peeled = PEELED_FULLY;
                        else if (strstr(traits, " peeled "))
                        continue;
                }
  
-               refname = parse_ref_line(refline, sha1);
+               refname = parse_ref_line(&line, 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 && starts_with(refname, "refs/tags/")))
                                last->flag |= REF_KNOWS_PEELED;
                        continue;
                }
                if (last &&
-                   refline[0] == '^' &&
-                   strlen(refline) == PEELED_LINE_LENGTH &&
-                   refline[PEELED_LINE_LENGTH - 1] == '\n' &&
-                   !get_sha1_hex(refline + 1, sha1)) {
+                   line.buf[0] == '^' &&
+                   line.len == PEELED_LINE_LENGTH &&
+                   line.buf[PEELED_LINE_LENGTH - 1] == '\n' &&
+                   !get_sha1_hex(line.buf + 1, sha1)) {
                        hashcpy(last->u.value.peeled, sha1);
                        /*
                         * Regardless of what the file header said,
                        last->flag |= REF_KNOWS_PEELED;
                }
        }
+       strbuf_release(&line);
  }
  
  /*
@@@ -1284,19 -1187,12 +1288,19 @@@ static void read_loose_refs(const char 
                                        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);
        }
@@@ -1415,10 -1311,10 +1419,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;
        }
  }
  
  /* This function needs to return a meaningful errno on failure */
 -const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int reading, int *flag)
 +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)) {
 -              errno = EINVAL;
 -              return NULL;
 -      }
 +              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;
                 */
        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 */
                                        !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;
                        }
                }
                         */
                        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;
 -                      errno = EINVAL;
 -                      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,22 -1481,22 +1629,22 @@@ 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,
@@@ -1753,7 -1609,7 +1757,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;
  
        /*
@@@ -1794,7 -1650,7 +1798,7 @@@ static int warn_if_dangling_symref(cons
        if (!(flags & REF_ISSYMREF))
                return 0;
  
 -      resolves_to = resolve_ref_unsafe(refname, junk, 0, NULL);
 +      resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);
        if (!resolves_to
            || (d->refname
                ? strcmp(resolves_to, d->refname)
@@@ -1919,7 -1775,7 +1923,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 -1855,7 +2003,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);
  
@@@ -2094,9 -1950,7 +2098,9 @@@ int refname_match(const char *abbrev_na
  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);
@@@ -2169,8 -2023,7 +2173,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);
@@@ -2199,8 -2052,7 +2203,8 @@@ int dwim_log(const char *str, int len, 
                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 (reflog_exists(path))
        return logs_found;
  }
  
 -/* This function should make sure errno is meaningful on error */
 +/*
 + * 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;
        }
        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);
  
        lock->lock_fd = hold_lock_file_for_update(lock->lk, ref_file, lflags);
        if (lock->lock_fd < 0) {
 +              last_errno = errno;
                if (errno == ENOENT && --attempts_remaining > 0)
                        /*
                         * Maybe somebody just deleted one of the
                         * again:
                         */
                        goto retry;
 -              else
 -                      unable_to_lock_index_die(ref_file, errno);
 +              else {
 +                      struct strbuf err = STRBUF_INIT;
 +                      unable_to_lock_message(ref_file, errno, &err);
 +                      error("%s", err.buf);
 +                      strbuf_reset(&err);
 +                      goto error_return;
 +              }
        }
        return old_sha1 ? verify_lock(lock, old_sha1, mustexist) : lock;
  
        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;
@@@ -2407,19 -2263,15 +2411,19 @@@ int commit_packed_refs(void
                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);
 +                               0, write_packed_entry_fn, out);
 +
        if (commit_lock_file(packed_ref_cache->lock)) {
                save_errno = errno;
                error = -1;
@@@ -2539,25 -2391,14 +2543,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_any_ref_for_update(r->name, r->sha1,
 -                                                      0, NULL);
 +      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)
@@@ -2613,7 -2454,7 +2617,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;
  }
  
 -int repack_without_refs(const char **refnames, int n, struct strbuf *err)
 +int repack_without_refs(struct string_list *refnames, 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, ret, removed = 0;
 +      struct string_list_item *refname, *ref_to_delete;
 +      int ret, needs_repacking = 0, removed = 0;
 +
 +      assert(err);
  
        /* Look for a packed ref */
 -      for (i = 0; i < n; i++)
 -              if (get_packed_ref(refnames[i]))
 +      for_each_string_list_item(refname, refnames) {
 +              if (get_packed_ref(refname->string)) {
 +                      needs_repacking = 1;
                        break;
 +              }
 +      }
  
        /* Avoid locking if we have nothing to do */
 -      if (i == n)
 +      if (!needs_repacking)
                return 0; /* no refname exists in packed refs */
  
        if (lock_packed_refs(0)) {
 -              if (err) {
 -                      unable_to_lock_message(git_path("packed-refs"), errno,
 -                                             err);
 -                      return -1;
 -              }
 -              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);
  
        /* Remove refnames from the cache */
 -      for (i = 0; i < n; i++)
 -              if (remove_entry(packed, refnames[i]) != -1)
 +      for_each_string_list_item(refname, refnames)
 +              if (remove_entry(packed, refname->string) != -1)
                        removed = 1;
        if (!removed) {
                /*
  
        /* Write what remains */
        ret = commit_packed_refs();
 -      if (ret && err)
 +      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, NULL);
 -}
 +      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;
  }
  
  /*
@@@ -2792,21 -2637,6 +2796,21 @@@ static int rename_tmp_log(const char *n
        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))) {
  
        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;
@@@ -2971,10 -2803,10 +2975,10 @@@ int log_ref_setup(const char *refname, 
  
        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)) {
                                int save_errno = errno;
                                error("There are still logs under '%s'",
@@@ -3052,11 -2884,8 +3056,11 @@@ int is_branch(const char *refname
        return !strcmp(refname, "HEAD") || starts_with(refname, "refs/heads/");
  }
  
 -/* This function must return a meaningful errno */
 -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';
            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);
 +              error("Couldn't write %s", lock->lk->filename.buf);
                unlock_ref(lock);
                errno = save_errno;
                return -1;
                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);
@@@ -3281,7 -3109,7 +3285,7 @@@ static int read_ref_at_ent_oldest(unsig
        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)
  {
  
        for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
  
 -      if (!cb.reccnt)
 -              die("Log for %s is empty.", refname);
 +      if (!cb.reccnt) {
 +              if (flags & GET_SHA1_QUIETLY)
 +                      exit(128);
 +              else
 +                      die("Log for %s is empty.", refname);
 +      }
        if (cb.found_it)
                return 0;
  
@@@ -3413,54 -3237,29 +3417,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);
@@@ -3514,7 -3313,7 +3518,7 @@@ static int do_for_each_reflog(struct st
                                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);
@@@ -3538,6 -3337,43 +3542,6 @@@ 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)
 -{
 -      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 UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 -              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 -              case UPDATE_REFS_QUIET_ON_ERR: break;
 -              }
 -      }
 -      return lock;
 -}
 -
 -static int update_ref_write(const char *action, const char *refname,
 -                          const unsigned char *sha1, struct ref_lock *lock,
 -                          struct strbuf *err, enum action_on_err onerr)
 -{
 -      if (write_ref_sha1(lock, sha1, action) < 0) {
 -              const char *str = "Cannot update the ref '%s'.";
 -              if (err)
 -                      strbuf_addf(err, str, refname);
 -
 -              switch (onerr) {
 -              case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
 -              case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
 -              case UPDATE_REFS_QUIET_ON_ERR: break;
 -              }
 -              return 1;
 -      }
 -      return 0;
 -}
 -
  /**
   * 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
@@@ -3551,25 -3387,9 +3555,25 @@@ struct ref_update 
        int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
        struct ref_lock *lock;
        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
@@@ -3579,13 -3399,10 +3583,13 @@@ struct ref_transaction 
        struct ref_update **updates;
        size_t alloc;
        size_t nr;
 +      enum ref_transaction_state state;
  };
  
 -struct ref_transaction *ref_transaction_begin(void)
 +struct ref_transaction *ref_transaction_begin(struct strbuf *err)
  {
 +      assert(err);
 +
        return xcalloc(1, sizeof(struct ref_transaction));
  }
  
@@@ -3596,10 -3413,9 +3600,10 @@@ void ref_transaction_free(struct ref_tr
        if (!transaction)
                return;
  
 -      for (i = 0; i < transaction->nr; i++)
 +      for (i = 0; i < transaction->nr; i++) {
 +              free(transaction->updates[i]->msg);
                free(transaction->updates[i]);
 -
 +      }
        free(transaction->updates);
        free(transaction);
  }
@@@ -3620,129 -3436,61 +3624,129 @@@ int ref_transaction_update(struct ref_t
                           const char *refname,
                           const unsigned char *new_sha1,
                           const unsigned char *old_sha1,
 -                         int flags, int have_old,
 +                         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;
  }
  
 -void ref_transaction_create(struct ref_transaction *transaction,
 -                          const char *refname,
 -                          const unsigned char *new_sha1,
 -                          int flags)
 +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 = add_update(transaction, refname);
 +      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);
  
 -      assert(!is_null_sha1(new_sha1));
        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;
  }
  
 -void ref_transaction_delete(struct ref_transaction *transaction,
 -                          const char *refname,
 -                          const unsigned char *old_sha1,
 -                          int flags, int have_old)
 +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 = add_update(transaction, refname);
 +      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;
  }
  
  int update_ref(const char *action, const char *refname,
               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, NULL, onerr);
 +      }
 +      strbuf_release(&err);
 +      ref_transaction_free(t);
 +      return 0;
  }
  
  static int ref_update_compare(const void *r1, const void *r2)
@@@ -3756,65 -3504,53 +3760,65 @@@ static int ref_update_reject_duplicates
                                        struct strbuf *err)
  {
        int i;
 +
 +      assert(err);
 +
        for (i = 1; i < n; i++)
                if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
 -                      const char *str =
 -                              "Multiple updates for ref '%s' not allowed.";
 -                      if (err)
 -                              strbuf_addf(err, str, updates[i]->refname);
 -
 +                      strbuf_addf(err,
 +                                  "Multiple updates for ref '%s' not allowed.",
 +                                  updates[i]->refname);
                        return 1;
                }
        return 0;
  }
  
  int ref_transaction_commit(struct ref_transaction *transaction,
 -                         const char *msg, struct strbuf *err)
 +                         struct strbuf *err)
  {
 -      int ret = 0, delnum = 0, i;
 -      const char **delnames;
 +      int ret = 0, i;
        int n = transaction->nr;
        struct ref_update **updates = transaction->updates;
 +      struct string_list refs_to_delete = STRING_LIST_INIT_NODUP;
 +      struct string_list_item *ref_to_delete;
  
 -      if (!n)
 -              return 0;
 +      assert(err);
 +
 +      if (transaction->state != REF_TRANSACTION_OPEN)
 +              die("BUG: commit called for transaction that is not open");
  
 -      /* Allocate work space */
 -      delnames = xmalloc(sizeof(*delnames) * n);
 +      if (!n) {
 +              transaction->state = REF_TRANSACTION_CLOSED;
 +              return 0;
 +      }
  
        /* Copy, sort, and reject duplicate refs */
        qsort(updates, n, sizeof(*updates), ref_update_compare);
 -      ret = ref_update_reject_duplicates(updates, n, err);
 -      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++) {
                struct ref_update *update = updates[i];
 -
 -              update->lock = update_ref_lock(update->refname,
 -                                             (update->have_old ?
 -                                              update->old_sha1 : NULL),
 -                                             update->flags,
 -                                             &update->type,
 -                                             UPDATE_REFS_QUIET_ON_ERR);
 +              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) {
 -                      if (err)
 -                              strbuf_addf(err, "Cannot lock the ref '%s'.",
 -                                          update->refname);
 -                      ret = 1;
 +                      ret = (errno == ENOTDIR)
 +                              ? TRANSACTION_NAME_CONFLICT
 +                              : TRANSACTION_GENERIC_ERROR;
 +                      strbuf_addf(err, "Cannot lock the ref '%s'.",
 +                                  update->refname);
                        goto cleanup;
                }
        }
                struct ref_update *update = updates[i];
  
                if (!is_null_sha1(update->new_sha1)) {
 -                      ret = update_ref_write(msg,
 -                                             update->refname,
 -                                             update->new_sha1,
 -                                             update->lock, err,
 -                                             UPDATE_REFS_QUIET_ON_ERR);
 -                      update->lock = NULL; /* freed by update_ref_write */
 -                      if (ret)
 +                      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 */
                }
        }
  
                struct ref_update *update = updates[i];
  
                if (update->lock) {
 -                      delnames[delnum++] = update->lock->ref_name;
 -                      ret |= delete_ref_loose(update->lock, update->type);
 +                      if (delete_ref_loose(update->lock, update->type, err)) {
 +                              ret = TRANSACTION_GENERIC_ERROR;
 +                              goto cleanup;
 +                      }
 +
 +                      if (!(update->flags & REF_ISPRUNING))
 +                              string_list_append(&refs_to_delete,
 +                                                 update->lock->ref_name);
                }
        }
  
 -      ret |= repack_without_refs(delnames, delnum, err);
 -      for (i = 0; i < delnum; i++)
 -              unlink_or_warn(git_path("logs/%s", delnames[i]));
 +      if (repack_without_refs(&refs_to_delete, err)) {
 +              ret = TRANSACTION_GENERIC_ERROR;
 +              goto cleanup;
 +      }
 +      for_each_string_list_item(ref_to_delete, &refs_to_delete)
 +              unlink_or_warn(git_path("logs/%s", ref_to_delete->string));
        clear_loose_ref_cache(&ref_cache);
  
  cleanup:
 +      transaction->state = REF_TRANSACTION_CLOSED;
 +
        for (i = 0; i < n; i++)
                if (updates[i]->lock)
                        unlock_ref(updates[i]->lock);
 -      free(delnames);
 +      string_list_clear(&refs_to_delete, 0);
        return ret;
  }