Discover refs via smart HTTP server when available
[git/git.git] / remote-curl.c
CommitLineData
a2d725b7
DB
1#include "cache.h"
2#include "remote.h"
3#include "strbuf.h"
4#include "walker.h"
5#include "http.h"
d01a8e32 6#include "exec_cmd.h"
ae4efe19 7#include "run-command.h"
97cc7bc4 8#include "pkt-line.h"
a2d725b7 9
37a8768f
SP
10static struct remote *remote;
11static const char *url;
12static struct walker *walker;
13
ef08ef9e
SP
14struct options {
15 int verbosity;
16 unsigned long depth;
17 unsigned progress : 1,
ae4efe19
SP
18 followtags : 1,
19 dry_run : 1;
ef08ef9e
SP
20};
21static struct options options;
22
37a8768f
SP
23static void init_walker(void)
24{
25 if (!walker)
26 walker = get_http_walker(url, remote);
27}
28
ef08ef9e
SP
29static int set_option(const char *name, const char *value)
30{
31 if (!strcmp(name, "verbosity")) {
32 char *end;
33 int v = strtol(value, &end, 10);
34 if (value == end || *end)
35 return -1;
36 options.verbosity = v;
37 return 0;
38 }
39 else if (!strcmp(name, "progress")) {
40 if (!strcmp(value, "true"))
41 options.progress = 1;
42 else if (!strcmp(value, "false"))
43 options.progress = 0;
44 else
45 return -1;
46 return 1 /* TODO implement later */;
47 }
48 else if (!strcmp(name, "depth")) {
49 char *end;
50 unsigned long v = strtoul(value, &end, 10);
51 if (value == end || *end)
52 return -1;
53 options.depth = v;
54 return 1 /* TODO implement later */;
55 }
56 else if (!strcmp(name, "followtags")) {
57 if (!strcmp(value, "true"))
58 options.followtags = 1;
59 else if (!strcmp(value, "false"))
60 options.followtags = 0;
61 else
62 return -1;
63 return 1 /* TODO implement later */;
64 }
ae4efe19
SP
65 else if (!strcmp(name, "dry-run")) {
66 if (!strcmp(value, "true"))
67 options.dry_run = 1;
68 else if (!strcmp(value, "false"))
69 options.dry_run = 0;
70 else
71 return -1;
72 return 0;
73 }
ef08ef9e
SP
74 else {
75 return 1 /* unsupported */;
76 }
77}
78
97cc7bc4
SP
79struct discovery {
80 const char *service;
81 char *buf_alloc;
82 char *buf;
83 size_t len;
84 unsigned proto_git : 1;
85};
86static struct discovery *last_discovery;
87
88static 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
98static struct discovery* discover_refs(const char *service)
a2d725b7
DB
99{
100 struct strbuf buffer = STRBUF_INIT;
97cc7bc4 101 struct discovery *last = last_discovery;
a2d725b7 102 char *refs_url;
97cc7bc4 103 int http_ret, is_http = 0;
a2d725b7 104
97cc7bc4
SP
105 if (last && !strcmp(service, last->service))
106 return last;
107 free_discovery(last);
a2d725b7 108
97cc7bc4
SP
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);
a2d725b7 119
37a8768f 120 init_walker();
a2d725b7
DB
121 http_ret = http_get_strbuf(refs_url, &buffer, HTTP_NO_CACHE);
122 switch (http_ret) {
123 case HTTP_OK:
124 break;
125 case HTTP_MISSING_TARGET:
126 die("%s not found: did you run git update-server-info on the"
127 " server?", refs_url);
128 default:
129 http_error(refs_url, http_ret);
130 die("HTTP request failed");
131 }
132
97cc7bc4
SP
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
171static 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
181static 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
199static 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;
a2d725b7
DB
210 start = NULL;
211 mid = data;
97cc7bc4 212 while (i < heads->len) {
a2d725b7
DB
213 if (!start) {
214 start = &data[i];
215 }
216 if (data[i] == '\t')
217 mid = &data[i];
218 if (data[i] == '\n') {
219 data[i] = 0;
220 ref_name = mid + 1;
221 ref = xmalloc(sizeof(struct ref) +
222 strlen(ref_name) + 1);
223 memset(ref, 0, sizeof(struct ref));
224 strcpy(ref->name, ref_name);
225 get_sha1_hex(start, ref->old_sha1);
226 if (!refs)
227 refs = ref;
228 if (last_ref)
229 last_ref->next = ref;
230 last_ref = ref;
231 start = NULL;
232 }
233 i++;
234 }
235
97cc7bc4 236 init_walker();
a2d725b7
DB
237 ref = alloc_ref("HEAD");
238 if (!walker->fetch_ref(walker, ref) &&
239 !resolve_remote_symref(ref, refs)) {
240 ref->next = refs;
241 refs = ref;
242 } else {
243 free(ref);
244 }
245
a2d725b7
DB
246 return refs;
247}
248
97cc7bc4
SP
249static 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
ae4efe19
SP
263static void output_refs(struct ref *refs)
264{
265 struct ref *posn;
266 for (posn = refs; posn; posn = posn->next) {
267 if (posn->symref)
268 printf("@%s %s\n", posn->symref, posn->name);
269 else
270 printf("%s %s\n", sha1_to_hex(posn->old_sha1), posn->name);
271 }
272 printf("\n");
273 fflush(stdout);
274 free_refs(refs);
275}
276
292ce46b
SP
277static int fetch_dumb(int nr_heads, struct ref **to_fetch)
278{
279 char **targets = xmalloc(nr_heads * sizeof(char*));
280 int ret, i;
281
282 for (i = 0; i < nr_heads; i++)
283 targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
284
285 init_walker();
286 walker->get_all = 1;
287 walker->get_tree = 1;
288 walker->get_history = 1;
ef08ef9e 289 walker->get_verbosely = options.verbosity >= 3;
292ce46b
SP
290 walker->get_recover = 0;
291 ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
292
293 for (i = 0; i < nr_heads; i++)
294 free(targets[i]);
295 free(targets);
296
297 return ret ? error("Fetch failed.") : 0;
298}
299
300static void parse_fetch(struct strbuf *buf)
301{
302 struct ref **to_fetch = NULL;
303 struct ref *list_head = NULL;
304 struct ref **list = &list_head;
305 int alloc_heads = 0, nr_heads = 0;
306
307 do {
308 if (!prefixcmp(buf->buf, "fetch ")) {
309 char *p = buf->buf + strlen("fetch ");
310 char *name;
311 struct ref *ref;
312 unsigned char old_sha1[20];
313
314 if (strlen(p) < 40 || get_sha1_hex(p, old_sha1))
315 die("protocol error: expected sha/ref, got %s'", p);
316 if (p[40] == ' ')
317 name = p + 41;
318 else if (!p[40])
319 name = "";
320 else
321 die("protocol error: expected sha/ref, got %s'", p);
322
323 ref = alloc_ref(name);
324 hashcpy(ref->old_sha1, old_sha1);
325
326 *list = ref;
327 list = &ref->next;
328
329 ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads);
330 to_fetch[nr_heads++] = ref;
331 }
332 else
333 die("http transport does not support %s", buf->buf);
334
335 strbuf_reset(buf);
336 if (strbuf_getline(buf, stdin, '\n') == EOF)
337 return;
338 if (!*buf->buf)
339 break;
340 } while (1);
341
342 if (fetch_dumb(nr_heads, to_fetch))
343 exit(128); /* error already reported */
344 free_refs(list_head);
345 free(to_fetch);
346
347 printf("\n");
348 fflush(stdout);
349 strbuf_reset(buf);
350}
351
ae4efe19
SP
352static int push_dav(int nr_spec, char **specs)
353{
354 const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
355 int argc = 0, i;
356
357 argv[argc++] = "http-push";
358 argv[argc++] = "--helper-status";
359 if (options.dry_run)
360 argv[argc++] = "--dry-run";
361 if (options.verbosity > 1)
362 argv[argc++] = "--verbose";
363 argv[argc++] = url;
364 for (i = 0; i < nr_spec; i++)
365 argv[argc++] = specs[i];
366 argv[argc++] = NULL;
367
368 if (run_command_v_opt(argv, RUN_GIT_CMD))
369 die("git-%s failed", argv[0]);
370 free(argv);
371 return 0;
372}
373
374static void parse_push(struct strbuf *buf)
375{
376 char **specs = NULL;
377 int alloc_spec = 0, nr_spec = 0, i;
378
379 do {
380 if (!prefixcmp(buf->buf, "push ")) {
381 ALLOC_GROW(specs, nr_spec + 1, alloc_spec);
382 specs[nr_spec++] = xstrdup(buf->buf + 5);
383 }
384 else
385 die("http transport does not support %s", buf->buf);
386
387 strbuf_reset(buf);
388 if (strbuf_getline(buf, stdin, '\n') == EOF)
389 return;
390 if (!*buf->buf)
391 break;
392 } while (1);
393
394 if (push_dav(nr_spec, specs))
395 exit(128); /* error already reported */
396 for (i = 0; i < nr_spec; i++)
397 free(specs[i]);
398 free(specs);
399
400 printf("\n");
401 fflush(stdout);
402}
403
a2d725b7
DB
404int main(int argc, const char **argv)
405{
a2d725b7 406 struct strbuf buf = STRBUF_INIT;
a2d725b7 407
c6dfb399 408 git_extract_argv0_path(argv[0]);
a2d725b7
DB
409 setup_git_directory();
410 if (argc < 2) {
411 fprintf(stderr, "Remote needed\n");
412 return 1;
413 }
414
ef08ef9e
SP
415 options.verbosity = 1;
416 options.progress = !!isatty(2);
417
a2d725b7
DB
418 remote = remote_get(argv[1]);
419
420 if (argc > 2) {
421 url = argv[2];
422 } else {
423 url = remote->url[0];
424 }
425
426 do {
427 if (strbuf_getline(&buf, stdin, '\n') == EOF)
428 break;
429 if (!prefixcmp(buf.buf, "fetch ")) {
292ce46b
SP
430 parse_fetch(&buf);
431
ae4efe19 432 } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
97cc7bc4
SP
433 int for_push = !!strstr(buf.buf + 4, "for-push");
434 output_refs(get_refs(for_push));
ae4efe19
SP
435
436 } else if (!prefixcmp(buf.buf, "push ")) {
437 parse_push(&buf);
438
ef08ef9e
SP
439 } else if (!prefixcmp(buf.buf, "option ")) {
440 char *name = buf.buf + strlen("option ");
441 char *value = strchr(name, ' ');
442 int result;
443
444 if (value)
445 *value++ = '\0';
446 else
447 value = "true";
448
449 result = set_option(name, value);
450 if (!result)
451 printf("ok\n");
452 else if (result < 0)
453 printf("error invalid value\n");
454 else
455 printf("unsupported\n");
456 fflush(stdout);
457
a2d725b7
DB
458 } else if (!strcmp(buf.buf, "capabilities")) {
459 printf("fetch\n");
ef08ef9e 460 printf("option\n");
ae4efe19 461 printf("push\n");
a2d725b7
DB
462 printf("\n");
463 fflush(stdout);
464 } else {
465 return 1;
466 }
467 strbuf_reset(&buf);
468 } while (1);
469 return 0;
470}