Discover refs via smart HTTP server when available
Instead of loading the cached info/refs, try to use the smart HTTP version when the server supports it. Since the smart variant is actually the pkt-line stream from the start of either upload-pack or receive-pack we need to parse these through get_remote_heads, which requires a background thread to feed its pipe. Signed-off-by: Shawn O. Pearce <spearce@spearce.org> CC: Daniel Barkalow <barkalow@iabervon.org> Signed-off-by: Junio C Hamano <gitster@pobox.com>
Shawn O. Pearce committed
Oct 30, 2009 at 17:47 UTC
97cc7bc45c2e076184b84749952b216036a6ecd6
1 file changed
+131
-17
remote-curl.c
+131
-17
@@ -5,6 +5,7 @@
5
#include "http.h"
6
#include "exec_cmd.h"
7
#include "run-command.h"
8
+#include "pkt-line.h"
9
10
static struct remote *remote;
11
static const char *url;
@@ -75,21 +76,46 @@ static int set_option(const char *name, const char *value)
76
}
77
}
78
78
-static struct ref *get_refs(void)
79
+struct discovery {
80
+ const char *service;
81
+ char *buf_alloc;
82
+ char *buf;
83
+ size_t len;
84
+ unsigned proto_git : 1;
85
+};
86
+static struct discovery *last_discovery;
87
+
88
+static void free_discovery(struct discovery *d)
89
+{
90
+ if (d) {
91
+ if (d == last_discovery)
92
+ last_discovery = NULL;
93
+ free(d->buf_alloc);
94
+ free(d);
95
+ }
96
+}
97
+
98
+static struct discovery* discover_refs(const char *service)
99
{
100
struct strbuf buffer = STRBUF_INIT;
81
- char *data, *start, *mid;
82
- char *ref_name;
101
+ struct discovery *last = last_discovery;
102
char *refs_url;
84
- int i = 0;
85
- int http_ret;
103
+ int http_ret, is_http = 0;
104
87
- struct ref *refs = NULL;
88
- struct ref *ref = NULL;
89
- struct ref *last_ref = NULL;
105
+ if (last && !strcmp(service, last->service))
106
+ return last;
107
+ free_discovery(last);
108
91
- refs_url = xmalloc(strlen(url) + 11);
92
- sprintf(refs_url, "%s/info/refs", url);
109
+ strbuf_addf(&buffer, "%s/info/refs", url);
110
+ if (!prefixcmp(url, "http://") || !prefixcmp(url, "https://")) {
111
+ is_http = 1;
112
+ if (!strchr(url, '?'))
113
+ strbuf_addch(&buffer, '?');
114
+ else
115
+ strbuf_addch(&buffer, '&');
116
+ strbuf_addf(&buffer, "service=%s", service);
117
+ }
118
+ refs_url = strbuf_detach(&buffer, NULL);
119
120
init_walker();
121
http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
@@ -104,10 +130,86 @@ static struct ref *get_refs(void)
130
die("HTTP request failed");
131
}
132
107
- data = buffer.buf;
133
+ last= xcalloc(1, sizeof(*last_discovery));
134
+ last->service = service;
135
+ last->buf_alloc = strbuf_detach(&buffer, &last->len);
136
+ last->buf = last->buf_alloc;
137
+
138
+ if (is_http && 5 <= last->len && last->buf[4] == '#') {
139
+ /* smart HTTP response; validate that the service
140
+ * pkt-line matches our request.
141
+ */
142
+ struct strbuf exp = STRBUF_INIT;
143
+
144
+ if (packet_get_line(&buffer, &last->buf, &last->len) <= 0)
145
+ die("%s has invalid packet header", refs_url);
146
+ if (buffer.len && buffer.buf[buffer.len - 1] == '\n')
147
+ strbuf_setlen(&buffer, buffer.len - 1);
148
+
149
+ strbuf_addf(&exp, "# service=%s", service);
150
+ if (strbuf_cmp(&exp, &buffer))
151
+ die("invalid server response; got '%s'", buffer.buf);
152
+ strbuf_release(&exp);
153
+
154
+ /* The header can include additional metadata lines, up
155
+ * until a packet flush marker. Ignore these now, but
156
+ * in the future we might start to scan them.
157
+ */
158
+ strbuf_reset(&buffer);
159
+ while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
160
+ strbuf_reset(&buffer);
161
+
162
+ last->proto_git = 1;
163
+ }
164
+
165
+ free(refs_url);
166
+ strbuf_release(&buffer);
167
+ last_discovery = last;
168
+ return last;
169
+}
170
+
171
+static int write_discovery(int fd, void *data)
172
+{
173
+ struct discovery *heads = data;
174
+ int err = 0;
175
+ if (write_in_full(fd, heads->buf, heads->len) != heads->len)
176
+ err = 1;
177
+ close(fd);
178
+ return err;
179
+}
180
+
181
+static struct ref *parse_git_refs(struct discovery *heads)
182
+{
183
+ struct ref *list = NULL;
184
+ struct async async;
185
+
186
+ memset(&async, 0, sizeof(async));
187
+ async.proc = write_discovery;
188
+ async.data = heads;
189
+
190
+ if (start_async(&async))
191
+ die("cannot start thread to parse advertised refs");
192
+ get_remote_heads(async.out, &list, 0, NULL, 0, NULL);
193
+ close(async.out);
194
+ if (finish_async(&async))
195
+ die("ref parsing thread failed");
196
+ return list;
197
+}
198
+
199
+static struct ref *parse_info_refs(struct discovery *heads)
200
+{
201
+ char *data, *start, *mid;
202
+ char *ref_name;
203
+ int i = 0;
204
+
205
+ struct ref *refs = NULL;
206
+ struct ref *ref = NULL;
207
+ struct ref *last_ref = NULL;
208
+
209
+ data = heads->buf;
210
start = NULL;
211
mid = data;
110
- while (i < buffer.len) {
212
+ while (i < heads->len) {
213
if (!start) {
214
start = &data[i];
215
}
@@ -131,8 +233,7 @@ static struct ref *get_refs(void)
233
i++;
234
}
235
134
- strbuf_release(&buffer);
135
-
236
+ init_walker();
237
ref = alloc_ref("HEAD");
238
if (!walker->fetch_ref(walker, ref) &&
239
!resolve_remote_symref(ref, refs)) {
@@ -142,11 +243,23 @@ static struct ref *get_refs(void)
243
free(ref);
244
}
245
145
- strbuf_release(&buffer);
146
- free(refs_url);
246
return refs;
247
}
248
249
+static struct ref *get_refs(int for_push)
250
+{
251
+ struct discovery *heads;
252
+
253
+ if (for_push)
254
+ heads = discover_refs("git-receive-pack");
255
+ else
256
+ heads = discover_refs("git-upload-pack");
257
+
258
+ if (heads->proto_git)
259
+ return parse_git_refs(heads);
260
+ return parse_info_refs(heads);
261
+}
262
+
263
static void output_refs(struct ref *refs)
264
{
265
struct ref *posn;
@@ -317,7 +430,8 @@ int main(int argc, const char **argv)
430
parse_fetch(&buf);
431
432
} else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
320
- output_refs(get_refs());
433
+ int for_push = !!strstr(buf.buf + 4, "for-push");
434
+ output_refs(get_refs(for_push));
435
436
} else if (!prefixcmp(buf.buf, "push ")) {
437
parse_push(&buf);