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