]> git.proxmox.com Git - libgit2.git/blob - tests/online/push.c
c82b606cb9afcb45bf0a7d59a2a368d579c94d1b
[libgit2.git] / tests / online / push.c
1 #include "clar_libgit2.h"
2 #include "buffer.h"
3 #include "posix.h"
4 #include "vector.h"
5 #include "../submodule/submodule_helpers.h"
6 #include "push_util.h"
7 #include "refspec.h"
8 #include "remote.h"
9
10 static git_repository *_repo;
11
12 static char *_remote_url = NULL;
13
14 static char *_remote_user = NULL;
15 static char *_remote_pass = NULL;
16
17 static char *_remote_ssh_key = NULL;
18 static char *_remote_ssh_pubkey = NULL;
19 static char *_remote_ssh_passphrase = NULL;
20
21 static char *_remote_default = NULL;
22 static char *_remote_expectcontinue = NULL;
23
24 static int cred_acquire_cb(git_credential **, const char *, const char *, unsigned int, void *);
25
26 static git_remote *_remote;
27 static record_callbacks_data _record_cbs_data = {{ 0 }};
28 static git_remote_callbacks _record_cbs = RECORD_CALLBACKS_INIT(&_record_cbs_data);
29
30 static git_oid _oid_b6;
31 static git_oid _oid_b5;
32 static git_oid _oid_b4;
33 static git_oid _oid_b3;
34 static git_oid _oid_b2;
35 static git_oid _oid_b1;
36
37 static git_oid _tag_commit;
38 static git_oid _tag_tree;
39 static git_oid _tag_blob;
40 static git_oid _tag_lightweight;
41 static git_oid _tag_tag;
42
43 static int cred_acquire_cb(
44 git_credential **cred,
45 const char *url,
46 const char *user_from_url,
47 unsigned int allowed_types,
48 void *payload)
49 {
50 GIT_UNUSED(url);
51 GIT_UNUSED(user_from_url);
52 GIT_UNUSED(payload);
53
54 if (GIT_CREDENTIAL_USERNAME & allowed_types) {
55 if (!_remote_user) {
56 printf("GITTEST_REMOTE_USER must be set\n");
57 return -1;
58 }
59
60 return git_credential_username_new(cred, _remote_user);
61 }
62
63 if (GIT_CREDENTIAL_DEFAULT & allowed_types) {
64 if (!_remote_default) {
65 printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n");
66 return -1;
67 }
68
69 return git_credential_default_new(cred);
70 }
71
72 if (GIT_CREDENTIAL_SSH_KEY & allowed_types) {
73 if (!_remote_user || !_remote_ssh_pubkey || !_remote_ssh_key || !_remote_ssh_passphrase) {
74 printf("GITTEST_REMOTE_USER, GITTEST_REMOTE_SSH_PUBKEY, GITTEST_REMOTE_SSH_KEY and GITTEST_REMOTE_SSH_PASSPHRASE must be set\n");
75 return -1;
76 }
77
78 return git_credential_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase);
79 }
80
81 if (GIT_CREDENTIAL_USERPASS_PLAINTEXT & allowed_types) {
82 if (!_remote_user || !_remote_pass) {
83 printf("GITTEST_REMOTE_USER and GITTEST_REMOTE_PASS must be set\n");
84 return -1;
85 }
86
87 return git_credential_userpass_plaintext_new(cred, _remote_user, _remote_pass);
88 }
89
90 return -1;
91 }
92
93 /**
94 * git_push_status_foreach callback that records status entries.
95 */
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 push_status *s;
100
101 cl_assert(s = git__calloc(1, sizeof(*s)));
102 if (ref)
103 cl_assert(s->ref = git__strdup(ref));
104 s->success = (msg == NULL);
105 if (msg)
106 cl_assert(s->msg = git__strdup(msg));
107
108 git_vector_insert(&data->statuses, s);
109
110 return 0;
111 }
112
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 push_status *iter;
117 bool failed = false;
118 size_t i;
119
120 if (expected_len != actual->length)
121 failed = true;
122 else
123 git_vector_foreach(actual, i, iter)
124 if (strcmp(expected[i].ref, iter->ref) ||
125 (expected[i].success != iter->success) ||
126 (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
127 failed = true;
128 break;
129 }
130
131 if (failed) {
132 git_buf msg = GIT_BUF_INIT;
133
134 git_buf_puts(&msg, "Expected and actual push statuses differ:\nEXPECTED:\n");
135
136 for(i = 0; i < expected_len; i++) {
137 git_buf_printf(&msg, "%s: %s\n",
138 expected[i].ref,
139 expected[i].success ? "success" : "failed");
140 }
141
142 git_buf_puts(&msg, "\nACTUAL:\n");
143
144 git_vector_foreach(actual, i, iter) {
145 if (iter->success)
146 git_buf_printf(&msg, "%s: success\n", iter->ref);
147 else
148 git_buf_printf(&msg, "%s: failed with message: %s", iter->ref, iter->msg);
149 }
150
151 cl_fail(git_buf_cstr(&msg));
152
153 git_buf_dispose(&msg);
154 }
155
156 git_vector_foreach(actual, i, iter) {
157 push_status *s = (push_status *)iter;
158 git__free(s->ref);
159 git__free(s->msg);
160 git__free(s);
161 }
162
163 git_vector_free(actual);
164 }
165
166 /**
167 * Verifies that after git_push_finish(), refs on a remote have the expected
168 * names, oids, and order.
169 *
170 * @param remote remote to verify
171 * @param expected_refs expected remote refs after push
172 * @param expected_refs_len length of expected_refs
173 */
174 static void verify_refs(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
175 {
176 const git_remote_head **actual_refs;
177 size_t actual_refs_len;
178
179 git_remote_ls(&actual_refs, &actual_refs_len, remote);
180 verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len);
181 }
182
183 /**
184 * Verifies that after git_push_update_tips(), remote tracking branches have the expected
185 * names and oids.
186 *
187 * @param remote remote to verify
188 * @param expected_refs expected remote refs after push
189 * @param expected_refs_len length of expected_refs
190 */
191 static void verify_tracking_branches(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
192 {
193 git_refspec *fetch_spec;
194 size_t i, j;
195 git_buf msg = GIT_BUF_INIT;
196 git_buf ref_name = GIT_BUF_INIT;
197 git_vector actual_refs = GIT_VECTOR_INIT;
198 git_branch_iterator *iter;
199 char *actual_ref;
200 git_oid oid;
201 int failed = 0, error;
202 git_branch_t branch_type;
203 git_reference *ref;
204
205 /* Get current remote-tracking branches */
206 cl_git_pass(git_branch_iterator_new(&iter, remote->repo, GIT_BRANCH_REMOTE));
207
208 while ((error = git_branch_next(&ref, &branch_type, iter)) == 0) {
209 cl_assert_equal_i(branch_type, GIT_BRANCH_REMOTE);
210
211 cl_git_pass(git_vector_insert(&actual_refs, git__strdup(git_reference_name(ref))));
212
213 git_reference_free(ref);
214 }
215
216 cl_assert_equal_i(error, GIT_ITEROVER);
217 git_branch_iterator_free(iter);
218
219 /* Loop through expected refs, make sure they exist */
220 for (i = 0; i < expected_refs_len; i++) {
221
222 /* Convert remote reference name into remote-tracking branch name.
223 * If the spec is not under refs/heads/, then skip.
224 */
225 fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
226 if (!fetch_spec)
227 continue;
228
229 cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
230
231 /* Find matching remote branch */
232 git_vector_foreach(&actual_refs, j, actual_ref) {
233 if (!strcmp(git_buf_cstr(&ref_name), actual_ref))
234 break;
235 }
236
237 if (j == actual_refs.length) {
238 git_buf_printf(&msg, "Did not find expected tracking branch '%s'.", git_buf_cstr(&ref_name));
239 failed = 1;
240 goto failed;
241 }
242
243 /* Make sure tracking branch is at expected commit ID */
244 cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref));
245
246 if (git_oid_cmp(expected_refs[i].oid, &oid) != 0) {
247 git_buf_puts(&msg, "Tracking branch commit does not match expected ID.");
248 failed = 1;
249 goto failed;
250 }
251
252 git__free(actual_ref);
253 cl_git_pass(git_vector_remove(&actual_refs, j));
254 }
255
256 /* Make sure there are no extra branches */
257 if (actual_refs.length > 0) {
258 git_buf_puts(&msg, "Unexpected remote tracking branches exist.");
259 failed = 1;
260 goto failed;
261 }
262
263 failed:
264 if (failed)
265 cl_fail(git_buf_cstr(&msg));
266
267 git_vector_foreach(&actual_refs, i, actual_ref)
268 git__free(actual_ref);
269
270 git_vector_free(&actual_refs);
271 git_buf_dispose(&msg);
272 git_buf_dispose(&ref_name);
273 }
274
275 static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
276 {
277 git_refspec *fetch_spec;
278 git_buf msg = GIT_BUF_INIT;
279 git_buf ref_name = GIT_BUF_INIT;
280 updated_tip *tip = NULL;
281 size_t i, j;
282 int failed = 0;
283
284 for (i = 0; i < expected_refs_len; ++i) {
285 /* Convert remote reference name into tracking branch name.
286 * If the spec is not under refs/heads/, then skip.
287 */
288 fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
289 if (!fetch_spec)
290 continue;
291
292 cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
293
294 /* Find matching update_tip entry */
295 git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) {
296 if (!strcmp(git_buf_cstr(&ref_name), tip->name))
297 break;
298 }
299
300 if (j == _record_cbs_data.updated_tips.length) {
301 git_buf_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", git_buf_cstr(&ref_name));
302 failed = 1;
303 goto failed;
304 }
305
306 if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) {
307 git_buf_printf(&msg, "Updated tip ID does not match expected ID");
308 failed = 1;
309 goto failed;
310 }
311 }
312
313 failed:
314 if (failed)
315 cl_fail(git_buf_cstr(&msg));
316
317 git_buf_dispose(&ref_name);
318 git_buf_dispose(&msg);
319 }
320
321 void test_online_push__initialize(void)
322 {
323 git_vector delete_specs = GIT_VECTOR_INIT;
324 const git_remote_head **heads;
325 size_t heads_len;
326 git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
327 git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
328
329 _repo = cl_git_sandbox_init("push_src");
330
331 cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com"));
332 cl_fixture_sandbox("testrepo.git");
333 cl_rename("push_src/submodule/.gitted", "push_src/submodule/.git");
334
335 rewrite_gitmodules(git_repository_workdir(_repo));
336
337 /* git log --format=oneline --decorate --graph
338 * *-. 951bbbb90e2259a4c8950db78946784fb53fcbce (HEAD, b6) merge b3, b4, and b5 to b6
339 * |\ \
340 * | | * fa38b91f199934685819bea316186d8b008c52a2 (b5) added submodule named 'submodule' pointing to '../testrepo.git'
341 * | * | 27b7ce66243eb1403862d05f958c002312df173d (b4) edited fold\b.txt
342 * | |/
343 * * | d9b63a88223d8367516f50bd131a5f7349b7f3e4 (b3) edited a.txt
344 * |/
345 * * a78705c3b2725f931d3ee05348d83cc26700f247 (b2, b1) added fold and fold/b.txt
346 * * 5c0bb3d1b9449d1cc69d7519fd05166f01840915 added a.txt
347 */
348 git_oid_fromstr(&_oid_b6, "951bbbb90e2259a4c8950db78946784fb53fcbce");
349 git_oid_fromstr(&_oid_b5, "fa38b91f199934685819bea316186d8b008c52a2");
350 git_oid_fromstr(&_oid_b4, "27b7ce66243eb1403862d05f958c002312df173d");
351 git_oid_fromstr(&_oid_b3, "d9b63a88223d8367516f50bd131a5f7349b7f3e4");
352 git_oid_fromstr(&_oid_b2, "a78705c3b2725f931d3ee05348d83cc26700f247");
353 git_oid_fromstr(&_oid_b1, "a78705c3b2725f931d3ee05348d83cc26700f247");
354
355 git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac");
356 git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e");
357 git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498");
358 git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce");
359 git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5");
360
361 /* Remote URL environment variable must be set. User and password are optional. */
362
363 _remote_url = cl_getenv("GITTEST_REMOTE_URL");
364 _remote_user = cl_getenv("GITTEST_REMOTE_USER");
365 _remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
366 _remote_ssh_key = cl_getenv("GITTEST_REMOTE_SSH_KEY");
367 _remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
368 _remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
369 _remote_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
370 _remote_expectcontinue = cl_getenv("GITTEST_REMOTE_EXPECTCONTINUE");
371 _remote = NULL;
372
373 /* Skip the test if we're missing the remote URL */
374 if (!_remote_url)
375 cl_skip();
376
377 if (_remote_expectcontinue)
378 git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 1);
379
380 cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
381
382 record_callbacks_data_clear(&_record_cbs_data);
383
384 cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL));
385
386 /* Clean up previously pushed branches. Fails if receive.denyDeletes is
387 * set on the remote. Also, on Git 1.7.0 and newer, you must run
388 * 'git config receive.denyDeleteCurrent ignore' in the remote repo in
389 * order to delete the remote branch pointed to by HEAD (usually master).
390 * See: https://raw.github.com/git/git/master/Documentation/RelNotes/1.7.0.txt
391 */
392 cl_git_pass(git_remote_ls(&heads, &heads_len, _remote));
393 cl_git_pass(create_deletion_refspecs(&delete_specs, heads, heads_len));
394 if (delete_specs.length) {
395 git_strarray arr = {
396 (char **) delete_specs.contents,
397 delete_specs.length,
398 };
399
400 memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
401 cl_git_pass(git_remote_upload(_remote, &arr, &push_opts));
402 }
403
404 git_remote_disconnect(_remote);
405 git_vector_free_deep(&delete_specs);
406
407 /* Now that we've deleted everything, fetch from the remote */
408 memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
409 cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL));
410 }
411
412 void test_online_push__cleanup(void)
413 {
414 if (_remote)
415 git_remote_free(_remote);
416 _remote = NULL;
417
418 git__free(_remote_url);
419 git__free(_remote_user);
420 git__free(_remote_pass);
421 git__free(_remote_ssh_key);
422 git__free(_remote_ssh_pubkey);
423 git__free(_remote_ssh_passphrase);
424 git__free(_remote_default);
425 git__free(_remote_expectcontinue);
426
427 /* Freed by cl_git_sandbox_cleanup */
428 _repo = NULL;
429
430 git_libgit2_opts(GIT_OPT_ENABLE_HTTP_EXPECT_CONTINUE, 0);
431
432 record_callbacks_data_clear(&_record_cbs_data);
433
434 cl_fixture_cleanup("testrepo.git");
435 cl_git_sandbox_cleanup();
436 }
437
438 static int push_pack_progress_cb(
439 int stage, unsigned int current, unsigned int total, void* payload)
440 {
441 record_callbacks_data *data = (record_callbacks_data *) payload;
442 GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
443 if (data->pack_progress_calls < 0)
444 return data->pack_progress_calls;
445
446 data->pack_progress_calls++;
447 return 0;
448 }
449
450 static int push_transfer_progress_cb(
451 unsigned int current, unsigned int total, size_t bytes, void* payload)
452 {
453 record_callbacks_data *data = (record_callbacks_data *) payload;
454 GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
455 if (data->transfer_progress_calls < 0)
456 return data->transfer_progress_calls;
457
458 data->transfer_progress_calls++;
459 return 0;
460 }
461
462 /**
463 * Calls push and relists refs on remote to verify success.
464 *
465 * @param refspecs refspecs to push
466 * @param refspecs_len length of refspecs
467 * @param expected_refs expected remote refs after push
468 * @param expected_refs_len length of expected_refs
469 * @param expected_ret expected return value from git_push_finish()
470 * @param check_progress_cb Check that the push progress callbacks are called
471 */
472 static void do_push(
473 const char *refspecs[], size_t refspecs_len,
474 push_status expected_statuses[], size_t expected_statuses_len,
475 expected_ref expected_refs[], size_t expected_refs_len,
476 int expected_ret, int check_progress_cb, int check_update_tips_cb)
477 {
478 git_push_options opts = GIT_PUSH_OPTIONS_INIT;
479 size_t i;
480 int error;
481 git_strarray specs = {0};
482 record_callbacks_data *data;
483
484 if (_remote) {
485 /* Auto-detect the number of threads to use */
486 opts.pb_parallelism = 0;
487
488 memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
489 data = opts.callbacks.payload;
490
491 opts.callbacks.pack_progress = push_pack_progress_cb;
492 opts.callbacks.push_transfer_progress = push_transfer_progress_cb;
493 opts.callbacks.push_update_reference = record_push_status_cb;
494
495 if (refspecs_len) {
496 specs.count = refspecs_len;
497 specs.strings = git__calloc(refspecs_len, sizeof(char *));
498 cl_assert(specs.strings);
499 }
500
501 for (i = 0; i < refspecs_len; i++)
502 specs.strings[i] = (char *) refspecs[i];
503
504 /* if EUSER, then abort in transfer */
505 if (check_progress_cb && expected_ret == GIT_EUSER)
506 data->transfer_progress_calls = GIT_EUSER;
507
508 error = git_remote_push(_remote, &specs, &opts);
509 git__free(specs.strings);
510
511 if (expected_ret < 0) {
512 cl_git_fail_with(expected_ret, error);
513 } else {
514 cl_git_pass(error);
515 }
516
517 if (check_progress_cb && expected_ret == 0) {
518 cl_assert(data->pack_progress_calls > 0);
519 cl_assert(data->transfer_progress_calls > 0);
520 }
521
522 do_verify_push_status(data, expected_statuses, expected_statuses_len);
523
524 verify_refs(_remote, expected_refs, expected_refs_len);
525 verify_tracking_branches(_remote, expected_refs, expected_refs_len);
526
527 if (check_update_tips_cb)
528 verify_update_tips_callback(_remote, expected_refs, expected_refs_len);
529
530 }
531
532 }
533
534 /* Call push_finish() without ever calling git_push_add_refspec() */
535 void test_online_push__noop(void)
536 {
537 do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0, 1);
538 }
539
540 void test_online_push__b1(void)
541 {
542 const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
543 push_status exp_stats[] = { { "refs/heads/b1", 1 } };
544 expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
545 do_push(specs, ARRAY_SIZE(specs),
546 exp_stats, ARRAY_SIZE(exp_stats),
547 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
548 }
549
550 void test_online_push__b2(void)
551 {
552 const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
553 push_status exp_stats[] = { { "refs/heads/b2", 1 } };
554 expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
555 do_push(specs, ARRAY_SIZE(specs),
556 exp_stats, ARRAY_SIZE(exp_stats),
557 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
558 }
559
560 void test_online_push__b3(void)
561 {
562 const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
563 push_status exp_stats[] = { { "refs/heads/b3", 1 } };
564 expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
565 do_push(specs, ARRAY_SIZE(specs),
566 exp_stats, ARRAY_SIZE(exp_stats),
567 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
568 }
569
570 void test_online_push__b4(void)
571 {
572 const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
573 push_status exp_stats[] = { { "refs/heads/b4", 1 } };
574 expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
575 do_push(specs, ARRAY_SIZE(specs),
576 exp_stats, ARRAY_SIZE(exp_stats),
577 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
578 }
579
580 void test_online_push__b5(void)
581 {
582 const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
583 push_status exp_stats[] = { { "refs/heads/b5", 1 } };
584 expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
585 do_push(specs, ARRAY_SIZE(specs),
586 exp_stats, ARRAY_SIZE(exp_stats),
587 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
588 }
589
590 void test_online_push__b5_cancel(void)
591 {
592 const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
593 do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
594 }
595
596 void test_online_push__multi(void)
597 {
598 git_reflog *log;
599 const git_reflog_entry *entry;
600
601 const char *specs[] = {
602 "refs/heads/b1:refs/heads/b1",
603 "refs/heads/b2:refs/heads/b2",
604 "refs/heads/b3:refs/heads/b3",
605 "refs/heads/b4:refs/heads/b4",
606 "refs/heads/b5:refs/heads/b5"
607 };
608 push_status exp_stats[] = {
609 { "refs/heads/b1", 1 },
610 { "refs/heads/b2", 1 },
611 { "refs/heads/b3", 1 },
612 { "refs/heads/b4", 1 },
613 { "refs/heads/b5", 1 }
614 };
615 expected_ref exp_refs[] = {
616 { "refs/heads/b1", &_oid_b1 },
617 { "refs/heads/b2", &_oid_b2 },
618 { "refs/heads/b3", &_oid_b3 },
619 { "refs/heads/b4", &_oid_b4 },
620 { "refs/heads/b5", &_oid_b5 }
621 };
622 do_push(specs, ARRAY_SIZE(specs),
623 exp_stats, ARRAY_SIZE(exp_stats),
624 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
625
626 cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
627 entry = git_reflog_entry_byindex(log, 0);
628 if (entry) {
629 cl_assert_equal_s("update by push", git_reflog_entry_message(entry));
630 cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
631 }
632
633 git_reflog_free(log);
634 }
635
636 void test_online_push__implicit_tgt(void)
637 {
638 const char *specs1[] = { "refs/heads/b1" };
639 push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
640 expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };
641
642 const char *specs2[] = { "refs/heads/b2" };
643 push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
644 expected_ref exp_refs2[] = {
645 { "refs/heads/b1", &_oid_b1 },
646 { "refs/heads/b2", &_oid_b2 }
647 };
648
649 do_push(specs1, ARRAY_SIZE(specs1),
650 exp_stats1, ARRAY_SIZE(exp_stats1),
651 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
652 do_push(specs2, ARRAY_SIZE(specs2),
653 exp_stats2, ARRAY_SIZE(exp_stats2),
654 exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0, 0);
655 }
656
657 void test_online_push__fast_fwd(void)
658 {
659 /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */
660
661 const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
662 push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
663 expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };
664
665 const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
666 push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
667 expected_ref exp_refs_ff[] = { { "refs/heads/b1", &_oid_b6 } };
668
669 /* Do a force push to reset b1 in target back to _oid_b1 */
670 const char *specs_reset[] = { "+refs/heads/b1:refs/heads/b1" };
671 /* Force should have no effect on a fast forward push */
672 const char *specs_ff_force[] = { "+refs/heads/b6:refs/heads/b1" };
673
674 do_push(specs_init, ARRAY_SIZE(specs_init),
675 exp_stats_init, ARRAY_SIZE(exp_stats_init),
676 exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1, 1);
677
678 do_push(specs_ff, ARRAY_SIZE(specs_ff),
679 exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
680 exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
681
682 do_push(specs_reset, ARRAY_SIZE(specs_reset),
683 exp_stats_init, ARRAY_SIZE(exp_stats_init),
684 exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0, 0);
685
686 do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
687 exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
688 exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0, 0);
689 }
690
691 void test_online_push__tag_commit(void)
692 {
693 const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" };
694 push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } };
695 expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } };
696 do_push(specs, ARRAY_SIZE(specs),
697 exp_stats, ARRAY_SIZE(exp_stats),
698 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
699 }
700
701 void test_online_push__tag_tree(void)
702 {
703 const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" };
704 push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } };
705 expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } };
706 do_push(specs, ARRAY_SIZE(specs),
707 exp_stats, ARRAY_SIZE(exp_stats),
708 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
709 }
710
711 void test_online_push__tag_blob(void)
712 {
713 const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" };
714 push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } };
715 expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } };
716 do_push(specs, ARRAY_SIZE(specs),
717 exp_stats, ARRAY_SIZE(exp_stats),
718 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
719 }
720
721 void test_online_push__tag_lightweight(void)
722 {
723 const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
724 push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
725 expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
726 do_push(specs, ARRAY_SIZE(specs),
727 exp_stats, ARRAY_SIZE(exp_stats),
728 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
729 }
730
731 void test_online_push__tag_to_tag(void)
732 {
733 const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
734 push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
735 expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
736 do_push(specs, ARRAY_SIZE(specs),
737 exp_stats, ARRAY_SIZE(exp_stats),
738 exp_refs, ARRAY_SIZE(exp_refs), 0, 0, 0);
739 }
740
741 void test_online_push__force(void)
742 {
743 const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
744 push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
745 expected_ref exp_refs1[] = { { "refs/heads/tgt", &_oid_b3 } };
746
747 const char *specs2[] = {"refs/heads/b4:refs/heads/tgt"};
748
749 const char *specs2_force[] = {"+refs/heads/b4:refs/heads/tgt"};
750 push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
751 expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };
752
753 do_push(specs1, ARRAY_SIZE(specs1),
754 exp_stats1, ARRAY_SIZE(exp_stats1),
755 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
756
757 do_push(specs2, ARRAY_SIZE(specs2),
758 NULL, 0,
759 exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
760
761 /* Non-fast-forward update with force should pass. */
762 record_callbacks_data_clear(&_record_cbs_data);
763 do_push(specs2_force, ARRAY_SIZE(specs2_force),
764 exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
765 exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1, 1);
766 }
767
768 void test_online_push__delete(void)
769 {
770 const char *specs1[] = {
771 "refs/heads/b1:refs/heads/tgt1",
772 "refs/heads/b1:refs/heads/tgt2"
773 };
774 push_status exp_stats1[] = {
775 { "refs/heads/tgt1", 1 },
776 { "refs/heads/tgt2", 1 }
777 };
778 expected_ref exp_refs1[] = {
779 { "refs/heads/tgt1", &_oid_b1 },
780 { "refs/heads/tgt2", &_oid_b1 }
781 };
782
783 const char *specs_del_fake[] = { ":refs/heads/fake" };
784 /* Force has no effect for delete. */
785 const char *specs_del_fake_force[] = { "+:refs/heads/fake" };
786 push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
787
788 const char *specs_delete[] = { ":refs/heads/tgt1" };
789 push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
790 expected_ref exp_refs_delete[] = { { "refs/heads/tgt2", &_oid_b1 } };
791 /* Force has no effect for delete. */
792 const char *specs_delete_force[] = { "+:refs/heads/tgt1" };
793
794 do_push(specs1, ARRAY_SIZE(specs1),
795 exp_stats1, ARRAY_SIZE(exp_stats1),
796 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1, 1);
797
798 /* When deleting a non-existent branch, the git client sends zero for both
799 * the old and new commit id. This should succeed on the server with the
800 * same status report as if the branch were actually deleted. The server
801 * returns a warning on the side-band iff the side-band is supported.
802 * Since libgit2 doesn't support the side-band yet, there are no warnings.
803 */
804 do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
805 exp_stats_fake, 1,
806 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
807 do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
808 exp_stats_fake, 1,
809 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
810
811 /* Delete one of the pushed branches. */
812 do_push(specs_delete, ARRAY_SIZE(specs_delete),
813 exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
814 exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
815
816 /* Re-push branches and retry delete with force. */
817 do_push(specs1, ARRAY_SIZE(specs1),
818 exp_stats1, ARRAY_SIZE(exp_stats1),
819 exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0, 0);
820 do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
821 exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
822 exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0, 0);
823 }
824
825 void test_online_push__bad_refspecs(void)
826 {
827 /* All classes of refspecs that should be rejected by
828 * git_push_add_refspec() should go in this test.
829 */
830 char *specs = {
831 "b6:b6",
832 };
833 git_strarray arr = {
834 &specs,
835 1,
836 };
837
838 if (_remote) {
839 cl_git_fail(git_remote_upload(_remote, &arr, NULL));
840 }
841 }
842
843 void test_online_push__expressions(void)
844 {
845 /* TODO: Expressions in refspecs doesn't actually work yet */
846 const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" };
847
848 /* TODO: Find a more precise way of checking errors than a exit code of -1. */
849 do_push(specs_left_expr, ARRAY_SIZE(specs_left_expr),
850 NULL, 0,
851 NULL, 0, -1, 0, 0);
852 }
853
854 void test_online_push__notes(void)
855 {
856 git_oid note_oid, *target_oid, expected_oid;
857 git_signature *signature;
858 const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
859 push_status exp_stats[] = { { "refs/notes/commits", 1 } };
860 expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
861 const char *specs_del[] = { ":refs/notes/commits" };
862
863 git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");
864
865 target_oid = &_oid_b6;
866
867 /* Create note to push */
868 cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
869 cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
870
871 do_push(specs, ARRAY_SIZE(specs),
872 exp_stats, ARRAY_SIZE(exp_stats),
873 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
874
875 /* And make sure to delete the note */
876
877 do_push(specs_del, ARRAY_SIZE(specs_del),
878 exp_stats, 1,
879 NULL, 0, 0, 0, 0);
880
881 git_signature_free(signature);
882 }
883
884 void test_online_push__configured(void)
885 {
886 git_oid note_oid, *target_oid, expected_oid;
887 git_signature *signature;
888 git_remote *old_remote;
889 const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
890 push_status exp_stats[] = { { "refs/notes/commits", 1 } };
891 expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
892 const char *specs_del[] = { ":refs/notes/commits" };
893
894 git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");
895
896 target_oid = &_oid_b6;
897
898 cl_git_pass(git_remote_add_push(_repo, git_remote_name(_remote), specs[0]));
899 old_remote = _remote;
900 cl_git_pass(git_remote_lookup(&_remote, _repo, git_remote_name(_remote)));
901 git_remote_free(old_remote);
902
903 /* Create note to push */
904 cl_git_pass(git_signature_new(&signature, "nulltoken", "emeric.fermas@gmail.com", 1323847743, 60)); /* Wed Dec 14 08:29:03 2011 +0100 */
905 cl_git_pass(git_note_create(&note_oid, _repo, NULL, signature, signature, target_oid, "hello world\n", 0));
906
907 do_push(NULL, 0,
908 exp_stats, ARRAY_SIZE(exp_stats),
909 exp_refs, ARRAY_SIZE(exp_refs), 0, 1, 1);
910
911 /* And make sure to delete the note */
912
913 do_push(specs_del, ARRAY_SIZE(specs_del),
914 exp_stats, 1,
915 NULL, 0, 0, 0, 0);
916
917 git_signature_free(signature);
918 }