push.c 27.7 KB
Newer Older
1 2 3 4 5 6
#include "clar_libgit2.h"
#include "buffer.h"
#include "posix.h"
#include "vector.h"
#include "../submodule/submodule_helpers.h"
#include "push_util.h"
7 8
#include "refspec.h"
#include "remote.h"
9 10 11

static git_repository *_repo;

12
static char *_remote_url = NULL;
13

14 15
static char *_remote_user = NULL;
static char *_remote_pass = NULL;
16

17 18 19
static char *_remote_ssh_key = NULL;
static char *_remote_ssh_pubkey = NULL;
static char *_remote_ssh_passphrase = NULL;
20

21
static char *_remote_default = NULL;
22

23 24
static int cred_acquire_cb(git_cred **,	const char *, const char *, unsigned int, void *);

25 26 27 28 29 30 31 32 33 34 35
static git_remote *_remote;
static record_callbacks_data _record_cbs_data = {{ 0 }};
static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data);

static git_oid _oid_b6;
static git_oid _oid_b5;
static git_oid _oid_b4;
static git_oid _oid_b3;
static git_oid _oid_b2;
static git_oid _oid_b1;

36 37 38 39
static git_oid _tag_commit;
static git_oid _tag_tree;
static git_oid _tag_blob;
static git_oid _tag_lightweight;
40
static git_oid _tag_tag;
41

42
static int cred_acquire_cb(
43 44 45 46 47
	git_cred **cred,
	const char *url,
	const char *user_from_url,
	unsigned int allowed_types,
	void *payload)
48 49
{
	GIT_UNUSED(url);
50
	GIT_UNUSED(user_from_url);
51
	GIT_UNUSED(payload);
52

53 54 55 56 57 58 59 60 61
	if (GIT_CREDTYPE_USERNAME & allowed_types) {
		if (!_remote_user) {
			printf("GITTEST_REMOTE_USER must be set\n");
			return -1;
		}

		return git_cred_username_new(cred, _remote_user);
	}

62 63 64 65 66 67 68 69 70
	if (GIT_CREDTYPE_DEFAULT & allowed_types) {
		if (!_remote_default) {
			printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n");
			return -1;
		}

		return git_cred_default_new(cred);
	}

71
	if (GIT_CREDTYPE_SSH_KEY & allowed_types) {
72 73 74 75
		if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) {
			printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n");
			return -1;
		}
76

77
		return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase);
78
	}
79

80 81 82 83 84
	if (GIT_CREDTYPE_USERPASS_PLAINTEXT & allowed_types) {
		if (!_remote_user || !_remote_pass) {
			printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n");
			return -1;
		}
85

86 87 88 89
		return git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass);
	}

	return -1;
90 91 92 93 94
}

/**
 * git_push_status_foreach callback that records status entries.
 */
95
static int record_push_status_cb(const char *ref, const char *msg, void *payload)
96
{
97
	record_callbacks_data *data = (record_callbacks_data *) payload;
98 99
	push_status *s;

100 101 102
	cl_assert(s = git__calloc(1, sizeof(*s)));
	if (ref)
		cl_assert(s->ref = git__strdup(ref));
103
	s->success = (msg == NULL);
104 105
	if (msg)
		cl_assert(s->msg = git__strdup(msg));
106

107
	git_vector_insert(&data->statuses, s);
108 109 110 111

	return 0;
}

112
static void do_verify_push_status(record_callbacks_data *data, const push_status expected[], const size_t expected_len)
113
{
114
	git_vector *actual = &data->statuses;
115 116 117 118
	push_status *iter;
	bool failed = false;
	size_t i;

119
	if (expected_len != actual->length)
120 121
		failed = true;
	else
122
		git_vector_foreach(actual, i, iter)
123
			if (strcmp(expected[i].ref, iter->ref) ||
124 125
				(expected[i].success != iter->success) ||
				(expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
126 127 128 129 130 131 132 133 134 135 136 137
				failed = true;
				break;
			}

	if (failed) {
		git_buf msg = GIT_BUF_INIT;

		git_buf_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n");

		for(i = 0; i < expected_len; i++) {
			git_buf_printf(&msg, "%s: %s\n",
				expected[i].ref,
138
				expected[i].success ? "success" : "failed");
139 140 141 142
		}

		git_buf_puts(&msg, "\nACTUAL:\n");

143
		git_vector_foreach(actual, i, iter) {
144 145 146 147 148
			if (iter->success)
				git_buf_printf(&msg, "%s: success\n", iter->ref);
			else
				git_buf_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg);
		}
149 150 151 152 153 154

		cl_fail(git_buf_cstr(&msg));

		git_buf_free(&msg);
	}

155
	git_vector_foreach(actual, i, iter)
156 157
		git__free(iter);

158
	git_vector_free(actual);
159 160 161 162 163
}

