Skip to content

Commit 954f535

Browse files
authored
Merge pull request libgit2#5062 from tiennou/fix/ssh-url-substitution
Remote URL last-chance resolution
2 parents 4aa36ff + 59647e1 commit 954f535

File tree

5 files changed

+142
-31
lines changed

5 files changed

+142
-31
lines changed

include/git2/remote.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,20 @@ typedef int GIT_CALLBACK(git_push_negotiation)(const git_push_update **updates,
474474
*/
475475
typedef int GIT_CALLBACK(git_push_update_reference_cb)(const char *refname, const char *status, void *data);
476476

477+
/**
478+
* Callback to resolve URLs before connecting to remote
479+
*
480+
* If you return GIT_PASSTHROUGH, you don't need to write anything to
481+
* url_resolved.
482+
*
483+
* @param url_resolved The buffer to write the resolved URL to
484+
* @param url The URL to resolve
485+
* @param direction GIT_DIRECTION_FETCH or GIT_DIRECTION_PUSH
486+
* @param payload Payload provided by the caller
487+
* @return 0 on success, GIT_PASSTHROUGH or an error
488+
*/
489+
typedef int GIT_CALLBACK(git_url_resolve_cb)(git_buf *url_resolved, const char *url, int direction, void *payload);
490+
477491
/**
478492
* The callback settings structure
479493
*
@@ -562,6 +576,12 @@ struct git_remote_callbacks {
562576
* as the last parameter.
563577
*/
564578
void *payload;
579+
580+
/**
581+
* Resolve URL before connecting to remote.
582+
* The returned URL will be used to connect to the remote instead.
583+
*/
584+
git_url_resolve_cb resolve_url;
565585
};
566586

567587
#define GIT_REMOTE_CALLBACKS_VERSION 1

src/remote.c

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -670,21 +670,44 @@ int git_remote_set_pushurl(git_repository *repo, const char *remote, const char*
670670
return set_url(repo, remote, CONFIG_PUSHURL_FMT, url);
671671
}
672672

