diff --git a/plugins/git4idea/src/META-INF/plugin.xml b/plugins/git4idea/src/META-INF/plugin.xml
index 80bc6a187329..85242d0449cd 100644
--- a/plugins/git4idea/src/META-INF/plugin.xml
+++ b/plugins/git4idea/src/META-INF/plugin.xml
@@ -149,8 +149,8 @@
serviceInterface="git4idea.history.wholeTree.GitCommitsSequentially"/>
-
+
handlers = new THashMap();
-
- @NotNull
- public static GitSSHService getInstance() {
- return ServiceManager.getService(GitSSHService.class);
- }
-
- /**
- * @return the port number for XML RCP
- */
- public int getXmlRcpPort() {
- return WebServerManager.getInstance().waitForStart().getPort();
- }
-
- /**
- * Get file to the script service
- *
- * @return path to the script
- * @throws IOException if script cannot be generated
- */
- @NotNull
- public synchronized File getScriptPath() throws IOException {
- if (myScriptPath == null || !myScriptPath.exists()) {
- ScriptGenerator generator = new ScriptGenerator(GitSSHHandler.GIT_SSH_PREFIX, SSHMain.class, getTempDir());
- generator.addClasses(XmlRpcClientLite.class, DecoderException.class, KnownHosts.class, FileUtilRt.class);
- generator.addResource(SSHMainBundle.class, "/org/jetbrains/git4idea/ssh/SSHMainBundle.properties");
- myScriptPath = generator.generate();
- }
- return myScriptPath;
- }
-
- /**
- * @return the temporary directory to use or null if the default directory might be used
- */
- @SuppressWarnings({"MethodMayBeStatic"})
- @Nullable
- protected File getTempDir() {
- return null;
- }
-
- /**
- * Register handler. Note that handlers must be unregistered using {@link #unregisterHandler(int)}.
- *
- * @param handler a handler to register
- * @return an identifier to pass to the environment variable
- */
- public synchronized int registerHandler(@NotNull GitSSHGUIHandler handler) {
- XmlRpcServer xmlRpcServer = XmlRpcServer.SERVICE.getInstance();
- if (!xmlRpcServer.hasHandler(GitSSHHandler.HANDLER_NAME)) {
- xmlRpcServer.addHandler(GitSSHHandler.HANDLER_NAME, new InternalRequestHandler());
- }
-
- while (true) {
- int candidate = RANDOM.nextInt();
- if (candidate == Integer.MIN_VALUE) {
- continue;
- }
- candidate = Math.abs(candidate);
- if (handlers.containsKey(candidate)) {
- continue;
- }
- handlers.put(candidate, handler);
- return candidate;
- }
- }
-
- /**
- * Get handler for the key
- *
- * @param key the key to use
- * @return the registered handler
- */
- @NotNull
- private synchronized GitSSHGUIHandler getHandler(int key) {
- GitSSHGUIHandler rc = handlers.get(key);
- if (rc == null) {
- throw new IllegalStateException("No handler for the key " + key);
- }
- return rc;
- }
-
- /**
- * Unregister handler by the key
- *
- * @param key the key to unregister
- */
- public synchronized void unregisterHandler(int key) {
- if (handlers.remove(key) == null) {
- throw new IllegalArgumentException("The handler " + key + " is not registered");
- }
- }
-
- /**
- * Internal handler implementation class, do not use it.
- */
- public class InternalRequestHandler implements GitSSHHandler {
- /**
- * {@inheritDoc}
- */
- public boolean verifyServerHostKey(final int handler,
- final String hostname,
- final int port,
- final String serverHostKeyAlgorithm,
- final String serverHostKey,
- final boolean isNew) {
- return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
- }
-
- /**
- * {@inheritDoc}
- */
- public String askPassphrase(final int handler,
- final String username,
- final String keyPath,
- final boolean resetPassword,
- final String lastError) {
- return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError));
- }
-
- /**
- * {@inheritDoc}
- */
- @SuppressWarnings({"UseOfObsoleteCollectionType"})
- public Vector replyToChallenge(final int handlerNo,
- final String username,
- final String name,
- final String instruction,
- final int numPrompts,
- final Vector prompt,
- final Vector echo,
- final String lastError) {
- return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
- }
-
- /**
- * {@inheritDoc}
- */
- public String askPassword(final int handlerNo, final String username, final boolean resetPassword, final String lastError) {
- return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError));
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String setLastSuccessful(int handlerNo, String userName, String method, String error) {
- getHandler(handlerNo).setLastSuccessful(userName, method, error);
- return "";
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public String getLastSuccessful(int handlerNo, String userName) {
- return getHandler(handlerNo).getLastSuccessful(userName);
- }
-
- /**
- * Adjust null value ({@code "-"} if null, {@code "+"+s) if non-null)
- *
- * @param s a value to adjust
- * @return adjusted string
- */
- private String adjustNull(final String s) {
- return s == null ? "-" : "+" + s;
- }
-
- /**
- * Adjust null value (returns empty array)
- *
- * @param s if null return empty array
- * @return s if not null, empty array otherwise
- */
- @SuppressWarnings({"UseOfObsoleteCollectionType"})
- private Vector adjustNull(final Vector s) {
- return s == null ? new Vector() : s;
- }
- }
-}
diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java
new file mode 100644
index 000000000000..f762f1114bb6
--- /dev/null
+++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcHandlerService.java
@@ -0,0 +1,166 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.git4idea.ssh;
+
+import com.intellij.ide.XmlRpcServer;
+import com.intellij.openapi.util.io.FileUtilRt;
+import gnu.trove.THashMap;
+import org.apache.commons.codec.DecoderException;
+import org.apache.xmlrpc.XmlRpcClientLite;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.git4idea.util.ScriptGenerator;
+import org.jetbrains.ide.WebServerManager;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Random;
+
+/**
+ * The provider of external application scripts called by Git when a remote operation needs communication with the user.
+ *
+ * Usage:
+ *
+ * - Get the script from {@link #getScriptPath()}.
+ * - Set up proper environment variable
+ * (e.g. {@code GIT_SSH} for SSH connections, or {@code GIT_ASKPASS} for HTTP) pointing to the script.
+ * - {@link #registerHandler(Object) Register} the handler of Git requests.
+ * - Call Git operation.
+ * - If the operation requires user interaction, the registered handler is called via XML RPC protocol.
+ * It can show a dialog in the GUI and return the answer via XML RPC to the external application, that further provides
+ * this value to the Git process.
+ * - {@link #unregisterHandler(int) Unregister} the handler after operation has completed.
+ *
+ *
+ */
+public abstract class GitXmlRpcHandlerService {
+
+ private static final Random RANDOM = new Random();
+
+ @Nullable private File myScriptPath;
+ @NotNull private final THashMap handlers = new THashMap();
+
+ /**
+ * @return the port number for XML RCP
+ */
+ public int getXmlRcpPort() {
+ return WebServerManager.getInstance().waitForStart().getPort();
+ }
+
+ /**
+ * Get file to the script service
+ *
+ * @return path to the script
+ * @throws IOException if script cannot be generated
+ */
+ @NotNull
+ public synchronized File getScriptPath() throws IOException {
+ if (myScriptPath == null || !myScriptPath.exists()) {
+ ScriptGenerator generator = new ScriptGenerator(getScriptTempFilePrefix(), getScriptMainClass(), getTempDir());
+ generator.addClasses(XmlRpcClientLite.class, DecoderException.class, FileUtilRt.class);
+ customizeScriptGenerator(generator);
+ myScriptPath = generator.generate();
+ }
+ return myScriptPath;
+ }
+
+ @NotNull
+ protected abstract String getScriptTempFilePrefix();
+
+ @NotNull
+ protected abstract Class> getScriptMainClass();
+
+ /**
+ * Adds more classes or resources to the script if needed.
+ */
+ protected abstract void customizeScriptGenerator(@NotNull ScriptGenerator generator);
+
+ /**
+ * @return the temporary directory to use or null if the default directory might be used
+ */
+ @SuppressWarnings({"MethodMayBeStatic"})
+ @Nullable
+ protected File getTempDir() {
+ return null;
+ }
+
+ /**
+ * Register handler. Note that handlers must be unregistered using {@link #unregisterHandler(int)}.
+ *
+ * @param handler a handler to register
+ * @return an identifier to pass to the environment variable
+ */
+ public synchronized int registerHandler(@NotNull T handler) {
+ XmlRpcServer xmlRpcServer = XmlRpcServer.SERVICE.getInstance();
+ if (!xmlRpcServer.hasHandler(getRpcHandlerName())) {
+ xmlRpcServer.addHandler(getRpcHandlerName(), createRpcRequestHandlerDelegate());
+ }
+
+ while (true) {
+ int candidate = RANDOM.nextInt();
+ if (candidate == Integer.MIN_VALUE) {
+ continue;
+ }
+ candidate = Math.abs(candidate);
+ if (handlers.containsKey(candidate)) {
+ continue;
+ }
+ handlers.put(candidate, handler);
+ return candidate;
+ }
+ }
+
+ /**
+ * Returns the name of the handler to be used by XML RPC client to call remote methods of a proper object.
+ */
+ @NotNull
+ protected abstract String getRpcHandlerName();
+
+ /**
+ * Creates an implementation of the xml rpc handler, which methods will be called from the external application.
+ * This method should just delegate the call to the specific handler of type {@link T}, which can be achieved by {@link #getHandler(int)}.
+ * @return New instance of the xml rpc handler delegate.
+ */
+ @NotNull
+ protected abstract Object createRpcRequestHandlerDelegate();
+
+ /**
+ * Get handler for the key
+ *
+ * @param key the key to use
+ * @return the registered handler
+ */
+ @NotNull
+ protected synchronized T getHandler(int key) {
+ T rc = handlers.get(key);
+ if (rc == null) {
+ throw new IllegalStateException("No handler for the key " + key);
+ }
+ return rc;
+ }
+
+ /**
+ * Unregister handler by the key
+ *
+ * @param key the key to unregister
+ */
+ public synchronized void unregisterHandler(int key) {
+ if (handlers.remove(key) == null) {
+ throw new IllegalArgumentException("The handler " + key + " is not registered");
+ }
+ }
+
+}
diff --git a/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
new file mode 100644
index 000000000000..3b0d89e44976
--- /dev/null
+++ b/plugins/git4idea/src/org/jetbrains/git4idea/ssh/GitXmlRpcSshService.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2000-2013 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.git4idea.ssh;
+
+import com.trilead.ssh2.KnownHosts;
+import git4idea.commands.GitSSHGUIHandler;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.git4idea.util.ScriptGenerator;
+
+import java.util.Vector;
+
+/**
+ * @author Kirill Likhodedov
+ */
+public class GitXmlRpcSshService extends GitXmlRpcHandlerService {
+
+ @NotNull
+ @Override
+ protected String getScriptTempFilePrefix() {
+ return GitSSHHandler.GIT_SSH_PREFIX;
+ }
+
+ @Override
+ protected void customizeScriptGenerator(@NotNull ScriptGenerator generator) {
+ generator.addClasses(KnownHosts.class);
+ generator.addResource(SSHMainBundle.class, "/org/jetbrains/git4idea/ssh/SSHMainBundle.properties");
+ }
+
+ @NotNull
+ @Override
+ protected Class> getScriptMainClass() {
+ return SSHMain.class;
+ }
+
+ @NotNull
+ @Override
+ protected String getRpcHandlerName() {
+ return GitSSHHandler.HANDLER_NAME;
+ }
+
+ @NotNull
+ @Override
+ protected Object createRpcRequestHandlerDelegate() {
+ return new InternalRequestHandler();
+ }
+
+ /**
+ * Internal handler implementation class, do not use it.
+ */
+ public class InternalRequestHandler implements GitSSHHandler {
+
+ @Override
+ public boolean verifyServerHostKey(int handler, String hostname, int port, String serverHostKeyAlgorithm, String serverHostKey,
+ boolean isNew) {
+ return getHandler(handler).verifyServerHostKey(hostname, port, serverHostKeyAlgorithm, serverHostKey, isNew);
+ }
+
+ @Override
+ public String askPassphrase(int handler, String username, String keyPath, boolean resetPassword, String lastError) {
+ return adjustNull(getHandler(handler).askPassphrase(username, keyPath, resetPassword, lastError));
+ }
+
+ @Override
+ @SuppressWarnings({"UseOfObsoleteCollectionType"})
+ public Vector replyToChallenge(int handlerNo, String username, String name, String instruction, int numPrompts,
+ Vector prompt, Vector echo, String lastError) {
+ return adjustNull(getHandler(handlerNo).replyToChallenge(username, name, instruction, numPrompts, prompt, echo, lastError));
+ }
+
+ @Override
+ public String askPassword(int handlerNo, String username, boolean resetPassword, String lastError) {
+ return adjustNull(getHandler(handlerNo).askPassword(username, resetPassword, lastError));
+ }
+
+ @Override
+ public String setLastSuccessful(int handlerNo, String userName, String method, String error) {
+ getHandler(handlerNo).setLastSuccessful(userName, method, error);
+ return "";
+ }
+
+ @Override
+ public String getLastSuccessful(int handlerNo, String userName) {
+ return getHandler(handlerNo).getLastSuccessful(userName);
+ }
+
+ /**
+ * Adjust null value ({@code "-"} if null, {@code "+"+s) if non-null)
+ *
+ * @param s a value to adjust
+ * @return adjusted string
+ */
+ private String adjustNull(final String s) {
+ return s == null ? "-" : "+" + s;
+ }
+
+ /**
+ * Adjust null value (returns empty array)
+ *
+ * @param s if null return empty array
+ * @return s if not null, empty array otherwise
+ */
+ @SuppressWarnings({"UseOfObsoleteCollectionType"})
+ private Vector adjustNull(final Vector s) {
+ return s == null ? new Vector() : s;
+ }
+ }
+}