Skip to content

Commit 20cb30b

Browse files
authored
Merge pull request libgit2#4667 from tiennou/feature/remote-create-api
Remote creation API
2 parents 28239be + 666c7bd commit 20cb30b

File tree

6 files changed

+575
-160
lines changed

6 files changed

+575
-160
lines changed

include/git2/remote.h

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,80 @@ GIT_EXTERN(int) git_remote_create(
4141
const char *name,
4242
const char *url);
4343

44+
/**
45+
* Remote creation options flags
46+
*/
47+
typedef enum {
48+
/** Ignore the repository apply.insteadOf configuration */
49+
GIT_REMOTE_CREATE_SKIP_INSTEADOF = (1 << 0),
50+
51+
/** Don't build a fetchspec from the name if none is set */
52+
GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC = (1 << 1),
53+
} git_remote_create_flags;
54+
55+
/**
56+
* Remote creation options structure
57+
*
58+
* Initialize with `GIT_REMOTE_CREATE_OPTIONS_INIT`. Alternatively, you can
59+
* use `git_remote_create_init_options`.
60+
*
61+
*/
62+
typedef struct git_remote_create_options {
63+
unsigned int version;
64+
65+
/**
66+
* The repository that should own the remote.
67+
* Setting this to NULL results in a detached remote.
68+
*/
69+
git_repository *repository;
70+
71+
/**
72+
* The remote's name.
73+
* Setting this to NULL results in an in-memory/anonymous remote.
74+
*/
75+
const char *name;
76+
77+
/** The fetchspec the remote should use. */
78+
const char *fetchspec;
79+
80+
/** Additional flags for the remote. See git_remote_create_flags. */
81+
unsigned int flags;
82+
} git_remote_create_options;
83+
84+
#define GIT_REMOTE_CREATE_OPTIONS_VERSION 1
85+
#define GIT_REMOTE_CREATE_OPTIONS_INIT {GIT_REMOTE_CREATE_OPTIONS_VERSION}
86+
87+
/**
88+
* Initialize git_remote_create_options structure
89+
*
90+
* Initializes a `git_remote_create_options` with default values. Equivalent to
91+
* creating an instance with `GIT_REMOTE_CREATE_OPTIONS_INIT`.
92+
*
93+
* @param opts The `git_remote_create_options` struct to initialize.
94+
* @param version The struct version; pass `GIT_REMOTE_CREATE_OPTIONS_VERSION`.
95+
* @return Zero on success; -1 on failure.
96+
*/
97+
GIT_EXTERN(int) git_remote_create_init_options(
98+
git_remote_create_options *opts,
99+
unsigned int version);
100+
101+
/**
102+
* Create a remote, with options.
103+
*
104+
* This function allows more fine-grained control over the remote creation.
105+
*
106+
* Passing NULL as the opts argument will result in a detached remote.
107+
*
108+
* @param out the resulting remote
109+
* @param url the remote's url
110+
* @param opts the remote creation options
111+
* @return 0, GIT_EINVALIDSPEC, GIT_EEXISTS or an error code
112+
*/
113+
GIT_EXTERN(int) git_remote_create_with_opts(
114+
git_remote **out,
115+
const char *url,
116+
const git_remote_create_options *opts);
117+
44118
/**
45119
* Add a remote with the provided fetch refspec (or default if NULL) to the repository's
46120
* configuration.

src/config.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,6 +1182,8 @@ int git_config_lock(git_transaction **out, git_config *cfg)
11821182
git_config_backend *backend;
11831183
backend_internal *internal;
11841184

1185+
assert(cfg);
1186+
11851187
internal = git_vector_get(&cfg->backends, 0);
11861188
if (!internal || !internal->backend) {
11871189
giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");
@@ -1200,6 +1202,8 @@ int git_config_unlock(git_config *cfg, int commit)
12001202
git_config_backend *backend;
12011203
backend_internal *internal;
12021204

1205+
assert(cfg);
1206+
12031207
internal = git_vector_get(&cfg->backends, 0);
12041208
if (!internal || !internal->backend) {
12051209
giterr_set(GITERR_CONFIG, "cannot lock; the config has no backends");

src/remote.c

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -189,58 +189,119 @@ static int canonicalize_url(git_buf *out, const char *in)
189189
return git_buf_puts(out, in);
190190
}
191191

192-
static int create_internal(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
192+
static int default_fetchspec_for_name(git_buf *buf, const char *name)
193193
{
194+
if (git_buf_printf(buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
195+
return -1;
196+
197+
return 0;
198+
}
199+
200+
static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
201+
{
202+
int error;
194203
git_remote *remote;
204+
205+
error = git_remote_lookup(&remote, repo, name);
206+
207+
if (error == GIT_ENOTFOUND)
208+
return 0;
209+
210+
if (error < 0)
211+
return error;
212+
213+
git_remote_free(remote);
214+
215+
giterr_set(GITERR_CONFIG, "remote '%s' already exists", name);
216+
217+
return GIT_EEXISTS;
218+
}
219+
220+
int git_remote_create_init_options(git_remote_create_options *opts, unsigned int version)
221+
{
222+
GIT_INIT_STRUCTURE_FROM_TEMPLATE(
223+
opts, version, git_remote_create_options, GIT_REMOTE_CREATE_OPTIONS_INIT);
224+
return 0;
225+
}
226+
227+
int git_remote_create_with_opts(git_remote **out, const char *url, const git_remote_create_options *opts)
228+
{
229+
git_remote *remote = NULL;
195230
git_config *config_ro = NULL, *config_rw;
196231
git_buf canonical_url = GIT_BUF_INIT;
197232
git_buf var = GIT_BUF_INIT;
233+
git_buf specbuf = GIT_BUF_INIT;
234+
const git_remote_create_options dummy_opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
198235
int error = -1;
199236

200-
/* repo, name, and fetch are optional */
201237
assert(out && url);
202238

