Create RemoteSession interface

The RemoteSession interface operates like a simplified version of
java.lang.Runtime with a single exec method (and a disconnect
method). It returns a java.lang.Process, which should begin execution
immediately. Note that this greatly simplifies the interface for
running commands. There is no longer a connect method, and most
implementations will contain the bulk of their code inside
Process.exec, or a constructor called by Process.exec. (See the
revised implementations of JschSession and ExtSession.)
Implementations can now configure their connections properly without
either ignoring the proper use of the interface or trying to adhere
to an overly strict interface with odd rules about what methods are
called first.  For example, Jsch needs to create the output stream
before executing, which it now does in the process constructor. These
changes should make it much easier to add alternate session
implementations in the future.

Also-by: John D Eblen <jdeblen@comcast.net>
Bug: 336749
CQ: 5004
Change-Id: Iece43632086afadf175af6638255041ccaf2bfbb
Signed-off-by: Chris Aniszczyk <caniszczyk@gmail.com>
This commit is contained in:
Roland Schulz 2011-03-04 19:50:14 -05:00 committed by Chris Aniszczyk
parent 770c733687
commit ccd3d83719
8 changed files with 449 additions and 284 deletions

View File

@ -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.
}

View File

@ -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<String, JSch> byIdentityFile = new HashMap<String, JSch>();
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);

View File

@ -0,0 +1,237 @@
/*
* Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2009, Google, Inc.
* Copyright (C) 2009, JetBrains s.r.o.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* 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.
* <p>
* 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.
* <p>
* 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();
}
}
}

View File

@ -0,0 +1,86 @@
/*
* Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2009, Google, Inc.
* Copyright (C) 2009, JetBrains s.r.o.
* Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
* 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.
* <p>
* 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();
}

View File

@ -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.
* <p>
@ -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.
* <p>
* 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 {
* <p>
* A factory is always available. By default the factory will read from the
* user's <code>$HOME/.ssh</code> 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 <code>connect()</code>
* 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
* <code>System.getProperty("user.name")</code>.
* @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();
}
}

View File

@ -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

View File

@ -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();
}
}
}

View File

@ -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) {