local.c 17.7 KB
Newer Older
Vicent Marti committed
1
/*
Edward Thomson committed
2
 * Copyright (C) the libgit2 contributors. All rights reserved.
Vicent Marti committed
3 4 5 6
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */
7

8
#include "common.h"
9

10 11 12
#include "pack-objects.h"
#include "refs.h"
#include "posix.h"
13
#include "fs_path.h"
14 15 16 17 18 19
#include "repository.h"
#include "odb.h"
#include "push.h"
#include "remote.h"
#include "proxy.h"

20 21 22
#include "git2/types.h"
#include "git2/net.h"
#include "git2/repository.h"
23 24
#include "git2/object.h"
#include "git2/tag.h"
25
#include "git2/transport.h"
26 27 28 29 30
#include "git2/revwalk.h"
#include "git2/odb_backend.h"
#include "git2/pack.h"
#include "git2/commit.h"
#include "git2/revparse.h"
31
#include "git2/sys/remote.h"
32

33
typedef struct {
34
	git_transport parent;
35
	git_remote *owner;
36 37
	char *url;
	int direction;
38
	git_atomic32 cancelled;
39
	git_repository *repo;
40
	git_remote_connect_options connect_opts;
41
	git_vector refs;
42 43
	unsigned connected : 1,
		have_refs : 1;
44
} transport_local;
45

46 47 48 49 50 51 52
static void free_head(git_remote_head *head)
{
	git__free(head->name);
	git__free(head->symref_target);
	git__free(head);
}

Carlos Martín Nieto committed
53 54 55 56 57 58 59 60 61 62 63
static void free_heads(git_vector *heads)
{
	git_remote_head *head;
	size_t i;

	git_vector_foreach(heads, i, head)
		free_head(head);

	git_vector_free(heads);
}

64
static int add_ref(transport_local *t, const char *name)
65 66
{
	const char peeled[] = "^{}";
67
	git_reference *ref, *resolved;
68
	git_remote_head *head;
69
	git_oid obj_id;
70
	git_object *obj = NULL, *target = NULL;
71
	git_str buf = GIT_STR_INIT;
72
	int error;
73

74 75 76 77
	if ((error = git_reference_lookup(&ref, t->repo, name)) < 0)
		return error;

	error = git_reference_resolve(&resolved, ref);
78
	if (error < 0) {
79
		git_reference_free(ref);
80
		if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
81 82
			/* This is actually okay.  Empty repos often have a HEAD that
			 * points to a nonexistent "refs/heads/master". */
83
			git_error_clear();
84 85 86
			return 0;
		}
		return error;
87 88
	}

89 90 91
	git_oid_cpy(&obj_id, git_reference_target(resolved));
	git_reference_free(resolved);

92
	head = git__calloc(1, sizeof(git_remote_head));
93
	GIT_ERROR_CHECK_ALLOC(head);
94 95

	head->name = git__strdup(name);
96
	GIT_ERROR_CHECK_ALLOC(head->name);
97

98 99
	git_oid_cpy(&head->oid, &obj_id);

100
	if (git_reference_type(ref) == GIT_REFERENCE_SYMBOLIC) {
101
		head->symref_target = git__strdup(git_reference_symbolic_target(ref));
102
		GIT_ERROR_CHECK_ALLOC(head->symref_target);
103 104
	}
	git_reference_free(ref);
105 106

	if ((error = git_vector_insert(&t->refs, head)) < 0) {
107
		free_head(head);
108
		return error;
109
	}
110 111 112

	/* If it's not a tag, we don't need to try to peel it */
	if (git__prefixcmp(name, GIT_REFS_TAGS_DIR))
113
		return 0;
114

115
	if ((error = git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJECT_ANY)) < 0)
116
		return error;
117

118 119
	head = NULL;

120 121
	/* If it's not an annotated tag, or if we're mocking
	 * git-receive-pack, just get out */
122
	if (git_object_type(obj) != GIT_OBJECT_TAG ||
123
		t->direction != GIT_DIRECTION_FETCH) {
124 125 126
		git_object_free(obj);
		return 0;
	}
