Merge branch 'rs/unify-is-branch'
authorJunio C Hamano <gitster@pobox.com>
Mon, 21 Jul 2014 18:18:57 +0000 (11:18 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 21 Jul 2014 18:18:57 +0000 (11:18 -0700)
* rs/unify-is-branch:
refs.c: add a public is_branch function

1  2 
refs.c
refs.h
diff --combined refs.c
index dc761599fde96cbf4ddd88608ffc13bce4bf0040,d0ddf5608ac1091766b0960364e6a1ef625829a5..3792314f3bfd0e88b91a245240cda3ec1ac24af3
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -6,71 -6,51 +6,71 @@@
  #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:
 + * This table is used by both the SIMD and non-SIMD code.  It has
 + * some cases that are only useful for the SIMD; these are handled
 + * equivalently to the listed disposition in the non-SIMD code.
 + * 0: An acceptable character for refs
 + * 1: @, look for a following { to reject @{ in refs (SIMD or = 0)
 + * 2: \0: End-of-component and string
 + * 3: /: End-of-component (SIMD or = 2)
 + * 4: ., look for a preceding . to reject .. in refs
 + * 5: {, look for a preceding @ to reject @{ in refs
 + * 6: *, usually a bad character except, once as a wildcard (SIMD or = 7)
 + * 7: A bad character except * (see check_refname_component below)
 + */
 +static unsigned char refname_disposition[256] = {
 +      2, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 +      7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
 +      7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 6, 0, 0, 0, 4, 3,
 +      0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 7, 0, 0, 0, 0, 7,
 +      1, 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, 7, 7, 0, 7, 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, 5, 0, 0, 7, 7
 +};
 +
 +/*
 + * 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 has ASCII control character, "~", "^", ":" or SP, anywhere, or
 - * - it ends with a "/".
 - * - it ends with ".lock"
 + * - it has pattern-matching notation "*", "?", "[", anywhere, or
 + * - it ends with a "/", or
 + * - it ends with ".lock", or
   * - 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 2: /* fall-through */
 +              case 3:
 +                      goto out;
 +              case 4:
 +                      if (last == '.')
 +                              return -1; /* Refname contains "..". */
 +                      break;
 +              case 5:
 +                      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 6: /* fall-through */
 +              case 7:
 +                      return -1;
 +              }
                last = ch;
        }
 +out:
        if (cp == refname)
                return 0; /* Component has zero length. */
        if (refname[0] == '.') {
@@@ -88,7 -68,7 +88,7 @@@
        return cp - refname;
  }
  
 -int check_refname_format(const char *refname, int flags)
 +static int check_refname_format_bytewise(const char *refname, int flags)
  {
        int component_len, component_count = 0;
  
        return 0;
  }
  
 +#if defined(__GNUC__) && defined(__x86_64__)
 +#define SSE_VECTOR_BYTES 16
 +
 +/* Vectorized version of check_refname_format. */
 +int check_refname_format(const char *refname, int flags)
 +{
 +      const char *cp = refname;
 +
 +      const __m128i dot = _mm_set1_epi8('.');
 +      const __m128i at = _mm_set1_epi8('@');
 +      const __m128i curly = _mm_set1_epi8('{');
 +      const __m128i slash = _mm_set1_epi8('/');
 +      const __m128i zero = _mm_set1_epi8('\000');
 +      const __m128i el = _mm_set1_epi8('l');
 +
 +      /* below '*', all characters are forbidden or rare */
 +      const __m128i star_ub = _mm_set1_epi8('*' + 1);
 +
 +      const __m128i colon = _mm_set1_epi8(':');
 +      const __m128i question = _mm_set1_epi8('?');
 +
 +      /* '['..'^' contains 4 characters: 3 forbidden and 1 rare */
 +      const __m128i bracket_lb = _mm_set1_epi8('[' - 1);
 +      const __m128i caret_ub = _mm_set1_epi8('^' + 1);
 +
 +      /* '~' and above are forbidden */
 +      const __m128i tilde_lb = _mm_set1_epi8('~' - 1);
 +
 +      int component_count = 0;
 +      int orig_flags = flags;
 +
 +      if (refname[0] == 0 || refname[0] == '/') {
 +              /* entirely empty ref or initial ref component */
 +              return -1;
 +      }
 +
 +      /*
 +       * Initial ref component of '.'; below we look for /. so we'll
 +       * miss this.
 +       */
 +      if (refname[0] == '.') {
 +              if (refname[1] == '/' || refname[1] == '\0')
 +                      return -1;
 +              if (!(flags & REFNAME_DOT_COMPONENT))
 +                      return -1;
 +      }
 +      while(1) {
 +              __m128i tmp, tmp1, result;
 +              uint64_t mask;
 +
 +              if ((uintptr_t) cp % PAGE_SIZE > PAGE_SIZE - SSE_VECTOR_BYTES  - 1)
 +                      /*
 +                       * End-of-page; fall back to slow method for
 +                       * this entire ref.
 +                       */
 +                      return check_refname_format_bytewise(refname, orig_flags);
 +
 +              tmp = _mm_loadu_si128((__m128i *)cp);
 +              tmp1 = _mm_loadu_si128((__m128i *)(cp + 1));
 +
 +              /*
 +               * This range (note the lt) contains some
 +               * permissible-but-rare characters (including all
 +               * characters >= 128), which we handle later.  It also
 +               * includes \000.
 +               */
 +              result = _mm_cmplt_epi8(tmp, star_ub);
 +
 +              result = _mm_or_si128(result, _mm_cmpeq_epi8(tmp, question));
 +              result = _mm_or_si128(result, _mm_cmpeq_epi8(tmp, colon));
 +
 +              /* This range contains the permissible ] as bycatch */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpgt_epi8(tmp, bracket_lb),
 +                                            _mm_cmplt_epi8(tmp, caret_ub)));
 +
 +              result = _mm_or_si128(result, _mm_cmpgt_epi8(tmp, tilde_lb));
 +
 +              /* .. */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, dot),
 +                                            _mm_cmpeq_epi8(tmp1, dot)));
 +              /* @{ */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, at),
 +                                            _mm_cmpeq_epi8(tmp1, curly)));
 +              /* // */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, slash),
 +                                            _mm_cmpeq_epi8(tmp1, slash)));
 +              /* trailing / */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, slash),
 +                                            _mm_cmpeq_epi8(tmp1, zero)));
 +              /* .l, beginning of .lock */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, dot),
 +                                            _mm_cmpeq_epi8(tmp1, el)));
 +              /*
 +               * Even though /. is not necessarily an error, we flag
 +               * it anyway. If we find it, we'll check if it's valid
 +               * and if so we'll advance just past it.
 +               */
 +              result = _mm_or_si128(result, _mm_and_si128(
 +                                            _mm_cmpeq_epi8(tmp, slash),
 +                                            _mm_cmpeq_epi8(tmp1, dot)));
 +
 +              mask = _mm_movemask_epi8(result);
 +              if (mask) {
 +                      /*
 +                       * We've found either end-of-string, or some
 +                       * probably-bad character or substring.
 +                       */
 +                      int i = __builtin_ctz(mask);
 +                      switch (refname_disposition[cp[i] & 255]) {
 +                      case 0: /* fall-through */
 +                      case 5:
 +                              /*
 +                               * bycatch: a good character that's in
 +                               * one of the ranges of mostly-forbidden
 +                               * characters
 +                               */
 +                              cp += i + 1;
 +                              break;
 +                      case 1:
 +                              if (cp[i + 1] == '{')
 +                                      return -1;
 +                              cp += i + 1;
 +                              break;
 +                      case 2:
 +                              if (!(flags & REFNAME_ALLOW_ONELEVEL)
 +                                  && !component_count && !strchr(refname, '/'))
 +                                      /* Refname has only one component. */
 +                                      return -1;
 +                              return 0;
 +                      case 3:
 +                              component_count ++;
 +                              /*
 +                               * Even if leading dots are allowed, don't
 +                               * allow "." as a component (".." is
 +                               * prevented by case 4 below).
 +                               */
 +                              if (cp[i + 1] == '.') {
 +                                      if (cp[i + 2] == '\0')
 +                                              return -1;
 +                                      if (flags & REFNAME_DOT_COMPONENT) {
 +                                              /* skip to just after the /. */
 +                                              cp += i + 2;
 +                                              break;
 +                                      }
 +                                      return -1;
 +                              } else if (cp[i + 1] == '/' || cp[i + 1] == '\0')
 +                                      return -1;
 +                              break;
 +                      case 4:
 +                              if (cp[i + 1] == '.' || cp[i + 1] == '\0')
 +                                      return -1;
 +                              /* .lock as end-of-component or end-of-string */
 +                              if ((!strncmp(cp + i, ".lock", 5))
 +                                  && (cp[i + 5] == '/' || cp[i + 5] == 0))
 +                                      return -1;
 +                              cp += 1;
 +                              break;
 +                      case 6:
 +                              if (((cp == refname + i) || cp[i - 1] == '/')
 +                                  && (cp[i + 1] == '/' || cp[i + 1] == 0))
 +                                      if (flags & REFNAME_REFSPEC_PATTERN) {
 +                                              flags &= ~REFNAME_REFSPEC_PATTERN;
 +                                              /* restart after the * */
 +                                              cp += i + 1;
 +                                              continue;
 +                                      }
 +                              /* fall-through */
 +                      case 7:
 +                              return -1;
 +                      }
 +              } else
 +                      cp += SSE_VECTOR_BYTES;
 +      }
 +}
 +
 +#else
 +
 +int check_refname_format (const char *refname, int flags)
 +{
 +      return check_refname_format_bytewise(refname, flags);
 +}
 +
 +#endif
 +
  struct ref_entry;
  
  /*
@@@ -1361,7 -1151,7 +1361,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
@@@ -1533,7 -1323,6 +1533,7 @@@ static const char *handle_missing_loose
        }
  }
  
 +/* 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)
  {
        int depth = MAXDEPTH;
        if (flag)
                *flag = 0;
  
 -      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
 +      if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
 +              errno = EINVAL;
                return NULL;
 +      }
  
        for (;;) {
                char path[PATH_MAX];
                char *buf;
                int fd;
  
 -              if (--depth < 0)
 +              if (--depth < 0) {
 +                      errno = ELOOP;
                        return NULL;
 +              }
  
                git_snpath(path, sizeof(path), "%s", refname);
  
                                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';
                            (buffer[40] != '\0' && !isspace(buffer[40]))) {
                                if (flag)
                                        *flag |= REF_ISBROKEN;
 +                              errno = EINVAL;
                                return NULL;
                        }
                        return refname;
                if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
                        if (flag)
                                *flag |= REF_ISBROKEN;
 +                      errno = EINVAL;
                        return NULL;
                }
                refname = strcpy(refname_buffer, buf);
@@@ -2142,22 -1921,18 +2142,22 @@@ int refname_match(const char *abbrev_na
        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)) {
 +              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;
@@@ -2170,16 -1945,14 +2170,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;
  }
@@@ -2243,6 -2016,7 +2243,6 @@@ 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;
                ref = resolve_ref_unsafe(path, hash, 1, 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;
  }
  
 +/* This function should make sure errno is meaningful on error */
  static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            const unsigned char *old_sha1,
                                            int flags, int *type_p)
