push.c 27.3 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 13
static char *_remote_url;

14 15 16 17
static char *_remote_ssh_key;
static char *_remote_ssh_pubkey;
static char *_remote_ssh_passphrase;

18 19 20
static char *_remote_user;
static char *_remote_pass;

21 22
static char *_remote_default;

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 95
}

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

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

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

	return 0;
}

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

120
	if (expected_len != actual->length)
121 122
		failed = true;
	else
123
		git_vector_foreach(actual, i, iter)
124
			if (strcmp(expected[i].ref, iter->ref) ||
125 126
				(expected[i].success != iter->success) ||
				(expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
127 128 129 130 131 132 133 134 135 136 137 138
				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,
139
				expected[i].success ? "success" : "failed");
140 141 142 143
		}

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

144
		git_vector_foreach(actual, i, iter) {
145 146 147 148 149
			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);
		}
150 151 152 153 154 155

		cl_fail(git_buf_cstr(&msg));

		git_buf_free(&msg);
	}

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

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

/**
 * Verifies that after git_push_finish(), refs on a remote have the expected
 * names, oids, and order.
165
 *
166 167 168 169 170 171
 * @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)
{
172 173
	const git_remote_head **actual_refs;
	size_t actual_refs_len;
174

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

179 180 181 182 183 184 185 186 187 188
/**
 * 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)
{
189
	git_refspec *fetch_spec;
190 191 192 193
	size_t i, j;
	git_buf msg = GIT_BUF_INIT;
	git_buf ref_name = GIT_BUF_INIT;
	git_vector actual_refs = GIT_VECTOR_INIT;
194
	git_branch_iterator *iter;
195 196
	char *actual_ref;
	git_oid oid;
197
	int failed = 0, error;
198
	git_branch_t branch_type;
199
	git_reference *ref;
200

201
	/* Get current remote-tracking branches */
202 203 204 205 206 207
	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))));
208 209

		git_reference_free(ref);
210 211 212
	}

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

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

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

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

		/* Find matching remote branch */
		git_vector_foreach(&actual_refs, j, actual_ref) {
229
			if (!strcmp(git_buf_cstr(&ref_name), actual_ref))
230 231 232 233 234 235 236 237 238 239
				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 */
240
		cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref));
241 242 243 244 245 246 247

		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;
		}

248
		git__free(actual_ref);
249 250 251 252 253 254 255 256 257 258 259
		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:
260
	if (failed)
261 262 263 264 265 266 267
		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);
268
	git_buf_free(&ref_name);
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 301 302 303 304 305 306 307 308 309 310 311 312 313 314
}

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;
		}

		if (git_oid_cmp(expected_refs[i].oid, tip->new_oid) != 0) {
			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);
315 316
}

317
void test_online_push__initialize(void)
318 319
{
	git_vector delete_specs = GIT_VECTOR_INIT;
320
	const git_remote_head **heads;
321
	size_t heads_len;
322 323 324

	_repo = cl_git_sandbox_init("push_src");

325
	cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com"));
326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348
	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");

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

355 356 357 358
	/* Remote URL environment variable must be set.  User and password are optional.  */
	_remote_url = cl_getenv("GITTEST_REMOTE_URL");
	_remote_user = cl_getenv("GITTEST_REMOTE_USER");
	_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
359 360 361
	_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");
362
	_remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
363 364
	_remote = NULL;

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

Vicent Marti committed
369
	cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
370

Vicent Marti committed
371 372
	record_callbacks_data_clear(&_record_cbs_data);
	git_remote_set_callbacks(_remote, &_record_cbs);
373

Vicent Marti committed
374
	cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
375

Vicent Marti committed
376 377 378 379 380 381 382 383 384
	/* 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) {
385 386 387 388
		git_strarray arr = {
			(char **) delete_specs.contents,
			delete_specs.length,
		};
389

390
		cl_git_pass(git_remote_upload(_remote, &arr, NULL));
Vicent Marti committed
391 392 393 394
	}

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

Vicent Marti committed
396 397
	/* Now that we've deleted everything, fetch from the remote */
	cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH));
398
	cl_git_pass(git_remote_download(_remote, NULL));
399
	cl_git_pass(git_remote_update_tips(_remote, NULL));
Vicent Marti committed
400
	git_remote_disconnect(_remote);
401 402
}

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

	/* Freed by cl_git_sandbox_cleanup */
	_repo = NULL;
411 412 413 414 415 416 417

	record_callbacks_data_clear(&_record_cbs_data);

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

418 419
static int push_pack_progress_cb(
	int stage, unsigned int current, unsigned int total, void* payload)
420
{
421
	record_callbacks_data *data = (record_callbacks_data *) payload;
422
	GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
423 424 425 426
	if (data->pack_progress_calls < 0)
		return data->pack_progress_calls;

	data->pack_progress_calls++;
427
	return 0;
428 429
}

