Skip to content

Commit fb585d0

Browse files
committed
Merge branch '4233'
2 parents d55431e + 868ce84 commit fb585d0

File tree

5 files changed

+137
-13
lines changed

5 files changed

+137
-13
lines changed

AUTHORS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Dmitry Kakurin
2222
Dmitry Kovega
2323
Emeric Fermas
2424
Emmanuel Rodriguez
25+
Eric Myhre
2526
Florian Forster
2627
Holger Weiss
2728
Ingmar Vanhassel

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,10 @@ v0.26 + 1
88

99
### API additions
1010

11+
* `git_remote_create_detached()` creates a remote that is not associated
12+
to any repository (and does not apply configuration like 'insteadof' rules).
13+
This is mostly useful for e.g. emulating `git ls-remote` behavior.
14+
1115
### API removals
1216

1317
### Breaking API changes

include/git2/remote.h

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,24 @@ GIT_EXTERN(int) git_remote_create_anonymous(
7575
git_repository *repo,
7676
const char *url);
7777

78+
/**
79+
* Create a remote without a connected local repo
80+
*
81+
* Create a remote with the given url in-memory. You can use this when
82+
* you have a URL instead of a remote's name.
83+
*
84+
* Contrasted with git_remote_create_anonymous, a detached remote
85+
* will not consider any repo configuration values (such as insteadof url
86+
* substitutions).
87+
*
88+
* @param out pointer to the new remote objects
89+
* @param url the remote repository's URL
90+
* @return 0 or an error code
91+
*/
92+
GIT_EXTERN(int) git_remote_create_detached(
93+
git_remote **out,
94+
const char *url);
95+
7896
/**
7997
* Get the information for a particular remote
8098
*

src/remote.c

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -197,10 +197,10 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
197197
git_buf var = GIT_BUF_INIT;
198198
int error = -1;
199199

200-
/* name is optional */
201-
assert(out && repo && url);
200+
/* repo, name, and fetch are optional */
201+
assert(out && url);
202202

203-
if ((error = git_repository_config_snapshot(&config_ro, repo)) < 0)
203+
if (repo && (error = git_repository_config_snapshot(&config_ro, repo)) < 0)
204204
return error;
205205

206206
remote = git__calloc(1, sizeof(git_remote));
@@ -212,7 +212,11 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
212212
(error = canonicalize_url(&canonical_url, url)) < 0)
213213
goto on_error;
214214

215-
remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
215+
if (repo) {
216+
remote->url = apply_insteadof(config_ro, canonical_url.ptr, GIT_DIRECTION_FETCH);
217+
} else {
218+
remote->url = git__strdup(canonical_url.ptr);
219+
}
216220
GITERR_CHECK_ALLOC(remote->url);
217221

218222
if (name != NULL) {
@@ -222,8 +226,9 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
222226
if ((error = git_buf_printf(&var, CONFIG_URL_FMT, name)) < 0)
223227
goto on_error;
224228

225-
if ((error = git_repository_config__weakptr(&config_rw, repo)) < 0 ||
226-
(error = git_config_set_string(config_rw, var.ptr, canonical_url.ptr)) < 0)
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))
227232
goto on_error;
228233
}
229234