/**
 * Verifies that after git_push_finish(), refs on a remote have the expected
 * names, oids, and order.
164
 *
165 166 167 168 169 170
 * @param remote remote to verify
 * @param expected_refs expected remote refs after push
 * @param expected_refs_len length of expected_refs
 */
static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
{
171 172
	const git_remote_head **actual_refs;
	size_t actual_refs_len;
173

174 175
	git_remote_ls(&actual_refs, &actual_refs_len, remote);
	verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len);
176 177
}

178 179 180 181 182 183 184 185 186 187
/**
 * Verifies that after git_push_update_tips(), remote tracking branches have the expected
 * names and oids.
 *
 * @param remote remote to verify
 * @param expected_refs expected remote refs after push
 * @param expected_refs_len length of expected_refs
 */
static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
{
188
	git_refspec *fetch_spec;
189 190 191 192
	size_t i, j;
	git_buf msg = GIT_BUF_INIT;
	git_buf ref_name = GIT_BUF_INIT;
	git_vector actual_refs = GIT_VECTOR_INIT;
193
	git_branch_iterator *iter;
194 195
	char *actual_ref;
	git_oid oid;
196
	int failed = 0, error;
197
	git_branch_t branch_type;
198
	git_reference *ref;
199

200
	/* Get current remote-tracking branches */
201 202 203 204 205 206
	cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE));

	while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) {
		cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE);

		cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref))));
207 208

		git_reference_free(ref);
209 210 211
	}

	cl_assert_equal_i(error, GIT_ITEROVER);
212
	git_branch_iterator_free(iter);
213 214 215 216

	/* Loop through expected refs, make sure they exist */
	for (i = 0; i < expected_refs_len; i++) {

217
		/* Convert remote reference name into remote-tracking branch name.
218 219
		 * If the spec is not under refs/heads/, then skip.
		 */
220 221
		fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
		if (!fetch_spec)
222 223
			continue;

224
		cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
225 226 227

		/* Find matching remote branch */
		git_vector_foreach(&actual_refs, j, actual_ref) {
228
			if (!strcmp(git_buf_cstr(&ref_name), actual_ref))
229 230 231 232 233 234 235 236 237 238
				break;
		}

		if (j == actual_refs.length) {
			git_buf_printf(&msg, "Did not find expected tracking branch '%s'.", git_buf_cstr(&ref_name));
			failed = 1;
			goto failed;
		}

		/* Make sure tracking branch is at expected commit ID */
239
		cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref));
240 241 242 243 244 245 246

		if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) {
			git_buf_puts(&msg, "Tracking branch commit does not match expected ID.");
			failed = 1;
			goto failed;
		}

247
		git__free(actual_ref);
248 249 250 251 252 253 254 255 256 257 258
		cl_git_pass(git_vector_remove(&actual_refs, j));
	}

	/* Make sure there are no extra branches */
	if (actual_refs.length > 0) {
		git_buf_puts(&msg, "Unexpected remote tracking branches exist.");
		failed = 1;
		goto failed;
	}

failed:
259
	if (failed)
260 261 262 263 264 265 266
		cl_fail(git_buf_cstr(&msg));

	git_vector_foreach(&actual_refs, i, actual_ref)
		git__free(actual_ref);

	git_vector_free(&actual_refs);
	git_buf_free(&msg);
267
	git_buf_free(&ref_name);
268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300
}

static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
{
	git_refspec *fetch_spec;
	git_buf msg = GIT_BUF_INIT;
	git_buf ref_name = GIT_BUF_INIT;
	updated_tip *tip = NULL;
	size_t i, j;
	int failed = 0;

	for (i = 0; i < expected_refs_len; ++i) {
		/* Convert remote reference name into tracking branch name.
		 * If the spec is not under refs/heads/, then skip.
		 */
		fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
		if (!fetch_spec)
			continue;

		cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));

		/* Find matching update_tip entry */
		git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) {
			if (!strcmp(git_buf_cstr(&ref_name), tip->name))
				break;
		}

		if (j == _record_cbs_data.updated_tips.length) {
			git_buf_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", git_buf_cstr(&ref_name));
			failed = 1;
			goto failed;
		}