127 128

	/* And if it's a tag, peel it, and add it to the list */
129
	head = git__calloc(1, sizeof(git_remote_head));
130
	GIT_ERROR_CHECK_ALLOC(head);
131

132
	if (git_str_join(&buf, 0, name, peeled) < 0) {
Jacques Germishuys committed
133
		free_head(head);
134
		return -1;
Jacques Germishuys committed
135
	}
136
	head->name = git_str_detach(&buf);
137

138 139
	if (!(error = git_tag_peel(&target, (git_tag *)obj))) {
		git_oid_cpy(&head->oid, git_object_id(target));
140

141
		if ((error = git_vector_insert(&t->refs, head)) < 0) {
142
			free_head(head);
143 144
		}
	}
145

146
	git_object_free(obj);
147
	git_object_free(target);
148 149

	return error;
150 151
}

152
static int store_refs(transport_local *t)
153
{
154 155
	size_t i;
	git_remote_head *head;
156
	git_strarray ref_names = {0};
157

158
	GIT_ASSERT_ARG(t);
159

160
	if (git_reference_list(&ref_names, t->repo) < 0)
161
		goto on_error;
162

163 164 165 166 167 168 169 170 171
	/* Clear all heads we might have fetched in a previous connect */
	git_vector_foreach(&t->refs, i, head) {
		git__free(head->name);
		git__free(head);
	}

	/* Clear the vector so we can reuse it */
	git_vector_clear(&t->refs);

172
	/* Sort the references first */
173
	git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
174

175 176
	/* Add HEAD iff direction is fetch */
	if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
177
		goto on_error;
178

179
	for (i = 0; i < ref_names.count; ++i) {
180 181
		if (add_ref(t, ref_names.strings[i]) < 0)
			goto on_error;
182
	}
183

184
	t->have_refs = 1;
185
	git_strarray_dispose(&ref_names);
186 187 188
	return 0;

on_error:
189
	git_vector_free(&t->refs);
190
	git_strarray_dispose(&ref_names);
191
	return -1;
192
}
193

194 195
/*
 * Try to open the url as a git directory. The direction doesn't
196
 * matter in this case because we're calculating the heads ourselves.
197
 */
198 199 200
static int local_connect(
	git_transport *transport,
	const char *url,
201 202
	int direction,
	const git_remote_connect_options *connect_opts)
203 204 205
{
	git_repository *repo;
	int error;
206
	transport_local *t = (transport_local *)transport;
207
	const char *path;
208
	git_str buf = GIT_STR_INIT;
209

210 211 212
	if (t->connected)
		return 0;

213
	if (git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts) < 0)
214 215
		return -1;

Carlos Martín Nieto committed
216 217
	free_heads(&t->refs);

218
	t->url = git__strdup(url);
219
	GIT_ERROR_CHECK_ALLOC(t->url);
220
	t->direction = direction;
221

222
	/* 'url' may be a url or path; convert to a path */
223
	if ((error = git_fs_path_from_url_or_path(&buf, url)) < 0) {
224
		git_str_dispose(&buf);
225
		return error;
226
	}
227
	path = git_str_cstr(&buf);
228 229

	error = git_repository_open(&repo, path);
230

231
	git_str_dispose(&buf);
232

233 234
	if (error < 0)
		return -1;
235

236 237
	t->repo = repo;

238 239
	if (store_refs(t) < 0)
		return -1;
240

241 242 243 244 245
	t->connected = 1;

	return 0;
}

246 247 248 249 250 251 252 253 254 255 256
static int local_set_connect_opts(
	git_transport *transport,
	const git_remote_connect_options *connect_opts)
{
	transport_local *t = (transport_local *)transport;

	if (!t->connected) {
		git_error_set(GIT_ERROR_NET, "cannot reconfigure a transport that is not connected");
		return -1;
	}

257
	return git_remote_connect_options_normalize(&t->connect_opts, t->owner->repo, connect_opts);
258 259
}

