local.c 14.4 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 9 10
#include "common.h"
#include "git2/types.h"
#include "git2/net.h"
#include "git2/repository.h"
11 12
#include "git2/object.h"
#include "git2/tag.h"
13
#include "git2/transport.h"
14 15 16 17 18
#include "git2/revwalk.h"
#include "git2/odb_backend.h"
#include "git2/pack.h"
#include "git2/commit.h"
#include "git2/revparse.h"
19
#include "git2/push.h"
20 21
#include "pack-objects.h"
#include "refs.h"
22
#include "posix.h"
23 24
#include "path.h"
#include "buffer.h"
25 26
#include "repository.h"
#include "odb.h"
27 28
#include "push.h"
#include "remote.h"
29

30
typedef struct {
31
	git_transport parent;
32
	git_remote *owner;
33 34 35 36
	char *url;
	int direction;
	int flags;
	git_atomic cancelled;
37
	git_repository *repo;
38
	git_vector refs;
39 40
	unsigned connected : 1,
		have_refs : 1;
41
} transport_local;
42

43
static int add_ref(transport_local *t, const char *name)
44 45 46
{
	const char peeled[] = "^{}";
	git_remote_head *head;
47 48
	git_object *obj = NULL, *target = NULL;
	git_buf buf = GIT_BUF_INIT;
49
	int error;
50

51
	head = git__calloc(1, sizeof(git_remote_head));
52
	GITERR_CHECK_ALLOC(head);
53

54 55
	head->name = git__strdup(name);
	GITERR_CHECK_ALLOC(head->name);
56

57 58
	error = git_reference_name_to_id(&head->oid, t->repo, name);
	if (error < 0) {
59
		git__free(head->name);
60
		git__free(head);
61
		if (!strcmp(name, GIT_HEAD_FILE) && error == GIT_ENOTFOUND) {
62
			/* This is actually okay.  Empty repos often have a HEAD that points to
63
			 * a nonexistent "refs/heads/master". */
64 65 66 67
			giterr_clear();
			return 0;
		}
		return error;
68 69
	}

70
	if (git_vector_insert(&t->refs, head) < 0)
71
	{
72 73
		git__free(head->name);
		git__free(head);
74
		return -1;
75
	}
76 77 78

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

81
	if (git_object_lookup(&obj, t->repo, &head->oid, GIT_OBJ_ANY) < 0)
82
		return -1;
83

84 85
	head = NULL;

86 87 88 89
	/* If it's not an annotated tag, or if we're mocking
	 * git-receive-pack, just get out */
	if (git_object_type(obj) != GIT_OBJ_TAG ||
		t->direction != GIT_DIRECTION_FETCH) {
90 91 92
		git_object_free(obj);
		return 0;
	}
93 94

	/* And if it's a tag, peel it, and add it to the list */
95
	head = git__calloc(1, sizeof(git_remote_head));
96 97 98
	GITERR_CHECK_ALLOC(head);
	if (git_buf_join(&buf, 0, name, peeled) < 0)
		return -1;
99

100
	head->name = git_buf_detach(&buf);
101

102 103
	if (git_tag_peel(&target, (git_tag *) obj) < 0)
		goto on_error;
104

105 106 107
	git_oid_cpy(&head->oid, git_object_id(target));
	git_object_free(obj);
	git_object_free(target);
108

109
	if (git_vector_insert(&t->refs, head) < 0)
110
		return -1;
111

112
	return 0;
113

114
on_error:
115
	git_object_free(obj);
116 117
	git_object_free(target);
	return -1;
118 119
}

120
static int store_refs(transport_local *t)
121
{
122
	unsigned int i;
123
	git_strarray ref_names = {0};
124

125
	assert(t);
126

127
	if (git_reference_list(&ref_names, t->repo) < 0 ||
128
		git_vector_init(&t->refs, ref_names.count, NULL) < 0)
129
		goto on_error;
130

131
	/* Sort the references first */
132
	git__tsort((void **)ref_names.strings, ref_names.count, &git__strcmp_cb);
133

134 135
	/* Add HEAD iff direction is fetch */
	if (t->direction == GIT_DIRECTION_FETCH && add_ref(t, GIT_HEAD_FILE) < 0)
136
		goto on_error;
137

138
	for (i = 0; i < ref_names.count; ++i) {
139 140
		if (add_ref(t, ref_names.strings[i]) < 0)
			goto on_error;
141
	}
142

143
	t->have_refs = 1;
144
	git_strarray_free(&ref_names);
145 146 147
	return 0;

on_error:
148
	git_vector_free(&t->refs);
149
	git_strarray_free(&ref_names);
150
	return -1;
151
}
152