301
		if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) {
302 303 304 305 306 307 308 309 310 311 312 313
			git_buf_printf(&msg, "Updated tip ID does not match expected ID");
			failed = 1;
			goto failed;
		}
	}

failed:
	if (failed)
		cl_fail(git_buf_cstr(&msg));

	git_buf_free(&ref_name);
	git_buf_free(&msg);
314 315
}

316
void test_online_push__initialize(void)
317 318
{
	git_vector delete_specs = GIT_VECTOR_INIT;
319
	const git_remote_head **heads;
320
	size_t heads_len;
321 322
	git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
	git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
323 324 325

	_repo = cl_git_sandbox_init("push_src");

326
	cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com"));
327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349
	cl_fixture_sandbox("testrepo.git");
	cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git");

	rewrite_gitmodules(git_repository_workdir(_repo));

	/* git log --format=oneline --decorate --graph
	 * *-.   951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6
	 * |\ \
	 * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
	 * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
	 * | |/
	 * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
	 * |/
	 * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
	 * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
	 */
	git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce");
	git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2");
	git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d");
	git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4");
	git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247");
	git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247");

350 351 352 353
	git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac");
	git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e");
	git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498");
	git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce");
354
	git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5");
355

356
	/* Remote URL environment variable must be set.  User and password are optional.  */
357

358 359 360
	_remote_url = cl_getenv("GITTEST_REMOTE_URL");
	_remote_user = cl_getenv("GITTEST_REMOTE_USER");
	_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
361 362 363
	_remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY");
	_remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
	_remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
364
	_remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
365 366
	_remote = NULL;

Vicent Marti committed
367 368 369
	/* Skip the test if we're missing the remote URL */
	if (!_remote_url)
		cl_skip();
370

Vicent Marti committed
371
	cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
372

Vicent Marti committed
373
	record_callbacks_data_clear(&_record_cbs_data);
374

375
	cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL));
376

Vicent Marti committed
377 378 379 380 381 382 383 384 385
	/* Clean up previously pushed branches.  Fails if receive.denyDeletes is
	 * set on the remote.  Also, on Git 1.7.0 and newer, you must run
	 * 'git config receive.denyDeleteCurrent ignore' in the remote repo in
	 * order to delete the remote branch pointed to by HEAD (usually master).
	 * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt
	 */
	cl_git_pass(git_remote_ls(&heads, &heads_len, _remote));
	cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len));
	if (delete_specs.length) {
386 387 388 389
		git_strarray arr = {
			(char **) delete_specs.contents,
			delete_specs.length,
		};
390

391 392
		memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
		cl_git_pass(git_remote_upload(_remote, &arr, &push_opts));
Vicent Marti committed
393 394 395 396
	}

	git_remote_disconnect(_remote);
	git_vector_free(&delete_specs);
397

Vicent Marti committed
398
	/* Now that we've deleted everything, fetch from the remote */
399 400
	memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
	cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL));
401 402
}

403
void test_online_push__cleanup(void)
404 405 406
{
	if (_remote)
		git_remote_free(_remote);
407 408
	_remote = NULL;

409 410 411 412 413 414 415 416
	git__free(_remote_url);
	git__free(_remote_user);
	git__free(_remote_pass);
	git__free(_remote_ssh_key);
	git__free(_remote_ssh_pubkey);
	git__free(_remote_ssh_passphrase);
	git__free(_remote_default);

417 418
	/* Freed by cl_git_sandbox_cleanup */
	_repo = NULL;
419 420 421 422 423 424 425

	record_callbacks_data_clear(&_record_cbs_data);

	cl_fixture_cleanup("testrepo.git");
	cl_git_sandbox_cleanup();
}

426 427
static int push_pack_progress_cb(
	int stage, unsigned int current, unsigned int total, void* payload)
428
{
429
	record_callbacks_data *data = (record_callbacks_data *) payload;
430
	GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
431 432 433 434
	if (data->pack_progress_calls < 0)
		return data->pack_progress_calls;

	data->pack_progress_calls++;
435
	return 0;
436 437
}