260 261 262 263
static int local_capabilities(unsigned int *capabilities, git_transport *transport)
{
	GIT_UNUSED(transport);

264 265
	*capabilities = GIT_REMOTE_CAPABILITY_TIP_OID |
	                GIT_REMOTE_CAPABILITY_REACHABLE_OID;
266 267 268
	return 0;
}

269
static int local_ls(const git_remote_head ***out, size_t *size, git_transport *transport)
270 271 272
{
	transport_local *t = (transport_local *)transport;

273
	if (!t->have_refs) {
274
		git_error_set(GIT_ERROR_NET, "the transport has not yet loaded the refs");
275 276 277
		return -1;
	}

278
	*out = (const git_remote_head **)t->refs.contents;
279
	*size = t->refs.length;
280

281
	return 0;
282 283
}

284 285 286
static int local_negotiate_fetch(
	git_transport *transport,
	git_repository *repo,
287 288
	const git_remote_head * const *refs,
	size_t count)
289
{
290 291 292 293
	transport_local *t = (transport_local*)transport;
	git_remote_head *rhead;
	unsigned int i;

294 295
	GIT_UNUSED(refs);
	GIT_UNUSED(count);
296

297 298 299 300 301 302 303 304 305
	/* Fill in the loids */
	git_vector_foreach(&t->refs, i, rhead) {
		git_object *obj;

		int error = git_revparse_single(&obj, repo, rhead->name);
		if (!error)
			git_oid_cpy(&rhead->loid, git_object_id(obj));
		else if (error != GIT_ENOTFOUND)
			return error;
306
		else
307
			git_error_clear();
nulltoken committed
308
		git_object_free(obj);
309 310 311 312 313
	}

	return 0;
}

314 315 316 317 318 319 320 321 322 323
static int local_push_update_remote_ref(
	git_repository *remote_repo,
	const char *lref,
	const char *rref,
	git_oid *loid,
	git_oid *roid)
{
	int error;
	git_reference *remote_ref = NULL;

324 325
	/* check for lhs, if it's empty it means to delete */
	if (lref[0] != '\0') {
326
		/* Create or update a ref */
327
		error = git_reference_create(NULL, remote_repo, rref, loid,
328
					     !git_oid_is_zero(roid), NULL);
329 330 331 332 333 334 335 336
	} else {
		/* Delete a ref */
		if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
			if (error == GIT_ENOTFOUND)
				error = 0;
			return error;
		}

337
		error = git_reference_delete(remote_ref);
338 339 340
		git_reference_free(remote_ref);
	}

341
	return error;
342 343
}

344
static int transfer_to_push_transfer(const git_indexer_progress *stats, void *payload)
345 346 347 348 349 350 351 352 353 354
{
	const git_remote_callbacks *cbs = payload;

	if (!cbs || !cbs->push_transfer_progress)
		return 0;

	return cbs->push_transfer_progress(stats->received_objects, stats->total_objects, stats->received_bytes,
					   cbs->payload);
}

355 356
static int local_push(
	git_transport *transport,
357
	git_push *push)