673-
const char* git_remote__urlfordirection(git_remote *remote, int direction)
673+
static int resolve_url(git_buf *resolved_url, const char *url, int direction, const git_remote_callbacks *callbacks)
674674
{
675-
assert(remote);
675+
int status;
676+
677+
if (callbacks && callbacks->resolve_url) {
678+
git_buf_clear(resolved_url);
679+
status = callbacks->resolve_url(resolved_url, url, direction, callbacks->payload);
680+
if (status != GIT_PASSTHROUGH) {
681+
git_error_set_after_callback_function(status, "git_resolve_url_cb");
682+
git_buf_sanitize(resolved_url);
683+
return status;
684+
}
685+
}
676686

687+
return git_buf_sets(resolved_url, url);
688+
}
689+
690+
int git_remote__urlfordirection(git_buf *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks)
691+
{
692+
const char *url = NULL;
693+
694+
assert(remote);
677695
assert(direction == GIT_DIRECTION_FETCH || direction == GIT_DIRECTION_PUSH);
678696

679697
if (direction == GIT_DIRECTION_FETCH) {
680-
return remote->url;
698+
url = remote->url;
699+
} else if (direction == GIT_DIRECTION_PUSH) {
700+
url = remote->pushurl ? remote->pushurl : remote->url;
681701
}
682702

683-
if (direction == GIT_DIRECTION_PUSH) {
684-
return remote->pushurl ? remote->pushurl : remote->url;
703+
if (!url) {
704+
git_error_set(GIT_ERROR_INVALID,
705+
"malformed remote '%s' - missing %s URL",
706+
remote->name ? remote->name : "(anonymous)",
707+
direction == GIT_DIRECTION_FETCH ? "fetch" : "push");
708+
return GIT_EINVALID;
685709
}
686-
687-
return NULL;
710+
return resolve_url(url_out, url, direction, callbacks);
688711
}
689712

690713
int set_transport_callbacks(git_transport *t, const git_remote_callbacks *cbs)
@@ -707,7 +730,7 @@ static int set_transport_custom_headers(git_transport *t, const git_strarray *cu
707730
int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn)
708731
{
709732
git_transport *t;
710-
const char *url;
733+
git_buf url = GIT_BUF_INIT;
711734
int flags = GIT_TRANSPORTFLAGS_NONE;
712735
int error;
713736
void *payload = NULL;
@@ -728,39 +751,38 @@ int git_remote__connect(git_remote *remote, git_direction direction, const git_r
728751

729752
t = remote->transport;
730753

731-
url = git_remote__urlfordirection(remote, direction);
732-
if (url == NULL) {
733-
git_error_set(GIT_ERROR_INVALID,
734-
"Malformed remote '%s' - missing %s URL",
735-
remote->name ? remote->name : "(anonymous)",
736-
direction == GIT_DIRECTION_FETCH ? "fetch" : "push");
737-
return -1;
738-
}
754+
if ((error = git_remote__urlfordirection(&url, remote, direction, callbacks)) < 0)
755+
goto on_error;
739756

740757
/* If we don't have a transport object yet, and the caller specified a
741758
* custom transport factory, use that */
742759
if (!t && transport &&
743760
(error = transport(&t, remote, payload)) < 0)
744-
return error;
761+
goto on_error;
745762

746763
/* If we still don't have a transport, then use the global
747764
* transport registrations which map URI schemes to transport factories */
748-
if (!t && (error = git_transport_new(&t, remote, url)) < 0)
749-
return error;
765+
if (!t && (error = git_transport_new(&t, remote, url.ptr)) < 0)
766+
goto on_error;
750767

751768
if ((error = set_transport_custom_headers(t, conn->custom_headers)) != 0)
752769
goto on_error;
753770

754771
if ((error = set_transport_callbacks(t, callbacks)) < 0 ||
755-
(error = t->connect(t, url, credentials, payload, conn->proxy, direction, flags)) != 0)
772+
(error = t->connect(t, url.ptr, credentials, payload, conn->proxy, direction, flags)) != 0)
756773
goto on_error;
757774

758775
remote->transport = t;
759776

777+
git_buf_dispose(&url);
778+
760779
return 0;
761780

762781
on_error:
763-
t->free(t);
782+
if (t)
783+
t->free(t);
784+
785+
git_buf_dispose(&url);
764786

765787
if (t == remote->transport)
766788
remote->transport = NULL;

src/remote.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ typedef struct git_remote_connection_opts {
4545

4646
int git_remote__connect(git_remote *remote, git_direction direction, const git_remote_callbacks *callbacks, const git_remote_connection_opts *conn);
4747

48-
const char* git_remote__urlfordirection(struct git_remote *remote, int direction);
48+
int git_remote__urlfordirection(git_buf *url_out, struct git_remote *remote, int direction, const git_remote_callbacks *callbacks);
4949
int git_remote__get_http_proxy(git_remote *remote, bool use_ssl, char **proxy_url);
5050

5151
git_refspec *git_remote__matching_refspec(git_remote *remote, const char *refname);

tests/network/remote/remotes.c

Lines changed: 77 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,28 +28,97 @@ void test_network_remote_remotes__cleanup(void)
2828

2929
void test_network_remote_remotes__parsing(void)
3030
{
31+
git_buf url = GIT_BUF_INIT;
3132
git_remote *_remote2 = NULL;
3233

3334
cl_assert_equal_s(git_remote_name(_remote), "test");
3435
cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
3536
cl_assert(git_remote_pushurl(_remote) == NULL);
3637

37-
cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_FETCH),
38-
"git://github.com/libgit2/libgit2");
39-
cl_assert_equal_s(git_remote__urlfordirection(_remote, GIT_DIRECTION_PUSH),
40-
"git://github.com/libgit2/libgit2");
38+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, NULL));
39+
cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2");
40+
41+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, NULL));
42+
cl_assert_equal_s(url.ptr, "git://github.com/libgit2/libgit2");
4143

4244
cl_git_pass(git_remote_lookup(&_remote2, _repo, "test_with_pushurl"));
4345
cl_assert_equal_s(git_remote_name(_remote2), "test_with_pushurl");
4446
cl_assert_equal_s(git_remote_url(_remote2), "git://github.com/libgit2/fetchlibgit2");
4547
cl_assert_equal_s(git_remote_pushurl(_remote2), "git://github.com/libgit2/pushlibgit2");
4648

