git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 8/9] update-ref: read commands in a line-wise fashion

From
Patrick Steinhardt <ps@pks.im>
Date
Apr 2, 2020, 07:09 UTC
Message-ID
<02ff6b7337cd821c74f2221c2c567e0105c01e0d.1585811014.git.ps@pks.im>
In-Reply-To
<cover.1585811013.git.ps@pks.im>

The git-update-ref(1) supports a `--stdin` mode that allows it to read all reference updates from standard input. This is mainly used to allow for atomic reference updates that are all or nothing, so that either all references will get updated or none.

Currently, git-update-ref(1) reads all commands as a single block of up to 1000 characters and only starts processing after stdin gets closed. This is less flexible than one might wish for, as it doesn't really allow for longer-lived transactions and doesn't allow any verification without committing everything. E.g. one may imagine the following exchange:

    > start
    < start: ok
    > update refs/heads/master $NEWOID1 $OLDOID1
    > update refs/heads/branch $NEWOID2 $OLDOID2
    > prepare
    < prepare: ok
    > commit
    < commit: ok

When reading all input as a whole block, the above interactive protocol is obviously impossible to achieve. But by converting the command to read commands linewise, we can make it more interactive than before.

Obviously, the linewise interface is only a first step in making git-update-ref(1) work in a more transaction-oriented way. Missing is most importantly support for transactional commands that manage the current transaction.

Signed-off-by: Patrick Steinhardt <ps@pks.im>
---
 builtin/update-ref.c | 85 +++++++++++++++++++++++---------------------
 1 file changed, 45 insertions(+), 40 deletions(-)
