Merge branch 'mh/ref-transaction'
authorJunio C Hamano <gitster@pobox.com>
Tue, 3 Jun 2014 19:06:40 +0000 (12:06 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 3 Jun 2014 19:06:41 +0000 (12:06 -0700)
Update "update-ref --stdin [-z]" and then introduce a transactional
support for (multi-)reference updates.

* mh/ref-transaction: (27 commits)
ref_transaction_commit(): work with transaction->updates in place
struct ref_update: add a type field
struct ref_update: add a lock field
ref_transaction_commit(): simplify code using temporary variables
struct ref_update: store refname as a FLEX_ARRAY
struct ref_update: rename field "ref_name" to "refname"
refs: remove API function update_refs()
update-ref --stdin: reimplement using reference transactions
refs: add a concept of a reference transaction
update-ref --stdin: harmonize error messages
update-ref --stdin: improve the error message for unexpected EOF
t1400: test one mistake at a time
update-ref --stdin -z: deprecate interpreting the empty string as zeros
update-ref.c: extract a new function, parse_next_sha1()
t1400: test that stdin -z update treats empty <newvalue> as zeros
update-ref --stdin: simplify error messages for missing oldvalues
update-ref --stdin: make error messages more consistent
update-ref --stdin: improve error messages for invalid values
update-ref.c: extract a new function, parse_refname()
parse_cmd_verify(): copy old_sha1 instead of evaluating <oldvalue> twice
...

13 files changed:
Documentation/git-update-ref.txt
builtin/checkout.c
builtin/clone.c
builtin/merge.c
builtin/notes.c
builtin/reset.c
builtin/update-ref.c
contrib/examples/builtin-fetch--tool.c
notes-cache.c
notes-utils.c
refs.c
refs.h
t/t1400-update-ref.sh
index 0a0a5512b3c62eb1179a3d5a9687949c39b2a9ef..c8f5ae5cb362276de94aa867af59818a75b04595 100644 (file)
@@ -68,7 +68,12 @@ performs all modifications together.  Specify commands of the form:
        option SP <opt> LF
 
 Quote fields containing whitespace as if they were strings in C source
-code.  Alternatively, use `-z` to specify commands without quoting:
+code; i.e., surrounded by double-quotes and with backslash escapes.
+Use 40 "0" characters or the empty string to specify a zero value.  To
+specify a missing value, omit the value and its preceding SP entirely.
+
+Alternatively, use `-z` to specify in NUL-terminated format, without
+quoting:
 
        update SP <ref> NUL <newvalue> NUL [<oldvalue>] NUL
        create SP <ref> NUL <newvalue> NUL
@@ -76,8 +81,12 @@ code.  Alternatively, use `-z` to specify commands without quoting:
        verify SP <ref> NUL [<oldvalue>] NUL
        option SP <opt> NUL
 
-Lines of any other format or a repeated <ref> produce an error.
-Command meanings are:
+In this format, use 40 "0" to specify a zero value, and use the empty
+string to specify a missing value.
+
+In either format, values can be specified in any form that Git
+recognizes as an object name.  Commands in any other format or a
+repeated <ref> produce an error.  Command meanings are:
 
 update::
        Set <ref> to <newvalue> after verifying <oldvalue>, if given.
@@ -102,9 +111,6 @@ option::
        The only valid option is `no-deref` to avoid dereferencing
        a symbolic ref.
 
-Use 40 "0" or the empty string to specify a zero value, except that
-with `-z` an empty <oldvalue> is considered missing.
-
 If all <ref>s can be locked with matching <oldvalue>s
 simultaneously, all modifications are performed.  Otherwise, no
 modifications are performed.  Note that while each individual
index 07cf55530918e8de6ae45e1c9b7e16707c32f844..ff4492162d9543598c1cdc8951f8604a4712516d 100644 (file)
@@ -624,7 +624,7 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                /* Nothing to do. */
        } else if (opts->force_detach || !new->path) {  /* No longer on any branch. */
                update_ref(msg.buf, "HEAD", new->commit->object.sha1, NULL,
-                          REF_NODEREF, DIE_ON_ERR);
+                          REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
                if (!opts->quiet) {
                        if (old->path && advice_detached_head)
                                detach_advice(new->name);
index 9b3c04d914b3d318f723c2d3ded403fca9503c07..b12989d1caecb48128e8a468014f07b237b1a4cc 100644 (file)
@@ -521,7 +521,7 @@ static void write_followtags(const struct ref *refs, const char *msg)
                if (!has_sha1_file(ref->old_sha1))
                        continue;
                update_ref(msg, ref->name, ref->old_sha1,
-                          NULL, 0, DIE_ON_ERR);
+                          NULL, 0, UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
@@ -589,14 +589,15 @@ static void update_head(const struct ref *our, const struct ref *remote,
                create_symref("HEAD", our->name, NULL);
                if (!option_bare) {
                        const char *head = skip_prefix(our->name, "refs/heads/");
-                       update_ref(msg, "HEAD", our->old_sha1, NULL, 0, DIE_ON_ERR);
+                       update_ref(msg, "HEAD", our->old_sha1, NULL, 0,
+                                  UPDATE_REFS_DIE_ON_ERR);
                        install_branch_config(0, head, option_origin, our->name);
                }
        } else if (our) {
                struct commit *c = lookup_commit_reference(our->old_sha1);
                /* --branch specifies a non-branch (i.e. tags), detach HEAD */
                update_ref(msg, "HEAD", c->object.sha1,
-                          NULL, REF_NODEREF, DIE_ON_ERR);
+                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
        } else if (remote) {
                /*
                 * We know remote HEAD points to a non-branch, or
@@ -604,7 +605,7 @@ static void update_head(const struct ref *our, const struct ref *remote,
                 * Detach HEAD in all these cases.
                 */
                update_ref(msg, "HEAD", remote->old_sha1,
-                          NULL, REF_NODEREF, DIE_ON_ERR);
+                          NULL, REF_NODEREF, UPDATE_REFS_DIE_ON_ERR);
        }
 }
 
