Jonathan Nieder wrote:
> Jonathan Nieder wrote:

>> The next series from Ronnie's collection is available at
>> https://code-review.googlesource.com/#/q/topic:ref-transaction in case
>> someone wants a fresh series to look at.
>
> Here is the outcome of that review.  It could use another set of eyes
> (hint, hint)

Another set of eyes arrived and helped.  Here's a reroll.

Jonathan Nieder (6):
  mv test: recreate mod/ directory instead of relying on stale copy
  branch -d: avoid repeated symref resolution
  packed-ref cache: forbid dot-components in refnames
  refs.c: do not permit err == NULL
  lockfile: remove unable_to_lock_error
  ref_transaction_commit: bail out on failure to remove a ref

Junio C Hamano (1):
  reflog test: test interaction with detached HEAD

Ronnie Sahlberg (17):
  wrapper.c: remove/unlink_or_warn: simplify, treat ENOENT as success
  wrapper.c: add a new function unlink_or_msg
  refs.c: add an err argument to delete_ref_loose
  refs.c: pass the ref log message to _create/delete/update instead of
    _commit
  rename_ref: don't ask read_ref_full where the ref came from
  refs.c: refuse to lock badly named refs in lock_ref_sha1_basic
  refs.c: call lock_ref_sha1_basic directly from commit
  refs.c: pass a list of names to skip to is_refname_available
  refs.c: ref_transaction_commit: distinguish name conflicts from other
    errors
  fetch.c: change s_update_ref to use a ref transaction
  refs.c: make write_ref_sha1 static
  refs.c: change resolve_ref_unsafe reading argument to be a flags field
  branch -d: simplify by using RESOLVE_REF_READING flag
  test: put tests for handling of bad ref names in one place
  refs.c: allow listing and deleting badly named refs
  for-each-ref.c: improve message before aborting on broken ref
  remote rm/prune: print a message when writing packed-refs fails

 branch.c                 |   6 +-
 builtin/blame.c          |   2 +-
 builtin/branch.c         |  22 ++-
 builtin/checkout.c       |   6 +-
 builtin/clone.c          |   2 +-
 builtin/commit.c         |   6 +-
 builtin/fetch.c          |  34 ++--
 builtin/fmt-merge-msg.c  |   2 +-
 builtin/for-each-ref.c   |  11 +-
 builtin/fsck.c           |   2 +-
 builtin/log.c            |   3 +-
 builtin/merge.c          |   2 +-
 builtin/notes.c          |   2 +-
 builtin/receive-pack.c   |   9 +-
 builtin/remote.c         |  20 ++-
 builtin/replace.c        |   5 +-
 builtin/show-branch.c    |   7 +-
 builtin/symbolic-ref.c   |   2 +-
 builtin/tag.c            |   4 +-
 builtin/update-ref.c     |  13 +-
 bundle.c                 |   2 +-
 cache.h                  |  42 +++--
 fast-import.c            |   8 +-
 git-compat-util.h        |  16 +-
 http-backend.c           |   4 +-
 lockfile.c               |  10 --
 notes-merge.c            |   2 +-
 reflog-walk.c            |   5 +-
 refs.c                   | 438 ++++++++++++++++++++++++++++++++---------------
 refs.h                   |  40 +++--
 remote.c                 |  11 +-
 sequencer.c              |   8 +-
 t/t1400-update-ref.sh    |  62 +++----
 t/t1413-reflog-detach.sh |  70 ++++++++
 t/t1430-bad-ref-name.sh  | 207 ++++++++++++++++++++++
 t/t3200-branch.sh        |   9 +
 t/t7001-mv.sh            |  15 +-
 t/t9300-fast-import.sh   |  30 ----
 transport-helper.c       |   5 +-
 transport.c              |   5 +-
 upload-pack.c            |   2 +-
 walker.c                 |   5 +-
 wrapper.c                |  28 ++-
 wt-status.c              |   2 +-
 44 files changed, 838 insertions(+), 348 deletions(-)
 create mode 100755 t/t1413-reflog-detach.sh
 create mode 100755 t/t1430-bad-ref-name.sh

-- 
2.0.0.450.ga793d96

---
Changes since v21:

 branch.c                    |   2 +-
 builtin/blame.c             |   2 +-
 builtin/branch.c            |  25 ++---
 builtin/checkout.c          |   6 +-
 builtin/clone.c             |   2 +-
 builtin/commit.c            |   2 +-
 builtin/fetch.c             |   6 +-
 builtin/fmt-merge-msg.c     |   2 +-
 builtin/for-each-ref.c      |  11 +-
 builtin/fsck.c              |   2 +-
 builtin/log.c               |   4 +-
 builtin/merge.c             |   2 +-
 builtin/notes.c             |   2 +-
 builtin/receive-pack.c      |   4 +-
 builtin/remote.c            |  21 ++--
 builtin/show-branch.c       |   9 +-
 builtin/symbolic-ref.c      |   2 +-
 builtin/update-ref.c        |   3 +-
 bundle.c                    |   2 +-
 cache.h                     |  33 ++++--
 http-backend.c              |   5 +-
 notes-merge.c               |   2 +-
 reflog-walk.c               |   6 +-
 refs.c                      | 263 +++++++++++++++++++++++++-------------------
 refs.h                      |  34 +++---
 remote.c                    |  13 ++-
 sequencer.c                 |   4 +-
 t/t1400-update-ref.sh       |  46 ++------
 t/t1402-check-ref-format.sh |  48 --------
 t/t1413-reflog-detach.sh    |  70 ++++++++++++
 t/t1430-bad-ref-name.sh     | 207 ++++++++++++++++++++++++++++++++++
 t/t9300-fast-import.sh      |  30 -----
 transport-helper.c          |   5 +-
 transport.c                 |   6 +-
 upload-pack.c               |   2 +-
 wt-status.c                 |   2 +-
 36 files changed, 557 insertions(+), 328 deletions(-)

diff --git c/branch.c w/branch.c
index ba3e1c1..adb07c6 100644
--- c/branch.c
+++ w/branch.c
@@ -186,7 +186,7 @@ int validate_new_branchname(const char *name, struct strbuf 
*ref,
                const char *head;
                unsigned char sha1[20];
 
-               head = resolve_ref_unsafe("HEAD", sha1, NULL, 0);
+               head = resolve_ref_unsafe("HEAD", 0, sha1, NULL);
                if (!is_bare_repository() && head && !strcmp(head, ref->buf))
                        die(_("Cannot force update the current branch."));
        }
diff --git c/builtin/blame.c w/builtin/blame.c
index b8bec0a..5cbd38f 100644
--- c/builtin/blame.c
+++ w/builtin/blame.c
@@ -2292,7 +2292,7 @@ static struct commit *fake_working_tree_commit(struct 
diff_options *opt,
        commit->object.type = OBJ_COMMIT;
        parent_tail = &commit->parents;
 
-       if (!resolve_ref_unsafe("HEAD", head_sha1, NULL, RESOLVE_REF_READING))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
                die("no such ref: HEAD");
 
        parent_tail = append_parent(parent_tail, head_sha1);
diff --git c/builtin/branch.c w/builtin/branch.c
index 5d5bc56..94aaea1 100644
--- c/builtin/branch.c
+++ w/builtin/branch.c
@@ -129,8 +129,8 @@ static int branch_merged(int kind, const char *name,
                    branch->merge[0] &&
                    branch->merge[0]->dst &&
                    (reference_name = reference_name_to_free =
-                    resolve_refdup(branch->merge[0]->dst, sha1,
-                                   NULL, RESOLVE_REF_READING)) != NULL)
+                    resolve_refdup(branch->merge[0]->dst, RESOLVE_REF_READING,
+                                   sha1, NULL)) != NULL)
                        reference_rev = lookup_commit_reference(sha1);
        }
        if (!reference_rev)