430 431
static int push_transfer_progress_cb(
	unsigned int current, unsigned int total, size_t bytes, void* payload)
432
{
433
	record_callbacks_data *data = (record_callbacks_data *) payload;
434
	GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
435 436 437 438
	if (data->transfer_progress_calls < 0)
		return data->transfer_progress_calls;

	data->transfer_progress_calls++;
439
	return 0;
440 441
}

442 443
/**
 * Calls push and relists refs on remote to verify success.
444
 *
445 446 447 448 449
 * @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()
450
 * @param check_progress_cb Check that the push progress callbacks are called
451
 */
452 453
static void do_push(
	const char *refspecs[], size_t refspecs_len,
454
	push_status expected_statuses[], size_t expected_statuses_len,
455
	expected_ref expected_refs[], size_t expected_refs_len,
456
	int expected_ret, int check_progress_cb, int check_update_tips_cb)
457
{
458
	git_push_options opts = GIT_PUSH_OPTIONS_INIT;
459
	size_t i;
460
	int error;
461
	git_strarray specs = {0};
462 463
	git_remote_callbacks callbacks;
	record_callbacks_data *data;
464 465

	if (_remote) {
466 467 468
		/* Auto-detect the number of threads to use */
		opts.pb_parallelism = 0;

469 470
		memcpy(&callbacks, git_remote_get_callbacks(_remote), sizeof(callbacks));
		data = callbacks.payload;
471

472 473 474 475
		callbacks.pack_progress = push_pack_progress_cb;
		callbacks.push_transfer_progress = push_transfer_progress_cb;
		callbacks.push_update_reference = record_push_status_cb;
		cl_git_pass(git_remote_set_callbacks(_remote, &callbacks));
476

477 478 479 480
		if (refspecs_len) {
			specs.count = refspecs_len;
			specs.strings = git__calloc(refspecs_len, sizeof(char *));
			cl_assert(specs.strings);
481
		}
482

483
		for (i = 0; i < refspecs_len; i++)
484 485 486 487 488 489
			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;

490
		error = git_remote_push(_remote, &specs, &opts);
491
		git__free(specs.strings);
492 493

		if (expected_ret < 0) {
494
			cl_git_fail_with(expected_ret, error);
495
		} else {
496
			cl_git_pass(error);
497 498
		}

499 500 501
		if (check_progress_cb && expected_ret == 0) {
			cl_assert(data->pack_progress_calls > 0);
			cl_assert(data->transfer_progress_calls > 0);
502 503
		}

504
		do_verify_push_status(data, expected_statuses, expected_statuses_len);
505 506

		verify_refs(_remote, expected_refs, expected_refs_len);
507 508
		verify_tracking_branches(_remote, expected_refs, expected_refs_len);

509 510 511
		if (check_update_tips_cb)
			verify_update_tips_callback(_remote, expected_refs, expected_refs_len);

512
	}
513

514 515 516
}

/* Call push_finish() without ever calling git_push_add_refspec() */
517
void test_online_push__noop(void)
518
{
519
	do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1);
520 521
}

522
void test_online_push__b1(void)
523 524
{
	const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
525
	push_status exp_stats[] = { { "refs/heads/b1", 1 } };
526 527 528
	expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
529
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
530 531
}

532
void test_online_push__b2(void)
533 534
{
	const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
535
	push_status exp_stats[] = { { "refs/heads/b2", 1 } };
536 537 538
	expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
539
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
540 541
}

542
void test_online_push__b3(void)
543 544
{
	const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
545
	push_status exp_stats[] = { { "refs/heads/b3", 1 } };
546 547 548
	expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
549
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
550 551
}

552
void test_online_push__b4(void)
553 554
{
	const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
555
	push_status exp_stats[] = { { "refs/heads/b4", 1 } };
556 557 558
	expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
559
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
560 561
}

562
void test_online_push__b5(void)
563 564
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
565
	push_status exp_stats[] = { { "refs/heads/b5", 1 } };
566 567 568
	expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
569
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
570 571
}

572 573 574
void test_online_push__b5_cancel(void)
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
575
	do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
576 577
}

578
void test_online_push__multi(void)
579
{
580 581 582
	git_reflog *log;
	const git_reflog_entry *entry;

583 584 585 586 587 588 589 590
	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[] = {
591 592 593 594 595
		{ "refs/heads/b1", 1 },
		{ "refs/heads/b2", 1 },
		{ "refs/heads/b3", 1 },
		{ "refs/heads/b4", 1 },
		{ "refs/heads/b5", 1 }
596 597 598 599 600 601 602 603 604 605
	};
	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),
606
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
607 608 609

	cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
	entry = git_reflog_entry_byindex(log, 0);
610
	if (entry) {
611
		cl_assert_equal_s("update by push", git_reflog_entry_message(entry));
612 613
		cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
	}
614 615

	git_reflog_free(log);
616 617
}

618
void test_online_push__implicit_tgt(void)
619
{
620
	const char *specs1[] = { "refs/heads/b1" };
621
	push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
622 623
	expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };

624
	const char *specs2[] = { "refs/heads/b2" };
625
	push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
626 627 628 629 630 631 632
	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),
633
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
634 635
	do_push(specs2, ARRAY_SIZE(specs2),
		exp_stats2, ARRAY_SIZE(exp_stats2),
636
		exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0);
637 638
}

639
void test_online_push__fast_fwd(void)
640 641 642 643
{
	/* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */

	const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
644
	push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
645 646 647
	expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };

	const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
648
	push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
649 650 651 652 653 654 655 656 657
	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),
658
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1);
659 660 661

	do_push(specs_ff, ARRAY_SIZE(specs_ff),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
662
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
663 664 665

	do_push(specs_reset, ARRAY_SIZE(specs_reset),
		exp_stats_init, ARRAY_SIZE(exp_stats_init),
666
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0);
667 668 669

	do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
670
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
671 672
}

673 674 675
void test_online_push__tag_commit(void)
{
	const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" };
676
	push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } };
677 678 679
	expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
680
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
681 682 683 684 685
}

void test_online_push__tag_tree(void)
{
	const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" };
686
	push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } };
687 688 689
	expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
690
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
691 692 693 694 695
}

void test_online_push__tag_blob(void)
{
	const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" };
696
	push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } };
697 698 699
	expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
700
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
701 702 703 704 705
}

void test_online_push__tag_lightweight(void)
{
	const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
706
	push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
707 708 709
	expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
710
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
711 712
}

713 714 715
void test_online_push__tag_to_tag(void)
{
	const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
716
	push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
717 718 719
	expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
720
		exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0);
721 722
}

723
void test_online_push__force(void)
724 725
{
	const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
726
	push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
727 728 729 730 731
	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"};
732
	push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
733 734 735 736
	expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };

	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
737
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
738 739 740

	do_push(specs2, ARRAY_SIZE(specs2),
		NULL, 0,
741
		exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
742 743

	/* Non-fast-forward update with force should pass. */
744
	record_callbacks_data_clear(&_record_cbs_data);
745 746
	do_push(specs2_force, ARRAY_SIZE(specs2_force),
		exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
747
		exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1);
748 749
}

750
void test_online_push__delete(void)
751 752 753 754 755 756
{
	const char *specs1[] = {
		"refs/heads/b1:refs/heads/tgt1",
		"refs/heads/b1:refs/heads/tgt2"
	};
	push_status exp_stats1[] = {
757 758
		{ "refs/heads/tgt1", 1 },
		{ "refs/heads/tgt2", 1 }
759 760 761 762 763 764 765 766 767
	};
	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" };
768
	push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
769 770

	const char *specs_delete[] = { ":refs/heads/tgt1" };
771
	push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
772 773 774 775 776 777
	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),
778
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
779

780 781 782 783 784
	/* 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.
785 786
	 */
	do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
787
		exp_stats_fake, 1,
788
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
789
	do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
790
		exp_stats_fake, 1,
791
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
792 793 794 795

	/* Delete one of the pushed branches. */
	do_push(specs_delete, ARRAY_SIZE(specs_delete),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
796
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
797 798 799 800

	/* Re-push branches and retry delete with force. */
	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
801
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
802 803
	do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
804
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
805 806
}

807
void test_online_push__bad_refspecs(void)
808 809 810 811
{
	/* All classes of refspecs that should be rejected by
	 * git_push_add_refspec() should go in this test.
	 */
812 813 814 815 816 817 818
	char *specs = {
		"b6:b6",
	};
	git_strarray arr = {
		&specs,
		1,
	};
819 820

	if (_remote) {
821
		cl_git_fail(git_remote_upload(_remote, &arr, NULL));
822 823 824
	}
}

825
void test_online_push__expressions(void)
826 827 828 829 830 831 832
{
	/* 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,
833
		NULL, 0, -1, 0, 0);
834
}
835

836
void test_online_push__notes(void)
837 838 839 840
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
	const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
841
	push_status exp_stats[] = { { "refs/notes/commits", 1 } };
842
	expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
843 844
	const char *specs_del[] = { ":refs/notes/commits" };

845 846 847 848 849 850
	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 */
851
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
852 853 854

	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
855
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
856

857 858 859 860
	/* And make sure to delete the note */

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

863 864
	git_signature_free(signature);
}
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882

void test_online_push__configured(void)
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
	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;

	cl_git_pass(git_remote_add_push(_remote, specs[0]));

	/* 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 */
883
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
884 885 886 887 888 889 890 891 892 893 894 895 896

	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);
}