index 66d8843301b22a6a89d9d765e9d3020942e6f672..7c9d8f2432903eba41e457d4b451ab6b928703a0 100644 (file)
@@ -398,7 +398,7 @@ static void finish(struct commit *head_commit,
                        const char *argv_gc_auto[] = { "gc", "--auto", NULL };
                        update_ref(reflog_message.buf, "HEAD",
                                new_head, head, 0,
-                               DIE_ON_ERR);
+                               UPDATE_REFS_DIE_ON_ERR);
                        /*
                         * We ignore errors in 'gc --auto', since the
                         * user should see them.
@@ -1222,7 +1222,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
                        die(_("%s - not something we can merge"), argv[0]);
                read_empty(remote_head->object.sha1, 0);
                update_ref("initial pull", "HEAD", remote_head->object.sha1,
-                          NULL, 0, DIE_ON_ERR);
+                          NULL, 0, UPDATE_REFS_DIE_ON_ERR);
                goto done;
        } else {
                struct strbuf merge_names = STRBUF_INIT;
@@ -1339,7 +1339,7 @@ int cmd_merge(int argc, const char **argv, const char *prefix)
        }
 
        update_ref("updating ORIG_HEAD", "ORIG_HEAD", head_commit->object.sha1,
-                  NULL, 0, DIE_ON_ERR);
+                  NULL, 0, UPDATE_REFS_DIE_ON_ERR);
 
        if (remoteheads && !common)
                ; /* No common ancestors found. We need a real merge. */
index 39c8573cde00bc15e673ae549fcd0f22ea67d29f..820c34135cab439e2168dd08947bd79d7ed7ae71 100644 (file)
@@ -717,7 +717,7 @@ static int merge_commit(struct notes_merge_options *o)
        strbuf_insert(&msg, 0, "notes: ", 7);
        update_ref(msg.buf, o->local_ref, sha1,
                   is_null_sha1(parent_sha1) ? NULL : parent_sha1,
-                  0, DIE_ON_ERR);
+                  0, UPDATE_REFS_DIE_ON_ERR);
 
        free_notes(t);
        strbuf_release(&msg);
@@ -812,11 +812,11 @@ static int merge(int argc, const char **argv, const char *prefix)
        if (result >= 0) /* Merge resulted (trivially) in result_sha1 */
                /* Update default notes ref with new commit */
                update_ref(msg.buf, default_notes_ref(), result_sha1, NULL,
-                          0, DIE_ON_ERR);
+                          0, UPDATE_REFS_DIE_ON_ERR);
        else { /* Merge has unresolved conflicts */
                /* Update .git/NOTES_MERGE_PARTIAL with partial merge result */
                update_ref(msg.buf, "NOTES_MERGE_PARTIAL", result_sha1, NULL,
-                          0, DIE_ON_ERR);
+                          0, UPDATE_REFS_DIE_ON_ERR);
                /* Store ref-to-be-updated into .git/NOTES_MERGE_REF */
                if (create_symref("NOTES_MERGE_REF", default_notes_ref(), NULL))
                        die("Failed to store link to current notes ref (%s)",
index f4e087596b6337d2306af4cf45119752b4a049ea..f368266762fae1e6c783d7351e289e423e408b3c 100644 (file)
@@ -252,11 +252,13 @@ static int reset_refs(const char *rev, const unsigned char *sha1)
        if (!get_sha1("HEAD", sha1_orig)) {
                orig = sha1_orig;
                set_reflog_message(&msg, "updating ORIG_HEAD", NULL);
-               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0, MSG_ON_ERR);
+               update_ref(msg.buf, "ORIG_HEAD", orig, old_orig, 0,
+                          UPDATE_REFS_MSG_ON_ERR);
        } else if (old_orig)
                delete_ref("ORIG_HEAD", old_orig, 0);
        set_reflog_message(&msg, "updating HEAD", rev);
-       update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0, MSG_ON_ERR);
+       update_ref_status = update_ref(msg.buf, "HEAD", sha1, orig, 0,
+                                      UPDATE_REFS_MSG_ON_ERR);
        strbuf_release(&msg);
        return update_ref_status;
 }
index 5c208bb1fc4f6c467842b37157435e87fe1415cd..405267f6e2776b3b2bf5c91c1102e090f5ac68f0 100644 (file)
@@ -12,238 +12,329 @@ static const char * const git_update_ref_usage[] = {
        NULL
 };
 
-static int updates_alloc;
-static int updates_count;
-static const struct ref_update **updates;
+static struct ref_transaction *transaction;
 
 static char line_termination = '\n';
 static int update_flags;
 
-static struct ref_update *update_alloc(void)
-{
-       struct ref_update *update;
-
-       /* Allocate and zero-init a struct ref_update */
-       update = xcalloc(1, sizeof(*update));
-       ALLOC_GROW(updates, updates_count + 1, updates_alloc);
-       updates[updates_count++] = update;
-
-       /* Store and reset accumulated options */
-       update->flags = update_flags;
-       update_flags = 0;
-
-       return update;
-}
-
-static void update_store_ref_name(struct ref_update *update,
-                                 const char *ref_name)
-{
-       if (check_refname_format(ref_name, REFNAME_ALLOW_ONELEVEL))
-               die("invalid ref format: %s", ref_name);
-       update->ref_name = xstrdup(ref_name);
-}
-
-static void update_store_new_sha1(struct ref_update *update,
-                                 const char *newvalue)
-{
-       if (*newvalue && get_sha1(newvalue, update->new_sha1))
-               die("invalid new value for ref %s: %s",
-                   update->ref_name, newvalue);
-}
-
-static void update_store_old_sha1(struct ref_update *update,
-                                 const char *oldvalue)
-{
-       if (*oldvalue && get_sha1(oldvalue, update->old_sha1))
-               die("invalid old value for ref %s: %s",
-                   update->ref_name, oldvalue);
-
-       /* We have an old value if non-empty, or if empty without -z */
-       update->have_old = *oldvalue || line_termination;
-}
-
+/*
+ * Parse one whitespace- or NUL-terminated, possibly C-quoted argument
+ * and append the result to arg.  Return a pointer to the terminator.
+ * Die if there is an error in how the argument is C-quoted.  This
+ * function is only used if not -z.
+ */
 static const char *parse_arg(const char *next, struct strbuf *arg)
 {
-       /* Parse SP-terminated, possibly C-quoted argument */
-       if (*next != '"')
+       if (*next == '"') {
+               const char *orig = next;
+
+               if (unquote_c_style(arg, next, &next))
+                       die("badly quoted argument: %s", orig);
+               if (*next && !isspace(*next))
+                       die("unexpected character after quoted argument: %s", orig);
+       } else {
                while (*next && !isspace(*next))
                        strbuf_addch(arg, *next++);
-       else if (unquote_c_style(arg, next, &next))
-               die("badly quoted argument: %s", next);
+       }
 
-       /* Return position after the argument */
        return next;
 }
 