358 359
{
	transport_local *t = (transport_local *)transport;
360
	git_remote_callbacks *cbs = &t->connect_opts.callbacks;
361 362 363
	git_repository *remote_repo = NULL;
	push_spec *spec;
	char *url = NULL;
364
	const char *path;
365
	git_str buf = GIT_STR_INIT, odb_path = GIT_STR_INIT;
366 367 368
	int error;
	size_t j;

369
	/* 'push->remote->url' may be a url or path; convert to a path */
370
	if ((error = git_fs_path_from_url_or_path(&buf, push->remote->url)) < 0) {
371
		git_str_dispose(&buf);
372
		return error;
373
	}
374
	path = git_str_cstr(&buf);
375 376 377

	error = git_repository_open(&remote_repo, path);

378
	git_str_dispose(&buf);
379 380

	if (error < 0)
381 382
		return error;

nulltoken committed
383
	/* We don't currently support pushing locally to non-bare repos. Proper
384
	   non-bare repo push support would require checking configs to see if
385 386 387 388
	   we should override the default 'don't let this happen' behavior.

	   Note that this is only an issue when pushing to the current branch,
	   but we forbid all pushes just in case */
389
	if (!remote_repo->is_bare) {
390
		error = GIT_EBAREREPO;
391
		git_error_set(GIT_ERROR_INVALID, "local push doesn't (yet) support pushing to non-bare repos.");
392 393 394
		goto on_error;
	}

395 396
	if ((error = git_repository__item_path(&odb_path, remote_repo, GIT_REPOSITORY_ITEM_OBJECTS)) < 0
		|| (error = git_str_joinpath(&odb_path, odb_path.ptr, "pack")) < 0)
397 398
		goto on_error;

399
	error = git_packbuilder_write(push->pb, odb_path.ptr, 0, transfer_to_push_transfer, (void *) cbs);
400
	git_str_dispose(&odb_path);
401 402 403

	if (error < 0)
		goto on_error;
404 405 406 407 408 409

	push->unpack_ok = 1;

	git_vector_foreach(&push->specs, j, spec) {
		push_status *status;
		const git_error *last;
410
		char *ref = spec->refspec.dst;
411

412
		status = git__calloc(1, sizeof(push_status));
413 414 415 416 417 418 419 420 421
		if (!status)
			goto on_error;

		status->ref = git__strdup(ref);
		if (!status->ref) {
			git_push_status_free(status);
			goto on_error;
		}

422
		error = local_push_update_remote_ref(remote_repo, spec->refspec.src, spec->refspec.dst,
423 424 425 426 427 428 429 430 431 432 433 434
			&spec->loid, &spec->roid);

		switch (error) {
			case GIT_OK:
				break;
			case GIT_EINVALIDSPEC:
				status->msg = git__strdup("funny refname");
				break;
			case GIT_ENOTFOUND:
				status->msg = git__strdup("Remote branch not found to delete");
				break;
			default:
435
				last = git_error_last();
436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461

				if (last && last->message)
					status->msg = git__strdup(last->message);
				else
					status->msg = git__strdup("Unspecified error encountered");
				break;
		}

		/* failed to allocate memory for a status message */
		if (error < 0 && !status->msg) {
			git_push_status_free(status);
			goto on_error;
		}

		/* failed to insert the ref update status */
		if ((error = git_vector_insert(&push->status, status)) < 0) {
			git_push_status_free(status);
			goto on_error;
		}
	}

	if (push->specs.length) {
		url = git__strdup(t->url);

		if (!url || t->parent.close(&t->parent) < 0 ||
			t->parent.connect(&t->parent, url,
462
			GIT_DIRECTION_PUSH, NULL))
463 464 465 466 467 468 469 470 471 472 473 474
			goto on_error;
	}

	error = 0;

on_error:
	git_repository_free(remote_repo);
	git__free(url);

	return error;
}

475
typedef struct foreach_data {
476 477
	git_indexer_progress *stats;
	git_indexer_progress_cb progress_cb;
478 479 480 481 482 483 484 485 486
	void *progress_payload;
	git_odb_writepack *writepack;
} foreach_data;

static int foreach_cb(void *buf, size_t len, void *payload)
{
	foreach_data *data = (foreach_data*)payload;

	data->stats->received_bytes += len;
487
	return data->writepack->append(data->writepack, buf, len, data->stats);
488 489
}

490
static const char *counting_objects_fmt = "Counting objects %d\r";
491 492 493 494
static const char *compressing_objects_fmt = "Compressing objects: %.0f%% (%d/%d)";

