@ -97,12 +97,16 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
@Override
public FetchConnection openFetch ( ) throws TransportException {
return new SshFetchConnection ( ) ;
return new SshFetchConnection ( newConnection ( ) ) ;
}
@Override
public PushConnection openPush ( ) throws TransportException {
return new SshPushConnection ( ) ;
return new SshPushConnection ( newConnection ( ) ) ;
}
private Connection newConnection ( ) {
return new JschConnection ( ) ;
}
private static void sqMinimal ( final StringBuilder cmd , final String val ) {
@ -128,7 +132,7 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
cmd . append ( QuotedString . BOURNE . quote ( val ) ) ;
}
private String commandFor ( final String exe ) {
String commandFor ( final String exe ) {
String path = uri . getPath ( ) ;
if ( uri . getScheme ( ) ! = null & & uri . getPath ( ) . startsWith ( "/~" ) )
path = ( uri . getPath ( ) . substring ( 1 ) ) ;
@ -146,28 +150,6 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
return cmd . toString ( ) ;
}
ChannelExec exec ( final String exe ) throws TransportException {
initSession ( ) ;
try {
final ChannelExec channel = ( ChannelExec ) sock . openChannel ( "exec" ) ;
channel . setCommand ( commandFor ( exe ) ) ;
return channel ;
} catch ( JSchException je ) {
throw new TransportException ( uri , je . getMessage ( ) , je ) ;
}
}
private void connect ( ChannelExec channel ) 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 ) ;
}
}
void checkExecFailure ( int status , String exe , String why )
throws TransportException {
if ( status = = 127 ) {
@ -198,60 +180,132 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
return new NoRemoteRepositoryException ( uri , why ) ;
}
// 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.
//
private OutputStream outputStream ( ChannelExec channel ) throws IOException {
final OutputStream out = channel . getOutputStream ( ) ;
if ( getTimeout ( ) < = 0 )
return out ;
final PipedInputStream pipeIn = new PipedInputStream ( ) ;
final StreamCopyThread copyThread = new StreamCopyThread ( pipeIn , out ) ;
final PipedOutputStream pipeOut = new PipedOutputStream ( pipeIn ) {
@Override
public void flush ( ) throws IOException {
super . flush ( ) ;
copyThread . flush ( ) ;
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
public void close ( ) throws IOException {
super . close ( ) ;
@Override
void close ( ) {
if ( channel ! = null ) {
try {
copyThread . join ( getTimeout ( ) * 1000 ) ;
} catch ( InterruptedException e ) {
// Just wake early, the thread will terminate anyway.
exitStatus = channel . getExitStatus ( ) ;
if ( channel . isConnected ( ) )
channel . disconnect ( ) ;
} finally {
channel = null ;
}
}
} ;
copyThread . start ( ) ;
return pipeOut ;
}
}
class SshFetchConnection extends BasePackFetchConnection {
private ChannelExec channel ;
private Connection conn ;
private StreamCopyThread errorThread ;
private int exitStatus ;
SshFetchConnection ( ) throws TransportException {
SshFetchConnection ( Connection conn ) throws TransportException {
super ( TransportGitSsh . this ) ;
this . conn = conn ;
try {
final MessageWriter msg = new MessageWriter ( ) ;
setMessageWriter ( msg ) ;
channel = exec ( getOptionUploadPack ( ) ) ;
conn . exec ( getOptionUploadPack ( ) ) ;
final InputStream upErr = channel . getErrStream ( ) ;
final InputStream upErr = conn . getErro rStream ( ) ;
errorThread = new StreamCopyThread ( upErr , msg . getRawStream ( ) ) ;
errorThread . start ( ) ;
init ( channel . getInputStream ( ) , outputStream ( channel ) ) ;
connect ( channel ) ;
init ( conn . getInputStream ( ) , conn . getOutputStream ( ) ) ;
conn . connect ( ) ;
} catch ( TransportException err ) {
close ( ) ;
@ -266,7 +320,8 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
readAdvertisedRefs ( ) ;
} catch ( NoRemoteRepositoryException notFound ) {
final String msgs = getMessages ( ) ;
checkExecFailure ( exitStatus , getOptionUploadPack ( ) , msgs ) ;
checkExecFailure ( conn . getExitStatus ( ) , getOptionUploadPack ( ) ,
msgs ) ;
throw cleanNotFound ( notFound , msgs ) ;
}
}
@ -286,40 +341,30 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
}
super . close ( ) ;
if ( channel ! = null ) {
try {
exitStatus = channel . getExitStatus ( ) ;
if ( channel . isConnected ( ) )
channel . disconnect ( ) ;
} finally {
channel = null ;
}
}
conn . close ( ) ;
}
}
class SshPushConnection extends BasePackPushConnection {
private ChannelExec channel ;
private Connection conn ;
private StreamCopyThread errorThread ;
private int exitStatus ;
SshPushConnection ( ) throws TransportException {
SshPushConnection ( Connection conn ) throws TransportException {
super ( TransportGitSsh . this ) ;
this . conn = conn ;
try {
final MessageWriter msg = new MessageWriter ( ) ;
setMessageWriter ( msg ) ;
channel = exec ( getOptionReceivePack ( ) ) ;
conn . exec ( getOptionReceivePack ( ) ) ;
final InputStream rpErr = channel . getErrStream ( ) ;
final InputStream rpErr = conn . getErro rStream ( ) ;
errorThread = new StreamCopyThread ( rpErr , msg . getRawStream ( ) ) ;
errorThread . start ( ) ;
init ( channel . getInputStream ( ) , outputStream ( channel ) ) ;
connect ( channel ) ;
init ( conn . getInputStream ( ) , conn . getOutputStream ( ) ) ;
conn . connect ( ) ;
} catch ( TransportException err ) {
close ( ) ;
@ -334,7 +379,8 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
readAdvertisedRefs ( ) ;
} catch ( NoRemoteRepositoryException notFound ) {
final String msgs = getMessages ( ) ;
checkExecFailure ( exitStatus , getOptionReceivePack ( ) , msgs ) ;
checkExecFailure ( conn . getExitStatus ( ) , getOptionReceivePack ( ) ,
msgs ) ;
throw cleanNotFound ( notFound , msgs ) ;
}
}
@ -354,16 +400,7 @@ public class TransportGitSsh extends SshTransport implements PackTransport {
}
super . close ( ) ;
if ( channel ! = null ) {
try {
exitStatus = channel . getExitStatus ( ) ;
if ( channel . isConnected ( ) )
channel . disconnect ( ) ;
} finally {
channel = null ;
}
}
conn . close ( ) ;
}
}
}