-static const char *parse_first_arg(const char *next, struct strbuf *arg)
+/*
+ * Parse the reference name immediately after "command SP".  If not
+ * -z, then handle C-quoting.  Return a pointer to a newly allocated
+ * string containing the name of the reference, or NULL if there was
+ * an error.  Update *next to point at the character that terminates
+ * the argument.  Die if C-quoting is malformed or the reference name
+ * is invalid.
+ */
+static char *parse_refname(struct strbuf *input, const char **next)
 {
-       /* Parse argument immediately after "command SP" */
-       strbuf_reset(arg);
+       struct strbuf ref = STRBUF_INIT;
+
        if (line_termination) {
                /* Without -z, use the next argument */
-               next = parse_arg(next, arg);
+               *next = parse_arg(*next, &ref);
        } else {
-               /* With -z, use rest of first NUL-terminated line */
-               strbuf_addstr(arg, next);
-               next = next + arg->len;
+               /* With -z, use everything up to the next NUL */
+               strbuf_addstr(&ref, *next);
+               *next += ref.len;
        }
-       return next;
+
+       if (!ref.len) {
+               strbuf_release(&ref);
+               return NULL;
+       }
+
+       if (check_refname_format(ref.buf, REFNAME_ALLOW_ONELEVEL))
+               die("invalid ref format: %s", ref.buf);
+
+       return strbuf_detach(&ref, NULL);
 }
 
-static const char *parse_next_arg(const char *next, struct strbuf *arg)
+/*
+ * The value being parsed is <oldvalue> (as opposed to <newvalue>; the
+ * difference affects which error messages are generated):
+ */
+#define PARSE_SHA1_OLD 0x01
+
+/*
+ * For backwards compatibility, accept an empty string for update's
+ * <newvalue> in binary mode to be equivalent to specifying zeros.
+ */
+#define PARSE_SHA1_ALLOW_EMPTY 0x02
+
+/*
+ * Parse an argument separator followed by the next argument, if any.
+ * If there is an argument, convert it to a SHA-1, write it to sha1,
+ * set *next to point at the character terminating the argument, and
+ * return 0.  If there is no argument at all (not even the empty
+ * string), return 1 and leave *next unchanged.  If the value is
+ * provided but cannot be converted to a SHA-1, die.  flags can
+ * include PARSE_SHA1_OLD and/or PARSE_SHA1_ALLOW_EMPTY.
+ */
+static int parse_next_sha1(struct strbuf *input, const char **next,
+                          unsigned char *sha1,
+                          const char *command, const char *refname,
+                          int flags)
 {
-       /* Parse next SP-terminated or NUL-terminated argument, if any */
-       strbuf_reset(arg);
+       struct strbuf arg = STRBUF_INIT;
+       int ret = 0;
+
+       if (*next == input->buf + input->len)
+               goto eof;
+
        if (line_termination) {
                /* Without -z, consume SP and use next argument */
-               if (!*next)
-                       return NULL;
-               if (*next != ' ')
-                       die("expected SP but got: %s", next);
-               next = parse_arg(next + 1, arg);
+               if (!**next || **next == line_termination)
+                       return 1;
+               if (**next != ' ')
+                       die("%s %s: expected SP but got: %s",
+                           command, refname, *next);
+               (*next)++;
+               *next = parse_arg(*next, &arg);
+               if (arg.len) {
+                       if (get_sha1(arg.buf, sha1))
+                               goto invalid;
+               } else {
+                       /* Without -z, an empty value means all zeros: */
+                       hashclr(sha1);
+               }
        } else {
                /* With -z, read the next NUL-terminated line */
-               if (*next)
-                       die("expected NUL but got: %s", next);
-               if (strbuf_getline(arg, stdin, '\0') == EOF)
-                       return NULL;
-               next = arg->buf + arg->len;
+               if (**next)
+                       die("%s %s: expected NUL but got: %s",
+                           command, refname, *next);
+               (*next)++;
+               if (*next == input->buf + input->len)
+                       goto eof;
+               strbuf_addstr(&arg, *next);
+               *next += arg.len;
+
+               if (arg.len) {
+                       if (get_sha1(arg.buf, sha1))
+                               goto invalid;
+               } else if (flags & PARSE_SHA1_ALLOW_EMPTY) {
+                       /* With -z, treat an empty value as all zeros: */
+                       warning("%s %s: missing <newvalue>, treating as zero",
+                               command, refname);
+                       hashclr(sha1);
+               } else {
+                       /*
+                        * With -z, an empty non-required value means
+                        * unspecified:
+                        */
+                       ret = 1;
+               }
        }
-       return next;
+
+       strbuf_release(&arg);
+
+       return ret;
+
+ invalid:
+       die(flags & PARSE_SHA1_OLD ?
+           "%s %s: invalid <oldvalue>: %s" :
+           "%s %s: invalid <newvalue>: %s",
+           command, refname, arg.buf);
+
+ eof:
+       die(flags & PARSE_SHA1_OLD ?
+           "%s %s: unexpected end of input when reading <oldvalue>" :
+           "%s %s: unexpected end of input when reading <newvalue>",
+           command, refname);
 }
 
-static void parse_cmd_update(const char *next)
+
+/*
+ * The following five parse_cmd_*() functions parse the corresponding
+ * command.  In each case, next points at the character following the
+ * command name and the following space.  They each return a pointer
+ * to the character terminating the command, and die with an
+ * explanatory message if there are any parsing problems.  All of
+ * these functions handle either text or binary format input,
+ * depending on how line_termination is set.
+ */
+
+static const char *parse_cmd_update(struct strbuf *input, const char *next)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf newvalue = STRBUF_INIT;
-       struct strbuf oldvalue = STRBUF_INIT;
-       struct ref_update *update;
+       char *refname;
+       unsigned char new_sha1[20];
+       unsigned char old_sha1[20];
+       int have_old;
 
-       update = update_alloc();
+       refname = parse_refname(input, &next);
+       if (!refname)
+               die("update: missing <ref>");
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("update line missing <ref>");
+       if (parse_next_sha1(input, &next, new_sha1, "update", refname,
+                           PARSE_SHA1_ALLOW_EMPTY))
+               die("update %s: missing <newvalue>", refname);
 
-       if ((next = parse_next_arg(next, &newvalue)) != NULL)
-               update_store_new_sha1(update, newvalue.buf);
-       else
-               die("update %s missing <newvalue>", ref.buf);
+       have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
+                                   PARSE_SHA1_OLD);
 
-       if ((next = parse_next_arg(next, &oldvalue)) != NULL)
-               update_store_old_sha1(update, oldvalue.buf);
-       else if(!line_termination)
-               die("update %s missing [<oldvalue>] NUL", ref.buf);
+       if (*next != line_termination)
+               die("update %s: extra input: %s", refname, next);
 
-       if (next && *next)
-               die("update %s has extra input: %s", ref.buf, next);
+       ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+                              update_flags, have_old);
+
+       update_flags = 0;
+       free(refname);
+
+       return next;
 }
 
-static void parse_cmd_create(const char *next)
+static const char *parse_cmd_create(struct strbuf *input, const char *next)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf newvalue = STRBUF_INIT;
-       struct ref_update *update;
+       char *refname;
+       unsigned char new_sha1[20];
 
