Commit 3cbaebdf by Etienne Samson

remote: provide a generic API for creating remotes

This supersedes the functionality of remote_create_with_fetchspec, remote_create_anonymous and remote_create_detached.
parent 43b4b2fa
......@@ -42,6 +42,66 @@ GIT_EXTERN(int) git_remote_create(
const char *url);
/**
* Remote creation options structure
*
* Initialize with `GIT_REMOTE_CREATE_OPTIONS_INIT`. Alternatively, you can
* use `git_remote_create_init_options`.
*
*/
typedef struct git_remote_create_options {
unsigned int version;
/**
* The repository that should own the remote.
* Setting this to NULL results in a detached remote.
*/
git_repository *repository;
/**
* The remote's name.
* Setting this to NULL results in an in-memory/anonymous remote.
*/
const char *name;
/** The fetchspec the remote should use. */
const char *fetchspec;
} git_remote_create_options;
#define GIT_REMOTE_CREATE_OPTIONS_VERSION 1
#define GIT_REMOTE_CREATE_OPTIONS_INIT {GIT_REMOTE_CREATE_OPTIONS_VERSION}
/**
* Initialize git_remote_create_options structure
*
* Initializes a `git_remote_create_options` with default values. Equivalent to
* creating an instance with `GIT_REMOTE_CREATE_OPTIONS_INIT`.
*
* @param opts The `git_remote_create_options` struct to initialize.
* @param version The struct version; pass `GIT_REMOTE_CREATE_OPTIONS_VERSION`.
* @return Zero on success; -1 on failure.
*/
GIT_EXTERN(int) git_remote_create_init_options(
git_remote_create_options *opts,
unsigned int version);
/**
* Create a remote, with options.
*
* This function allows more fine-grained control over the remote creation.
*
* Passing NULL as the opts argument will result in a detached remote.
*
* @param out the resulting remote
* @param url the remote's url
* @param opts the remote creation options
* @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
*/
GIT_EXTERN(int) git_remote_create_with_opts(
git_remote **out,
const char *url,
const git_remote_create_options *opts);
/**
* Add a remote with the provided fetch refspec (or default if NULL) to the repository's
* configuration.
*
......
......@@ -217,57 +217,79 @@ static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
return GIT_EEXISTS;
}
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version)
{
git_remote *remote;
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT);
return 0;
}
static int create_internal(git_remote **out, const char *url, const git_remote_create_options *opts)
{
git_remote *remote = NULL;
git_config *config_ro = NULL, *config_rw;
git_buf canonical_url = GIT_BUF_INIT;
git_buf var = GIT_BUF_INIT;
const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
int error = -1;
/* repo, name, and fetch are optional */
assert(out && url);
if (repo && (error = git_repository_config_snapshot(&config_ro, repo)) < 0)
if (!opts) {
opts = &dummy_opts;
}
GITERR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options");
if (opts->name != NULL) {
if ((error = ensure_remote_name_is_valid(opts->name)) < 0)
return error;
if (opts->repository &&
(error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0)
return error;
}
if (opts->repository) {
if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0)
goto on_error;
}
remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote);
remote->repo = repo;
remote->repo = opts->repository;
if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
(error = canonicalize_url(&canonical_url, url)) < 0)
goto on_error;
if (repo) {
if (opts->repository) {
remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
} else {
remote->url = git__strdup(canonical_url.ptr);
}
GITERR_CHECK_ALLOC(remote->url);
if (name != NULL) {
remote->name = git__strdup(name);
if (opts->name != NULL) {
remote->name = git__strdup(opts->name);
GITERR_CHECK_ALLOC(remote->name);
if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0)
goto on_error;
if (repo &&
((error = git_repository_config__weakptr(&config_rw, repo)) < 0 ||
if (opts->repository &&
((error = git_buf_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 ||
(error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 ||
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0))
goto on_error;
}
if (fetch != NULL) {
if ((error = add_refspec(remote, fetch, true)) < 0)
if (opts->fetchspec != NULL) {
if ((error = add_refspec(remote, opts->fetchspec, true)) < 0)
goto on_error;
/* only write for named remotes with a repository */
if (repo && name &&
((error = write_add_refspec(repo, name, fetch, true)) < 0 ||
(error = lookup_remote_prune_config(remote, config_ro, name)) < 0))
if (opts->repository && opts->name &&
((error = write_add_refspec(opts->repository, opts->name, opts->fetchspec, true)) < 0 ||
(error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0))
goto on_error;
/* Move the data over to where the matching functions can find them */
......@@ -276,7 +298,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
}
/* A remote without a name doesn't download tags */
if (!name)
if (!opts->name)
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
else
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
......@@ -301,47 +323,60 @@ int git_remote_create(git_remote **out, git_repository *repo, const char *name,
{
git_buf buf = GIT_BUF_INIT;
int error;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
if (default_fetchspec_for_name(&buf, name) < 0)
return -1;
/* Those 2 tests are duplicated here because of backward-compatibility */
if ((error = ensure_remote_name_is_valid(name)) < 0)
return error;
if (canonicalize_url(&buf, url) < 0)
return GIT_ERROR;
git_buf_clear(&buf);
opts.repository = repo;
opts.name = name;
opts.fetchspec = git_buf_cstr(&buf);
error = create_internal(out, url, &opts);
error = git_remote_create_with_fetchspec(out, repo, name, url, git_buf_cstr(&buf));
git_buf_dispose(&buf);
return error;
}
int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts)
{
return create_internal(out, url, opts);
}
int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
{
git_remote *remote = NULL;
int error;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
if ((error = ensure_remote_name_is_valid(name)) < 0)
return error;
if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
return error;
if (create_internal(&remote, repo, name, url, fetch) < 0)
goto on_error;
*out = remote;
return 0;
opts.repository = repo;
opts.name = name;
opts.fetchspec = fetch;
on_error:
git_remote_free(remote);
return -1;
return create_internal(out, url, &opts);
}
int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url)
{
return create_internal(out, repo, NULL, url, NULL);
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.repository = repo;
return create_internal(out, url, &opts);
}
int git_remote_create_detached(git_remote **out, const char *url)
{
return create_internal(out, NULL, NULL, url, NULL);
return create_internal(out, url, NULL);
}
int git_remote_dup(git_remote **dest, git_remote *source)
......
......@@ -52,6 +52,8 @@ void test_remote_create__named(void)
git_config *cfg;
const char *cfg_val;
cl_skip(); // Later
size_t section_count = count_config_entries_match(_repo, "remote\\.");
cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL));
......@@ -188,3 +190,153 @@ void test_remote_create__detached_invalid_url(void)
{
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, git_remote_create_detached(&r, ""));
}
void test_remote_create__with_opts_named(void)
{
git_remote *remote;
git_strarray array;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.name = "test-new";
opts.repository = _repo;
cl_skip(); // Later
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts));
cl_assert_equal_s(git_remote_name(remote), "test-new");
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), _repo);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(1, array.count);
cl_assert_equal_s("+refs/heads/*:refs/remotes/test-new/*", array.strings[0]);
git_strarray_free(&array);
git_remote_free(remote);
}
void test_remote_create__with_opts_named_and_fetchspec(void)
{
git_remote *remote;
git_strarray array;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.name = "test-new";
opts.repository = _repo;
opts.fetchspec = "+refs/*:refs/*";
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts));
cl_assert_equal_s(git_remote_name(remote), "test-new");
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), _repo);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(1, array.count);
cl_assert_equal_s("+refs/*:refs/*", array.strings[0]);
git_strarray_free(&array);
git_remote_free(remote);
}
void test_remote_create__with_opts_named_no_fetchspec(void)
{
git_remote *remote;
git_strarray array;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.name = "test-new";
opts.repository = _repo;
// opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC; // Later
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts));
cl_assert_equal_s(git_remote_name(remote), "test-new");
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), _repo);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(0, array.count);
git_strarray_free(&array);
git_remote_free(remote);
}
void test_remote_create__with_opts_anonymous(void)
{
git_remote *remote;
git_strarray array;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.repository = _repo;
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts));
cl_assert_equal_s(git_remote_name(remote), NULL);
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), _repo);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(0, array.count);
git_strarray_free(&array);
git_remote_free(remote);
}
void test_remote_create__with_opts_detached(void)
{
git_remote *remote;
git_strarray array;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, &opts));
cl_assert_equal_s(git_remote_name(remote), NULL);
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), NULL);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(0, array.count);
git_strarray_free(&array);
git_remote_free(remote);
cl_git_pass(git_remote_create_with_opts(&remote, TEST_URL, NULL));
cl_assert_equal_s(git_remote_name(remote), NULL);
cl_assert_equal_s(git_remote_url(remote), TEST_URL);
cl_assert_equal_p(git_remote_owner(remote), NULL);
cl_git_pass(git_remote_get_fetch_refspecs(&array, remote));
cl_assert_equal_i(0, array.count);
git_strarray_free(&array);
git_remote_free(remote);
}
static int create_with_name(git_remote **remote, git_repository *repo, const char *name, const char *url)
{
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
opts.repository = repo;
opts.name = name;
return git_remote_create_with_opts(remote, url, &opts);
}
void test_remote_create__with_opts_invalid_name(void)
{
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "Inv@{id", TEST_URL));
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "", TEST_URL));
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "/", TEST_URL));
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "//", TEST_URL));
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, ".lock", TEST_URL));
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "a.lock", TEST_URL));
}
void test_remote_create__with_opts_conflicting_name(void)
{
cl_git_assert_cannot_create_remote(GIT_EEXISTS, create_with_name(&r, _repo, "test", TEST_URL));
}
void test_remote_create__with_opts_invalid_url(void)
{
cl_git_assert_cannot_create_remote(GIT_EINVALIDSPEC, create_with_name(&r, _repo, "test-new", ""));
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment