Merge branch 'mh/init-delete-refs-api'
authorJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:17 +0000 (11:01 -0700)
committerJunio C Hamano <gitster@pobox.com>
Mon, 3 Aug 2015 18:01:17 +0000 (11:01 -0700)
Clean up refs API and make "git clone" less intimate with the
implementation detail.

* mh/init-delete-refs-api:
delete_ref(): use the usual convention for old_sha1
cmd_update_ref(): make logic more straightforward
update_ref(): don't read old reference value before delete
check_branch_commit(): make first parameter const
refs.h: add some parameter names to function declarations
refs: move the remaining ref module declarations to refs.h
initial_ref_transaction_commit(): check for ref D/F conflicts
initial_ref_transaction_commit(): check for duplicate refs
refs: remove some functions from the module's public interface
initial_ref_transaction_commit(): function for initial ref creation
repack_without_refs(): make function private
prune_refs(): use delete_refs()
prune_remote(): use delete_refs()
delete_refs(): bail early if the packed-refs file cannot be rewritten
delete_refs(): make error message more generic
delete_refs(): new function for the refs API
delete_ref(): handle special case more explicitly
remove_branches(): remove temporary
delete_ref(): move declaration to refs.h

1  2 
builtin/blame.c
builtin/clone.c
builtin/log.c
cache.h
refs.c
diff --combined builtin/blame.c
index a22ac174078742cc8991c0ee563980d239731e9b,1c998cb26c2bd8ee931dbfb0fed7766450aab0a7..272a2226876fffb757feeb89dc494be4cf276aaa
@@@ -6,6 -6,7 +6,7 @@@
   */
  
  #include "cache.h"
+ #include "refs.h"
  #include "builtin.h"
  #include "blob.h"
  #include "commit.h"
