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 <bmw...@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

Reply via email to