diff --git a/builtin/update-ref.c b/builtin/update-ref.c
index 0f34b68904..348407b896 100644
--- a/builtin/update-ref.c
+++ b/builtin/update-ref.c
@@ -178,8 +178,8 @@ static int parse_next_oid(const char **next, const char *end,
  * depending on how line_termination is set.
  */
 
-static const char *parse_cmd_update(struct ref_transaction *transaction,
-				    const char *next, const char *end)
+static void parse_cmd_update(struct ref_transaction *transaction,
+			     const char *next, const char *end)
 {
 	struct strbuf err = STRBUF_INIT;
 	char *refname;
@@ -209,12 +209,10 @@ static const char *parse_cmd_update(struct ref_transaction *transaction,
 	update_flags = default_flags;
 	free(refname);
 	strbuf_release(&err);
-
-	return next;
 }
 
-static const char *parse_cmd_create(struct ref_transaction *transaction,
-				    const char *next, const char *end)
+static void parse_cmd_create(struct ref_transaction *transaction,
+			     const char *next, const char *end)
 {
 	struct strbuf err = STRBUF_INIT;
 	char *refname;
@@ -241,12 +239,10 @@ static const char *parse_cmd_create(struct ref_transaction *transaction,
 	update_flags = default_flags;
 	free(refname);
 	strbuf_release(&err);
-
-	return next;
 }
 
-static const char *parse_cmd_delete(struct ref_transaction *transaction,
-				    const char *next, const char *end)
+static void parse_cmd_delete(struct ref_transaction *transaction,
+			     const char *next, const char *end)
 {
 	struct strbuf err = STRBUF_INIT;
 	char *refname;
@@ -277,12 +273,10 @@ static const char *parse_cmd_delete(struct ref_transaction *transaction,
 	update_flags = default_flags;
 	free(refname);
 	strbuf_release(&err);
-
-	return next;
 }
 
-static const char *parse_cmd_verify(struct ref_transaction *transaction,
-				    const char *next, const char *end)
+static void parse_cmd_verify(struct ref_transaction *transaction,
+			     const char *next, const char *end)
 {
 	struct strbuf err = STRBUF_INIT;
 	char *refname;
@@ -306,71 +300,82 @@ static const char *parse_cmd_verify(struct ref_transaction *transaction,
 	update_flags = default_flags;
 	free(refname);
 	strbuf_release(&err);
-
-	return next;
 }
 
-static const char *parse_cmd_option(struct ref_transaction *transaction,
-				    const char *next, const char *end)
+static void parse_cmd_option(struct ref_transaction *transaction,
+			     const char *next, const char *end)
 {
 	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 rest;
 }
 
 static const struct parse_cmd {
 	const char *prefix;
-	const char *(*fn)(struct ref_transaction *, const char *, const char *);
+	void (*fn)(struct ref_transaction *, const char *, const char *);
+	unsigned args;
 } command[] = {
-	{ "update", parse_cmd_update },
-	{ "create", parse_cmd_create },
-	{ "delete", parse_cmd_delete },
-	{ "verify", parse_cmd_verify },
-	{ "option", parse_cmd_option },
+	{ "update", parse_cmd_update, 3 },
+	{ "create", parse_cmd_create, 2 },
+	{ "delete", parse_cmd_delete, 2 },
+	{ "verify", parse_cmd_verify, 2 },
+	{ "option", parse_cmd_option, 1 },
 };
 
 static void update_refs_stdin(void)
 {
 	struct strbuf input = STRBUF_INIT, err = STRBUF_INIT;
 	struct ref_transaction *transaction;
-	const char *next;
-	int i;
-
-	if (strbuf_read(&input, 0, 1000) < 0)
-		die_errno("could not read from stdin");
-	next = input.buf;
+	int i, j;
 
 	transaction = ref_transaction_begin(&err);
 	if (!transaction)
 		die("%s", err.buf);
 
 	/* Read each line dispatch its command */
-	while (next < input.buf + input.len) {
+	while (!strbuf_getwholeline(&input, stdin, line_termination)) {
 		const struct parse_cmd *cmd = NULL;
 
-		if (*next == line_termination)
+		if (*input.buf == line_termination)
 			die("empty command in input");
-		else if (isspace(*next))
-			die("whitespace before command: %s", 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 (!skip_prefix(next, prefix, &next) ||
-			    !skip_prefix(next, " ", &next))
+			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", next);
+			die("unknown command: %s", input.buf);
 
-		next = cmd->fn(transaction, next, input.buf + input.len);
-		next++;
+		/*
+		 * 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;
+
+		cmd->fn(transaction, input.buf + strlen(cmd->prefix) + !!cmd->args,
+			input.buf + input.len);
 	}
 
 	if (ref_transaction_commit(transaction, &err))
-- 
2.26.0
Previous: Patrick SteinhardtNext: Patrick Steinhardt
Message 42 of 48 in “Support for transactions in `git-update-ref --stdin`”
  1. 0/9 Support for transactions in `git-update-ref --stdin`Patrick Steinhardt, Mar 25, 2020
  2. 1/9 refs: fix segfault when aborting empty transactionPatrick Steinhardt, Mar 25, 2020
  3. Junio C HamanoMar 27, 2020
  4. 2/9 git-update-ref.txt: add missing wordPatrick Steinhardt, Mar 25, 2020
  5. 3/9 strbuf: provide function to append whole linesPatrick Steinhardt, Mar 25, 2020
  6. Junio C HamanoMar 27, 2020
  7. Patrick SteinhardtMar 30, 2020
  8. Junio C HamanoMar 30, 2020
  9. 4/9 update-ref: organize commands in an arrayPatrick Steinhardt, Mar 25, 2020
  10. Junio C HamanoMar 27, 2020
  11. Patrick SteinhardtMar 30, 2020
  12. Junio C HamanoMar 30, 2020
  13. Patrick SteinhardtMar 30, 2020
  14. 5/9 update-ref: drop unused argument for `parse_refname`Patrick Steinhardt, Mar 25, 2020
  15. 6/9 update-ref: pass end pointer instead of strbufPatrick Steinhardt, Mar 25, 2020
  16. 7/9 update-ref: move transaction handling into `update_refs_stdin()`Patrick Steinhardt, Mar 25, 2020
  17. Junio C HamanoMar 27, 2020
  18. 8/9 update-ref: read commands in a line-wise fashionPatrick Steinhardt, Mar 25, 2020
  19. Junio C HamanoMar 27, 2020
  20. Patrick SteinhardtMar 30, 2020
  21. Junio C HamanoMar 30, 2020
  22. 9/9 update-ref: implement interactive transaction handlingPatrick Steinhardt, Mar 25, 2020
  23. Junio C HamanoMar 27, 2020
  24. 0/9 Support for transactions in `git-update-ref --stdin`Patrick Steinhardt, Mar 30, 2020
  25. 1/9 refs: fix segfault when aborting empty transactionPatrick Steinhardt, Mar 30, 2020
  26. 2/9 git-update-ref.txt: add missing wordPatrick Steinhardt, Mar 30, 2020
  27. 3/9 strbuf: provide function to append whole linesPatrick Steinhardt, Mar 30, 2020
  28. 4/9 update-ref: organize commands in an arrayPatrick Steinhardt, Mar 30, 2020
  29. 5/9 update-ref: drop unused argument for `parse_refname`Patrick Steinhardt, Mar 30, 2020
  30. 6/9 update-ref: pass end pointer instead of strbufPatrick Steinhardt, Mar 30, 2020
  31. 7/9 update-ref: move transaction handling into `update_refs_stdin()`Patrick Steinhardt, Mar 30, 2020
  32. 8/9 update-ref: read commands in a line-wise fashionPatrick Steinhardt, Mar 30, 2020
  33. 9/9 update-ref: implement interactive transaction handlingPatrick Steinhardt, Mar 30, 2020
  34. 0/9 Support for transactions in `git-update-ref --stdin`Patrick Steinhardt, Apr 2, 2020
  35. 1/9 refs: fix segfault when aborting empty transactionPatrick Steinhardt, Apr 2, 2020
  36. 2/9 git-update-ref.txt: add missing wordPatrick Steinhardt, Apr 2, 2020
  37. 3/9 strbuf: provide function to append whole linesPatrick Steinhardt, Apr 2, 2020
  38. 4/9 update-ref: organize commands in an arrayPatrick Steinhardt, Apr 2, 2020
  39. 5/9 update-ref: drop unused argument for `parse_refname`Patrick Steinhardt, Apr 2, 2020
  40. 6/9 update-ref: pass end pointer instead of strbufPatrick Steinhardt, Apr 2, 2020
  41. 7/9 update-ref: move transaction handling into `update_refs_stdin()`Patrick Steinhardt, Apr 2, 2020
  42. 8/9 update-ref: read commands in a line-wise fashionPatrick Steinhardt, Apr 2, 2020
  43. 9/9 update-ref: implement interactive transaction handlingPatrick Steinhardt, Apr 2, 2020
  44. Phillip WoodApr 3, 2020
  45. Patrick SteinhardtApr 3, 2020
  46. Junio C HamanoApr 3, 2020
  47. Junio C HamanoApr 3, 2020
  48. Patrick SteinhardtApr 6, 2020

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.