static int local_counting(int stage, unsigned int current, unsigned int total, void *payload)
{
495
	git_str progress_info = GIT_STR_INIT;
496
	transport_local *t = payload;
497
	int error;
498

499
	if (!t->connect_opts.callbacks.sideband_progress)
500 501 502
		return 0;

	if (stage == GIT_PACKBUILDER_ADDING_OBJECTS) {
503
		git_str_printf(&progress_info, counting_objects_fmt, current);
504 505
	} else if (stage == GIT_PACKBUILDER_DELTAFICATION) {
		float perc = (((float) current) / total) * 100;
506
		git_str_printf(&progress_info, compressing_objects_fmt, perc, current, total);
507
		if (current == total)
508
			git_str_printf(&progress_info, ", done\n");
509
		else
510
			git_str_putc(&progress_info, '\r');
511 512 513

	}

514
	if (git_str_oom(&progress_info))
515 516
		return -1;

517 518 519 520 521 522 523 524 525 526 527
	if (progress_info.size > INT_MAX) {
		git_error_set(GIT_ERROR_NET, "remote sent overly large progress data");
		git_str_dispose(&progress_info);
		return -1;
	}


	error = t->connect_opts.callbacks.sideband_progress(
		progress_info.ptr,
		(int)progress_info.size,
		t->connect_opts.callbacks.payload);
528

529
	git_str_dispose(&progress_info);
530
	return error;
531
}
532

533 534 535
static int foreach_reference_cb(git_reference *reference, void *payload)
{
	git_revwalk *walk = (git_revwalk *)payload;
536 537
	int error;

538
	if (git_reference_type(reference) != GIT_REFERENCE_DIRECT) {
539
		git_reference_free(reference);
540
		return 0;
541
	}
542

543
	error = git_revwalk_hide(walk, git_reference_target(reference));
544 545
	/* The reference is in the local repository, so the target may not
	 * exist on the remote.  It also may not be a commit. */
546 547
	if (error == GIT_ENOTFOUND || error == GIT_ERROR_INVALID) {
		git_error_clear();
548 549 550
		error = 0;
	}

551 552
	git_reference_free(reference);

553 554 555
	return error;
}

556 557 558
static int local_download_pack(
		git_transport *transport,
		git_repository *repo,
559
		git_indexer_progress *stats)
560 561 562 563 564 565 566 567
{
	transport_local *t = (transport_local*)transport;
	git_revwalk *walk = NULL;
	git_remote_head *rhead;
	unsigned int i;
	int error = -1;
	git_packbuilder *pack = NULL;
	git_odb_writepack *writepack = NULL;
568
	git_odb *odb = NULL;
569
	git_str progress_info = GIT_STR_INIT;
570
	foreach_data data = {0};
571 572 573

	if ((error = git_revwalk_new(&walk, t->repo)) < 0)
		goto cleanup;
574

575 576 577 578 579
	git_revwalk_sorting(walk, GIT_SORT_TIME);

	if ((error = git_packbuilder_new(&pack, t->repo)) < 0)
		goto cleanup;

580 581
	git_packbuilder_set_callbacks(pack, local_counting, t);

582 583 584 585 586 587 588
	stats->total_objects = 0;
	stats->indexed_objects = 0;
	stats->received_objects = 0;
	stats->received_bytes = 0;

	git_vector_foreach(&t->refs, i, rhead) {
		git_object *obj;
589
		if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJECT_ANY)) < 0)
590 591
			goto cleanup;

592
		if (git_object_type(obj) == GIT_OBJECT_COMMIT) {
593 594 595
			/* Revwalker includes only wanted commits */
			error = git_revwalk_push(walk, &rhead->oid);
		} else {
596 597
			/* Tag or some other wanted object. Add it on its own */
			error = git_packbuilder_insert_recur(pack, &rhead->oid, rhead->name);
598
		}
Linquize committed
599
		git_object_free(obj);
600 601
		if (error < 0)
			goto cleanup;
602 603
	}

604 605 606
	if ((error = git_reference_foreach(repo, foreach_reference_cb, walk)))
		goto cleanup;

607 608 609
	if ((error = git_packbuilder_insert_walk(pack, walk)))
		goto cleanup;

610 611 612 613 614 615 616 617 618 619 620
	if (t->connect_opts.callbacks.sideband_progress) {
		if ((error = git_str_printf(
				&progress_info,
				counting_objects_fmt,
				git_packbuilder_object_count(pack))) < 0 ||
		    (error = t->connect_opts.callbacks.sideband_progress(
				progress_info.ptr,
				(int)progress_info.size,
				t->connect_opts.callbacks.payload)) < 0)
			goto cleanup;
	}
621

