Code

Discover refs via smart HTTP server when available
[git.git] / remote-curl.c
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;
20 };
21 static struct options options;
23 static void init_walker(void)
24 {
25         if (!walker)
26                 walker = get_http_walker(url, remote);
27 }
29 static 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         }
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         }
74         else {
75                 return 1 /* unsupported */;
76         }
77 }
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;
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 }
98 static struct discovery* discover_refs(const char *service)
99 {
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);
117         }
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");
131         }
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.
141                  */
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.
157                  */
158                 strbuf_reset(&buffer);
159                 while (packet_get_line(&buffer, &last->buf, &last->len) > 0)
160                         strbuf_reset(&buffer);
162                 last->proto_git = 1;
163         }
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];
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         }
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);
244         }
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);
271         }
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;
331                 }
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);
383                 }
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;
413         }
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];
424         }
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;
466                 }
467                 strbuf_reset(&buf);
468         } while (1);
469         return 0;