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

[WIP 11/15] serve: introduce git-serve

From
BWBrandon Williams <bmwill@google.com>
Date
Dec 4, 2017, 23:58 UTC
Message-ID
<20171204235823.63299-12-bmwill@google.com>
In-Reply-To
<20171204235823.63299-1-bmwill@google.com>
Introduce git-serve, the base server for protocol version 2.

When connecting to a server supporting protocol version 2, the server will send a list all of its capabilities and then wait for the client to send a command request. Some capabilities advertised are 'commands' which the client can request (push and fetch are examples of such commands). A command request is comprised of a list of capabilities, including a command request "command=<command>", a delimiter packet, followed by a list of parameters for the requested command.

At the end of each command a client can request that another command be executed or can terminate the connection by sending a flush packet.

Signed-off-by: Brandon Williams <bmwill@google.com>
---
 .gitignore      |   1 +
 Makefile        |   2 +
 builtin.h       |   1 +
 builtin/serve.c |  25 ++++++++
 git.c           |   1 +
 serve.c         | 185 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 serve.h         |   6 ++
 7 files changed, 221 insertions(+)
 create mode 100644 builtin/serve.c
 create mode 100644 serve.c
 create mode 100644 serve.h
diff --git a/.gitignore b/.gitignore
index 833ef3b0b..2d0450c26 100644
--- a/.gitignore
+++ b/.gitignore
@@ -140,6 +140,7 @@
 /git-rm
 /git-send-email
 /git-send-pack
+/git-serve
 /git-sh-i18n
 /git-sh-i18n--envsubst
 /git-sh-setup
diff --git a/Makefile b/Makefile
index 86394b69d..710672cf4 100644
--- a/Makefile
+++ b/Makefile
@@ -862,6 +862,7 @@ LIB_OBJS += revision.o
 LIB_OBJS += run-command.o
 LIB_OBJS += send-pack.o
 LIB_OBJS += sequencer.o
+LIB_OBJS += serve.o
 LIB_OBJS += server-info.o
 LIB_OBJS += setup.o
 LIB_OBJS += sha1-array.o
@@ -995,6 +996,7 @@ BUILTIN_OBJS += builtin/rev-parse.o
 BUILTIN_OBJS += builtin/revert.o
 BUILTIN_OBJS += builtin/rm.o
 BUILTIN_OBJS += builtin/send-pack.o
+BUILTIN_OBJS += builtin/serve.o
 BUILTIN_OBJS += builtin/shortlog.o
 BUILTIN_OBJS += builtin/show-branch.o
 BUILTIN_OBJS += builtin/show-ref.o
diff --git a/builtin.h b/builtin.h
index f332a1257..3f3fdfc28 100644
--- a/builtin.h
+++ b/builtin.h
@@ -215,6 +215,7 @@ extern int cmd_rev_parse(int argc, const char **argv, const char *prefix);
 extern int cmd_revert(int argc, const char **argv, const char *prefix);
 extern int cmd_rm(int argc, const char **argv, const char *prefix);
 extern int cmd_send_pack(int argc, const char **argv, const char *prefix);
+extern int cmd_serve(int argc, const char **argv, const char *prefix);
 extern int cmd_shortlog(int argc, const char **argv, const char *prefix);
 extern int cmd_show(int argc, const char **argv, const char *prefix);
 extern int cmd_show_branch(int argc, const char **argv, const char *prefix);
