Imported Upstream version 2.30.0
[platform/upstream/git.git] / builtin / update-ref.c
index 5c208bb..6029a80 100644 (file)
 #include "cache.h"
+#include "config.h"
 #include "refs.h"
 #include "builtin.h"
 #include "parse-options.h"
 #include "quote.h"
-#include "argv-array.h"
+#include "strvec.h"
 
 static const char * const git_update_ref_usage[] = {
-       N_("git update-ref [options] -d <refname> [<oldval>]"),
-       N_("git update-ref [options]    <refname> <newval> [<oldval>]"),
-       N_("git update-ref [options] --stdin [-z]"),
+       N_("git update-ref [<options>] -d <refname> [<old-val>]"),
+       N_("git update-ref [<options>]    <refname> <new-val> [<old-val>]"),
+       N_("git update-ref [<options>] --stdin [-z]"),
        NULL
 };
 
-static int updates_alloc;
-static int updates_count;
-static const struct ref_update **updates;
-
 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;
-}
-
+static unsigned int update_flags;
+static unsigned int default_flags;
+static unsigned create_reflog_flag;
+static const char *msg;
+
+/*
+ * 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(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_oid(const char **next, const char *end,
+                         struct object_id *oid,
+                         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 == end)
+               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_oid(arg.buf, oid))
+                               goto invalid;
+               } else {
+                       /* Without -z, an empty value means all zeros: */
+                       oidclr(oid);
+               }
        } 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 == end)
+                       goto eof;
+               strbuf_addstr(&arg, *next);
+               *next += arg.len;
+
+               if (arg.len) {
+                       if (get_oid(arg.buf, oid))
+                               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);
+                       oidclr(oid);
+               } else {
+                       /*
+                        * With -z, an empty non-required value means
+                        * unspecified:
+                        */
+                       ret = 1;
+               }
        }
-       return next;
-}
 
-static void parse_cmd_update(const char *next)
-{
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf newvalue = STRBUF_INIT;
-       struct strbuf oldvalue = STRBUF_INIT;
-       struct ref_update *update;
+       strbuf_release(&arg);
 
-       update = update_alloc();
+       return ret;
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("update line missing <ref>");
+ invalid:
+       die(flags & PARSE_SHA1_OLD ?
+           "%s %s: invalid <oldvalue>: %s" :
+           "%s %s: invalid <newvalue>: %s",
+           command, refname, arg.buf);
 
-       if ((next = parse_next_arg(next, &newvalue)) != NULL)
-               update_store_new_sha1(update, newvalue.buf);
-       else
-               die("update %s missing <newvalue>", ref.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);
+}
 
