]> git.proxmox.com Git - libgit2.git/blame - src/transports/local.c
Merge pull request #2168 from ethomson/clar
[libgit2.git] / src / transports / local.c
CommitLineData
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 */
8f866dae
CMN
7#include "common.h"
8#include "git2/types.h"
8f866dae
CMN
9#include "git2/net.h"
10#include "git2/repository.h"
d6258deb
CMN
11#include "git2/object.h"
12#include "git2/tag.h"
41fb1ca0 13#include "git2/transport.h"
505da062
BS
14#include "git2/revwalk.h"
15#include "git2/odb_backend.h"
16#include "git2/pack.h"
17#include "git2/commit.h"
18#include "git2/revparse.h"
20858f6e 19#include "git2/push.h"
505da062
BS
20#include "pack-objects.h"
21#include "refs.h"
84dd3820 22#include "posix.h"
e2580375 23#include "path.h"
24#include "buffer.h"
505da062
BS
25#include "repository.h"
26#include "odb.h"
20858f6e 27#include "push.h"
28#include "remote.h"
8f866dae 29
0a9a38e5 30typedef struct {
4e913309 31 git_transport parent;
613d5eb9 32 git_remote *owner;
41fb1ca0
PK
33 char *url;
34 int direction;
35 int flags;
36 git_atomic cancelled;
0a9a38e5 37 git_repository *repo;
41fb1ca0 38 git_vector refs;
67ba7d20
CMN
39 unsigned connected : 1,
40 have_refs : 1;
4e913309 41} transport_local;
0a9a38e5 42
d88d4311 43static int add_ref(transport_local *t, const char *name)
d6258deb 44{
d6258deb 45 const char peeled[] = "^{}";
25e0b157 46 git_oid head_oid;
d6258deb 47 git_remote_head *head;
a62053a0
CMN
48 git_object *obj = NULL, *target = NULL;
49 git_buf buf = GIT_BUF_INIT;
b524fe1a 50 int error;
d6258deb 51
25e0b157 52 error = git_reference_name_to_id(&head_oid, t->repo, name);
b524fe1a 53 if (error < 0) {
2a2d1ab0 54 if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
25e0b157
RB
55 /* This is actually okay. Empty repos often have a HEAD that
56 * points to a nonexistent "refs/heads/master". */
b524fe1a
BS
57 giterr_clear();
58 return 0;
59 }
60 return error;
ad4b5beb
CMN
61 }
62
25e0b157
RB
63 head = git__calloc(1, sizeof(git_remote_head));
64 GITERR_CHECK_ALLOC(head);
65
66 head->name = git__strdup(name);
67 GITERR_CHECK_ALLOC(head->name);
68
69 git_oid_cpy(&head->oid, &head_oid);
70
71 if ((error = git_vector_insert(&t->refs, head)) < 0) {
41fb1ca0
PK
72 git__free(head->name);
73 git__free(head);
25e0b157 74 return error;
f201d613 75 }
d6258deb
CMN
76
77 /* If it's not a tag, we don't need to try to peel it */
78 if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
a62053a0 79 return 0;
d6258deb 80
25e0b157
RB
81 if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY)) < 0)
82 return error;
d6258deb 83
db1f7e59 84 head = NULL;
85
20858f6e 86 /* If it's not an annotated tag, or if we're mocking
87 * git-receive-pack, just get out */
88 if (git_object_type(obj) != GIT_OBJ_TAG ||
89 t->direction != GIT_DIRECTION_FETCH) {
a62053a0
CMN
90 git_object_free(obj);
91 return 0;
92 }
d6258deb
CMN
93
94 /* And if it's a tag, peel it, and add it to the list */
6762fe08 95 head = git__calloc(1, sizeof(git_remote_head));
a62053a0 96 GITERR_CHECK_ALLOC(head);
25e0b157 97
a62053a0
CMN
98 if (git_buf_join(&buf, 0, name, peeled) < 0)
99 return -1;
a62053a0 100 head->name = git_buf_detach(&buf);
d6258deb 101
25e0b157
RB
102 if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
103 git_oid_cpy(&head->oid, git_object_id(target));
79fd4230 104
25e0b157
RB
105 if ((error = git_vector_insert(&t->refs, head)) < 0) {
106 git__free(head->name);
107 git__free(head);
108 }
109 }
75abd2b9 110
45e79e37 111 git_object_free(obj);
a62053a0 112 git_object_free(target);
25e0b157
RB
113
114 return error;
d6258deb
CMN
115}
116
d88d4311 117static int store_refs(transport_local *t)
8f866dae 118{
edbaa63a
AS
119 size_t i;
120 git_remote_head *head;
d88d4311 121 git_strarray ref_names = {0};
d6258deb 122
d88d4311 123 assert(t);
7e305056 124
edbaa63a 125 if (git_reference_list(&ref_names, t->repo) < 0)
a62053a0 126 goto on_error;
d6258deb 127
edbaa63a
AS
128 /* Clear all heads we might have fetched in a previous connect */
129 git_vector_foreach(&t->refs, i, head) {
130 git__free(head->name);
131 git__free(head);
132 }
133
134 /* Clear the vector so we can reuse it */
135 git_vector_clear(&t->refs);
136
7e305056 137 /* Sort the references first */
d88d4311 138 git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
7e305056 139
20858f6e 140 /* Add HEAD iff direction is fetch */
141 if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
a62053a0 142 goto on_error;
7e305056 143
d88d4311 144 for (i = 0; i < ref_names.count; ++i) {
a62053a0
CMN
145 if (add_ref(t, ref_names.strings[i]) < 0)
146 goto on_error;
7e305056 147 }
d6258deb 148
67ba7d20 149 t->have_refs = 1;
a1515693 150 git_strarray_free(&ref_names);
a62053a0
CMN
151 return 0;
152
153on_error:
41fb1ca0 154 git_vector_free(&t->refs);
d88d4311 155 git_strarray_free(&ref_names);
a62053a0 156 return -1;
d88d4311 157}
0a9a38e5 158
8bf476ac
GD
159static int path_from_url_or_path(git_buf *local_path_out, const char *url_or_path)
160{
161 int error;
162
163 /* If url_or_path begins with file:// treat it as a URL */
164 if (!git__prefixcmp(url_or_path, "file://")) {
165 if ((error = git_path_fromurl(local_path_out, url_or_path)) < 0) {
166 return error;
167 }
168 } else { /* We assume url_or_path is already a path */
169 if ((error = git_buf_sets(local_path_out, url_or_path)) < 0) {
170 return error;
171 }
172 }
173
174 return 0;
175}
176
d88d4311
VM
177/*
178 * Try to open the url as a git directory. The direction doesn't
179 * matter in this case because we're calulating the heads ourselves.
180 */
091361f5
PK
181static int local_connect(
182 git_transport *transport,
183 const char *url,
184 git_cred_acquire_cb cred_acquire_cb,
59bccf33 185 void *cred_acquire_payload,
091361f5 186 int direction, int flags)
d88d4311
VM
187{
188 git_repository *repo;
189 int error;
190 transport_local *t = (transport_local *) transport;
191 const char *path;
e2580375 192 git_buf buf = GIT_BUF_INIT;
193
091361f5 194 GIT_UNUSED(cred_acquire_cb);
59bccf33 195 GIT_UNUSED(cred_acquire_payload);
091361f5 196
41fb1ca0
PK
197 t->url = git__strdup(url);
198 GITERR_CHECK_ALLOC(t->url);
199 t->direction = direction;
200 t->flags = flags;
d88d4311 201
8bf476ac
GD
202 /* 'url' may be a url or path; convert to a path */
203 if ((error = path_from_url_or_path(&buf, url)) < 0) {
204 git_buf_free(&buf);
205 return error;
a62053a0 206 }
8bf476ac 207 path = git_buf_cstr(&buf);
d88d4311
VM
208
209 error = git_repository_open(&repo, path);
e2580375 210
211 git_buf_free(&buf);
212
a62053a0
CMN
213 if (error < 0)
214 return -1;
d88d4311 215
db1f7e59 216 t->repo = repo;
217
a62053a0
CMN
218 if (store_refs(t) < 0)
219 return -1;
d88d4311 220
41fb1ca0
PK
221 t->connected = 1;
222
223 return 0;
224}
225
359dce72 226static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
41fb1ca0
PK
227{
228 transport_local *t = (transport_local *)transport;
41fb1ca0 229
67ba7d20
CMN
230 if (!t->have_refs) {
231 giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
41fb1ca0
PK
232 return -1;
233 }
234
25e0b157 235 *out = (const git_remote_head **)t->refs.contents;
359dce72 236 *size = t->refs.length;
d88d4311 237
a62053a0 238 return 0;
d6258deb
CMN
239}
240
41fb1ca0
PK
241static int local_negotiate_fetch(
242 git_transport *transport,
243 git_repository *repo,
505da062
BS
244 const git_remote_head * const *refs,
245 size_t count)
a167002f 246{
505da062
BS
247 transport_local *t = (transport_local*)transport;
248 git_remote_head *rhead;
249 unsigned int i;
250
41fb1ca0
PK
251 GIT_UNUSED(refs);
252 GIT_UNUSED(count);
a167002f 253
505da062
BS
254 /* Fill in the loids */
255 git_vector_foreach(&t->refs, i, rhead) {
256 git_object *obj;
257
258 int error = git_revparse_single(&obj, repo, rhead->name);
259 if (!error)
260 git_oid_cpy(&rhead->loid, git_object_id(obj));
261 else if (error != GIT_ENOTFOUND)
262 return error;
9f77b3f6
RB
263 else
264 giterr_clear();
3dee3655 265 git_object_free(obj);
505da062
BS
266 }
267
268 return 0;
269}
270
20858f6e 271static int local_push_copy_object(
272 git_odb *local_odb,
273 git_odb *remote_odb,
274 git_pobject *obj)
275{
276 int error = 0;
277 git_odb_object *odb_obj = NULL;
278 git_odb_stream *odb_stream;
279 size_t odb_obj_size;
280 git_otype odb_obj_type;
281 git_oid remote_odb_obj_oid;
282
283 /* Object already exists in the remote ODB; do nothing and return 0*/
284 if (git_odb_exists(remote_odb, &obj->id))
285 return 0;
286
287 if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
288 return error;
289
290 odb_obj_size = git_odb_object_size(odb_obj);
291 odb_obj_type = git_odb_object_type(odb_obj);
292
293 if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
294 odb_obj_size, odb_obj_type)) < 0)
295 goto on_error;
296
376e6c9f 297 if (git_odb_stream_write(odb_stream, (char *)git_odb_object_data(odb_obj),
20858f6e 298 odb_obj_size) < 0 ||
376e6c9f 299 git_odb_stream_finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
20858f6e 300 error = -1;
b7f167da 301 } else if (git_oid__cmp(&obj->id, &remote_odb_obj_oid) != 0) {
20858f6e 302 giterr_set(GITERR_ODB, "Error when writing object to remote odb "
303 "during local push operation. Remote odb object oid does not "
304 "match local oid.");
305 error = -1;
306 }
307
376e6c9f 308 git_odb_stream_free(odb_stream);
20858f6e 309
310on_error:
311 git_odb_object_free(odb_obj);
312 return error;
313}
314
315static int local_push_update_remote_ref(
316 git_repository *remote_repo,
317 const char *lref,
318 const char *rref,
319 git_oid *loid,
320 git_oid *roid)
321{
322 int error;
323 git_reference *remote_ref = NULL;
324
325 /* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
326 rref = rref ? rref : lref;
327
328 if (lref) {
329 /* Create or update a ref */
330 if ((error = git_reference_create(NULL, remote_repo, rref, loid,
0b28217b 331 !git_oid_iszero(roid), NULL, NULL)) < 0)
20858f6e 332 return error;
333 } else {
334 /* Delete a ref */
335 if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
336 if (error == GIT_ENOTFOUND)
337 error = 0;
338 return error;
339 }
340
341 if ((error = git_reference_delete(remote_ref)) < 0)
342 return error;
343
344 git_reference_free(remote_ref);
345 }
346
347 return 0;
348}
349
350static int local_push(
351 git_transport *transport,
352 git_push *push)
353{
354 transport_local *t = (transport_local *)transport;
355 git_odb *remote_odb = NULL;
356 git_odb *local_odb = NULL;
357 git_repository *remote_repo = NULL;
358 push_spec *spec;
359 char *url = NULL;
4e974c97
GD
360 const char *path;
361 git_buf buf = GIT_BUF_INIT;
20858f6e 362 int error;
363 unsigned int i;
364 size_t j;
365
8bf476ac
GD
366 /* 'push->remote->url' may be a url or path; convert to a path */
367 if ((error = path_from_url_or_path(&buf, push->remote->url)) < 0) {
368 git_buf_free(&buf);
369 return error;
4e974c97 370 }
8bf476ac 371 path = git_buf_cstr(&buf);
4e974c97
GD
372
373 error = git_repository_open(&remote_repo, path);
374
375 git_buf_free(&buf);
376
377 if (error < 0)
20858f6e 378 return error;
379
1fed6b07 380 /* We don't currently support pushing locally to non-bare repos. Proper
20858f6e 381 non-bare repo push support would require checking configs to see if
382 we should override the default 'don't let this happen' behavior */
383 if (!remote_repo->is_bare) {
f42d546c
BS
384 error = GIT_EBAREREPO;
385 giterr_set(GITERR_INVALID, "Local push doesn't (yet) support pushing to non-bare repos.");
20858f6e 386 goto on_error;
387 }
388
389 if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
390 (error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
391 goto on_error;
392
393 for (i = 0; i < push->pb->nr_objects; i++) {
394 if ((error = local_push_copy_object(local_odb, remote_odb,
395 &push->pb->object_list[i])) < 0)
396 goto on_error;
397 }
398
399 push->unpack_ok = 1;
400
401 git_vector_foreach(&push->specs, j, spec) {
402 push_status *status;
403 const git_error *last;
404 char *ref = spec->rref ? spec->rref : spec->lref;
405
406 status = git__calloc(sizeof(push_status), 1);
407 if (!status)
408 goto on_error;
409
410 status->ref = git__strdup(ref);
411 if (!status->ref) {
412 git_push_status_free(status);
413 goto on_error;
414 }
415
416 error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
417 &spec->loid, &spec->roid);
418
419 switch (error) {
420 case GIT_OK:
421 break;
422 case GIT_EINVALIDSPEC:
423 status->msg = git__strdup("funny refname");
424 break;
425 case GIT_ENOTFOUND:
426 status->msg = git__strdup("Remote branch not found to delete");
427 break;
428 default:
429 last = giterr_last();
430
431 if (last && last->message)
432 status->msg = git__strdup(last->message);
433 else
434 status->msg = git__strdup("Unspecified error encountered");
435 break;
436 }
437
438 /* failed to allocate memory for a status message */
439 if (error < 0 && !status->msg) {
440 git_push_status_free(status);
441 goto on_error;
442 }
443
444 /* failed to insert the ref update status */
445 if ((error = git_vector_insert(&push->status, status)) < 0) {
446 git_push_status_free(status);
447 goto on_error;
448 }
449 }
450
451 if (push->specs.length) {
452 int flags = t->flags;
453 url = git__strdup(t->url);
454
455 if (!url || t->parent.close(&t->parent) < 0 ||
456 t->parent.connect(&t->parent, url,
e3c131c5 457 push->remote->callbacks.credentials, NULL, GIT_DIRECTION_PUSH, flags))
20858f6e 458 goto on_error;
459 }
460
461 error = 0;
462
463on_error:
464 git_repository_free(remote_repo);
465 git__free(url);
466
467 return error;
468}
469
505da062
BS
470typedef struct foreach_data {
471 git_transfer_progress *stats;
472 git_transfer_progress_callback progress_cb;
473 void *progress_payload;
474 git_odb_writepack *writepack;
475} foreach_data;
476
477static int foreach_cb(void *buf, size_t len, void *payload)
478{
479 foreach_data *data = (foreach_data*)payload;
480
481 data->stats->received_bytes += len;
a6154f21 482 return data->writepack->append(data->writepack, buf, len, data->stats);
505da062
BS
483}
484
485static int local_download_pack(
486 git_transport *transport,
487 git_repository *repo,
488 git_transfer_progress *stats,
489 git_transfer_progress_callback progress_cb,
490 void *progress_payload)
491{
492 transport_local *t = (transport_local*)transport;
493 git_revwalk *walk = NULL;
494 git_remote_head *rhead;
495 unsigned int i;
496 int error = -1;
497 git_oid oid;
498 git_packbuilder *pack = NULL;
499 git_odb_writepack *writepack = NULL;
90207709 500 git_odb *odb = NULL;
505da062
BS
501
502 if ((error = git_revwalk_new(&walk, t->repo)) < 0)
503 goto cleanup;
504 git_revwalk_sorting(walk, GIT_SORT_TIME);
505
506 if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
507 goto cleanup;
508
509 stats->total_objects = 0;
510 stats->indexed_objects = 0;
511 stats->received_objects = 0;
512 stats->received_bytes = 0;
513
514 git_vector_foreach(&t->refs, i, rhead) {
515 git_object *obj;
516 if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
517 goto cleanup;
518
519 if (git_object_type(obj) == GIT_OBJ_COMMIT) {
520 /* Revwalker includes only wanted commits */
521 error = git_revwalk_push(walk, &rhead->oid);
522 if (!git_oid_iszero(&rhead->loid))
523 error = git_revwalk_hide(walk, &rhead->loid);
524 } else {
525 /* Tag or some other wanted object. Add it on its own */
505da062
BS
526 error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
527 }
0cb16fe9 528 git_object_free(obj);
505da062
BS
529 }
530
531 /* Walk the objects, building a packfile */
90207709
BS
532 if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
533 goto cleanup;
505da062
BS
534
535 while ((error = git_revwalk_next(&oid, walk)) == 0) {
536 git_commit *commit;
537
90207709
BS
538 /* Skip commits we already have */
539 if (git_odb_exists(odb, &oid)) continue;
540
505da062 541 if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
cfbe4be3 542 const git_oid *tree_oid = git_commit_tree_id(commit);
505da062
BS
543
544 /* Add the commit and its tree */
545 if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
88183c19
FL
546 (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
547 git_commit_free(commit);
505da062 548 goto cleanup;
88183c19
FL
549 }
550
551 git_commit_free(commit);
505da062
BS
552 }
553 }
554
25e0b157 555 if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) != 0)
90207709 556 goto cleanup;
505da062
BS
557
558 /* Write the data to the ODB */
559 {
560 foreach_data data = {0};
561 data.stats = stats;
562 data.progress_cb = progress_cb;
563 data.progress_payload = progress_payload;
564 data.writepack = writepack;
565
25e0b157 566 if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
505da062
BS
567 goto cleanup;
568 }
569 error = writepack->commit(writepack, stats);
570
571cleanup:
572 if (writepack) writepack->free(writepack);
573 git_packbuilder_free(pack);
574 git_revwalk_free(walk);
575 return error;
a167002f
MS
576}
577
613d5eb9 578static int local_is_connected(git_transport *transport)
41fb1ca0
PK
579{
580 transport_local *t = (transport_local *)transport;
581
613d5eb9 582 return t->connected;
41fb1ca0
PK
583}
584
585static int local_read_flags(git_transport *transport, int *flags)
586{
587 transport_local *t = (transport_local *)transport;
588
589 *flags = t->flags;
590
591 return 0;
592}
593
594static void local_cancel(git_transport *transport)
595{
596 transport_local *t = (transport_local *)transport;
597
598 git_atomic_set(&t->cancelled, 1);
599}
600
854eccbb 601static int local_close(git_transport *transport)
d6258deb 602{
d88d4311
VM
603 transport_local *t = (transport_local *)transport;
604
41fb1ca0 605 t->connected = 0;
20858f6e 606
607 if (t->repo) {
608 git_repository_free(t->repo);
609 t->repo = NULL;
610 }
611
20858f6e 612 if (t->url) {
613 git__free(t->url);
614 t->url = NULL;
615 }
d88d4311 616
a62053a0 617 return 0;
8f866dae
CMN
618}
619
d6258deb
CMN
620static void local_free(git_transport *transport)
621{
20858f6e 622 transport_local *t = (transport_local *)transport;
9728cfde
AS
623 size_t i;
624 git_remote_head *head;
625
626 git_vector_foreach(&t->refs, i, head) {
627 git__free(head->name);
628 git__free(head);
629 }
d6258deb 630
edbaa63a
AS
631 git_vector_free(&t->refs);
632
20858f6e 633 /* Close the transport, if it's still open. */
634 local_close(transport);
a2888919 635
20858f6e 636 /* Free the transport */
3286c408 637 git__free(t);
d6258deb
CMN
638}
639
8f866dae
CMN
640/**************
641 * Public API *
642 **************/
643
613d5eb9 644int git_transport_local(git_transport **out, git_remote *owner, void *param)
8f866dae 645{
4e913309
CMN
646 transport_local *t;
647
41fb1ca0
PK
648 GIT_UNUSED(param);
649
10711769 650 t = git__calloc(1, sizeof(transport_local));
a62053a0 651 GITERR_CHECK_ALLOC(t);
4e913309 652
10711769 653 t->parent.version = GIT_TRANSPORT_VERSION;
4e913309 654 t->parent.connect = local_connect;
a167002f 655 t->parent.negotiate_fetch = local_negotiate_fetch;
505da062 656 t->parent.download_pack = local_download_pack;
20858f6e 657 t->parent.push = local_push;
4e913309
CMN
658 t->parent.close = local_close;
659 t->parent.free = local_free;
41fb1ca0
PK
660 t->parent.ls = local_ls;
661 t->parent.is_connected = local_is_connected;
662 t->parent.read_flags = local_read_flags;
663 t->parent.cancel = local_cancel;
4e913309 664
edbaa63a 665 git_vector_init(&t->refs, 0, NULL);
613d5eb9
PK
666 t->owner = owner;
667
4e913309 668 *out = (git_transport *) t;
8f866dae 669
a62053a0 670 return 0;
8f866dae 671}