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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
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: