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

static git_repository *_repo;

11
static char *_remote_url = NULL;
12

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

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

20
static char *_remote_default = NULL;
21
static char *_remote_expectcontinue = NULL;
22

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

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
	git_credential **cred,
44 45 46 47
	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
	if (GIT_CREDENTIAL_USERNAME & allowed_types) {
54 55 56 57 58
		if (!_remote_user) {
			printf("GITTEST_REMOTE_USER must be set\n");
			return -1;
		}

59
		return git_credential_username_new(cred, _remote_user);
60 61
	}

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

68
		return git_credential_default_new(cred);
69 70
	}

71
	if (GIT_CREDENTIAL_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_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase);
78
	}
79

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

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

	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
				failed = true;
				break;
			}

	if (failed) {
131
		git_str msg = GIT_STR_INIT;
132

133
		git_str_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n");
134 135

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

141
		git_str_puts(&msg, "\nACTUAL:\n");
142

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

150
		cl_fail(git_str_cstr(&msg));
151

152
		git_str_dispose(&msg);
153 154
	}

155 156 157 158 159 160
	git_vector_foreach(actual, i, iter) {
		push_status *s = (push_status *)iter;
		git__free(s->ref);
		git__free(s->msg);
		git__free(s);
	}
161

162
	git_vector_free(actual);
163 164 165 166 167
}

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

178 179
	git_remote_ls(&actual_refs, &actual_refs_len, remote);
	verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len);
180 181
}

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

204
	/* Get current remote-tracking branches */
205 206 207 208 209 210
	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))));
211 212

		git_reference_free(ref);
213 214 215
	}

	cl_assert_equal_i(error, GIT_ITEROVER);
216
	git_branch_iterator_free(iter);
217 218 219 220

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

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

228
		cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
229 230 231

		/* Find matching remote branch */
		git_vector_foreach(&actual_refs, j, actual_ref) {
232
			if (!strcmp(ref_name.ptr, actual_ref))
233 234 235 236
				break;
		}

		if (j == actual_refs.length) {
237
			git_str_printf(&msg, "Did not find expected tracking branch '%s'.", ref_name.ptr);
238 239 240 241 242
			failed = 1;
			goto failed;
		}

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

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

251
		git__free(actual_ref);
252 253 254 255 256
		cl_git_pass(git_vector_remove(&actual_refs, j));
	}

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

failed:
263
	if (failed)
264
		cl_fail(git_str_cstr(&msg));
265 266 267 268 269

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

	git_vector_free(&actual_refs);
270
	git_str_dispose(&msg);
271
	git_buf_dispose(&ref_name);
272 273 274 275 276
}

static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
{
	git_refspec *fetch_spec;
277
	git_str msg = GIT_STR_INIT;
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294
	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) {
295
			if (!strcmp(ref_name.ptr, tip->name))
296 297 298 299
				break;
		}

		if (j == _record_cbs_data.updated_tips.length) {
300
			git_str_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", ref_name.ptr);
301 302 303 304
			failed = 1;
			goto failed;
		}

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

failed:
	if (failed)
314
		cl_fail(git_str_cstr(&msg));
315

316
	git_buf_dispose(&ref_name);
317
	git_str_dispose(&msg);
318 319
}

320
void test_online_push__initialize(void)
321 322
{
	git_vector delete_specs = GIT_VECTOR_INIT;
323
	const git_remote_head **heads;
324
	size_t heads_len;
325 326
	git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
	git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
327 328 329

	_repo = cl_git_sandbox_init("push_src");

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

354 355 356 357
	git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac");
	git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e");
	git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498");
	git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce");
358
	git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5");
359

360
	/* Remote URL environment variable must be set.  User and password are optional.  */
361

362 363 364
	_remote_url = cl_getenv("GITTEST_REMOTE_URL");
	_remote_user = cl_getenv("GITTEST_REMOTE_USER");
	_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
365 366 367
	_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");
368
	_remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
369
	_remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE");
370 371
	_remote = NULL;

Vicent Marti committed
372 373 374
	/* Skip the test if we're missing the remote URL */
	if (!_remote_url)
		cl_skip();
375

376 377 378
	if (_remote_expectcontinue)
		git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);

Vicent Marti committed
379
	cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
380

Vicent Marti committed
381
	record_callbacks_data_clear(&_record_cbs_data);
382

383
	cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL));
384

Vicent Marti committed
385 386 387 388 389 390 391 392 393
	/* 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) {
394 395 396 397
		git_strarray arr = {
			(char **) delete_specs.contents,
			delete_specs.length,
		};
398

399 400
		memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
		cl_git_pass(git_remote_upload(_remote, &arr, &push_opts));
Vicent Marti committed
401 402 403
	}

	git_remote_disconnect(_remote);
404
	git_vector_free_deep(&delete_specs);
405

Vicent Marti committed
406
	/* Now that we've deleted everything, fetch from the remote */
407 408
	memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
	cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL));
409 410
}

411
void test_online_push__cleanup(void)
412 413 414
{
	if (_remote)
		git_remote_free(_remote);
415 416
	_remote = NULL;

417 418 419 420 421 422 423
	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);
424
	git__free(_remote_expectcontinue);
425

426 427
	/* Freed by cl_git_sandbox_cleanup */
	_repo = NULL;
428

429 430
	git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 0);

431 432 433 434 435 436
	record_callbacks_data_clear(&_record_cbs_data);

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

437 438
static int push_pack_progress_cb(
	int stage, unsigned int current, unsigned int total, void* payload)
439
{
440
	record_callbacks_data *data = (record_callbacks_data *) payload;
441
	GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
442 443 444 445
	if (data->pack_progress_calls < 0)
		return data->pack_progress_calls;

	data->pack_progress_calls++;
446
	return 0;
447 448
}

449 450
static int push_transfer_progress_cb(
	unsigned int current, unsigned int total, size_t bytes, void* payload)
451
{
452
	record_callbacks_data *data = (record_callbacks_data *) payload;
453
	GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
454 455 456 457
	if (data->transfer_progress_calls < 0)
		return data->transfer_progress_calls;

	data->transfer_progress_calls++;
458
	return 0;
459 460
}

461 462
/**
 * Calls push and relists refs on remote to verify success.
463
 *
464 465 466 467 468
 * @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()
469
 * @param check_progress_cb Check that the push progress callbacks are called
470
 */
471 472
static void do_push(
	const char *refspecs[], size_t refspecs_len,
473
	push_status expected_statuses[], size_t expected_statuses_len,
474
	expected_ref expected_refs[], size_t expected_refs_len,
475
	int expected_ret, int check_progress_cb, int check_update_tips_cb)
476
{
477
	git_push_options opts = GIT_PUSH_OPTIONS_INIT;
478
	size_t i;
479
	int error;
480
	git_strarray specs = {0};
481
	record_callbacks_data *data;
482 483

	if (_remote) {
484 485 486
		/* Auto-detect the number of threads to use */
		opts.pb_parallelism = 0;

487 488
		memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
		data = opts.callbacks.payload;
489

490 491 492
		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;
493

494 495 496 497
		if (refspecs_len) {
			specs.count = refspecs_len;
			specs.strings = git__calloc(refspecs_len, sizeof(char *));
			cl_assert(specs.strings);
498
		}
499

500
		for (i = 0; i < refspecs_len; i++)
501 502 503 504 505 506
			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;

507
		error = git_remote_push(_remote, &specs, &opts);
508
		git__free(specs.strings);
509 510

		if (expected_ret < 0) {
511
			cl_git_fail_with(expected_ret, error);
512
		} else {
513
			cl_git_pass(error);
514 515
		}

516 517 518
		if (check_progress_cb && expected_ret == 0) {
			cl_assert(data->pack_progress_calls > 0);
			cl_assert(data->transfer_progress_calls > 0);
519 520
		}

521
		do_verify_push_status(data, expected_statuses, expected_statuses_len);
522 523

		verify_refs(_remote, expected_refs, expected_refs_len);
524 525
		verify_tracking_branches(_remote, expected_refs, expected_refs_len);

526 527 528
		if (check_update_tips_cb)
			verify_update_tips_callback(_remote, expected_refs, expected_refs_len);

529
	}
530

531 532 533
}

/* Call push_finish() without ever calling git_push_add_refspec() */
534
void test_online_push__noop(void)
535
{
536
	do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1);
537 538
}

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

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

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

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

579
void test_online_push__b5(void)
580 581
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
582
	push_status exp_stats[] = { { "refs/heads/b5", 1 } };
583 584 585
	expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
586
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
587 588
}

589 590 591
void test_online_push__b5_cancel(void)
{
	const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
592
	do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
593 594
}

595
void test_online_push__multi(void)
596
{
597 598 599
	git_reflog *log;
	const git_reflog_entry *entry;

600 601 602 603 604 605 606 607
	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[] = {
608 609 610 611 612
		{ "refs/heads/b1", 1 },
		{ "refs/heads/b2", 1 },
		{ "refs/heads/b3", 1 },
		{ "refs/heads/b4", 1 },
		{ "refs/heads/b5", 1 }
613 614 615 616 617 618 619 620 621 622
	};
	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),
623
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
624 625 626

	cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
	entry = git_reflog_entry_byindex(log, 0);
627
	if (entry) {
628
		cl_assert_equal_s("update by push", git_reflog_entry_message(entry));
629 630
		cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
	}
