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