-       update = update_alloc();
-       update->have_old = 1;
+       refname = parse_refname(input, &next);
+       if (!refname)
+               die("create: missing <ref>");
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("create line missing <ref>");
+       if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
+               die("create %s: missing <newvalue>", refname);
 
-       if ((next = parse_next_arg(next, &newvalue)) != NULL)
-               update_store_new_sha1(update, newvalue.buf);
-       else
-               die("create %s missing <newvalue>", ref.buf);
-       if (is_null_sha1(update->new_sha1))
-               die("create %s given zero new value", ref.buf);
+       if (is_null_sha1(new_sha1))
+               die("create %s: zero <newvalue>", refname);
+
+       if (*next != line_termination)
+               die("create %s: extra input: %s", refname, next);
 
-       if (next && *next)
-               die("create %s has extra input: %s", ref.buf, next);
+       ref_transaction_create(transaction, refname, new_sha1, update_flags);
+
+       update_flags = 0;
+       free(refname);
+
+       return next;
 }
 
-static void parse_cmd_delete(const char *next)
+static const char *parse_cmd_delete(struct strbuf *input, const char *next)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf oldvalue = STRBUF_INIT;
-       struct ref_update *update;
+       char *refname;
+       unsigned char old_sha1[20];
+       int have_old;
 
-       update = update_alloc();
+       refname = parse_refname(input, &next);
+       if (!refname)
+               die("delete: missing <ref>");
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("delete line missing <ref>");
+       if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
+                           PARSE_SHA1_OLD)) {
+               have_old = 0;
+       } else {
+               if (is_null_sha1(old_sha1))
+                       die("delete %s: zero <oldvalue>", refname);
+               have_old = 1;
+       }
+
+       if (*next != line_termination)
+               die("delete %s: extra input: %s", refname, next);
 
-       if ((next = parse_next_arg(next, &oldvalue)) != NULL)
-               update_store_old_sha1(update, oldvalue.buf);
-       else if(!line_termination)
-               die("delete %s missing [<oldvalue>] NUL", ref.buf);
-       if (update->have_old && is_null_sha1(update->old_sha1))
-               die("delete %s given zero old value", ref.buf);
+       ref_transaction_delete(transaction, refname, old_sha1,
+                              update_flags, have_old);
 
-       if (next && *next)
-               die("delete %s has extra input: %s", ref.buf, next);
+       update_flags = 0;
+       free(refname);
+
+       return next;
 }
 
-static void parse_cmd_verify(const char *next)
+static const char *parse_cmd_verify(struct strbuf *input, const char *next)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf value = STRBUF_INIT;
-       struct ref_update *update;
+       char *refname;
+       unsigned char new_sha1[20];
+       unsigned char old_sha1[20];
+       int have_old;
+
+       refname = parse_refname(input, &next);
+       if (!refname)
+               die("verify: missing <ref>");
+
+       if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
+                           PARSE_SHA1_OLD)) {
+               hashclr(new_sha1);
+               have_old = 0;
+       } else {
+               hashcpy(new_sha1, old_sha1);
+               have_old = 1;
+       }
 
-       update = update_alloc();
+       if (*next != line_termination)
+               die("verify %s: extra input: %s", refname, next);
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("verify line missing <ref>");
+       ref_transaction_update(transaction, refname, new_sha1, old_sha1,
+                              update_flags, have_old);
 
-       if ((next = parse_next_arg(next, &value)) != NULL) {
-               update_store_old_sha1(update, value.buf);
-               update_store_new_sha1(update, value.buf);
-       } else if(!line_termination)
-               die("verify %s missing [<oldvalue>] NUL", ref.buf);
+       update_flags = 0;
+       free(refname);
 
-       if (next && *next)
-               die("verify %s has extra input: %s", ref.buf, next);
+       return next;
 }
 
-static void parse_cmd_option(const char *next)
+static const char *parse_cmd_option(struct strbuf *input, const char *next)
 {
-       if (!strcmp(next, "no-deref"))
+       if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
                update_flags |= REF_NODEREF;
        else
                die("option unknown: %s", next);
+       return next + 8;
 }
 
 static void update_refs_stdin(void)
 {
-       struct strbuf cmd = STRBUF_INIT;
+       struct strbuf input = STRBUF_INIT;
+       const char *next;
 
+       if (strbuf_read(&input, 0, 1000) < 0)
+               die_errno("could not read from stdin");
+       next = input.buf;
        /* Read each line dispatch its command */
-       while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
-               if (!cmd.buf[0])
+       while (next < input.buf + input.len) {
+               if (*next == line_termination)
                        die("empty command in input");
-               else if (isspace(*cmd.buf))
-                       die("whitespace before command: %s", cmd.buf);
-               else if (starts_with(cmd.buf, "update "))
-                       parse_cmd_update(cmd.buf + 7);
-               else if (starts_with(cmd.buf, "create "))
-                       parse_cmd_create(cmd.buf + 7);
-               else if (starts_with(cmd.buf, "delete "))
-                       parse_cmd_delete(cmd.buf + 7);
-               else if (starts_with(cmd.buf, "verify "))
-                       parse_cmd_verify(cmd.buf + 7);
-               else if (starts_with(cmd.buf, "option "))
-                       parse_cmd_option(cmd.buf + 7);
+               else if (isspace(*next))
+                       die("whitespace before command: %s", next);
+               else if (starts_with(next, "update "))
+                       next = parse_cmd_update(&input, next + 7);
+               else if (starts_with(next, "create "))
+                       next = parse_cmd_create(&input, next + 7);
+               else if (starts_with(next, "delete "))
+                       next = parse_cmd_delete(&input, next + 7);
+               else if (starts_with(next, "verify "))
+                       next = parse_cmd_verify(&input, next + 7);
+               else if (starts_with(next, "option "))
+                       next = parse_cmd_option(&input, next + 7);
                else
-                       die("unknown command: %s", cmd.buf);
+                       die("unknown command: %s", next);
+
+               next++;
+       }
 
-       strbuf_release(&cmd);
+       strbuf_release(&input);
 }
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
@@ -268,12 +359,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                die("Refusing to perform update with empty message.");
 
        if (read_stdin) {
+               int ret;
+               transaction = ref_transaction_begin();
+
                if (delete || no_deref || argc > 0)
                        usage_with_options(git_update_ref_usage, options);
                if (end_null)
                        line_termination = '\0';
                update_refs_stdin();
-               return update_refs(msg, updates, updates_count, DIE_ON_ERR);
+               ret = ref_transaction_commit(transaction, msg,
+                                            UPDATE_REFS_DIE_ON_ERR);
+               return ret;
        }
 
        if (end_null)
