Skip to content

Commit 59647e1

Browse files
Erik Aignertiennou
authored andcommitted
remote: add callback to resolve URLs before connecting
Since libssh2 doesn't read host configuration from the config file, this callback can be used to hand over URL resolving to the client without touching the SSH implementation itself.
1 parent 4aa36ff commit 59647e1

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)