Initial commit

This commit is contained in:
jiapengyu
2026-07-27 13:37:48 +08:00
commit ecba71e2a5
39123 changed files with 5989154 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
#include "clar_libgit2.h"
#include "git2/clone.h"
static git_repository *g_repo;
#ifdef GIT_HTTPS
static bool g_has_ssl = true;
#else
static bool g_has_ssl = false;
#endif
static int cert_check_assert_invalid(git_cert *cert, int valid, const char* host, void *payload)
{
GIT_UNUSED(cert); GIT_UNUSED(host); GIT_UNUSED(payload);
cl_assert_equal_i(0, valid);
return GIT_ECERTIFICATE;
}
void test_online_badssl__expired(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid;
if (!g_has_ssl)
cl_skip();
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", NULL));
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://expired.badssl.com/fake.git", "./fake", &opts));
}
void test_online_badssl__wrong_host(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid;
if (!g_has_ssl)
cl_skip();
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", NULL));
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://wrong.host.badssl.com/fake.git", "./fake", &opts));
}
void test_online_badssl__self_signed(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid;
if (!g_has_ssl)
cl_skip();
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", NULL));
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://self-signed.badssl.com/fake.git", "./fake", &opts));
}
void test_online_badssl__old_cipher(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
opts.fetch_opts.callbacks.certificate_check = cert_check_assert_invalid;
/* FIXME: we don't actually reject RC4 anywhere, figure out what to tweak */
cl_skip();
if (!g_has_ssl)
cl_skip();
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", NULL));
cl_git_fail_with(GIT_ECERTIFICATE,
git_clone(&g_repo, "https://rc4.badssl.com/fake.git", "./fake", &opts));
}
+877
View File
@@ -0,0 +1,877 @@
#include "clar_libgit2.h"
#include "git2/clone.h"
#include "git2/cred_helpers.h"
#include "remote.h"
#include "futils.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://libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_PASS "https://libgit3:libgit3@bitbucket.org/libgit2/testgitrepository.git"
#define BB_REPO_URL_WITH_WRONG_PASS "https://libgit3:wrong@bitbucket.org/libgit2/testgitrepository.git"
#define SSH_REPO_URL "ssh://github.com/libgit2/TestGitRepository"
static git_repository *g_repo;
static git_clone_options g_options;
static char *_remote_url = NULL;
static char *_remote_user = NULL;
static char *_remote_pass = NULL;
static char *_remote_sslnoverify = NULL;
static char *_remote_ssh_pubkey = NULL;
static char *_remote_ssh_privkey = NULL;
static char *_remote_ssh_passphrase = NULL;
static char *_remote_ssh_fingerprint = NULL;
static char *_remote_proxy_scheme = NULL;
static char *_remote_proxy_host = NULL;
static char *_remote_proxy_user = NULL;
static char *_remote_proxy_pass = NULL;
static char *_remote_proxy_selfsigned = NULL;
static int _orig_proxies_need_reset = 0;
static char *_orig_http_proxy = NULL;
static char *_orig_https_proxy = NULL;
static int ssl_cert(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(host);
GIT_UNUSED(payload);
if (_remote_sslnoverify != NULL)
valid = 1;
return valid ? 0 : GIT_ECERTIFICATE;
}
void test_online_clone__initialize(void)
{
git_checkout_options dummy_opts = GIT_CHECKOUT_OPTIONS_INIT;
git_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_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.fetch_opts = dummy_fetch;
g_options.fetch_opts.callbacks.certificate_check = ssl_cert;
_remote_url = cl_getenv("GITTEST_REMOTE_URL");
_remote_user = cl_getenv("GITTEST_REMOTE_USER");
_remote_pass = cl_getenv("GITTEST_REMOTE_PASS");
_remote_sslnoverify = cl_getenv("GITTEST_REMOTE_SSL_NOVERIFY");
_remote_ssh_pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
_remote_ssh_privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY");
_remote_ssh_passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
_remote_ssh_fingerprint = cl_getenv("GITTEST_REMOTE_SSH_FINGERPRINT");
_remote_proxy_scheme = cl_getenv("GITTEST_REMOTE_PROXY_SCHEME");
_remote_proxy_host = cl_getenv("GITTEST_REMOTE_PROXY_HOST");
_remote_proxy_user = cl_getenv("GITTEST_REMOTE_PROXY_USER");
_remote_proxy_pass = cl_getenv("GITTEST_REMOTE_PROXY_PASS");
_remote_proxy_selfsigned = cl_getenv("GITTEST_REMOTE_PROXY_SELFSIGNED");
_orig_proxies_need_reset = 0;
}
void test_online_clone__cleanup(void)
{
if (g_repo) {
git_repository_free(g_repo);
g_repo = NULL;
}
cl_fixture_cleanup("./foo");
git__free(_remote_url);
git__free(_remote_user);
git__free(_remote_pass);
git__free(_remote_sslnoverify);
git__free(_remote_ssh_pubkey);
git__free(_remote_ssh_privkey);
git__free(_remote_ssh_passphrase);
git__free(_remote_ssh_fingerprint);
git__free(_remote_proxy_scheme);
git__free(_remote_proxy_host);
git__free(_remote_proxy_user);
git__free(_remote_proxy_pass);
git__free(_remote_proxy_selfsigned);
if (_orig_proxies_need_reset) {
cl_setenv("HTTP_PROXY", _orig_http_proxy);
cl_setenv("HTTPS_PROXY", _orig_https_proxy);
git__free(_orig_http_proxy);
git__free(_orig_https_proxy);
}
}
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_lookup(&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_lookup(&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_REFERENCE_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_indexer_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;
g_options.checkout_opts.progress_cb = &checkout_progress;
g_options.checkout_opts.progress_payload = &checkout_progress_cb_was_called;
g_options.fetch_opts.callbacks.transfer_progress = &fetch_progress;
g_options.fetch_opts.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_REFERENCE_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_dispose(&path);
}
static int remote_mirror_cb(git_remote **out, git_repository *repo,
const char *name, const char *url, void *payload)
{
int error;
git_remote *remote;
GIT_UNUSED(payload);
if ((error = git_remote_create_with_fetchspec(&remote, repo, name, url, "+refs/*:refs/*")) < 0)
return error;
*out = remote;
return 0;
}
void test_online_clone__clone_mirror(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
git_reference *head;
bool fetch_progress_cb_was_called = false;
opts.fetch_opts.callbacks.transfer_progress = &fetch_progress;
opts.fetch_opts.callbacks.payload = &fetch_progress_cb_was_called;
opts.bare = true;
opts.remote_cb = remote_mirror_cb;
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo.git", &opts));
cl_git_pass(git_reference_lookup(&head, g_repo, "HEAD"));
cl_assert_equal_i(GIT_REFERENCE_SYMBOLIC, git_reference_type(head));
cl_assert_equal_s("refs/heads/master", git_reference_symbolic_target(head));
cl_assert_equal_i(true, fetch_progress_cb_was_called);
git_reference_free(head);
git_repository_free(g_repo);
g_repo = NULL;
cl_fixture_cleanup("./foo.git");
}
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.fetch_opts.callbacks.update_tips = update_tips;
g_options.fetch_opts.callbacks.payload = &callcount;
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
cl_assert(callcount > 0);
}
void test_online_clone__custom_headers(void)
{
char *empty_header = "";
char *unnamed_header = "this is a header about nothing";
char *newlines = "X-Custom: almost OK\n";
char *conflict = "Accept: defined-by-git";
char *ok = "X-Custom: this should be ok";
g_options.fetch_opts.custom_headers.count = 1;
g_options.fetch_opts.custom_headers.strings = &empty_header;
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
g_options.fetch_opts.custom_headers.strings = &unnamed_header;
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
g_options.fetch_opts.custom_headers.strings = &newlines;
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
g_options.fetch_opts.custom_headers.strings = &conflict;
cl_git_fail(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
/* Finally, we got it right! */
g_options.fetch_opts.custom_headers.strings = &ok;
cl_git_pass(git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options));
}
static int cred_failure_cb(
git_cred **cred,
const char *url,
const char *username_from_url,
unsigned int allowed_types,
void *data)
{
GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url);
GIT_UNUSED(allowed_types); GIT_UNUSED(data);
return -172;
}
void test_online_clone__cred_callback_failure_return_code_is_tunnelled(void)
{
git__free(_remote_url);
git__free(_remote_user);
_remote_url = git__strdup("https://github.com/libgit2/non-existent");
_remote_user = git__strdup("libgit2test");
g_options.fetch_opts.callbacks.credentials = cred_failure_cb;
cl_git_fail_with(-172, git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
static int cred_count_calls_cb(git_cred **cred, const char *url, const char *user,
unsigned int allowed_types, void *data)
{
size_t *counter = (size_t *) data;
GIT_UNUSED(url); GIT_UNUSED(user); GIT_UNUSED(allowed_types);
if (allowed_types == GIT_CREDTYPE_USERNAME)
return git_cred_username_new(cred, "foo");
(*counter)++;
if (*counter == 3)
return GIT_EUSER;
return git_cred_userpass_plaintext_new(cred, "foo", "bar");
}
void test_online_clone__cred_callback_called_again_on_auth_failure(void)
{
size_t counter = 0;
git__free(_remote_url);
git__free(_remote_user);
_remote_url = git__strdup("https://gitlab.com/libgit2/non-existent");
_remote_user = git__strdup("libgit2test");
g_options.fetch_opts.callbacks.credentials = cred_count_calls_cb;
g_options.fetch_opts.callbacks.payload = &counter;
cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options));
cl_assert_equal_i(3, counter);
}
int cred_default(
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 (!(allowed_types & GIT_CREDTYPE_DEFAULT))
return 0;
return git_cred_default_new(cred);
}
void test_online_clone__credentials(void)
{
/* Remote URL environment variable must be set.
* User and password are optional.
*/
git_cred_userpass_payload user_pass = {
_remote_user,
_remote_pass
};
if (!_remote_url)
clar__skip();
if (cl_is_env_set("GITTEST_REMOTE_DEFAULT")) {
g_options.fetch_opts.callbacks.credentials = cred_default;
} else {
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.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 = {
"libgit3", "libgit3"
};
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.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");
}
void test_online_clone__bitbucket_uses_creds_in_url(void)
{
git_cred_userpass_payload user_pass = {
"libgit2", "wrong"
};
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.callbacks.payload = &user_pass;
/*
* Correct user and pass are in the URL; the (incorrect) creds in
* the `git_cred_userpass_payload` should be ignored.
*/
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");
}
void test_online_clone__bitbucket_falls_back_to_specified_creds(void)
{
git_cred_userpass_payload user_pass = {
"libgit2", "libgit2"
};
g_options.fetch_opts.callbacks.credentials = git_cred_userpass;
g_options.fetch_opts.callbacks.payload = &user_pass;
/*
* TODO: as of March 2018, bitbucket sporadically fails with
* 403s instead of replying with a 401 - but only sometimes.
*/
cl_skip();
/*
* Incorrect user and pass are in the URL; the (correct) creds in
* the `git_cred_userpass_payload` should be used as a fallback.
*/
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");
}
static int cancel_at_half(const git_indexer_progress *stats, void *payload)
{
GIT_UNUSED(payload);
if (stats->received_objects > (stats->total_objects/2))
return 4321;
return 0;
}
void test_online_clone__can_cancel(void)
{
g_options.fetch_opts.callbacks.transfer_progress = cancel_at_half;
cl_git_fail_with(
git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), 4321);
}
static int cred_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 (allowed_types & GIT_CREDTYPE_USERNAME)
return git_cred_username_new(cred, _remote_user);
if (allowed_types & GIT_CREDTYPE_SSH_KEY)
return git_cred_ssh_key_new(cred,
_remote_user, _remote_ssh_pubkey,
_remote_ssh_privkey, _remote_ssh_passphrase);
git_error_set(GIT_ERROR_NET, "unexpected cred type");
return -1;
}
static int check_ssh_auth_methods(git_cred **cred, const char *url, const char *username_from_url,
unsigned int allowed_types, void *data)
{
int *with_user = (int *) data;
GIT_UNUSED(cred); GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(data);
if (!*with_user)
cl_assert_equal_i(GIT_CREDTYPE_USERNAME, allowed_types);
else
cl_assert(!(allowed_types & GIT_CREDTYPE_USERNAME));
return GIT_EUSER;
}
void test_online_clone__ssh_auth_methods(void)
{
int with_user;
#ifndef GIT_SSH
clar__skip();
#endif
g_options.fetch_opts.callbacks.credentials = check_ssh_auth_methods;
g_options.fetch_opts.callbacks.payload = &with_user;
g_options.fetch_opts.callbacks.certificate_check = NULL;
with_user = 0;
cl_git_fail_with(GIT_EUSER,
git_clone(&g_repo, SSH_REPO_URL, "./foo", &g_options));
with_user = 1;
cl_git_fail_with(GIT_EUSER,
git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
static int custom_remote_ssh_with_paths(
git_remote **out,
git_repository *repo,
const char *name,
const char *url,
void *payload)
{
int error;
GIT_UNUSED(payload);
if ((error = git_remote_create(out, repo, name, url)) < 0)
return error;
return 0;
}
void test_online_clone__ssh_with_paths(void)
{
char *bad_paths[] = {
"/bin/yes",
"/bin/false",
};
char *good_paths[] = {
"/usr/bin/git-upload-pack",
"/usr/bin/git-receive-pack",
};
git_strarray arr = {
bad_paths,
2,
};
#ifndef GIT_SSH
clar__skip();
#endif
if (!_remote_url || !_remote_user || strncmp(_remote_url, "ssh://", 5) != 0)
clar__skip();
g_options.remote_cb = custom_remote_ssh_with_paths;
g_options.fetch_opts.callbacks.transport = git_transport_ssh_with_paths;
g_options.fetch_opts.callbacks.credentials = cred_cb;
g_options.fetch_opts.callbacks.payload = &arr;
g_options.fetch_opts.callbacks.certificate_check = NULL;
cl_git_fail(git_clone(&g_repo, _remote_url, "./foo", &g_options));
arr.strings = good_paths;
cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
static int cred_foo_bar(git_cred **cred, const char *url, const char *username_from_url,
unsigned int allowed_types, void *data)
{
GIT_UNUSED(url); GIT_UNUSED(username_from_url); GIT_UNUSED(allowed_types); GIT_UNUSED(data);
return git_cred_userpass_plaintext_new(cred, "foo", "bar");
}
void test_online_clone__ssh_cannot_change_username(void)
{
#ifndef GIT_SSH
clar__skip();
#endif
g_options.fetch_opts.callbacks.credentials = cred_foo_bar;
cl_git_fail(git_clone(&g_repo, "ssh://git@github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
int ssh_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
git_cert_hostkey *key;
git_oid expected = {{0}}, actual = {{0}};
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert(_remote_ssh_fingerprint);
cl_git_pass(git_oid_fromstrp(&expected, _remote_ssh_fingerprint));
cl_assert_equal_i(GIT_CERT_HOSTKEY_LIBSSH2, cert->cert_type);
key = (git_cert_hostkey *) cert;
/*
* We need to figure out how long our input was to check for
* the type. Here we abuse the fact that both hashes fit into
* our git_oid type.
*/
if (strlen(_remote_ssh_fingerprint) == 32 && key->type & GIT_CERT_SSH_MD5) {
memcpy(&actual.id, key->hash_md5, 16);
} else if (strlen(_remote_ssh_fingerprint) == 40 && key->type & GIT_CERT_SSH_SHA1) {
memcpy(&actual, key->hash_sha1, 20);
} else {
cl_fail("Cannot find a usable SSH hash");
}
cl_assert(!memcmp(&expected, &actual, 20));
cl_assert_equal_s("localhost", host);
return GIT_EUSER;
}
void test_online_clone__ssh_cert(void)
{
g_options.fetch_opts.callbacks.certificate_check = ssh_certificate_check;
if (!_remote_ssh_fingerprint)
cl_skip();
cl_git_fail_with(GIT_EUSER, git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
static char *read_key_file(const char *path)
{
FILE *f;
char *buf;
long key_length;
if (!path || !*path)
return NULL;
cl_assert((f = fopen(path, "r")) != NULL);
cl_assert(fseek(f, 0, SEEK_END) != -1);
cl_assert((key_length = ftell(f)) != -1);
cl_assert(fseek(f, 0, SEEK_SET) != -1);
cl_assert((buf = malloc(key_length)) != NULL);
cl_assert(fread(buf, key_length, 1, f) == 1);
fclose(f);
return buf;
}
static int ssh_memory_cred_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 (allowed_types & GIT_CREDTYPE_USERNAME)
return git_cred_username_new(cred, _remote_user);
if (allowed_types & GIT_CREDTYPE_SSH_KEY)
{
char *pubkey = read_key_file(_remote_ssh_pubkey);
char *privkey = read_key_file(_remote_ssh_privkey);
int ret = git_cred_ssh_key_memory_new(cred, _remote_user, pubkey, privkey, _remote_ssh_passphrase);
if (privkey)
free(privkey);
if (pubkey)
free(pubkey);
return ret;
}
git_error_set(GIT_ERROR_NET, "unexpected cred type");
return -1;
}
void test_online_clone__ssh_memory_auth(void)
{
#ifndef GIT_SSH_MEMORY_CREDENTIALS
clar__skip();
#endif
if (!_remote_url || !_remote_user || !_remote_ssh_privkey || strncmp(_remote_url, "ssh://", 5) != 0)
clar__skip();
g_options.fetch_opts.callbacks.credentials = ssh_memory_cred_cb;
cl_git_pass(git_clone(&g_repo, _remote_url, "./foo", &g_options));
}
static int fail_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(host);
GIT_UNUSED(payload);
return GIT_ECERTIFICATE;
}
void test_online_clone__certificate_invalid(void)
{
g_options.fetch_opts.callbacks.certificate_check = fail_certificate_check;
cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
#ifdef GIT_SSH
cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
GIT_ECERTIFICATE);
#endif
}
static int succeed_certificate_check(git_cert *cert, int valid, const char *host, void *payload)
{
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert_equal_s("github.com", host);
return 0;
}
void test_online_clone__certificate_valid(void)
{
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
void test_online_clone__start_with_http(void)
{
g_options.fetch_opts.callbacks.certificate_check = succeed_certificate_check;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
static int called_proxy_creds;
static int proxy_cred_cb(git_cred **out, const char *url, const char *username, unsigned int allowed, void *payload)
{
GIT_UNUSED(url);
GIT_UNUSED(username);
GIT_UNUSED(allowed);
GIT_UNUSED(payload);
called_proxy_creds = 1;
return git_cred_userpass_plaintext_new(out, _remote_proxy_user, _remote_proxy_pass);
}
static int proxy_cert_cb(git_cert *cert, int valid, const char *host, void *payload)
{
char *colon;
size_t host_len;
GIT_UNUSED(cert);
GIT_UNUSED(valid);
GIT_UNUSED(payload);
cl_assert(_remote_proxy_host);
if ((colon = strchr(_remote_proxy_host, ':')) != NULL)
host_len = (colon - _remote_proxy_host);
else
host_len = strlen(_remote_proxy_host);
if (_remote_proxy_selfsigned != NULL &&
strlen(host) == host_len &&
strncmp(_remote_proxy_host, host, host_len) == 0)
valid = 1;
return valid ? 0 : GIT_ECERTIFICATE;
}
void test_online_clone__proxy_credentials_request(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
cl_git_pass(git_buf_printf(&url, "%s://%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_host));
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
g_options.fetch_opts.proxy_opts.url = url.ptr;
g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
called_proxy_creds = 0;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
cl_assert(called_proxy_creds);
git_buf_dispose(&url);
}
void test_online_clone__proxy_credentials_in_url(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
cl_git_pass(git_buf_printf(&url, "%s://%s:%s@%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
g_options.fetch_opts.proxy_opts.url = url.ptr;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
called_proxy_creds = 0;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
cl_assert(called_proxy_creds == 0);
git_buf_dispose(&url);
}
void test_online_clone__proxy_credentials_in_environment(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
_orig_http_proxy = cl_getenv("HTTP_PROXY");
_orig_https_proxy = cl_getenv("HTTPS_PROXY");
_orig_proxies_need_reset = 1;
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
cl_git_pass(git_buf_printf(&url, "%s://%s:%s@%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
cl_setenv("HTTP_PROXY", url.ptr);
cl_setenv("HTTPS_PROXY", url.ptr);
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
git_buf_dispose(&url);
}
void test_online_clone__proxy_auto_not_detected(void)
{
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
}
void test_online_clone__proxy_cred_callback_after_failed_url_creds(void)
{
git_buf url = GIT_BUF_INIT;
if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
cl_skip();
cl_git_pass(git_buf_printf(&url, "%s://invalid_user_name:INVALID_pass_WORD@%s/",
_remote_proxy_scheme ? _remote_proxy_scheme : "http",
_remote_proxy_host));
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
g_options.fetch_opts.proxy_opts.url = url.ptr;
g_options.fetch_opts.proxy_opts.credentials = proxy_cred_cb;
g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
called_proxy_creds = 0;
cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
cl_assert(called_proxy_creds);
git_buf_dispose(&url);
}
void test_online_clone__azurerepos(void)
{
cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/test", "./foo", &g_options));
cl_assert(git_path_exists("./foo/master.txt"));
}
void test_online_clone__path_whitespace(void)
{
cl_git_pass(git_clone(&g_repo, "https://libgit2@dev.azure.com/libgit2/test/_git/spaces%20in%20the%20name", "./foo", &g_options));
cl_assert(git_path_exists("./foo/master.txt"));
}
+209
View File
@@ -0,0 +1,209 @@
#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_indexer_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_fetch_options options = GIT_FETCH_OPTIONS_INIT;
size_t bytes_received = 0;
options.callbacks.transfer_progress = progress;
options.callbacks.update_tips = update_tips;
options.callbacks.payload = &bytes_received;
options.download_tags = flag;
counter = 0;
cl_git_pass(git_remote_create(&remote, _repo, "test", url));
cl_git_pass(git_remote_fetch(remote, NULL, &options, NULL));
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);
}
void test_online_fetch__fetch_twice(void)
{
git_remote *remote;
cl_git_pass(git_remote_create(&remote, _repo, "test", "git://github.com/libgit2/TestGitRepository.git"));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_pass(git_remote_download(remote, NULL, NULL));
git_remote_disconnect(remote);
git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL);
cl_git_pass(git_remote_download(remote, NULL, NULL));
git_remote_disconnect(remote);
git_remote_free(remote);
}
static int transferProgressCallback(const git_indexer_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_fetch_options options = GIT_FETCH_OPTIONS_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_lookup(&remote, _repository, "origin"));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_assert_equal_i(false, invoked);
options.callbacks.transfer_progress = &transferProgressCallback;
options.callbacks.payload = &invoked;
cl_git_pass(git_remote_download(remote, NULL, &options));
cl_assert_equal_i(false, invoked);
cl_git_pass(git_remote_update_tips(remote, &options.callbacks, 1, options.download_tags, NULL));
git_remote_disconnect(remote);
git_remote_free(remote);
git_repository_free(_repository);
}
static int cancel_at_half(const git_indexer_progress *stats, void *payload)
{
GIT_UNUSED(payload);
if (stats->received_objects > (stats->total_objects/2))
return -4321;
return 0;
}
void test_online_fetch__can_cancel(void)
{
git_remote *remote;
size_t bytes_received = 0;
git_fetch_options options = GIT_FETCH_OPTIONS_INIT;
cl_git_pass(git_remote_create(&remote, _repo, "test",
"http://github.com/libgit2/TestGitRepository.git"));
options.callbacks.transfer_progress = cancel_at_half;
options.callbacks.payload = &bytes_received;
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_fail_with(git_remote_download(remote, NULL, &options), -4321);
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, NULL, NULL, NULL));
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);
}
void test_online_fetch__remote_symrefs(void)
{
const git_remote_head **refs;
size_t refs_len;
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, NULL, NULL, NULL));
git_remote_disconnect(remote);
cl_git_pass(git_remote_ls(&refs, &refs_len, remote));
cl_assert_equal_s("HEAD", refs[0]->name);
cl_assert_equal_s("refs/heads/master", refs[0]->symref_target);
git_remote_free(remote);
}
void test_online_fetch__twice(void)
{
git_remote *remote;
cl_git_pass(git_remote_create(&remote, _repo, "test", "http://github.com/libgit2/TestGitRepository.git"));
cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL));
cl_git_pass(git_remote_fetch(remote, NULL, NULL, NULL));
git_remote_free(remote);
}
+156
View File
@@ -0,0 +1,156 @@
#include "clar_libgit2.h"
#include "futils.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_fetch_options dummy_fetch = GIT_FETCH_OPTIONS_INIT;
g_repo = NULL;
memset(&g_options, 0, sizeof(git_clone_options));
g_options.version = GIT_CLONE_OPTIONS_VERSION;
g_options.fetch_opts = dummy_fetch;
}
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 size_t count_references(void)
{
git_strarray array;
size_t refs;
cl_git_pass(git_reference_list(&array, g_repo));
refs = array.count;
git_strarray_free(&array);
return refs;
}
static void fetchhead_test_fetch(const char *fetchspec, const char *expected_fetchhead)
{
git_remote *remote;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
git_buf fetchhead_buf = GIT_BUF_INIT;
int equals = 0;
git_strarray array, *active_refs = NULL;
cl_git_pass(git_remote_lookup(&remote, g_repo, "origin"));
fetch_opts.download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
if(fetchspec != NULL) {
array.count = 1;
array.strings = (char **) &fetchspec;
active_refs = &array;
}
cl_git_pass(git_remote_fetch(remote, active_refs, &fetch_opts, NULL));
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_dispose(&fetchhead_buf);
cl_assert(equals);
}
void test_online_fetchhead__wildcard_spec(void)
{
fetchhead_test_clone();
fetchhead_test_fetch(NULL, FETCH_HEAD_WILDCARD_DATA2);
cl_git_pass(git_tag_delete(g_repo, "annotated_tag"));
cl_git_pass(git_tag_delete(g_repo, "blob"));
cl_git_pass(git_tag_delete(g_repo, "commit_tree"));
cl_git_pass(git_tag_delete(g_repo, "nearly-dangling"));
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_DATA2);
cl_git_pass(git_tag_delete(g_repo, "annotated_tag"));
cl_git_pass(git_tag_delete(g_repo, "blob"));
cl_git_pass(git_tag_delete(g_repo, "commit_tree"));
cl_git_pass(git_tag_delete(g_repo, "nearly-dangling"));
fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA);
cl_git_pass(git_tag_delete(g_repo, "commit_tree"));
fetchhead_test_fetch(NULL, FETCH_HEAD_NO_MERGE_DATA3);
}
void test_online_fetchhead__explicit_dst_refspec_creates_branch(void)
{
git_reference *ref;
size_t refs;
fetchhead_test_clone();
refs = count_references();
fetchhead_test_fetch("refs/heads/first-merge:refs/heads/explicit-refspec", FETCH_HEAD_EXPLICIT_DATA);
cl_git_pass(git_branch_lookup(&ref, g_repo, "explicit-refspec", GIT_BRANCH_ALL));
cl_assert_equal_i(refs + 1, count_references());
git_reference_free(ref);
}
void test_online_fetchhead__empty_dst_refspec_creates_no_branch(void)
{
git_reference *ref;
size_t refs;
fetchhead_test_clone();
refs = count_references();
fetchhead_test_fetch("refs/heads/first-merge", FETCH_HEAD_EXPLICIT_DATA);
cl_git_fail(git_branch_lookup(&ref, g_repo, "first-merge", GIT_BRANCH_ALL));
cl_assert_equal_i(refs, count_references());
}
void test_online_fetchhead__colon_only_dst_refspec_creates_no_branch(void)
{
size_t refs;
fetchhead_test_clone();
refs = count_references();
fetchhead_test_fetch("refs/heads/first-merge:", FETCH_HEAD_EXPLICIT_DATA);
cl_assert_equal_i(refs, count_references());
}
+910
View File
@@ -0,0 +1,910 @@
#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_url = NULL;
static char *_remote_user = NULL;
static char *_remote_pass = NULL;
static char *_remote_ssh_key = NULL;
static char *_remote_ssh_pubkey = NULL;
static char *_remote_ssh_passphrase = NULL;
static char *_remote_default = NULL;
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_USERNAME & allowed_types) {
if (!_remote_user) {
printf("GITTEST_REMOTE_USER must be set\n");
return -1;
}
return git_cred_username_new(cred, _remote_user);
}
if (GIT_CREDTYPE_DEFAULT & allowed_types) {
if (!_remote_default) {
printf("GITTEST_REMOTE_DEFAULT must be set to use NTLM/Negotiate credentials\n");
return -1;
}
return git_cred_default_new(cred);
}
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;
}
/**
* git_push_status_foreach callback that records status entries.
*/
static int record_push_status_cb(const char *ref, const char *msg, void *payload)
{
record_callbacks_data *data = (record_callbacks_data *) payload;
push_status *s;
cl_assert(s = git__calloc(1, sizeof(*s)));
if (ref)
cl_assert(s->ref = git__strdup(ref));
s->success = (msg == NULL);
if (msg)
cl_assert(s->msg = git__strdup(msg));
git_vector_insert(&data->statuses, s);
return 0;
}
static void do_verify_push_status(record_callbacks_data *data, const push_status expected[], const size_t expected_len)
{
git_vector *actual = &data->statuses;
push_status *iter;
bool failed = false;
size_t i;
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_dispose(&msg);
}
git_vector_foreach(actual, i, iter) {
push_status *s = (push_status *)iter;
git__free(s->ref);
git__free(s->msg);
git__free(s);
}
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-tracking 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))));
git_reference_free(ref);
}
cl_assert_equal_i(error, GIT_ITEROVER);
git_branch_iterator_free(iter);
/* Loop through expected refs, make sure they exist */
for (i = 0; i < expected_refs_len; i++) {
/* Convert remote reference name into remote-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 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_dispose(&msg);
git_buf_dispose(&ref_name);
}
static void verify_update_tips_callback(git_remote *remote, expected_ref expected_refs[], size_t expected_refs_len)
{
git_refspec *fetch_spec;
git_buf msg = GIT_BUF_INIT;
git_buf ref_name = GIT_BUF_INIT;
updated_tip *tip = NULL;
size_t i, j;
int failed = 0;
for (i = 0; i < expected_refs_len; ++i) {
/* Convert remote reference name into tracking branch name.
* If the spec is not under refs/heads/, then skip.
*/
fetch_spec = git_remote__matching_refspec(remote, expected_refs[i].name);
if (!fetch_spec)
continue;
cl_git_pass(git_refspec_transform(&ref_name, fetch_spec, expected_refs[i].name));
/* Find matching update_tip entry */
git_vector_foreach(&_record_cbs_data.updated_tips, j, tip) {
if (!strcmp(git_buf_cstr(&ref_name), tip->name))
break;
}
if (j == _record_cbs_data.updated_tips.length) {
git_buf_printf(&msg, "Did not find expected updated tip entry for branch '%s'.", git_buf_cstr(&ref_name));
failed = 1;
goto failed;
}
if (git_oid_cmp(expected_refs[i].oid, &tip->new_oid) != 0) {
git_buf_printf(&msg, "Updated tip ID does not match expected ID");
failed = 1;
goto failed;
}
}
failed:
if (failed)
cl_fail(git_buf_cstr(&msg));
git_buf_dispose(&ref_name);
git_buf_dispose(&msg);
}
void test_online_push__initialize(void)
{
git_vector delete_specs = GIT_VECTOR_INIT;
const git_remote_head **heads;
size_t heads_len;
git_push_options push_opts = GIT_PUSH_OPTIONS_INIT;
git_fetch_options fetch_opts = GIT_FETCH_OPTIONS_INIT;
_repo = cl_git_sandbox_init("push_src");
cl_git_pass(git_repository_set_ident(_repo, "Random J. Hacker", "foo@example.com"));
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_default = cl_getenv("GITTEST_REMOTE_DEFAULT");
_remote = NULL;
/* Skip the test if we're missing the remote URL */
if (!_remote_url)
cl_skip();
cl_git_pass(git_remote_create(&_remote, _repo, "test", _remote_url));
record_callbacks_data_clear(&_record_cbs_data);
cl_git_pass(git_remote_connect(_remote, GIT_DIRECTION_PUSH, &_record_cbs, NULL, NULL));
/* 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_strarray arr = {
(char **) delete_specs.contents,
delete_specs.length,
};
memcpy(&push_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
cl_git_pass(git_remote_upload(_remote, &arr, &push_opts));
}
git_remote_disconnect(_remote);
git_vector_free_deep(&delete_specs);
/* Now that we've deleted everything, fetch from the remote */
memcpy(&fetch_opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
cl_git_pass(git_remote_fetch(_remote, NULL, &fetch_opts, NULL));
}
void test_online_push__cleanup(void)
{
if (_remote)
git_remote_free(_remote);
_remote = NULL;
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);
/* 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)
{
record_callbacks_data *data = (record_callbacks_data *) payload;
GIT_UNUSED(stage); GIT_UNUSED(current); GIT_UNUSED(total);
if (data->pack_progress_calls < 0)
return data->pack_progress_calls;
data->pack_progress_calls++;
return 0;
}
static int push_transfer_progress_cb(
unsigned int current, unsigned int total, size_t bytes, void* payload)
{
record_callbacks_data *data = (record_callbacks_data *) payload;
GIT_UNUSED(current); GIT_UNUSED(total); GIT_UNUSED(bytes);
if (data->transfer_progress_calls < 0)
return data->transfer_progress_calls;
data->transfer_progress_calls++;
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, int check_update_tips_cb)
{
git_push_options opts = GIT_PUSH_OPTIONS_INIT;
size_t i;
int error;
git_strarray specs = {0};
record_callbacks_data *data;
if (_remote) {
/* Auto-detect the number of threads to use */
opts.pb_parallelism = 0;
memcpy(&opts.callbacks, &_record_cbs, sizeof(git_remote_callbacks));
data = opts.callbacks.payload;
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;
if (refspecs_len) {
specs.count = refspecs_len;
specs.strings = git__calloc(refspecs_len, sizeof(char *));
cl_assert(specs.strings);
}
for (i = 0; i < refspecs_len; i++)
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;
error = git_remote_push(_remote, &specs, &opts);
git__free(specs.strings);
if (expected_ret < 0) {
cl_git_fail_with(expected_ret, error);
} else {
cl_git_pass(error);
}
if (check_progress_cb && expected_ret == 0) {
cl_assert(data->pack_progress_calls > 0);
cl_assert(data->transfer_progress_calls > 0);
}
do_verify_push_status(data, expected_statuses, expected_statuses_len);
verify_refs(_remote, expected_refs, expected_refs_len);
verify_tracking_branches(_remote, expected_refs, expected_refs_len);
if (check_update_tips_cb)
verify_update_tips_callback(_remote, expected_refs, expected_refs_len);
}
}
/* 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, 1);
}
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, 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, 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, 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, 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, 1);
}
void test_online_push__b5_cancel(void)
{
const char *specs[] = { "refs/heads/b5:refs/heads/b5" };
do_push(specs, ARRAY_SIZE(specs), NULL, 0, NULL, 0, GIT_EUSER, 1, 1);
}
void test_online_push__multi(void)
{
git_reflog *log;
const git_reflog_entry *entry;
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, 1);
cl_git_pass(git_reflog_read(&log, _repo, "refs/remotes/test/b1"));
entry = git_reflog_entry_byindex(log, 0);
if (entry) {
cl_assert_equal_s("update by push", git_reflog_entry_message(entry));
cl_assert_equal_s("foo@example.com", git_reflog_entry_committer(entry)->email);
}
git_reflog_free(log);
}
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, 1);
do_push(specs2, ARRAY_SIZE(specs2),
exp_stats2, ARRAY_SIZE(exp_stats2),
exp_refs2, ARRAY_SIZE(exp_refs2), 0, 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, 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, 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, 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, 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, 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, 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, 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, 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, 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, 1);
do_push(specs2, ARRAY_SIZE(specs2),
NULL, 0,
exp_refs1, ARRAY_SIZE(exp_refs1), GIT_ENONFASTFORWARD, 0, 0);
/* Non-fast-forward update with force should pass. */
record_callbacks_data_clear(&_record_cbs_data);
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, 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, 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, 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, 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, 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, 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, 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.
*/
char *specs = {
"b6:b6",
};
git_strarray arr = {
&specs,
1,
};
if (_remote) {
cl_git_fail(git_remote_upload(_remote, &arr, NULL));
}
}
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" };
/* 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, 0);
}
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 } };
const char *specs_del[] = { ":refs/notes/commits" };
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, NULL, signature, signature, 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, 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);
}
void test_online_push__configured(void)
{
git_oid note_oid, *target_oid, expected_oid;
git_signature *signature;
git_remote *old_remote;
const char *specs[] = { "refs/notes/commits:refs/notes/commits" };
push_status exp_stats[] = { { "refs/notes/commits", 1 } };
expected_ref exp_refs[] = { { "refs/notes/commits", &expected_oid } };
const char *specs_del[] = { ":refs/notes/commits" };
git_oid_fromstr(&expected_oid, "8461a99b27b7043e58ff6e1f5d2cf07d282534fb");
target_oid = &_oid_b6;
cl_git_pass(git_remote_add_push(_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);
/* 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, NULL, signature, signature, target_oid, "hello world\n", 0));
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);
}
+141
View File
@@ -0,0 +1,141 @@
#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);
}
void push_status_free(push_status *s)
{
git__free(s->ref);
git__free(s->msg);
git__free(s);
}
void record_callbacks_data_clear(record_callbacks_data *data)
{
size_t i;
updated_tip *tip;
push_status *status;
git_vector_foreach(&data->updated_tips, i, tip)
updated_tip_free(tip);
git_vector_free(&data->updated_tips);
git_vector_foreach(&data->statuses, i, status)
push_status_free(status);
git_vector_free(&data->statuses);
data->pack_progress_calls = 0;
data->transfer_progress_calls = 0;
}
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__calloc(1, sizeof(*t)));
cl_assert(t->name = git__strdup(refname));
git_oid_cpy(&t->old_oid, a);
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++) {
oid_str = git_oid_tostr_s(expected_refs[i].oid);
cl_git_pass(git_buf_printf(&msg, "%s = %s\n", expected_refs[i].name, 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;
oid_str = git_oid_tostr_s(&actual->oid);
cl_git_pass(git_buf_printf(&msg, "%s = %s\n", actual->name, oid_str));
}
cl_fail(git_buf_cstr(&msg));
git_buf_dispose(&msg);
}
+83
View File
@@ -0,0 +1,83 @@
#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, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, data, NULL }
typedef struct {
char *name;
git_oid old_oid;
git_oid new_oid;
} updated_tip;
typedef struct {
git_vector updated_tips;
git_vector statuses;
int pack_progress_calls;
int transfer_progress_calls;
} record_callbacks_data;
typedef struct {
const char *name;
const git_oid *oid;
} expected_ref;
/* 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 {
char *ref;
int success;
char *msg;
} push_status;
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__ */
+127
View File
@@ -0,0 +1,127 @@
#include "clar_libgit2.h"
#define URL "git://github.com/libgit2/TestGitRepository"
#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge"
static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
{
GIT_UNUSED(payload);
cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, REFSPEC));
return 0;
}
void test_online_remotes__single_branch(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
git_repository *repo;
git_remote *remote;
git_strarray refs;
size_t i, count = 0;
opts.remote_cb = remote_single_branch;
opts.checkout_branch = "first-merge";
cl_git_pass(git_clone(&repo, URL, "./single-branch", &opts));
cl_git_pass(git_reference_list(&refs, repo));
for (i = 0; i < refs.count; i++) {
if (!git__prefixcmp(refs.strings[i], "refs/heads/"))
count++;
}
cl_assert_equal_i(1, count);
git_strarray_free(&refs);
cl_git_pass(git_remote_lookup(&remote, repo, "origin"));
cl_git_pass(git_remote_get_fetch_refspecs(&refs, remote));
cl_assert_equal_i(1, refs.count);
cl_assert_equal_s(REFSPEC, refs.strings[0]);
git_strarray_free(&refs);
git_remote_free(remote);
git_repository_free(repo);
}
void test_online_remotes__restricted_refspecs(void)
{
git_clone_options opts = GIT_CLONE_OPTIONS_INIT;
git_repository *repo;
opts.remote_cb = remote_single_branch;
cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, URL, "./restrict-refspec", &opts));
}
void test_online_remotes__detached_remote_fails_downloading(void)
{
git_remote *remote;
cl_git_pass(git_remote_create_detached(&remote, URL));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_fail(git_remote_download(remote, NULL, NULL));
git_remote_free(remote);
}
void test_online_remotes__detached_remote_fails_uploading(void)
{
git_remote *remote;
cl_git_pass(git_remote_create_detached(&remote, URL));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_fail(git_remote_upload(remote, NULL, NULL));
git_remote_free(remote);
}
void test_online_remotes__detached_remote_fails_pushing(void)
{
git_remote *remote;
cl_git_pass(git_remote_create_detached(&remote, URL));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_fail(git_remote_push(remote, NULL, NULL));
git_remote_free(remote);
}
void test_online_remotes__detached_remote_succeeds_ls(void)
{
const char *refs[] = {
"HEAD",
"refs/heads/first-merge",
"refs/heads/master",
"refs/heads/no-parent",
"refs/tags/annotated_tag",
"refs/tags/annotated_tag^{}",
"refs/tags/blob",
"refs/tags/commit_tree",
"refs/tags/nearly-dangling",
};
const git_remote_head **heads;
git_remote *remote;
size_t i, j, n;
cl_git_pass(git_remote_create_detached(&remote, URL));
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
cl_git_pass(git_remote_ls(&heads, &n, remote));
cl_assert_equal_sz(n, 9);
for (i = 0; i < n; i++) {
char found = false;
for (j = 0; j < ARRAY_SIZE(refs); j++) {
if (!strcmp(heads[i]->name, refs[j])) {
found = true;
break;
}
}
cl_assert_(found, heads[i]->name);
}
git_remote_free(remote);
}