622
	/* Walk the objects, building a packfile */
623 624
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
		goto cleanup;
625

626
	/* One last one with the newline */
627 628 629 630 631 632 633 634 635 636 637 638 639 640
	if (t->connect_opts.callbacks.sideband_progress) {
		git_str_clear(&progress_info);

		if ((error = git_str_printf(
				&progress_info,
				counting_objects_fmt,
				git_packbuilder_object_count(pack))) < 0 ||
		    (error = git_str_putc(&progress_info, '\n')) < 0 ||
		    (error = t->connect_opts.callbacks.sideband_progress(
				progress_info.ptr,
				(int)progress_info.size,
				t->connect_opts.callbacks.payload)) < 0)
			goto cleanup;
	}
641

642 643 644 645 646
	if ((error = git_odb_write_pack(
			&writepack,
			odb,
			t->connect_opts.callbacks.transfer_progress,
			t->connect_opts.callbacks.payload)) < 0)
647
		goto cleanup;
648 649

	/* Write the data to the ODB */
650 651 652 653
	data.stats = stats;
	data.progress_cb = t->connect_opts.callbacks.transfer_progress;
	data.progress_payload = t->connect_opts.callbacks.payload;
	data.writepack = writepack;
654

655 656
	/* autodetect */
	git_packbuilder_set_threads(pack, 0);
657

658 659
	if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) != 0)
		goto cleanup;
660

661 662 663 664
	error = writepack->commit(writepack, stats);

cleanup:
	if (writepack) writepack->free(writepack);
665
	git_str_dispose(&progress_info);
666 667 668
	git_packbuilder_free(pack);
	git_revwalk_free(walk);
	return error;
669 670
}

671
static int local_is_connected(git_transport *transport)
672 673 674
{
	transport_local *t = (transport_local *)transport;

675
	return t->connected;
676 677 678 679 680 681
}

static void local_cancel(git_transport *transport)
{
	transport_local *t = (transport_local *)transport;

682
	git_atomic32_set(&t->cancelled, 1);
683 684
}

685
static int local_close(git_transport *transport)
686
{
687 688
	transport_local *t = (transport_local *)transport;

689
	t->connected = 0;
690 691 692 693 694 695 696 697 698 699

	if (t->repo) {
		git_repository_free(t->repo);
		t->repo = NULL;
	}

	if (t->url) {
		git__free(t->url);
		t->url = NULL;
	}
700

701
	return 0;
702 703
}

704 705
static void local_free(git_transport *transport)
{
706
	transport_local *t = (transport_local *)transport;
707

Carlos Martín Nieto committed
708
	free_heads(&t->refs);
709

710 711
	/* Close the transport, if it's still open. */
	local_close(transport);
712

713
	/* Free the transport */
714
	git__free(t);
715 716
}

717 718 719 720
/**************
 * Public API *
 **************/

721
int git_transport_local(git_transport **out, git_remote *owner, void *param)
722
{
Jacques Germishuys committed
723
	int error;
724 725
	transport_local *t;

726 727
	GIT_UNUSED(param);

728
	t = git__calloc(1, sizeof(transport_local));
729
	GIT_ERROR_CHECK_ALLOC(t);
730

731
	t->parent.version = GIT_TRANSPORT_VERSION;
732
	t->parent.connect = local_connect;
733
	t->parent.set_connect_opts = local_set_connect_opts;
734
	t->parent.capabilities = local_capabilities;
735
	t->parent.negotiate_fetch = local_negotiate_fetch;
736
	t->parent.download_pack = local_download_pack;
737
	t->parent.push = local_push;
738 739
	t->parent.close = local_close;
	t->parent.free = local_free;
740 741 742
	t->parent.ls = local_ls;
	t->parent.is_connected = local_is_connected;
	t->parent.cancel = local_cancel;
743

Jacques Germishuys committed
744 745 746 747 748
	if ((error = git_vector_init(&t->refs, 0, NULL)) < 0) {
		git__free(t);
		return error;
	}

749 750
	t->owner = owner;

751
	*out = (git_transport *) t;
752

753
	return 0;
754
}