Merge 'hv/submodule-config' to 'sb/submodule-helper'
[gitweb.git] / refs.c
diff --git a/refs.c b/refs.c
index 97a75f3c3125d6a73e020ee2da73fcb6cce3d13c..84b1aff8421de069c0e89e5a145642c8efe5d412 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -19,12 +19,14 @@ struct ref_lock {
  * 1: End-of-component
  * 2: ., look for a preceding . to reject .. in refs
  * 3: {, look for a preceding @ to reject @{ in refs
- * 4: A bad character: ASCII control characters, "~", "^", ":" or SP
+ * 4: A bad character: ASCII control characters, and
+ *    ":", "?", "[", "\", "^", "~", SP, or TAB
+ * 5: *, reject unless REFNAME_REFSPEC_PATTERN is set
  */
 static unsigned char refname_disposition[256] = {
        1, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
        4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4,
-       4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 2, 1,
+       4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 0, 0, 0, 2, 1,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 4,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 0, 4, 0,
@@ -75,12 +77,14 @@ static unsigned char refname_disposition[256] = {
  *
  * - 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 contains a "\" (backslash)
+ * - it has ASCII control characters, or
+ * - it has ":", "?", "[", "\", "^", "~", SP, or TAB anywhere, or
+ * - it has "*" anywhere unless REFNAME_REFSPEC_PATTERN is set, or
+ * - it ends with a "/", or
+ * - it ends with ".lock", or
+ * - it contains a "@{" portion
  */
-static int check_refname_component(const char *refname, int flags)
+static int check_refname_component(const char *refname, int *flags)
 {
        const char *cp;
        char last = '\0';
@@ -101,6 +105,16 @@ static int check_refname_component(const char *refname, int flags)
                        break;
                case 4:
                        return -1;
+               case 5:
+                       if (!(*flags & REFNAME_REFSPEC_PATTERN))
+                               return -1; /* refspec can't be a pattern */
+
+                       /*
+                        * Unset the pattern flag so that we only accept
+                        * a single asterisk for one side of refspec.
+                        */
+                       *flags &= ~ REFNAME_REFSPEC_PATTERN;
+                       break;
                }
                last = ch;
        }
@@ -125,18 +139,10 @@ int check_refname_format(const char *refname, int flags)
 
        while (1) {
                /* We are at the start of a path component. */
-               component_len = check_refname_component(refname, flags);
-               if (component_len <= 0) {
-                       if ((flags & REFNAME_REFSPEC_PATTERN) &&
-                                       refname[0] == '*' &&
-                                       (refname[1] == '\0' || refname[1] == '/')) {
-                               /* Accept one wildcard as a full refname component. */
-                               flags &= ~REFNAME_REFSPEC_PATTERN;
-                               component_len = 1;
-                       } else {
-                               return -1;
-                       }
-               }
+               component_len = check_refname_component(refname, &flags);
+               if (component_len <= 0)
+                       return -1;
+
                component_count++;
                if (refname[component_len] == '\0')
                        break;
@@ -1282,12 +1288,12 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
  */
 static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
 {
-       const char *packed_refs_file;
+       char *packed_refs_file;
 
        if (*refs->name)
-               packed_refs_file = git_path_submodule(refs->name, "packed-refs");
+               packed_refs_file = git_pathdup_submodule(refs->name, "packed-refs");
        else
-               packed_refs_file = git_path("packed-refs");
+               packed_refs_file = git_pathdup("packed-refs");
 
        if (refs->packed &&
            !stat_validity_check(&refs->packed->validity, packed_refs_file))
@@ -1306,6 +1312,7 @@ static struct packed_ref_cache *get_packed_ref_cache(struct ref_cache *refs)
                        fclose(f);
                }
        }
+       free(packed_refs_file);
        return refs->packed;
 }
 
@@ -1319,7 +1326,13 @@ static struct ref_dir *get_packed_refs(struct ref_cache *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);
@@ -1339,19 +1352,23 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
 {
        struct ref_cache *refs = dir->ref_cache;
        DIR *d;
-       const char *path;
        struct dirent *de;
        int dirnamelen = strlen(dirname);
        struct strbuf refname;
+       struct strbuf path = STRBUF_INIT;
+       size_t path_baselen;
 
        if (*refs->name)
-               path = git_path_submodule(refs->name, "%s", dirname);
+               strbuf_git_path_submodule(&path, refs->name, "%s", dirname);
        else
-               path = git_path("%s", dirname);
+               strbuf_git_path(&path, "%s", dirname);
+       path_baselen = path.len;
 
-       d = opendir(path);
-       if (!d)
+       d = opendir(path.buf);
+       if (!d) {
+               strbuf_release(&path);
                return;
+       }
 
        strbuf_init(&refname, dirnamelen + 257);
        strbuf_add(&refname, dirname, dirnamelen);
@@ -1360,17 +1377,14 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                unsigned char sha1[20];
                struct stat st;
                int flag;
-               const char *refdir;
 
                if (de->d_name[0] == '.')
                        continue;
                if (ends_with(de->d_name, ".lock"))
                        continue;
                strbuf_addstr(&refname, de->d_name);
-               refdir = *refs->name
-                       ? git_path_submodule(refs->name, "%s", refname.buf)
-                       : git_path("%s", refname.buf);
-               if (stat(refdir, &st) < 0) {
+               strbuf_addstr(&path, de->d_name);
+               if (stat(path.buf, &st) < 0) {
                        ; /* silently ignore */
                } else if (S_ISDIR(st.st_mode)) {
                        strbuf_addch(&refname, '/');
@@ -1417,8 +1431,10 @@ static void read_loose_refs(const char *dirname, struct ref_dir *dir)
                                         create_ref_entry(refname.buf, sha1, flag, 0));
                }
                strbuf_setlen(&refname, dirnamelen);
+               strbuf_setlen(&path, path_baselen);
        }
        strbuf_release(&refname);
+       strbuf_release(&path);
        closedir(d);
 }
 
@@ -1469,14 +1485,15 @@ static int resolve_gitlink_ref_recursive(struct ref_cache *refs,
 {
        int fd, len;
        char buffer[128], *p;
-       const char *path;
+       char *path;
 
        if (recursion > MAXDEPTH || strlen(refname) > MAXREFLEN)
                return -1;
        path = *refs->name
-               ? git_path_submodule(refs->name, "%s", refname)
-               : git_path("%s", refname);
+               ? git_pathdup_submodule(refs->name, "%s", refname)
+               : git_pathdup("%s", refname);
        fd = open(path, O_RDONLY);
+       free(path);
        if (fd < 0)
                return resolve_gitlink_packed_ref(refs, refname, sha1);
 
@@ -1746,9 +1763,11 @@ const char *resolve_ref_unsafe(const char *refname, int resolve_flags,
        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 */
@@ -2127,7 +2146,8 @@ int for_each_remote_ref_submodule(const char *submodule, each_ref_fn fn, void *c
 
 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)
@@ -2270,25 +2290,14 @@ static int verify_lock(struct ref_lock *lock,
        return 0;
 }
 
-static int remove_empty_directories(const char *file)
+static int remove_empty_directories(struct strbuf *path)
 {
-       /* we want to create a file but there is a directory there;
+       /*
+        * we want to create a file but there is a directory there;
         * if that is an empty directory (or a directory that contains
         * only empty directories), remove them.
         */
-       struct strbuf path;
-       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;
+       return remove_dir_recursively(path, REMOVE_DIR_EMPTY_ONLY);
 }
 
 /*
@@ -2388,7 +2397,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                                            unsigned int flags, int *type_p,
                                            struct strbuf *err)
 {
-       const char *ref_file;
+       struct strbuf ref_file = STRBUF_INIT;
+       struct strbuf orig_ref_file = STRBUF_INIT;
        const char *orig_refname = refname;
        struct ref_lock *lock;
        int last_errno = 0;
@@ -2412,20 +2422,19 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
        refname = resolve_ref_unsafe(refname, resolve_flags,
                                     lock->old_oid.hash, &type);
        if (!refname && errno == EISDIR) {
-               /* we are trying to lock foo but we used to
+               /*
+                * we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
                 * it is normal for the empty directory 'foo'
                 * to remain.
                 */
-               ref_file = git_path("%s", orig_refname);
-               if (remove_empty_directories(ref_file)) {
+               strbuf_git_path(&orig_ref_file, "%s", orig_refname);
+               if (remove_empty_directories(&orig_ref_file)) {
                        last_errno = errno;
-
                        if (!verify_refname_available(orig_refname, extras, skip,
                                                      get_loose_refs(&ref_cache), err))
                                strbuf_addf(err, "there are still refs under '%s'",
                                            orig_refname);
-
                        goto error_return;
                }
                refname = resolve_ref_unsafe(orig_refname, resolve_flags,
@@ -2465,10 +2474,10 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
        }
        lock->ref_name = xstrdup(refname);
        lock->orig_ref_name = xstrdup(orig_refname);
-       ref_file = git_path("%s", refname);
+       strbuf_git_path(&ref_file, "%s", refname);
 
  retry:
-       switch (safe_create_leading_directories_const(ref_file)) {
+       switch (safe_create_leading_directories_const(ref_file.buf)) {
        case SCLD_OK:
                break; /* success */
        case SCLD_VANISHED:
@@ -2477,11 +2486,12 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                /* fall through */
        default:
                last_errno = errno;
-               strbuf_addf(err, "unable to create directory for %s", ref_file);
+               strbuf_addf(err, "unable to create directory for %s",
+                           ref_file.buf);
                goto error_return;
        }
 
-       if (hold_lock_file_for_update(lock->lk, ref_file, lflags) < 0) {
+       if (hold_lock_file_for_update(lock->lk, ref_file.buf, lflags) < 0) {
                last_errno = errno;
                if (errno == ENOENT && --attempts_remaining > 0)
                        /*
@@ -2491,7 +2501,7 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                         */
                        goto retry;
                else {
-                       unable_to_lock_message(ref_file, errno, err);
+                       unable_to_lock_message(ref_file.buf, errno, err);
                        goto error_return;
                }
        }
@@ -2499,12 +2509,17 @@ static struct ref_lock *lock_ref_sha1_basic(const char *refname,
                last_errno = errno;
                goto error_return;
        }
-       return lock;
+       goto out;
 
  error_return:
        unlock_ref(lock);
+       lock = NULL;
+
+ out:
+       strbuf_release(&ref_file);
+       strbuf_release(&orig_ref_file);
        errno = last_errno;
-       return NULL;
+       return lock;
 }
 
 /*
@@ -2535,8 +2550,12 @@ static int write_packed_entry_fn(struct ref_entry *entry, void *cb_data)
        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;
@@ -2566,10 +2585,12 @@ int lock_packed_refs(int flags)
 }
 
 /*
- * 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);
@@ -2598,7 +2619,12 @@ int commit_packed_refs(void)
        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);
@@ -2756,7 +2782,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;
@@ -2821,15 +2854,15 @@ static int delete_ref_loose(struct ref_lock *lock, int flag, struct strbuf *err)
        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);
@@ -2842,6 +2875,44 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag
        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.
@@ -2854,9 +2925,13 @@ int delete_ref(const char *refname, const unsigned char *sha1, unsigned int flag
 static int rename_tmp_log(const char *newrefname)
 {
        int attempts_remaining = 4;
+       struct strbuf path = STRBUF_INIT;
+       int ret = -1;
 
  retry:
-       switch (safe_create_leading_directories_const(git_path("logs/%s", newrefname))) {
+       strbuf_reset(&path);
+       strbuf_git_path(&path, "logs/%s", newrefname);
+       switch (safe_create_leading_directories_const(path.buf)) {
        case SCLD_OK:
                break; /* success */
        case SCLD_VANISHED:
@@ -2865,19 +2940,19 @@ static int rename_tmp_log(const char *newrefname)
                /* fall through */
        default:
                error("unable to create directory for %s", newrefname);
-               return -1;
+               goto out;
        }
 
-       if (rename(git_path(TMP_RENAMED_LOG), git_path("logs/%s", newrefname))) {
+       if (rename(git_path(TMP_RENAMED_LOG), path.buf)) {
                if ((errno==EISDIR || errno==ENOTDIR) && --attempts_remaining > 0) {
                        /*
                         * rename(a, b) when b is an existing
                         * directory ought to result in ISDIR, but
                         * Solaris 5.8 gives ENOTDIR.  Sheesh.
                         */
-                       if (remove_empty_directories(git_path("logs/%s", newrefname))) {
+                       if (remove_empty_directories(&path)) {
                                error("Directory not empty: logs/%s", newrefname);
-                               return -1;
+                               goto out;
                        }
                        goto retry;
                } else if (errno == ENOENT && --attempts_remaining > 0) {
@@ -2890,10 +2965,13 @@ static int rename_tmp_log(const char *newrefname)
                } else {
                        error("unable to move logfile "TMP_RENAMED_LOG" to logs/%s: %s",
                                newrefname, strerror(errno));
-                       return -1;
+                       goto out;
                }
        }
-       return 0;
+       ret = 0;
+out:
+       strbuf_release(&path);
+       return ret;
 }
 
 static int rename_ref_available(const char *oldname, const char *newname)
@@ -2957,7 +3035,14 @@ int rename_ref(const char *oldrefname, const char *newrefname, const char *logms
        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))) {
+                       struct strbuf path = STRBUF_INIT;
+                       int result;
+
+                       strbuf_git_path(&path, "%s", newrefname);
+                       result = remove_empty_directories(&path);
+                       strbuf_release(&path);
+
+                       if (result) {
                                error("Directory not empty: %s", newrefname);
                                goto rollback;
                        }
@@ -3074,25 +3159,21 @@ static int should_autocreate_reflog(const char *refname)
  * should_autocreate_reflog returns non-zero.  Otherwise, create it
  * regardless of the ref name.  Fill in *err and return -1 on failure.
  */
-static int log_ref_setup(const char *refname, struct strbuf *sb_logfile, struct strbuf *err, int force_create)
+static int log_ref_setup(const char *refname, struct strbuf *logfile, struct strbuf *err, int force_create)
 {
        int logfd, oflags = O_APPEND | O_WRONLY;
-       char *logfile;
 
-       strbuf_git_path(sb_logfile, "logs/%s", refname);
-       logfile = sb_logfile->buf;
-       /* make sure the rest of the function can't change "logfile" */
-       sb_logfile = NULL;
+       strbuf_git_path(logfile, "logs/%s", refname);
        if (force_create || should_autocreate_reflog(refname)) {
-               if (safe_create_leading_directories(logfile) < 0) {
+               if (safe_create_leading_directories(logfile->buf) < 0) {
                        strbuf_addf(err, "unable to create directory for %s: "
-                                   "%s", logfile, strerror(errno));
+                                   "%s", logfile->buf, strerror(errno));
                        return -1;
                }
                oflags |= O_CREAT;
        }
 
-       logfd = open(logfile, oflags, 0666);
+       logfd = open(logfile->buf, oflags, 0666);
        if (logfd < 0) {
                if (!(oflags & O_CREAT) && (errno == ENOENT || errno == EISDIR))
                        return 0;
@@ -3100,20 +3181,20 @@ static int log_ref_setup(const char *refname, struct strbuf *sb_logfile, struct
                if (errno == EISDIR) {
                        if (remove_empty_directories(logfile)) {
                                strbuf_addf(err, "There are still logs under "
-                                           "'%s'", logfile);
+                                           "'%s'", logfile->buf);
                                return -1;
                        }
-                       logfd = open(logfile, oflags, 0666);
+                       logfd = open(logfile->buf, oflags, 0666);
                }
 
                if (logfd < 0) {
                        strbuf_addf(err, "unable to append to %s: %s",
-                                   logfile, strerror(errno));
+                                   logfile->buf, strerror(errno));
                        return -1;
                }
        }
 
-       adjust_shared_perm(logfile);
+       adjust_shared_perm(logfile->buf);
        close(logfd);
        return 0;
 }
@@ -3157,36 +3238,32 @@ static int log_ref_write_fd(int fd, const unsigned char *old_sha1,
 
 static int log_ref_write_1(const char *refname, const unsigned char *old_sha1,
                           const unsigned char *new_sha1, const char *msg,
-                          struct strbuf *sb_log_file, int flags,
+                          struct strbuf *logfile, int flags,
                           struct strbuf *err)
 {
        int logfd, result, oflags = O_APPEND | O_WRONLY;
-       char *log_file;
 
        if (log_all_ref_updates < 0)
                log_all_ref_updates = !is_bare_repository();
 
-       result = log_ref_setup(refname, sb_log_file, err, flags & REF_FORCE_CREATE_REFLOG);
+       result = log_ref_setup(refname, logfile, err, flags & REF_FORCE_CREATE_REFLOG);
 
        if (result)
                return result;
-       log_file = sb_log_file->buf;
-       /* make sure the rest of the function can't change "log_file" */
-       sb_log_file = NULL;
 
-       logfd = open(log_file, oflags);
+       logfd = open(logfile->buf, oflags);
        if (logfd < 0)
                return 0;
        result = log_ref_write_fd(logfd, old_sha1, new_sha1,
                                  git_committer_info(0), msg);
        if (result) {
-               strbuf_addf(err, "unable to append to %s: %s", log_file,
+               strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
                            strerror(errno));
                close(logfd);
                return -1;
        }
        if (close(logfd)) {
-               strbuf_addf(err, "unable to append to %s: %s", log_file,
+               strbuf_addf(err, "unable to append to %s: %s", logfile->buf,
                            strerror(errno));
                return -1;
        }
@@ -3307,7 +3384,7 @@ static int commit_ref_update(struct ref_lock *lock,
 int create_symref(const char *ref_target, const char *refs_heads_master,
                  const char *logmsg)
 {
-       const char *lockpath;
+       char *lockpath = NULL;
        char ref[1000];
        int fd, len, written;
        char *git_HEAD = git_pathdup("%s", ref_target);
@@ -3334,7 +3411,7 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
                error("refname too long: %s", refs_heads_master);
                goto error_free_return;
        }
-       lockpath = mkpath("%s.lock", git_HEAD);
+       lockpath = mkpathdup("%s.lock", git_HEAD);
        fd = open(lockpath, O_CREAT | O_EXCL | O_WRONLY, 0666);
        if (fd < 0) {
                error("Unable to open %s for writing", lockpath);
@@ -3354,9 +3431,11 @@ int create_symref(const char *ref_target, const char *refs_heads_master,
        error_unlink_return:
                unlink_or_warn(lockpath);
        error_free_return:
+               free(lockpath);
                free(git_HEAD);
                return -1;
        }
+       free(lockpath);
 
 #ifndef NO_SYMLINK_HEAD
        done:
@@ -3416,14 +3495,14 @@ static int read_ref_at_ent(unsigned char *osha1, unsigned char *nsha1,
                        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));
+                                       cb->refname, show_date(cb->date, cb->tz, DATE_MODE(RFC2822)));
                }
                else if (cb->date == cb->at_time)
                        hashcpy(cb->sha1, nsha1);
                else if (hashcmp(nsha1, cb->sha1))
                        warning("Log for ref %s unexpectedly ended on %s.",
                                cb->refname, show_date(cb->date, cb->tz,
-                                                  DATE_RFC2822));
+                                                      DATE_MODE(RFC2822)));
                hashcpy(cb->osha1, osha1);
                hashcpy(cb->nsha1, nsha1);
                cb->found_it = 1;
@@ -4087,6 +4166,98 @@ int ref_transaction_commit(struct ref_transaction *transaction,
        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;