Discover refs via smart HTTP server when available
[git.git] / remote-curl.c
blob3917d45deb782b0da438a4d70db2af8c1e2c6859
1 #include "cache.h"
2 #include "remote.h"
3 #include "strbuf.h"
4 #include "walker.h"
5 #include "http.h"
6 #include "exec_cmd.h"
7 #include "run-command.h"
8 #include "pkt-line.h"
10 static struct remote *remote;
11 static const char *url;
12 static struct walker *walker;
14 struct options {
15 int verbosity;
16 unsigned long depth;
17 unsigned progress : 1,
18 followtags : 1,
19 dry_run : 1;
21 static struct options options;
23 static void init_walker(void)
25 if (!walker)
26 walker = get_http_walker(url, remote);
29 static int set_option(const char *name, const char *value)
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;
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 */;
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 */;
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 */;
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;
74 else {
75 return 1 /* unsupported */;
79 struct discovery {
80 const char *service;
81 char *buf_alloc;
82 char *buf;
83 size_t len;
84 unsigned proto_git : 1;
86 static struct discovery *last_discovery;
88 static void free_discovery(struct discovery *d)
90 if (d) {
91 if (d == last_discovery)
92 last_discovery = NULL;
93 free(d->buf_alloc);
94 free(d);
98 static struct discovery* discover_refs(const char *service)
100 struct strbuf buffer = STRBUF_INIT;
101 struct discovery *last = last_discovery;
102 char *refs_url;
103 int http_ret, is_http = 0;
105 if (last && !strcmp(service, last->service))
106 return last;
107 free_discovery(last);
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);
118 refs_url = strbuf_detach(&buffer, NULL);
120 init_walker();
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");
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;
138 if (is_http && 5 <= last->len && last->buf[4] == '#') {
139 /* smart HTTP response; validate that the service
140 * pkt-line matches our request.
142 struct strbuf exp = STRBUF_INIT;
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);
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);
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.
158 strbuf_reset(&buffer);
159 while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
160 strbuf_reset(&buffer);
162 last->proto_git = 1;
165 free(refs_url);
166 strbuf_release(&buffer);
167 last_discovery = last;
168 return last;
171 static int write_discovery(int fd, void *data)
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;
181 static struct ref *parse_git_refs(struct discovery *heads)
183 struct ref *list = NULL;
184 struct async async;
186 memset(&async, 0, sizeof(async));
187 async.proc = write_discovery;
188 async.data = heads;
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;
199 static struct ref *parse_info_refs(struct discovery *heads)
201 char *data, *start, *mid;
202 char *ref_name;
203 int i = 0;
205 struct ref *refs = NULL;
206 struct ref *ref = NULL;
207 struct ref *last_ref = NULL;
209 data = heads->buf;
210 start = NULL;
211 mid = data;
212 while (i < heads->len) {
213 if (!start) {
214 start = &data[i];
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;
233 i++;
236 init_walker();
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);
246 return refs;
249 static struct ref *get_refs(int for_push)
251 struct discovery *heads;
253 if (for_push)
254 heads = discover_refs("git-receive-pack");
255 else
256 heads = discover_refs("git-upload-pack");
258 if (heads->proto_git)
259 return parse_git_refs(heads);
260 return parse_info_refs(heads);
263 static void output_refs(struct ref *refs)
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);
272 printf("\n");
273 fflush(stdout);
274 free_refs(refs);
277 static int fetch_dumb(int nr_heads, struct ref **to_fetch)
279 char **targets = xmalloc(nr_heads * sizeof(char*));
280 int ret, i;
282 for (i = 0; i < nr_heads; i++)
283 targets[i] = xstrdup(sha1_to_hex(to_fetch[i]->old_sha1));
285 init_walker();
286 walker->get_all = 1;
287 walker->get_tree = 1;
288 walker->get_history = 1;
289 walker->get_verbosely = options.verbosity >= 3;
290 walker->get_recover = 0;
291 ret = walker_fetch(walker, nr_heads, targets, NULL, NULL);
293 for (i = 0; i < nr_heads; i++)
294 free(targets[i]);
295 free(targets);
297 return ret ? error("Fetch failed.") : 0;
300 static void parse_fetch(struct strbuf *buf)
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;
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];
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);
323 ref = alloc_ref(name);
324 hashcpy(ref->old_sha1, old_sha1);
326 *list = ref;
327 list = &ref->next;
329 ALLOC_GROW(to_fetch, nr_heads + 1, alloc_heads);
330 to_fetch[nr_heads++] = ref;
332 else
333 die("http transport does not support %s", buf->buf);
335 strbuf_reset(buf);
336 if (strbuf_getline(buf, stdin, '\n') == EOF)
337 return;
338 if (!*buf->buf)
339 break;
340 } while (1);
342 if (fetch_dumb(nr_heads, to_fetch))
343 exit(128); /* error already reported */
344 free_refs(list_head);
345 free(to_fetch);
347 printf("\n");
348 fflush(stdout);
349 strbuf_reset(buf);
352 static int push_dav(int nr_spec, char **specs)
354 const char **argv = xmalloc((10 + nr_spec) * sizeof(char*));
355 int argc = 0, i;
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;
368 if (run_command_v_opt(argv, RUN_GIT_CMD))
369 die("git-%s failed", argv[0]);
370 free(argv);
371 return 0;
374 static void parse_push(struct strbuf *buf)
376 char **specs = NULL;
377 int alloc_spec = 0, nr_spec = 0, i;
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);
384 else
385 die("http transport does not support %s", buf->buf);
387 strbuf_reset(buf);
388 if (strbuf_getline(buf, stdin, '\n') == EOF)
389 return;
390 if (!*buf->buf)
391 break;
392 } while (1);
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);
400 printf("\n");
401 fflush(stdout);
404 int main(int argc, const char **argv)
406 struct strbuf buf = STRBUF_INIT;
408 git_extract_argv0_path(argv[0]);
409 setup_git_directory();
410 if (argc < 2) {
411 fprintf(stderr, "Remote needed\n");
412 return 1;
415 options.verbosity = 1;
416 options.progress = !!isatty(2);
418 remote = remote_get(argv[1]);
420 if (argc > 2) {
421 url = argv[2];
422 } else {
423 url = remote->url[0];
426 do {
427 if (strbuf_getline(&buf, stdin, '\n') == EOF)
428 break;
429 if (!prefixcmp(buf.buf, "fetch ")) {
430 parse_fetch(&buf);
432 } else if (!strcmp(buf.buf, "list") || !prefixcmp(buf.buf, "list ")) {
433 int for_push = !!strstr(buf.buf + 4, "for-push");
434 output_refs(get_refs(for_push));
436 } else if (!prefixcmp(buf.buf, "push ")) {
437 parse_push(&buf);
439 } else if (!prefixcmp(buf.buf, "option ")) {
440 char *name = buf.buf + strlen("option ");
441 char *value = strchr(name, ' ');
442 int result;
444 if (value)
445 *value++ = '\0';
446 else
447 value = "true";
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);
458 } else if (!strcmp(buf.buf, "capabilities")) {
459 printf("fetch\n");
460 printf("option\n");
461 printf("push\n");
462 printf("\n");
463 fflush(stdout);
464 } else {
465 return 1;
467 strbuf_reset(&buf);
468 } while (1);
469 return 0;