diff --git a/builtin/serve.c b/builtin/serve.c
new file mode 100644
index 000000000..2ecaad3b6
--- /dev/null
+++ b/builtin/serve.c
@@ -0,0 +1,25 @@
+#include "cache.h"
+#include "builtin.h"
+#include "parse-options.h"
+#include "serve.h"
+
+static char const * const grep_usage[] = {
+	N_("git serve [<options>]"),
+	NULL
+};
+
+int cmd_serve(int argc, const char **argv, const char *prefix)
+{
+
+	struct option options[] = {
+		OPT_END()
+	};
+
+	/* ignore all unknown cmdline switches for now */
+	argc = parse_options(argc, argv, prefix, options, grep_usage,
+			     PARSE_OPT_KEEP_DASHDASH |
+			     PARSE_OPT_KEEP_UNKNOWN);
+	serve();
+
+	return 0;
+}
diff --git a/git.c b/git.c
index e32e16f2d..527086eaf 100644
--- a/git.c
+++ b/git.c
@@ -457,6 +457,7 @@ static struct cmd_struct commands[] = {
 	{ "revert", cmd_revert, RUN_SETUP | NEED_WORK_TREE },
 	{ "rm", cmd_rm, RUN_SETUP },
 	{ "send-pack", cmd_send_pack, RUN_SETUP },
+	{ "serve", cmd_serve, RUN_SETUP },
 	{ "shortlog", cmd_shortlog, RUN_SETUP_GENTLY | USE_PAGER },
 	{ "show", cmd_show, RUN_SETUP },
 	{ "show-branch", cmd_show_branch, RUN_SETUP },
diff --git a/serve.c b/serve.c
new file mode 100644
index 000000000..476e73b54
--- /dev/null
+++ b/serve.c
@@ -0,0 +1,185 @@
+#include "cache.h"
+#include "repository.h"
+#include "config.h"
+#include "pkt-line.h"
+#include "version.h"
+#include "argv-array.h"
+#include "serve.h"
+
+static int agent_advertise(struct repository *r,
+			   struct strbuf *value)
+{
+	strbuf_addstr(value, git_user_agent_sanitized());
+	return 1;
+}
+
+struct protocol_capability {
+	const char *name;
+	int advertised; /* capability was advertised */
+	/* int advertise(struct strbuf *value, struct repository *r) */
+	int (*advertise)(struct repository *r, struct strbuf *value);
+	/* int command(struct repository *r, struct argv_array *keys, struct argv_array *args)*/
+	int (*command)(struct repository *r,
+		       struct argv_array *keys,
+		       struct argv_array *args);
+};
+
+static struct protocol_capability capabilities[] = {
+	{ "agent", 0, agent_advertise, NULL },
+};
+
+static void advertise_capabilities(void)
+{
+	struct strbuf capability = STRBUF_INIT;
+	struct strbuf value = STRBUF_INIT;
+	int i;
+
+	for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
+		struct protocol_capability *c = &capabilities[i];
+
+		c->advertised = c->advertise(the_repository, &value);
+		if (c->advertised) {
+			strbuf_addstr(&capability, c->name);
+
+			if (value.len) {
+				strbuf_addch(&capability, '=');
+				strbuf_addbuf(&capability, &value);
+			}
+
+			strbuf_addch(&capability, '\n');
+			packet_write(1, capability.buf, capability.len);
+		}
+
+		strbuf_reset(&capability);
+		strbuf_reset(&value);
+	}
+
+	packet_flush(1);
+	strbuf_release(&capability);
+	strbuf_release(&value);
+}
+
+static struct protocol_capability *get_capability(const char *key)
+{
+	int i;
+
+	if (!key)
+		return NULL;
+
+	for (i = 0; i < ARRAY_SIZE(capabilities); i++) {
+		struct protocol_capability *c = &capabilities[i];
+		const char *out;
+		if (skip_prefix(key, c->name, &out) && (!*out || *out == '='))
+			return c;
+	}
+
+	return NULL;
+}
+
+static int is_valid_capability(const char *key)
+{
+	const struct protocol_capability *c = get_capability(key);
+
+	return c && c->advertised;
+}
+
+static int is_command(const char *key, struct protocol_capability **command)
+{
+	const char *out;
+
+	if (skip_prefix(key, "command=", &out)) {
+		struct protocol_capability *cmd = get_capability(out);
+
+		if (!cmd || !cmd->advertised || !cmd->command)
+			die("invalid cmd '%s'", out);
+		if (*command)
+			die("command already requested");
+
+		*command = cmd;
+		return 1;
+	}
+
+	return 0;
+}
+
+#define PROCESS_REQUEST_KEYS 0
+#define PROCESS_REQUEST_ARGS 1
+#define PROCESS_REQUEST_DONE 2
+
+static int process_request(void)
+{
+	int state = PROCESS_REQUEST_KEYS;
+	struct packet_reader reader;
+	struct argv_array keys = ARGV_ARRAY_INIT;
+	struct argv_array args = ARGV_ARRAY_INIT;
+	struct protocol_capability *command = NULL;
+
+	packet_reader_init(&reader, 0, NULL, 0);
+
+	while (state != PROCESS_REQUEST_DONE) {
+		switch (packet_reader_read(&reader)) {
+		case PACKET_READ_ERROR:
+			BUG("invalid state");
+		case PACKET_READ_NORMAL:
+			break;
+		case PACKET_READ_FLUSH:
+			state = PROCESS_REQUEST_DONE;
+			continue;
+		case PACKET_READ_DELIM:
+			if (state != PROCESS_REQUEST_KEYS)
+				die("protocol error");
+			state = PROCESS_REQUEST_ARGS;
+			/*
+			 * maybe include a check to make sure that a
+			 * command/capabilities were given.
+			 */
+			continue;
+		}
+
+		switch (state) {
+		case PROCESS_REQUEST_KEYS:
+			/* collect request; a sequence of keys and values */
+			if (is_command(reader.line, &command) ||
+			    is_valid_capability(reader.line))
+				argv_array_push(&keys, reader.line);
+			break;
+		case PROCESS_REQUEST_ARGS:
+			/* collect arguments for the requested command */
+			argv_array_push(&args, reader.line);
+			break;
+		case PROCESS_REQUEST_DONE:
+			continue;
+		default:
+			BUG("invalid state");
+		}
+	}
+
+	/*
+	 * If no command and no keys were given then the client wanted to
+	 * terminate the connection.
+	 */
+	if (!keys.argc && !args.argc)
+		return 1;
+
+	if (!command)
+		die("no command requested");
+
+	command->command(the_repository, &keys, &args);
+
+	argv_array_clear(&keys);
+	argv_array_clear(&args);
+	return 0;
+}
+
+/* Main serve loop for protocol version 2 */
+void serve(void)
+{
+	/* serve by default supports v2 */
+	packet_write_fmt(1, "version 2\n");
+
+	advertise_capabilities();
+
+	for (;;)
+		if (process_request())
+			break;
+}
diff --git a/serve.h b/serve.h
new file mode 100644
index 000000000..1ed9685ca
--- /dev/null
+++ b/serve.h
@@ -0,0 +1,6 @@
+#ifndef SERVE_H
+#define SERVE_H
+
+extern void serve(void);
+
+#endif /* SERVE_H */
-- 
2.15.1.424.g9478a66081-goog
Previous: Brandon WilliamsNext: Junio C Hamano
Message 39 of 47 in “[RFC] protocol version 2”
  1. Brandon WilliamsOct 20, 2017
  2. Junio C HamanoOct 24, 2017
  3. Brandon WilliamsOct 24, 2017
  4. Junio C HamanoOct 25, 2017
  5. Junio C HamanoOct 26, 2017
  6. Derrick StoleeOct 25, 2017
  7. Brandon WilliamsOct 25, 2017
  8. Philip OakleyOct 28, 2017
  9. Brandon WilliamsOct 31, 2017
  10. Jonathan TanNov 10, 2017
  11. 00/15 protocol version 2Brandon Williams, Dec 4, 2017
  12. 01/15 pkt-line: introduce packet_read_with_statusBrandon Williams, Dec 4, 2017
  13. Stefan BellerDec 7, 2017
  14. Brandon WilliamsDec 8, 2017
  15. 02/15 pkt-line: introduce struct packet_readerBrandon Williams, Dec 4, 2017
  16. Stefan BellerDec 7, 2017
  17. Brandon WilliamsDec 8, 2017
  18. 03/15 pkt-line: add delim packet supportBrandon Williams, Dec 4, 2017
  19. Stefan BellerDec 7, 2017
  20. Brandon WilliamsDec 8, 2017
  21. 04/15 upload-pack: convert to a builtinBrandon Williams, Dec 4, 2017
  22. Junio C HamanoDec 6, 2017
  23. Johannes SchindelinDec 7, 2017
  24. Junio C HamanoDec 8, 2017
  25. Brandon WilliamsDec 8, 2017
  26. 07/15 connect: convert get_remote_heads to use struct packet_readerBrandon Williams, Dec 4, 2017
  27. Junio C HamanoDec 6, 2017
  28. Brandon WilliamsDec 8, 2017
  29. 06/15 transport: use get_refs_via_connect to get refsBrandon Williams, Dec 4, 2017
  30. Junio C HamanoDec 6, 2017
  31. Brandon WilliamsDec 7, 2017
  32. 08/15 connect: discover protocol version outside of get_remote_headsBrandon Williams, Dec 4, 2017
  33. Junio C HamanoDec 7, 2017
  34. Brandon WilliamsDec 7, 2017
  35. Junio C HamanoDec 7, 2017
  36. Brandon WilliamsDec 8, 2017
  37. 13/15 connect: request remote refs using v2Brandon Williams, Dec 4, 2017
  38. 09/15 transport: store protocol versionBrandon Williams, Dec 4, 2017
  39. 11/15 serve: introduce git-serveBrandon Williams, Dec 4, 2017
  40. Junio C HamanoDec 7, 2017
  41. Brandon WilliamsDec 8, 2017
  42. 12/15 ls-refs: introduce ls-refs server commandBrandon Williams, Dec 4, 2017
  43. Philip OakleyDec 13, 2017
  44. 15/15 fetch-pack: perform a fetch using v2Brandon Williams, Dec 4, 2017
  45. 14/15 upload_pack: introduce fetch server commandBrandon Williams, Dec 4, 2017
  46. 10/15 protocol: introduce enum protocol_version value protocol_v2Brandon Williams, Dec 4, 2017
  47. 05/15 upload-pack: factor out processing linesBrandon Williams, Dec 4, 2017

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.