@@@ -2429,7 -2205,6 +2429,7 @@@ static int write_packed_entry_fn(struc
        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;
  
        if (!packed_ref_cache->lock)
                die("internal error: packed-refs not locked");
        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))
 +      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;
  }
  
@@@ -2681,12 -2448,12 +2681,12 @@@ static int curate_packed_ref_fn(struct 
        return 0;
  }
  
 -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;
  
        /* Look for a packed ref */
        for (i = 0; i < n; i++)
                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]);
        }
        }
  
        /* Write what remains */
 -      return commit_packed_refs();
 +      ret = commit_packed_refs();
 +      if (ret && err)
 +              strbuf_addf(err, "unable to overwrite old ref-pack file: %s",
 +                          strerror(errno));
 +      return ret;
  }
  
  static int repack_without_ref(const char *refname)
  {
 -      return repack_without_refs(&refname, 1);
 +      return repack_without_refs(&refname, 1, NULL);
  }
  
  static int delete_ref_loose(struct ref_lock *lock, int flag)
@@@ -2976,7 -2734,6 +2976,7 @@@ 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;
             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;
        }
  
  
                if ((oflags & O_CREAT) && 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);
@@@ -3058,38 -2805,24 +3058,38 @@@ 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") || starts_with(refname, "refs/heads/");
  }
  
 +/* This function must return a meaningful errno */
  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) {
 +          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);
                unlock_ref(lock);
 +              errno = save_errno;
                return -1;
        }
        clear_loose_ref_cache(&ref_cache);