153 154 155 156
/*
 * Try to open the url as a git directory. The direction doesn't
 * matter in this case because we're calulating the heads ourselves.
 */
157 158 159 160
static int local_connect(
	git_transport *transport,
	const char *url,
	git_cred_acquire_cb cred_acquire_cb,
161
	void *cred_acquire_payload,
162
	int direction, int flags)
163 164 165 166 167
{
	git_repository *repo;
	int error;
	transport_local *t = (transport_local *) transport;
	const char *path;
168 169
	git_buf buf = GIT_BUF_INIT;

170
	GIT_UNUSED(cred_acquire_cb);
171
	GIT_UNUSED(cred_acquire_payload);
172

173 174 175 176
	t->url = git__strdup(url);
	GITERR_CHECK_ALLOC(t->url);
	t->direction = direction;
	t->flags = flags;
177 178

	/* The repo layer doesn't want the prefix */
179 180
	if (!git__prefixcmp(t->url, "file://")) {
		if (git_path_fromurl(&buf, t->url) < 0) {
181
			git_buf_free(&buf);
182
			return -1;
183 184 185
		}
		path = git_buf_cstr(&buf);

186
	} else { /* We assume transport->url is already a path */
187
		path = t->url;
188
	}
189 190

	error = git_repository_open(&repo, path);
191 192 193

	git_buf_free(&buf);

194 195
	if (error < 0)
		return -1;
196

197 198
	t->repo = repo;

199 200
	if (store_refs(t) < 0)
		return -1;
201

202 203 204 205 206 207 208 209 210 211 212
	t->connected = 1;

	return 0;
}

static int local_ls(git_transport *transport, git_headlist_cb list_cb, void *payload)
{
	transport_local *t = (transport_local *)transport;
	unsigned int i;
	git_remote_head *head = NULL;

213 214
	if (!t->have_refs) {
		giterr_set(GITERR_NET, "The transport has not yet loaded the refs");
215 216 217 218 219 220 221
		return -1;
	}

	git_vector_foreach(&t->refs, i, head) {
		if (list_cb(head, payload))
			return GIT_EUSER;
	}
222

223
	return 0;
224 225
}

226 227 228
static int local_negotiate_fetch(
	git_transport *transport,
	git_repository *repo,
229 230
	const git_remote_head * const *refs,
	size_t count)
231
{
232 233 234 235
	transport_local *t = (transport_local*)transport;
	git_remote_head *rhead;
	unsigned int i;

236 237
	GIT_UNUSED(refs);
	GIT_UNUSED(count);
238

239 240 241 242 243 244 245 246 247
	/* 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;
nulltoken committed
248
		git_object_free(obj);
249 250 251 252 253 254
		giterr_clear();
	}

	return 0;
}

255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284
static int local_push_copy_object(
	git_odb *local_odb,
	git_odb *remote_odb,
	git_pobject *obj)
{
	int error = 0;
	git_odb_object *odb_obj = NULL;
	git_odb_stream *odb_stream;
	size_t odb_obj_size;
	git_otype odb_obj_type;
	git_oid remote_odb_obj_oid;

	/* Object already exists in the remote ODB; do nothing and return 0*/
	if (git_odb_exists(remote_odb, &obj->id))
		return 0;

	if ((error = git_odb_read(&odb_obj, local_odb, &obj->id)) < 0)
		return error;

	odb_obj_size = git_odb_object_size(odb_obj);
	odb_obj_type = git_odb_object_type(odb_obj);

	if ((error = git_odb_open_wstream(&odb_stream, remote_odb,
		odb_obj_size, odb_obj_type)) < 0)
		goto on_error;

	if (odb_stream->write(odb_stream, (char *)git_odb_object_data(odb_obj),
		odb_obj_size) < 0 ||
		odb_stream->finalize_write(&remote_odb_obj_oid, odb_stream) < 0) {
		error = -1;
285
	} else if (git_oid__cmp(&obj->id, &remote_odb_obj_oid) != 0) {
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350
		giterr_set(GITERR_ODB, "Error when writing object to remote odb "
			"during local push operation. Remote odb object oid does not "
			"match local oid.");
		error = -1;
	}

	odb_stream->free(odb_stream);

on_error:
	git_odb_object_free(odb_obj);
	return error;
}

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;

	/* rref will be NULL if it is implicit in the pushspec (e.g. 'b1:') */
	rref = rref ? rref : lref;

	if (lref) {
		/* Create or update a ref */
		if ((error = git_reference_create(NULL, remote_repo, rref, loid,
				!git_oid_iszero(roid))) < 0)
			return error;
	} else {
		/* Delete a ref */
		if ((error = git_reference_lookup(&remote_ref, remote_repo, rref)) < 0) {
			if (error == GIT_ENOTFOUND)
				error = 0;
			return error;
		}

		if ((error = git_reference_delete(remote_ref)) < 0)
			return error;

		git_reference_free(remote_ref);
	}

	return 0;
}

