Commit 8156835df17d89bd7ce1525792aa13146fab551e

Carlos Martín Nieto 2014-05-20T09:29:39

smart: store reported symrefs The protocol has a capability which allows the server to tell us which refs are symrefs, so we can e.g. know which is the default branch. This capability is different from the ones we already support, as it's not setting a flag to true, but requires us to store a list of refspec-formatted mappings. This commit does not yet expose the information in the reference listing.

diff --git a/src/transports/smart.c b/src/transports/smart.c
index 69eaf9b..2f3e777 100644
--- a/src/transports/smart.c
+++ b/src/transports/smart.c
@@ -7,6 +7,7 @@
 #include "git2.h"
 #include "smart.h"
 #include "refs.h"
+#include "refspec.h"
 
 static int git_smart__recv_cb(gitno_buffer *buf)
 {
@@ -63,7 +64,7 @@ static int git_smart__set_callbacks(
 	return 0;
 }
 
-int git_smart__update_heads(transport_smart *t)
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs)
 {
 	size_t i;
 	git_pkt *pkt;
@@ -81,6 +82,19 @@ int git_smart__update_heads(transport_smart *t)
 	return 0;
 }
 
+static void free_symrefs(git_vector *symrefs)
+{
+	git_refspec *spec;
+	size_t i;
+
+	git_vector_foreach(symrefs, i, spec) {
+		git_refspec__free(spec);
+		git__free(spec);
+	}
+
+	git_vector_free(symrefs);
+}
+
 static int git_smart__connect(
 	git_transport *transport,
 	const char *url,
@@ -94,6 +108,7 @@ static int git_smart__connect(
 	int error;
 	git_pkt *pkt;
 	git_pkt_ref *first;
+	git_vector symrefs;
 	git_smart_service_t service;
 
 	if (git_smart__reset_stream(t, true) < 0)
@@ -147,8 +162,11 @@ static int git_smart__connect(
 
 	first = (git_pkt_ref *)git_vector_get(&t->refs, 0);
 
+	if ((error = git_vector_init(&symrefs, 1, NULL)) < 0)
+		return error;
+
 	/* Detect capabilities */
-	if (git_smart__detect_caps(first, &t->caps) < 0)
+	if (git_smart__detect_caps(first, &t->caps, &symrefs) < 0)
 		return -1;
 
 	/* If the only ref in the list is capabilities^{} with OID_ZERO, remove it */
@@ -159,7 +177,9 @@ static int git_smart__connect(
 	}
 
 	/* Keep a list of heads for _ls */
-	git_smart__update_heads(t);
+	git_smart__update_heads(t, &symrefs);
+
+	free_symrefs(&symrefs);
 
 	if (t->rpc && git_smart__reset_stream(t, false) < 0)
 		return -1;
diff --git a/src/transports/smart.h b/src/transports/smart.h
index a2b6b2a..f1fc295 100644
--- a/src/transports/smart.h
+++ b/src/transports/smart.h
@@ -23,6 +23,7 @@
 #define GIT_CAP_DELETE_REFS "delete-refs"
 #define GIT_CAP_REPORT_STATUS "report-status"
 #define GIT_CAP_THIN_PACK "thin-pack"
+#define GIT_CAP_SYMREF "symref"
 
 enum git_pkt_type {
 	GIT_PKT_CMD,
@@ -154,7 +155,7 @@ typedef struct {
 
 /* smart_protocol.c */
 int git_smart__store_refs(transport_smart *t, int flushes);
-int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps);
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs);
 int git_smart__push(git_transport *transport, git_push *push);
 
 int git_smart__negotiate_fetch(
@@ -174,7 +175,7 @@ int git_smart__download_pack(
 int git_smart__negotiation_step(git_transport *transport, void *data, size_t len);
 int git_smart__get_push_stream(transport_smart *t, git_smart_subtransport_stream **out);
 
-int git_smart__update_heads(transport_smart *t);
+int git_smart__update_heads(transport_smart *t, git_vector *symrefs);
 
 /* smart_pkt.c */
 int git_pkt_parse_line(git_pkt **head, const char *line, const char **out, size_t len);
diff --git a/src/transports/smart_protocol.c b/src/transports/smart_protocol.c
index 5dd6bab..bab0cf1 100644
--- a/src/transports/smart_protocol.c
+++ b/src/transports/smart_protocol.c
@@ -78,7 +78,52 @@ int git_smart__store_refs(transport_smart *t, int flushes)
 	return flush;
 }
 
-int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
+static int append_symref(const char **out, git_vector *symrefs, const char *ptr)
+{
+	int error;
+	const char *end;
+	git_buf buf = GIT_BUF_INIT;
+	git_refspec *mapping;
+
+	ptr += strlen(GIT_CAP_SYMREF);
+	if (*ptr != '=')
+		goto on_invalid;
+
+	ptr++;
+	if (!(end = strchr(ptr, ' ')) &&
+	    !(end = strchr(ptr, '\0')))
+		goto on_invalid;
+
+	if ((error = git_buf_put(&buf, ptr, end - ptr)) < 0)
+		return error;
+
+	/* symref mapping has refspec format */
+	mapping = git__malloc(sizeof(git_refspec));
+	GITERR_CHECK_ALLOC(mapping);
+
+	error = git_refspec__parse(mapping, git_buf_cstr(&buf), true);
+	git_buf_free(&buf);
+
+	/* if the error isn't OOM, then it's a parse error; let's use a nicer message */
+	if (error < 0) {
+		if (giterr_last()->klass != GITERR_NOMEMORY)
+			goto on_invalid;
+
+		return error;
+	}
+
+	if ((error = git_vector_insert(symrefs, mapping)) < 0)
+		return error;
+
+	*out = end;
+	return 0;
+
+on_invalid:
+	giterr_set(GITERR_NET, "remote sent invalid symref");
+	return -1;
+}
+
+int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps, git_vector *symrefs)
 {
 	const char *ptr;
 
@@ -141,6 +186,15 @@ int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps)
 			continue;
 		}
 
+		if (!git__prefixcmp(ptr, GIT_CAP_SYMREF)) {
+			int error;
+
+			if ((error = append_symref(&ptr, symrefs, ptr)) < 0)
+				return error;
+
+			continue;
+		}
+
 		/* We don't know this capability, so skip it */
 		ptr = strchr(ptr, ' ');
 	}
@@ -969,7 +1023,7 @@ int git_smart__push(git_transport *transport, git_push *push)
 		if (error < 0)
 			goto done;
 
-		error = git_smart__update_heads(t);
+		error = git_smart__update_heads(t, NULL);
 	}
 
 done: