Imported Upstream version 2.30.0
[platform/upstream/git.git] / builtin / update-ref.c
index 40ccfc1..6029a80 100644 (file)
@@ -4,7 +4,7 @@
 #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> [<old-val>]"),
@@ -14,7 +14,8 @@ static const char * const git_update_ref_usage[] = {
 };
 
 static char line_termination = '\n';
-static int update_flags;
+static unsigned int update_flags;
+static unsigned int default_flags;
 static unsigned create_reflog_flag;
 static const char *msg;
 
@@ -49,7 +50,7 @@ static const char *parse_arg(const char *next, struct strbuf *arg)
  * the argument.  Die if C-quoting is malformed or the reference name
  * is invalid.
  */
-static char *parse_refname(struct strbuf *input, const char **next)
+static char *parse_refname(const char **next)
 {
        struct strbuf ref = STRBUF_INIT;
 
@@ -94,15 +95,15 @@ static char *parse_refname(struct strbuf *input, const char **next)
  * 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)
+static int parse_next_oid(const char **next, const char *end,
+                         struct object_id *oid,
+                         const char *command, const char *refname,
+                         int flags)
 {
        struct strbuf arg = STRBUF_INIT;
        int ret = 0;
 
-       if (*next == input->buf + input->len)
+       if (*next == end)
                goto eof;
 
        if (line_termination) {
@@ -115,11 +116,11 @@ static int parse_next_sha1(struct strbuf *input, const char **next,
                (*next)++;
                *next = parse_arg(*next, &arg);
                if (arg.len) {
-                       if (get_sha1(arg.buf, sha1))
+                       if (get_oid(arg.buf, oid))
                                goto invalid;
                } else {
                        /* Without -z, an empty value means all zeros: */
-                       hashclr(sha1);
+                       oidclr(oid);
                }
        } else {
                /* With -z, read the next NUL-terminated line */
@@ -127,19 +128,19 @@ static int parse_next_sha1(struct strbuf *input, const char **next,
                        die("%s %s: expected NUL but got: %s",
                            command, refname, *next);
                (*next)++;
-               if (*next == input->buf + input->len)
+               if (*next == end)
                        goto eof;
                strbuf_addstr(&arg, *next);
                *next += arg.len;
 
                if (arg.len) {
-                       if (get_sha1(arg.buf, sha1))
+                       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);
-                       hashclr(sha1);
+                       oidclr(oid);
                } else {
                        /*
                         * With -z, an empty non-required value means
@@ -177,91 +178,86 @@ static int parse_next_sha1(struct strbuf *input, const char **next,
  * depending on how line_termination is set.
  */
 
-static const char *parse_cmd_update(struct ref_transaction *transaction,
-                                   struct strbuf *input, const char *next)
+static void parse_cmd_update(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
        struct strbuf err = STRBUF_INIT;
        char *refname;
-       unsigned char new_sha1[20];
-       unsigned char old_sha1[20];
+       struct object_id new_oid, old_oid;
        int have_old;
 
-       refname = parse_refname(input, &next);
+       refname = parse_refname(&next);
        if (!refname)
                die("update: missing <ref>");
 
-       if (parse_next_sha1(input, &next, new_sha1, "update", refname,
-                           PARSE_SHA1_ALLOW_EMPTY))
+       if (parse_next_oid(&next, end, &new_oid, "update", refname,
+                          PARSE_SHA1_ALLOW_EMPTY))
                die("update %s: missing <newvalue>", refname);
 
-       have_old = !parse_next_sha1(input, &next, old_sha1, "update", refname,
-                                   PARSE_SHA1_OLD);
+       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_sha1, have_old ? old_sha1 : NULL,
+                                  &new_oid, have_old ? &old_oid : NULL,
                                   update_flags | create_reflog_flag,
                                   msg, &err))
                die("%s", err.buf);
 
-       update_flags = 0;
+       update_flags = default_flags;
        free(refname);
        strbuf_release(&err);
-
-       return next;
 }
 
-static const char *parse_cmd_create(struct ref_transaction *transaction,
-                                   struct strbuf *input, const char *next)
+static void parse_cmd_create(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
        struct strbuf err = STRBUF_INIT;
        char *refname;
-       unsigned char new_sha1[20];
+       struct object_id new_oid;
 
-       refname = parse_refname(input, &next);
+       refname = parse_refname(&next);
        if (!refname)
                die("create: missing <ref>");
 
-       if (parse_next_sha1(input, &next, new_sha1, "create", refname, 0))
+       if (parse_next_oid(&next, end, &new_oid, "create", refname, 0))
                die("create %s: missing <newvalue>", refname);
 
-       if (is_null_sha1(new_sha1))
+       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_sha1,
+       if (ref_transaction_create(transaction, refname, &new_oid,
                                   update_flags | create_reflog_flag,
                                   msg, &err))
                die("%s", err.buf);
 
-       update_flags = 0;
+       update_flags = default_flags;
        free(refname);
        strbuf_release(&err);
-
-       return next;
 }
 
-static const char *parse_cmd_delete(struct ref_transaction *transaction,
-                                   struct strbuf *input, const char *next)
+static void parse_cmd_delete(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
        struct strbuf err = STRBUF_INIT;
        char *refname;
-       unsigned char old_sha1[20];
+       struct object_id old_oid;
        int have_old;
 
-       refname = parse_refname(input, &next);
+       refname = parse_refname(&next);
        if (!refname)
                die("delete: missing <ref>");
 
-       if (parse_next_sha1(input, &next, old_sha1, "delete", refname,
-                           PARSE_SHA1_OLD)) {
+       if (parse_next_oid(&next, end, &old_oid, "delete", refname,
+                          PARSE_SHA1_OLD)) {
                have_old = 0;
        } else {
-               if (is_null_sha1(old_sha1))
+               if (is_null_oid(&old_oid))
                        die("delete %s: zero <oldvalue>", refname);
                have_old = 1;
        }
@@ -270,94 +266,234 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,
                die("delete %s: extra input: %s", refname, next);
 
        if (ref_transaction_delete(transaction, refname,
-                                  have_old ? old_sha1 : NULL,
+                                  have_old ? &old_oid : NULL,
                                   update_flags, msg, &err))
                die("%s", err.buf);
 
-       update_flags = 0;
+       update_flags = default_flags;
        free(refname);
        strbuf_release(&err);
-
-       return next;
 }
 
-static const char *parse_cmd_verify(struct ref_transaction *transaction,
-                                   struct strbuf *input, const char *next)
+static void parse_cmd_verify(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
        struct strbuf err = STRBUF_INIT;
        char *refname;
-       unsigned char old_sha1[20];
+       struct object_id old_oid;
 
-       refname = parse_refname(input, &next);
+       refname = parse_refname(&next);
        if (!refname)
                die("verify: missing <ref>");
 
-       if (parse_next_sha1(input, &next, old_sha1, "verify", refname,
-                           PARSE_SHA1_OLD))
-               hashclr(old_sha1);
+       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 (ref_transaction_verify(transaction, refname, old_sha1,
+       if (ref_transaction_verify(transaction, refname, &old_oid,
                                   update_flags, &err))
                die("%s", err.buf);
 
-       update_flags = 0;
+       update_flags = default_flags;
        free(refname);
        strbuf_release(&err);
-
-       return next;
 }
 
-static const char *parse_cmd_option(struct strbuf *input, const char *next)
+static void parse_cmd_option(struct ref_transaction *transaction,
+                            const char *next, const char *end)
 {
-       if (!strncmp(next, "no-deref", 8) && next[8] == line_termination)
-               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);
-       return next + 8;
 }
 
-static void update_refs_stdin(struct ref_transaction *transaction)
+static void parse_cmd_start(struct ref_transaction *transaction,
+                           const char *next, const char *end)
 {
-       struct strbuf input = STRBUF_INIT;
-       const char *next;
+       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 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);
 
-       if (strbuf_read(&input, 0, 1000) < 0)
-               die_errno("could not read from stdin");
-       next = input.buf;
        /* Read each line dispatch its command */
-       while (next < input.buf + input.len) {
-               if (*next == line_termination)
+       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(*next))
-                       die("whitespace before command: %s", next);
-               else if (starts_with(next, "update "))
-                       next = parse_cmd_update(transaction, &input, next + 7);
-               else if (starts_with(next, "create "))
-                       next = parse_cmd_create(transaction, &input, next + 7);
-               else if (starts_with(next, "delete "))
-                       next = parse_cmd_delete(transaction, &input, next + 7);
-               else if (starts_with(next, "verify "))
-                       next = parse_cmd_verify(transaction, &input, next + 7);
-               else if (starts_with(next, "option "))
-                       next = parse_cmd_option(&input, next + 7);
-               else
-                       die("unknown command: %s", next);
-
-               next++;
+               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;
-       unsigned char sha1[20], oldsha1[20];
+       struct object_id oid, oldoid;
        int delete = 0, no_deref = 0, read_stdin = 0, end_null = 0;
-       unsigned int flags = 0;
        int create_reflog = 0;
        struct option options[] = {
                OPT_STRING( 'm', NULL, &msg, N_("reason"), N_("reason of the update")),
@@ -378,22 +514,17 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
 
        create_reflog_flag = create_reflog ? REF_FORCE_CREATE_REFLOG : 0;
 
-       if (read_stdin) {
-               struct strbuf err = STRBUF_INIT;
-               struct ref_transaction *transaction;
+       if (no_deref) {
+               default_flags = REF_NO_DEREF;
+               update_flags = default_flags;
+       }
 
-               transaction = ref_transaction_begin(&err);
-               if (!transaction)
-                       die("%s", err.buf);
-               if (delete || no_deref || argc > 0)
+       if (read_stdin) {
+               if (delete || argc > 0)
                        usage_with_options(git_update_ref_usage, options);
                if (end_null)
                        line_termination = '\0';
-               update_refs_stdin(transaction);
-               if (ref_transaction_commit(transaction, &err))
-                       die("%s", err.buf);
-               ref_transaction_free(transaction);
-               strbuf_release(&err);
+               update_refs_stdin();
                return 0;
        }
 
@@ -412,7 +543,7 @@ 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);
        }
 
@@ -422,23 +553,21 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix)
                         * The empty string implies that the reference
                         * must not already exist:
                         */
-                       hashclr(oldsha1);
-               else if (get_sha1(oldval, oldsha1))
+                       oidclr(&oldoid);
+               else if (get_oid(oldval, &oldoid))
                        die("%s: not a valid old SHA1", oldval);
        }
 
-       if (no_deref)
-               flags = REF_NODEREF;
        if (delete)
                /*
                 * For purposes of backwards compatibility, we treat
                 * NULL_SHA1 as "don't care" here:
                 */
                return delete_ref(msg, refname,
-                                 (oldval && !is_null_sha1(oldsha1)) ? oldsha1 : NULL,
-                                 flags);
+                                 (oldval && !is_null_oid(&oldoid)) ? &oldoid : NULL,
+                                 default_flags);
        else
-               return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL,
-                                 flags | create_reflog_flag,
+               return update_ref(msg, refname, &oid, oldval ? &oldoid : NULL,
+                                 default_flags | create_reflog_flag,
                                  UPDATE_REFS_DIE_ON_ERR);
 }