static int local_push(
	git_transport *transport,
	git_push *push)
{
	transport_local *t = (transport_local *)transport;
	git_odb *remote_odb = NULL;
	git_odb *local_odb = NULL;
	git_repository *remote_repo = NULL;
	push_spec *spec;
	char *url = NULL;
	int error;
	unsigned int i;
	size_t j;

	if ((error = git_repository_open(&remote_repo, push->remote->url)) < 0)
		return error;

nulltoken committed
351
	/* We don't currently support pushing locally to non-bare repos. Proper
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439
	   non-bare repo push support would require checking configs to see if
	   we should override the default 'don't let this happen' behavior */
	if (!remote_repo->is_bare) {
		error = -1;
		goto on_error;
	}

	if ((error = git_repository_odb__weakptr(&remote_odb, remote_repo)) < 0 ||
		(error = git_repository_odb__weakptr(&local_odb, push->repo)) < 0)
		goto on_error;

	for (i = 0; i < push->pb->nr_objects; i++) {
		if ((error = local_push_copy_object(local_odb, remote_odb,
			&push->pb->object_list[i])) < 0)
			goto on_error;
	}

	push->unpack_ok = 1;

	git_vector_foreach(&push->specs, j, spec) {
		push_status *status;
		const git_error *last;
		char *ref = spec->rref ? spec->rref : spec->lref;

		status = git__calloc(sizeof(push_status), 1);
		if (!status)
			goto on_error;

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

		error = local_push_update_remote_ref(remote_repo, spec->lref, spec->rref,
			&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:
				last = giterr_last();

				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) {
		int flags = t->flags;
		url = git__strdup(t->url);

		if (!url || t->parent.close(&t->parent) < 0 ||
			t->parent.connect(&t->parent, url,
			push->remote->cred_acquire_cb, NULL, GIT_DIRECTION_PUSH, flags))
			goto on_error;
	}

	error = 0;

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

	return error;
}

440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
typedef struct foreach_data {
	git_transfer_progress *stats;
	git_transfer_progress_callback progress_cb;
	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;
	return data->writepack->add(data->writepack, buf, len, data->stats);
}

static int local_download_pack(
		git_transport *transport,
		git_repository *repo,
		git_transfer_progress *stats,
		git_transfer_progress_callback progress_cb,
		void *progress_payload)
{
	transport_local *t = (transport_local*)transport;
	git_revwalk *walk = NULL;
	git_remote_head *rhead;
	unsigned int i;
	int error = -1;
	git_oid oid;
	git_packbuilder *pack = NULL;
	git_odb_writepack *writepack = NULL;
470
	git_odb *odb = NULL;
471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497

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

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

	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;
		if ((error = git_object_lookup(&obj, t->repo, &rhead->oid, GIT_OBJ_ANY)) < 0)
			goto cleanup;

		if (git_object_type(obj) == GIT_OBJ_COMMIT) {
			/* Revwalker includes only wanted commits */
			error = git_revwalk_push(walk, &rhead->oid);
			if (!git_oid_iszero(&rhead->loid))
				error = git_revwalk_hide(walk, &rhead->loid);
		} else {
			/* Tag or some other wanted object. Add it on its own */
			error = git_packbuilder_insert(pack, &rhead->oid, rhead->name);
		}
Linquize committed
498
		git_object_free(obj);
499 500 501
	}

	/* Walk the objects, building a packfile */
502 503
	if ((error = git_repository_odb__weakptr(&odb, repo)) < 0)
		goto cleanup;
504 505 506 507

	while ((error = git_revwalk_next(&oid, walk)) == 0) {
		git_commit *commit;

508 509 510
		/* Skip commits we already have */
		if (git_odb_exists(odb, &oid)) continue;

511
		if (!git_object_lookup((git_object**)&commit, t->repo, &oid, GIT_OBJ_COMMIT)) {
Vicent Marti committed
512
			const git_oid *tree_oid = git_commit_tree_id(commit);
513 514 515

			/* Add the commit and its tree */
			if ((error = git_packbuilder_insert(pack, &oid, NULL)) < 0 ||
516 517
				 (error = git_packbuilder_insert_tree(pack, tree_oid)) < 0) {
				git_commit_free(commit);
518
				goto cleanup;
519 520 521
			}

			git_commit_free(commit);
522 523 524
		}
	}

525 526
	if ((error = git_odb_write_pack(&writepack, odb, progress_cb, progress_payload)) < 0)
		goto cleanup;
527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545

	/* Write the data to the ODB */
	{
		foreach_data data = {0};
		data.stats = stats;
		data.progress_cb = progress_cb;
		data.progress_payload = progress_payload;
		data.writepack = writepack;

		if ((error = git_packbuilder_foreach(pack, foreach_cb, &data)) < 0)
			goto cleanup;
	}
	error = writepack->commit(writepack, stats);

cleanup:
	if (writepack) writepack->free(writepack);
	git_packbuilder_free(pack);
	git_revwalk_free(walk);
	return error;
546 547
}

