Unverified Commit 20cb30b6 by Patrick Steinhardt Committed by GitHub

Merge pull request #4667 from tiennou/feature/remote-create-api

Remote creation API
parents 28239be3 666c7bd8
......@@ -42,6 +42,80 @@ GIT_EXTERN(int) git_remote_create(
const char *url);
/**
* Remote creation options flags
*/
typedef enum {
/** Ignore the repository apply.insteadOf configuration */
GIT_REMOTE_CREATE_SKIP_INSTEADOF = (1 << 0),
/** Don't build a fetchspec from the name if none is set */
GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC = (1 << 1),
} git_remote_create_flags;
/**
* 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;
/** Additional flags for the remote. See git_remote_create_flags. */
unsigned int flags;
} 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.
*
......
......@@ -1182,6 +1182,8 @@ int git_config_lock(git_transaction **out, git_config *cfg)
git_config_backend *backend;
backend_internal *internal;
assert(cfg);
internal = git_vector_get(&cfg->backends, 0);
if (!internal || !internal->backend) {
giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");
......@@ -1200,6 +1202,8 @@ int git_config_unlock(git_config *cfg, int commit)
git_config_backend *backend;
backend_internal *internal;
assert(cfg);
internal = git_vector_get(&cfg->backends, 0);
if (!internal || !internal->backend) {
giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");
......
......@@ -189,58 +189,119 @@ static int canonicalize_url(git_buf *out, const char *in)
return git_buf_puts(out, in);
}
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
static int default_fetchspec_for_name(git_buf *buf, const char *name)
{
if (git_buf_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
return -1;
return 0;
}
static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
{
int error;
git_remote *remote;
error = git_remote_lookup(&remote, repo, name);
if (error == GIT_ENOTFOUND)
return 0;
if (error < 0)
return error;
git_remote_free(remote);
giterr_set(GITERR_CONFIG, "remote '%s' already exists", name);
return GIT_EEXISTS;
}
int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version)
{
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT);
return 0;
}
int git_remote_create_with_opts(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;
git_buf specbuf = 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)
return error;
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 ||
if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 ||
(error = canonicalize_url(&canonical_url, url)) < 0)
goto on_error;
if (repo) {
if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) {
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 ||
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 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)
goto on_error;
if (opts->fetchspec != NULL ||
(opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) {
const char *fetch = NULL;
if (opts->fetchspec) {
fetch = opts->fetchspec;
} else {
if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0)
goto on_error;
fetch = git_buf_cstr(&specbuf);
}
/* only write for non-anonymous remotes */
if (repo && name && (error = write_add_refspec(repo, name, fetch, true)) < 0)
if ((error = add_refspec(remote, fetch, true)) < 0)
goto on_error;
if (repo && (error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
/* only write for named remotes with a repository */
if (opts->repository && opts->name &&
((error = write_add_refspec(opts->repository, opts->name, fetch, 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 */
......@@ -249,7 +310,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;
......@@ -265,43 +326,32 @@ on_error:
git_remote_free(remote);
git_config_free(config_ro);
git_buf_dispose(&specbuf);
git_buf_dispose(&canonical_url);
git_buf_dispose(&var);
return error;
}
static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
{
git_buf buf = GIT_BUF_INIT;
int error;
git_remote *remote;
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
error = git_remote_lookup(&remote, repo, name);
if (error == GIT_ENOTFOUND)
return 0;
if (error < 0)
/* Those 2 tests are duplicated here because of backward-compatibility */
if ((error = ensure_remote_name_is_valid(name)) < 0)
return error;
git_remote_free(remote);
giterr_set(
GITERR_CONFIG,
"remote '%s' already exists", name);
return GIT_EEXISTS;
}
if (canonicalize_url(&buf, url) < 0)
return GIT_ERROR;
git_buf_clear(&buf);
int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
{
git_buf buf = GIT_BUF_INIT;
int error;
opts.repository = repo;
opts.name = name;
if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
return -1;
error = git_remote_create_with_opts(out, url, &opts);
error = git_remote_create_with_fetchspec(out, repo, name, url, git_buf_cstr(&buf));
git_buf_dispose(&buf);
return error;
......@@ -309,35 +359,32 @@ int git_remote_create(git_remote **out, git_repository *repo, const char *name,
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;
opts.repository = repo;
opts.name = name;
opts.fetchspec = fetch;
opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC;
if (create_internal(&remote, repo, name, url, fetch) < 0)
goto on_error;
*out = remote;
return 0;
on_error:
git_remote_free(remote);
return -1;
return git_remote_create_with_opts(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 git_remote_create_with_opts(out, url, &opts);
}
int git_remote_create_detached(git_remote **out, const char *url)
{
return create_internal(out, NULL, NULL, url, NULL);
return git_remote_create_with_opts(out, url, NULL);
}
int git_remote_dup(git_remote **dest, git_remote *source)
......@@ -1946,8 +1993,7 @@ static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const
if ((error = git_vector_init(problems, 1, NULL)) < 0)
return error;
if ((error = git_buf_printf(
&base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0)
if ((error = default_fetchspec_for_name(&base, remote->name)) < 0)
return error;
git_vector_foreach(&remote->refspecs, i, spec) {
......@@ -1972,8 +2018,7 @@ static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const
git_buf_clear(&val);
git_buf_clear(&var);
if (git_buf_printf(
&val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0 ||
if (default_fetchspec_for_name(&val, new_name) < 0 ||
git_buf_printf(&var, "remote.%s.fetch", new_name) < 0)
{
error = -1;
......
#include "clar_libgit2.h"
static git_remote *_remote;
static git_repository *_repo;
static git_config *_config;
static char url[] = "http://github.com/libgit2/libgit2.git";
void test_network_remote_createthenload__initialize(void)
{
cl_fixture_sandbox("testrepo.git");
cl_git_pass(git_repository_open(&_repo, "testrepo.git"));
cl_git_pass(git_repository_config(&_config, _repo));
cl_git_pass(git_config_set_string(_config, "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"));
cl_git_pass(git_config_set_string(_config, "remote.origin.url", url));
git_config_free(_config);
cl_git_pass(git_remote_lookup(&_remote, _repo, "origin"));
}
void test_network_remote_createthenload__cleanup(void)
{
git_remote_free(_remote);
_remote = NULL;
git_repository_free(_repo);
_repo = NULL;
cl_fixture_cleanup("testrepo.git");
}
void test_network_remote_createthenload__parsing(void)
{
cl_assert_equal_s(git_remote_name(_remote), "origin");
cl_assert_equal_s(git_remote_url(_remote), url);
}
......@@ -312,30 +312,6 @@ void test_network_remote_remotes__add(void)
cl_assert_equal_s(git_remote_url(_remote), "http://github.com/libgit2/libgit2");
}
void test_network_remote_remotes__cannot_add_a_nameless_remote(void)
{
git_remote *remote;
cl_assert_equal_i(
GIT_EINVALIDSPEC,
git_remote_create(&remote, _repo, NULL, "git://github.com/libgit2/libgit2"));
}
void test_network_remote_remotes__cannot_add_a_remote_with_an_invalid_name(void)
{
git_remote *remote = NULL;
cl_assert_equal_i(
GIT_EINVALIDSPEC,
git_remote_create(&remote, _repo, "Inv@{id", "git://github.com/libgit2/libgit2"));
cl_assert_equal_p(remote, NULL);
cl_assert_equal_i(
GIT_EINVALIDSPEC,
git_remote_create(&remote, _repo, "", "git://github.com/libgit2/libgit2"));
cl_assert_equal_p(remote, NULL);
}
void test_network_remote_remotes__tagopt(void)
{
const char *name = git_remote_name(_remote);
......@@ -389,41 +365,6 @@ void test_network_remote_remotes__returns_ENOTFOUND_when_neither_url_nor_pushurl
git_remote_lookup(&remote, _repo, "no-remote-url"), GIT_ENOTFOUND);
}
void assert_cannot_create_remote(const char *name, int expected_error)
{
git_remote *remote = NULL;
cl_git_fail_with(
git_remote_create(&remote, _repo, name, "git://github.com/libgit2/libgit2"),
expected_error);
cl_assert_equal_p(remote, NULL);
}
void test_network_remote_remotes__cannot_create_a_remote_which_name_conflicts_with_an_existing_remote(void)
{
assert_cannot_create_remote("test", GIT_EEXISTS);
}
void test_network_remote_remotes__cannot_create_a_remote_which_name_is_invalid(void)
{
assert_cannot_create_remote("/", GIT_EINVALIDSPEC);
assert_cannot_create_remote("//", GIT_EINVALIDSPEC);
assert_cannot_create_remote(".lock", GIT_EINVALIDSPEC);
assert_cannot_create_remote("a.lock", GIT_EINVALIDSPEC);
}
void test_network_remote_remote__git_remote_create_with_fetchspec(void)
{
git_remote *remote;
git_strarray array;
cl_git_pass(git_remote_create_with_fetchspec(&remote, _repo, "test-new", "git://github.com/libgit2/libgit2", "+refs/*:refs/*"));
git_remote_get_fetch_refspecs(&array, remote);
cl_assert_equal_s("+refs/*:refs/*", array.strings[0]);
git_remote_free(remote);
}
static const char *fetch_refspecs[] = {
"+refs/heads/*:refs/remotes/origin/*",
"refs/tags/*:refs/tags/*",
......
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