631 632

	git_reflog_free(log);
633 634
}

635
void test_online_push__implicit_tgt(void)
636
{
637
	const char *specs1[] = { "refs/heads/b1" };
638
	push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
639 640
	expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };

641
	const char *specs2[] = { "refs/heads/b2" };
642
	push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
643 644 645 646 647 648 649
	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),
650
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
651 652
	do_push(specs2, ARRAY_SIZE(specs2),
		exp_stats2, ARRAY_SIZE(exp_stats2),
653
		exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0);
654 655
}

656
void test_online_push__fast_fwd(void)
657 658 659 660
{
	/* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */

	const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
661
	push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
662 663 664
	expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };

	const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
665
	push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
666 667 668 669 670 671 672 673 674
	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),
675
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1);
676 677 678

	do_push(specs_ff, ARRAY_SIZE(specs_ff),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
679
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
680 681 682

	do_push(specs_reset, ARRAY_SIZE(specs_reset),
		exp_stats_init, ARRAY_SIZE(exp_stats_init),
683
		exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0);
684 685 686

	do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
		exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
687
		exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
688 689
}

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

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

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

void test_online_push__tag_lightweight(void)
{
	const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
723
	push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
724 725 726
	expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
727
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
728 729
}

730 731 732
void test_online_push__tag_to_tag(void)
{
	const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
733
	push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
734 735 736
	expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
737
		exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0);
738 739
}

740
void test_online_push__force(void)
741 742
{
	const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
743
	push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
744 745 746 747 748
	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"};
749
	push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
750 751 752 753
	expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };

	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
754
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
755 756 757

	do_push(specs2, ARRAY_SIZE(specs2),
		NULL, 0,
758
		exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
759 760

	/* Non-fast-forward update with force should pass. */
761
	record_callbacks_data_clear(&_record_cbs_data);
762 763
	do_push(specs2_force, ARRAY_SIZE(specs2_force),
		exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
764
		exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1);
765 766
}

767
void test_online_push__delete(void)
768 769 770 771 772 773
{
	const char *specs1[] = {
		"refs/heads/b1:refs/heads/tgt1",
		"refs/heads/b1:refs/heads/tgt2"
	};
	push_status exp_stats1[] = {
774 775
		{ "refs/heads/tgt1", 1 },
		{ "refs/heads/tgt2", 1 }
776 777 778 779 780 781 782 783 784
	};
	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" };
785
	push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
786 787

	const char *specs_delete[] = { ":refs/heads/tgt1" };
788
	push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
789 790 791 792 793 794
	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),
795
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
796

797 798 799 800 801
	/* 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.
802 803
	 */
	do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
804
		exp_stats_fake, 1,
805
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
806
	do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
807
		exp_stats_fake, 1,
808
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
809 810 811 812

	/* Delete one of the pushed branches. */
	do_push(specs_delete, ARRAY_SIZE(specs_delete),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
813
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
814 815 816 817

	/* Re-push branches and retry delete with force. */
	do_push(specs1, ARRAY_SIZE(specs1),
		exp_stats1, ARRAY_SIZE(exp_stats1),
818
		exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
819 820
	do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
		exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
821
		exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
822 823
}

824
void test_online_push__bad_refspecs(void)
825 826 827 828
{
	/* All classes of refspecs that should be rejected by
	 * git_push_add_refspec() should go in this test.
	 */
829 830 831 832 833 834 835
	char *specs = {
		"b6:b6",
	};
	git_strarray arr = {
		&specs,
		1,
	};
836 837

	if (_remote) {
838
		cl_git_fail(git_remote_upload(_remote, &arr, NULL));
839 840 841
	}
}

842
void test_online_push__expressions(void)
843 844 845 846 847 848 849
{
	/* 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,
850
		NULL, 0, -1, 0, 0);
851
}
852

853
void test_online_push__notes(void)
854 855 856 857
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
	const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
858
	push_status exp_stats[] = { { "refs/notes/commits", 1 } };
859
	expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
860 861
	const char *specs_del[] = { ":refs/notes/commits" };

862 863 864 865 866 867
	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 */
868
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
869 870 871

	do_push(specs, ARRAY_SIZE(specs),
		exp_stats, ARRAY_SIZE(exp_stats),
872
		exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
873

874 875 876 877
	/* And make sure to delete the note */

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

880 881
	git_signature_free(signature);
}
882 883 884 885 886

void test_online_push__configured(void)
{
	git_oid note_oid, *target_oid, expected_oid;
	git_signature *signature;
887
	git_remote *old_remote;
888 889 890 891 892 893 894 895 896
	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;

897 898 899 900
	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);
901 902 903

	/* 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 */
904
	cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
905 906 907 908 909 910 911 912 913 914 915 916 917

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