1 /*
2 * Copyright (C) the libgit2 contributors. All rights reserved.
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 */
7
8 #include "common.h"
9
10 #include "git2/types.h"
11 #include "git2/net.h"
12 #include "git2/repository.h"
13 #include "git2/object.h"
14 #include "git2/tag.h"
15 #include "git2/transport.h"
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"
21
22 #include "pack-objects.h"
23 #include "refs.h"
24 #include "posix.h"
25 #include "path.h"
26 #include "buffer.h"
27 #include "repository.h"
28 #include "odb.h"
29 #include "push.h"
30 #include "remote.h"
31 #include "proxy.h"
32
33 typedef struct {
34 git_transport parent;
35 git_remote *owner;
36 char *url;
37 int direction;
38 int flags;
39 git_atomic32 cancelled;
40 git_repository *repo;
41 git_transport_message_cb progress_cb;
42 git_transport_message_cb error_cb;
43 void *message_cb_payload;
44 git_vector refs;
45 unsigned connected : 1,
46 have_refs : 1;
47 } transport_local;
48
free_head(git_remote_head * head)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
free_heads(git_vector * heads)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
add_ref(transport_local * t,const char * name)67 static int add_ref(transport_local *t, const char *name)
68 {
69 const char peeled[] = "^{}";
70 git_reference *ref, *resolved;
71 git_remote_head *head;
72 git_oid obj_id;
73 git_object *obj = NULL, *target = NULL;
74 git_buf buf = GIT_BUF_INIT;
75 int error;
76
77 if ((error = git_reference_lookup(&ref, t->repo, name)) < 0)
78 return error;
79
80 error = git_reference_resolve(&resolved, ref);
81 if (error < 0) {
82 git_reference_free(ref);
83 if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
84 /* This is actually okay. Empty repos often have a HEAD that
85 * points to a nonexistent "refs/heads/master". */
86 git_error_clear();
87 return 0;
88 }
89 return error;
90 }
91
92 git_oid_cpy(&obj_id, git_reference_target(resolved));
93 git_reference_free(resolved);
94
95 head = git__calloc(1, sizeof(git_remote_head));
96 GIT_ERROR_CHECK_ALLOC(head);
97
98 head->name = git__strdup(name);
99 GIT_ERROR_CHECK_ALLOC(head->name);
100
101 git_oid_cpy(&head->oid, &obj_id);
102
103 if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) {
104 head->symref_target = git__strdup(git_reference_symbolic_target(ref));
105 GIT_ERROR_CHECK_ALLOC(head->symref_target);
106 }
107 git_reference_free(ref);
108
109 if ((error = git_vector_insert(&t->refs, head)) < 0) {
110 free_head(head);
111 return error;
112 }
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))
116 return 0;
117
118 if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0)
119 return error;
120
121 head = NULL;
122
123 /* If it's not an annotated tag, or if we're mocking
124 * git-receive-pack, just get out */
125 if (git_object_type(obj) != GIT_OBJECT_TAG ||
126 t->direction != GIT_DIRECTION_FETCH) {
127 git_object_free(obj);
128 return 0;
129 }
130
131 /* And if it's a tag, peel it, and add it to the list */
132 head = git__calloc(1, sizeof(git_remote_head));
133 GIT_ERROR_CHECK_ALLOC(head);
134
135 if (git_buf_join(&buf, 0, name, peeled) < 0) {
136 free_head(head);
137 return -1;
138 }
139 head->name = git_buf_detach(&buf);
140
141 if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
142 git_oid_cpy(&head->oid, git_object_id(target));
143
144 if ((error = git_vector_insert(&t->refs, head)) < 0) {
145 free_head(head);
146 }
147 }
148
149 git_object_free(obj);
150 git_object_free(target);
151
152 return error;
153 }
154
store_refs(transport_local * t)155 static int store_refs(transport_local *t)
156 {
157 size_t i;
158 git_remote_head *head;
159 git_strarray ref_names = {0};
160
161 GIT_ASSERT_ARG(t);
162
163 if (git_reference_list(&ref_names, t->repo) < 0)
164 goto on_error;
165
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
175 /* Sort the references first */
176 git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
177
178 /* Add HEAD iff direction is fetch */
179 if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
180 goto on_error;
181
182 for (i = 0; i < ref_names.count; ++i) {
183 if (add_ref(t, ref_names.strings[i]) < 0)
184 goto on_error;
185 }
186
187 t->have_refs = 1;
188 git_strarray_dispose(&ref_names);
189 return 0;
190
191 on_error:
192 git_vector_free(&t->refs);
193 git_strarray_dispose(&ref_names);
194 return -1;
195 }
196
197 /*
198 * Try to open the url as a git directory. The direction doesn't
199 * matter in this case because we're calculating the heads ourselves.
200 */
local_connect(git_transport * transport,const char * url,git_credential_acquire_cb cred_acquire_cb,void * cred_acquire_payload,const git_proxy_options * proxy,int direction,int flags)201 static int local_connect(
202 git_transport *transport,
203 const char *url,
204 git_credential_acquire_cb cred_acquire_cb,
205 void *cred_acquire_payload,
206 const git_proxy_options *proxy,
207 int direction, int flags)
208 {
209 git_repository *repo;
210 int error;
211 transport_local *t = (transport_local *) transport;
212 const char *path;
213 git_buf buf = GIT_BUF_INIT;
214
215 GIT_UNUSED(cred_acquire_cb);
216 GIT_UNUSED(cred_acquire_payload);
217 GIT_UNUSED(proxy);
218
219 if (t->connected)
220 return 0;
221
222 free_heads(&t->refs);
223
224 t->url = git__strdup(url);
225 GIT_ERROR_CHECK_ALLOC(t->url);
226 t->direction = direction;
227 t->flags = flags;
228
229 /* 'url' may be a url or path; convert to a path */
230 if ((error = git_path_from_url_or_path(&buf, url)) < 0) {
231 git_buf_dispose(&buf);
232 return error;
233 }
234 path = git_buf_cstr(&buf);
235
236 error = git_repository_open(&repo, path);
237
238 git_buf_dispose(&buf);
239
240 if (error < 0)
241 return -1;
242
243 t->repo = repo;
244
245 if (store_refs(t) < 0)
246 return -1;
247
248 t->connected = 1;
249
250 return 0;
251 }
252
local_ls(const git_remote_head *** out,size_t * size,git_transport * transport)253 static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
254 {
255 transport_local *t = (transport_local *)transport;
256
257 if (!t->have_refs) {
258 git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
259 return -1;
260 }
261
262 *out = (const git_remote_head **)t->refs.contents;
263 *size = t->refs.length;
264
265 return 0;
266 }
267
local_negotiate_fetch(git_transport * transport,git_repository * repo,const git_remote_head * const * refs,size_t count)268 static int local_negotiate_fetch(
269 git_transport *transport,
270 git_repository *repo,
271 const git_remote_head * const *refs,
272 size_t count)
273 {
274 transport_local *t = (transport_local*)transport;
275 git_remote_head *rhead;
276 unsigned int i;
277
278 GIT_UNUSED(refs);
279 GIT_UNUSED(count);
280
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;
290 else
291 git_error_clear();
292 git_object_free(obj);
293 }
294
295 return 0;
296 }
297
local_push_update_remote_ref(git_repository * remote_repo,const char * lref,const char * rref,git_oid * loid,git_oid * roid)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
308 /* check for lhs, if it's empty it means to delete */
309 if (lref[0] != '\0') {
310 /* Create or update a ref */
311 error = git_reference_create(NULL, remote_repo, rref, loid,
312 !git_oid_is_zero(roid), NULL);
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
321 error = git_reference_delete(remote_ref);
322 git_reference_free(remote_ref);
323 }
324
325 return error;
326 }
327
transfer_to_push_transfer(const git_indexer_progress * stats,void * payload)328 static int transfer_to_push_transfer(const git_indexer_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
local_push(git_transport * transport,git_push * push,const git_remote_callbacks * cbs)339 static int local_push(
340 git_transport *transport,
341 git_push *push,
342 const git_remote_callbacks *cbs)
343 {
344 transport_local *t = (transport_local *)transport;
345 git_repository *remote_repo = NULL;
346 push_spec *spec;
347 char *url = NULL;
348 const char *path;
349 git_buf buf = GIT_BUF_INIT, odb_path = GIT_BUF_INIT;
350 int error;
351 size_t j;
352
353 GIT_UNUSED(cbs);
354
355 /* 'push->remote->url' may be a url or path; convert to a path */
356 if ((error = git_path_from_url_or_path(&buf, push->remote->url)) < 0) {
357 git_buf_dispose(&buf);
358 return error;
359 }
360 path = git_buf_cstr(&buf);
361
362 error = git_repository_open(&remote_repo, path);
363
364 git_buf_dispose(&buf);
365
366 if (error < 0)
367 return error;
368
369 /* We don't currently support pushing locally to non-bare repos. Proper
370 non-bare repo push support would require checking configs to see if
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 */
375 if (!remote_repo->is_bare) {
376 error = GIT_EBAREREPO;
377 git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos.");
378 goto on_error;
379 }
380
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)
383 goto on_error;
384
385 error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
386 git_buf_dispose(&odb_path);
387
388 if (error < 0)
389 goto on_error;
390
391 push->unpack_ok = 1;
392
393 git_vector_foreach(&push->specs, j, spec) {
394 push_status *status;
395 const git_error *last;
396 char *ref = spec->refspec.dst;
397
398 status = git__calloc(1, sizeof(push_status));
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
408 error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst,
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:
421 last = git_error_last();
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,
449 NULL, NULL, NULL, GIT_DIRECTION_PUSH, flags))
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
462 typedef struct foreach_data {
463 git_indexer_progress *stats;
464 git_indexer_progress_cb progress_cb;
465 void *progress_payload;
466 git_odb_writepack *writepack;
467 } foreach_data;
468
foreach_cb(void * buf,size_t len,void * payload)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;
474 return data->writepack->append(data->writepack, buf, len, data->stats);
475 }
476
477 static const char *counting_objects_fmt = "Counting objects %d\r";
478 static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)";
479
local_counting(int stage,unsigned int current,unsigned int total,void * payload)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;
484 int error;
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
504 error = t->progress_cb(git_buf_cstr(&progress_info), (int)git_buf_len(&progress_info), t->message_cb_payload);
505 git_buf_dispose(&progress_info);
506
507 return error;
508 }
509
foreach_reference_cb(git_reference * reference,void * payload)510 static int foreach_reference_cb(git_reference *reference, void *payload)
511 {
512 git_revwalk *walk = (git_revwalk *)payload;
513 int error;
514
515 if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) {
516 git_reference_free(reference);
517 return 0;
518 }
519
520 error = git_revwalk_hide(walk, git_reference_target(reference));
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. */
523 if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) {
524 git_error_clear();
525 error = 0;
526 }
527
528 git_reference_free(reference);
529
530 return error;
531 }
532
local_download_pack(git_transport * transport,git_repository * repo,git_indexer_progress * stats,git_indexer_progress_cb progress_cb,void * progress_payload)533 static int local_download_pack(
534 git_transport *transport,
535 git_repository *repo,
536 git_indexer_progress *stats,
537 git_indexer_progress_cb progress_cb,
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;
545 git_packbuilder *pack = NULL;
546 git_odb_writepack *writepack = NULL;
547 git_odb *odb = NULL;
548 git_buf progress_info = GIT_BUF_INIT;
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
557 git_packbuilder_set_callbacks(pack, local_counting, t);
558
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;
566 if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0)
567 goto cleanup;
568
569 if (git_object_type(obj) == GIT_OBJECT_COMMIT) {
570 /* Revwalker includes only wanted commits */
571 error = git_revwalk_push(walk, &rhead->oid);
572 } else {
573 /* Tag or some other wanted object. Add it on its own */
574 error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name);
575 }
576 git_object_free(obj);
577 if (error < 0)
578 goto cleanup;
579 }
580
581 if ((error = git_reference_foreach(repo, foreach_reference_cb, walk)))
582 goto cleanup;
583
584 if ((error = git_packbuilder_insert_walk(pack, walk)))
585 goto cleanup;
586
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), (int)git_buf_len(&progress_info), t->message_cb_payload)) < 0)
592 goto cleanup;
593
594 /* Walk the objects, building a packfile */
595 if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
596 goto cleanup;
597
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), (int)git_buf_len(&progress_info), t->message_cb_payload)) < 0)
606 goto cleanup;
607
608 if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)
609 goto cleanup;
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
619 /* autodetect */
620 git_packbuilder_set_threads(pack, 0);
621
622 if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
623 goto cleanup;
624 }
625
626 error = writepack->commit(writepack, stats);
627
628 cleanup:
629 if (writepack) writepack->free(writepack);
630 git_buf_dispose(&progress_info);
631 git_packbuilder_free(pack);
632 git_revwalk_free(walk);
633 return error;
634 }
635
local_set_callbacks(git_transport * transport,git_transport_message_cb progress_cb,git_transport_message_cb error_cb,git_transport_certificate_check_cb certificate_check_cb,void * message_cb_payload)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
local_is_connected(git_transport * transport)654 static int local_is_connected(git_transport *transport)
655 {
656 transport_local *t = (transport_local *)transport;
657
658 return t->connected;
659 }
660
local_read_flags(git_transport * transport,int * flags)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
local_cancel(git_transport * transport)670 static void local_cancel(git_transport *transport)
671 {
672 transport_local *t = (transport_local *)transport;
673
674 git_atomic32_set(&t->cancelled, 1);
675 }
676
local_close(git_transport * transport)677 static int local_close(git_transport *transport)
678 {
679 transport_local *t = (transport_local *)transport;
680
681 t->connected = 0;
682
683 if (t->repo) {
684 git_repository_free(t->repo);
685 t->repo = NULL;
686 }
687
688 if (t->url) {
689 git__free(t->url);
690 t->url = NULL;
691 }
692
693 return 0;
694 }
695
local_free(git_transport * transport)696 static void local_free(git_transport *transport)
697 {
698 transport_local *t = (transport_local *)transport;
699
700 free_heads(&t->refs);
701
702 /* Close the transport, if it's still open. */
703 local_close(transport);
704
705 /* Free the transport */
706 git__free(t);
707 }
708
709 /**************
710 * Public API *
711 **************/
712
git_transport_local(git_transport ** out,git_remote * owner,void * param)713 int git_transport_local(git_transport **out, git_remote *owner, void *param)
714 {
715 int error;
716 transport_local *t;
717
718 GIT_UNUSED(param);
719
720 t = git__calloc(1, sizeof(transport_local));
721 GIT_ERROR_CHECK_ALLOC(t);
722
723 t->parent.version = GIT_TRANSPORT_VERSION;
724 t->parent.set_callbacks = local_set_callbacks;
725 t->parent.connect = local_connect;
726 t->parent.negotiate_fetch = local_negotiate_fetch;
727 t->parent.download_pack = local_download_pack;
728 t->parent.push = local_push;
729 t->parent.close = local_close;
730 t->parent.free = local_free;
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;
735
736 if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) {
737 git__free(t);
738 return error;
739 }
740
741 t->owner = owner;
742
743 *out = (git_transport *) t;
744
745 return 0;
746 }
747