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( ...@@ -42,6 +42,66 @@ GIT_EXTERN(int) git_remote_create(
const char *url); 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 * Add a remote with the provided fetch refspec (or default if NULL) to the repository's
* configuration. * configuration.
* *
......
...@@ -217,57 +217,79 @@ static int ensure_remote_doesnot_exist(git_repository *repo, const char *name) ...@@ -217,57 +217,79 @@ static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
return GIT_EEXISTS; 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_config *config_ro = NULL, *config_rw;
git_buf canonical_url = GIT_BUF_INIT; git_buf canonical_url = GIT_BUF_INIT;
git_buf var = GIT_BUF_INIT; git_buf var = GIT_BUF_INIT;
const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
int error = -1; int error = -1;
/* repo, name, and fetch are optional */
assert(out && url); assert(out && url);
if (repo && (error = git_repository_config_snapshot(&config_ro, repo)) < 0) if (!opts) {
return error; 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)); remote = git__calloc(1, sizeof(git_remote));
GITERR_CHECK_ALLOC(remote); GITERR_CHECK_ALLOC(remote);
remote->repo = repo; remote->repo = opts->repository;
if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 || if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
(error = canonicalize_url(&canonical_url, url)) < 0) (error = canonicalize_url(&canonical_url, url)) < 0)
goto on_error; goto on_error;
if (repo) { if (opts->repository) {
remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH); remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
} else { } else {
remote->url = git__strdup(canonical_url.ptr); remote->url = git__strdup(canonical_url.ptr);
} }
GITERR_CHECK_ALLOC(remote->url); GITERR_CHECK_ALLOC(remote->url);
if (name != NULL) { if (opts->name != NULL) {
remote->name = git__strdup(name); remote->name = git__strdup(opts->name);
GITERR_CHECK_ALLOC(remote->name); GITERR_CHECK_ALLOC(remote->name);
if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0) if (opts->repository &&
goto on_error; ((error = git_buf_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 ||
(error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 ||
if (repo && (error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0))
((error = git_repository_config__weakptr(&config_rw, repo)) < 0 ||
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0))
goto on_error; goto on_error;
} }
if (fetch != NULL) { if (opts->fetchspec != NULL) {
if ((error = add_refspec(remote, fetch, true)) < 0) if ((error = add_refspec(remote, opts->fetchspec, true)) < 0)
goto on_error; goto on_error;
/* only write for named remotes with a repository */ /* only write for named remotes with a repository */
if (repo && name && if (opts->repository && opts->name &&
((error = write_add_refspec(repo, name, fetch, true)) < 0 || ((error = write_add_refspec(opts->repository, opts->name, opts->fetchspec, true)) < 0 ||
(error = lookup_remote_prune_config(remote, config_ro, name)) < 0)) (error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0))
goto on_error; goto on_error;
/* Move the data over to where the matching functions can find them */ /* 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 ...@@ -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 */ /* A remote without a name doesn't download tags */
if (!name) if (!opts->name)
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE; remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
else else
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO; 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, ...@@ -301,47 +323,60 @@ int git_remote_create(git_remote **out, git_repository *repo, const char *name,
{ {
git_buf buf = GIT_BUF_INIT; git_buf buf = GIT_BUF_INIT;
int error; int error;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
if (default_fetchspec_for_name(&buf, name) < 0) /* Those 2 tests are duplicated here because of backward-compatibility */
return -1; 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); git_buf_dispose(&buf);
return error; 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) 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; int error;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
if ((error = ensure_remote_name_is_valid(name)) < 0) if ((error = ensure_remote_name_is_valid(name)) < 0)
return error; return error;
if ((error = ensure_remote_doesnot_exist(repo, name)) < 0) opts.repository = repo;
return error; opts.name = name;
opts.fetchspec = fetch;
if (create_internal(&remote, repo, name, url, fetch) < 0)
goto on_error;
*out = remote;
return 0;
on_error: return create_internal(out, url, &opts);
git_remote_free(remote);
return -1;
} }
int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url) 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) 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) int git_remote_dup(git_remote **dest, git_remote *source)
......
...@@ -52,6 +52,8 @@ void test_remote_create__named(void) ...@@ -52,6 +52,8 @@ void test_remote_create__named(void)
git_config *cfg; git_config *cfg;
const char *cfg_val; const char *cfg_val;
cl_skip(); // Later
size_t section_count = count_config_entries_match(_repo, "remote\\."); size_t section_count = count_config_entries_match(_repo, "remote\\.");
cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL)); cl_git_pass(git_remote_create(&remote, _repo, "valid-name", TEST_URL));
...@@ -188,3 +190,153 @@ void test_remote_create__detached_invalid_url(void) ...@@ -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, "")); 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