Commit 44cfb6f387555722e84b0997ecff4718b40dd23f

Vicent Marti 2014-07-11T16:49:23

Merge pull request #2463 from libgit2/cmn/ssh-factory-for-paths ssh: provide a factory function for setting ssh paths

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e8e14a8..d389b2c 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -15,6 +15,10 @@ v0.21 + 1
 * The git_remote_set_transport function now sets a transport factory function,
   rather than a pre-existing transport instance.
 
+* A factory function for ssh has been added which allows to change the
+  path of the programs to execute for receive-pack and upload-pack on
+  the server, git_transport_ssh_with_paths.
+
 * The git_clone_options struct no longer provides the ignore_cert_errors or
   remote_name members for remote customization.
 
diff --git a/include/git2/transport.h b/include/git2/transport.h
index 1df264e..67939a7 100644
--- a/include/git2/transport.h
+++ b/include/git2/transport.h
@@ -336,6 +336,22 @@ GIT_EXTERN(int) git_transport_init(
  */
 GIT_EXTERN(int) git_transport_new(git_transport **out, git_remote *owner, const char *url);
 
+/**
+ * Create an ssh transport with custom git command paths
+ *
+ * This is a factory function suitable for setting as the transport
+ * callback in a remote (or for a clone in the options).
+ *
+ * The payload argument must be a strarray pointer with the paths for
+ * the `git-upload-pack` and `git-receive-pack` at index 0 and 1.
+ *
+ * @param out the resulting transport
+ * @param owner the owning remote
+ * @param payload a strarray with the paths
+ * @return 0 or an error code
+ */
+GIT_EXTERN(int) git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload);
+
 /* Signature of a function which creates a transport */
 typedef int (*git_transport_cb)(git_transport **out, git_remote *owner, void *param);
 
diff --git a/script/cibuild.sh b/script/cibuild.sh
index 699404b..5ba0746 100755
--- a/script/cibuild.sh
+++ b/script/cibuild.sh
@@ -34,5 +34,8 @@ export GITTEST_REMOTE_SSH_PUBKEY="$HOME/.ssh/id_rsa.pub"
 export GITTEST_REMOTE_SSH_PASSPHRASE=""
 
 if [ -e ./libgit2_clar ]; then
-    ./libgit2_clar -sonline::push -sonline::clone::cred_callback_failure
+    ./libgit2_clar -sonline::push -sonline::clone::cred_callback_failure &&
+    rm -rf $HOME/_temp/test.git &&
+    git init --bare $HOME/_temp/test.git && # create an empty one
+    ./libgit2_clar -sonline::clone::ssh_with_paths
 fi
diff --git a/src/transports/ssh.c b/src/transports/ssh.c
index a1081b3..f84ea4d 100644
--- a/src/transports/ssh.c
+++ b/src/transports/ssh.c
@@ -37,6 +37,8 @@ typedef struct {
 	transport_smart *owner;
 	ssh_stream *current_stream;
 	git_cred *cred;
+	char *cmd_uploadpack;
+	char *cmd_receivepack;
 } ssh_subtransport;
 
 static void ssh_error(LIBSSH2_SESSION *session, const char *errmsg)