@@@ -2176,14 -2177,6 +2177,14 @@@ static int git_blame_config(const char 
                blank_boundary = git_config_bool(var, value);
                return 0;
        }
 +      if (!strcmp(var, "blame.showemail")) {
 +              int *output_option = cb;
 +              if (git_config_bool(var, value))
 +                      *output_option |= OUTPUT_SHOW_EMAIL;
 +              else
 +                      *output_option &= ~OUTPUT_SHOW_EMAIL;
 +              return 0;
 +      }
        if (!strcmp(var, "blame.date")) {
                if (!value)
                        return config_error_nonbool(var);
@@@ -2528,7 -2521,7 +2529,7 @@@ int cmd_blame(int argc, const char **ar
        unsigned int range_i;
        long anchor;
  
 -      git_config(git_blame_config, NULL);
 +      git_config(git_blame_config, &output_option);
        init_revisions(&revs, NULL);
        revs.date_mode = blame_date_mode;
        DIFF_OPT_SET(&revs.diffopt, ALLOW_TEXTCONV);
diff --combined builtin/clone.c
index a72ff7e0098da9c89f6000e724ea097e3403601f,8539b8de64076dae79f993552828b65440e6c357..303a3a7eb6cdc383230e38a5a0a6979fa385b6eb
@@@ -147,7 -147,6 +147,7 @@@ static char *get_repo_path(const char *
  static char *guess_dir_name(const char *repo, int is_bundle, int is_bare)
  {
        const char *end = repo + strlen(repo), *start;
 +      size_t len;
        char *dir;
  
        /*
        /*
         * Strip .{bundle,git}.
         */
 -      if (is_bundle) {
 -              if (end - start > 7 && !strncmp(end - 7, ".bundle", 7))
 -                      end -= 7;
 -      } else {
 -              if (end - start > 4 && !strncmp(end - 4, ".git", 4))
 -                      end -= 4;
 -      }
 +      strip_suffix(start, is_bundle ? ".bundle" : ".git" , &len);
  
 -      if (is_bare) {
 -              struct strbuf result = STRBUF_INIT;
 -              strbuf_addf(&result, "%.*s.git", (int)(end - start), start);
 -              dir = strbuf_detach(&result, NULL);
 -      } else
 -              dir = xstrndup(start, end - start);
 +      if (is_bare)
 +              dir = xstrfmt("%.*s.git", (int)len, start);
 +      else
 +              dir = xstrndup(start, len);
        /*
         * Replace sequences of 'control' characters and whitespace
         * with one ascii space, remove leading and trailing spaces.
@@@ -484,16 -491,26 +484,26 @@@ static void write_remote_refs(const str
  {
        const struct ref *r;
  
-       lock_packed_refs(LOCK_DIE_ON_ERROR);
+       struct ref_transaction *t;
+       struct strbuf err = STRBUF_INIT;
+       t = ref_transaction_begin(&err);
+       if (!t)
+               die("%s", err.buf);
  
        for (r = local_refs; r; r = r->next) {
                if (!r->peer_ref)
                        continue;
-               add_packed_ref(r->peer_ref->name, r->old_sha1);
+               if (ref_transaction_create(t, r->peer_ref->name, r->old_sha1,
+                                          0, NULL, &err))
+                       die("%s", err.buf);
        }
  
-       if (commit_packed_refs())
-               die_errno("unable to overwrite old ref-pack file");
+       if (initial_ref_transaction_commit(t, &err))
+               die("%s", err.buf);
+       strbuf_release(&err);
+       ref_transaction_free(t);
  }
  
  static void write_followtags(const struct ref *refs, const char *msg)
diff --combined builtin/log.c
index 878104943f04b6302dfdfd279a2afabadf89c191,3caa91776a99c348584c34e0445b29c220799799..93025d08cdbf386c73188ee48aa7b443c72647c7
@@@ -5,6 -5,7 +5,7 @@@
   *             2006 Junio Hamano
   */
  #include "cache.h"
+ #include "refs.h"
  #include "color.h"
  #include "commit.h"
  #include "diff.h"
@@@ -795,7 -796,7 +796,7 @@@ static int reopen_stdout(struct commit 
  static void get_patch_ids(struct rev_info *rev, struct patch_ids *ids)
  {
        struct rev_info check_rev;
 -      struct commit *commit;
 +      struct commit *commit, *c1, *c2;
        struct object *o1, *o2;
        unsigned flags1, flags2;
  
                die(_("Need exactly one range."));
  
        o1 = rev->pending.objects[0].item;
 -      flags1 = o1->flags;
        o2 = rev->pending.objects[1].item;
 +      flags1 = o1->flags;
        flags2 = o2->flags;
 +      c1 = lookup_commit_reference(o1->sha1);
 +      c2 = lookup_commit_reference(o2->sha1);
  
        if ((flags1 & UNINTERESTING) == (flags2 & UNINTERESTING))
                die(_("Not a range."));
        }
  
        /* reset for next revision walk */
 -      clear_commit_marks((struct commit *)o1,
 -                      SEEN | UNINTERESTING | SHOWN | ADDED);
 -      clear_commit_marks((struct commit *)o2,
 -                      SEEN | UNINTERESTING | SHOWN | ADDED);
 +      clear_commit_marks(c1, SEEN | UNINTERESTING | SHOWN | ADDED);
 +      clear_commit_marks(c2, SEEN | UNINTERESTING | SHOWN | ADDED);
        o1->flags = flags1;
        o2->flags = flags2;
  }
diff --combined cache.h
index dcc9d3f40b1ebfd3e2a4d959b11b5df9328750f9,1c00098f87833bc1fe7bb140a1b33ffe22d7005c..6997b2cd47fdfa0700cdcfe9c5da7259aa1a011d
+++ b/cache.h
@@@ -397,7 -397,6 +397,7 @@@ static inline enum object_type object_t
  #define EXEC_PATH_ENVIRONMENT "GIT_EXEC_PATH"
  #define CEILING_DIRECTORIES_ENVIRONMENT "GIT_CEILING_DIRECTORIES"
  #define NO_REPLACE_OBJECTS_ENVIRONMENT "GIT_NO_REPLACE_OBJECTS"
 +#define GIT_REPLACE_REF_BASE_ENVIRONMENT "GIT_REPLACE_REF_BASE"
  #define GITATTRIBUTES_FILE ".gitattributes"
  #define INFOATTRIBUTES_FILE "info/attributes"
  #define ATTRIBUTE_MACRO_PREFIX "[attr]"
@@@ -447,17 -446,7 +447,17 @@@ extern int get_common_dir(struct strbu
  extern const char *get_git_namespace(void);
  extern const char *strip_namespace(const char *namespaced_ref);
  extern const char *get_git_work_tree(void);
 -extern const char *read_gitfile(const char *path);
 +
 +#define READ_GITFILE_ERR_STAT_FAILED 1
 +#define READ_GITFILE_ERR_NOT_A_FILE 2
 +#define READ_GITFILE_ERR_OPEN_FAILED 3
 +#define READ_GITFILE_ERR_READ_FAILED 4
 +#define READ_GITFILE_ERR_INVALID_FORMAT 5
 +#define READ_GITFILE_ERR_NO_PATH 6
 +#define READ_GITFILE_ERR_NOT_A_REPO 7
 +#define READ_GITFILE_ERR_TOO_LARGE 8
 +extern const char *read_gitfile_gently(const char *path, int *return_error_code);
 +#define read_gitfile(path) read_gitfile_gently((path), NULL)
  extern const char *resolve_gitdir(const char *suspect);
  extern void set_git_work_tree(const char *tree);
  
@@@ -596,8 -585,6 +596,6 @@@ extern void update_index_if_able(struc
  extern int hold_locked_index(struct lock_file *, int);
  extern void set_alternate_index_output(const char *);
  
- extern int delete_ref(const char *, const unsigned char *sha1, unsigned int flags);
  /* Environment bits from configuration mechanism */
  extern int trust_executable_bit;
  extern int trust_ctime;
@@@ -633,7 -620,6 +631,7 @@@ extern unsigned long pack_size_limit_cf
   * been sought but there were none.
   */
  extern int check_replace_refs;
 +extern char *git_replace_ref_base;
  
  extern int fsync_object_files;
  extern int core_preload_index;
@@@ -955,17 -941,8 +953,17 @@@ extern int has_sha1_pack(const unsigne
   * Return true iff we have an object named sha1, whether local or in
   * an alternate object database, and whether packed or loose.  This
   * function does not respect replace references.
 + *
 + * If the QUICK flag is set, do not re-check the pack directory
 + * when we cannot find the object (this means we may give a false
 + * negative answer if another process is simultaneously repacking).
   */
 -extern int has_sha1_file(const unsigned char *sha1);
 +#define HAS_SHA1_QUICK 0x1
 +extern int has_sha1_file_with_flags(const unsigned char *sha1, int flags);
 +static inline int has_sha1_file(const unsigned char *sha1)
 +{
 +      return has_sha1_file_with_flags(sha1, 0);
 +}
  
  /*
   * Return true iff an alternate object database has a loose object
@@@ -1032,76 -1009,10 +1030,10 @@@ extern int get_oid_hex(const char *hex
  
  extern char *sha1_to_hex(const unsigned char *sha1);  /* static buffer result! */
  extern char *oid_to_hex(const struct object_id *oid); /* same static buffer as sha1_to_hex */
- extern int read_ref_full(const char *refname, int resolve_flags,
-                        unsigned char *sha1, int *flags);
- extern int read_ref(const char *refname, unsigned char *sha1);
  
- /*
-  * Resolve a reference, recursively following symbolic refererences.
-  *
-  * Store the referred-to object's name in sha1 and return the name of
-  * the non-symbolic reference that ultimately pointed at it.  The
-  * return value, if not NULL, is a pointer into either a static buffer
-  * or the input ref.
-  *
-  * If the reference cannot be resolved to an object, the behavior
-  * depends on the RESOLVE_REF_READING flag:
-  *
-  * - If RESOLVE_REF_READING is set, return NULL.
-  *
-  * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of
-  *   the last reference name in the chain, which will either be a non-symbolic
-  *   reference or an undefined reference.  If this is a prelude to
-  *   "writing" to the ref, the return value is the name of the ref
-  *   that will actually be created or changed.
-  *
-  * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one
-  * level of symbolic reference.  The value stored in sha1 for a symbolic
-  * reference will always be null_sha1 in this case, and the return
-  * value is the reference that the symref refers to directly.
-  *
-  * If flags is non-NULL, set the value that it points to the
-  * combination of REF_ISPACKED (if the reference was found among the
-  * packed references), REF_ISSYMREF (if the initial reference was a
-  * symbolic reference), REF_BAD_NAME (if the reference name is ill
-  * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN
-  * (if the ref is malformed or has a bad name). See refs.h for more detail
-  * on each flag.
-  *
-  * If ref is not a properly-formatted, normalized reference, return
-  * NULL.  If more than MAXDEPTH recursive symbolic lookups are needed,
-  * give up and return NULL.
-  *
-  * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their
-  * name is invalid according to git-check-ref-format(1).  If the name
-  * is bad then the value stored in sha1 will be null_sha1 and the two
-  * flags REF_ISBROKEN and REF_BAD_NAME will be set.
-  *
-  * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/
-  * directory and do not consist of all caps and underscores cannot be
-  * resolved. The function returns NULL for such ref names.
-  * Caps and underscores refers to the special refs, such as HEAD,
-  * FETCH_HEAD and friends, that all live outside of the refs/ directory.
-  */
- #define RESOLVE_REF_READING 0x01
- #define RESOLVE_REF_NO_RECURSE 0x02
- #define RESOLVE_REF_ALLOW_BAD_NAME 0x04
- extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
- extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags);
- extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref);
- extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref);
  extern int interpret_branch_name(const char *str, int len, struct strbuf *);
  extern int get_sha1_mb(const char *str, unsigned char *sha1);
  
- /*
-  * Return true iff abbrev_name is a possible abbreviation for
-  * full_name according to the rules defined by ref_rev_parse_rules in
-  * refs.c.
-  */
- extern int refname_match(const char *abbrev_name, const char *full_name);
- extern int create_symref(const char *ref, const char *refs_heads_master, const char *logmsg);
  extern int validate_headref(const char *ref);
  
  extern int base_name_compare(const char *name1, int len1, int mode1, const char *name2, int len2, int mode2);
@@@ -1719,6 -1630,5 +1651,6 @@@ int stat_validity_check(struct stat_val
  void stat_validity_update(struct stat_validity *sv, int fd);
  
  int versioncmp(const char *s1, const char *s2);
 +void sleep_millisec(int millisec);
  
  #endif /* CACHE_H */
diff --combined refs.c
index 227cb50c442275cf40941f9ddd451278717abac2,c5086ae70c11527ec0ffc15312ab8683bb719c41..fb568d7a31adeb69ff13cd7048dadb7847ee9ea8
--- 1/refs.c
--- 2/refs.c
+++ b/refs.c
@@@ -1314,7 -1314,13 +1314,13 @@@ static struct ref_dir *get_packed_refs(
        return get_packed_ref_dir(get_packed_ref_cache(refs));
  }
  
- void add_packed_ref(const char *refname, const unsigned char *sha1)
+ /*
+  * Add a reference to the in-memory packed reference cache.  This may
+  * only be called while the packed-refs file is locked (see
+  * lock_packed_refs()).  To actually write the packed-refs file, call
+  * commit_packed_refs().
+  */
+ static void add_packed_ref(const char *refname, const unsigned char *sha1)
  {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
@@@ -1373,34 -1379,19 +1379,34 @@@ static void read_loose_refs(const char 
                                         create_dir_entry(refs, refname.buf,
                                                          refname.len, 1));
                } else {
 +                      int read_ok;
 +
                        if (*refs->name) {
                                hashclr(sha1);
                                flag = 0;
 -                              if (resolve_gitlink_ref(refs->name, refname.buf, sha1) < 0) {
 -                                      hashclr(sha1);
 -                                      flag |= REF_ISBROKEN;
 -                              }
 -                      } else if (read_ref_full(refname.buf,
 -                                               RESOLVE_REF_READING,
 -                                               sha1, &flag)) {
 +                              read_ok = !resolve_gitlink_ref(refs->name,
 +                                                             refname.buf, sha1);
 +                      } else {
 +                              read_ok = !read_ref_full(refname.buf,
 +                                                       RESOLVE_REF_READING,
 +                                                       sha1, &flag);
 +                      }
 +
 +                      if (!read_ok) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
 +                      } else if (is_null_sha1(sha1)) {
 +                              /*
 +                               * It is so astronomically unlikely
 +                               * that NULL_SHA1 is the SHA-1 of an
 +                               * actual object that we consider its
 +                               * appearance in a loose reference
 +                               * file to be repo corruption
 +                               * (probably due to a software bug).
 +                               */
 +                              flag |= REF_ISBROKEN;
                        }
 +
                        if (check_refname_format(refname.buf,
                                                 REFNAME_ALLOW_ONELEVEL)) {
                                if (!refname_is_safe(refname.buf))
@@@ -1741,9 -1732,11 +1747,11 @@@ const char *resolve_ref_unsafe(const ch
        return ret;
  }
  
- char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags)
+ char *resolve_refdup(const char *refname, int resolve_flags,
+                    unsigned char *sha1, int *flags)
  {
-       return xstrdup_or_null(resolve_ref_unsafe(ref, resolve_flags, sha1, flags));
+       return xstrdup_or_null(resolve_ref_unsafe(refname, resolve_flags,
+                                                 sha1, flags));
  }
  
  /* The argument to filter_refs */
@@@ -2122,8 -2115,7 +2130,8 @@@ int for_each_remote_ref_submodule(cons
  
  int for_each_replace_ref(each_ref_fn fn, void *cb_data)
  {
 -      return do_for_each_ref(&ref_cache, "refs/replace/", fn, 13, 0, cb_data);
 +      return do_for_each_ref(&ref_cache, git_replace_ref_base, fn,
 +                             strlen(git_replace_ref_base), 0, cb_data);
  }
  
  int head_ref_namespaced(each_ref_fn fn, void *cb_data)
@@@ -2531,8 -2523,12 +2539,12 @@@ static int write_packed_entry_fn(struc
        return 0;
  }
  
- /* This should return a meaningful errno on failure */
- int lock_packed_refs(int flags)
+ /*
+  * Lock the packed-refs file for writing. Flags is passed to
+  * hold_lock_file_for_update(). Return 0 on success. On errors, set
+  * errno appropriately and return a nonzero value.
+  */
+ static int lock_packed_refs(int flags)
  {
        static int timeout_configured = 0;
        static int timeout_value = 1000;
  }
  
  /*
-  * Commit the packed refs changes.
-  * On error we must make sure that errno contains a meaningful value.
+  * 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. On errors, set errno
+  * and return a nonzero value
   */
- int commit_packed_refs(void)
static int commit_packed_refs(void)
  {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
        return error;
  }
  
- void rollback_packed_refs(void)
+ /*
+  * Rollback the lockfile for the packed-refs file, and discard the
+  * in-memory packed reference cache.  (The packed-refs file will be
+  * read anew if it is needed again after this function is called.)
+  */
+ static void rollback_packed_refs(void)
  {
        struct packed_ref_cache *packed_ref_cache =
                get_packed_ref_cache(&ref_cache);
@@@ -2752,7 -2755,14 +2771,14 @@@ int pack_refs(unsigned int flags
        return 0;
  }
  
- int repack_without_refs(struct string_list *refnames, struct strbuf *err)
+ /*
+  * Rewrite the packed-refs file, omitting any refs listed in
+  * 'refnames'. On error, leave packed-refs unchanged, write an error
+  * message to 'err', and return a nonzero value.
+  *
+  * The refs in 'refnames' needn't be sorted. `err` must not be NULL.
+  */
+ static int repack_without_refs(struct string_list *refnames, struct strbuf *err)
  {
        struct ref_dir *packed;
        struct string_list_item *refname;
@@@ -2817,15 -2827,15 +2843,15 @@@ static int delete_ref_loose(struct ref_
        return 0;
  }
  
- int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flags)
+ int delete_ref(const char *refname, const unsigned char *old_sha1,
+              unsigned int flags)
  {
        struct ref_transaction *transaction;
        struct strbuf err = STRBUF_INIT;
  
        transaction = ref_transaction_begin(&err);
        if (!transaction ||
-           ref_transaction_delete(transaction, refname,
-                                  (sha1 && !is_null_sha1(sha1)) ? sha1 : NULL,
+           ref_transaction_delete(transaction, refname, old_sha1,
                                   flags, NULL, &err) ||
            ref_transaction_commit(transaction, &err)) {
                error("%s", err.buf);
        return 0;
  }
  
+ int delete_refs(struct string_list *refnames)
+ {
+       struct strbuf err = STRBUF_INIT;
+       int i, result = 0;
+       if (!refnames->nr)
+               return 0;
+       result = repack_without_refs(refnames, &err);
+       if (result) {
+               /*
+                * If we failed to rewrite the packed-refs file, then
+                * it is unsafe to try to remove loose refs, because
+                * doing so might expose an obsolete packed value for
+                * a reference that might even point at an object that
+                * has been garbage collected.
+                */
+               if (refnames->nr == 1)
+                       error(_("could not delete reference %s: %s"),
+                             refnames->items[0].string, err.buf);
+               else
+                       error(_("could not delete references: %s"), err.buf);
+               goto out;
+       }
+       for (i = 0; i < refnames->nr; i++) {
+               const char *refname = refnames->items[i].string;
+               if (delete_ref(refname, NULL, 0))
+                       result |= error(_("could not remove reference %s"), refname);
+       }
+ out:
+       strbuf_release(&err);
+       return result;
+ }
  /*
   * People using contrib's git-new-workdir have .git/logs/refs ->
   * /some/other/path/.git/logs/refs, and that may live on another device.
@@@ -4039,6 -4087,98 +4103,98 @@@ cleanup
        return ret;
  }
  
+ static int ref_present(const char *refname,
+                      const struct object_id *oid, int flags, void *cb_data)
+ {
+       struct string_list *affected_refnames = cb_data;
+       return string_list_has_string(affected_refnames, refname);
+ }
+ int initial_ref_transaction_commit(struct ref_transaction *transaction,
+                                  struct strbuf *err)
+ {
+       struct ref_dir *loose_refs = get_loose_refs(&ref_cache);
+       struct ref_dir *packed_refs = get_packed_refs(&ref_cache);
+       int ret = 0, i;
+       int n = transaction->nr;
+       struct ref_update **updates = transaction->updates;
+       struct string_list affected_refnames = STRING_LIST_INIT_NODUP;
+       assert(err);
+       if (transaction->state != REF_TRANSACTION_OPEN)
+               die("BUG: commit called for transaction that is not open");
+       /* Fail if a refname appears more than once in the transaction: */
+       for (i = 0; i < n; i++)
+               string_list_append(&affected_refnames, updates[i]->refname);
+       string_list_sort(&affected_refnames);
+       if (ref_update_reject_duplicates(&affected_refnames, err)) {
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+       /*
+        * It's really undefined to call this function in an active
+        * repository or when there are existing references: we are
+        * only locking and changing packed-refs, so (1) any
+        * simultaneous processes might try to change a reference at
+        * the same time we do, and (2) any existing loose versions of
+        * the references that we are setting would have precedence
+        * over our values. But some remote helpers create the remote
+        * "HEAD" and "master" branches before calling this function,
+        * so here we really only check that none of the references
+        * that we are creating already exists.
+        */
+       if (for_each_rawref(ref_present, &affected_refnames))
+               die("BUG: initial ref transaction called with existing refs");
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+               if ((update->flags & REF_HAVE_OLD) &&
+                   !is_null_sha1(update->old_sha1))
+                       die("BUG: initial ref transaction with old_sha1 set");
+               if (verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            loose_refs, err) ||
+                   verify_refname_available(update->refname,
+                                            &affected_refnames, NULL,
+                                            packed_refs, err)) {
+                       ret = TRANSACTION_NAME_CONFLICT;
+                       goto cleanup;
+               }
+       }
+       if (lock_packed_refs(0)) {
+               strbuf_addf(err, "unable to lock packed-refs file: %s",
+                           strerror(errno));
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+               if ((update->flags & REF_HAVE_NEW) &&
+                   !is_null_sha1(update->new_sha1))
+                       add_packed_ref(update->refname, update->new_sha1);
+       }
+       if (commit_packed_refs()) {
+               strbuf_addf(err, "unable to commit packed-refs file: %s",
+                           strerror(errno));
+               ret = TRANSACTION_GENERIC_ERROR;
+               goto cleanup;
+       }
+ cleanup:
+       transaction->state = REF_TRANSACTION_CLOSED;
+       string_list_clear(&affected_refnames, 0);
+       return ret;
+ }
  char *shorten_unambiguous_ref(const char *refname, int strict)
  {
        int i;