548
static int local_is_connected(git_transport *transport)
549 550 551
{
	transport_local *t = (transport_local *)transport;

552
	return t->connected;
553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570
}

static int local_read_flags(git_transport *transport, int *flags)
{
	transport_local *t = (transport_local *)transport;

	*flags = t->flags;

	return 0;
}

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

	git_atomic_set(&t->cancelled, 1);
}

571
static int local_close(git_transport *transport)
572
{
573 574
	transport_local *t = (transport_local *)transport;

575
	t->connected = 0;
576 577 578 579 580 581 582 583 584 585

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

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

587
	return 0;
588 589
}

590 591
static void local_free(git_transport *transport)
{
592
	transport_local *t = (transport_local *)transport;
593 594
	size_t i;
	git_remote_head *head;
595

596 597
	/* Close the transport, if it's still open. */
	local_close(transport);
598

599 600 601 602 603 604 605
	git_vector_foreach(&t->refs, i, head) {
		git__free(head->name);
		git__free(head);
	}

	git_vector_free(&t->refs);

606
	/* Free the transport */
607
	git__free(t);
608 609
}

610 611 612 613
/**************
 * Public API *
 **************/

614
int git_transport_local(git_transport **out, git_remote *owner, void *param)
615
{
616 617
	transport_local *t;

618 619
	GIT_UNUSED(param);

620
	t = git__calloc(1, sizeof(transport_local));
621
	GITERR_CHECK_ALLOC(t);
622

623
	t->parent.version = GIT_TRANSPORT_VERSION;
624
	t->parent.connect = local_connect;
625
	t->parent.negotiate_fetch = local_negotiate_fetch;
626
	t->parent.download_pack = local_download_pack;
627
	t->parent.push = local_push;
628 629
	t->parent.close = local_close;
	t->parent.free = local_free;
630 631 632 633
	t->parent.ls = local_ls;
	t->parent.is_connected = local_is_connected;
	t->parent.read_flags = local_read_flags;
	t->parent.cancel = local_cancel;
634

635 636
	t->owner = owner;

637
	*out = (git_transport *) t;
638

639
	return 0;
640
}