]>
Commit | Line | Data |
---|---|---|
bb742ede | 1 | /* |
359fc2d2 | 2 | * Copyright (C) the libgit2 contributors. All rights reserved. |
bb742ede VM |
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 | */ | |
eae0bfdc | 7 | |
8f866dae | 8 | #include "common.h" |
eae0bfdc | 9 | |
e579e0f7 MB |
10 | #include "pack-objects.h" |
11 | #include "refs.h" | |
12 | #include "posix.h" | |
13 | #include "fs_path.h" | |
14 | #include "repository.h" | |
15 | #include "odb.h" | |
16 | #include "push.h" | |
17 | #include "remote.h" | |
18 | #include "proxy.h" | |
19 | ||
8f866dae | 20 | #include "git2/types.h" |
8f866dae CMN |
21 | #include "git2/net.h" |
22 | #include "git2/repository.h" | |
d6258deb CMN |
23 | #include "git2/object.h" |
24 | #include "git2/tag.h" | |
41fb1ca0 | 25 | #include "git2/transport.h" |
505da062 BS |
26 | #include "git2/revwalk.h" |
27 | #include "git2/odb_backend.h" | |
28 | #include "git2/pack.h" | |
29 | #include "git2/commit.h" | |
30 | #include "git2/revparse.h" | |
e579e0f7 | 31 | #include "git2/sys/remote.h" |
8f866dae | 32 | |
0a9a38e5 | 33 | typedef struct { |
4e913309 | 34 | git_transport parent; |
613d5eb9 | 35 | git_remote *owner; |
41fb1ca0 PK |
36 | char *url; |
37 | int direction; | |
c25aa7cd | 38 | git_atomic32 cancelled; |
0a9a38e5 | 39 | git_repository *repo; |
e579e0f7 | 40 | git_remote_connect_options connect_opts; |
41fb1ca0 | 41 | git_vector refs; |
67ba7d20 CMN |
42 | unsigned connected : 1, |
43 | have_refs : 1; | |
4e913309 | 44 | } transport_local; |
0a9a38e5 | 45 | |
4c4408c3 CMN |
46 | static void free_head(git_remote_head *head) |
47 | { | |
48 | git__free(head->name); | |
49 | git__free(head->symref_target); | |
50 | git__free(head); | |
51 | } | |
52 | ||
f7fcb18f CMN |
53 | static void free_heads(git_vector *heads) |
54 | { | |
55 | git_remote_head *head; | |
56 | size_t i; | |
57 | ||
58 | git_vector_foreach(heads, i, head) | |
59 | free_head(head); | |
60 | ||
61 | git_vector_free(heads); | |
62 | } | |
63 | ||
d88d4311 | 64 | static int add_ref(transport_local *t, const char *name) |
d6258deb | 65 | { |
d6258deb | 66 | const char peeled[] = "^{}"; |
04865aa0 | 67 | git_reference *ref, *resolved; |
d6258deb | 68 | git_remote_head *head; |
04865aa0 | 69 | git_oid obj_id; |
a62053a0 | 70 | git_object *obj = NULL, *target = NULL; |
e579e0f7 | 71 | git_str buf = GIT_STR_INIT; |
b524fe1a | 72 | int error; |
d6258deb | 73 | |
04865aa0 CMN |
74 | if ((error = git_reference_lookup(&ref, t->repo, name)) < 0) |
75 | return error; | |
76 | ||
77 | error = git_reference_resolve(&resolved, ref); | |
b524fe1a | 78 | if (error < 0) { |
04865aa0 | 79 | git_reference_free(ref); |
2a2d1ab0 | 80 | if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) { |
25e0b157 RB |
81 | /* This is actually okay. Empty repos often have a HEAD that |
82 | * points to a nonexistent "refs/heads/master". */ | |
ac3d33df | 83 | git_error_clear(); |
b524fe1a BS |
84 | return 0; |
85 | } | |
86 | return error; | |
ad4b5beb CMN |
87 | } |
88 | ||
04865aa0 CMN |
89 | git_oid_cpy(&obj_id, git_reference_target(resolved)); |
90 | git_reference_free(resolved); | |
91 | ||
25e0b157 | 92 | head = git__calloc(1, sizeof(git_remote_head)); |
ac3d33df | 93 | GIT_ERROR_CHECK_ALLOC(head); |
25e0b157 RB |
94 | |
95 | head->name = git__strdup(name); | |
ac3d33df | 96 | GIT_ERROR_CHECK_ALLOC(head->name); |
25e0b157 | 97 | |
04865aa0 CMN |
98 | git_oid_cpy(&head->oid, &obj_id); |
99 | ||
ac3d33df | 100 | if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) { |
04865aa0 | 101 | head->symref_target = git__strdup(git_reference_symbolic_target(ref)); |
ac3d33df | 102 | GIT_ERROR_CHECK_ALLOC(head->symref_target); |
04865aa0 CMN |
103 | } |
104 | git_reference_free(ref); | |
25e0b157 RB |
105 | |
106 | if ((error = git_vector_insert(&t->refs, head)) < 0) { | |
4c4408c3 | 107 | free_head(head); |
25e0b157 | 108 | return error; |
f201d613 | 109 | } |
d6258deb CMN |
110 | |
111 | /* If it's not a tag, we don't need to try to peel it */ | |
112 | if (git__prefixcmp(name, GIT_REFS_TAGS_DIR)) | |
a62053a0 | 113 | return 0; |
d6258deb | 114 | |
ac3d33df | 115 | if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0) |
25e0b157 | 116 | return error; |
d6258deb | 117 | |
db1f7e59 | 118 | head = NULL; |
119 | ||
20858f6e | 120 | /* If it's not an annotated tag, or if we're mocking |
121 | * git-receive-pack, just get out */ | |
ac3d33df | 122 | if (git_object_type(obj) != GIT_OBJECT_TAG || |
20858f6e | 123 | t->direction != GIT_DIRECTION_FETCH) { |
a62053a0 CMN |
124 | git_object_free(obj); |
125 | return 0; | |
126 | } | |
d6258deb CMN |
127 | |
128 | /* And if it's a tag, peel it, and add it to the list */ | |
6762fe08 | 129 | head = git__calloc(1, sizeof(git_remote_head)); |
ac3d33df | 130 | GIT_ERROR_CHECK_ALLOC(head); |
25e0b157 | 131 | |
e579e0f7 | 132 | if (git_str_join(&buf, 0, name, peeled) < 0) { |
6f73e026 | 133 | free_head(head); |
a62053a0 | 134 | return -1; |
6f73e026 | 135 | } |
e579e0f7 | 136 | head->name = git_str_detach(&buf); |
d6258deb | 137 | |
25e0b157 RB |
138 | if (!(error = git_tag_peel(&target, (git_tag *)obj))) { |
139 | git_oid_cpy(&head->oid, git_object_id(target)); | |
79fd4230 | 140 | |
25e0b157 | 141 | if ((error = git_vector_insert(&t->refs, head)) < 0) { |
4c4408c3 | 142 | free_head(head); |
25e0b157 RB |
143 | } |
144 | } | |
75abd2b9 | 145 | |
45e79e37 | 146 | git_object_free(obj); |
a62053a0 | 147 | git_object_free(target); |
25e0b157 RB |
148 | |
149 | return error; | |
d6258deb CMN |
150 | } |
151 | ||
d88d4311 | 152 | static int store_refs(transport_local *t) |
8f866dae | 153 | { |
edbaa63a AS |
154 | size_t i; |
155 | git_remote_head *head; | |
d88d4311 | 156 | git_strarray ref_names = {0}; |
d6258deb | 157 | |
c25aa7cd | 158 | GIT_ASSERT_ARG(t); |
7e305056 | 159 | |
edbaa63a | 160 | if (git_reference_list(&ref_names, t->repo) < 0) |
a62053a0 | 161 | goto on_error; |
d6258deb | 162 | |
edbaa63a AS |
163 | /* Clear all heads we might have fetched in a previous connect */ |
164 | git_vector_foreach(&t->refs, i, head) { | |
165 | git__free(head->name); | |
166 | git__free(head); | |
167 | } | |
168 | ||
169 | /* Clear the vector so we can reuse it */ | |
170 | git_vector_clear(&t->refs); | |
171 | ||
7e305056 | 172 | /* Sort the references first */ |
d88d4311 | 173 | git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb); |
7e305056 | 174 | |
20858f6e | 175 | /* Add HEAD iff direction is fetch */ |
176 | if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0) | |
a62053a0 | 177 | goto on_error; |
7e305056 | 178 | |
d88d4311 | 179 | for (i = 0; i < ref_names.count; ++i) { |
a62053a0 CMN |
180 | if (add_ref(t, ref_names.strings[i]) < 0) |
181 | goto on_error; | |
7e305056 | 182 | } |
d6258deb | 183 | |
67ba7d20 | 184 | t->have_refs = 1; |
22a2d3d5 | 185 | git_strarray_dispose(&ref_names); |
a62053a0 CMN |
186 | return 0; |
187 | ||
188 | on_error: | |
41fb1ca0 | 189 | git_vector_free(&t->refs); |
22a2d3d5 | 190 | git_strarray_dispose(&ref_names); |
a62053a0 | 191 | return -1; |
d88d4311 | 192 | } |
0a9a38e5 | 193 | |
d88d4311 VM |
194 | /* |
195 | * Try to open the url as a git directory. The direction doesn't | |
04865aa0 | 196 | * matter in this case because we're calculating the heads ourselves. |
d88d4311 | 197 | */ |
091361f5 PK |
198 | static int local_connect( |
199 | git_transport *transport, | |
200 | const char *url, | |
e579e0f7 MB |
201 | int direction, |
202 | const git_remote_connect_options *connect_opts) | |
d88d4311 VM |
203 | { |
204 | git_repository *repo; | |
205 | int error; | |
e579e0f7 | 206 | transport_local *t = (transport_local *)transport; |
d88d4311 | 207 | const char *path; |
e579e0f7 | 208 | git_str buf = GIT_STR_INIT; |
091361f5 | 209 | |
5187b609 CMN |
210 | if (t->connected) |
211 | return 0; | |
212 | ||
e579e0f7 MB |
213 | if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0) |
214 | return -1; | |
215 | ||
f7fcb18f CMN |
216 | free_heads(&t->refs); |
217 | ||
41fb1ca0 | 218 | t->url = git__strdup(url); |
ac3d33df | 219 | GIT_ERROR_CHECK_ALLOC(t->url); |
41fb1ca0 | 220 | t->direction = direction; |
d88d4311 | 221 | |
8bf476ac | 222 | /* 'url' may be a url or path; convert to a path */ |
e579e0f7 MB |
223 | if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) { |
224 | git_str_dispose(&buf); | |
8bf476ac | 225 | return error; |
a62053a0 | 226 | } |
e579e0f7 | 227 | path = git_str_cstr(&buf); |
d88d4311 VM |
228 | |
229 | error = git_repository_open(&repo, path); | |
e2580375 | 230 | |
e579e0f7 | 231 | git_str_dispose(&buf); |
e2580375 | 232 | |
a62053a0 CMN |
233 | if (error < 0) |
234 | return -1; | |
d88d4311 | 235 | |
db1f7e59 | 236 | t->repo = repo; |
237 | ||
a62053a0 CMN |
238 | if (store_refs(t) < 0) |
239 | return -1; | |
d88d4311 | 240 | |
41fb1ca0 PK |
241 | t->connected = 1; |
242 | ||
243 | return 0; | |
244 | } | |
245 | ||
e579e0f7 MB |
246 | static int local_set_connect_opts( |
247 | git_transport *transport, | |
248 | const git_remote_connect_options *connect_opts) | |
249 | { | |
250 | transport_local *t = (transport_local *)transport; | |
251 | ||
252 | if (!t->connected) { | |
253 | git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected"); | |
254 | return -1; | |
255 | } | |
256 | ||
257 | return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts); | |
258 | } | |
259 | ||
260 | static int local_capabilities(unsigned int *capabilities, git_transport *transport) | |
261 | { | |
262 | GIT_UNUSED(transport); | |
263 | ||
264 | *capabilities = GIT_REMOTE_CAPABILITY_TIP_OID | | |
265 | GIT_REMOTE_CAPABILITY_REACHABLE_OID; | |
266 | return 0; | |
267 | } | |
268 | ||
359dce72 | 269 | static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport) |
41fb1ca0 PK |
270 | { |
271 | transport_local *t = (transport_local *)transport; | |
41fb1ca0 | 272 | |
67ba7d20 | 273 | if (!t->have_refs) { |
ac3d33df | 274 | git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs"); |
41fb1ca0 PK |
275 | return -1; |
276 | } | |
277 | ||
25e0b157 | 278 | *out = (const git_remote_head **)t->refs.contents; |
359dce72 | 279 | *size = t->refs.length; |
d88d4311 | 280 | |
a62053a0 | 281 | return 0; |
d6258deb CMN |
282 | } |
283 | ||
41fb1ca0 PK |
284 | static int local_negotiate_fetch( |
285 | git_transport *transport, | |
286 | git_repository *repo, | |
505da062 BS |
287 | const git_remote_head * const *refs, |
288 | size_t count) | |
a167002f | 289 | { |
505da062 BS |
290 | transport_local *t = (transport_local*)transport; |
291 | git_remote_head *rhead; | |
292 | unsigned int i; | |
293 | ||
41fb1ca0 PK |
294 | GIT_UNUSED(refs); |
295 | GIT_UNUSED(count); | |
a167002f | 296 | |
505da062 BS |
297 | /* Fill in the loids */ |
298 | git_vector_foreach(&t->refs, i, rhead) { | |
299 | git_object *obj; | |
300 | ||
301 | int error = git_revparse_single(&obj, repo, rhead->name); | |
302 | if (!error) | |
303 | git_oid_cpy(&rhead->loid, git_object_id(obj)); | |
304 | else if (error != GIT_ENOTFOUND) | |
305 | return error; | |
9f77b3f6 | 306 | else |
ac3d33df | 307 | git_error_clear(); |
3dee3655 | 308 | git_object_free(obj); |
505da062 BS |
309 | } |
310 | ||
311 | return 0; | |
312 | } | |
313 | ||
20858f6e | 314 | static int local_push_update_remote_ref( |
315 | git_repository *remote_repo, | |
316 | const char *lref, | |
317 | const char *rref, | |
318 | git_oid *loid, | |
319 | git_oid *roid) | |
320 | { | |
321 | int error; | |
322 | git_reference *remote_ref = NULL; | |
323 | ||
d5c84f67 CMN |
324 | /* check for lhs, if it's empty it means to delete */ |
325 | if (lref[0] != '\0') { | |
20858f6e | 326 | /* Create or update a ref */ |
d5c84f67 | 327 | error = git_reference_create(NULL, remote_repo, rref, loid, |
22a2d3d5 | 328 | !git_oid_is_zero(roid), NULL); |
20858f6e | 329 | } else { |
330 | /* Delete a ref */ | |
331 | if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) { | |
332 | if (error == GIT_ENOTFOUND) | |
333 | error = 0; | |
334 | return error; | |
335 | } | |
336 | ||
d5c84f67 | 337 | error = git_reference_delete(remote_ref); |
20858f6e | 338 | git_reference_free(remote_ref); |
339 | } | |
340 | ||
d5c84f67 | 341 | return error; |
20858f6e | 342 | } |
343 | ||
22a2d3d5 | 344 | static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload) |
4a5b781a CMN |
345 | { |
346 | const git_remote_callbacks *cbs = payload; | |
347 | ||
348 | if (!cbs || !cbs->push_transfer_progress) | |
349 | return 0; | |
350 | ||
351 | return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes, | |
352 | cbs->payload); | |
353 | } | |
354 | ||
20858f6e | 355 | static int local_push( |
356 | git_transport *transport, | |
e579e0f7 | 357 | git_push *push) |
20858f6e | 358 | { |
359 | transport_local *t = (transport_local *)transport; | |
e579e0f7 | 360 | git_remote_callbacks *cbs = &t->connect_opts.callbacks; |
20858f6e | 361 | git_repository *remote_repo = NULL; |
362 | push_spec *spec; | |
363 | char *url = NULL; | |
4e974c97 | 364 | const char *path; |
e579e0f7 | 365 | git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT; |
20858f6e | 366 | int error; |
20858f6e | 367 | size_t j; |
368 | ||
8bf476ac | 369 | /* 'push->remote->url' may be a url or path; convert to a path */ |
e579e0f7 MB |
370 | if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) { |
371 | git_str_dispose(&buf); | |
8bf476ac | 372 | return error; |
4e974c97 | 373 | } |
e579e0f7 | 374 | path = git_str_cstr(&buf); |
4e974c97 GD |
375 | |
376 | error = git_repository_open(&remote_repo, path); | |
377 | ||
e579e0f7 | 378 | git_str_dispose(&buf); |
4e974c97 GD |
379 | |
380 | if (error < 0) | |
20858f6e | 381 | return error; |
382 | ||
1fed6b07 | 383 | /* We don't currently support pushing locally to non-bare repos. Proper |
20858f6e | 384 | non-bare repo push support would require checking configs to see if |
81c0fb08 CMN |
385 | we should override the default 'don't let this happen' behavior. |
386 | ||
387 | Note that this is only an issue when pushing to the current branch, | |
388 | but we forbid all pushes just in case */ | |
20858f6e | 389 | if (!remote_repo->is_bare) { |
f42d546c | 390 | error = GIT_EBAREREPO; |
ac3d33df | 391 | git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos."); |
20858f6e | 392 | goto on_error; |
393 | } | |
394 | ||
e579e0f7 MB |
395 | if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0 |
396 | || (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0) | |
20858f6e | 397 | goto on_error; |
398 | ||
4a5b781a | 399 | error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs); |
e579e0f7 | 400 | git_str_dispose(&odb_path); |
4a5b781a CMN |
401 | |
402 | if (error < 0) | |
403 | goto on_error; | |
20858f6e | 404 | |
405 | push->unpack_ok = 1; | |
406 | ||
407 | git_vector_foreach(&push->specs, j, spec) { | |
408 | push_status *status; | |
409 | const git_error *last; | |
aad638f3 | 410 | char *ref = spec->refspec.dst; |
20858f6e | 411 | |
392702ee | 412 | status = git__calloc(1, sizeof(push_status)); |
20858f6e | 413 | if (!status) |
414 | goto on_error; | |
415 | ||
416 | status->ref = git__strdup(ref); | |
417 | if (!status->ref) { | |
418 | git_push_status_free(status); | |
419 | goto on_error; | |
420 | } | |
421 | ||
aad638f3 | 422 | error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst, |
20858f6e | 423 | &spec->loid, &spec->roid); |
424 | ||
425 | switch (error) { | |
426 | case GIT_OK: | |
427 | break; | |
428 | case GIT_EINVALIDSPEC: | |
429 | status->msg = git__strdup("funny refname"); | |
430 | break; | |
431 | case GIT_ENOTFOUND: | |
432 | status->msg = git__strdup("Remote branch not found to delete"); | |
433 | break; | |
434 | default: | |
ac3d33df | 435 | last = git_error_last(); |
20858f6e | 436 | |
437 | if (last && last->message) | |
438 | status->msg = git__strdup(last->message); | |
439 | else | |
440 | status->msg = git__strdup("Unspecified error encountered"); | |
441 | break; | |
442 | } | |
443 | ||
444 | /* failed to allocate memory for a status message */ | |
445 | if (error < 0 && !status->msg) { | |
446 | git_push_status_free(status); | |
447 | goto on_error; | |
448 | } | |
449 | ||
450 | /* failed to insert the ref update status */ | |
451 | if ((error = git_vector_insert(&push->status, status)) < 0) { | |
452 | git_push_status_free(status); | |
453 | goto on_error; | |
454 | } | |
455 | } | |
456 | ||
457 | if (push->specs.length) { | |
20858f6e | 458 | url = git__strdup(t->url); |
459 | ||
460 | if (!url || t->parent.close(&t->parent) < 0 || | |
461 | t->parent.connect(&t->parent, url, | |
e579e0f7 | 462 | GIT_DIRECTION_PUSH, NULL)) |
20858f6e | 463 | goto on_error; |
464 | } | |
465 | ||
466 | error = 0; | |
467 | ||
468 | on_error: | |
469 | git_repository_free(remote_repo); | |
470 | git__free(url); | |
471 | ||
472 | return error; | |
473 | } | |
474 | ||
505da062 | 475 | typedef struct foreach_data { |
22a2d3d5 UG |
476 | git_indexer_progress *stats; |
477 | git_indexer_progress_cb progress_cb; | |
505da062 BS |
478 | void *progress_payload; |
479 | git_odb_writepack *writepack; | |
480 | } foreach_data; | |
481 | ||
482 | static int foreach_cb(void *buf, size_t len, void *payload) | |
483 | { | |
484 | foreach_data *data = (foreach_data*)payload; | |
485 | ||
486 | data->stats->received_bytes += len; | |
a6154f21 | 487 | return data->writepack->append(data->writepack, buf, len, data->stats); |
505da062 BS |
488 | } |
489 | ||
4fd2bda9 | 490 | static const char *counting_objects_fmt = "Counting objects %d\r"; |
8cec2b8a CMN |
491 | static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)"; |
492 | ||
493 | static int local_counting(int stage, unsigned int current, unsigned int total, void *payload) | |
494 | { | |
e579e0f7 | 495 | git_str progress_info = GIT_STR_INIT; |
8cec2b8a | 496 | transport_local *t = payload; |
542a7de0 | 497 | int error; |
8cec2b8a | 498 | |
e579e0f7 | 499 | if (!t->connect_opts.callbacks.sideband_progress) |
8cec2b8a CMN |
500 | return 0; |
501 | ||
502 | if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) { | |
e579e0f7 | 503 | git_str_printf(&progress_info, counting_objects_fmt, current); |
8cec2b8a CMN |
504 | } else if (stage == GIT_PACKBUILDER_DELTAFICATION) { |
505 | float perc = (((float) current) / total) * 100; | |
e579e0f7 | 506 | git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total); |
8cec2b8a | 507 | if (current == total) |
e579e0f7 | 508 | git_str_printf(&progress_info, ", done\n"); |
8cec2b8a | 509 | else |
e579e0f7 | 510 | git_str_putc(&progress_info, '\r'); |
8cec2b8a CMN |
511 | |
512 | } | |
513 | ||
e579e0f7 MB |
514 | if (git_str_oom(&progress_info)) |
515 | return -1; | |
516 | ||
517 | if (progress_info.size > INT_MAX) { | |
518 | git_error_set(GIT_ERROR_NET, "remote sent overly large progress data"); | |
519 | git_str_dispose(&progress_info); | |
8cec2b8a | 520 | return -1; |
e579e0f7 MB |
521 | } |
522 | ||
8cec2b8a | 523 | |
e579e0f7 MB |
524 | error = t->connect_opts.callbacks.sideband_progress( |
525 | progress_info.ptr, | |
526 | (int)progress_info.size, | |
527 | t->connect_opts.callbacks.payload); | |
542a7de0 | 528 | |
e579e0f7 | 529 | git_str_dispose(&progress_info); |
542a7de0 | 530 | return error; |
8cec2b8a | 531 | } |
4fd2bda9 | 532 | |
eae0bfdc PP |
533 | static int foreach_reference_cb(git_reference *reference, void *payload) |
534 | { | |
535 | git_revwalk *walk = (git_revwalk *)payload; | |
4b3ec53c XL |
536 | int error; |
537 | ||
ac3d33df | 538 | if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) { |
4b3ec53c XL |
539 | git_reference_free(reference); |
540 | return 0; | |
541 | } | |
eae0bfdc | 542 | |
4b3ec53c | 543 | error = git_revwalk_hide(walk, git_reference_target(reference)); |
eae0bfdc PP |
544 | /* The reference is in the local repository, so the target may not |
545 | * exist on the remote. It also may not be a commit. */ | |
ac3d33df JK |
546 | if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) { |
547 | git_error_clear(); | |
eae0bfdc PP |
548 | error = 0; |
549 | } | |
550 | ||
551 | git_reference_free(reference); | |
552 | ||
553 | return error; | |
554 | } | |
555 | ||
505da062 BS |
556 | static int local_download_pack( |
557 | git_transport *transport, | |
558 | git_repository *repo, | |
e579e0f7 | 559 | git_indexer_progress *stats) |
505da062 BS |
560 | { |
561 | transport_local *t = (transport_local*)transport; | |
562 | git_revwalk *walk = NULL; | |
563 | git_remote_head *rhead; | |
564 | unsigned int i; | |
565 | int error = -1; | |
505da062 BS |
566 | git_packbuilder *pack = NULL; |
567 | git_odb_writepack *writepack = NULL; | |
90207709 | 568 | git_odb *odb = NULL; |
e579e0f7 MB |
569 | git_str progress_info = GIT_STR_INIT; |
570 | foreach_data data = {0}; | |
505da062 BS |
571 | |
572 | if ((error = git_revwalk_new(&walk, t->repo)) < 0) | |
573 | goto cleanup; | |
e579e0f7 | 574 | |
505da062 BS |
575 | git_revwalk_sorting(walk, GIT_SORT_TIME); |
576 | ||
577 | if ((error = git_packbuilder_new(&pack, t->repo)) < 0) | |
578 | goto cleanup; | |
579 | ||
8cec2b8a CMN |
580 | git_packbuilder_set_callbacks(pack, local_counting, t); |
581 | ||
505da062 BS |
582 | stats->total_objects = 0; |
583 | stats->indexed_objects = 0; | |
584 | stats->received_objects = 0; | |
585 | stats->received_bytes = 0; | |
586 | ||
587 | git_vector_foreach(&t->refs, i, rhead) { | |
588 | git_object *obj; | |
ac3d33df | 589 | if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0) |
505da062 BS |
590 | goto cleanup; |
591 | ||
ac3d33df | 592 | if (git_object_type(obj) == GIT_OBJECT_COMMIT) { |
505da062 BS |
593 | /* Revwalker includes only wanted commits */ |
594 | error = git_revwalk_push(walk, &rhead->oid); | |
505da062 | 595 | } else { |
c84a9dd2 CMN |
596 | /* Tag or some other wanted object. Add it on its own */ |
597 | error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name); | |
505da062 | 598 | } |
0cb16fe9 | 599 | git_object_free(obj); |
e68b31a1 CMN |
600 | if (error < 0) |
601 | goto cleanup; | |
505da062 BS |
602 | } |
603 | ||
eae0bfdc PP |
604 | if ((error = git_reference_foreach(repo, foreach_reference_cb, walk))) |
605 | goto cleanup; | |
606 | ||
e68b31a1 CMN |
607 | if ((error = git_packbuilder_insert_walk(pack, walk))) |
608 | goto cleanup; | |
609 | ||
e579e0f7 MB |
610 | if (t->connect_opts.callbacks.sideband_progress) { |
611 | if ((error = git_str_printf( | |
612 | &progress_info, | |
613 | counting_objects_fmt, | |
614 | git_packbuilder_object_count(pack))) < 0 || | |
615 | (error = t->connect_opts.callbacks.sideband_progress( | |
616 | progress_info.ptr, | |
617 | (int)progress_info.size, | |
618 | t->connect_opts.callbacks.payload)) < 0) | |
619 | goto cleanup; | |
620 | } | |
4fd2bda9 | 621 | |
505da062 | 622 | /* Walk the objects, building a packfile */ |
90207709 BS |
623 | if ((error = git_repository_odb__weakptr(&odb, repo)) < 0) |
624 | goto cleanup; | |
505da062 | 625 | |
4fd2bda9 | 626 | /* One last one with the newline */ |
e579e0f7 MB |
627 | if (t->connect_opts.callbacks.sideband_progress) { |
628 | git_str_clear(&progress_info); | |
629 | ||
630 | if ((error = git_str_printf( | |
631 | &progress_info, | |
632 | counting_objects_fmt, | |
633 | git_packbuilder_object_count(pack))) < 0 || | |
634 | (error = git_str_putc(&progress_info, '\n')) < 0 || | |
635 | (error = t->connect_opts.callbacks.sideband_progress( | |
636 | progress_info.ptr, | |
637 | (int)progress_info.size, | |
638 | t->connect_opts.callbacks.payload)) < 0) | |
639 | goto cleanup; | |
640 | } | |
4fd2bda9 | 641 | |
e579e0f7 MB |
642 | if ((error = git_odb_write_pack( |
643 | &writepack, | |
644 | odb, | |
645 | t->connect_opts.callbacks.transfer_progress, | |
646 | t->connect_opts.callbacks.payload)) < 0) | |
90207709 | 647 | goto cleanup; |
505da062 BS |
648 | |
649 | /* Write the data to the ODB */ | |
e579e0f7 MB |
650 | data.stats = stats; |
651 | data.progress_cb = t->connect_opts.callbacks.transfer_progress; | |
652 | data.progress_payload = t->connect_opts.callbacks.payload; | |
653 | data.writepack = writepack; | |
505da062 | 654 | |
e579e0f7 MB |
655 | /* autodetect */ |
656 | git_packbuilder_set_threads(pack, 0); | |
0ef54a63 | 657 | |
e579e0f7 MB |
658 | if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0) |
659 | goto cleanup; | |
e68b31a1 | 660 | |
505da062 BS |
661 | error = writepack->commit(writepack, stats); |
662 | ||
663 | cleanup: | |
664 | if (writepack) writepack->free(writepack); | |
e579e0f7 | 665 | git_str_dispose(&progress_info); |
505da062 BS |
666 | git_packbuilder_free(pack); |
667 | git_revwalk_free(walk); | |
668 | return error; | |
a167002f MS |
669 | } |
670 | ||
613d5eb9 | 671 | static int local_is_connected(git_transport *transport) |
41fb1ca0 PK |
672 | { |
673 | transport_local *t = (transport_local *)transport; | |
674 | ||
613d5eb9 | 675 | return t->connected; |
41fb1ca0 PK |
676 | } |
677 | ||
41fb1ca0 PK |
678 | static void local_cancel(git_transport *transport) |
679 | { | |
680 | transport_local *t = (transport_local *)transport; | |
681 | ||
c25aa7cd | 682 | git_atomic32_set(&t->cancelled, 1); |
41fb1ca0 PK |
683 | } |
684 | ||
854eccbb | 685 | static int local_close(git_transport *transport) |
d6258deb | 686 | { |
d88d4311 VM |
687 | transport_local *t = (transport_local *)transport; |
688 | ||
41fb1ca0 | 689 | t->connected = 0; |
20858f6e | 690 | |
691 | if (t->repo) { | |
692 | git_repository_free(t->repo); | |
693 | t->repo = NULL; | |
694 | } | |
695 | ||
20858f6e | 696 | if (t->url) { |
697 | git__free(t->url); | |
698 | t->url = NULL; | |
699 | } | |
d88d4311 | 700 | |
a62053a0 | 701 | return 0; |
8f866dae CMN |
702 | } |
703 | ||
d6258deb CMN |
704 | static void local_free(git_transport *transport) |
705 | { | |
20858f6e | 706 | transport_local *t = (transport_local *)transport; |
9728cfde | 707 | |
f7fcb18f | 708 | free_heads(&t->refs); |
edbaa63a | 709 | |
20858f6e | 710 | /* Close the transport, if it's still open. */ |
711 | local_close(transport); | |
a2888919 | 712 | |
20858f6e | 713 | /* Free the transport */ |
3286c408 | 714 | git__free(t); |
d6258deb CMN |
715 | } |
716 | ||
8f866dae CMN |
717 | /************** |
718 | * Public API * | |
719 | **************/ | |
720 | ||
613d5eb9 | 721 | int git_transport_local(git_transport **out, git_remote *owner, void *param) |
8f866dae | 722 | { |
6f73e026 | 723 | int error; |
4e913309 CMN |
724 | transport_local *t; |
725 | ||
41fb1ca0 PK |
726 | GIT_UNUSED(param); |
727 | ||
10711769 | 728 | t = git__calloc(1, sizeof(transport_local)); |
ac3d33df | 729 | GIT_ERROR_CHECK_ALLOC(t); |
4e913309 | 730 | |
10711769 | 731 | t->parent.version = GIT_TRANSPORT_VERSION; |
4e913309 | 732 | t->parent.connect = local_connect; |
e579e0f7 MB |
733 | t->parent.set_connect_opts = local_set_connect_opts; |
734 | t->parent.capabilities = local_capabilities; | |
a167002f | 735 | t->parent.negotiate_fetch = local_negotiate_fetch; |
505da062 | 736 | t->parent.download_pack = local_download_pack; |
20858f6e | 737 | t->parent.push = local_push; |
4e913309 CMN |
738 | t->parent.close = local_close; |
739 | t->parent.free = local_free; | |
41fb1ca0 PK |
740 | t->parent.ls = local_ls; |
741 | t->parent.is_connected = local_is_connected; | |
41fb1ca0 | 742 | t->parent.cancel = local_cancel; |
4e913309 | 743 | |
6f73e026 JG |
744 | if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) { |
745 | git__free(t); | |
746 | return error; | |
747 | } | |
748 | ||
613d5eb9 PK |
749 | t->owner = owner; |
750 | ||
4e913309 | 751 | *out = (git_transport *) t; |
8f866dae | 752 | |
a62053a0 | 753 | return 0; |
8f866dae | 754 | } |