438 439
static int push_transfer_progress_cb(
	unsigned int current, unsigned int total, size_t bytes, void* payload)
440
{
441
	record_callbacks_data *data = (record_callbacks_data *) payload;
442
	GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
443 444 445 446
	if (data->transfer_progress_calls < 0)
		return data->transfer_progress_calls;

	data->transfer_progress_calls++;
447
	return 0;
448 449
}

450 451
/**
 * Calls push and relists refs on remote to verify success.
452
 *
453 454 455 456 457
 * @param refspecs refspecs to push
 * @param refspecs_len length of refspecs
 * @param expected_refs expected remote refs after push
 * @param expected_refs_len length of expected_refs
 * @param expected_ret expected return value from git_push_finish()
458
 * @param check_progress_cb Check that the push progress callbacks are called
459
 */
460 461
static void do_push(
	const char *refspecs[], size_t refspecs_len,
462
	push_status expected_statuses[], size_t expected_statuses_len,
463
	expected_ref expected_refs[], size_t expected_refs_len,
464
	int expected_ret, int check_progress_cb, int check_update_tips_cb)
465
{
466
	git_push_options opts = GIT_PUSH_OPTIONS_INIT;
467
	size_t i;
468
	int error;
469
	git_strarray specs = {0};
470
	record_callbacks_data *data;
471 472

	if (_remote) {
473 474 475
		/* Auto-detect the number of threads to use */
		opts.pb_parallelism = 0;

476 477
		memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
		data = opts.callbacks.payload;
478

479 480 481
		opts.callbacks.pack_progress = push_pack_progress_cb;
		opts.callbacks.push_transfer_progress = push_transfer_progress_cb;
		opts.callbacks.push_update_reference = record_push_status_cb;
482

483 484 485 486
		if (refspecs_len) {
			specs.count = refspecs_len;
			specs.strings = git__calloc(refspecs_len, sizeof(char *));
			cl_assert(specs.strings);
487
		}
488

489
		for (i = 0; i < refspecs_len; i++)
490 491 492 493 494 495
			specs.strings[i] = (char *) refspecs[i];

		/* if EUSER, then abort in transfer */
		if (check_progress_cb && expected_ret == GIT_EUSER)
			data->transfer_progress_calls = GIT_EUSER;

496
		error = git_remote_push(_remote, &specs, &opts);
497
		git__free(specs.strings);
498 499

		if (expected_ret < 0) {
500
			cl_git_fail_with(expected_ret, error);
501
		} else {
502
			cl_git_pass(error);
503 504
		}

505 506 507
		if (check_progress_cb && expected_ret == 0) {
			cl_assert(data->pack_progress_calls > 0);
			cl_assert(data->transfer_progress_calls > 0);
508 509
		}

510
		do_verify_push_status(data, expected_statuses, expected_statuses_len);
511 512

		verify_refs(_remote, expected_refs, expected_refs_len);
513 514
		verify_tracking_branches(_remote, expected_refs, expected_refs_len);

515 516 517
		if (check_update_tips_cb)
			verify_update_tips_callback(_remote, expected_refs, expected_refs_len);

518
	}
519

520 521 522
}

/* Call push_finish() without ever calling git_push_add_refspec() */
523
void test_online_push__noop(void)
524
{
525
	do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1);
526 527
}

528
void test_online_push__b1(void)
529 530
{
	const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
531
	push_status exp_stats[] = { { "refs/heads/b1", 1 } };
532 533 534
	expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
535
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
536 537
}

538
void test_online_push__b2(void)
539 540
{
	const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
541
	push_status exp_stats[] = { { "refs/heads/b2", 1 } };
542 543 544
	expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
545
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
546 547
}

548
void test_online_push__b3(void)
549 550
{
	const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
551
	push_status exp_stats[] = { { "refs/heads/b3", 1 } };
552 553 554
	expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
555
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
556 557
}

558
void test_online_push__b4(void)
559 560
{
	const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
561
	push_status exp_stats[] = { { "refs/heads/b4", 1 } };
562 563 564
	expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
565
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
566 567
}

568
void test_online_push__b5(void)
569 570
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
571
	push_status exp_stats[] = { { "refs/heads/b5", 1 } };
572 573 574
	expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
575
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
576 577
}

578 579 580
void test_online_push__b5_cancel(void)
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
581
	do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