@@ -305,5 +401,5 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
        else
                return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 flags, DIE_ON_ERR);
+                                 flags, UPDATE_REFS_DIE_ON_ERR);
 }
index 8bc8c7533a8678e2a43d3ddd49bc9aa8d670897d..ee1916641e46c1adfaac31e27c78c11b023d6ffa 100644 (file)
@@ -31,7 +31,8 @@ static int update_ref_env(const char *action,
                rla = "(reflog update)";
        if (snprintf(msg, sizeof(msg), "%s: %s", rla, action) >= sizeof(msg))
                warning("reflog message too long: %.*s...", 50, msg);
-       return update_ref(msg, refname, sha1, oldval, 0, QUIET_ON_ERR);
+       return update_ref(msg, refname, sha1, oldval, 0,
+                         UPDATE_REFS_QUIET_ON_ERR);
 }
 
 static int update_local_ref(const char *name,
index eabe4a0d9bf44050dba43cbd821642cdfd3fb40b..97dfd63c9bf564ac052addc4a3f62a325bbc1303 100644 (file)
@@ -62,7 +62,7 @@ int notes_cache_write(struct notes_cache *c)
        if (commit_tree(&msg, tree_sha1, NULL, commit_sha1, NULL, NULL) < 0)
                return -1;
        if (update_ref("update notes cache", c->tree.ref, commit_sha1, NULL,
-                      0, QUIET_ON_ERR) < 0)
+                      0, UPDATE_REFS_QUIET_ON_ERR) < 0)
                return -1;
 
        return 0;
index 4aa7023903374aa3ccea23d5fc9ae82c9c67bca7..a0b1d7be98253368921a6e95653ceb36dbda28b2 100644 (file)
@@ -48,7 +48,8 @@ void commit_notes(struct notes_tree *t, const char *msg)
 
        create_notes_commit(t, NULL, &buf, commit_sha1);
        strbuf_insert(&buf, 0, "notes: ", 7); /* commit message starts at index 7 */
-       update_ref(buf.buf, t->ref, commit_sha1, NULL, 0, DIE_ON_ERR);
+       update_ref(buf.buf, t->ref, commit_sha1, NULL, 0,
+                  UPDATE_REFS_DIE_ON_ERR);
 
        strbuf_release(&buf);
 }
diff --git a/refs.c b/refs.c
index 28d5eca8eaff7be48a4b89e6217886b8c96eb3b2..728a76164830368637e95390d95ce3f7c7df659e 100644 (file)
--- a/refs.c
+++ b/refs.c
@@ -3243,9 +3243,9 @@ static struct ref_lock *update_ref_lock(const char *refname,
        if (!lock) {
                const char *str = "Cannot lock the ref '%s'.";
                switch (onerr) {
-               case MSG_ON_ERR: error(str, refname); break;
-               case DIE_ON_ERR: die(str, refname); break;
-               case QUIET_ON_ERR: break;
+               case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+               case UPDATE_REFS_QUIET_ON_ERR: break;
                }
        }
        return lock;
@@ -3258,15 +3258,118 @@ static int update_ref_write(const char *action, const char *refname,
        if (write_ref_sha1(lock, sha1, action) < 0) {
                const char *str = "Cannot update the ref '%s'.";
                switch (onerr) {
-               case MSG_ON_ERR: error(str, refname); break;
-               case DIE_ON_ERR: die(str, refname); break;
-               case QUIET_ON_ERR: break;
+               case UPDATE_REFS_MSG_ON_ERR: error(str, refname); break;
+               case UPDATE_REFS_DIE_ON_ERR: die(str, refname); break;
+               case UPDATE_REFS_QUIET_ON_ERR: break;
                }
                return 1;
        }
        return 0;
 }
 
+/**
+ * Information needed for a single ref update.  Set new_sha1 to the
+ * new value or to zero to delete the ref.  To check the old value
+ * while locking the ref, set have_old to 1 and set old_sha1 to the
+ * value or to zero to ensure the ref does not exist before update.
+ */
+struct ref_update {
+       unsigned char new_sha1[20];
+       unsigned char old_sha1[20];
+       int flags; /* REF_NODEREF? */
+       int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
+       struct ref_lock *lock;
+       int type;
+       const char refname[FLEX_ARRAY];
+};
+
+/*
+ * Data structure for holding a reference transaction, which can
+ * consist of checks and updates to multiple references, carried out
+ * as atomically as possible.  This structure is opaque to callers.
+ */
+struct ref_transaction {
+       struct ref_update **updates;
+       size_t alloc;
+       size_t nr;
+};
+
+struct ref_transaction *ref_transaction_begin(void)
+{
+       return xcalloc(1, sizeof(struct ref_transaction));
+}
+
+static void ref_transaction_free(struct ref_transaction *transaction)
+{
+       int i;
+
+       for (i = 0; i < transaction->nr; i++)
+               free(transaction->updates[i]);
+
+       free(transaction->updates);
+       free(transaction);
+}
+
+void ref_transaction_rollback(struct ref_transaction *transaction)
+{
+       ref_transaction_free(transaction);
+}
+
+static struct ref_update *add_update(struct ref_transaction *transaction,
+                                    const char *refname)
+{
+       size_t len = strlen(refname);
+       struct ref_update *update = xcalloc(1, sizeof(*update) + len + 1);
+
+       strcpy((char *)update->refname, refname);
+       ALLOC_GROW(transaction->updates, transaction->nr + 1, transaction->alloc);
+       transaction->updates[transaction->nr++] = update;
+       return update;
+}
+
+void ref_transaction_update(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1, unsigned char *old_sha1,
+                           int flags, int have_old)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       hashcpy(update->new_sha1, new_sha1);
+       update->flags = flags;
+       update->have_old = have_old;
+       if (have_old)
+               hashcpy(update->old_sha1, old_sha1);
+}
+
+void ref_transaction_create(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1,
+                           int flags)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       assert(!is_null_sha1(new_sha1));
+       hashcpy(update->new_sha1, new_sha1);
+       hashclr(update->old_sha1);
+       update->flags = flags;
+       update->have_old = 1;
+}
+
+void ref_transaction_delete(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *old_sha1,
+                           int flags, int have_old)
+{
+       struct ref_update *update = add_update(transaction, refname);
+
+       update->flags = flags;
+       update->have_old = have_old;
+       if (have_old) {
+               assert(!is_null_sha1(old_sha1));
+               hashcpy(update->old_sha1, old_sha1);
+       }
+}
+
 int update_ref(const char *action, const char *refname,
               const unsigned char *sha1, const unsigned char *oldval,
               int flags, enum action_on_err onerr)
