]>
Commit | Line | Data |
---|---|---|
41fb1ca0 | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
41fb1ca0 PK |
3 | * |
4 | * This file is part of libgit2, distributed under the GNU GPL v2 with | |
5 | * a Linking Exception. For full terms see the included COPYING file. | |
6 | */ | |
613d5eb9 | 7 | #include "git2.h" |
83cc70d9 | 8 | #include "git2/odb_backend.h" |
613d5eb9 | 9 | |
41fb1ca0 PK |
10 | #include "smart.h" |
11 | #include "refs.h" | |
09cc0b92 | 12 | #include "repository.h" |
613d5eb9 PK |
13 | #include "push.h" |
14 | #include "pack-objects.h" | |
15 | #include "remote.h" | |
41fb1ca0 PK |
16 | |
17 | #define NETWORK_XFER_THRESHOLD (100*1024) | |
18 | ||
19 | int git_smart__store_refs(transport_smart *t, int flushes) | |
20 | { | |
21 | gitno_buffer *buf = &t->buffer; | |
22 | git_vector *refs = &t->refs; | |
23 | int error, flush = 0, recvd; | |
e583334c L |
24 | const char *line_end = NULL; |
25 | git_pkt *pkt = NULL; | |
d8041638 ET |
26 | git_pkt_ref *ref; |
27 | size_t i; | |
41fb1ca0 | 28 | |
613d5eb9 PK |
29 | /* Clear existing refs in case git_remote_connect() is called again |
30 | * after git_remote_disconnect(). | |
31 | */ | |
d8041638 ET |
32 | git_vector_foreach(refs, i, ref) { |
33 | git__free(ref->head.name); | |
34 | git__free(ref); | |
35 | } | |
613d5eb9 PK |
36 | git_vector_clear(refs); |
37 | ||
41fb1ca0 PK |
38 | do { |
39 | if (buf->offset > 0) | |
40 | error = git_pkt_parse_line(&pkt, buf->data, &line_end, buf->offset); | |
41 | else | |
42 | error = GIT_EBUFS; | |
43 | ||
44 | if (error < 0 && error != GIT_EBUFS) | |
45 | return -1; | |
46 | ||
47 | if (error == GIT_EBUFS) { | |
48 | if ((recvd = gitno_recv(buf)) < 0) | |
49 | return -1; | |
50 | ||
51 | if (recvd == 0 && !flush) { | |
52 | giterr_set(GITERR_NET, "Early EOF"); | |
53 | return -1; | |
54 | } | |
55 | ||
56 | continue; | |
57 | } | |
58 | ||
59 | gitno_consume(buf, line_end); | |
60 | if (pkt->type == GIT_PKT_ERR) { | |
61 | giterr_set(GITERR_NET, "Remote error: %s", ((git_pkt_err *)pkt)->error); | |
62 | git__free(pkt); | |
63 | return -1; | |
64 | } | |
65 | ||
66 | if (pkt->type != GIT_PKT_FLUSH && git_vector_insert(refs, pkt) < 0) | |
67 | return -1; | |
68 | ||
69 | if (pkt->type == GIT_PKT_FLUSH) { | |
70 | flush++; | |
71 | git_pkt_free(pkt); | |
72 | } | |
73 | } while (flush < flushes); | |
74 | ||
75 | return flush; | |
76 | } | |
77 | ||
78 | int git_smart__detect_caps(git_pkt_ref *pkt, transport_smart_caps *caps) | |
79 | { | |
80 | const char *ptr; | |
81 | ||
82 | /* No refs or capabilites, odd but not a problem */ | |
83 | if (pkt == NULL || pkt->capabilities == NULL) | |
84 | return 0; | |
85 | ||
86 | ptr = pkt->capabilities; | |
87 | while (ptr != NULL && *ptr != '\0') { | |
88 | if (*ptr == ' ') | |
89 | ptr++; | |
90 | ||
613d5eb9 | 91 | if (!git__prefixcmp(ptr, GIT_CAP_OFS_DELTA)) { |
41fb1ca0 PK |
92 | caps->common = caps->ofs_delta = 1; |
93 | ptr += strlen(GIT_CAP_OFS_DELTA); | |
94 | continue; | |
95 | } | |
96 | ||
613d5eb9 | 97 | if (!git__prefixcmp(ptr, GIT_CAP_MULTI_ACK)) { |
41fb1ca0 PK |
98 | caps->common = caps->multi_ack = 1; |
99 | ptr += strlen(GIT_CAP_MULTI_ACK); | |
100 | continue; | |
101 | } | |
102 | ||
613d5eb9 | 103 | if (!git__prefixcmp(ptr, GIT_CAP_INCLUDE_TAG)) { |
41fb1ca0 PK |
104 | caps->common = caps->include_tag = 1; |
105 | ptr += strlen(GIT_CAP_INCLUDE_TAG); | |
106 | continue; | |
107 | } | |
108 | ||
109 | /* Keep side-band check after side-band-64k */ | |
613d5eb9 | 110 | if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND_64K)) { |
41fb1ca0 PK |
111 | caps->common = caps->side_band_64k = 1; |
112 | ptr += strlen(GIT_CAP_SIDE_BAND_64K); | |
113 | continue; | |
114 | } | |
115 | ||
613d5eb9 | 116 | if (!git__prefixcmp(ptr, GIT_CAP_SIDE_BAND)) { |
41fb1ca0 PK |
117 | caps->common = caps->side_band = 1; |
118 | ptr += strlen(GIT_CAP_SIDE_BAND); | |
119 | continue; | |
120 | } | |
121 | ||
613d5eb9 PK |
122 | if (!git__prefixcmp(ptr, GIT_CAP_DELETE_REFS)) { |
123 | caps->common = caps->delete_refs = 1; | |
124 | ptr += strlen(GIT_CAP_DELETE_REFS); | |
125 | continue; | |
126 | } | |
127 | ||
41fb1ca0 PK |
128 | /* We don't know this capability, so skip it */ |
129 | ptr = strchr(ptr, ' '); | |
130 | } | |
131 | ||
132 | return 0; | |
133 | } | |
134 | ||
135 | static int recv_pkt(git_pkt **out, gitno_buffer *buf) | |
136 | { | |
137 | const char *ptr = buf->data, *line_end = ptr; | |
e583334c | 138 | git_pkt *pkt = NULL; |
41fb1ca0 PK |
139 | int pkt_type, error = 0, ret; |
140 | ||
141 | do { | |
142 | if (buf->offset > 0) | |
143 | error = git_pkt_parse_line(&pkt, ptr, &line_end, buf->offset); | |
144 | else | |
145 | error = GIT_EBUFS; | |
146 | ||
147 | if (error == 0) | |
148 | break; /* return the pkt */ | |
149 | ||
150 | if (error < 0 && error != GIT_EBUFS) | |
151 | return -1; | |
152 | ||
153 | if ((ret = gitno_recv(buf)) < 0) | |
154 | return -1; | |
155 | } while (error); | |
156 | ||
157 | gitno_consume(buf, line_end); | |
158 | pkt_type = pkt->type; | |
159 | if (out != NULL) | |
160 | *out = pkt; | |
161 | else | |
162 | git__free(pkt); | |
163 | ||
164 | return pkt_type; | |
165 | } | |
166 | ||
167 | static int store_common(transport_smart *t) | |
168 | { | |
169 | git_pkt *pkt = NULL; | |
170 | gitno_buffer *buf = &t->buffer; | |
171 | ||
172 | do { | |
173 | if (recv_pkt(&pkt, buf) < 0) | |
174 | return -1; | |
175 | ||
176 | if (pkt->type == GIT_PKT_ACK) { | |
177 | if (git_vector_insert(&t->common, pkt) < 0) | |
178 | return -1; | |
179 | } else { | |
180 | git__free(pkt); | |
181 | return 0; | |
182 | } | |
183 | ||
184 | } while (1); | |
185 | ||
186 | return 0; | |
187 | } | |
188 | ||
189 | static int fetch_setup_walk(git_revwalk **out, git_repository *repo) | |
190 | { | |
191 | git_revwalk *walk; | |
192 | git_strarray refs; | |
193 | unsigned int i; | |
194 | git_reference *ref; | |
195 | ||
2b562c3a | 196 | if (git_reference_list(&refs, repo) < 0) |
41fb1ca0 PK |
197 | return -1; |
198 | ||
199 | if (git_revwalk_new(&walk, repo) < 0) | |
200 | return -1; | |
201 | ||
202 | git_revwalk_sorting(walk, GIT_SORT_TIME); | |
203 | ||
204 | for (i = 0; i < refs.count; ++i) { | |
205 | /* No tags */ | |
206 | if (!git__prefixcmp(refs.strings[i], GIT_REFS_TAGS_DIR)) | |
207 | continue; | |
208 | ||
209 | if (git_reference_lookup(&ref, repo, refs.strings[i]) < 0) | |
210 | goto on_error; | |
211 | ||
212 | if (git_reference_type(ref) == GIT_REF_SYMBOLIC) | |
213 | continue; | |
2508cc66 | 214 | if (git_revwalk_push(walk, git_reference_target(ref)) < 0) |
41fb1ca0 PK |
215 | goto on_error; |
216 | ||
217 | git_reference_free(ref); | |
218 | } | |
219 | ||
220 | git_strarray_free(&refs); | |
221 | *out = walk; | |
222 | return 0; | |
223 | ||
224 | on_error: | |
225 | git_reference_free(ref); | |
226 | git_strarray_free(&refs); | |
227 | return -1; | |
228 | } | |
229 | ||
230 | int git_smart__negotiate_fetch(git_transport *transport, git_repository *repo, const git_remote_head * const *refs, size_t count) | |
231 | { | |
232 | transport_smart *t = (transport_smart *)transport; | |
233 | gitno_buffer *buf = &t->buffer; | |
234 | git_buf data = GIT_BUF_INIT; | |
235 | git_revwalk *walk = NULL; | |
236 | int error = -1, pkt_type; | |
237 | unsigned int i; | |
238 | git_oid oid; | |
239 | ||
240 | /* No own logic, do our thing */ | |
77844988 PK |
241 | if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) |
242 | return error; | |
41fb1ca0 | 243 | |
77844988 | 244 | if ((error = fetch_setup_walk(&walk, repo)) < 0) |
41fb1ca0 PK |
245 | goto on_error; |
246 | /* | |
247 | * We don't support any kind of ACK extensions, so the negotiation | |
248 | * boils down to sending what we have and listening for an ACK | |
249 | * every once in a while. | |
250 | */ | |
251 | i = 0; | |
77844988 PK |
252 | while (true) { |
253 | error = git_revwalk_next(&oid, walk); | |
254 | ||
255 | if (error < 0) { | |
256 | if (GIT_ITEROVER == error) | |
257 | break; | |
258 | ||
259 | goto on_error; | |
260 | } | |
261 | ||
41fb1ca0 PK |
262 | git_pkt_buffer_have(&oid, &data); |
263 | i++; | |
264 | if (i % 20 == 0) { | |
265 | if (t->cancelled.val) { | |
266 | giterr_set(GITERR_NET, "The fetch was cancelled by the user"); | |
267 | error = GIT_EUSER; | |
268 | goto on_error; | |
269 | } | |
270 | ||
271 | git_pkt_buffer_flush(&data); | |
77844988 PK |
272 | if (git_buf_oom(&data)) { |
273 | error = -1; | |
41fb1ca0 | 274 | goto on_error; |
77844988 | 275 | } |
41fb1ca0 | 276 | |
77844988 | 277 | if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) |
41fb1ca0 PK |
278 | goto on_error; |
279 | ||
280 | git_buf_clear(&data); | |
281 | if (t->caps.multi_ack) { | |
77844988 | 282 | if ((error = store_common(t)) < 0) |
41fb1ca0 PK |
283 | goto on_error; |
284 | } else { | |
285 | pkt_type = recv_pkt(NULL, buf); | |
286 | ||
287 | if (pkt_type == GIT_PKT_ACK) { | |
288 | break; | |
289 | } else if (pkt_type == GIT_PKT_NAK) { | |
290 | continue; | |
77844988 PK |
291 | } else if (pkt_type < 0) { |
292 | /* recv_pkt returned an error */ | |
293 | error = pkt_type; | |
294 | goto on_error; | |
41fb1ca0 PK |
295 | } else { |
296 | giterr_set(GITERR_NET, "Unexpected pkt type"); | |
77844988 | 297 | error = -1; |
41fb1ca0 PK |
298 | goto on_error; |
299 | } | |
300 | } | |
301 | } | |
302 | ||
303 | if (t->common.length > 0) | |
304 | break; | |
305 | ||
306 | if (i % 20 == 0 && t->rpc) { | |
307 | git_pkt_ack *pkt; | |
308 | unsigned int i; | |
309 | ||
77844988 | 310 | if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) |
41fb1ca0 PK |
311 | goto on_error; |
312 | ||
313 | git_vector_foreach(&t->common, i, pkt) { | |
77844988 PK |
314 | if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) |
315 | goto on_error; | |
41fb1ca0 PK |
316 | } |
317 | ||
77844988 PK |
318 | if (git_buf_oom(&data)) { |
319 | error = -1; | |
41fb1ca0 | 320 | goto on_error; |
77844988 | 321 | } |
41fb1ca0 PK |
322 | } |
323 | } | |
324 | ||
41fb1ca0 PK |
325 | /* Tell the other end that we're done negotiating */ |
326 | if (t->rpc && t->common.length > 0) { | |
327 | git_pkt_ack *pkt; | |
328 | unsigned int i; | |
329 | ||
77844988 | 330 | if ((error = git_pkt_buffer_wants(refs, count, &t->caps, &data)) < 0) |
41fb1ca0 PK |
331 | goto on_error; |
332 | ||
333 | git_vector_foreach(&t->common, i, pkt) { | |
77844988 PK |
334 | if ((error = git_pkt_buffer_have(&pkt->oid, &data)) < 0) |
335 | goto on_error; | |
41fb1ca0 PK |
336 | } |
337 | ||
77844988 PK |
338 | if (git_buf_oom(&data)) { |
339 | error = -1; | |
41fb1ca0 | 340 | goto on_error; |
77844988 | 341 | } |
41fb1ca0 PK |
342 | } |
343 | ||
77844988 PK |
344 | if ((error = git_pkt_buffer_done(&data)) < 0) |
345 | goto on_error; | |
346 | ||
41fb1ca0 PK |
347 | if (t->cancelled.val) { |
348 | giterr_set(GITERR_NET, "The fetch was cancelled by the user"); | |
349 | error = GIT_EUSER; | |
350 | goto on_error; | |
351 | } | |
77844988 | 352 | if ((error = git_smart__negotiation_step(&t->parent, data.ptr, data.size)) < 0) |
41fb1ca0 PK |
353 | goto on_error; |
354 | ||
355 | git_buf_free(&data); | |
356 | git_revwalk_free(walk); | |
357 | ||
358 | /* Now let's eat up whatever the server gives us */ | |
359 | if (!t->caps.multi_ack) { | |
360 | pkt_type = recv_pkt(NULL, buf); | |
77844988 PK |
361 | |
362 | if (pkt_type < 0) { | |
363 | return pkt_type; | |
364 | } else if (pkt_type != GIT_PKT_ACK && pkt_type != GIT_PKT_NAK) { | |
41fb1ca0 PK |
365 | giterr_set(GITERR_NET, "Unexpected pkt type"); |
366 | return -1; | |
367 | } | |
368 | } else { | |
369 | git_pkt_ack *pkt; | |
370 | do { | |
77844988 PK |
371 | if ((error = recv_pkt((git_pkt **)&pkt, buf)) < 0) |
372 | return error; | |
41fb1ca0 PK |
373 | |
374 | if (pkt->type == GIT_PKT_NAK || | |
33c8c6f0 | 375 | (pkt->type == GIT_PKT_ACK && pkt->status != GIT_ACK_CONTINUE)) { |
41fb1ca0 PK |
376 | git__free(pkt); |
377 | break; | |
378 | } | |
379 | ||
380 | git__free(pkt); | |
381 | } while (1); | |
382 | } | |
383 | ||
384 | return 0; | |
385 | ||
386 | on_error: | |
387 | git_revwalk_free(walk); | |
388 | git_buf_free(&data); | |
389 | return error; | |
390 | } | |
391 | ||
09cc0b92 | 392 | static int no_sideband(transport_smart *t, struct git_odb_writepack *writepack, gitno_buffer *buf, git_transfer_progress *stats) |
41fb1ca0 PK |
393 | { |
394 | int recvd; | |
395 | ||
396 | do { | |
397 | if (t->cancelled.val) { | |
398 | giterr_set(GITERR_NET, "The fetch was cancelled by the user"); | |
399 | return GIT_EUSER; | |
400 | } | |
401 | ||
09cc0b92 | 402 | if (writepack->add(writepack, buf->data, buf->offset, stats) < 0) |
41fb1ca0 PK |
403 | return -1; |
404 | ||
405 | gitno_consume_n(buf, buf->offset); | |
406 | ||
407 | if ((recvd = gitno_recv(buf)) < 0) | |
408 | return -1; | |
409 | } while(recvd > 0); | |
410 | ||
09cc0b92 | 411 | if (writepack->commit(writepack, stats)) |
41fb1ca0 PK |
412 | return -1; |
413 | ||
414 | return 0; | |
415 | } | |
416 | ||
a8122b5d | 417 | struct network_packetsize_payload |
41fb1ca0 PK |
418 | { |
419 | git_transfer_progress_callback callback; | |
420 | void *payload; | |
421 | git_transfer_progress *stats; | |
438906e1 | 422 | size_t last_fired_bytes; |
41fb1ca0 PK |
423 | }; |
424 | ||
a8122b5d | 425 | static void network_packetsize(size_t received, void *payload) |
41fb1ca0 PK |
426 | { |
427 | struct network_packetsize_payload *npp = (struct network_packetsize_payload*)payload; | |
428 | ||
429 | /* Accumulate bytes */ | |
430 | npp->stats->received_bytes += received; | |
431 | ||
432 | /* Fire notification if the threshold is reached */ | |
433 | if ((npp->stats->received_bytes - npp->last_fired_bytes) > NETWORK_XFER_THRESHOLD) { | |
a8122b5d RB |
434 | npp->last_fired_bytes = npp->stats->received_bytes; |
435 | npp->callback(npp->stats, npp->payload); | |
41fb1ca0 PK |
436 | } |
437 | } | |
438 | ||
439 | int git_smart__download_pack( | |
440 | git_transport *transport, | |
441 | git_repository *repo, | |
442 | git_transfer_progress *stats, | |
443 | git_transfer_progress_callback progress_cb, | |
444 | void *progress_payload) | |
445 | { | |
446 | transport_smart *t = (transport_smart *)transport; | |
41fb1ca0 | 447 | gitno_buffer *buf = &t->buffer; |
09cc0b92 ET |
448 | git_odb *odb; |
449 | struct git_odb_writepack *writepack = NULL; | |
41fb1ca0 PK |
450 | int error = -1; |
451 | struct network_packetsize_payload npp = {0}; | |
452 | ||
438906e1 PK |
453 | memset(stats, 0, sizeof(git_transfer_progress)); |
454 | ||
41fb1ca0 PK |
455 | if (progress_cb) { |
456 | npp.callback = progress_cb; | |
457 | npp.payload = progress_payload; | |
458 | npp.stats = stats; | |
459 | t->packetsize_cb = &network_packetsize; | |
460 | t->packetsize_payload = &npp; | |
438906e1 PK |
461 | |
462 | /* We might have something in the buffer already from negotiate_fetch */ | |
463 | if (t->buffer.offset > 0) | |
10c06114 | 464 | t->packetsize_cb(t->buffer.offset, t->packetsize_payload); |
41fb1ca0 PK |
465 | } |
466 | ||
09cc0b92 ET |
467 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0 || |
468 | ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)) | |
41fb1ca0 PK |
469 | goto on_error; |
470 | ||
41fb1ca0 PK |
471 | /* |
472 | * If the remote doesn't support the side-band, we can feed | |
09cc0b92 | 473 | * the data directly to the pack writer. Otherwise, we need to |
41fb1ca0 PK |
474 | * check which one belongs there. |
475 | */ | |
476 | if (!t->caps.side_band && !t->caps.side_band_64k) { | |
09cc0b92 | 477 | if (no_sideband(t, writepack, buf, stats) < 0) |
41fb1ca0 PK |
478 | goto on_error; |
479 | ||
438906e1 | 480 | goto on_success; |
41fb1ca0 PK |
481 | } |
482 | ||
483 | do { | |
484 | git_pkt *pkt; | |
485 | ||
486 | if (t->cancelled.val) { | |
487 | giterr_set(GITERR_NET, "The fetch was cancelled by the user"); | |
488 | error = GIT_EUSER; | |
489 | goto on_error; | |
490 | } | |
491 | ||
492 | if (recv_pkt(&pkt, buf) < 0) | |
493 | goto on_error; | |
494 | ||
495 | if (pkt->type == GIT_PKT_PROGRESS) { | |
496 | if (t->progress_cb) { | |
497 | git_pkt_progress *p = (git_pkt_progress *) pkt; | |
498 | t->progress_cb(p->data, p->len, t->message_cb_payload); | |
499 | } | |
500 | git__free(pkt); | |
501 | } else if (pkt->type == GIT_PKT_DATA) { | |
502 | git_pkt_data *p = (git_pkt_data *) pkt; | |
a9e1339c | 503 | error = writepack->add(writepack, p->data, p->len, stats); |
41fb1ca0 PK |
504 | |
505 | git__free(pkt); | |
a9e1339c BS |
506 | if (error < 0) |
507 | goto on_error; | |
41fb1ca0 PK |
508 | } else if (pkt->type == GIT_PKT_FLUSH) { |
509 | /* A flush indicates the end of the packfile */ | |
510 | git__free(pkt); | |
511 | break; | |
512 | } | |
513 | } while (1); | |
514 | ||
09cc0b92 | 515 | if (writepack->commit(writepack, stats) < 0) |
41fb1ca0 PK |
516 | goto on_error; |
517 | ||
438906e1 PK |
518 | on_success: |
519 | error = 0; | |
41fb1ca0 PK |
520 | |
521 | on_error: | |
613d5eb9 PK |
522 | if (writepack) |
523 | writepack->free(writepack); | |
438906e1 PK |
524 | |
525 | /* Trailing execution of progress_cb, if necessary */ | |
526 | if (npp.callback && npp.stats->received_bytes > npp.last_fired_bytes) | |
527 | npp.callback(npp.stats, npp.payload); | |
528 | ||
41fb1ca0 PK |
529 | return error; |
530 | } | |
613d5eb9 PK |
531 | |
532 | static int gen_pktline(git_buf *buf, git_push *push) | |
533 | { | |
613d5eb9 | 534 | push_spec *spec; |
4128f5aa CW |
535 | size_t i, len; |
536 | char old_id[41], new_id[41]; | |
537 | ||
538 | old_id[40] = '\0'; new_id[40] = '\0'; | |
613d5eb9 PK |
539 | |
540 | git_vector_foreach(&push->specs, i, spec) { | |
4128f5aa | 541 | len = 2*GIT_OID_HEXSZ + 7 + strlen(spec->rref); |
613d5eb9 PK |
542 | |
543 | if (i == 0) { | |
4128f5aa | 544 | ++len; /* '\0' */ |
613d5eb9 | 545 | if (push->report_status) |
b8c32580 PK |
546 | len += strlen(GIT_CAP_REPORT_STATUS) + 1; |
547 | len += strlen(GIT_CAP_SIDE_BAND_64K) + 1; | |
613d5eb9 PK |
548 | } |
549 | ||
4128f5aa CW |
550 | git_oid_fmt(old_id, &spec->roid); |
551 | git_oid_fmt(new_id, &spec->loid); | |
613d5eb9 | 552 | |
47fc2642 | 553 | git_buf_printf(buf, "%04"PRIxZ"%s %s %s", len, old_id, new_id, spec->rref); |
613d5eb9 PK |
554 | |
555 | if (i == 0) { | |
556 | git_buf_putc(buf, '\0'); | |
b8c32580 PK |
557 | /* Core git always starts their capabilities string with a space */ |
558 | if (push->report_status) { | |
559 | git_buf_putc(buf, ' '); | |
613d5eb9 | 560 | git_buf_printf(buf, GIT_CAP_REPORT_STATUS); |
b8c32580 PK |
561 | } |
562 | git_buf_putc(buf, ' '); | |
563 | git_buf_printf(buf, GIT_CAP_SIDE_BAND_64K); | |
613d5eb9 PK |
564 | } |
565 | ||
566 | git_buf_putc(buf, '\n'); | |
567 | } | |
4128f5aa | 568 | |
613d5eb9 PK |
569 | git_buf_puts(buf, "0000"); |
570 | return git_buf_oom(buf) ? -1 : 0; | |
571 | } | |
572 | ||
b8c32580 PK |
573 | static int add_push_report_pkt(git_push *push, git_pkt *pkt) |
574 | { | |
575 | push_status *status; | |
576 | ||
577 | switch (pkt->type) { | |
578 | case GIT_PKT_OK: | |
7026ad89 | 579 | status = git__calloc(1, sizeof(push_status)); |
b8c32580 PK |
580 | GITERR_CHECK_ALLOC(status); |
581 | status->msg = NULL; | |
582 | status->ref = git__strdup(((git_pkt_ok *)pkt)->ref); | |
583 | if (!status->ref || | |
584 | git_vector_insert(&push->status, status) < 0) { | |
585 | git_push_status_free(status); | |
586 | return -1; | |
587 | } | |
588 | break; | |
589 | case GIT_PKT_NG: | |
590 | status = git__calloc(sizeof(push_status), 1); | |
591 | GITERR_CHECK_ALLOC(status); | |
592 | status->ref = git__strdup(((git_pkt_ng *)pkt)->ref); | |
593 | status->msg = git__strdup(((git_pkt_ng *)pkt)->msg); | |
594 | if (!status->ref || !status->msg || | |
595 | git_vector_insert(&push->status, status) < 0) { | |
596 | git_push_status_free(status); | |
597 | return -1; | |
598 | } | |
599 | break; | |
600 | case GIT_PKT_UNPACK: | |
601 | push->unpack_ok = ((git_pkt_unpack *)pkt)->unpack_ok; | |
602 | break; | |
603 | case GIT_PKT_FLUSH: | |
604 | return GIT_ITEROVER; | |
605 | default: | |
606 | giterr_set(GITERR_NET, "report-status: protocol error"); | |
607 | return -1; | |
608 | } | |
609 | ||
610 | return 0; | |
611 | } | |
612 | ||
613 | static int add_push_report_sideband_pkt(git_push *push, git_pkt_data *data_pkt) | |
614 | { | |
615 | git_pkt *pkt; | |
616 | const char *line = data_pkt->data, *line_end; | |
617 | size_t line_len = data_pkt->len; | |
618 | int error; | |
619 | ||
620 | while (line_len > 0) { | |
621 | error = git_pkt_parse_line(&pkt, line, &line_end, line_len); | |
622 | ||
623 | if (error < 0) | |
624 | return error; | |
625 | ||
626 | /* Advance in the buffer */ | |
627 | line_len -= (line_end - line); | |
628 | line = line_end; | |
629 | ||
630 | error = add_push_report_pkt(push, pkt); | |
631 | ||
632 | git_pkt_free(pkt); | |
633 | ||
f5898324 | 634 | if (error < 0 && error != GIT_ITEROVER) |
b8c32580 PK |
635 | return error; |
636 | } | |
637 | ||
638 | return 0; | |
639 | } | |
640 | ||
613d5eb9 PK |
641 | static int parse_report(gitno_buffer *buf, git_push *push) |
642 | { | |
e583334c L |
643 | git_pkt *pkt = NULL; |
644 | const char *line_end = NULL; | |
613d5eb9 PK |
645 | int error, recvd; |
646 | ||
647 | for (;;) { | |
648 | if (buf->offset > 0) | |
649 | error = git_pkt_parse_line(&pkt, buf->data, | |
650 | &line_end, buf->offset); | |
651 | else | |
652 | error = GIT_EBUFS; | |
653 | ||
654 | if (error < 0 && error != GIT_EBUFS) | |
655 | return -1; | |
656 | ||
657 | if (error == GIT_EBUFS) { | |
658 | if ((recvd = gitno_recv(buf)) < 0) | |
659 | return -1; | |
660 | ||
661 | if (recvd == 0) { | |
662 | giterr_set(GITERR_NET, "Early EOF"); | |
663 | return -1; | |
664 | } | |
665 | continue; | |
666 | } | |
667 | ||
668 | gitno_consume(buf, line_end); | |
669 | ||
b8c32580 | 670 | error = 0; |
613d5eb9 | 671 | |
b8c32580 PK |
672 | switch (pkt->type) { |
673 | case GIT_PKT_DATA: | |
674 | /* This is a sideband packet which contains other packets */ | |
675 | error = add_push_report_sideband_pkt(push, (git_pkt_data *)pkt); | |
676 | break; | |
677 | case GIT_PKT_ERR: | |
678 | giterr_set(GITERR_NET, "report-status: Error reported: %s", | |
679 | ((git_pkt_err *)pkt)->error); | |
680 | error = -1; | |
681 | break; | |
682 | case GIT_PKT_PROGRESS: | |
683 | break; | |
684 | default: | |
685 | error = add_push_report_pkt(push, pkt); | |
686 | break; | |
613d5eb9 PK |
687 | } |
688 | ||
b8c32580 | 689 | git_pkt_free(pkt); |
613d5eb9 | 690 | |
b8c32580 | 691 | /* add_push_report_pkt returns GIT_ITEROVER when it receives a flush */ |
f5898324 | 692 | if (error == GIT_ITEROVER) |
613d5eb9 | 693 | return 0; |
613d5eb9 | 694 | |
b8c32580 PK |
695 | if (error < 0) |
696 | return error; | |
613d5eb9 PK |
697 | } |
698 | } | |
699 | ||
df93a681 PK |
700 | static int add_ref_from_push_spec(git_vector *refs, push_spec *push_spec) |
701 | { | |
702 | git_pkt_ref *added = git__calloc(1, sizeof(git_pkt_ref)); | |
703 | GITERR_CHECK_ALLOC(added); | |
704 | ||
705 | added->type = GIT_PKT_REF; | |
706 | git_oid_cpy(&added->head.oid, &push_spec->loid); | |
707 | added->head.name = git__strdup(push_spec->rref); | |
708 | ||
709 | if (!added->head.name || | |
710 | git_vector_insert(refs, added) < 0) { | |
711 | git_pkt_free((git_pkt *)added); | |
712 | return -1; | |
713 | } | |
714 | ||
715 | return 0; | |
716 | } | |
717 | ||
718 | static int update_refs_from_report( | |
719 | git_vector *refs, | |
720 | git_vector *push_specs, | |
721 | git_vector *push_report) | |
722 | { | |
723 | git_pkt_ref *ref; | |
724 | push_spec *push_spec; | |
a150cc87 | 725 | push_status *push_status; |
df93a681 PK |
726 | size_t i, j, refs_len; |
727 | int cmp; | |
728 | ||
729 | /* For each push spec we sent to the server, we should have | |
730 | * gotten back a status packet in the push report */ | |
731 | if (push_specs->length != push_report->length) { | |
732 | giterr_set(GITERR_NET, "report-status: protocol error"); | |
733 | return -1; | |
734 | } | |
735 | ||
736 | /* We require that push_specs be sorted with push_spec_rref_cmp, | |
737 | * and that push_report be sorted with push_status_ref_cmp */ | |
738 | git_vector_sort(push_specs); | |
739 | git_vector_sort(push_report); | |
740 | ||
741 | git_vector_foreach(push_specs, i, push_spec) { | |
742 | push_status = git_vector_get(push_report, i); | |
743 | ||
744 | /* For each push spec we sent to the server, we should have | |
745 | * gotten back a status packet in the push report which matches */ | |
746 | if (strcmp(push_spec->rref, push_status->ref)) { | |
747 | giterr_set(GITERR_NET, "report-status: protocol error"); | |
748 | return -1; | |
749 | } | |
750 | } | |
751 | ||
752 | /* We require that refs be sorted with ref_name_cmp */ | |
753 | git_vector_sort(refs); | |
754 | i = j = 0; | |
755 | refs_len = refs->length; | |
756 | ||
757 | /* Merge join push_specs with refs */ | |
758 | while (i < push_specs->length && j < refs_len) { | |
759 | push_spec = git_vector_get(push_specs, i); | |
a150cc87 | 760 | push_status = git_vector_get(push_report, i); |
df93a681 PK |
761 | ref = git_vector_get(refs, j); |
762 | ||
763 | cmp = strcmp(push_spec->rref, ref->head.name); | |
764 | ||
765 | /* Iterate appropriately */ | |
766 | if (cmp <= 0) i++; | |
767 | if (cmp >= 0) j++; | |
768 | ||
769 | /* Add case */ | |
770 | if (cmp < 0 && | |
771 | !push_status->msg && | |
772 | add_ref_from_push_spec(refs, push_spec) < 0) | |
773 | return -1; | |
774 | ||
775 | /* Update case, delete case */ | |
776 | if (cmp == 0 && | |
777 | !push_status->msg) | |
778 | git_oid_cpy(&ref->head.oid, &push_spec->loid); | |
779 | } | |
780 | ||
781 | for (; i < push_specs->length; i++) { | |
782 | push_spec = git_vector_get(push_specs, i); | |
a150cc87 | 783 | push_status = git_vector_get(push_report, i); |
df93a681 PK |
784 | |
785 | /* Add case */ | |
786 | if (!push_status->msg && | |
787 | add_ref_from_push_spec(refs, push_spec) < 0) | |
788 | return -1; | |
789 | } | |
790 | ||
791 | /* Remove any refs which we updated to have a zero OID. */ | |
792 | git_vector_rforeach(refs, i, ref) { | |
793 | if (git_oid_iszero(&ref->head.oid)) { | |
794 | git_vector_remove(refs, i); | |
795 | git_pkt_free((git_pkt *)ref); | |
796 | } | |
797 | } | |
798 | ||
799 | git_vector_sort(refs); | |
800 | ||
801 | return 0; | |
802 | } | |
803 | ||
613d5eb9 PK |
804 | static int stream_thunk(void *buf, size_t size, void *data) |
805 | { | |
806 | git_smart_subtransport_stream *s = (git_smart_subtransport_stream *)data; | |
807 | ||
808 | return s->write(s, (const char *)buf, size); | |
809 | } | |
810 | ||
811 | int git_smart__push(git_transport *transport, git_push *push) | |
812 | { | |
813 | transport_smart *t = (transport_smart *)transport; | |
814 | git_smart_subtransport_stream *s; | |
815 | git_buf pktline = GIT_BUF_INIT; | |
51e4da6d CMN |
816 | int error = -1, need_pack = 0; |
817 | push_spec *spec; | |
818 | unsigned int i; | |
613d5eb9 PK |
819 | |
820 | #ifdef PUSH_DEBUG | |
821 | { | |
822 | git_remote_head *head; | |
613d5eb9 PK |
823 | char hex[41]; hex[40] = '\0'; |
824 | ||
825 | git_vector_foreach(&push->remote->refs, i, head) { | |
826 | git_oid_fmt(hex, &head->oid); | |
827 | fprintf(stderr, "%s (%s)\n", hex, head->name); | |
828 | } | |
829 | ||
830 | git_vector_foreach(&push->specs, i, spec) { | |
831 | git_oid_fmt(hex, &spec->roid); | |
832 | fprintf(stderr, "%s (%s) -> ", hex, spec->lref); | |
833 | git_oid_fmt(hex, &spec->loid); | |
834 | fprintf(stderr, "%s (%s)\n", hex, spec->rref ? | |
835 | spec->rref : spec->lref); | |
836 | } | |
837 | } | |
838 | #endif | |
839 | ||
51e4da6d CMN |
840 | /* |
841 | * Figure out if we need to send a packfile; which is in all | |
842 | * cases except when we only send delete commands | |
843 | */ | |
844 | git_vector_foreach(&push->specs, i, spec) { | |
845 | if (spec->lref) { | |
846 | need_pack = 1; | |
847 | break; | |
848 | } | |
849 | } | |
850 | ||
613d5eb9 PK |
851 | if (git_smart__get_push_stream(t, &s) < 0 || |
852 | gen_pktline(&pktline, push) < 0 || | |
51e4da6d CMN |
853 | s->write(s, git_buf_cstr(&pktline), git_buf_len(&pktline)) < 0) |
854 | goto on_error; | |
855 | ||
856 | if (need_pack && git_packbuilder_foreach(push->pb, &stream_thunk, s) < 0) | |
613d5eb9 PK |
857 | goto on_error; |
858 | ||
859 | /* If we sent nothing or the server doesn't support report-status, then | |
860 | * we consider the pack to have been unpacked successfully */ | |
861 | if (!push->specs.length || !push->report_status) | |
862 | push->unpack_ok = 1; | |
863 | else if (parse_report(&t->buffer, push) < 0) | |
864 | goto on_error; | |
865 | ||
df93a681 PK |
866 | if (push->status.length && |
867 | update_refs_from_report(&t->refs, &push->specs, &push->status) < 0) | |
868 | goto on_error; | |
613d5eb9 PK |
869 | |
870 | error = 0; | |
871 | ||
872 | on_error: | |
613d5eb9 PK |
873 | git_buf_free(&pktline); |
874 | ||
875 | return error; | |
876 | } |