@@ -234,13 +234,12 @@ static int delete_branches(int argc, const char **argv, 
int force, int kinds,
                free(name);
 
                name = mkpathdup(fmt, bname.buf);
-               target = resolve_ref_unsafe(name, sha1, &flags,
+               target = resolve_ref_unsafe(name,
                                            RESOLVE_REF_READING
-                                           | RESOLVE_REF_NODEREF
-                                           | RESOLVE_REF_ALLOW_BAD_NAME);
-               if (!target ||
-                   (!(flags & (REF_ISSYMREF|REF_ISBROKEN)) &&
-                    is_null_sha1(sha1))) {
+                                           | RESOLVE_REF_NO_RECURSE
+                                           | RESOLVE_REF_ALLOW_BAD_NAME,
+                                           sha1, &flags);
+               if (!target) {
                        error(remote_branch
                              ? _("remote branch '%s' not found.")
                              : _("branch '%s' not found."), bname.buf);
@@ -255,7 +254,7 @@ static int delete_branches(int argc, const char **argv, int 
force, int kinds,
                        continue;
                }
 
-               if (delete_ref(name, sha1, REF_NODEREF|REF_BADNAMEOK)) {
+               if (delete_ref(name, sha1, REF_NODEREF)) {
                        error(remote_branch
                              ? _("Error deleting remote branch '%s'")
                              : _("Error deleting branch '%s'"),
@@ -268,8 +267,8 @@ static int delete_branches(int argc, const char **argv, int 
force, int kinds,
                               ? _("Deleted remote branch %s (was %s).\n")
                               : _("Deleted branch %s (was %s).\n"),
                               bname.buf,
-                              (flags & REF_ISSYMREF)
-                              ? target
+                              (flags & REF_ISBROKEN) ? "broken"
+                              : (flags & REF_ISSYMREF) ? target
                               : find_unique_abbrev(sha1, DEFAULT_ABBREV));
                }
                delete_branch_config(bname.buf);
@@ -301,7 +300,7 @@ static char *resolve_symref(const char *src, const char 
*prefix)
        int flag;
        const char *dst, *cp;
 
-       dst = resolve_ref_unsafe(src, sha1, &flag, 0);
+       dst = resolve_ref_unsafe(src, 0, sha1, &flag);
        if (!(dst && (flag & REF_ISSYMREF)))
                return NULL;
        if (prefix && (cp = skip_prefix(dst, prefix)))
@@ -867,7 +866,7 @@ int cmd_branch(int argc, const char **argv, const char 
*prefix)
 
        track = git_branch_track;
 
-       head = resolve_refdup("HEAD", head_sha1, NULL, 0);
+       head = resolve_refdup("HEAD", 0, head_sha1, NULL);
        if (!head)
                die(_("Failed to resolve HEAD as a valid ref."));
        if (!strcmp(head, "HEAD")) {
diff --git c/builtin/checkout.c w/builtin/checkout.c
index 64af1f7..a5fef2d 100644
--- c/builtin/checkout.c
+++ w/builtin/checkout.c
@@ -356,7 +356,7 @@ static int checkout_paths(const struct checkout_opts *opts,
            commit_locked_index(lock_file))
                die(_("unable to write new index file"));
 
-       read_ref_full("HEAD", rev, &flag, 0);
+       read_ref_full("HEAD", 0, rev, &flag);
        head = lookup_commit_reference_gently(rev, 1);
 
        errs |= post_checkout_hook(head, head, 0);
@@ -771,7 +771,7 @@ static int switch_branches(const struct checkout_opts *opts,
        unsigned char rev[20];
        int flag, writeout_error = 0;
        memset(&old, 0, sizeof(old));
-       old.path = path_to_free = resolve_refdup("HEAD", rev, &flag, 0);
+       old.path = path_to_free = resolve_refdup("HEAD", 0, rev, &flag);
        old.commit = lookup_commit_reference_gently(rev, 1);
        if (!(flag & REF_ISSYMREF))
                old.path = NULL;
@@ -1068,7 +1068,7 @@ static int checkout_branch(struct checkout_opts *opts,
                unsigned char rev[20];
                int flag;
 
-               if (!read_ref_full("HEAD", rev, &flag, 0) &&
+               if (!read_ref_full("HEAD", 0, rev, &flag) &&
                    (flag & REF_ISSYMREF) && is_null_sha1(rev))
                        return switch_unborn_to_new_branch(opts);
        }
diff --git c/builtin/clone.c w/builtin/clone.c
index 12a78e1..0f5c880 100644
--- c/builtin/clone.c
+++ w/builtin/clone.c
@@ -622,7 +622,7 @@ static int checkout(void)
        if (option_no_checkout)
                return 0;
 
-       head = resolve_refdup("HEAD", sha1, NULL, RESOLVE_REF_READING);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, sha1, NULL);
        if (!head) {
                warning(_("remote HEAD refers to nonexistent ref, "
                          "unable to checkout.\n"));
diff --git c/builtin/commit.c w/builtin/commit.c
index 3536330..9ccc78b 100644
--- c/builtin/commit.c
+++ w/builtin/commit.c
@@ -1468,7 +1468,7 @@ static void print_summary(const char *prefix, const 
unsigned char *sha1,
        rev.diffopt.break_opt = 0;
        diff_setup_done(&rev.diffopt);
 
-       head = resolve_ref_unsafe("HEAD", junk_sha1, NULL, 0);
+       head = resolve_ref_unsafe("HEAD", 0, junk_sha1, NULL);
        printf("[%s%s ",
                starts_with(head, "refs/heads/") ?
                        head + 11 :
diff --git c/builtin/fetch.c w/builtin/fetch.c
index 2e3bc73..30b40f6 100644
--- c/builtin/fetch.c
+++ w/builtin/fetch.c
@@ -392,10 +392,10 @@ static int s_update_ref(const char *action,
                goto fail;
 
        ret = ref_transaction_commit(transaction, &err);
-       if (ret == UPDATE_REFS_NAME_CONFLICT)
-               df_conflict = 1;
-       if (ret)
+       if (ret) {
+               df_conflict = (ret == TRANSACTION_NAME_CONFLICT);
                goto fail;
+       }
 
        ref_transaction_free(transaction);
        strbuf_release(&err);
diff --git c/builtin/fmt-merge-msg.c w/builtin/fmt-merge-msg.c
index b2355ad..afe05dc 100644
--- c/builtin/fmt-merge-msg.c
+++ w/builtin/fmt-merge-msg.c
@@ -602,7 +602,7 @@ int fmt_merge_msg(struct strbuf *in, struct strbuf *out,
 
        /* get current branch */
        current_branch = current_branch_to_free =
-               resolve_refdup("HEAD", head_sha1, NULL, RESOLVE_REF_READING);
+               resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
        if (!current_branch)
                die("No current branch");
        if (starts_with(current_branch, "refs/heads/"))
diff --git c/builtin/for-each-ref.c w/builtin/for-each-ref.c
index 090390c..a88d681 100644
--- c/builtin/for-each-ref.c
+++ w/builtin/for-each-ref.c
@@ -649,8 +649,8 @@ static void populate_value(struct refinfo *ref)
 
        if (need_symref && (ref->flag & REF_ISSYMREF) && !ref->symref) {
                unsigned char unused1[20];
-               ref->symref = resolve_refdup(ref->refname, unused1,
-                                            NULL, RESOLVE_REF_READING);
+               ref->symref = resolve_refdup(ref->refname, RESOLVE_REF_READING,
+                                            unused1, NULL);
                if (!ref->symref)
                        ref->symref = "";
        }
@@ -708,8 +708,8 @@ static void populate_value(struct refinfo *ref)
                        const char *head;
                        unsigned char sha1[20];
 
-                       head = resolve_ref_unsafe("HEAD", sha1,
-                                                 NULL, RESOLVE_REF_READING);
+                       head = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+                                                 sha1, NULL);
                        if (!strcmp(ref->refname, head))
                                v->s = "*";
                        else
@@ -853,8 +853,7 @@ static int grab_single_ref(const char *refname, const 
unsigned char *sha1, int f
        struct refinfo *ref;
        int cnt;
 
-       if ((flag & REF_ISBROKEN) &&
-           check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+       if (flag & REF_BAD_NAME) {
                  warning("ignoring ref with broken name %s", refname);
                  return 0;
        }
diff --git c/builtin/fsck.c w/builtin/fsck.c
index 506e32b..7cd109a 100644
--- c/builtin/fsck.c
+++ w/builtin/fsck.c
@@ -560,7 +560,7 @@ static int fsck_head_link(void)
        if (verbose)
                fprintf(stderr, "Checking HEAD link\n");
 
-       head_points_at = resolve_ref_unsafe("HEAD", head_sha1, &flag, 0);
+       head_points_at = resolve_ref_unsafe("HEAD", 0, head_sha1, &flag);
        if (!head_points_at)
                return error("Invalid HEAD");
        if (!strcmp(head_points_at, "HEAD"))
diff --git c/builtin/log.c w/builtin/log.c
index 230a9ef..493440a 100644
--- c/builtin/log.c
+++ w/builtin/log.c
@@ -1395,8 +1395,8 @@ int cmd_format_patch(int argc, const char **argv, const 
char *prefix)
                if (check_head) {
                        unsigned char sha1[20];
                        const char *ref;
-                       ref = resolve_ref_unsafe("HEAD", sha1, NULL,
-                                                RESOLVE_REF_READING);
+                       ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+                                                sha1, NULL);
                        if (ref && starts_with(ref, "refs/heads/"))
                                branch_name = xstrdup(ref + 
strlen("refs/heads/"));
                        else
diff --git c/builtin/merge.c w/builtin/merge.c
index d262279..6f56967 100644
--- c/builtin/merge.c
+++ w/builtin/merge.c
@@ -1108,7 +1108,7 @@ int cmd_merge(int argc, const char **argv, const char 
*prefix)
         * Check if we are _not_ on a detached HEAD, i.e. if there is a
         * current branch.
         */
-       branch = branch_to_free = resolve_refdup("HEAD", head_sha1, &flag, 0);
+       branch = branch_to_free = resolve_refdup("HEAD", 0, head_sha1, &flag);
        if (branch && starts_with(branch, "refs/heads/"))
                branch += 11;
        if (!branch || is_null_sha1(head_sha1))
diff --git c/builtin/notes.c w/builtin/notes.c
index 16df78c..eaf297d 100644
--- c/builtin/notes.c
+++ w/builtin/notes.c
@@ -703,7 +703,7 @@ static int merge_commit(struct notes_merge_options *o)
        init_notes(t, "NOTES_MERGE_PARTIAL", combine_notes_overwrite, 0);
 
        o->local_ref = local_ref_to_free =
-               resolve_refdup("NOTES_MERGE_REF", sha1, NULL, 0);
+               resolve_refdup("NOTES_MERGE_REF", 0, sha1, NULL);
        if (!o->local_ref)
                die("Failed to resolve NOTES_MERGE_REF");
 
diff --git c/builtin/receive-pack.c w/builtin/receive-pack.c
index 555a4a6..8a6e7e3 100644
--- c/builtin/receive-pack.c
+++ w/builtin/receive-pack.c
@@ -656,7 +656,7 @@ static void check_aliased_update(struct command *cmd, 
struct string_list *list)
        int flag;
 
        strbuf_addf(&buf, "%s%s", get_git_namespace(), cmd->ref_name);
-       dst_name = resolve_ref_unsafe(buf.buf, sha1, &flag, 0);
+       dst_name = resolve_ref_unsafe(buf.buf, 0, sha1, &flag);
        strbuf_release(&buf);
 
        if (!(flag & REF_ISSYMREF))
@@ -817,7 +817,7 @@ static void execute_commands(struct command *commands,
        check_aliased_updates(commands);
 
        free(head_name_to_free);
-       head_name = head_name_to_free = resolve_refdup("HEAD", sha1, NULL, 0);
+       head_name = head_name_to_free = resolve_refdup("HEAD", 0, sha1, NULL);
 
        checked_connectivity = 1;
        for (cmd = commands; cmd; cmd = cmd->next) {
diff --git c/builtin/remote.c w/builtin/remote.c
index 6eaeee7..8517cfa 100644
--- c/builtin/remote.c
+++ w/builtin/remote.c
@@ -568,8 +568,8 @@ static int read_remote_branches(const char *refname,
        strbuf_addf(&buf, "refs/remotes/%s/", rename->old);
        if (starts_with(refname, buf.buf)) {
                item = string_list_append(rename->remote_branches, 
xstrdup(refname));
-               symref = resolve_ref_unsafe(refname, orig_sha1, &flag,
-                                           RESOLVE_REF_READING);
+               symref = resolve_ref_unsafe(refname, RESOLVE_REF_READING,
+                                           orig_sha1, &flag);
                if (flag & REF_ISSYMREF)
                        item->util = xstrdup(symref);
                else
@@ -705,7 +705,7 @@ static int mv(int argc, const char **argv)
                int flag = 0;
                unsigned char sha1[20];
 
-               read_ref_full(item->string, sha1, &flag, RESOLVE_REF_READING);
+               read_ref_full(item->string, RESOLVE_REF_READING, sha1, &flag);
                if (!(flag & REF_ISSYMREF))
                        continue;
                if (delete_ref(item->string, NULL, REF_NODEREF))
@@ -750,13 +750,16 @@ static int mv(int argc, const char **argv)
 
 static int remove_branches(struct string_list *branches)
 {
+       struct strbuf err = STRBUF_INIT;
        const char **branch_names;
        int i, result = 0;
 
        branch_names = xmalloc(branches->nr * sizeof(*branch_names));
        for (i = 0; i < branches->nr; i++)
                branch_names[i] = branches->items[i].string;
-       result |= repack_without_refs(branch_names, branches->nr, NULL);
+       if (repack_without_refs(branch_names, branches->nr, &err))
+               result |= error("%s", err.buf);
+       strbuf_release(&err);
        free(branch_names);
 
        for (i = 0; i < branches->nr; i++) {
@@ -1333,9 +1336,13 @@ static int prune_remote(const char *remote, int dry_run)
                delete_refs = xmalloc(states.stale.nr * sizeof(*delete_refs));
                for (i = 0; i < states.stale.nr; i++)
                        delete_refs[i] = states.stale.items[i].util;
-               if (!dry_run)
-                       result |= repack_without_refs(delete_refs,
-                                                     states.stale.nr, NULL);
+               if (!dry_run) {
+                       struct strbuf err = STRBUF_INIT;
+                       if (repack_without_refs(delete_refs, states.stale.nr,
+                                               &err))
+                               result |= error("%s", err.buf);
+                       strbuf_release(&err);
+               }
                free(delete_refs);
        }
 
diff --git c/builtin/show-branch.c w/builtin/show-branch.c
index ef6ea52..acc8dc1 100644
--- c/builtin/show-branch.c
+++ w/builtin/show-branch.c
@@ -727,8 +727,9 @@ int cmd_show_branch(int ac, const char **av, const char 
*prefix)
                if (ac == 0) {
                        static const char *fake_av[2];
 
-                       fake_av[0] = resolve_refdup("HEAD", sha1, NULL,
-                                                   RESOLVE_REF_READING);
+                       fake_av[0] = resolve_refdup("HEAD",
+                                                   RESOLVE_REF_READING,
+                                                   sha1, NULL);
                        fake_av[1] = NULL;
                        av = fake_av;
                        ac = 1;
@@ -790,8 +791,8 @@ int cmd_show_branch(int ac, const char **av, const char 
*prefix)
                }
        }
 
-       head_p = resolve_ref_unsafe("HEAD", head_sha1, NULL,
-                                   RESOLVE_REF_READING);
+       head_p = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+                                   head_sha1, NULL);
        if (head_p) {
                head_len = strlen(head_p);
                memcpy(head, head_p, head_len + 1);
diff --git c/builtin/symbolic-ref.c w/builtin/symbolic-ref.c
index 1dd04af..29fb3f1 100644
--- c/builtin/symbolic-ref.c
+++ w/builtin/symbolic-ref.c
@@ -13,7 +13,7 @@ static int check_symref(const char *HEAD, int quiet, int 
shorten, int print)
 {
        unsigned char sha1[20];
        int flag;
-       const char *refname = resolve_ref_unsafe(HEAD, sha1, &flag, 0);
+       const char *refname = resolve_ref_unsafe(HEAD, 0, sha1, &flag);
 
        if (!refname)
                die("No such ref: %s", HEAD);
diff --git c/builtin/update-ref.c w/builtin/update-ref.c
index e379fdd..6c9be05 100644
--- c/builtin/update-ref.c
+++ w/builtin/update-ref.c
@@ -419,8 +419,7 @@ int cmd_update_ref(int argc, const char **argv, const char 
*prefix)
        if (no_deref)
                flags = REF_NODEREF;
        if (delete)
-               return delete_ref(refname, oldval ? oldsha1 : NULL,
-                                 flags|REF_BADNAMEOK);
+               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
        else
                return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
                                  flags, UPDATE_REFS_DIE_ON_ERR);
diff --git c/bundle.c w/bundle.c
index 32dd2f7..d92e49c 100644
--- c/bundle.c
+++ w/bundle.c
@@ -311,7 +311,7 @@ int create_bundle(struct bundle_header *header, const char 
*path,
                        continue;
                if (dwim_ref(e->name, strlen(e->name), sha1, &ref) != 1)
                        continue;
-               if (read_ref_full(e->name, sha1, &flag, RESOLVE_REF_READING))
+               if (read_ref_full(e->name, RESOLVE_REF_READING, sha1, &flag))
                        flag = 0;
                display_ref = (flag & REF_ISSYMREF) ? e->name : ref;
 
diff --git c/cache.h w/cache.h
index 995729f..f582b6c 100644
--- c/cache.h
+++ w/cache.h
@@ -946,8 +946,8 @@ extern int for_each_abbrev(const char *prefix, 
each_abbrev_fn, void *);
 extern int get_sha1_hex(const char *hex, unsigned char *sha1);
 
 extern char *sha1_to_hex(const unsigned char *sha1);   /* static buffer 
result! */
-extern int read_ref_full(const char *refname, unsigned char *sha1,
-                        int *flags, int resolve_flags);
+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);
 
 /*
@@ -969,27 +969,36 @@ extern int read_ref(const char *refname, unsigned char 
*sha1);
  *   "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) and REF_ISBROKEN (if the ref is malformed).
+ * 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).
  *
  * 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 disables most of the ref name checking except
- * for names that are absolute paths or contain ".." components. For both
- * these cases the function will return NULL and set errno to EINVAL.
- * If the name is bad then the function will set the REF_ISBROKEN flag and
- * return the name, if the ref exists, or NULL, if it does not.
- * When this flag is set, any badly named refs will resolve to nullsha1.
+ * 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
+ * REF_ISBROKEN and REF_BAD_NAME flags 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.
  */
 #define RESOLVE_REF_READING 0x01
-#define RESOLVE_REF_NODEREF 0x02
+#define RESOLVE_REF_NO_RECURSE 0x02
 #define RESOLVE_REF_ALLOW_BAD_NAME 0x04
-extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, 
int *flags, int resolve_flags);
-extern char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, 
int resolve_flags);
+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);
diff --git c/http-backend.c w/http-backend.c
index 8f94f9b..e172886 100644
--- c/http-backend.c
+++ w/http-backend.c
@@ -417,8 +417,9 @@ static int show_head_ref(const char *refname, const 
unsigned char *sha1,
 
        if (flag & REF_ISSYMREF) {
                unsigned char unused[20];
-               const char *target = resolve_ref_unsafe(refname, unused,
-                                               NULL, RESOLVE_REF_READING);
+               const char *target = resolve_ref_unsafe(refname,
+                                                       RESOLVE_REF_READING,
+                                                       unused, NULL);
                const char *target_nons = strip_namespace(target);
 
                strbuf_addf(buf, "ref: %s\n", target_nons);
diff --git c/notes-merge.c w/notes-merge.c
index ffca602..3c88d17 100644
--- c/notes-merge.c
+++ w/notes-merge.c
@@ -549,7 +549,7 @@ int notes_merge(struct notes_merge_options *o,
               o->local_ref, o->remote_ref);
 
        /* Dereference o->local_ref into local_sha1 */
-       if (read_ref_full(o->local_ref, local_sha1, NULL, 0))
+       if (read_ref_full(o->local_ref, 0, local_sha1, NULL))
                die("Failed to resolve local notes ref '%s'", o->local_ref);
        else if (!check_refname_format(o->local_ref, 0) &&
                is_null_sha1(local_sha1))
diff --git c/reflog-walk.c w/reflog-walk.c
index feeaf0a..23345ea 100644
--- c/reflog-walk.c
+++ w/reflog-walk.c
@@ -48,8 +48,8 @@ static struct complete_reflogs *read_complete_reflog(const 
char *ref)
                unsigned char sha1[20];
                const char *name;
                void *name_to_free;
-               name = name_to_free = resolve_refdup(ref, sha1, NULL,
-                                                    RESOLVE_REF_READING);
+               name = name_to_free = resolve_refdup(ref, RESOLVE_REF_READING,
+                                                    sha1, NULL);
                if (name) {
                        for_each_reflog_ent(name, read_one_reflog, reflogs);
                        free(name_to_free);
@@ -175,7 +175,7 @@ int add_reflog_for_walk(struct reflog_walk_info *info,
                if (*branch == '\0') {
                        unsigned char sha1[20];
                        free(branch);
-                       branch = resolve_refdup("HEAD", sha1, NULL, 0);
+                       branch = resolve_refdup("HEAD", 0, sha1, NULL);
                        if (!branch)
                                die ("No current branch");
 
diff --git c/refs.c w/refs.c
index 3b27758..d9d327d 100644
--- c/refs.c
+++ w/refs.c
@@ -69,16 +69,8 @@ static int check_refname_component(const char *refname, int 
flags)
 out:
        if (cp == refname)
                return 0; /* Component has zero length. */
-       if (refname[0] == '.') {
-               if (!(flags & REFNAME_DOT_COMPONENT))
-                       return -1; /* Component starts with '.'. */
-               /*
-                * Even if leading dots are allowed, don't allow "."
-                * as a component (".." is prevented by a rule above).
-                */
-               if (refname[1] == '\0')
-                       return -1; /* Component equals ".". */
-       }
+       if (refname[0] == '.')
+               return -1; /* Component starts with '.'. */
        if (cp - refname >= 5 && !memcmp(cp - 5, ".lock", 5))
                return -1; /* Refname ends with ".lock". */
        return cp - refname;
@@ -193,8 +185,8 @@ struct ref_dir {
 
 /*
  * Bit values for ref_entry::flag.  REF_ISSYMREF=0x01,
- * REF_ISPACKED=0x02, and REF_ISBROKEN=0x04 are public values; see
- * refs.h.
+ * REF_ISPACKED=0x02, REF_ISBROKEN=0x04 and REF_BAD_NAME=0x08 are
+ * public values; see refs.h.
  */
 
 /*
@@ -202,16 +194,16 @@ struct ref_dir {
  * the correct peeled value for the reference, which might be
  * null_sha1 if the reference is not a tag or if it is broken.
  */
-#define REF_KNOWS_PEELED 0x08
+#define REF_KNOWS_PEELED 0x10
 
 /* ref_entry represents a directory of references */
-#define REF_DIR 0x10
+#define REF_DIR 0x20
 
 /*
  * Entry has not yet been read from disk (used only for REF_DIR
  * entries representing loose references)
  */
-#define REF_INCOMPLETE 0x20
+#define REF_INCOMPLETE 0x40
 
 /*
  * A ref_entry represents either a reference or a "subdirectory" of
@@ -280,6 +272,37 @@ static struct ref_dir *get_ref_dir(struct ref_entry *entry)
        return dir;
 }
 
+static int escapes_cwd(const char *path) {
+       char *buf;
+       int result;
+
+       if (is_absolute_path(path))
+               return 1;
+       buf = xmalloc(strlen(path) + 1);
+       result = !!normalize_path_copy(buf, path);
+       free(buf);
+       return result;
+}
+
+/*
+ * Check if a refname is safe.
+ * For refs that start with "refs/" we consider it safe as long as the rest
+ * of the path components does not allow it to escape from this directory.
+ * For all other refs we only consider them safe iff they only contain
+ * upper case characters and '_'.
+ */
+static int refname_is_safe(const char *refname)
+{
+       if (starts_with(refname, "refs/"))
+               return !escapes_cwd(refname + strlen("refs/"));
+       while (*refname) {
+               if (!isupper(*refname) && *refname != '_')
+                       return 0;
+               refname++;
+       }
+       return 1;
+}
+
 static struct ref_entry *create_ref_entry(const char *refname,
                                          const unsigned char *sha1, int flag,
                                          int check_name)
@@ -288,8 +311,10 @@ static struct ref_entry *create_ref_entry(const char 
*refname,
        struct ref_entry *ref;
 
        if (check_name &&
-           check_refname_format(refname, 
REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT))
+           check_refname_format(refname, REFNAME_ALLOW_ONELEVEL))
                die("Reference has invalid format: '%s'", refname);
+       if (!check_name && !refname_is_safe(refname))
+               die("Reference has invalid name: '%s'", refname);
        len = strlen(refname) + 1;
        ref = xmalloc(sizeof(struct ref_entry) + len);
        hashcpy(ref->u.value.sha1, sha1);
@@ -822,10 +847,9 @@ static int name_conflict_fn(struct ref_entry *entry, void 
*cb_data)
 /*
  * Return true iff a reference named refname could be created without
  * conflicting with the name of an existing reference in dir.  If
- * oldrefname is non-NULL, ignore potential conflicts with oldrefname
- * (e.g., because oldrefname is scheduled for deletion in the same
- * operation). skiplist contains a list of refs we want to skip checking for
- * conflicts with. skiplist must be sorted.
+ * skiplist is non-NULL, ignore potential conflicts with names in
+ * skiplist (e.g., because those refs are scheduled for deletion in
+ * the same operation).  skiplist must be sorted.
  */
 static int is_refname_available(const char *refname,
                                struct ref_dir *dir,
@@ -1062,9 +1086,9 @@ static void read_packed_refs(FILE *f, struct ref_dir *dir)
                if (refname) {
                        int flag = REF_ISPACKED;
 
-                       if (check_refname_format(refname, 
REFNAME_ALLOW_ONELEVEL|REFNAME_DOT_COMPONENT)) {
-                               flag |= REF_ISBROKEN;
+                       if (check_refname_format(refname, 
REFNAME_ALLOW_ONELEVEL)) {
                                hashclr(sha1);
+                               flag |= REF_BAD_NAME | REF_ISBROKEN;
                        }
                        last = create_ref_entry(refname, sha1, flag, 0);
                        if (peeled == PEELED_FULLY ||
@@ -1198,16 +1222,16 @@ static void read_loose_refs(const char *dirname, struct 
ref_dir *dir)
                                        hashclr(sha1);
                                        flag |= REF_ISBROKEN;
                                }
-                       } else if (read_ref_full(refname.buf, sha1, &flag,
-                                                RESOLVE_REF_READING)) {
+                       } else if (read_ref_full(refname.buf,
+                                                RESOLVE_REF_READING,
+                                                sha1, &flag)) {
                                hashclr(sha1);
                                flag |= REF_ISBROKEN;
                        }
                        if (check_refname_format(refname.buf,
-                                                REFNAME_ALLOW_ONELEVEL|
-                                                REFNAME_DOT_COMPONENT)) {
+                                                REFNAME_ALLOW_ONELEVEL)) {
                                hashclr(sha1);
-                               flag |= REF_ISBROKEN;
+                               flag |= REF_BAD_NAME | REF_ISBROKEN;
                        }
                        add_entry_to_dir(dir,
                                         create_ref_entry(refname.buf, sha1, 
flag, 0));
@@ -1329,10 +1353,10 @@ static struct ref_entry *get_packed_ref(const char 
*refname)
  * A loose ref file doesn't exist; check for a packed ref.  The
  * options are forwarded from resolve_safe_unsafe().
  */
-static const char *handle_missing_loose_ref(const char *refname,
-                                           unsigned char *sha1,
-                                           int reading,
-                                           int *flag)
+static int resolve_missing_loose_ref(const char *refname,
+                                    int resolve_flags,
+                                    unsigned char *sha1,
+                                    int *flags)
 {
        struct ref_entry *entry;
 
@@ -1343,33 +1367,22 @@ static const char *handle_missing_loose_ref(const char 
*refname,
        entry = get_packed_ref(refname);
        if (entry) {
                hashcpy(sha1, entry->u.value.sha1);
-               if (flag)
-                       *flag |= REF_ISPACKED;
-               return refname;
+               if (flags)
+                       *flags |= REF_ISPACKED;
+               return 0;
        }
        /* The reference is not a packed reference, either. */
-       if (reading) {
-               return NULL;
+       if (resolve_flags & RESOLVE_REF_READING) {
+               errno = ENOENT;
+               return -1;
        } else {
                hashclr(sha1);
-               return refname;
+               return 0;
        }
 }
 
-static int escapes_cwd(const char *path) {
-       char *buf;
-       int result;
-
-       if (is_absolute_path(path))
-               return 1;
-       buf = xmalloc(strlen(path) + 1);
-       result = normalize_path_copy(buf, path);
-       free(buf);
-       return result;
-}
-
 /* This function needs to return a meaningful errno on failure */
-const char *resolve_ref_unsafe(const char *refname, unsigned char *sha1, int 
*flags, int resolve_flags)
+const char *resolve_ref_unsafe(const char *refname, int resolve_flags, 
unsigned char *sha1, int *flags)
 {
        int depth = MAXDEPTH;
        ssize_t len;
@@ -1381,14 +1394,22 @@ const char *resolve_ref_unsafe(const char *refname, 
unsigned char *sha1, int *fl
                *flags = 0;
 
        if (check_refname_format(refname, REFNAME_ALLOW_ONELEVEL)) {
+               if (flags)
+                       *flags |= REF_BAD_NAME;
+
                if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
-                   escapes_cwd(refname)) {
+                   !refname_is_safe(refname)) {
                        errno = EINVAL;
                        return NULL;
                }
-               hashclr(sha1);
-               if (flags)
-                       *flags |= REF_ISBROKEN;
+               /*
+                * dwim_ref() uses REF_ISBROKEN to distinguish between
+                * missing refs and refs that were present but invalid,
+                * to complain about the latter to stderr.
+                *
+                * We don't know whether the ref exists, so don't set
+                * REF_ISBROKEN yet.
+                */
                bad_name = 1;
        }
        for (;;) {
@@ -1415,12 +1436,17 @@ const char *resolve_ref_unsafe(const char *refname, 
unsigned char *sha1, int *fl
                 */
        stat_ref:
                if (lstat(path, &st) < 0) {
-                       if (errno == ENOENT)
-                               return handle_missing_loose_ref(refname, sha1,
-                                       resolve_flags & RESOLVE_REF_READING,
-                                       flags);
-                       else
+                       if (errno != ENOENT)
                                return NULL;
+                       if (resolve_missing_loose_ref(refname, resolve_flags,
+                                                     sha1, flags))
+                               return NULL;
+                       if (bad_name) {
+                               hashclr(sha1);
+                               if (flags)
+                                       *flags |= REF_ISBROKEN;
+                       }
+                       return refname;
                }
 
                /* Follow "normalized" - ie "refs/.." symlinks by hand */
@@ -1440,7 +1466,7 @@ const char *resolve_ref_unsafe(const char *refname, 
unsigned char *sha1, int *fl
                                refname = refname_buffer;
                                if (flags)
                                        *flags |= REF_ISSYMREF;
-                               if (resolve_flags & RESOLVE_REF_NODEREF) {
+                               if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                                        hashclr(sha1);
                                        return refname;
                                }
@@ -1493,8 +1519,11 @@ const char *resolve_ref_unsafe(const char *refname, 
unsigned char *sha1, int *fl
                                errno = EINVAL;
                                return NULL;
                        }
-                       if (bad_name)
+                       if (bad_name) {
                                hashclr(sha1);
+                               if (flags)
+                                       *flags |= REF_ISBROKEN;
+                       }
                        return refname;
                }
                if (flags)
@@ -1502,23 +1531,28 @@ const char *resolve_ref_unsafe(const char *refname, 
unsigned char *sha1, int *fl
                buf = buffer + 4;
                while (isspace(*buf))
                        buf++;
-               if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
-                       if (flags)
-                               *flags |= REF_ISBROKEN;
-                       errno = EINVAL;
-                       return NULL;
-               }
                refname = strcpy(refname_buffer, buf);
-               if (resolve_flags & RESOLVE_REF_NODEREF) {
+               if (resolve_flags & RESOLVE_REF_NO_RECURSE) {
                        hashclr(sha1);
                        return refname;
                }
+               if (check_refname_format(buf, REFNAME_ALLOW_ONELEVEL)) {
+                       if (flags)
+                               *flags |= REF_BAD_NAME | REF_ISBROKEN;
+
+                       if (!(resolve_flags & RESOLVE_REF_ALLOW_BAD_NAME) ||
+                           !refname_is_safe(buf)) {
+                               errno = EINVAL;
+                               return NULL;
+                       }
+                       bad_name = 1;
+               }
        }
 }
 
-char *resolve_refdup(const char *ref, unsigned char *sha1, int *flags, int 
resolve_flags)
+char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, 
int *flags)
 {
-       const char *ret = resolve_ref_unsafe(ref, sha1, flags, resolve_flags);
+       const char *ret = resolve_ref_unsafe(ref, resolve_flags, sha1, flags);
        return ret ? xstrdup(ret) : NULL;
 }
 
@@ -1529,22 +1563,22 @@ struct ref_filter {
        void *cb_data;
 };
 
-int read_ref_full(const char *refname, unsigned char *sha1, int *flags, int 
resolve_flags)
+int read_ref_full(const char *refname, int resolve_flags, unsigned char *sha1, 
int *flags)
 {
-       if (resolve_ref_unsafe(refname, sha1, flags, resolve_flags))
+       if (resolve_ref_unsafe(refname, resolve_flags, sha1, flags))
                return 0;
        return -1;
 }
 
 int read_ref(const char *refname, unsigned char *sha1)
 {
-       return read_ref_full(refname, sha1, NULL, RESOLVE_REF_READING);
+       return read_ref_full(refname, RESOLVE_REF_READING, sha1, NULL);
 }
 
 int ref_exists(const char *refname)
 {
        unsigned char sha1[20];
-       return !!resolve_ref_unsafe(refname, sha1, NULL, RESOLVE_REF_READING);
+       return !!resolve_ref_unsafe(refname, RESOLVE_REF_READING, sha1, NULL);
 }
 
 static int filter_refs(const char *refname, const unsigned char *sha1, int 
flags,
@@ -1658,7 +1692,7 @@ int peel_ref(const char *refname, unsigned char *sha1)
                return 0;
        }
 
-       if (read_ref_full(refname, base, &flag, RESOLVE_REF_READING))
+       if (read_ref_full(refname, RESOLVE_REF_READING, base, &flag))
                return -1;
 
        /*
@@ -1699,7 +1733,7 @@ static int warn_if_dangling_symref(const char *refname, 
const unsigned char *sha
        if (!(flags & REF_ISSYMREF))
                return 0;
 
-       resolves_to = resolve_ref_unsafe(refname, junk, NULL, 0);
+       resolves_to = resolve_ref_unsafe(refname, 0, junk, NULL);
        if (!resolves_to
            || (d->refname
                ? strcmp(resolves_to, d->refname)
@@ -1824,7 +1858,7 @@ static int do_head_ref(const char *submodule, each_ref_fn 
fn, void *cb_data)
                return 0;
        }
 
-       if (!read_ref_full("HEAD", sha1, &flag, RESOLVE_REF_READING))
+       if (!read_ref_full("HEAD", RESOLVE_REF_READING, sha1, &flag))
                return fn("HEAD", sha1, flag, cb_data);
 
        return 0;
@@ -1904,7 +1938,7 @@ int head_ref_namespaced(each_ref_fn fn, void *cb_data)
        int flag;
 
        strbuf_addf(&buf, "%sHEAD", get_git_namespace());
-       if (!read_ref_full(buf.buf, sha1, &flag, RESOLVE_REF_READING))
+       if (!read_ref_full(buf.buf, RESOLVE_REF_READING, sha1, &flag))
                ret = fn(buf.buf, sha1, flag, cb_data);
        strbuf_release(&buf);
 
@@ -1999,8 +2033,9 @@ int refname_match(const char *abbrev_name, const char 
*full_name)
 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, NULL,
-                         mustexist ? RESOLVE_REF_READING : 0)) {
+       if (read_ref_full(lock->ref_name,
+                         mustexist ? RESOLVE_REF_READING : 0,
+                         lock->old_sha1, NULL)) {
                int save_errno = errno;
                error("Can't verify ref %s", lock->ref_name);
                unlock_ref(lock);
@@ -2073,8 +2108,8 @@ int dwim_ref(const char *str, int len, unsigned char 
*sha1, char **ref)
 
                this_result = refs_found ? sha1_from_ref : sha1;
                mksnpath(fullref, sizeof(fullref), *p, len, str);
-               r = resolve_ref_unsafe(fullref, this_result, &flag,
-                                      RESOLVE_REF_READING);
+               r = resolve_ref_unsafe(fullref, RESOLVE_REF_READING,
+                                      this_result, &flag);
                if (r) {
                        if (!refs_found++)
                                *ref = xstrdup(r);
@@ -2103,7 +2138,8 @@ int dwim_log(const char *str, int len, unsigned char 
*sha1, char **log)
                const char *ref, *it;
 
                mksnpath(path, sizeof(path), *p, len, str);
-               ref = resolve_ref_unsafe(path, hash, NULL, RESOLVE_REF_READING);
+               ref = resolve_ref_unsafe(path, RESOLVE_REF_READING,
+                                        hash, NULL);
                if (!ref)
                        continue;
                if (reflog_exists(path))
@@ -2145,16 +2181,16 @@ static struct ref_lock *lock_ref_sha1_basic(const char 
*refname,
        lock = xcalloc(1, sizeof(struct ref_lock));
        lock->lock_fd = -1;
 
-       if (flags & REF_BADNAMEOK)
-               resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
-
        if (mustexist)
                resolve_flags |= RESOLVE_REF_READING;
-       if (flags & REF_NODEREF)
-               resolve_flags |= RESOLVE_REF_NODEREF;
+       if (flags & REF_DELETING) {
+               resolve_flags |= RESOLVE_REF_ALLOW_BAD_NAME;
+               if (flags & REF_NODEREF)
+                       resolve_flags |= RESOLVE_REF_NO_RECURSE;
+       }
 
-       refname = resolve_ref_unsafe(refname, lock->old_sha1, &type,
-                                    resolve_flags);
+       refname = resolve_ref_unsafe(refname, resolve_flags,
+                                    lock->old_sha1, &type);
        if (!refname && errno == EISDIR) {
                /* we are trying to lock foo but we used to
                 * have foo/bar which now does not exist;
@@ -2167,8 +2203,8 @@ static struct ref_lock *lock_ref_sha1_basic(const char 
*refname,
                        error("there are still refs under '%s'", orig_refname);
                        goto error_return;
                }
-               refname = resolve_ref_unsafe(orig_refname, lock->old_sha1,
-                                            &type, resolve_flags);
+               refname = resolve_ref_unsafe(orig_refname, resolve_flags,
+                                            lock->old_sha1, &type);
        }
        if (type_p)
            *type_p = type;
@@ -2521,7 +2557,7 @@ static int curate_packed_ref_fn(struct ref_entry *entry, 
void *cb_data)
                unsigned char sha1[20];
                int flags;
 
-               if (read_ref_full(entry->name, sha1, &flags, 0))
+               if (read_ref_full(entry->name, 0, sha1, &flags))
                        /* We should at least have found the packed ref. */
                        die("Internal error");
                if ((flags & REF_ISSYMREF) || !(flags & REF_ISPACKED)) {
@@ -2712,8 +2748,8 @@ int rename_ref(const char *oldrefname, const char 
*newrefname, const char *logms
        if (log && S_ISLNK(loginfo.st_mode))
                return error("reflog for %s is a symlink", oldrefname);
 
-       symref = resolve_ref_unsafe(oldrefname, orig_sha1, &flag,
-                                   RESOLVE_REF_READING);
+       symref = resolve_ref_unsafe(oldrefname, RESOLVE_REF_READING,
+                                   orig_sha1, &flag);
        if (flag & REF_ISSYMREF)
                return error("refname %s is a symbolic ref, renaming it is not 
supported",
                        oldrefname);
@@ -2742,7 +2778,7 @@ int rename_ref(const char *oldrefname, const char 
*newrefname, const char *logms
                goto rollback;
        }
 
-       if (!read_ref_full(newrefname, sha1, NULL, RESOLVE_REF_READING) &&
+       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))) {
@@ -2956,7 +2992,7 @@ static int is_branch(const char *refname)
 }
 
 /*
- * Writes sha1 into the ref specified by the lock. Makes sure that errno
+ * Write sha1 into the ref specified by the lock. Make sure that errno
  * is sane on error.
  */
 static int write_ref_sha1(struct ref_lock *lock,
@@ -3020,8 +3056,8 @@ static int write_ref_sha1(struct ref_lock *lock,
                unsigned char head_sha1[20];
                int head_flag;
                const char *head_ref;
-               head_ref = resolve_ref_unsafe("HEAD", head_sha1, &head_flag,
-                                             RESOLVE_REF_READING);
+               head_ref = resolve_ref_unsafe("HEAD", RESOLVE_REF_READING,
+                                             head_sha1, &head_flag);
                if (head_ref && (head_flag & REF_ISSYMREF) &&
                    !strcmp(head_ref, lock->ref_name))
                        log_ref_write("HEAD", lock->old_sha1, sha1, logmsg);
@@ -3388,7 +3424,7 @@ static int do_for_each_reflog(struct strbuf *name, 
each_ref_fn fn, void *cb_data
                                retval = do_for_each_reflog(name, fn, cb_data);
                        } else {
                                unsigned char sha1[20];
-                               if (read_ref_full(name->buf, sha1, NULL, 0))
+                               if (read_ref_full(name->buf, 0, sha1, NULL))
                                        retval = error("bad ref for %s", 
name->buf);
                                else
                                        retval = fn(name->buf, sha1, 0, 
cb_data);
@@ -3667,27 +3703,30 @@ int ref_transaction_commit(struct ref_transaction 
*transaction,
        /* Copy, sort, and reject duplicate refs */
        qsort(updates, n, sizeof(*updates), ref_update_compare);
        if (ref_update_reject_duplicates(updates, n, err)) {
-               ret = -1;
+               ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
 
        /* Acquire all locks while verifying old values */
        for (i = 0; i < n; i++) {
                struct ref_update *update = updates[i];
+               int flags = update->flags;
 
+               if (is_null_sha1(update->new_sha1))
+                       flags |= REF_DELETING;
                update->lock = lock_ref_sha1_basic(update->refname,
                                                   (update->have_old ?
                                                    update->old_sha1 :
                                                    NULL),
                                                   NULL,
-                                                  update->flags,
+                                                  flags,
                                                   &update->type);
                if (!update->lock) {
-                       int df_conflict = (errno == ENOTDIR);
-
+                       ret = (errno == ENOTDIR)
+                               ? TRANSACTION_NAME_CONFLICT
+                               : TRANSACTION_GENERIC_ERROR;
                        strbuf_addf(err, "Cannot lock the ref '%s'.",
                                    update->refname);
-                       ret = df_conflict ? UPDATE_REFS_NAME_CONFLICT : -1;
                        goto cleanup;
                }
        }
@@ -3697,15 +3736,15 @@ int ref_transaction_commit(struct ref_transaction 
*transaction,
                struct ref_update *update = updates[i];
 
                if (!is_null_sha1(update->new_sha1)) {
-                       ret = write_ref_sha1(update->lock, update->new_sha1,
-                                            update->msg);
-                       update->lock = NULL; /* freed by write_ref_sha1 */
-                       if (ret) {
+                       if (write_ref_sha1(update->lock, update->new_sha1,
+                                          update->msg)) {
+                               update->lock = NULL; /* freed by write_ref_sha1 
*/
                                strbuf_addf(err, "Cannot update the ref '%s'.",
                                            update->refname);
-                               ret = -1;
+                               ret = TRANSACTION_GENERIC_ERROR;
                                goto cleanup;
                        }
+                       update->lock = NULL; /* freed by write_ref_sha1 */
                }
        }
 
@@ -3715,7 +3754,7 @@ int ref_transaction_commit(struct ref_transaction 
*transaction,
 
                if (update->lock) {
                        if (delete_ref_loose(update->lock, update->type, err)) {
-                               ret = -1;
+                               ret = TRANSACTION_GENERIC_ERROR;
                                goto cleanup;
                        }
 
@@ -3725,7 +3764,7 @@ int ref_transaction_commit(struct ref_transaction 
*transaction,
        }
 
        if (repack_without_refs(delnames, delnum, err)) {
-               ret = -1;
+               ret = TRANSACTION_GENERIC_ERROR;
                goto cleanup;
        }
        for (i = 0; i < delnum; i++)
diff --git c/refs.h w/refs.h
index a96e174..3b35387 100644
--- c/refs.h
+++ w/refs.h
@@ -56,11 +56,15 @@ struct ref_transaction;
 
 /*
  * Reference cannot be resolved to an object name: dangling symbolic
- * reference (directly or indirectly), corrupt reference file, or
- * symbolic reference refers to ill-formatted reference name.
+ * reference (directly or indirectly), corrupt reference file,
+ * reference exists but name is bad, or symbolic reference refers to
+ * ill-formatted reference name.
  */
 #define REF_ISBROKEN 0x04
 
+/* Reference name is not well formed (see git-check-ref-format(1)). */
+#define REF_BAD_NAME 0x08
+
 /*
  * The signature for the callback function for the for_each_*()
  * functions below.  The memory pointed to by the refname and sha1
@@ -175,12 +179,12 @@ extern int peel_ref(const char *refname, unsigned char 
*sha1);
  * ref_transaction_create(), etc.
  * REF_NODEREF: act on the ref directly, instead of dereferencing
  *              symbolic references.
- * REF_BADNAMEOK: allow locking a ref that has a bad name.
+ * REF_DELETING: tolerate broken refs
  *
  * Flags >= 0x100 are reserved for internal use.
  */
 #define REF_NODEREF    0x01
-#define REF_BADNAMEOK  0x02
+#define REF_DELETING   0x02
 /*
  * This function sets errno to something meaningful on failure.
  */
@@ -226,7 +230,6 @@ extern int for_each_reflog(each_ref_fn, void *);
 
 #define REFNAME_ALLOW_ONELEVEL 1
 #define REFNAME_REFSPEC_PATTERN 2
-#define REFNAME_DOT_COMPONENT 4
 
 /*
  * Return 0 iff refname has the correct format for a refname according
@@ -234,10 +237,7 @@ extern int for_each_reflog(each_ref_fn, void *);
  * If REFNAME_ALLOW_ONELEVEL is set in flags, then accept one-level
  * reference names.  If REFNAME_REFSPEC_PATTERN is set in flags, then
  * allow a "*" wildcard character in place of one of the name
- * components.  No leading or repeated slashes are accepted.  If
- * REFNAME_DOT_COMPONENT is set in flags, then allow refname
- * components to start with "." (but not a whole component equal to
- * "." or "..").
+ * components.  No leading or repeated slashes are accepted.
  */
 extern int check_refname_format(const char *refname, int flags);
 
@@ -270,8 +270,8 @@ struct ref_transaction *ref_transaction_begin(struct strbuf 
*err);
  * 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().
+ * refname and msg, so the caller retains ownership of these parameters.
+ * flags can be REF_NODEREF; it is passed to update_ref_lock().
  */
 
 /*
@@ -322,14 +322,14 @@ int ref_transaction_delete(struct ref_transaction 
*transaction,
 
 /*
  * Commit all of the changes that have been queued in transaction, as
- * atomically as possible.  Return a nonzero value if there is a
- * problem.
+ * atomically as possible.
  *
- * Function returns 0 on success, -1 for generic failures and
- * UPDATE_REFS_NAME_CONFLICT (-2) if the failure was due to a naming conflict.
- * For example, the ref names A and A/B conflict.
+ * Returns 0 for success, or one of the below error codes for errors.
  */
-#define UPDATE_REFS_NAME_CONFLICT -2
+/* Naming conflict (for example, the ref names A and A/B conflict). */
+#define TRANSACTION_NAME_CONFLICT -1
+/* All other errors. */
+#define TRANSACTION_GENERIC_ERROR -2
 int ref_transaction_commit(struct ref_transaction *transaction,
                           struct strbuf *err);
 
diff --git c/remote.c w/remote.c
index 67c375d..25b07ac 100644
--- c/remote.c
+++ w/remote.c
@@ -486,7 +486,7 @@ static void read_config(void)
                return;
        default_remote_name = "origin";
        current_branch = NULL;
-       head_ref = resolve_ref_unsafe("HEAD", sha1, &flag, 0);
+       head_ref = resolve_ref_unsafe("HEAD", 0, sha1, &flag);
        if (head_ref && (flag & REF_ISSYMREF) &&
            starts_with(head_ref, "refs/heads/")) {
                current_branch =
@@ -1121,8 +1121,8 @@ static char *guess_ref(const char *name, struct ref *peer)
        struct strbuf buf = STRBUF_INIT;
        unsigned char sha1[20];
 
-       const char *r = resolve_ref_unsafe(peer->name, sha1, NULL,
-                                          RESOLVE_REF_READING);
+       const char *r = resolve_ref_unsafe(peer->name, RESOLVE_REF_READING,
+                                          sha1, NULL);
        if (!r)
                return NULL;
 
@@ -1183,8 +1183,9 @@ static int match_explicit(struct ref *src, struct ref 
*dst,
                unsigned char sha1[20];
                int flag;
 
-               dst_value = resolve_ref_unsafe(matched_src->name, sha1, &flag,
-                                              RESOLVE_REF_READING);
+               dst_value = resolve_ref_unsafe(matched_src->name,
+                                              RESOLVE_REF_READING,
+                                              sha1, &flag);
                if (!dst_value ||
                    ((flag & REF_ISSYMREF) &&
                     !starts_with(dst_value, "refs/heads/")))
@@ -1658,7 +1659,7 @@ static int ignore_symref_update(const char *refname)
        unsigned char sha1[20];
        int flag;
 
-       if (!resolve_ref_unsafe(refname, sha1, &flag, 0))
+       if (!resolve_ref_unsafe(refname, 0, sha1, &flag))
                return 0; /* non-existing refs are OK */
        return (flag & REF_ISSYMREF);
 }
diff --git c/sequencer.c w/sequencer.c
index 6a05ad4..70fb7a8 100644
--- c/sequencer.c
+++ w/sequencer.c
@@ -366,7 +366,7 @@ static int is_index_unchanged(void)
        unsigned char head_sha1[20];
        struct commit *head_commit;
 
-       if (!resolve_ref_unsafe("HEAD", head_sha1, NULL, RESOLVE_REF_READING))
+       if (!resolve_ref_unsafe("HEAD", RESOLVE_REF_READING, head_sha1, NULL))
                return error(_("Could not resolve HEAD commit\n"));
 
        head_commit = lookup_commit(head_sha1);
@@ -912,7 +912,7 @@ static int rollback_single_pick(void)
        if (!file_exists(git_path("CHERRY_PICK_HEAD")) &&
            !file_exists(git_path("REVERT_HEAD")))
                return error(_("no cherry-pick or revert in progress"));
-       if (read_ref_full("HEAD", head_sha1, NULL, 0))
+       if (read_ref_full("HEAD", 0, head_sha1, NULL))
                return error(_("cannot resolve HEAD"));
        if (is_null_sha1(head_sha1))
                return error(_("cannot abort from a branch yet to be born"));
diff --git c/t/t1400-update-ref.sh w/t/t1400-update-ref.sh
index ff4607b..7b4707b 100755
--- c/t/t1400-update-ref.sh
+++ w/t/t1400-update-ref.sh
@@ -126,6 +126,16 @@ test_expect_success 'update-ref --no-deref -d can delete 
self-reference' '
        test_path_is_missing .git/refs/heads/self
 '
 
+test_expect_success 'update-ref --no-deref -d can delete reference to bad ref' 
'
+       >.git/refs/heads/bad &&
+       test_when_finished "rm -f .git/refs/heads/bad" &&
+       git symbolic-ref refs/heads/ref-to-bad refs/heads/bad &&
+       test_when_finished "rm -f .git/refs/heads/ref-to-bad" &&
+       test_path_is_file .git/refs/heads/ref-to-bad &&
+       git update-ref --no-deref -d refs/heads/ref-to-bad &&
+       test_path_is_missing .git/refs/heads/ref-to-bad
+'
+
 test_expect_success '(not) create HEAD with old sha1' "
        test_must_fail git update-ref HEAD $A $B
 "
@@ -390,12 +400,6 @@ test_expect_success 'stdin fails create with no ref' '
        grep "fatal: create: missing <ref>" err
 '
 
-test_expect_success 'stdin fails create with bad ref name' '
-       echo "create ~a $m" >stdin &&
-       test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a" err
-'
-
 test_expect_success 'stdin fails create with no new value' '
        echo "create $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
@@ -414,12 +418,6 @@ test_expect_success 'stdin fails update with no ref' '
        grep "fatal: update: missing <ref>" err
 '
 
-test_expect_success 'stdin fails update with bad ref name' '
-       echo "update ~a $m" >stdin &&
-       test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a" err
-'
-
 test_expect_success 'stdin fails update with no new value' '
        echo "update $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
@@ -438,12 +436,6 @@ test_expect_success 'stdin fails delete with no ref' '
        grep "fatal: delete: missing <ref>" err
 '
 
-test_expect_success 'stdin fails delete with bad ref name' '
-       echo "delete ~a $m" >stdin &&
-       test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a" err
-'
-
 test_expect_success 'stdin fails delete with too many arguments' '
        echo "delete $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
@@ -716,12 +708,6 @@ test_expect_success 'stdin -z fails create with no ref' '
        grep "fatal: create: missing <ref>" err
 '
 
-test_expect_success 'stdin -z fails create with bad ref name' '
-       printf $F "create ~a " "$m" >stdin &&
-       test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a " err
-'
-
 test_expect_success 'stdin -z fails create with no new value' '
        printf $F "create $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
@@ -746,12 +732,6 @@ test_expect_success 'stdin -z fails update with too few 
args' '
        grep "fatal: update $a: unexpected end of input when reading 
<oldvalue>" err
 '
 
-test_expect_success 'stdin -z fails update with bad ref name' '
-       printf $F "update ~a" "$m" "" >stdin &&
-       test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a" err
-'
-
 test_expect_success 'stdin -z emits warning with empty new value' '
        git update-ref $a $m &&
        printf $F "update $a" "" "" >stdin &&
@@ -784,12 +764,6 @@ test_expect_success 'stdin -z fails delete with no ref' '
        grep "fatal: delete: missing <ref>" err
 '
 
-test_expect_success 'stdin -z fails delete with bad ref name' '
-       printf $F "delete ~a" "$m" >stdin &&
-       test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: invalid ref format: ~a" err
-'
-
 test_expect_success 'stdin -z fails delete with no old value' '
        printf $F "delete $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
diff --git c/t/t1402-check-ref-format.sh w/t/t1402-check-ref-format.sh
index 058fa37..1a5a5f3 100755
--- c/t/t1402-check-ref-format.sh
+++ w/t/t1402-check-ref-format.sh
@@ -196,52 +196,4 @@ invalid_ref_normalized 'heads///foo.lock'
 invalid_ref_normalized 'foo.lock/bar'
 invalid_ref_normalized 'foo.lock///bar'
 
-test_expect_success 'git branch shows badly named ref' '
-       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch >output &&
-       grep -e "broken...ref" output
-'
-
-test_expect_success 'git branch -d can delete badly named ref' '
-       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch -d broken...ref &&
-       git branch >output &&
-       ! grep -e "broken...ref" output
-'
-
-test_expect_success 'git branch -D can delete badly named ref' '
-       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch -D broken...ref &&
-       git branch >output &&
-       ! grep -e "broken...ref" output
-'
-
-test_expect_success 'git update-ref -d can delete badly named ref' '
-       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git update-ref -d refs/heads/broken...ref &&
-       git branch >output &&
-       ! grep -e "broken...ref" output
-'
-
-test_expect_success 'git branch can not create a badly named ref' '
-       test_must_fail git branch broken...ref
-'
-
-test_expect_success 'git branch can not rename to a bad ref name' '
-       git branch goodref &&
-       test_must_fail git branch -m goodref broken...ref
-'
-
-test_expect_failure 'git branch can rename from a bad ref name' '
-       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
-       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
-       git branch -m broken...ref renamed &&
-       test_must_fail git rev-parse broken...ref &&
-       test_cmp_rev master renamed
-'
-
 test_done
diff --git c/t/t1413-reflog-detach.sh w/t/t1413-reflog-detach.sh
new file mode 100755
index 0000000..c730600
--- /dev/null
+++ w/t/t1413-reflog-detach.sh
@@ -0,0 +1,70 @@
+#!/bin/sh
+
+test_description='Test reflog interaction with detached HEAD'
+. ./test-lib.sh
+
+reset_state () {
+       git checkout master &&
+       cp saved_reflog .git/logs/HEAD
+}
+
+test_expect_success setup '
+       test_tick &&
+       git commit --allow-empty -m initial &&
+       git branch side &&
+       test_tick &&
+       git commit --allow-empty -m second &&
+       cat .git/logs/HEAD >saved_reflog
+'
+
+test_expect_success baseline '
+       reset_state &&
+       git rev-parse master master^ >expect &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'switch to branch' '
+       reset_state &&
+       git rev-parse side master master^ >expect &&
+       git checkout side &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'detach to other' '
+       reset_state &&
+       git rev-parse master side master master^ >expect &&
+       git checkout side &&
+       git checkout master^0 &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'detach to self' '
+       reset_state &&
+       git rev-parse master master master^ >expect &&
+       git checkout master^0 &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'attach to self' '
+       reset_state &&
+       git rev-parse master master master master^ >expect &&
+       git checkout master^0 &&
+       git checkout master &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'attach to other' '
+       reset_state &&
+       git rev-parse side master master master^ >expect &&
+       git checkout master^0 &&
+       git checkout side &&
+       git log -g --format=%H >actual &&
+       test_cmp expect actual
+'
+
+test_done
diff --git c/t/t1430-bad-ref-name.sh w/t/t1430-bad-ref-name.sh
new file mode 100755
index 0000000..468e856
--- /dev/null
+++ w/t/t1430-bad-ref-name.sh
@@ -0,0 +1,207 @@
+#!/bin/sh
+
+test_description='Test handling of ref names that check-ref-format rejects'
+. ./test-lib.sh
+
+test_expect_success setup '
+       test_commit one &&
+       test_commit two
+'
+
+test_expect_success 'fast-import: fail on invalid branch name 
".badbranchname"' '
+       test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" &&
+       cat >input <<-INPUT_END &&
+               commit .badbranchname
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 
$GIT_COMMITTER_DATE
+               data <<COMMIT
+               corrupt
+               COMMIT
+
+               from refs/heads/master
+
+       INPUT_END
+       test_must_fail git fast-import <input
+'
+
+test_expect_success 'fast-import: fail on invalid branch name 
"bad[branch]name"' '
+       test_when_finished "rm -f .git/objects/pack_* .git/objects/index_*" &&
+       cat >input <<-INPUT_END &&
+               commit bad[branch]name
+               committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> 
$GIT_COMMITTER_DATE
+               data <<COMMIT
+               corrupt
+               COMMIT
+
+               from refs/heads/master
+
+       INPUT_END
+       test_must_fail git fast-import <input
+'
+
+test_expect_success 'git branch shows badly named ref' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch >output &&
+       grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'branch -d can delete badly named ref' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch -d broken...ref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'branch -D can delete badly named ref' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch -D broken...ref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'branch -D cannot delete non-ref in .git dir' '
+       echo precious >.git/my-private-file &&
+       echo precious >expect &&
+       test_must_fail git branch -D ../../my-private-file &&
+       test_cmp expect .git/my-private-file
+'
+
+test_expect_success 'branch -D cannot delete absolute path' '
+       git branch -f extra &&
+       test_must_fail git branch -D "$(pwd)/.git/refs/heads/extra" &&
+       test_cmp_rev HEAD extra
+'
+
+test_expect_success 'git branch cannot create a badly named ref' '
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       test_must_fail git branch broken...ref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'branch -m cannot rename to a bad ref name' '
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       test_might_fail git branch -D goodref &&
+       git branch goodref &&
+       test_must_fail git branch -m goodref broken...ref &&
+       test_cmp_rev master goodref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_failure 'branch -m can rename from a bad ref name' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch -m broken...ref renamed &&
+       test_cmp_rev master renamed &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'push cannot create a badly named ref' '
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       test_must_fail git push "file://$(pwd)" HEAD:refs/heads/broken...ref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_failure 'push --mirror can delete badly named ref' '
+       top=$(pwd) &&
+       git init src &&
+       git init dest &&
+
+       (
+               cd src &&
+               test_commit one
+       ) &&
+       (
+               cd dest &&
+               test_commit two &&
+               git checkout --detach &&
+               cp .git/refs/heads/master .git/refs/heads/broken...ref
+       ) &&
+       git -C src push --mirror "file://$top/dest" &&
+       git -C dest branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'rev-parse skips symref pointing to broken name' '
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git branch shadow one &&
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       git symbolic-ref refs/tags/shadow refs/heads/broken...ref &&
+
+       git rev-parse --verify one >expect &&
+       git rev-parse --verify shadow >actual 2>err &&
+       test_cmp expect actual &&
+       test_i18ngrep "ignoring.*refs/tags/shadow" err
+'
+
+test_expect_success 'update-ref --no-deref -d can delete reference to broken 
name' '
+       git symbolic-ref refs/heads/badname refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/badname" &&
+       test_path_is_file .git/refs/heads/badname &&
+       git update-ref --no-deref -d refs/heads/badname &&
+       test_path_is_missing .git/refs/heads/badname
+'
+
+test_expect_success 'update-ref -d can delete broken name' '
+       cp .git/refs/heads/master .git/refs/heads/broken...ref &&
+       test_when_finished "rm -f .git/refs/heads/broken...ref" &&
+       git update-ref -d refs/heads/broken...ref &&
+       git branch >output &&
+       ! grep -e "broken\.\.\.ref" output
+'
+
+test_expect_success 'update-ref -d cannot delete non-ref in .git dir' '
+       echo precious >.git/my-private-file &&
+       echo precious >expect &&
+       test_must_fail git update-ref -d my-private-file &&
+       test_cmp expect .git/my-private-file
+'
+
+test_expect_success 'update-ref -d cannot delete absolute path' '
+       git branch -f extra &&
+       test_must_fail git update-ref -d "$(pwd)/.git/refs/heads/extra" &&
+       test_cmp_rev HEAD extra
+'
+
+test_expect_success 'update-ref --stdin fails create with bad ref name' '
+       echo "create ~a refs/heads/master" >stdin &&
+       test_must_fail git update-ref --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'update-ref --stdin fails update with bad ref name' '
+       echo "update ~a refs/heads/master" >stdin &&
+       test_must_fail git update-ref --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'update-ref --stdin fails delete with bad ref name' '
+       echo "delete ~a refs/heads/master" >stdin &&
+       test_must_fail git update-ref --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'update-ref --stdin -z fails create with bad ref name' '
+       printf "%s\0" "create ~a " refs/heads/master >stdin &&
+       test_must_fail git update-ref -z --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a " err
+'
+
+test_expect_success 'update-ref --stdin -z fails update with bad ref name' '
+       printf "%s\0" "update ~a" refs/heads/master "" >stdin &&
+       test_must_fail git update-ref -z --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a" err
+'
+
+test_expect_success 'update-ref --stdin -z fails delete with bad ref name' '
+       printf "%s\0" "delete ~a" refs/heads/master >stdin &&
+       test_must_fail git update-ref -z --stdin <stdin 2>err &&
+       grep "fatal: invalid ref format: ~a" err
+'
+
+test_done
diff --git c/t/t9300-fast-import.sh w/t/t9300-fast-import.sh
index 5fc9ef2..3d156f9 100755
--- c/t/t9300-fast-import.sh
+++ w/t/t9300-fast-import.sh
@@ -347,36 +347,6 @@ test_expect_success 'B: fail on invalid blob sha1' '
 rm -f .git/objects/pack_* .git/objects/index_*
 
 cat >input <<INPUT_END
-commit .badbranchname
-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
-data <<COMMIT
-corrupt
-COMMIT
-
-from refs/heads/master
-
-INPUT_END
-test_expect_success 'B: fail on invalid branch name ".badbranchname"' '
-    test_must_fail git fast-import <input
-'
-rm -f .git/objects/pack_* .git/objects/index_*
-
-cat >input <<INPUT_END
-commit bad[branch]name
-committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
-data <<COMMIT
-corrupt
-COMMIT
-
-from refs/heads/master
-
-INPUT_END
-test_expect_success 'B: fail on invalid branch name "bad[branch]name"' '
-    test_must_fail git fast-import <input
-'
-rm -f .git/objects/pack_* .git/objects/index_*
-
-cat >input <<INPUT_END
 commit TEMP_TAG
 committer $GIT_COMMITTER_NAME <$GIT_COMMITTER_EMAIL> $GIT_COMMITTER_DATE
 data <<COMMIT
diff --git c/transport-helper.c w/transport-helper.c
index 8365441..3497a5e 100644
--- c/transport-helper.c
+++ w/transport-helper.c
@@ -890,8 +890,9 @@ static int push_refs_with_export(struct transport 
*transport,
 
                                        /* Follow symbolic refs (mainly for 
HEAD). */
                                        name = resolve_ref_unsafe(
-                                                ref->peer_ref->name, sha1,
-                                                &flag, RESOLVE_REF_READING);
+                                                ref->peer_ref->name,
+                                                RESOLVE_REF_READING,
+                                                sha1, &flag);
                                        if (!name || !(flag & REF_ISSYMREF))
                                                name = ref->peer_ref->name;
 
diff --git c/transport.c w/transport.c
index 3ba7bbf..76e0a9a 100644
--- c/transport.c
+++ w/transport.c
@@ -168,8 +168,8 @@ static void set_upstreams(struct transport *transport, 
struct ref *refs,
                /* Follow symbolic refs (mainly for HEAD). */
                localname = ref->peer_ref->name;
                remotename = ref->name;
-               tmp = resolve_ref_unsafe(localname, sha, &flag,
-                                        RESOLVE_REF_READING);
+               tmp = resolve_ref_unsafe(localname, RESOLVE_REF_READING,
+                                        sha, &flag);
                if (tmp && flag & REF_ISSYMREF &&
                        starts_with(tmp, "refs/heads/"))
                        localname = tmp;
@@ -754,7 +754,7 @@ void transport_print_push_status(const char *dest, struct 
ref *refs,
        unsigned char head_sha1[20];
        char *head;
 
-       head = resolve_refdup("HEAD", head_sha1, NULL, RESOLVE_REF_READING);
+       head = resolve_refdup("HEAD", RESOLVE_REF_READING, head_sha1, NULL);
 
        if (verbose) {
                for (ref = refs; ref; ref = ref->next)
diff --git c/upload-pack.c w/upload-pack.c
index 3b51ccb..4542565 100644
--- c/upload-pack.c
+++ w/upload-pack.c
@@ -743,7 +743,7 @@ static int find_symref(const char *refname, const unsigned 
char *sha1, int flag,
 
        if ((flag & REF_ISSYMREF) == 0)
                return 0;
-       symref_target = resolve_ref_unsafe(refname, unused, &flag, 0);
+       symref_target = resolve_ref_unsafe(refname, 0, unused, &flag);
        if (!symref_target || (flag & REF_ISSYMREF) == 0)
                die("'%s' is a symref but it is not?", refname);
        item = string_list_append(cb_data, refname);
diff --git c/wt-status.c w/wt-status.c
index 6819e2b..c3cbf50 100644
--- c/wt-status.c
+++ w/wt-status.c
@@ -128,7 +128,7 @@ void wt_status_prepare(struct wt_status *s)
        s->show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES;
        s->use_color = -1;
        s->relative_paths = 1;
-       s->branch = resolve_refdup("HEAD", sha1, NULL, 0);
+       s->branch = resolve_refdup("HEAD", 0, sha1, NULL);
        s->reference = "HEAD";
        s->fp = stdout;
        s->index_file = get_index_file();
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to