582 583
}

584
void test_online_push__multi(void)
585
{
586 587 588
	git_reflog *log;
	const git_reflog_entry *entry;

589 590 591 592 593 594 595 596
	const char *specs[] = {
		"refs/heads/b1:refs/heads/b1",
		"refs/heads/b2:refs/heads/b2",
		"refs/heads/b3:refs/heads/b3",
		"refs/heads/b4:refs/heads/b4",
		"refs/heads/b5:refs/heads/b5"
	};
	push_status exp_stats[] = {
597 598 599 600 601
		{ "refs/heads/b1", 1 },
		{ "refs/heads/b2", 1 },
		{ "refs/heads/b3", 1 },
		{ "refs/heads/b4", 1 },
		{ "refs/heads/b5", 1 }
602 603 604 605 606 607 608 609 610 611
	};
	expected_ref exp_refs[] = {
		{ "refs/heads/b1", &_oid_b1 },
		{ "refs/heads/b2", &_oid_b2 },
		{ "refs/heads/b3", &_oid_b3 },
		{ "refs/heads/b4", &_oid_b4 },
		{ "refs/heads/b5", &_oid_b5 }
	};
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
612
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
613 614 615

	cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
	entry = git_reflog_entry_byindex(log, 0);
616
	if (entry) {
617
		cl_assert_equal_s("update by push", git_reflog_entry_message(entry));
618 619
		cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
	}
620 621

	git_reflog_free(log);
622 623
}

624
void test_online_push__implicit_tgt(void)
625
{
626
	const char *specs1[] = { "refs/heads/b1" };
627
	push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
628 629
	expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };

630
	const char *specs2[] = { "refs/heads/b2" };
631
	push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
632 633 634 635 636 637 638
	expected_ref exp_refs2[] = {
	{ "refs/heads/b1", &_oid_b1 },
	{ "refs/heads/b2", &_oid_b2 }
	};

	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
639
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
640 641
	do_push(specs2, ARRAY_SIZE(specs2),
		exp_stats2, ARRAY_SIZE(exp_stats2),
642
		exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0);
643 644
}

645
void test_online_push__fast_fwd(void)
646 647 648 649
{
	/* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */

	const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
650
	push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
651 652 653
	expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };

	const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
654
	push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
655 656 657 658 659 660 661 662 663
	expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } };

	/* Do a force push to reset b1 in target back to _oid_b1 */
	const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" };
	/* Force should have no effect on a fast forward push */
	const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" };

	do_push(specs_init, ARRAY_SIZE(specs_init),
		exp_stats_init, ARRAY_SIZE(exp_stats_init),
664
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1);
665 666 667

	do_push(specs_ff, ARRAY_SIZE(specs_ff),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
668
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
669 670 671

	do_push(specs_reset, ARRAY_SIZE(specs_reset),
		exp_stats_init, ARRAY_SIZE(exp_stats_init),
672
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0);
673 674 675

	do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
676
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
677 678
}

679 680 681
void test_online_push__tag_commit(void)
{
	const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" };
682
	push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } };
683 684 685
	expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
686
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
687 688 689 690 691
}

void test_online_push__tag_tree(void)
{
	const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" };
692
	push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } };
693 694 695
	expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
696
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
697 698 699 700 701
}

void test_online_push__tag_blob(void)
{
	const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" };
702
	push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } };
703 704 705
	expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
706
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
707 708 709 710 711
}

void test_online_push__tag_lightweight(void)
{
	const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
712
	push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
713 714 715
	expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
716
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
717 718
}

719 720 721
void test_online_push__tag_to_tag(void)
{
	const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
722
	push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
723 724 725
	expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
726
		exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0);
727 728
}

729
void test_online_push__force(void)
730 731
{
	const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
732
	push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
733 734 735 736 737
	expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } };

	const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"};

	const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"};
738
	push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
739 740 741 742
	expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };

	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
743
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
744 745 746

	do_push(specs2, ARRAY_SIZE(specs2),
		NULL, 0,
747
		exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
748 749

	/* Non-fast-forward update with force should pass. */
750
	record_callbacks_data_clear(&_record_cbs_data);
751 752
	do_push(specs2_force, ARRAY_SIZE(specs2_force),
		exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
753
		exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1);
754 755
}