-       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 && *next)
-               die("update %s has extra input: %s", ref.buf, 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 void parse_cmd_update(struct ref_transaction *transaction,
+                            const char *next, const char *end)
+{
+       struct strbuf err = STRBUF_INIT;
+       char *refname;
+       struct object_id new_oid, old_oid;
+       int have_old;
+
+       refname = parse_refname(&next);
+       if (!refname)
+               die("update: missing <ref>");
+
+       if (parse_next_oid(&next, end, &new_oid, "update", refname,
+                          PARSE_SHA1_ALLOW_EMPTY))
+               die("update %s: missing <newvalue>", refname);
+
+       have_old = !parse_next_oid(&next, end, &old_oid, "update", refname,
+                                  PARSE_SHA1_OLD);
+
+       if (*next != line_termination)
+               die("update %s: extra input: %s", refname, next);
+
+       if (ref_transaction_update(transaction, refname,
+                                  &new_oid, have_old ? &old_oid : NULL,
+                                  update_flags | create_reflog_flag,
+                                  msg, &err))
+               die("%s", err.buf);
+
+       update_flags = default_flags;
+       free(refname);
+       strbuf_release(&err);
 }
 
-static void parse_cmd_create(const char *next)
+static void parse_cmd_create(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf newvalue = STRBUF_INIT;
-       struct ref_update *update;
+       struct strbuf err = STRBUF_INIT;
+       char *refname;
+       struct object_id new_oid;
 
-       update = update_alloc();
-       update->have_old = 1;
+       refname = parse_refname(&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_oid(&next, end, &new_oid, "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_oid(&new_oid))
+               die("create %s: zero <newvalue>", refname);
+
+       if (*next != line_termination)
+               die("create %s: extra input: %s", refname, next);
+
+       if (ref_transaction_create(transaction, refname, &new_oid,
+                                  update_flags | create_reflog_flag,
+                                  msg, &err))
+               die("%s", err.buf);
 
-       if (next && *next)
-               die("create %s has extra input: %s", ref.buf, next);
+       update_flags = default_flags;
+       free(refname);
+       strbuf_release(&err);
 }
 
-static void parse_cmd_delete(const char *next)
+static void parse_cmd_delete(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf oldvalue = STRBUF_INIT;
-       struct ref_update *update;
-
-       update = update_alloc();
+       struct strbuf err = STRBUF_INIT;
+       char *refname;
+       struct object_id old_oid;
+       int have_old;
+
+       refname = parse_refname(&next);
+       if (!refname)
+               die("delete: missing <ref>");
+
+       if (parse_next_oid(&next, end, &old_oid, "delete", refname,
+                          PARSE_SHA1_OLD)) {
+               have_old = 0;
+       } else {
+               if (is_null_oid(&old_oid))
+                       die("delete %s: zero <oldvalue>", refname);
+               have_old = 1;
+       }
 
-       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 (*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);
+       if (ref_transaction_delete(transaction, refname,
+                                  have_old ? &old_oid : NULL,
+                                  update_flags, msg, &err))
+               die("%s", err.buf);
 
-       if (next && *next)
-               die("delete %s has extra input: %s", ref.buf, next);
+       update_flags = default_flags;
+       free(refname);
+       strbuf_release(&err);
 }
 
-static void parse_cmd_verify(const char *next)
+static void parse_cmd_verify(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
-       struct strbuf ref = STRBUF_INIT;
-       struct strbuf value = STRBUF_INIT;
-       struct ref_update *update;
+       struct strbuf err = STRBUF_INIT;
+       char *refname;
+       struct object_id old_oid;
 
-       update = update_alloc();
+       refname = parse_refname(&next);
+       if (!refname)
+               die("verify: missing <ref>");
 
-       if ((next = parse_first_arg(next, &ref)) != NULL && ref.buf[0])
-               update_store_ref_name(update, ref.buf);
-       else
-               die("verify line missing <ref>");
+       if (parse_next_oid(&next, end, &old_oid, "verify", refname,
+                          PARSE_SHA1_OLD))
+               oidclr(&old_oid);
+
+       if (*next != line_termination)
+               die("verify %s: extra input: %s", refname, next);
 
-       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);
+       if (ref_transaction_verify(transaction, refname, &old_oid,
+                                  update_flags, &err))
+               die("%s", err.buf);
 
-       if (next && *next)
-               die("verify %s has extra input: %s", ref.buf, next);
+       update_flags = default_flags;
+       free(refname);
+       strbuf_release(&err);
 }
 
-static void parse_cmd_option(const char *next)
+static void parse_cmd_option(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
-       if (!strcmp(next, "no-deref"))
-               update_flags |= REF_NODEREF;
+       const char *rest;
+       if (skip_prefix(next, "no-deref", &rest) && *rest == line_termination)
+               update_flags |= REF_NO_DEREF;
        else
                die("option unknown: %s", next);
 }
 
+static void parse_cmd_start(struct ref_transaction *transaction,
+                           const char *next, const char *end)
+{
+       if (*next != line_termination)
+               die("start: extra input: %s", next);
+       puts("start: ok");
+}
+
+static void parse_cmd_prepare(struct ref_transaction *transaction,
+                             const char *next, const char *end)
+{
+       struct strbuf error = STRBUF_INIT;
+       if (*next != line_termination)
+               die("prepare: extra input: %s", next);
+       if (ref_transaction_prepare(transaction, &error))
+               die("prepare: %s", error.buf);
+       puts("prepare: ok");
+}
+
+static void parse_cmd_abort(struct ref_transaction *transaction,
+                           const char *next, const char *end)
+{
+       struct strbuf error = STRBUF_INIT;
+       if (*next != line_termination)
+               die("abort: extra input: %s", next);
+       if (ref_transaction_abort(transaction, &error))
+               die("abort: %s", error.buf);
+       puts("abort: ok");
+}
+
+static void parse_cmd_commit(struct ref_transaction *transaction,
+                            const char *next, const char *end)
+{
+       struct strbuf error = STRBUF_INIT;
+       if (*next != line_termination)
+               die("commit: extra input: %s", next);
+       if (ref_transaction_commit(transaction, &error))
+               die("commit: %s", error.buf);
+       puts("commit: ok");
+       ref_transaction_free(transaction);
+}
+
+enum update_refs_state {
+       /* Non-transactional state open for updates. */
+       UPDATE_REFS_OPEN,
+       /* A transaction has been started. */
+       UPDATE_REFS_STARTED,
+       /* References are locked and ready for commit */
+       UPDATE_REFS_PREPARED,
+       /* Transaction has been committed or closed. */
+       UPDATE_REFS_CLOSED,
+};
+
+static const struct parse_cmd {
+       const char *prefix;
+       void (*fn)(struct ref_transaction *, const char *, const char *);
+       unsigned args;
+       enum update_refs_state state;
+} command[] = {
+       { "update",  parse_cmd_update,  3, UPDATE_REFS_OPEN },
+       { "create",  parse_cmd_create,  2, UPDATE_REFS_OPEN },
+       { "delete",  parse_cmd_delete,  2, UPDATE_REFS_OPEN },
+       { "verify",  parse_cmd_verify,  2, UPDATE_REFS_OPEN },
+       { "option",  parse_cmd_option,  1, UPDATE_REFS_OPEN },
+       { "start",   parse_cmd_start,   0, UPDATE_REFS_STARTED },
+       { "prepare", parse_cmd_prepare, 0, UPDATE_REFS_PREPARED },
+       { "abort",   parse_cmd_abort,   0, UPDATE_REFS_CLOSED },
+       { "commit",  parse_cmd_commit,  0, UPDATE_REFS_CLOSED },
+};
+
 static void update_refs_stdin(void)
 {
-       struct strbuf cmd = STRBUF_INIT;
+       struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
+       enum update_refs_state state = UPDATE_REFS_OPEN;
+       struct ref_transaction *transaction;
+       int i, j;
+
+       transaction = ref_transaction_begin(&err);
+       if (!transaction)
+               die("%s", err.buf);
 
        /* Read each line dispatch its command */
-       while (strbuf_getline(&cmd, stdin, line_termination) != EOF)
-               if (!cmd.buf[0])
+       while (!strbuf_getwholeline(&input, stdin, line_termination)) {
+               const struct parse_cmd *cmd = NULL;
+
+               if (*input.buf == 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
-                       die("unknown command: %s", cmd.buf);
-
-       strbuf_release(&cmd);
+               else if (isspace(*input.buf))
+                       die("whitespace before command: %s", input.buf);
+
+               for (i = 0; i < ARRAY_SIZE(command); i++) {
+                       const char *prefix = command[i].prefix;
+                       char c;
+
+                       if (!starts_with(input.buf, prefix))
+                               continue;
+
+                       /*
+                        * If the command has arguments, verify that it's
+                        * followed by a space. Otherwise, it shall be followed
+                        * by a line terminator.
+                        */
+                       c = command[i].args ? ' ' : line_termination;
+                       if (input.buf[strlen(prefix)] != c)
+                               continue;
+
+                       cmd = &command[i];
+                       break;
+               }
+               if (!cmd)
+                       die("unknown command: %s", input.buf);
+
+               /*
+                * Read additional arguments if NUL-terminated. Do not raise an
+                * error in case there is an early EOF to let the command
+                * handle missing arguments with a proper error message.
+                */
+               for (j = 1; line_termination == '\0' && j < cmd->args; j++)
+                       if (strbuf_appendwholeline(&input, stdin, line_termination))
+                               break;
+
+               switch (state) {
+               case UPDATE_REFS_OPEN:
+               case UPDATE_REFS_STARTED:
+                       if (state == UPDATE_REFS_STARTED && cmd->state == UPDATE_REFS_STARTED)
+                               die("cannot restart ongoing transaction");
+                       /* Do not downgrade a transaction to a non-transaction. */
+                       if (cmd->state >= state)
+                               state = cmd->state;
+                       break;
+               case UPDATE_REFS_PREPARED:
+                       if (cmd->state != UPDATE_REFS_CLOSED)
+                               die("prepared transactions can only be closed");
+                       state = cmd->state;
+                       break;
+               case UPDATE_REFS_CLOSED:
+                       if (cmd->state != UPDATE_REFS_STARTED)
+                               die("transaction is closed");
+
+                       /*
+                        * Open a new transaction if we're currently closed and
+                        * get a "start".
+                        */
+                       state = cmd->state;
+                       transaction = ref_transaction_begin(&err);
+                       if (!transaction)
+                               die("%s", err.buf);
+
+                       break;
+               }
+
+               cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
+                       input.buf + input.len);
+       }
+
+       switch (state) {
+       case UPDATE_REFS_OPEN:
+               /* Commit by default if no transaction was requested. */
+               if (ref_transaction_commit(transaction, &err))
+                       die("%s", err.buf);
+               ref_transaction_free(transaction);
+               break;
+       case UPDATE_REFS_STARTED:
+       case UPDATE_REFS_PREPARED:
+               /* If using a transaction, we want to abort it. */
+               if (ref_transaction_abort(transaction, &err))
+                       die("%s", err.buf);
+               break;
+       case UPDATE_REFS_CLOSED:
+               /* Otherwise no need to do anything, the transaction was closed already. */
+               break;
+       }
+
+       strbuf_release(&err);
+       strbuf_release(&input);
 }
 
 int cmd_update_ref(int argc, const char **argv, const char *prefix)
 {
-       const char *refname, *oldval, *msg = NULL;
-       unsigned char sha1[20], oldsha1[20];
-       int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0, flags = 0;
+       const char *refname, *oldval;
+       struct object_id oid, oldoid;
+       int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
+       int create_reflog = 0;
        struct option options[] = {
                OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
                OPT_BOOL('d', NULL, &delete, N_("delete the reference")),
@@ -258,6 +502,7 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                                        N_("update <refname> not the one it points to")),
                OPT_BOOL('z', NULL, &end_null, N_("stdin has NUL-terminated arguments")),
                OPT_BOOL( 0 , "stdin", &read_stdin, N_("read updates from stdin")),
+               OPT_BOOL( 0 , "create-reflog", &create_reflog, N_("create a reflog")),
                OPT_END(),
        };
 
@@ -267,13 +512,20 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
        if (msg && !*msg)
                die("Refusing to perform update with empty message.");
 
+       create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
+
+       if (no_deref) {
+               default_flags = REF_NO_DEREF;
+               update_flags = default_flags;
+       }
+
        if (read_stdin) {
-               if (delete || no_deref || argc > 0)
+               if (delete || 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);
+               return 0;
        }
 
        if (end_null)
@@ -291,19 +543,31 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                refname = argv[0];
                value = argv[1];
                oldval = argv[2];
-               if (get_sha1(value, sha1))
+               if (get_oid(value, &oid))
                        die("%s: not a valid SHA1", value);
        }
 
-       hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */
-       if (oldval && *oldval && get_sha1(oldval, oldsha1))
-               die("%s: not a valid old SHA1", oldval);
+       if (oldval) {
+               if (!*oldval)
+                       /*
+                        * The empty string implies that the reference
+                        * must not already exist:
+                        */
+                       oidclr(&oldoid);
+               else if (get_oid(oldval, &oldoid))
+                       die("%s: not a valid old SHA1", oldval);
+       }
 
-       if (no_deref)
-               flags = REF_NODEREF;
        if (delete)
-               return delete_ref(refname, oldval ? oldsha1 : NULL, flags);
+               /*
+                * For purposes of backwards compatibility, we treat
+                * NULL_SHA1 as "don't care" here:
+                */
+               return delete_ref(msg, refname,
+                                 (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
+                                 default_flags);
        else
-               return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 flags, DIE_ON_ERR);
+               return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL,
+                                 default_flags | create_reflog_flag,
+                                 UPDATE_REFS_DIE_ON_ERR);
 }