@@ -504,7 +506,9 @@ static int ssh_uploadpack_ls(
 	const char *url,
 	git_smart_subtransport_stream **stream)
 {
-	if (_git_ssh_setup_conn(t, url, cmd_uploadpack, stream) < 0)
+	const char *cmd = t->cmd_uploadpack ? t->cmd_uploadpack : cmd_uploadpack;
+
+	if (_git_ssh_setup_conn(t, url, cmd, stream) < 0)
 		return -1;
 
 	return 0;
@@ -531,7 +535,9 @@ static int ssh_receivepack_ls(
 	const char *url,
 	git_smart_subtransport_stream **stream)
 {
-	if (_git_ssh_setup_conn(t, url, cmd_receivepack, stream) < 0)
+	const char *cmd = t->cmd_receivepack ? t->cmd_receivepack : cmd_receivepack;
+
+	if (_git_ssh_setup_conn(t, url, cmd, stream) < 0)
 		return -1;
 
 	return 0;
@@ -596,6 +602,8 @@ static void _ssh_free(git_smart_subtransport *subtransport)
 
 	assert(!t->current_stream);
 
+	git__free(t->cmd_uploadpack);
+	git__free(t->cmd_receivepack);
 	git__free(t);
 }
 #endif
@@ -628,3 +636,45 @@ int git_smart_subtransport_ssh(
 	return -1;
 #endif
 }
+
+int git_transport_ssh_with_paths(git_transport **out, git_remote *owner, void *payload)
+{
+#ifdef GIT_SSH
+	git_strarray *paths = (git_strarray *) payload;
+	git_transport *transport;
+	transport_smart *smart;
+	ssh_subtransport *t;
+	int error;
+	git_smart_subtransport_definition ssh_definition = {
+		git_smart_subtransport_ssh,
+		0, /* no RPC */
+	};
+
+	if (paths->count != 2) {
+		giterr_set(GITERR_SSH, "invalid ssh paths, must be two strings");
+		return GIT_EINVALIDSPEC;
+	}
+
+	if ((error = git_transport_smart(&transport, owner, &ssh_definition)) < 0)
+		return error;
+
+	smart = (transport_smart *) transport;
+	t = (ssh_subtransport *) smart->wrapped;
+
+	t->cmd_uploadpack = git__strdup(paths->strings[0]);
+	GITERR_CHECK_ALLOC(t->cmd_uploadpack);
+	t->cmd_receivepack = git__strdup(paths->strings[1]);
+	GITERR_CHECK_ALLOC(t->cmd_receivepack);
+
+	*out = transport;
+	return 0;
+#else
+	GIT_UNUSED(owner);
+
+	assert(out);
+	*out = NULL;
+
+	giterr_set(GITERR_INVALID, "Cannot create SSH transport. Library was built without SSH support");
+	return -1;
+#endif
+}
diff --git a/tests/online/clone.c b/tests/online/clone.c
index 2e2e976..b672a09 100644
--- a/tests/online/clone.c
+++ b/tests/online/clone.c
@@ -288,8 +288,73 @@ void test_online_clone__can_cancel(void)
 		git_clone(&g_repo, LIVE_REPO_URL, "./foo", &g_options), 4321);
 }
 
+static int cred_cb(git_cred **cred, const char *url, const char *user_from_url,
+		   unsigned int allowed_types, void *payload)
+{
+	const char *remote_user = cl_getenv("GITTEST_REMOTE_USER");
+	const char *pubkey = cl_getenv("GITTEST_REMOTE_SSH_PUBKEY");
+	const char *privkey = cl_getenv("GITTEST_REMOTE_SSH_KEY");
+	const char *passphrase = cl_getenv("GITTEST_REMOTE_SSH_PASSPHRASE");
+
+	GIT_UNUSED(url); GIT_UNUSED(user_from_url); GIT_UNUSED(payload);
+
+	if (allowed_types & GIT_CREDTYPE_SSH_KEY)
+		return git_cred_ssh_key_new(cred, remote_user, pubkey, privkey, passphrase);
+
+	giterr_set(GITERR_NET, "unexpected cred type");
+	return -1;
+}
+
+static int custom_remote_ssh_with_paths(
+	git_remote **out,
+	git_repository *repo,
+	const char *name,
+	const char *url,
+	void *payload)
+{
+	int error;
+
+	git_remote_callbacks callbacks = GIT_REMOTE_CALLBACKS_INIT;
+
+	if ((error = git_remote_create(out, repo, name, url)) < 0)
+		return error;
+
+	if ((error = git_remote_set_transport(*out, git_transport_ssh_with_paths, payload)) < 0)
+		return error;
 
+	callbacks.credentials = cred_cb;
+	git_remote_set_callbacks(*out, &callbacks);
 
+	return 0;
+}
+
+void test_online_clone__ssh_with_paths(void)
+{
+	char *bad_paths[] = {
+		"/bin/yes",
+		"/bin/false",
+	};
+	char *good_paths[] = {
+		"/usr/bin/git-upload-pack",
+		"/usr/bin/git-receive-pack",
+	};
+	git_strarray arr = {
+		bad_paths,
+		2,
+	};
+
+	const char *remote_url = cl_getenv("GITTEST_REMOTE_URL");
+	const char *remote_user = cl_getenv("GITTEST_REMOTE_USER");
+
+	if (!remote_url || !remote_user)
+		clar__skip();
+
+	g_options.remote_cb = custom_remote_ssh_with_paths;
+	g_options.remote_cb_payload = &arr;
 
+	cl_git_fail(git_clone(&g_repo, remote_url, "./foo", &g_options));
 
+	arr.strings = good_paths;
+	cl_git_pass(git_clone(&g_repo, remote_url, "./foo", &g_options));
+}