756
void test_online_push__delete(void)
757 758 759 760 761 762
{
	const char *specs1[] = {
		"refs/heads/b1:refs/heads/tgt1",
		"refs/heads/b1:refs/heads/tgt2"
	};
	push_status exp_stats1[] = {
763 764
		{ "refs/heads/tgt1", 1 },
		{ "refs/heads/tgt2", 1 }
765 766 767 768 769 770 771 772 773
	};
	expected_ref exp_refs1[] = {
		{ "refs/heads/tgt1", &_oid_b1 },
		{ "refs/heads/tgt2", &_oid_b1 }
	};

	const char *specs_del_fake[] = { ":refs/heads/fake" };
	/* Force has no effect for delete. */
	const char *specs_del_fake_force[] = { "+:refs/heads/fake" };
774
	push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
775 776

	const char *specs_delete[] = { ":refs/heads/tgt1" };
777
	push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
778 779 780 781 782 783
	expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } };
	/* Force has no effect for delete. */
	const char *specs_delete_force[] = { "+:refs/heads/tgt1" };

	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
784
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
785

786 787 788 789 790
	/* When deleting a non-existent branch, the git client sends zero for both
	 * the old and new commit id.  This should succeed on the server with the
	 * same status report as if the branch were actually deleted.  The server
	 * returns a warning on the side-band iff the side-band is supported.
	 *  Since libgit2 doesn't support the side-band yet, there are no warnings.
791 792
	 */
	do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
793
		exp_stats_fake, 1,
794
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
795
	do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
796
		exp_stats_fake, 1,
797
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
798 799 800 801

	/* Delete one of the pushed branches. */
	do_push(specs_delete, ARRAY_SIZE(specs_delete),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
802
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
803 804 805 806

	/* Re-push branches and retry delete with force. */
	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
807
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
808 809
	do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
810
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
811 812
}

813
void test_online_push__bad_refspecs(void)
814 815 816 817
{
	/* All classes of refspecs that should be rejected by
	 * git_push_add_refspec() should go in this test.
	 */
818 819 820 821 822 823 824
	char *specs = {
		"b6:b6",
	};
	git_strarray arr = {
		&specs,
		1,
	};
825 826

	if (_remote) {
827
		cl_git_fail(git_remote_upload(_remote, &arr, NULL));
828 829 830
	}
}

831
void test_online_push__expressions(void)
832 833 834 835 836 837 838
{
	/* TODO: Expressions in refspecs doesn't actually work yet */
	const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" };

	/* TODO: Find a more precise way of checking errors than a exit code of -1. */
	do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr),
		NULL, 0,
839
		NULL, 0, -1, 0, 0);
840
}
841

842
void test_online_push__notes(void)
843 844 845 846
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
	const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
847
	push_status exp_stats[] = { { "refs/notes/commits", 1 } };
848
	expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
849 850
	const char *specs_del[] = { ":refs/notes/commits" };

851 852 853 854 855 856
	git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");

	target_oid = &_oid_b6;

	/* Create note to push */
	cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
857
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
858 859 860

	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
861
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
862

863 864 865 866
	/* And make sure to delete the note */

	do_push(specs_del, ARRAY_SIZE(specs_del),
		exp_stats, 1,
867
		NULL, 0, 0, 0, 0);
868

869 870
	git_signature_free(signature);
}
871 872 873 874 875

void test_online_push__configured(void)
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
876
	git_remote *old_remote;
877 878 879 880 881 882 883 884 885
	const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
	push_status exp_stats[] = { { "refs/notes/commits", 1 } };
	expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
	const char *specs_del[] = { ":refs/notes/commits" };

	git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");

	target_oid = &_oid_b6;

886 887 888 889
	cl_git_pass(git_remote_add_push(_repo, git_remote_name(_remote), specs[0]));
	old_remote = _remote;
	cl_git_pass(git_remote_lookup(&_remote, _repo, git_remote_name(_remote)));
	git_remote_free(old_remote);
890 891 892

	/* Create note to push */
	cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
893
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
894 895 896 897 898 899 900 901 902 903 904 905 906

	do_push(NULL, 0,
		exp_stats, ARRAY_SIZE(exp_stats),
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);

	/* And make sure to delete the note */

	do_push(specs_del, ARRAY_SIZE(specs_del),
		exp_stats, 1,
		NULL, 0, 0, 0, 0);

	git_signature_free(signature);
}