Commit 7eb7edd4a19fb64cc2ff53d21e4d2c93a1197a2b

Edward Thomson 2022-06-12T10:51:13

Merge pull request #6278 from lhchavez/git_transport_smart_remote_connect_options transport: introduce `git_transport_smart_remote_connect_options`

diff --git a/include/git2/sys/remote.h b/include/git2/sys/remote.h
index dd243ca..0eae923 100644
--- a/include/git2/sys/remote.h
+++ b/include/git2/sys/remote.h
@@ -8,6 +8,8 @@
 #ifndef INCLUDE_sys_git_remote_h
 #define INCLUDE_sys_git_remote_h
 
+#include "git2/remote.h"
+
 /**
  * @file git2/sys/remote.h
  * @brief Low-level remote functionality for custom transports
@@ -26,6 +28,19 @@ typedef enum {
 	GIT_REMOTE_CAPABILITY_REACHABLE_OID = (1 << 1),
 } git_remote_capability_t;
 
+/**
+ * Disposes libgit2-initialized fields from a git_remote_connect_options.
+ * This should only be used for git_remote_connect_options returned by
+ * git_transport_remote_connect_options.
+ *
+ * Note that this does not free the `git_remote_connect_options` itself, just
+ * the memory pointed to by it.
+ *
+ * @param opts The `git_remote_connect_options` struct to dispose.
+ */
+GIT_EXTERN(void) git_remote_connect_options_dispose(
+		git_remote_connect_options *opts);
+
 /** @} */
 GIT_END_DECL
 #endif
diff --git a/include/git2/sys/transport.h b/include/git2/sys/transport.h
index 8b2e927..06ae707 100644
--- a/include/git2/sys/transport.h
+++ b/include/git2/sys/transport.h
@@ -9,11 +9,11 @@
 #define INCLUDE_sys_git_transport_h
 
 #include "git2/net.h"
+#include "git2/proxy.h"
 #include "git2/remote.h"
+#include "git2/strarray.h"
 #include "git2/transport.h"
 #include "git2/types.h"
-#include "git2/strarray.h"
-#include "git2/proxy.h"
 
 /**
  * @file git2/sys/transport.h
@@ -261,6 +261,19 @@ GIT_EXTERN(int) git_transport_smart_certificate_check(git_transport *transport, 
  */
 GIT_EXTERN(int) git_transport_smart_credentials(git_credential **out, git_transport *transport, const char *user, int methods);
 