@@@ -3218,133 -2947,122 +3218,133 @@@ 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,
                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;
 +
 +      for_each_reflog_ent_reverse(refname, read_ref_at_ent, &cb);
 +
 +      if (!cb.reccnt)
 +              die("Log for %s is empty.", refname);
 +      if (cb.found_it)
 +              return 0;
 +
 +      for_each_reflog_ent(refname, read_ref_at_ent_oldest, &cb);
  
 -      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);
 -      }
 -      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;
        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];
@@@ -3497,7 -3215,7 +3497,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) {
@@@ -3542,9 -3260,9 +3542,9 @@@ static struct ref_lock *update_ref_lock
        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;
 +              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,
 -                          enum action_on_err onerr)
 +                          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 MSG_ON_ERR: error(str, refname); break;
 -              case DIE_ON_ERR: die(str, refname); break;
 -              case QUIET_ON_ERR: break;
 +              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
 + * 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;
 +      int type;
 +      const char refname[FLEX_ARRAY];
 +};
 +
 +/*
 + * 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;
 +};
 +
 +struct ref_transaction *ref_transaction_begin(void)
 +{
 +      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]);
 +
 +      free(transaction->updates);
 +      free(transaction);
 +}
 +
 +static struct ref_update *add_update(struct ref_transaction *transaction,
 +                                   const char *refname)
 +{
 +      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,
 +                         struct strbuf *err)
 +{
 +      struct ref_update *update;
 +
 +      if (have_old && !old_sha1)
 +              die("BUG: have_old is true but old_sha1 is NULL");
 +
 +      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);
 +      return 0;
 +}
 +
 +void ref_transaction_create(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          const unsigned char *new_sha1,
 +                          int flags)
 +{
 +      struct ref_update *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;
 +}
 +
 +void ref_transaction_delete(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          const unsigned char *old_sha1,
 +                          int flags, int have_old)
 +{
 +      struct ref_update *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);
 +      }
 +}
 +
  int update_ref(const char *action, const char *refname,
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
        lock = update_ref_lock(refname, oldval, flags, NULL, onerr);
        if (!lock)
                return 1;
 -      return update_ref_write(action, refname, sha1, lock, onerr);
 +      return update_ref_write(action, refname, sha1, lock, NULL, onerr);
  }
  
  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;
        for (i = 1; i < n; i++)
 -              if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
 +              if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
                        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 (err)
 +                              strbuf_addf(err, str, 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,
 +                         const char *msg, 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;
  
 -      if (!updates_orig || !n)
 +      if (!n)
                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);
 +      ret = ref_update_reject_duplicates(updates, n, err);
        if (ret)
                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]) {
 +              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);
 +              if (!update->lock) {
 +                      if (err)
 +                              strbuf_addf(err, "Cannot lock the ref '%s'.",
 +                                          update->refname);
                        ret = 1;
                        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 */
 +      for (i = 0; i < n; i++) {
 +              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)
                                goto cleanup;
                }
 +      }
  
        /* 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) {
 +                      delnames[delnum++] = update->lock->ref_name;
 +                      ret |= delete_ref_loose(update->lock, update->type);
                }
 -      ret |= repack_without_refs(delnames, delnum);
 +      }
 +
 +      ret |= repack_without_refs(delnames, delnum, err);
        for (i = 0; i < delnum; i++)
                unlink_or_warn(git_path("logs/%s", delnames[i]));
        clear_loose_ref_cache(&ref_cache);
  
  cleanup:
        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;
  }
diff --combined refs.h
index c5376cee8d84f94ad2e08ce5baae7537e9d4a47a,1b223e58a19f613885da62a9441786d76b30bdf9..ec46acdde7b67d3531a4c0e18d33901d2a4f307a
--- 1/refs.h
--- 2/refs.h
+++ b/refs.h
@@@ -10,7 -10,19 +10,7 @@@ struct ref_lock 
        int force_write;
  };
  
 -/**
 - * 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 {
 -      const char *ref_name;
 -      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_transaction;
  
  /*
   * Bit values set in the flags argument passed to each_ref_fn():
@@@ -82,7 -94,6 +82,7 @@@ extern void warn_dangling_symrefs(FILE 
  /*
   * Lock the packed-refs file for writing.  Flags is passed to
   * hold_lock_file_for_update().  Return 0 on success.
 + * Errno is set to something meaningful on error.
   */
  extern int lock_packed_refs(int flags);
  
