diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java index 53c1a2ce3..70128077d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/DefaultSshSessionFactory.java @@ -58,7 +58,7 @@ * If user interactivity is required by SSH (e.g. to obtain a password), the * connection will immediately fail. */ -class DefaultSshSessionFactory extends SshConfigSessionFactory { +class DefaultSshSessionFactory extends JschConfigSessionFactory { protected void configure(final OpenSshConfig.Host hc, final Session session) { // No additional configuration required. } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java similarity index 78% rename from org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java rename to org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java index 99e7b8333..b1f23243e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshConfigSessionFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschConfigSessionFactory.java @@ -52,9 +52,13 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.net.ConnectException; +import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; +import org.eclipse.jgit.JGitText; +import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.util.FS; import com.jcraft.jsch.JSch; @@ -74,7 +78,7 @@ * {@link #configure(org.eclipse.jgit.transport.OpenSshConfig.Host, Session)} * to supply appropriate {@link UserInfo} to the session. */ -public abstract class SshConfigSessionFactory extends SshSessionFactory { +public abstract class JschConfigSessionFactory extends SshSessionFactory { private final Map byIdentityFile = new HashMap(); private JSch defaultJSch; @@ -82,42 +86,63 @@ public abstract class SshConfigSessionFactory extends SshSessionFactory { private OpenSshConfig config; @Override - public synchronized Session getSession(String user, String pass, - String host, int port, CredentialsProvider credentialsProvider, - FS fs) throws JSchException { - if (config == null) - config = OpenSshConfig.get(fs); + public synchronized RemoteSession getSession(URIish uri, + CredentialsProvider credentialsProvider, FS fs, int tms) + throws TransportException { - final OpenSshConfig.Host hc = config.lookup(host); - host = hc.getHostName(); - if (port <= 0) - port = hc.getPort(); - if (user == null) - user = hc.getUser(); + String user = uri.getUser(); + final String pass = uri.getPass(); + String host = uri.getHost(); + int port = uri.getPort(); - final Session session = createSession(hc, user, host, port, fs); - if (pass != null) - session.setPassword(pass); - final String strictHostKeyCheckingPolicy = hc - .getStrictHostKeyChecking(); - if (strictHostKeyCheckingPolicy != null) - session.setConfig("StrictHostKeyChecking", - strictHostKeyCheckingPolicy); - final String pauth = hc.getPreferredAuthentications(); - if (pauth != null) - session.setConfig("PreferredAuthentications", pauth); - if (credentialsProvider != null + try { + if (config == null) + config = OpenSshConfig.get(fs); + + final OpenSshConfig.Host hc = config.lookup(host); + host = hc.getHostName(); + if (port <= 0) + port = hc.getPort(); + if (user == null) + user = hc.getUser(); + + final Session session = createSession(hc, user, host, port, fs); + if (pass != null) + session.setPassword(pass); + final String strictHostKeyCheckingPolicy = hc + .getStrictHostKeyChecking(); + if (strictHostKeyCheckingPolicy != null) + session.setConfig("StrictHostKeyChecking", + strictHostKeyCheckingPolicy); + final String pauth = hc.getPreferredAuthentications(); + if (pauth != null) + session.setConfig("PreferredAuthentications", pauth); + if (credentialsProvider != null && (!hc.isBatchMode() || !credentialsProvider.isInteractive())) { - session.setUserInfo(new CredentialsProviderUserInfo(session, - credentialsProvider)); + session.setUserInfo(new CredentialsProviderUserInfo(session, + credentialsProvider)); + } + configure(hc, session); + + if (!session.isConnected()) + session.connect(tms); + + return new JschSession(session, uri); + + } catch (JSchException je) { + final Throwable c = je.getCause(); + if (c instanceof UnknownHostException) + throw new TransportException(uri, JGitText.get().unknownHost); + if (c instanceof ConnectException) + throw new TransportException(uri, c.getMessage()); + throw new TransportException(uri, je.getMessage(), je); } - configure(hc, session); - return session; + } /** - * Create a new JSch session for the requested address. - * + * Create a new remote session for the requested address. + * * @param hc * host configuration * @param user @@ -165,15 +190,13 @@ protected Session createSession(final OpenSshConfig.Host hc, protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException { if (defaultJSch == null) { defaultJSch = createDefaultJSch(fs); - for (Object name : defaultJSch.getIdentityNames()) { + for (Object name : defaultJSch.getIdentityNames()) byIdentityFile.put((String) name, defaultJSch); - } } final File identityFile = hc.getIdentityFile(); - if (identityFile == null) { + if (identityFile == null) return defaultJSch; - } final String identityKey = identityFile.getAbsolutePath(); JSch jsch = byIdentityFile.get(identityKey); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java new file mode 100644 index 000000000..9dc0da614 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/JschSession.java @@ -0,0 +1,237 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Google, Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; + +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.util.io.StreamCopyThread; + +import com.jcraft.jsch.Channel; +import com.jcraft.jsch.ChannelExec; +import com.jcraft.jsch.JSchException; +import com.jcraft.jsch.Session; + +/** + * Run remote commands using Jsch. + *

+ * This class is the default session implementation using Jsch. Note that + * {@link JschConfigSessionFactory} is used to create the actual session passed + * to the constructor. + */ +public class JschSession implements RemoteSession { + private final Session sock; + private final URIish uri; + + /** + * Create a new session object by passing the real Jsch session and the URI + * information. + * + * @param session + * the real Jsch session created elsewhere. + * @param uri + * the URI information for the remote connection + */ + public JschSession(final Session session, URIish uri) { + sock = session; + this.uri = uri; + } + + public Process exec(String command, int timeout) throws IOException { + return new JschProcess(command, timeout); + } + + public void disconnect() { + if (sock.isConnected()) + sock.disconnect(); + } + + /** + * A kludge to allow {@link TransportSftp} to get an Sftp channel from Jsch. + * Ideally, this method would be generic, which would require implementing + * generic Sftp channel operations in the RemoteSession class. + * + * @return a channel suitable for Sftp operations. + * @throws JSchException + * on problems getting the channel. + */ + public Channel getSftpChannel() throws JSchException { + return sock.openChannel("sftp"); + } + + /** + * Implementation of Process for running a single command using Jsch. + *

+ * Uses the Jsch session to do actual command execution and manage the + * execution. + */ + private class JschProcess extends Process { + private ChannelExec channel; + + private final int timeout; + + private InputStream inputStream; + + private OutputStream outputStream; + + private InputStream errStream; + + /** + * Opens a channel on the session ("sock") for executing the given + * command, opens streams, and starts command execution. + * + * @param commandName + * the command to execute + * @param tms + * the timeout value, in seconds, for the command. + * @throws TransportException + * on problems opening a channel or connecting to the remote + * host + * @throws IOException + * on problems opening streams + */ + private JschProcess(final String commandName, int tms) + throws TransportException, IOException { + timeout = tms; + try { + channel = (ChannelExec) sock.openChannel("exec"); + channel.setCommand(commandName); + setupStreams(); + channel.connect(timeout > 0 ? timeout * 1000 : 0); + if (!channel.isConnected()) + throw new TransportException(uri, "connection failed"); + } catch (JSchException e) { + throw new TransportException(uri, e.getMessage(), e); + } + } + + private void setupStreams() throws IOException { + inputStream = channel.getInputStream(); + + // JSch won't let us interrupt writes when we use our InterruptTimer + // to break out of a long-running write operation. To work around + // that we spawn a background thread to shuttle data through a pipe, + // as we can issue an interrupted write out of that. Its slower, so + // we only use this route if there is a timeout. + final OutputStream out = channel.getOutputStream(); + if (timeout <= 0) { + outputStream = out; + } else { + final PipedInputStream pipeIn = new PipedInputStream(); + final StreamCopyThread copier = new StreamCopyThread(pipeIn, + out); + final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) { + @Override + public void flush() throws IOException { + super.flush(); + copier.flush(); + } + + @Override + public void close() throws IOException { + super.close(); + try { + copier.join(timeout * 1000); + } catch (InterruptedException e) { + // Just wake early, the thread will terminate + // anyway. + } + } + }; + copier.start(); + outputStream = pipeOut; + } + + errStream = channel.getErrStream(); + } + + @Override + public InputStream getInputStream() { + return inputStream; + } + + @Override + public OutputStream getOutputStream() { + return outputStream; + } + + @Override + public InputStream getErrorStream() { + return errStream; + } + + @Override + public int exitValue() { + if (isRunning()) + throw new IllegalStateException(); + return channel.getExitStatus(); + } + + private boolean isRunning() { + return channel.getExitStatus() < 0 && channel.isConnected(); + } + + @Override + public void destroy() { + if (channel.isConnected()) + channel.disconnect(); + } + + @Override + public int waitFor() throws InterruptedException { + while (isRunning()) + Thread.sleep(100); + return exitValue(); + } + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java new file mode 100644 index 000000000..5a73cf5af --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteSession.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2009, Constantine Plotnikov + * Copyright (C) 2008-2009, Google Inc. + * Copyright (C) 2009, Google, Inc. + * Copyright (C) 2009, JetBrains s.r.o. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.transport; + +import java.io.IOException; + +/** + * Create a remote "session" for executing remote commands. + *

+ * Clients should subclass RemoteSession to create an alternate way for JGit to + * execute remote commands. (The client application may already have this + * functionality available.) Note that this class is just a factory for creating + * remote processes. If the application already has a persistent connection to + * the remote machine, RemoteSession may do nothing more than return a new + * RemoteProcess when exec is called. + */ +public interface RemoteSession { + /** + * Generate a new remote process to execute the given command. This function + * should also start execution and may need to create the streams prior to + * execution. + * @param commandName + * command to execute + * @param timeout + * timeout value, in seconds, for command execution + * @return a new remote process + * @throws IOException + * may be thrown in several cases. For example, on problems + * opening input or output streams or on problems connecting or + * communicating with the remote host. For the latter two cases, + * a TransportException may be thrown (a subclass of + * IOException). + */ + public Process exec(String commandName, int timeout) throws IOException; + + /** + * Disconnect the remote session + */ + public void disconnect(); +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java index 34aa3dbd2..a1aeceb2f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshSessionFactory.java @@ -44,11 +44,9 @@ package org.eclipse.jgit.transport; +import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.util.FS; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; - /** * Creates and destroys SSH connections to a remote system. *

@@ -56,9 +54,9 @@ * communicating with the end-user as well as reading their personal SSH * configuration settings, such as known hosts and private keys. *

- * A {@link Session} must be returned to the factory that created it. Callers - * are encouraged to retain the SshSessionFactory for the duration of the period - * they are using the Session. + * A {@link RemoteSession} must be returned to the factory that created it. + * Callers are encouraged to retain the SshSessionFactory for the duration of + * the period they are using the Session. */ public abstract class SshSessionFactory { private static SshSessionFactory INSTANCE = new DefaultSshSessionFactory(); @@ -68,7 +66,7 @@ public abstract class SshSessionFactory { *

* A factory is always available. By default the factory will read from the * user's $HOME/.ssh and assume OpenSSH compatibility. - * + * * @return factory the current factory for this JVM. */ public static SshSessionFactory getInstance() { @@ -98,42 +96,32 @@ public static void setInstance(final SshSessionFactory newFactory) { * The caller must connect the session by invoking connect() * if it has not already been connected. * - * @param user - * username to authenticate as. If null a reasonable default must - * be selected by the implementation. This may be - * System.getProperty("user.name"). - * @param pass - * optional user account password or passphrase. If not null a - * UserInfo that supplies this value to the SSH library will be - * configured. - * @param host - * hostname (or IP address) to connect to. Must not be null. - * @param port - * port number the server is listening for connections on. May be <= - * 0 to indicate the IANA registered port of 22 should be used. + * @param uri + * URI information about the remote host * @param credentialsProvider * provider to support authentication, may be null. * @param fs * the file system abstraction which will be necessary to * perform certain file system operations. + * @param tms + * Timeout value, in milliseconds. * @return a session that can contact the remote host. - * @throws JSchException + * @throws TransportException * the session could not be created. */ - public abstract Session getSession(String user, String pass, String host, - int port, CredentialsProvider credentialsProvider, FS fs) - throws JSchException; + public abstract RemoteSession getSession(URIish uri, + CredentialsProvider credentialsProvider, FS fs, int tms) + throws TransportException; /** * Close (or recycle) a session to a host. * * @param session * a session previously obtained from this factory's - * {@link #getSession(String,String, String, int, CredentialsProvider, FS)} + * {@link #getSession(URIish, CredentialsProvider, FS, int)} * method. */ - public void releaseSession(final Session session) { - if (session.isConnected()) - session.disconnect(); + public void releaseSession(final RemoteSession session) { + session.disconnect(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java index 81d233f1e..46de01c2f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SshTransport.java @@ -47,16 +47,10 @@ package org.eclipse.jgit.transport; -import java.net.ConnectException; -import java.net.UnknownHostException; - import org.eclipse.jgit.JGitText; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Repository; -import com.jcraft.jsch.JSchException; -import com.jcraft.jsch.Session; - /** * The base class for transports that use SSH protocol. This class allows * customizing SSH connection settings. @@ -68,7 +62,7 @@ public abstract class SshTransport extends TcpTransport { /** * The open SSH session */ - protected Session sock; + private RemoteSession sock; /** * Create a new transport instance. @@ -111,35 +105,22 @@ public SshSessionFactory getSshSessionFactory() { return sch; } - /** - * Initialize SSH session - * + * Get the default SSH session + * + * @return a remote session * @throws TransportException * in case of error with opening SSH session */ - protected void initSession() throws TransportException { + protected RemoteSession getSession() throws TransportException { if (sock != null) - return; + return sock; final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0; - final String user = uri.getUser(); - final String pass = uri.getPass(); - final String host = uri.getHost(); - final int port = uri.getPort(); - try { - sock = sch.getSession(user, pass, host, port, - getCredentialsProvider(), local.getFS()); - if (!sock.isConnected()) - sock.connect(tms); - } catch (JSchException je) { - final Throwable c = je.getCause(); - if (c instanceof UnknownHostException) - throw new TransportException(uri, JGitText.get().unknownHost); - if (c instanceof ConnectException) - throw new TransportException(uri, c.getMessage()); - throw new TransportException(uri, je.getMessage(), je); - } + + sock = sch + .getSession(uri, getCredentialsProvider(), local.getFS(), tms); + return sock; } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java index 47959f5c9..33a87109b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java @@ -48,9 +48,6 @@ import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -70,9 +67,7 @@ import org.eclipse.jgit.util.SystemReader; import org.eclipse.jgit.util.io.MessageWriter; import org.eclipse.jgit.util.io.StreamCopyThread; - -import com.jcraft.jsch.ChannelExec; -import com.jcraft.jsch.JSchException; +import org.eclipse.jgit.util.FS; /** * Transport through an SSH tunnel. @@ -135,22 +130,26 @@ public Transport open(URIish uri, Repository local, String remoteName) TransportGitSsh(final Repository local, final URIish uri) { super(local, uri); + if (useExtSession()) { + setSshSessionFactory(new SshSessionFactory() { + @Override + public RemoteSession getSession(URIish uri2, + CredentialsProvider credentialsProvider, FS fs, int tms) + throws TransportException { + return new ExtSession(); + } + }); + } } @Override public FetchConnection openFetch() throws TransportException { - return new SshFetchConnection(newConnection()); + return new SshFetchConnection(); } @Override public PushConnection openPush() throws TransportException { - return new SshPushConnection(newConnection()); - } - - private Connection newConnection() { - if (useExtConnection()) - return new ExtConnection(); - return new JschConnection(); + return new SshPushConnection(); } String commandFor(final String exe) { @@ -195,123 +194,13 @@ NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf, return new NoRemoteRepositoryException(uri, why); } - private abstract class Connection { - abstract void exec(String commandName) throws TransportException; - - abstract void connect() throws TransportException; - - abstract InputStream getInputStream() throws IOException; - - abstract OutputStream getOutputStream() throws IOException; - - abstract InputStream getErrorStream() throws IOException; - - abstract int getExitStatus(); - - abstract void close(); - } - - private class JschConnection extends Connection { - private ChannelExec channel; - - private int exitStatus; - - @Override - void exec(String commandName) throws TransportException { - initSession(); - try { - channel = (ChannelExec) sock.openChannel("exec"); - channel.setCommand(commandFor(commandName)); - } catch (JSchException je) { - throw new TransportException(uri, je.getMessage(), je); - } - } - - @Override - void connect() throws TransportException { - try { - channel.connect(getTimeout() > 0 ? getTimeout() * 1000 : 0); - if (!channel.isConnected()) - throw new TransportException(uri, "connection failed"); - } catch (JSchException e) { - throw new TransportException(uri, e.getMessage(), e); - } - } - - @Override - InputStream getInputStream() throws IOException { - return channel.getInputStream(); - } - - @Override - OutputStream getOutputStream() throws IOException { - // JSch won't let us interrupt writes when we use our InterruptTimer - // to break out of a long-running write operation. To work around - // that we spawn a background thread to shuttle data through a pipe, - // as we can issue an interrupted write out of that. Its slower, so - // we only use this route if there is a timeout. - // - final OutputStream out = channel.getOutputStream(); - if (getTimeout() <= 0) - return out; - final PipedInputStream pipeIn = new PipedInputStream(); - final StreamCopyThread copier = new StreamCopyThread(pipeIn, out); - final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) { - @Override - public void flush() throws IOException { - super.flush(); - copier.flush(); - } - - @Override - public void close() throws IOException { - super.close(); - try { - copier.join(getTimeout() * 1000); - } catch (InterruptedException e) { - // Just wake early, the thread will terminate anyway. - } - } - }; - copier.start(); - return pipeOut; - } - - @Override - InputStream getErrorStream() throws IOException { - return channel.getErrStream(); - } - - @Override - int getExitStatus() { - return exitStatus; - } - - @Override - void close() { - if (channel != null) { - try { - exitStatus = channel.getExitStatus(); - if (channel.isConnected()) - channel.disconnect(); - } finally { - channel = null; - } - } - } - } - - private static boolean useExtConnection() { + private static boolean useExtSession() { return SystemReader.getInstance().getenv("GIT_SSH") != null; } - private class ExtConnection extends Connection { - private Process proc; - - private int exitStatus; - - @Override - void exec(String commandName) throws TransportException { + private class ExtSession implements RemoteSession { + public Process exec(String command, int timeout) + throws TransportException { String ssh = SystemReader.getInstance().getenv("GIT_SSH"); boolean putty = ssh.toLowerCase().contains("plink"); @@ -327,7 +216,7 @@ void exec(String commandName) throws TransportException { args.add(getURI().getUser() + "@" + getURI().getHost()); else args.add(getURI().getHost()); - args.add(commandFor(commandName)); + args.add(command); ProcessBuilder pb = new ProcessBuilder(); pb.command(args); @@ -337,73 +226,35 @@ void exec(String commandName) throws TransportException { local.getDirectory().getPath()); try { - proc = pb.start(); + return pb.start(); } catch (IOException err) { - throw new TransportException(uri, err.getMessage(), err); + throw new TransportException(err.getMessage(), err); } } - @Override - void connect() throws TransportException { - // Nothing to do, the process was already opened. - } - - @Override - InputStream getInputStream() throws IOException { - return proc.getInputStream(); - } - - @Override - OutputStream getOutputStream() throws IOException { - return proc.getOutputStream(); - } - - @Override - InputStream getErrorStream() throws IOException { - return proc.getErrorStream(); - } - - @Override - int getExitStatus() { - return exitStatus; - } - - @Override - void close() { - if (proc != null) { - try { - try { - exitStatus = proc.waitFor(); - } catch (InterruptedException e) { - // Ignore the interrupt, but return immediately. - } - } finally { - proc = null; - } - } + public void disconnect() { + // Nothing to do } } class SshFetchConnection extends BasePackFetchConnection { - private Connection conn; + private final Process process; private StreamCopyThread errorThread; - SshFetchConnection(Connection conn) throws TransportException { + SshFetchConnection() throws TransportException { super(TransportGitSsh.this); - this.conn = conn; try { + process = getSession().exec(commandFor(getOptionUploadPack()), + getTimeout()); final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); - conn.exec(getOptionUploadPack()); - - final InputStream upErr = conn.getErrorStream(); + final InputStream upErr = process.getErrorStream(); errorThread = new StreamCopyThread(upErr, msg.getRawStream()); errorThread.start(); - init(conn.getInputStream(), conn.getOutputStream()); - conn.connect(); + init(process.getInputStream(), process.getOutputStream()); } catch (TransportException err) { close(); @@ -418,7 +269,7 @@ class SshFetchConnection extends BasePackFetchConnection { readAdvertisedRefs(); } catch (NoRemoteRepositoryException notFound) { final String msgs = getMessages(); - checkExecFailure(conn.getExitStatus(), getOptionUploadPack(), + checkExecFailure(process.exitValue(), getOptionUploadPack(), msgs); throw cleanNotFound(notFound, msgs); } @@ -439,30 +290,28 @@ public void close() { } super.close(); - conn.close(); + process.destroy(); } } class SshPushConnection extends BasePackPushConnection { - private Connection conn; + private final Process process; private StreamCopyThread errorThread; - SshPushConnection(Connection conn) throws TransportException { + SshPushConnection() throws TransportException { super(TransportGitSsh.this); - this.conn = conn; try { + process = getSession().exec(commandFor(getOptionReceivePack()), + getTimeout()); final MessageWriter msg = new MessageWriter(); setMessageWriter(msg); - conn.exec(getOptionReceivePack()); - - final InputStream rpErr = conn.getErrorStream(); + final InputStream rpErr = process.getErrorStream(); errorThread = new StreamCopyThread(rpErr, msg.getRawStream()); errorThread.start(); - init(conn.getInputStream(), conn.getOutputStream()); - conn.connect(); + init(process.getInputStream(), process.getOutputStream()); } catch (TransportException err) { close(); @@ -477,7 +326,7 @@ class SshPushConnection extends BasePackPushConnection { readAdvertisedRefs(); } catch (NoRemoteRepositoryException notFound) { final String msgs = getMessages(); - checkExecFailure(conn.getExitStatus(), getOptionReceivePack(), + checkExecFailure(process.exitValue(), getOptionReceivePack(), msgs); throw cleanNotFound(notFound, msgs); } @@ -498,7 +347,7 @@ public void close() { } super.close(); - conn.close(); + process.destroy(); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java index 9ab4a9926..2388b543c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportSftp.java @@ -147,11 +147,12 @@ public PushConnection openPush() throws TransportException { } ChannelSftp newSftp() throws TransportException { - initSession(); - final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0; try { - final Channel channel = sock.openChannel("sftp"); + // @TODO: Fix so that this operation is generic and casting to + // JschSession is no longer necessary. + final Channel channel = ((JschSession) getSession()) + .getSftpChannel(); channel.connect(tms); return (ChannelSftp) channel; } catch (JSchException je) {