47-
cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_FETCH),
48-
"git://github.com/libgit2/fetchlibgit2");
49-
cl_assert_equal_s(git_remote__urlfordirection(_remote2, GIT_DIRECTION_PUSH),
50-
"git://github.com/libgit2/pushlibgit2");
49+
cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_FETCH, NULL));
50+
cl_assert_equal_s(url.ptr, "git://github.com/libgit2/fetchlibgit2");
51+
52+
cl_git_pass(git_remote__urlfordirection(&url, _remote2, GIT_DIRECTION_PUSH, NULL));
53+
cl_assert_equal_s(url.ptr, "git://github.com/libgit2/pushlibgit2");
5154

5255
git_remote_free(_remote2);
56+
git_buf_dispose(&url);
57+
}
58+
59+
static int urlresolve_callback(git_buf *url_resolved, const char *url, int direction, void *payload)
60+
{
61+
cl_assert(strcmp(url, "git://github.com/libgit2/libgit2") == 0);
62+
cl_assert(strcmp(payload, "payload") == 0);
63+
cl_assert(url_resolved->size == 0);
64+
65+
if (direction == GIT_DIRECTION_PUSH)
66+
git_buf_sets(url_resolved, "pushresolve");
67+
if (direction == GIT_DIRECTION_FETCH)
68+
git_buf_sets(url_resolved, "fetchresolve");
69+
70+
return GIT_OK;
71+
}
72+
73+
void test_network_remote_remotes__urlresolve(void)
74+
{
75+
git_buf url = GIT_BUF_INIT;
76+
77+
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
78+
callbacks.resolve_url = urlresolve_callback;
79+
callbacks.payload = "payload";
80+
81+
cl_assert_equal_s(git_remote_name(_remote), "test");
82+
cl_assert_equal_s(git_remote_url(_remote), "git://github.com/libgit2/libgit2");
83+
cl_assert(git_remote_pushurl(_remote) == NULL);
84+
85+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks));
86+
cl_assert_equal_s(url.ptr, "fetchresolve");
87+
88+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks));
89+
cl_assert_equal_s(url.ptr, "pushresolve");
90+
91+
git_buf_dispose(&url);
92+
}
93+
94+
static int urlresolve_passthrough_callback(git_buf *url_resolved, const char *url, int direction, void *payload)
95+
{
96+
GIT_UNUSED(url_resolved);
97+
GIT_UNUSED(url);
98+
GIT_UNUSED(direction);
99+
GIT_UNUSED(payload);
100+
return GIT_PASSTHROUGH;
101+
}
102+
103+
void test_network_remote_remotes__urlresolve_passthrough(void)
104+
{
105+
git_buf url = GIT_BUF_INIT;
106+
const char *orig_url = "git://github.com/libgit2/libgit2";
107+
108+
git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
109+
callbacks.resolve_url = urlresolve_passthrough_callback;
110+
111+
cl_assert_equal_s(git_remote_name(_remote), "test");
112+
cl_assert_equal_s(git_remote_url(_remote), orig_url);
113+
cl_assert(git_remote_pushurl(_remote) == NULL);
114+
115+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_FETCH, &callbacks));
116+
cl_assert_equal_s(url.ptr, orig_url);
117+
118+
cl_git_pass(git_remote__urlfordirection(&url, _remote, GIT_DIRECTION_PUSH, &callbacks));
119+
cl_assert_equal_s(url.ptr, orig_url);
120+
121+
git_buf_dispose(&url);
53122
}
54123

55124
void test_network_remote_remotes__pushurl(void)

tests/online/push_util.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ extern const git_oid OID_ZERO;
1212
* @param data pointer to a record_callbacks_data instance
1313
*/
1414
#define RECORD_CALLBACKS_INIT(data) \
15-
{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, data }
15+
{ GIT_REMOTE_CALLBACKS_VERSION, NULL, NULL, cred_acquire_cb, NULL, NULL, record_update_tips_cb, NULL, NULL, NULL, NULL, NULL, data, NULL }
1616

1717
typedef struct {
1818
char *name;

0 commit comments

Comments
 (0)