@@ -3282,7 +3385,7 @@ static int ref_update_compare(const void *r1, const void *r2)
 {
        const struct ref_update * const *u1 = r1;
        const struct ref_update * const *u2 = r2;
-       return strcmp((*u1)->ref_name, (*u2)->ref_name);
+       return strcmp((*u1)->refname, (*u2)->refname);
 }
 
 static int ref_update_reject_duplicates(struct ref_update **updates, int n,
@@ -3290,15 +3393,15 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
 {
        int i;
        for (i = 1; i < n; i++)
-               if (!strcmp(updates[i - 1]->ref_name, updates[i]->ref_name)) {
+               if (!strcmp(updates[i - 1]->refname, updates[i]->refname)) {
                        const char *str =
                                "Multiple updates for ref '%s' not allowed.";
                        switch (onerr) {
-                       case MSG_ON_ERR:
-                               error(str, updates[i]->ref_name); break;
-                       case DIE_ON_ERR:
-                               die(str, updates[i]->ref_name); break;
-                       case QUIET_ON_ERR:
+                       case UPDATE_REFS_MSG_ON_ERR:
+                               error(str, updates[i]->refname); break;
+                       case UPDATE_REFS_DIE_ON_ERR:
+                               die(str, updates[i]->refname); break;
+                       case UPDATE_REFS_QUIET_ON_ERR:
                                break;
                        }
                        return 1;
@@ -3306,26 +3409,21 @@ static int ref_update_reject_duplicates(struct ref_update **updates, int n,
        return 0;
 }
 
-int update_refs(const char *action, const struct ref_update **updates_orig,
-               int n, enum action_on_err onerr)
+int ref_transaction_commit(struct ref_transaction *transaction,
+                          const char *msg, enum action_on_err onerr)
 {
        int ret = 0, delnum = 0, i;
-       struct ref_update **updates;
-       int *types;
-       struct ref_lock **locks;
        const char **delnames;
+       int n = transaction->nr;
+       struct ref_update **updates = transaction->updates;
 
-       if (!updates_orig || !n)
+       if (!n)
                return 0;
 
        /* Allocate work space */
-       updates = xmalloc(sizeof(*updates) * n);
-       types = xmalloc(sizeof(*types) * n);
-       locks = xcalloc(n, sizeof(*locks));
        delnames = xmalloc(sizeof(*delnames) * n);
 
        /* Copy, sort, and reject duplicate refs */
-       memcpy(updates, updates_orig, sizeof(*updates) * n);
        qsort(updates, n, sizeof(*updates), ref_update_compare);
        ret = ref_update_reject_duplicates(updates, n, onerr);
        if (ret)
@@ -3333,35 +3431,44 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
 
        /* Acquire all locks while verifying old values */
        for (i = 0; i < n; i++) {
-               locks[i] = update_ref_lock(updates[i]->ref_name,
-                                          (updates[i]->have_old ?
-                                           updates[i]->old_sha1 : NULL),
-                                          updates[i]->flags,
-                                          &types[i], onerr);
-               if (!locks[i]) {
+               struct ref_update *update = updates[i];
+
+               update->lock = update_ref_lock(update->refname,
+                                              (update->have_old ?
+                                               update->old_sha1 : NULL),
+                                              update->flags,
+                                              &update->type, onerr);
+               if (!update->lock) {
                        ret = 1;
                        goto cleanup;
                }
        }
 
        /* Perform updates first so live commits remain referenced */
-       for (i = 0; i < n; i++)
-               if (!is_null_sha1(updates[i]->new_sha1)) {
-                       ret = update_ref_write(action,
-                                              updates[i]->ref_name,
-                                              updates[i]->new_sha1,
-                                              locks[i], onerr);
-                       locks[i] = NULL; /* freed by update_ref_write */
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if (!is_null_sha1(update->new_sha1)) {
+                       ret = update_ref_write(msg,
+                                              update->refname,
+                                              update->new_sha1,
+                                              update->lock, onerr);
+                       update->lock = NULL; /* freed by update_ref_write */
                        if (ret)
                                goto cleanup;
                }
+       }
 
        /* Perform deletes now that updates are safely completed */
-       for (i = 0; i < n; i++)
-               if (locks[i]) {
-                       delnames[delnum++] = locks[i]->ref_name;
-                       ret |= delete_ref_loose(locks[i], types[i]);
+       for (i = 0; i < n; i++) {
+               struct ref_update *update = updates[i];
+
+               if (update->lock) {
+                       delnames[delnum++] = update->lock->ref_name;
+                       ret |= delete_ref_loose(update->lock, update->type);
                }
+       }
+
        ret |= repack_without_refs(delnames, delnum);
        for (i = 0; i < delnum; i++)
                unlink_or_warn(git_path("logs/%s", delnames[i]));
@@ -3369,12 +3476,10 @@ int update_refs(const char *action, const struct ref_update **updates_orig,
 
 cleanup:
        for (i = 0; i < n; i++)
-               if (locks[i])
-                       unlock_ref(locks[i]);
-       free(updates);
-       free(types);
-       free(locks);
+               if (updates[i]->lock)
+                       unlock_ref(updates[i]->lock);
        free(delnames);
+       ref_transaction_free(transaction);
        return ret;
 }
 
diff --git a/refs.h b/refs.h
index 87a1a79ad659f3520a22ae14bac1d5082cf2c27b..0f08def21020a166902de169dd46c63c85fdfd0e 100644 (file)
--- a/refs.h
+++ b/refs.h
@@ -10,19 +10,7 @@ struct ref_lock {
        int force_write;
 };
 
-/**
- * Information needed for a single ref update.  Set new_sha1 to the
- * new value or to zero to delete the ref.  To check the old value
- * while locking the ref, set have_old to 1 and set old_sha1 to the
- * value or to zero to ensure the ref does not exist before update.
- */
-struct ref_update {
-       const char *ref_name;
-       unsigned char new_sha1[20];
-       unsigned char old_sha1[20];
-       int flags; /* REF_NODEREF? */
-       int have_old; /* 1 if old_sha1 is valid, 0 otherwise */
-};
+struct ref_transaction;
 
 /*
  * Bit values set in the flags argument passed to each_ref_fn():
@@ -166,7 +154,7 @@ extern void unlock_ref(struct ref_lock *lock);
 extern int write_ref_sha1(struct ref_lock *lock, const unsigned char *sha1, const char *msg);
 
 /** Setup reflog before using. **/
-int log_ref_setup(const char *ref_name, char *logfile, int bufsize);
+int log_ref_setup(const char *refname, char *logfile, int bufsize);
 
 /** Reads log for the value of ref during at_time. **/
 extern int read_ref_at(const char *refname, unsigned long at_time, int cnt,
@@ -214,18 +202,80 @@ extern int rename_ref(const char *oldref, const char *newref, const char *logmsg
  */
 extern int resolve_gitlink_ref(const char *path, const char *refname, unsigned char *sha1);
 
-/** lock a ref and then write its file */
-enum action_on_err { MSG_ON_ERR, DIE_ON_ERR, QUIET_ON_ERR };
+enum action_on_err {
+       UPDATE_REFS_MSG_ON_ERR,
+       UPDATE_REFS_DIE_ON_ERR,
+       UPDATE_REFS_QUIET_ON_ERR
+};
+
+/*
+ * Begin a reference transaction.  The reference transaction must
+ * eventually be commited using ref_transaction_commit() or rolled
+ * back using ref_transaction_rollback().
+ */
+struct ref_transaction *ref_transaction_begin(void);
+
+/*
+ * Roll back a ref_transaction and free all associated data.
+ */
+void ref_transaction_rollback(struct ref_transaction *transaction);
+
+
+/*
+ * The following functions add a reference check or update to a
+ * ref_transaction.  In all of them, refname is the name of the
+ * reference to be affected.  The functions make internal copies of
+ * refname, so the caller retains ownership of the parameter.  flags
+ * can be REF_NODEREF; it is passed to update_ref_lock().
+ */
+
+
+/*
+ * Add a reference update to transaction.  new_sha1 is the value that
+ * the reference should have after the update, or zeros if it should
+ * be deleted.  If have_old is true, then old_sha1 holds the value
+ * that the reference should have had before the update, or zeros if
+ * it must not have existed beforehand.
+ */
+void ref_transaction_update(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1, unsigned char *old_sha1,
+                           int flags, int have_old);
+
+/*
+ * Add a reference creation to transaction.  new_sha1 is the value
+ * that the reference should have after the update; it must not be the
+ * null SHA-1.  It is verified that the reference does not exist
+ * already.
+ */
+void ref_transaction_create(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *new_sha1,
+                           int flags);
+
+/*
+ * Add a reference deletion to transaction.  If have_old is true, then
+ * old_sha1 holds the value that the reference should have had before
+ * the update (which must not be the null SHA-1).
+ */
+void ref_transaction_delete(struct ref_transaction *transaction,
+                           const char *refname,
+                           unsigned char *old_sha1,
+                           int flags, int have_old);
+
+/*
+ * Commit all of the changes that have been queued in transaction, as
+ * atomically as possible.  Return a nonzero value if there is a
+ * problem.  The ref_transaction is freed by this function.
+ */
+int ref_transaction_commit(struct ref_transaction *transaction,
+                          const char *msg, enum action_on_err onerr);
+
+/** Lock a ref and then write its file */
 int update_ref(const char *action, const char *refname,
                const unsigned char *sha1, const unsigned char *oldval,
                int flags, enum action_on_err onerr);
 
-/**
- * Lock all refs and then perform all modifications.
- */
-int update_refs(const char *action, const struct ref_update **updates,
-               int n, enum action_on_err onerr);
-
 extern int parse_hide_refs_config(const char *var, const char *value, const char *);
 extern int ref_is_hidden(const char *);
 
index e130c528fe5a0115c99b1203cfe9dced046cece3..4e2459afc549d5d2985024e8c23bf5fa117d570a 100755 (executable)
@@ -350,22 +350,28 @@ test_expect_success 'stdin fails on unknown command' '
        grep "fatal: unknown command: unknown $a" err
 '
 
-test_expect_success 'stdin fails on badly quoted input' '
+test_expect_success 'stdin fails on unbalanced quotes' '
        echo "create $a \"master" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
        grep "fatal: badly quoted argument: \\\"master" err
 '
 
-test_expect_success 'stdin fails on arguments not separated by space' '
+test_expect_success 'stdin fails on invalid escape' '
+       echo "create $a \"ma\zter\"" >stdin &&
+       test_must_fail git update-ref --stdin <stdin 2>err &&
+       grep "fatal: badly quoted argument: \\\"ma\\\\zter\\\"" err
+'
+
+test_expect_success 'stdin fails on junk after quoted argument' '
        echo "create \"$a\"master" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: expected SP but got: master" err
+       grep "fatal: unexpected character after quoted argument: \\\"$a\\\"master" err
 '
 
 test_expect_success 'stdin fails create with no ref' '
        echo "create " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: create line missing <ref>" err
+       grep "fatal: create: missing <ref>" err
 '
 
 test_expect_success 'stdin fails create with bad ref name' '
@@ -377,19 +383,19 @@ test_expect_success 'stdin fails create with bad ref name' '
 test_expect_success 'stdin fails create with no new value' '
        echo "create $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: create $a missing <newvalue>" err
+       grep "fatal: create $a: missing <newvalue>" err
 '
 
 test_expect_success 'stdin fails create with too many arguments' '
        echo "create $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: create $a has extra input:  $m" err
+       grep "fatal: create $a: extra input:  $m" err
 '
 
 test_expect_success 'stdin fails update with no ref' '
        echo "update " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: update line missing <ref>" err
+       grep "fatal: update: missing <ref>" err
 '
 
 test_expect_success 'stdin fails update with bad ref name' '
@@ -401,19 +407,19 @@ test_expect_success 'stdin fails update with bad ref name' '
 test_expect_success 'stdin fails update with no new value' '
        echo "update $a" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: update $a missing <newvalue>" err
+       grep "fatal: update $a: missing <newvalue>" err
 '
 
 test_expect_success 'stdin fails update with too many arguments' '
        echo "update $a $m $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: update $a has extra input:  $m" err
+       grep "fatal: update $a: extra input:  $m" err
 '
 
 test_expect_success 'stdin fails delete with no ref' '
        echo "delete " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: delete line missing <ref>" err
+       grep "fatal: delete: missing <ref>" err
 '
 
 test_expect_success 'stdin fails delete with bad ref name' '
@@ -425,13 +431,13 @@ test_expect_success 'stdin fails delete with bad ref name' '
 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 &&
-       grep "fatal: delete $a has extra input:  $m" err
+       grep "fatal: delete $a: extra input:  $m" err
 '
 
 test_expect_success 'stdin fails verify with too many arguments' '
        echo "verify $a $m $m" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: verify $a has extra input:  $m" err
+       grep "fatal: verify $a: extra input:  $m" err
 '
 
 test_expect_success 'stdin fails option with unknown name' '
@@ -458,6 +464,24 @@ test_expect_success 'stdin create ref works' '
        test_cmp expect actual
 '
 
+test_expect_success 'stdin succeeds with quoted argument' '
+       git update-ref -d $a &&
+       echo "create $a \"$m\"" >stdin &&
+       git update-ref --stdin <stdin &&
+       git rev-parse $m >expect &&
+       git rev-parse $a >actual &&
+       test_cmp expect actual
+'
+
+test_expect_success 'stdin succeeds with escaped character' '
+       git update-ref -d $a &&
+       echo "create $a \"ma\\163ter\"" >stdin &&
+       git update-ref --stdin <stdin &&
+       git rev-parse $m >expect &&
+       git rev-parse $a >actual &&
+       test_cmp expect actual
+'
+
 test_expect_success 'stdin update ref creates with zero old value' '
        echo "update $b $m $Z" >stdin &&
        git update-ref --stdin <stdin &&
@@ -494,21 +518,21 @@ test_expect_success 'stdin update ref fails with wrong old value' '
 test_expect_success 'stdin update ref fails with bad old value' '
        echo "update $c $m does-not-exist" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+       grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
 test_expect_success 'stdin create ref fails with bad new value' '
        echo "create $c does-not-exist" >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+       grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
 test_expect_success 'stdin create ref fails with zero new value' '
        echo "create $c " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: create $c given zero new value" err &&
+       grep "fatal: create $c: zero <newvalue>" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
@@ -532,7 +556,7 @@ test_expect_success 'stdin delete ref fails with wrong old value' '
 test_expect_success 'stdin delete ref fails with zero old value' '
        echo "delete $a " >stdin &&
        test_must_fail git update-ref --stdin <stdin 2>err &&
-       grep "fatal: delete $a given zero old value" err &&
+       grep "fatal: delete $a: zero <oldvalue>" err &&
        git rev-parse $m >expect &&
        git rev-parse $a >actual &&
        test_cmp expect actual
@@ -673,7 +697,7 @@ test_expect_success 'stdin -z fails on unknown command' '
 test_expect_success 'stdin -z fails create with no ref' '
        printf $F "create " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: create line missing <ref>" err
+       grep "fatal: create: missing <ref>" err
 '
 
 test_expect_success 'stdin -z fails create with bad ref name' '
@@ -685,7 +709,7 @@ test_expect_success 'stdin -z fails create with bad ref name' '
 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 &&
-       grep "fatal: create $a missing <newvalue>" err
+       grep "fatal: create $a: unexpected end of input when reading <newvalue>" err
 '
 
 test_expect_success 'stdin -z fails create with too many arguments' '
@@ -697,25 +721,39 @@ test_expect_success 'stdin -z fails create with too many arguments' '
 test_expect_success 'stdin -z fails update with no ref' '
        printf $F "update " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: update line missing <ref>" err
+       grep "fatal: update: missing <ref>" err
+'
+
+test_expect_success 'stdin -z fails update with too few args' '
+       printf $F "update $a" "$m" >stdin &&
+       test_must_fail git update-ref -z --stdin <stdin 2>err &&
+       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 &&
+       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 &&
+       git update-ref -z --stdin <stdin 2>err &&
+       grep "warning: update $a: missing <newvalue>, treating as zero" err &&
+       test_must_fail git rev-parse --verify -q $a
+'
+
 test_expect_success 'stdin -z fails update with no new value' '
        printf $F "update $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: update $a missing <newvalue>" err
+       grep "fatal: update $a: unexpected end of input when reading <newvalue>" err
 '
 
 test_expect_success 'stdin -z fails update with no old value' '
        printf $F "update $a" "$m" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: update $a missing \\[<oldvalue>\\] NUL" err
+       grep "fatal: update $a: unexpected end of input when reading <oldvalue>" err
 '
 
 test_expect_success 'stdin -z fails update with too many arguments' '
@@ -727,7 +765,7 @@ test_expect_success 'stdin -z fails update with too many arguments' '
 test_expect_success 'stdin -z fails delete with no ref' '
        printf $F "delete " >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: delete line missing <ref>" err
+       grep "fatal: delete: missing <ref>" err
 '
 
 test_expect_success 'stdin -z fails delete with bad ref name' '
@@ -739,7 +777,7 @@ test_expect_success 'stdin -z fails delete with bad ref name' '
 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 &&
-       grep "fatal: delete $a missing \\[<oldvalue>\\] NUL" err
+       grep "fatal: delete $a: unexpected end of input when reading <oldvalue>" err
 '
 
 test_expect_success 'stdin -z fails delete with too many arguments' '
@@ -757,7 +795,7 @@ test_expect_success 'stdin -z fails verify with too many arguments' '
 test_expect_success 'stdin -z fails verify with no old value' '
        printf $F "verify $a" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: verify $a missing \\[<oldvalue>\\] NUL" err
+       grep "fatal: verify $a: unexpected end of input when reading <oldvalue>" err
 '
 
 test_expect_success 'stdin -z fails option with unknown name' '
@@ -816,7 +854,7 @@ test_expect_success 'stdin -z update ref fails with wrong old value' '
 test_expect_success 'stdin -z update ref fails with bad old value' '
        printf $F "update $c" "$m" "does-not-exist" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: invalid old value for ref $c: does-not-exist" err &&
+       grep "fatal: update $c: invalid <oldvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
@@ -834,14 +872,14 @@ test_expect_success 'stdin -z create ref fails with bad new value' '
        git update-ref -d "$c" &&
        printf $F "create $c" "does-not-exist" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: invalid new value for ref $c: does-not-exist" err &&
+       grep "fatal: create $c: invalid <newvalue>: does-not-exist" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
-test_expect_success 'stdin -z create ref fails with zero new value' '
+test_expect_success 'stdin -z create ref fails with empty new value' '
        printf $F "create $c" "" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: create $c given zero new value" err &&
+       grep "fatal: create $c: missing <newvalue>" err &&
        test_must_fail git rev-parse --verify -q $c
 '
 
@@ -865,7 +903,7 @@ test_expect_success 'stdin -z delete ref fails with wrong old value' '
 test_expect_success 'stdin -z delete ref fails with zero old value' '
        printf $F "delete $a" "$Z" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
-       grep "fatal: delete $a given zero old value" err &&
+       grep "fatal: delete $a: zero <oldvalue>" err &&
        git rev-parse $m >expect &&
        git rev-parse $a >actual &&
        test_cmp expect actual
@@ -923,7 +961,7 @@ test_expect_success 'stdin -z update refs works with identity updates' '
 
 test_expect_success 'stdin -z update refs fails with wrong old value' '
        git update-ref $c $m &&
-       printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "" "$Z" >stdin &&
+       printf $F "update $a" "$m" "$m" "update $b" "$m" "$m" "update $c" "$m" "$Z" >stdin &&
        test_must_fail git update-ref -z --stdin <stdin 2>err &&
        grep "fatal: Cannot lock the ref '"'"'$c'"'"'" err &&
        git rev-parse $m >expect &&