203-
if (repo && (error = git_repository_config_snapshot(&config_ro, repo)) < 0)
204-
return error;
239+
if (!opts) {
240+
opts = &dummy_opts;
241+
}
242+
243+
GITERR_CHECK_VERSION(opts, GIT_REMOTE_CREATE_OPTIONS_VERSION, "git_remote_create_options");
244+
245+
if (opts->name != NULL) {
246+
if ((error = ensure_remote_name_is_valid(opts->name)) < 0)
247+
return error;
248+
249+
if (opts->repository &&
250+
(error = ensure_remote_doesnot_exist(opts->repository, opts->name)) < 0)
251+
return error;
252+
}
253+
254+
if (opts->repository) {
255+
if ((error = git_repository_config_snapshot(&config_ro, opts->repository)) < 0)
256+
goto on_error;
257+
}
205258

206259
remote = git__calloc(1, sizeof(git_remote));
207260
GITERR_CHECK_ALLOC(remote);
208261

209-
remote->repo = repo;
262+
remote->repo = opts->repository;
210263

211-
if ((error = git_vector_init(&remote->refs, 32, NULL)) < 0 ||
264+
if ((error = git_vector_init(&remote->refs, 8, NULL)) < 0 ||
212265
(error = canonicalize_url(&canonical_url, url)) < 0)
213266
goto on_error;
214267

215-
if (repo) {
268+
if (opts->repository && !(opts->flags & GIT_REMOTE_CREATE_SKIP_INSTEADOF)) {
216269
remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
217270
} else {
218271
remote->url = git__strdup(canonical_url.ptr);
219272
}
220273
GITERR_CHECK_ALLOC(remote->url);
221274

222-
if (name != NULL) {
223-
remote->name = git__strdup(name);
275+
if (opts->name != NULL) {
276+
remote->name = git__strdup(opts->name);
224277
GITERR_CHECK_ALLOC(remote->name);
225278

226-
if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0)
227-
goto on_error;
228-
229-
if (repo &&
230-
((error = git_repository_config__weakptr(&config_rw, repo)) < 0 ||
231-
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0))
279+
if (opts->repository &&
280+
((error = git_buf_printf(&var, CONFIG_URL_FMT, opts->name)) < 0 ||
281+
(error = git_repository_config__weakptr(&config_rw, opts->repository)) < 0 ||
282+
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0))
232283
goto on_error;
233284
}
234285

235-
if (fetch != NULL) {
236-
if ((error = add_refspec(remote, fetch, true)) < 0)
237-
goto on_error;
286+
if (opts->fetchspec != NULL ||
287+
(opts->name && !(opts->flags & GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC))) {
288+
const char *fetch = NULL;
289+
if (opts->fetchspec) {
290+
fetch = opts->fetchspec;
291+
} else {
292+
if ((error = default_fetchspec_for_name(&specbuf, opts->name)) < 0)
293+
goto on_error;
294+
295+
fetch = git_buf_cstr(&specbuf);
296+
}
238297

239-
/* only write for non-anonymous remotes */
240-
if (repo && name && (error = write_add_refspec(repo, name, fetch, true)) < 0)
298+
if ((error = add_refspec(remote, fetch, true)) < 0)
241299
goto on_error;
242300

243-
if (repo && (error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
301+
/* only write for named remotes with a repository */
302+
if (opts->repository && opts->name &&
303+
((error = write_add_refspec(opts->repository, opts->name, fetch, true)) < 0 ||
304+
(error = lookup_remote_prune_config(remote, config_ro, opts->name)) < 0))
244305
goto on_error;
245306

246307
/* 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
249310
}
250311

251312
/* A remote without a name doesn't download tags */
252-
if (!name)
313+
if (!opts->name)
253314
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_NONE;
254315
else
255316
remote->download_tags = GIT_REMOTE_DOWNLOAD_TAGS_AUTO;
@@ -265,79 +326,65 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
265326
git_remote_free(remote);
266327

267328
git_config_free(config_ro);
329+
git_buf_dispose(&specbuf);
268330
git_buf_dispose(&canonical_url);
269331
git_buf_dispose(&var);
270332
return error;
271333
}
272334

273-
static int ensure_remote_doesnot_exist(git_repository *repo, const char *name)
335+
int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
274336
{
337+
git_buf buf = GIT_BUF_INIT;
275338
int error;
276-
git_remote *remote;
339+
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
277340

278-
error = git_remote_lookup(&remote, repo, name);
279-
280-
if (error == GIT_ENOTFOUND)
281-
return 0;
282-
283-
if (error < 0)
341+
/* Those 2 tests are duplicated here because of backward-compatibility */
342+
if ((error = ensure_remote_name_is_valid(name)) < 0)
284343
return error;
285344

286-
git_remote_free(remote);
287-
288-
giterr_set(
289-
GITERR_CONFIG,
290-
"remote '%s' already exists", name);
291-
292-
return GIT_EEXISTS;
293-
}
345+
if (canonicalize_url(&buf, url) < 0)
346+
return GIT_ERROR;
294347

348+
git_buf_clear(&buf);
295349

296-
int git_remote_create(git_remote **out, git_repository *repo, const char *name, const char *url)
297-
{
298-
git_buf buf = GIT_BUF_INIT;
299-
int error;
350+
opts.repository = repo;
351+
opts.name = name;
300352

301-
if (git_buf_printf(&buf, "+refs/heads/*:refs/remotes/%s/*", name) < 0)
302-
return -1;
353+
error = git_remote_create_with_opts(out, url, &opts);
303354

304-
error = git_remote_create_with_fetchspec(out, repo, name, url, git_buf_cstr(&buf));
305355
git_buf_dispose(&buf);
306356

307357
return error;
308358
}
309359

310360
int git_remote_create_with_fetchspec(git_remote **out, git_repository *repo, const char *name, const char *url, const char *fetch)
311361
{
312-
git_remote *remote = NULL;
313362
int error;
363+
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
314364

315365
if ((error = ensure_remote_name_is_valid(name)) < 0)
316366
return error;
317367

318-
if ((error = ensure_remote_doesnot_exist(repo, name)) < 0)
319-
return error;
368+
opts.repository = repo;
369+
opts.name = name;
370+
opts.fetchspec = fetch;
371+
opts.flags = GIT_REMOTE_CREATE_SKIP_DEFAULT_FETCHSPEC;
320372

321-
if (create_internal(&remote, repo, name, url, fetch) < 0)
322-
goto on_error;
323-
324-
*out = remote;
325-
326-
return 0;
327-
328-
on_error:
329-
git_remote_free(remote);
330-
return -1;
373+
return git_remote_create_with_opts(out, url, &opts);
331374
}
332375

333376
int git_remote_create_anonymous(git_remote **out, git_repository *repo, const char *url)
334377
{
335-
return create_internal(out, repo, NULL, url, NULL);
378+
git_remote_create_options opts = GIT_REMOTE_CREATE_OPTIONS_INIT;
379+
380+
opts.repository = repo;
381+
382+
return git_remote_create_with_opts(out, url, &opts);
336383
}
337384

338385
int git_remote_create_detached(git_remote **out, const char *url)
339386
{
340-
return create_internal(out, NULL, NULL, url, NULL);
387+
return git_remote_create_with_opts(out, url, NULL);
341388
}
342389

343390
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
19461993
if ((error = git_vector_init(problems, 1, NULL)) < 0)
19471994
return error;
19481995

1949-
if ((error = git_buf_printf(
1950-
&base, "+refs/heads/*:refs/remotes/%s/*", remote->name)) < 0)
1996+
if ((error = default_fetchspec_for_name(&base, remote->name)) < 0)
19511997
return error;
19521998

19531999
git_vector_foreach(&remote->refspecs, i, spec) {
@@ -1972,8 +2018,7 @@ static int rename_fetch_refspecs(git_vector *problems, git_remote *remote, const
19722018
git_buf_clear(&val);
19732019
git_buf_clear(&var);
19742020

1975-
if (git_buf_printf(
1976-
&val, "+refs/heads/*:refs/remotes/%s/*", new_name) < 0 ||
2021+
if (default_fetchspec_for_name(&val, new_name) < 0 ||
19772022
git_buf_printf(&var, "remote.%s.fetch", new_name) < 0)
19782023
{
19792024
error = -1;

0 commit comments

Comments
 (0)