@@@ -98,7 -109,6 +98,7 @@@ extern void add_packed_ref(const char *
   * Write the current version of the packed refs cache from memory to
   * disk.  The packed-refs file must already be locked for writing (see
   * lock_packed_refs()).  Return zero on success.
 + * Sets errno to something meaningful on error.
   */
  extern int commit_packed_refs(void);
  
@@@ -123,11 -133,12 +123,13 @@@ extern void rollback_packed_refs(void)
   */
  int pack_refs(unsigned int flags);
  
 -extern int repack_without_refs(const char **refnames, int n);
 +extern int repack_without_refs(const char **refnames, int n,
 +                             struct strbuf *err);
  
  extern int ref_exists(const char *);
  
+ extern int is_branch(const char *refname);
  /*
   * If refname is a non-symbolic reference that refers to a tag object,
   * and the tag can be (recursively) dereferenced to a non-tag object,
   */
  extern int peel_ref(const char *refname, unsigned char *sha1);
  
 -/** Locks a "refs/" ref returning the lock on success and NULL on failure. **/
 +/*
 + * Locks a "refs/" ref returning the lock on success and NULL on failure.
 + * On failure errno is set to something meaningful.
 + */
  extern struct ref_lock *lock_ref_sha1(const char *refname, const unsigned char *old_sha1);
  
  /** Locks any ref (for 'HEAD' type refs). */
  #define REF_NODEREF   0x01
 +/* errno is set to something meaningful on failure */
  extern struct ref_lock *lock_any_ref_for_update(const char *refname,
                                                const unsigned char *old_sha1,
                                                int flags, int *type_p);
@@@ -163,22 -170,14 +165,22 @@@ extern void unlock_ref(struct ref_lock 
  /** Writes sha1 into the ref specified by the lock. **/
  extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
  
 -/** Setup reflog before using. **/
 -int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
 +/*
 + * Setup reflog before using. Set errno to something meaningful on failure.
 + */
 +int log_ref_setup(const char *refname, char *logfile, int bufsize);
  
  /** Reads log for the value of ref during at_time. **/
  extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
                       unsigned char *sha1, char **msg,
                       unsigned long *cutoff_time, int *cutoff_tz, int *cutoff_cnt);
  
 +/** Check if a particular reflog exists */
 +extern int reflog_exists(const char *refname);
 +
 +/** Delete a reflog */
 +extern int delete_reflog(const char *refname);
 +
  /* iterate over reflog entries */
  typedef int each_reflog_ent_fn(unsigned char *osha1, unsigned char *nsha1, const char *, unsigned long, int, const char *, void *);
  int for_each_reflog_ent(const char *refname, each_reflog_ent_fn fn, void *cb_data);
@@@ -220,84 -219,18 +222,84 @@@ extern int rename_ref(const char *oldre
   */
  extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
  
 -/** lock a ref and then write its file */
 -enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
 +enum action_on_err {
 +      UPDATE_REFS_MSG_ON_ERR,
 +      UPDATE_REFS_DIE_ON_ERR,
 +      UPDATE_REFS_QUIET_ON_ERR
 +};
 +
 +/*
 + * Begin a reference transaction.  The reference transaction must
 + * be freed by calling ref_transaction_free().
 + */
 +struct ref_transaction *ref_transaction_begin(void);
 +
 +/*
 + * The following functions add a reference check or update to a
 + * ref_transaction.  In all of them, refname is the name of the
 + * reference to be affected.  The functions make internal copies of
 + * refname, so the caller retains ownership of the parameter.  flags
 + * can be REF_NODEREF; it is passed to update_ref_lock().
 + */
 +
 +/*
 + * Add a reference update to transaction.  new_sha1 is the value that
 + * the reference should have after the update, or zeros if it should
 + * be deleted.  If have_old is true, then old_sha1 holds the value
 + * that the reference should have had before the update, or zeros if
 + * it must not have existed beforehand.
 + * Function returns 0 on success and non-zero on failure. A failure to update
 + * means that the transaction as a whole has failed and will need to be
 + * rolled back. On failure the err buffer will be updated.
 + */
 +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,
 +                         struct strbuf *err);
 +
 +/*
 + * Add a reference creation to transaction.  new_sha1 is the value
 + * that the reference should have after the update; it must not be the
 + * null SHA-1.  It is verified that the reference does not exist
 + * already.
 + */
 +void ref_transaction_create(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          const unsigned char *new_sha1,
 +                          int flags);
 +
 +/*
 + * Add a reference deletion to transaction.  If have_old is true, then
 + * old_sha1 holds the value that the reference should have had before
 + * the update (which must not be the null SHA-1).
 + */
 +void ref_transaction_delete(struct ref_transaction *transaction,
 +                          const char *refname,
 +                          const unsigned char *old_sha1,
 +                          int flags, int have_old);
 +
 +/*
 + * Commit all of the changes that have been queued in transaction, as
 + * atomically as possible.  Return a nonzero value if there is a
 + * problem.
 + * If err is non-NULL we will add an error string to it to explain why
 + * the transaction failed. The string does not end in newline.
 + */
 +int ref_transaction_commit(struct ref_transaction *transaction,
 +                         const char *msg, struct strbuf *err);
 +
 +/*
 + * Free an existing transaction and all associated data.
 + */
 +void ref_transaction_free(struct ref_transaction *transaction);
 +
 +/** Lock a ref and then write its file */
  int update_ref(const char *action, const char *refname,
                const unsigned char *sha1, const unsigned char *oldval,
                int flags, enum action_on_err onerr);
  
 -/**
 - * Lock all refs and then perform all modifications.
 - */
 -int update_refs(const char *action, const struct ref_update **updates,
 -              int n, enum action_on_err onerr);
 -
  extern int parse_hide_refs_config(const char *var, const char *value, const char *);
  extern int ref_is_hidden(const char *);