@@ -235,7 +240,7 @@ static int create_internal(git_remote **out, git_repository *repo, const char *n
235240
if (name && (error = write_add_refspec(repo, name, fetch, true)) < 0)
236241
goto on_error;
237242

238-
if ((error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
243+
if (repo && (error = lookup_remote_prune_config(remote, config_ro, name)) < 0)
239244
goto on_error;
240245

241246
/* Move the data over to where the matching functions can find them */
@@ -330,6 +335,11 @@ int git_remote_create_anonymous(git_remote **out, git_repository *repo, const ch
330335
return create_internal(out, repo, NULL, url, NULL);
331336
}
332337

338+
int git_remote_create_detached(git_remote **out, const char *url)
339+
{
340+
return create_internal(out, NULL, NULL, url, NULL);
341+
}
342+
333343
int git_remote_dup(git_remote **dest, git_remote *source)
334344
{
335345
size_t i;
@@ -674,7 +684,9 @@ int git_remote_connect(git_remote *remote, git_direction direction, const git_re
674684
url = git_remote__urlfordirection(remote, direction);
675685
if (url == NULL) {
676686
giterr_set(GITERR_INVALID,
677-
"Malformed remote '%s' - missing URL", remote->name);
687+
"Malformed remote '%s' - missing %s URL",
688+
remote->name ? remote->name : "(anonymous)",
689+
direction == GIT_DIRECTION_FETCH ? "fetch" : "push");
678690
return -1;
679691
}
680692

@@ -859,6 +871,11 @@ int git_remote_download(git_remote *remote, const git_strarray *refspecs, const
859871

860872
assert(remote);
861873

874+
if (!remote->repo) {
875+
giterr_set(GITERR_INVALID, "cannot download detached remote");
876+
return -1;
877+
}
878+
862879
if (opts) {
863880
GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
864881
cbs = &opts->callbacks;
@@ -2344,6 +2361,11 @@ int git_remote_upload(git_remote *remote, const git_strarray *refspecs, const gi
23442361

23452362
assert(remote);
23462363

2364+
if (!remote->repo) {
2365+
giterr_set(GITERR_INVALID, "cannot download detached remote");
2366+
return -1;
2367+
}
2368+
23472369
if (opts) {
23482370
cbs = &opts->callbacks;
23492371
custom_headers = &opts->custom_headers;
@@ -2403,6 +2425,13 @@ int git_remote_push(git_remote *remote, const git_strarray *refspecs, const git_
24032425
const git_strarray *custom_headers = NULL;
24042426
const git_proxy_options *proxy = NULL;
24052427

2428+
assert(remote);
2429+
2430+
if (!remote->repo) {
2431+
giterr_set(GITERR_INVALID, "cannot download detached remote");
2432+
return -1;
2433+
}
2434+
24062435
if (opts) {
24072436
GITERR_CHECK_VERSION(&opts->callbacks, GIT_REMOTE_CALLBACKS_VERSION, "git_remote_callbacks");
24082437
cbs = &opts->callbacks;

tests/online/remotes.c

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
#include "clar_libgit2.h"
22

3-
static const char *refspec = "refs/heads/first-merge:refs/remotes/origin/first-merge";
3+
#define URL "git://github.com/libgit2/TestGitRepository"
4+
#define REFSPEC "refs/heads/first-merge:refs/remotes/origin/first-merge"
45

56
static int remote_single_branch(git_remote **out, git_repository *repo, const char *name, const char *url, void *payload)
67
{
78
GIT_UNUSED(payload);
89

9-
cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, refspec));
10+
cl_git_pass(git_remote_create_with_fetchspec(out, repo, name, url, REFSPEC));
1011

1112
return 0;
1213
}
@@ -22,7 +23,7 @@ void test_online_remotes__single_branch(void)
2223
opts.remote_cb = remote_single_branch;
2324
opts.checkout_branch = "first-merge";
2425

25-
cl_git_pass(git_clone(&repo, "git://github.com/libgit2/TestGitRepository", "./single-branch", &opts));
26+
cl_git_pass(git_clone(&repo, URL, "./single-branch", &opts));
2627
cl_git_pass(git_reference_list(&refs, repo));
2728

2829
for (i = 0; i < refs.count; i++) {
@@ -37,7 +38,7 @@ void test_online_remotes__single_branch(void)
3738
cl_git_pass(git_remote_get_fetch_refspecs(&refs, remote));
3839

3940
cl_assert_equal_i(1, refs.count);
40-
cl_assert_equal_s(refspec, refs.strings[0]);
41+
cl_assert_equal_s(REFSPEC, refs.strings[0]);
4142

4243
git_strarray_free(&refs);
4344
git_remote_free(remote);
@@ -51,5 +52,76 @@ void test_online_remotes__restricted_refspecs(void)
5152

5253
opts.remote_cb = remote_single_branch;
5354

54-
cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, "git://github.com/libgit2/TestGitRepository", "./restrict-refspec", &opts));
55+
cl_git_fail_with(GIT_EINVALIDSPEC, git_clone(&repo, URL, "./restrict-refspec", &opts));
56+
}
57+
58+
void test_online_remotes__detached_remote_fails_downloading(void)
59+
{
60+
git_remote *remote;
61+
62+
cl_git_pass(git_remote_create_detached(&remote, URL));
63+
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
64+
cl_git_fail(git_remote_download(remote, NULL, NULL));
65+
66+
git_remote_free(remote);
67+
}
68+
69+
void test_online_remotes__detached_remote_fails_uploading(void)
70+
{
71+
git_remote *remote;
72+
73+
cl_git_pass(git_remote_create_detached(&remote, URL));
74+
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
75+
cl_git_fail(git_remote_upload(remote, NULL, NULL));
76+
77+
git_remote_free(remote);
78+
}
79+
80+
void test_online_remotes__detached_remote_fails_pushing(void)
81+
{
82+
git_remote *remote;
83+
84+
cl_git_pass(git_remote_create_detached(&remote, URL));
85+
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
86+
cl_git_fail(git_remote_push(remote, NULL, NULL));
87+
88+
git_remote_free(remote);
89+
}
90+
91+
void test_online_remotes__detached_remote_succeeds_ls(void)
92+
{
93+
const char *refs[] = {
94+
"HEAD",
95+
"refs/heads/first-merge",
96+
"refs/heads/master",
97+
"refs/heads/no-parent",
98+
"refs/tags/annotated_tag",
99+
"refs/tags/annotated_tag^{}",
100+
"refs/tags/blob",
101+
"refs/tags/commit_tree",
102+
"refs/tags/nearly-dangling",
103+
};
104+
const git_remote_head **heads;
105+
git_remote *remote;
106+
size_t i, j, n;
107+
108+
cl_git_pass(git_remote_create_detached(&remote, URL));
109+
cl_git_pass(git_remote_connect(remote, GIT_DIRECTION_FETCH, NULL, NULL, NULL));
110+
cl_git_pass(git_remote_ls(&heads, &n, remote));
111+
112+
cl_assert_equal_sz(n, 9);
113+
for (i = 0; i < n; i++) {
114+
char found = false;
115+
116+
for (j = 0; j < ARRAY_SIZE(refs); j++) {
117+
if (!strcmp(heads[i]->name, refs[j])) {
118+
found = true;
119+
break;
120+
}
121+
}
122+
123+
cl_assert_(found, heads[i]->name);
124+
}
125+
126+
git_remote_free(remote);
55127
}

0 commit comments

Comments
 (0)