Commit | Line | Data |
---|---|---|
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 |
10 | static struct remote *remote; |
11 | static const char *url; | |
12 | static struct walker *walker; | |
13 | ||
ef08ef9e SP |
14 | struct options { |
15 | int verbosity; | |
16 | unsigned long depth; | |
17 | unsigned progress : 1, | |
ae4efe19 SP |
18 | followtags : 1, |
19 | dry_run : 1; | |
ef08ef9e SP |
20 | }; |
21 | static struct options options; | |
22 | ||
37a8768f SP |
23 | static void init_walker(void) |
24 | { | |
25 | if (!walker) | |
26 | walker = get_http_walker(url, remote); | |
27 | } | |
28 | ||
ef08ef9e SP |
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 | } | |
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 |
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) | |
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 | ||
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; | |
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 |
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 | ||
ae4efe19 SP |
263 | static 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 |
277 | static 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 | ||
300 | static 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 |
352 | static 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 | ||
374 | static 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 |
404 | int 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 | } |