summaryrefslogtreecommitdiff
path: root/tests/online
diff options
context:
space:
mode:
authorBen Straub <bs@github.com>2013-11-14 14:05:52 -0800
committerBen Straub <bs@github.com>2013-11-14 14:05:52 -0800
commit1782038144ef3413831801bb9c2f3038a84ac6f4 (patch)
treef074cc30890a20f5418c10fae1815ca516588a27 /tests/online
parent7b947bf5cc59eefa83c28eb5f5fd8434207ebb8b (diff)
downloadlibgit2-1782038144ef3413831801bb9c2f3038a84ac6f4.tar.gz
Rename tests-clar to tests
Diffstat (limited to 'tests/online')
-rw-r--r--tests/online/clone.c256
-rw-r--r--tests/online/fetch.c171
-rw-r--r--tests/online/fetchhead.c91
-rw-r--r--tests/online/push.c770
-rw-r--r--tests/online/push_util.c132
-rw-r--r--tests/online/push_util.h71
6 files changed, 1491 insertions, 0 deletions
diff --git a/tests/online/clone.c b/tests/online/clone.c
new file mode 100644
index 000000000..aa3d6b26a
--- /dev/null
+++ b/tests/online/clone.c
@@ -0,0 +1,256 @@
+#include "clar_libgit2.h"
+
+#include "git2/clone.h"
+#include "git2/cred_helpers.h"
+#include "remote.h"
+#include "fileops.h"
+#include "refs.h"
+
+#define LIVE_REPO_URL "http://github.com/libgit2/TestGitRepository"
+#define LIVE_EMPTYREPO_URL "http://github.com/libgit2/TestEmptyRepository"
+#define BB_REPO_URL "https://libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_PASS "https://libgit2:libgit2@bitbucket.org/libgit2/testgitrepository.git"
+#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit2:wrong@bitbucket.org/libgit2/testgitrepository.git"
+#define ASSEMBLA_REPO_URL "https://libgit2:_Libgit2@git.assembla.com/libgit2-test-repos.git"
+
+static git_repository *g_repo;
+static git_clone_options g_options;
+
+void test_online_clone__initialize(void)
+{
+ git_checkout_opts dummy_opts = GIT_CHECKOUT_OPTS_INIT;
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.checkout_opts = dummy_opts;
+ g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
+ g_options.remote_callbacks = dummy_callbacks;
+}
+
+void test_online_clone__cleanup(void)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__network_full(void)
+{
+ git_remote *origin;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(!git_repository_is_bare(g_repo));
+ cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
+
+ cl_assert_equal_i(GIT_REMOTE_DOWNLOAD_TAGS_AUTO, origin->download_tags);
+
+ git_remote_free(origin);
+}
+
+void test_online_clone__network_bare(void)
+{
+ git_remote *origin;
+
+ g_options.bare = true;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(git_repository_is_bare(g_repo));
+ cl_git_pass(git_remote_load(&origin, g_repo, "origin"));
+
+ git_remote_free(origin);
+}
+
+void test_online_clone__empty_repository(void)
+{
+ git_reference *head;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_EMPTYREPO_URL, "./foo", &g_options));
+
+ cl_assert_equal_i(true, git_repository_is_empty(g_repo));
+ cl_assert_equal_i(true, git_repository_head_unborn(g_repo));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, GIT_HEAD_FILE));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ git_reference_free(head);
+}
+
+static void checkout_progress(const char *path, size_t cur, size_t tot, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
+ (*was_called) = true;
+}
+
+static int fetch_progress(const git_transfer_progress *stats, void *payload)
+{
+ bool *was_called = (bool*)payload;
+ GIT_UNUSED(stats);
+ (*was_called) = true;
+ return 0;
+}
+
+void test_online_clone__can_checkout_a_cloned_repo(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_reference *head;
+ bool checkout_progress_cb_was_called = false,
+ fetch_progress_cb_was_called = false;
+
+ g_options.checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ g_options.checkout_opts.progress_cb = &checkout_progress;
+ g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
+ g_options.remote_callbacks.transfer_progress = &fetch_progress;
+ g_options.remote_callbacks.payload = &fetch_progress_cb_was_called;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
+ cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path)));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ cl_assert_equal_i(true, checkout_progress_cb_was_called);
+ cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
+ git_reference_free(head);
+ git_buf_free(&path);
+}
+
+void test_online_clone__clone_into(void)
+{
+ git_buf path = GIT_BUF_INIT;
+ git_remote *remote;
+ git_reference *head;
+ git_checkout_opts checkout_opts = GIT_CHECKOUT_OPTS_INIT;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ bool checkout_progress_cb_was_called = false,
+ fetch_progress_cb_was_called = false;
+
+ checkout_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
+ checkout_opts.progress_cb = &checkout_progress;
+ checkout_opts.progress_payload = &checkout_progress_cb_was_called;
+
+ cl_git_pass(git_repository_init(&g_repo, "./foo", false));
+ cl_git_pass(git_remote_create(&remote, g_repo, "origin", LIVE_REPO_URL));
+
+ callbacks.transfer_progress = &fetch_progress;
+ callbacks.payload = &fetch_progress_cb_was_called;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ cl_git_pass(git_clone_into(g_repo, remote, &checkout_opts, NULL));
+
+ cl_git_pass(git_buf_joinpath(&path, git_repository_workdir(g_repo), "master.txt"));
+ cl_assert_equal_i(true, git_path_isfile(git_buf_cstr(&path)));
+
+ cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
+ cl_assert_equal_i(GIT_REF_SYMBOLIC, git_reference_type(head));
+ cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
+
+ cl_assert_equal_i(true, checkout_progress_cb_was_called);
+ cl_assert_equal_i(true, fetch_progress_cb_was_called);
+
+ git_remote_free(remote);
+ git_reference_free(head);
+ git_buf_free(&path);
+}
+
+static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *payload)
+{
+ int *callcount = (int*)payload;
+ GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b);
+ *callcount = *callcount + 1;
+ return 0;
+}
+
+void test_online_clone__custom_remote_callbacks(void)
+{
+ int callcount = 0;
+
+ g_options.remote_callbacks.update_tips = update_tips;
+ g_options.remote_callbacks.payload = &callcount;
+
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+ cl_assert(callcount > 0);
+}
+
+void test_online_clone__credentials(void)
+{
+ /* Remote URL environment variable must be set. User and password are optional. */
+ const char *remote_url = cl_getenv("GITTEST_REMOTE_URL");
+ git_cred_userpass_payload user_pass = {
+ cl_getenv("GITTEST_REMOTE_USER"),
+ cl_getenv("GITTEST_REMOTE_PASS")
+ };
+
+ if (!remote_url) return;
+
+ g_options.remote_callbacks.credentials = git_cred_userpass;
+ g_options.remote_callbacks.payload = &user_pass;
+
+ cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__bitbucket_style(void)
+{
+ git_cred_userpass_payload user_pass = {
+ "libgit2", "libgit2"
+ };
+
+ g_options.remote_callbacks.credentials = git_cred_userpass;
+ g_options.remote_callbacks.payload = &user_pass;
+
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* User and pass from URL */
+ user_pass.password = "wrong";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+
+ /* Wrong password in URL, fall back to user_pass */
+ user_pass.password = "libgit2";
+ cl_git_pass(git_clone(&g_repo, BB_REPO_URL_WITH_WRONG_PASS, "./foo", &g_options));
+ git_repository_free(g_repo); g_repo = NULL;
+ cl_fixture_cleanup("./foo");
+}
+
+void test_online_clone__assembla_style(void)
+{
+ cl_git_pass(git_clone(&g_repo, ASSEMBLA_REPO_URL, "./foo", NULL));
+}
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ if (stats->received_objects > (stats->total_objects/2))
+ return 1;
+ return 0;
+}
+
+void test_online_clone__can_cancel(void)
+{
+ g_options.remote_callbacks.transfer_progress = cancel_at_half;
+
+ cl_git_fail_with(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), GIT_EUSER);
+}
+
+
+
+
+
+
diff --git a/tests/online/fetch.c b/tests/online/fetch.c
new file mode 100644
index 000000000..5153a7ae0
--- /dev/null
+++ b/tests/online/fetch.c
@@ -0,0 +1,171 @@
+#include "clar_libgit2.h"
+
+static git_repository *_repo;
+static int counter;
+
+void test_online_fetch__initialize(void)
+{
+ cl_git_pass(git_repository_init(&_repo, "./fetch", 0));
+}
+
+void test_online_fetch__cleanup(void)
+{
+ git_repository_free(_repo);
+ _repo = NULL;
+
+ cl_fixture_cleanup("./fetch");
+}
+
+static int update_tips(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ GIT_UNUSED(refname); GIT_UNUSED(a); GIT_UNUSED(b); GIT_UNUSED(data);
+
+ ++counter;
+
+ return 0;
+}
+
+static int progress(const git_transfer_progress *stats, void *payload)
+{
+ size_t *bytes_received = (size_t *)payload;
+ *bytes_received = stats->received_bytes;
+ return 0;
+}
+
+static void do_fetch(const char *url, git_remote_autotag_option_t flag, int n)
+{
+ git_remote *remote;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ size_t bytes_received = 0;
+
+ callbacks.transfer_progress = progress;
+ callbacks.update_tips = update_tips;
+ callbacks.payload = &bytes_received;
+ counter = 0;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test", url));
+ git_remote_set_callbacks(remote, &callbacks);
+ git_remote_set_autotag(remote, flag);
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote));
+ git_remote_disconnect(remote);
+ cl_assert_equal_i(counter, n);
+ cl_assert(bytes_received > 0);
+
+ git_remote_free(remote);
+}
+
+void test_online_fetch__default_git(void)
+{
+ do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
+}
+
+void test_online_fetch__default_http(void)
+{
+ do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
+}
+
+void test_online_fetch__default_https(void)
+{
+ do_fetch("https://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_AUTO, 6);
+}
+
+void test_online_fetch__no_tags_git(void)
+{
+ do_fetch("git://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
+}
+
+void test_online_fetch__no_tags_http(void)
+{
+ do_fetch("http://github.com/libgit2/TestGitRepository.git", GIT_REMOTE_DOWNLOAD_TAGS_NONE, 3);
+}
+
+static int transferProgressCallback(const git_transfer_progress *stats, void *payload)
+{
+ bool *invoked = (bool *)payload;
+
+ GIT_UNUSED(stats);
+ *invoked = true;
+ return 0;
+}
+
+void test_online_fetch__doesnt_retrieve_a_pack_when_the_repository_is_up_to_date(void)
+{
+ git_repository *_repository;
+ bool invoked = false;
+ git_remote *remote;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
+ opts.bare = true;
+
+ cl_git_pass(git_clone(&_repository, "https://github.com/libgit2/TestGitRepository.git",
+ "./fetch/lg2", &opts));
+ git_repository_free(_repository);
+
+ cl_git_pass(git_repository_open(&_repository, "./fetch/lg2"));
+
+ cl_git_pass(git_remote_load(&remote, _repository, "origin"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+
+ cl_assert_equal_i(false, invoked);
+
+ callbacks.transfer_progress = &transferProgressCallback;
+ callbacks.payload = &invoked;
+ git_remote_set_callbacks(remote, &callbacks);
+ cl_git_pass(git_remote_download(remote));
+
+ cl_assert_equal_i(false, invoked);
+
+ cl_git_pass(git_remote_update_tips(remote));
+ git_remote_disconnect(remote);
+
+ git_remote_free(remote);
+ git_repository_free(_repository);
+}
+
+static int cancel_at_half(const git_transfer_progress *stats, void *payload)
+{
+ GIT_UNUSED(payload);
+
+ if (stats->received_objects > (stats->total_objects/2))
+ return -1;
+ return 0;
+}
+
+void test_online_fetch__can_cancel(void)
+{
+ git_remote *remote;
+ size_t bytes_received = 0;
+ git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "http://github.com/libgit2/TestGitRepository.git"));
+
+ callbacks.transfer_progress = cancel_at_half;
+ callbacks.payload = &bytes_received;
+ git_remote_set_callbacks(remote, &callbacks);
+
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_fail_with(git_remote_download(remote), GIT_EUSER);
+ git_remote_disconnect(remote);
+ git_remote_free(remote);
+}
+
+void test_online_fetch__ls_disconnected(void)
+{
+ const git_remote_head **refs;
+ size_t refs_len_before, refs_len_after;
+ git_remote *remote;
+
+ cl_git_pass(git_remote_create(&remote, _repo, "test",
+ "http://github.com/libgit2/TestGitRepository.git"));
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_ls(&refs, &refs_len_before, remote));
+ git_remote_disconnect(remote);
+ cl_git_pass(git_remote_ls(&refs, &refs_len_after, remote));
+
+ cl_assert_equal_i(refs_len_before, refs_len_after);
+
+ git_remote_free(remote);
+}
diff --git a/tests/online/fetchhead.c b/tests/online/fetchhead.c
new file mode 100644
index 000000000..57b183f88
--- /dev/null
+++ b/tests/online/fetchhead.c
@@ -0,0 +1,91 @@
+#include "clar_libgit2.h"
+
+#include "fileops.h"
+#include "fetchhead.h"
+#include "../fetchhead/fetchhead_data.h"
+#include "git2/clone.h"
+
+#define LIVE_REPO_URL "git://github.com/libgit2/TestGitRepository"
+
+static git_repository *g_repo;
+static git_clone_options g_options;
+
+void test_online_fetchhead__initialize(void)
+{
+ git_remote_callbacks dummy_callbacks = GIT_REMOTE_CALLBACKS_INIT;
+ g_repo = NULL;
+
+ memset(&g_options, 0, sizeof(git_clone_options));
+ g_options.version = GIT_CLONE_OPTIONS_VERSION;
+ g_options.remote_callbacks = dummy_callbacks;
+}
+
+void test_online_fetchhead__cleanup(void)
+{
+ if (g_repo) {
+ git_repository_free(g_repo);
+ g_repo = NULL;
+ }
+
+ cl_fixture_cleanup("./foo");
+}
+
+static void fetchhead_test_clone(void)
+{
+ cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
+}
+
+static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
+{
+ git_remote *remote;
+ git_buf fetchhead_buf = GIT_BUF_INIT;
+ int equals = 0;
+
+ cl_git_pass(git_remote_load(&remote, g_repo, "origin"));
+ git_remote_set_autotag(remote, GIT_REMOTE_DOWNLOAD_TAGS_AUTO);
+
+ if(fetchspec != NULL) {
+ git_remote_clear_refspecs(remote);
+ git_remote_add_fetch(remote, fetchspec);
+ }
+
+ cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(remote));
+ cl_git_pass(git_remote_update_tips(remote));
+ git_remote_disconnect(remote);
+ git_remote_free(remote);
+
+ cl_git_pass(git_futils_readbuffer(&fetchhead_buf, "./foo/.git/FETCH_HEAD"));
+
+ equals = (strcmp(fetchhead_buf.ptr, expected_fetchhead) == 0);
+
+ git_buf_free(&fetchhead_buf);
+
+ cl_assert(equals);
+}
+
+void test_online_fetchhead__wildcard_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA);
+}
+
+void test_online_fetchhead__explicit_spec(void)
+{
+ fetchhead_test_clone();
+ fetchhead_test_fetch("refs/heads/first-merge:refs/remotes/origin/first-merge", FETCH_HEAD_EXPLICIT_DATA);
+}
+
+void test_online_fetchhead__no_merges(void)
+{
+ git_config *config;
+
+ fetchhead_test_clone();
+
+ cl_git_pass(git_repository_config(&config, g_repo));
+ cl_git_pass(git_config_delete_entry(config, "branch.master.remote"));
+ cl_git_pass(git_config_delete_entry(config, "branch.master.merge"));
+ git_config_free(config);
+
+ fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA);
+}
diff --git a/tests/online/push.c b/tests/online/push.c
new file mode 100644
index 000000000..aeb1ab47d
--- /dev/null
+++ b/tests/online/push.c
@@ -0,0 +1,770 @@
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "posix.h"
+#include "vector.h"
+#include "../submodule/submodule_helpers.h"
+#include "push_util.h"
+#include "refspec.h"
+#include "remote.h"
+
+static git_repository *_repo;
+
+static char *_remote_ssh_key;
+static char *_remote_ssh_pubkey;
+static char *_remote_ssh_passphrase;
+
+static char *_remote_url;
+static char *_remote_user;
+static char *_remote_pass;
+
+static int cred_acquire_cb(git_cred **, const char *, const char *, unsigned int, void *);
+
+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;
+
+static git_oid _tag_commit;
+static git_oid _tag_tree;
+static git_oid _tag_blob;
+static git_oid _tag_lightweight;
+static git_oid _tag_tag;
+
+static int cred_acquire_cb(
+ git_cred **cred,
+ const char *url,
+ const char *user_from_url,
+ unsigned int allowed_types,
+ void *payload)
+{
+ GIT_UNUSED(url);
+ GIT_UNUSED(user_from_url);
+ GIT_UNUSED(payload);
+
+ if (GIT_CREDTYPE_SSH_KEY & allowed_types) {
+ 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;
+ }
+ return git_cred_ssh_key_new(cred, _remote_user, _remote_ssh_pubkey, _remote_ssh_key, _remote_ssh_passphrase);
+ }
+
+ 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;
+ }
+
+ return git_cred_userpass_plaintext_new(cred, _remote_user, _remote_pass);
+ }
+
+ return -1;
+}
+
+/* the results of a push status. when used for expected values, msg may be NULL
+ * to indicate that it should not be matched. */
+typedef struct {
+ const char *ref;
+ int success;
+ const char *msg;
+} push_status;
+
+/**
+ * git_push_status_foreach callback that records status entries.
+ * @param data (git_vector *) of push_status instances
+ */
+static int record_push_status_cb(const char *ref, const char *msg, void *data)
+{
+ git_vector *statuses = (git_vector *)data;
+ push_status *s;
+
+ cl_assert(s = git__malloc(sizeof(*s)));
+ s->ref = ref;
+ s->success = (msg == NULL);
+ s->msg = msg;
+
+ git_vector_insert(statuses, s);
+
+ return 0;
+}
+
+static void do_verify_push_status(git_push *push, const push_status expected[], const size_t expected_len)
+{
+ git_vector actual = GIT_VECTOR_INIT;
+ push_status *iter;
+ bool failed = false;
+ size_t i;
+
+ git_push_status_foreach(push, record_push_status_cb, &actual);
+
+ if (expected_len != actual.length)
+ failed = true;
+ else
+ git_vector_foreach(&actual, i, iter)
+ if (strcmp(expected[i].ref, iter->ref) ||
+ (expected[i].success != iter->success) ||
+ (expected[i].msg && (!iter->msg || strcmp(expected[i].msg, iter->msg)))) {
+ 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,
+ expected[i].success ? "success" : "failed");
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+
+ git_vector_foreach(&actual, i, iter) {
+ 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);
+ }
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+ }
+
+ git_vector_foreach(&actual, i, iter)
+ git__free(iter);
+
+ git_vector_free(&actual);
+}
+
+/**
+ * Verifies that after git_push_finish(), refs on a remote have the expected
+ * names, oids, and order.
+ *
+ * @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)
+{
+ const git_remote_head **actual_refs;
+ size_t actual_refs_len;
+
+ git_remote_ls(&actual_refs, &actual_refs_len, remote);
+ verify_remote_refs(actual_refs, actual_refs_len, expected_refs, expected_refs_len);
+}
+
+/**
+ * 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)
+{
+ git_refspec *fetch_spec;
+ size_t i, j;
+ git_buf msg = GIT_BUF_INIT;
+ git_buf ref_name = GIT_BUF_INIT;
+ git_vector actual_refs = GIT_VECTOR_INIT;
+ git_branch_iterator *iter;
+ char *actual_ref;
+ git_oid oid;
+ int failed = 0, error;
+ git_branch_t branch_type;
+ git_reference *ref;
+
+ /* Get current remote branches */
+ 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))));
+ }
+
+ cl_assert_equal_i(error, GIT_ITEROVER);
+
+ /* Loop through expected refs, make sure they exist */
+ 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_r(&ref_name, fetch_spec, expected_refs[i].name));
+
+ /* Find matching remote branch */
+ git_vector_foreach(&actual_refs, j, actual_ref) {
+ if (!strcmp(git_buf_cstr(&ref_name), actual_ref))
+ 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 */
+ cl_git_pass(git_reference_name_to_id(&oid, remote->repo, actual_ref));
+
+ 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;
+ }
+
+ git__free(actual_ref);
+ 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:
+
+ if(failed)
+ 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);
+ git_buf_free(&ref_name);
+ return;
+}
+
+void test_online_push__initialize(void)
+{
+ git_vector delete_specs = GIT_VECTOR_INIT;
+ const git_remote_head **heads;
+ size_t i, heads_len;
+ char *curr_del_spec;
+
+ _repo = cl_git_sandbox_init("push_src");
+
+ 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");
+
+ git_oid_fromstr(&_tag_commit, "805c54522e614f29f70d2413a0470247d8b424ac");
+ git_oid_fromstr(&_tag_tree, "ff83aa4c5e5d28e3bcba2f5c6e2adc61286a4e5e");
+ git_oid_fromstr(&_tag_blob, "b483ae7ba66decee9aee971f501221dea84b1498");
+ git_oid_fromstr(&_tag_lightweight, "951bbbb90e2259a4c8950db78946784fb53fcbce");
+ git_oid_fromstr(&_tag_tag, "eea4f2705eeec2db3813f2430829afce99cd00b5");
+
+ /* 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");
+ _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");
+ _remote = NULL;
+
+ if (_remote_url) {
+ cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
+
+ record_callbacks_data_clear(&_record_cbs_data);
+ git_remote_set_callbacks(_remote, &_record_cbs);
+
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ /* 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) {
+ git_push *push;
+
+ cl_git_pass(git_push_new(&push, _remote));
+
+ git_vector_foreach(&delete_specs, i, curr_del_spec) {
+ git_push_add_refspec(push, curr_del_spec);
+ git__free(curr_del_spec);
+ }
+
+ cl_git_pass(git_push_finish(push));
+ git_push_free(push);
+ }
+
+ git_remote_disconnect(_remote);
+ git_vector_free(&delete_specs);
+
+ /* Now that we've deleted everything, fetch from the remote */
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_FETCH));
+ cl_git_pass(git_remote_download(_remote));
+ cl_git_pass(git_remote_update_tips(_remote));
+ git_remote_disconnect(_remote);
+ } else
+ printf("GITTEST_REMOTE_URL unset; skipping push test\n");
+}
+
+void test_online_push__cleanup(void)
+{
+ if (_remote)
+ git_remote_free(_remote);
+ _remote = NULL;
+
+ /* Freed by cl_git_sandbox_cleanup */
+ _repo = NULL;
+
+ record_callbacks_data_clear(&_record_cbs_data);
+
+ cl_fixture_cleanup("testrepo.git");
+ cl_git_sandbox_cleanup();
+}
+
+static int push_pack_progress_cb(int stage, unsigned int current, unsigned int total, void* payload)
+{
+ int *was_called = (int *) payload;
+ GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
+ *was_called = 1;
+ return 0;
+}
+
+static int push_transfer_progress_cb(unsigned int current, unsigned int total, size_t bytes, void* payload)
+{
+ int *was_called = (int *) payload;
+ GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
+ *was_called = 1;
+ return 0;
+}
+
+/**
+ * Calls push and relists refs on remote to verify success.
+ *
+ * @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()
+ * @param check_progress_cb Check that the push progress callbacks are called
+ */
+static void do_push(const char *refspecs[], size_t refspecs_len,
+ push_status expected_statuses[], size_t expected_statuses_len,
+ expected_ref expected_refs[], size_t expected_refs_len, int expected_ret, int check_progress_cb)
+{
+ git_push *push;
+ git_push_options opts = GIT_PUSH_OPTIONS_INIT;
+ size_t i;
+ int ret;
+ int pack_progress_called = 0, transfer_progress_called = 0;
+
+ if (_remote) {
+ /* Auto-detect the number of threads to use */
+ opts.pb_parallelism = 0;
+
+ cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+
+ cl_git_pass(git_push_new(&push, _remote));
+ cl_git_pass(git_push_set_options(push, &opts));
+
+ if (check_progress_cb)
+ cl_git_pass(git_push_set_callbacks(push, push_pack_progress_cb, &pack_progress_called, push_transfer_progress_cb, &transfer_progress_called));
+
+ for (i = 0; i < refspecs_len; i++)
+ cl_git_pass(git_push_add_refspec(push, refspecs[i]));
+
+ if (expected_ret < 0) {
+ cl_git_fail(ret = git_push_finish(push));
+ cl_assert_equal_i(0, git_push_unpack_ok(push));
+ }
+ else {
+ cl_git_pass(ret = git_push_finish(push));
+ cl_assert_equal_i(1, git_push_unpack_ok(push));
+ }
+
+ if (check_progress_cb) {
+ cl_assert_equal_i(1, pack_progress_called);
+ cl_assert_equal_i(1, transfer_progress_called);
+ }
+
+ do_verify_push_status(push, expected_statuses, expected_statuses_len);
+
+ cl_assert_equal_i(expected_ret, ret);
+
+ verify_refs(_remote, expected_refs, expected_refs_len);
+
+ cl_git_pass(git_push_update_tips(push));
+ verify_tracking_branches(_remote, expected_refs, expected_refs_len);
+
+ git_push_free(push);
+
+ git_remote_disconnect(_remote);
+ }
+}
+
+/* Call push_finish() without ever calling git_push_add_refspec() */
+void test_online_push__noop(void)
+{
+ do_push(NULL, 0, NULL, 0, NULL, 0, 0, 0);
+}
+
+void test_online_push__b1(void)
+{
+ const char *specs[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b1", &_oid_b1 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__b2(void)
+{
+ const char *specs[] = { "refs/heads/b2:refs/heads/b2" };
+ push_status exp_stats[] = { { "refs/heads/b2", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b2", &_oid_b2 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__b3(void)
+{
+ const char *specs[] = { "refs/heads/b3:refs/heads/b3" };
+ push_status exp_stats[] = { { "refs/heads/b3", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b3", &_oid_b3 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__b4(void)
+{
+ const char *specs[] = { "refs/heads/b4:refs/heads/b4" };
+ push_status exp_stats[] = { { "refs/heads/b4", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b4", &_oid_b4 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__b5(void)
+{
+ const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
+ push_status exp_stats[] = { { "refs/heads/b5", 1 } };
+ expected_ref exp_refs[] = { { "refs/heads/b5", &_oid_b5 } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__multi(void)
+{
+ 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[] = {
+ { "refs/heads/b1", 1 },
+ { "refs/heads/b2", 1 },
+ { "refs/heads/b3", 1 },
+ { "refs/heads/b4", 1 },
+ { "refs/heads/b5", 1 }
+ };
+ 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),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__implicit_tgt(void)
+{
+ const char *specs1[] = { "refs/heads/b1:" };
+ push_status exp_stats1[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs1[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs2[] = { "refs/heads/b2:" };
+ push_status exp_stats2[] = { { "refs/heads/b2", 1 } };
+ 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),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1);
+ do_push(specs2, ARRAY_SIZE(specs2),
+ exp_stats2, ARRAY_SIZE(exp_stats2),
+ exp_refs2, ARRAY_SIZE(exp_refs2), 0, 0);
+}
+
+void test_online_push__fast_fwd(void)
+{
+ /* Fast forward b1 in tgt from _oid_b1 to _oid_b6. */
+
+ const char *specs_init[] = { "refs/heads/b1:refs/heads/b1" };
+ push_status exp_stats_init[] = { { "refs/heads/b1", 1 } };
+ expected_ref exp_refs_init[] = { { "refs/heads/b1", &_oid_b1 } };
+
+ const char *specs_ff[] = { "refs/heads/b6:refs/heads/b1" };
+ push_status exp_stats_ff[] = { { "refs/heads/b1", 1 } };
+ 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),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 1);
+
+ do_push(specs_ff, ARRAY_SIZE(specs_ff),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0);
+
+ do_push(specs_reset, ARRAY_SIZE(specs_reset),
+ exp_stats_init, ARRAY_SIZE(exp_stats_init),
+ exp_refs_init, ARRAY_SIZE(exp_refs_init), 0, 0);
+
+ do_push(specs_ff_force, ARRAY_SIZE(specs_ff_force),
+ exp_stats_ff, ARRAY_SIZE(exp_stats_ff),
+ exp_refs_ff, ARRAY_SIZE(exp_refs_ff), 0, 0);
+}
+
+void test_online_push__tag_commit(void)
+{
+ const char *specs[] = { "refs/tags/tag-commit:refs/tags/tag-commit" };
+ push_status exp_stats[] = { { "refs/tags/tag-commit", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-commit", &_tag_commit } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__tag_tree(void)
+{
+ const char *specs[] = { "refs/tags/tag-tree:refs/tags/tag-tree" };
+ push_status exp_stats[] = { { "refs/tags/tag-tree", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-tree", &_tag_tree } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__tag_blob(void)
+{
+ const char *specs[] = { "refs/tags/tag-blob:refs/tags/tag-blob" };
+ push_status exp_stats[] = { { "refs/tags/tag-blob", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-blob", &_tag_blob } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__tag_lightweight(void)
+{
+ const char *specs[] = { "refs/tags/tag-lightweight:refs/tags/tag-lightweight" };
+ push_status exp_stats[] = { { "refs/tags/tag-lightweight", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-lightweight", &_tag_lightweight } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+}
+
+void test_online_push__tag_to_tag(void)
+{
+ const char *specs[] = { "refs/tags/tag-tag:refs/tags/tag-tag" };
+ push_status exp_stats[] = { { "refs/tags/tag-tag", 1 } };
+ expected_ref exp_refs[] = { { "refs/tags/tag-tag", &_tag_tag } };
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 0);
+}
+
+void test_online_push__force(void)
+{
+ const char *specs1[] = {"refs/heads/b3:refs/heads/tgt"};
+ push_status exp_stats1[] = { { "refs/heads/tgt", 1 } };
+ 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"};
+ push_status exp_stats2_force[] = { { "refs/heads/tgt", 1 } };
+ expected_ref exp_refs2_force[] = { { "refs/heads/tgt", &_oid_b4 } };
+
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1);
+
+ do_push(specs2, ARRAY_SIZE(specs2),
+ NULL, 0,
+ exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0);
+
+ /* Non-fast-forward update with force should pass. */
+ do_push(specs2_force, ARRAY_SIZE(specs2_force),
+ exp_stats2_force, ARRAY_SIZE(exp_stats2_force),
+ exp_refs2_force, ARRAY_SIZE(exp_refs2_force), 0, 1);
+}
+
+void test_online_push__delete(void)
+{
+ const char *specs1[] = {
+ "refs/heads/b1:refs/heads/tgt1",
+ "refs/heads/b1:refs/heads/tgt2"
+ };
+ push_status exp_stats1[] = {
+ { "refs/heads/tgt1", 1 },
+ { "refs/heads/tgt2", 1 }
+ };
+ 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" };
+ push_status exp_stats_fake[] = { { "refs/heads/fake", 1 } };
+
+ const char *specs_delete[] = { ":refs/heads/tgt1" };
+ push_status exp_stats_delete[] = { { "refs/heads/tgt1", 1 } };
+ 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),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 1);
+
+ /* 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.
+ */
+ do_push(specs_del_fake, ARRAY_SIZE(specs_del_fake),
+ exp_stats_fake, 1,
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0);
+ do_push(specs_del_fake_force, ARRAY_SIZE(specs_del_fake_force),
+ exp_stats_fake, 1,
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0);
+
+ /* Delete one of the pushed branches. */
+ do_push(specs_delete, ARRAY_SIZE(specs_delete),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0);
+
+ /* Re-push branches and retry delete with force. */
+ do_push(specs1, ARRAY_SIZE(specs1),
+ exp_stats1, ARRAY_SIZE(exp_stats1),
+ exp_refs1, ARRAY_SIZE(exp_refs1), 0, 0);
+ do_push(specs_delete_force, ARRAY_SIZE(specs_delete_force),
+ exp_stats_delete, ARRAY_SIZE(exp_stats_delete),
+ exp_refs_delete, ARRAY_SIZE(exp_refs_delete), 0, 0);
+}
+
+void test_online_push__bad_refspecs(void)
+{
+ /* All classes of refspecs that should be rejected by
+ * git_push_add_refspec() should go in this test.
+ */
+ git_push *push;
+
+ if (_remote) {
+// cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH));
+ cl_git_pass(git_push_new(&push, _remote));
+
+ /* Unexpanded branch names not supported */
+ cl_git_fail(git_push_add_refspec(push, "b6:b6"));
+
+ git_push_free(push);
+ }
+}
+
+void test_online_push__expressions(void)
+{
+ /* TODO: Expressions in refspecs doesn't actually work yet */
+ const char *specs_left_expr[] = { "refs/heads/b2~1:refs/heads/b2" };
+
+ /* expect not NULL to indicate failure (core git replies "funny refname",
+ * other servers may be less pithy. */
+ const char *specs_right_expr[] = { "refs/heads/b2:refs/heads/b2~1" };
+ push_status exp_stats_right_expr[] = { { "refs/heads/b2~1", 0 } };
+
+ /* 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,
+ NULL, 0, -1, 0);
+
+ do_push(specs_right_expr, ARRAY_SIZE(specs_right_expr),
+ exp_stats_right_expr, ARRAY_SIZE(exp_stats_right_expr),
+ NULL, 0, 0, 1);
+}
+
+void test_online_push__notes(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 } };
+ 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 */
+ cl_git_pass(git_note_create(&note_oid, _repo, signature, signature, NULL, target_oid, "hello world\n", 0));
+
+ do_push(specs, ARRAY_SIZE(specs),
+ exp_stats, ARRAY_SIZE(exp_stats),
+ exp_refs, ARRAY_SIZE(exp_refs), 0, 1);
+
+ git_signature_free(signature);
+}
diff --git a/tests/online/push_util.c b/tests/online/push_util.c
new file mode 100644
index 000000000..038c144db
--- /dev/null
+++ b/tests/online/push_util.c
@@ -0,0 +1,132 @@
+
+#include "clar_libgit2.h"
+#include "buffer.h"
+#include "vector.h"
+#include "push_util.h"
+
+const git_oid OID_ZERO = {{ 0 }};
+
+void updated_tip_free(updated_tip *t)
+{
+ git__free(t->name);
+ git__free(t->old_oid);
+ git__free(t->new_oid);
+ git__free(t);
+}
+
+void record_callbacks_data_clear(record_callbacks_data *data)
+{
+ size_t i;
+ updated_tip *tip;
+
+ git_vector_foreach(&data->updated_tips, i, tip)
+ updated_tip_free(tip);
+
+ git_vector_free(&data->updated_tips);
+}
+
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data)
+{
+ updated_tip *t;
+ record_callbacks_data *record_data = (record_callbacks_data *)data;
+
+ cl_assert(t = git__malloc(sizeof(*t)));
+
+ cl_assert(t->name = git__strdup(refname));
+ cl_assert(t->old_oid = git__malloc(sizeof(*t->old_oid)));
+ git_oid_cpy(t->old_oid, a);
+
+ cl_assert(t->new_oid = git__malloc(sizeof(*t->new_oid)));
+ git_oid_cpy(t->new_oid, b);
+
+ git_vector_insert(&record_data->updated_tips, t);
+
+ return 0;
+}
+
+int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len)
+{
+ git_buf del_spec = GIT_BUF_INIT;
+ size_t i;
+
+ for (i = 0; i < heads_len; i++) {
+ const git_remote_head *head = heads[i];
+ /* Ignore malformed ref names (which also saves us from tag^{} */
+ if (!git_reference_is_valid_name(head->name))
+ return 0;
+
+ /* Create a refspec that deletes a branch in the remote */
+ if (strcmp(head->name, "refs/heads/master")) {
+ cl_git_pass(git_buf_putc(&del_spec, ':'));
+ cl_git_pass(git_buf_puts(&del_spec, head->name));
+ cl_git_pass(git_vector_insert(out, git_buf_detach(&del_spec)));
+ }
+ }
+
+ return 0;
+}
+
+int record_ref_cb(git_remote_head *head, void *payload)
+{
+ git_vector *refs = (git_vector *) payload;
+ return git_vector_insert(refs, head);
+}
+
+void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len)
+{
+ size_t i, j = 0;
+ git_buf msg = GIT_BUF_INIT;
+ const git_remote_head *actual;
+ char *oid_str;
+ bool master_present = false;
+
+ /* We don't care whether "master" is present on the other end or not */
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (!strcmp(actual->name, "refs/heads/master")) {
+ master_present = true;
+ break;
+ }
+ }
+
+ if (expected_refs_len + (master_present ? 1 : 0) != actual_refs_len)
+ goto failed;
+
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ if (strcmp(expected_refs[j].name, actual->name) ||
+ git_oid_cmp(expected_refs[j].oid, &actual->oid))
+ goto failed;
+
+ j++;
+ }
+
+ return;
+
+failed:
+ git_buf_puts(&msg, "Expected and actual refs differ:\nEXPECTED:\n");
+
+ for(i = 0; i < expected_refs_len; i++) {
+ cl_assert(oid_str = git_oid_allocfmt(expected_refs[i].oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", expected_refs[i].name, oid_str));
+ git__free(oid_str);
+ }
+
+ git_buf_puts(&msg, "\nACTUAL:\n");
+ for (i = 0; i < actual_refs_len; i++) {
+ actual = actual_refs[i];
+ if (master_present && !strcmp(actual->name, "refs/heads/master"))
+ continue;
+
+ cl_assert(oid_str = git_oid_allocfmt(&actual->oid));
+ cl_git_pass(git_buf_printf(&msg, "%s = %s\n", actual->name, oid_str));
+ git__free(oid_str);
+ }
+
+ cl_fail(git_buf_cstr(&msg));
+
+ git_buf_free(&msg);
+}
diff --git a/tests/online/push_util.h b/tests/online/push_util.h
new file mode 100644
index 000000000..a7207c49e
--- /dev/null
+++ b/tests/online/push_util.h
@@ -0,0 +1,71 @@
+#ifndef INCLUDE_cl_push_util_h__
+#define INCLUDE_cl_push_util_h__
+
+#include "git2/oid.h"
+
+/* Constant for zero oid */
+extern const git_oid OID_ZERO;
+
+/**
+ * Macro for initializing git_remote_callbacks to use test helpers that
+ * record data in a record_callbacks_data instance.
+ * @param data pointer to a record_callbacks_data instance
+ */
+#define RECORD_CALLBACKS_INIT(data) \
+ { GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, record_update_tips_cb, data }
+
+typedef struct {
+ char *name;
+ git_oid *old_oid;
+ git_oid *new_oid;
+} updated_tip;
+
+typedef struct {
+ git_vector updated_tips;
+} record_callbacks_data;
+
+typedef struct {
+ const char *name;
+ const git_oid *oid;
+} expected_ref;
+
+void updated_tip_free(updated_tip *t);
+
+void record_callbacks_data_clear(record_callbacks_data *data);
+
+/**
+ * Callback for git_remote_update_tips that records updates
+ *
+ * @param data (git_vector *) of updated_tip instances
+ */
+int record_update_tips_cb(const char *refname, const git_oid *a, const git_oid *b, void *data);
+
+/**
+ * Create a set of refspecs that deletes each of the inputs
+ *
+ * @param out the vector in which to store the refspecs
+ * @param heads the remote heads
+ * @param heads_len the size of the array
+ */
+int create_deletion_refspecs(git_vector *out, const git_remote_head **heads, size_t heads_len);
+
+/**
+ * Callback for git_remote_list that adds refspecs to vector
+ *
+ * @param head a ref on the remote
+ * @param payload (git_vector *) of git_remote_head instances
+ */
+int record_ref_cb(git_remote_head *head, void *payload);
+
+/**
+ * Verifies that refs on remote stored by record_ref_cb match the expected
+ * names, oids, and order.
+ *
+ * @param actual_refs actual refs in the remote
+ * @param actual_refs_len length of actual_refs
+ * @param expected_refs expected remote refs
+ * @param expected_refs_len length of expected_refs
+ */
+void verify_remote_refs(const git_remote_head *actual_refs[], size_t actual_refs_len, const expected_ref expected_refs[], size_t expected_refs_len);
+
+#endif /* INCLUDE_cl_push_util_h__ */