+/**
+ * Get a copy of the remote connect options
+ *
+ * All data is copied and must be freed by the caller by calling
+ * `git_remote_connect_options_dispose`.
+ *
+ * @param out options struct to fill
+ * @param transport the transport to extract the data from.
+ */
+GIT_EXTERN(int) git_transport_remote_connect_options(
+		git_remote_connect_options *out,
+		git_transport *transport);
+
 /*
  *** End of base transport interface ***
  *** Begin interface for subtransports for the smart transport ***
diff --git a/src/libgit2/remote.h b/src/libgit2/remote.h
index ea9c7d1..41ee58e 100644
--- a/src/libgit2/remote.h
+++ b/src/libgit2/remote.h
@@ -11,6 +11,7 @@
 
 #include "git2/remote.h"
 #include "git2/transport.h"
+#include "git2/sys/remote.h"
 #include "git2/sys/transport.h"
 
 #include "refspec.h"
@@ -53,7 +54,6 @@ int git_remote_connect_options_normalize(
 	git_remote_connect_options *dst,
 	git_repository *repo,
 	const git_remote_connect_options *src);
-void git_remote_connect_options_dispose(git_remote_connect_options *opts);
 
 int git_remote_capabilities(unsigned int *out, git_remote *remote);
 
diff --git a/src/libgit2/transports/smart.c b/src/libgit2/transports/smart.c
index 801fcbe..7f57dba 100644
--- a/src/libgit2/transports/smart.c
+++ b/src/libgit2/transports/smart.c
@@ -425,6 +425,18 @@ int git_transport_smart_credentials(git_credential **out, git_transport *transpo
 	return connect_opts->callbacks.credentials(out, t->url, user, methods, connect_opts->callbacks.payload);
 }
 
+int git_transport_remote_connect_options(
+		git_remote_connect_options *out,
+		git_transport *transport)
+{
+	transport_smart *t = GIT_CONTAINER_OF(transport, transport_smart, parent);
+
+	GIT_ASSERT_ARG(out);
+	GIT_ASSERT_ARG(transport);
+
+	return git_remote_connect_options_dup(out, &t->connect_opts);
+}
+
 int git_transport_smart(git_transport **out, git_remote *owner, void *param)
 {
 	transport_smart *t;
diff --git a/tests/libgit2/transport/register.c b/tests/libgit2/transport/register.c
index 88ba247..4d3c097 100644
--- a/tests/libgit2/transport/register.c
+++ b/tests/libgit2/transport/register.c
@@ -1,6 +1,8 @@
 #include "clar_libgit2.h"
+#include "git2/sys/remote.h"
 #include "git2/sys/transport.h"
 
+static const char *proxy_url = "https://proxy";
 static git_transport _transport = GIT_TRANSPORT_INIT;
 
 static int dummy_transport(git_transport **transport, git_remote *owner, void *param)
@@ -77,3 +79,146 @@ void test_transport_register__custom_transport_ssh(void)
 #endif
 	}
 }
+
+static int custom_subtransport_stream__read(
+		git_smart_subtransport_stream *stream,
+		char *buffer,
+		size_t buf_size,
+		size_t *bytes_read)
+{
+	GIT_UNUSED(stream);
+	GIT_UNUSED(buffer);
+	GIT_UNUSED(buf_size);
+
+	*bytes_read = 0;
+
+	git_error_set_str(42, "unimplemented");
+	return GIT_EUSER;
+}
+
+static int custom_subtransport_stream__write(
+		git_smart_subtransport_stream *stream,
+		const char *buffer,
+		size_t len)
+{
+	GIT_UNUSED(stream);
+	GIT_UNUSED(buffer);
+	GIT_UNUSED(len);
+
+	git_error_set_str(42, "unimplemented");
+	return GIT_EUSER;
+}
+
+static void custom_subtransport_stream__free(
+		git_smart_subtransport_stream *stream)
+{
+	git__free(stream);
+}
+
+struct custom_subtransport {
+	git_smart_subtransport subtransport;
+	git_transport *owner;
+	int *called;
+};
+
+static int custom_subtransport__action(
+		git_smart_subtransport_stream **out,
+		git_smart_subtransport *transport,
+		const char *url,
+		git_smart_service_t action)
+{
+	struct custom_subtransport *t = (struct custom_subtransport *)transport;
+	git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT;
+	int ret;
+
+	GIT_UNUSED(url);
+	GIT_UNUSED(action);
+
+	ret = git_transport_remote_connect_options(&opts, t->owner);
+
+	/* increase the counter once if this function was called at all and once more if the URL matches. */
+	(*t->called)++;
+	if (strcmp(proxy_url, opts.proxy_opts.url) == 0)
+		(*t->called)++;
+
+	git_remote_connect_options_dispose(&opts);
+
+	*out = git__calloc(1, sizeof(git_smart_subtransport_stream));
+	(*out)->subtransport = transport;
+	(*out)->read = custom_subtransport_stream__read;
+	(*out)->write = custom_subtransport_stream__write;
+	(*out)->free = custom_subtransport_stream__free;
+
+	return ret;
+}
+
+static int custom_subtransport__close(git_smart_subtransport *transport)
+{
+	GIT_UNUSED(transport);
+
+	return 0;
+}
+
+static void custom_subtransport__free(git_smart_subtransport *transport)
+{
+	GIT_UNUSED(transport);
+
+	git__free(transport);
+}
+
+static int custom_transport_callback(git_smart_subtransport **out, git_transport *owner, void *param)
+{
+	struct custom_subtransport *subtransport = git__calloc(1, sizeof(struct custom_subtransport));
+	subtransport->called = (int *)param;
+	subtransport->owner = owner;
+	subtransport->subtransport.action = custom_subtransport__action;
+	subtransport->subtransport.close = custom_subtransport__close;
+	subtransport->subtransport.free = custom_subtransport__free;
+
+	*out = &subtransport->subtransport;
+
+	return 0;
+}
+
+
+static int custom_transport(git_transport **out, git_remote *owner, void *param)
+{
+	struct git_smart_subtransport_definition definition;
+	definition.callback = custom_transport_callback;
+	definition.rpc = false;
+	definition.param = param;
+	return git_transport_smart(out, owner, &definition);
+}
+
+void test_transport_register__custom_transport_callbacks(void)
+{
+	git_transport *transport;
+	int called = 0;
+	const char *url = "custom://somepath";
+	git_remote_connect_options opts = GIT_REMOTE_CONNECT_OPTIONS_INIT;
+	git_repository *repo;
+	git_remote *remote;
+
+	cl_git_pass(git_repository_init(&repo, "./transport", 0));
+	cl_git_pass(git_remote_create(&remote, repo, "test",
+		cl_fixture("testrepo.git")));
+
+	cl_git_pass(git_transport_register("custom", custom_transport, &called));
+
+	cl_git_pass(git_transport_new(&transport, remote, url));
+
+	opts.follow_redirects = GIT_REMOTE_REDIRECT_NONE;
+	opts.proxy_opts.url = proxy_url;
+
+	/* This is expected to fail, since the subtransport_stream is not implemented */
+	transport->connect(transport, url, GIT_SERVICE_UPLOADPACK_LS, &opts);
+	/* the counter is increased twice if everything goes as planned */
+	cl_assert_equal_i(2, called);
+	cl_git_pass(transport->close(transport));
+	transport->free(transport);
+
+	cl_git_pass(git_transport_unregister("custom"));
+	git_remote_free(remote);
+	git_repository_free(repo);
+	cl_fixture_cleanup("testrepo.git");
+}