2 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above
16 * copyright notice, this list of conditions and the following
17 * disclaimer in the documentation and/or other materials provided
18 * with the distribution.
20 * - Neither the name of the Git Development Community nor the
21 * names of its contributors may be used to endorse or promote
22 * products derived from this software without specific prior
25 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
26 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
27 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
28 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
29 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
30 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
31 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
32 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
33 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
34 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
35 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
36 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
37 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
40 package org
.spearce
.jgit
.transport
;
42 import java
.io
.IOException
;
43 import java
.io
.OutputStream
;
45 import org
.spearce
.jgit
.errors
.NoRemoteRepositoryException
;
46 import org
.spearce
.jgit
.errors
.TransportException
;
47 import org
.spearce
.jgit
.lib
.Repository
;
48 import org
.spearce
.jgit
.util
.QuotedString
;
50 import com
.jcraft
.jsch
.ChannelExec
;
51 import com
.jcraft
.jsch
.JSchException
;
54 * Transport through an SSH tunnel.
56 * The SSH transport requires the remote side to have Git installed, as the
57 * transport logs into the remote system and executes a Git helper program on
58 * the remote side to read (or write) the remote repository's files.
60 * This transport does not support direct SCP style of copying files, as it
61 * assumes there are Git specific smarts on the remote side to perform object
62 * enumeration, save file modification and hook execution.
64 public class TransportGitSsh
extends SshTransport
implements PackTransport
{
65 static boolean canHandle(final URIish uri
) {
68 final String scheme
= uri
.getScheme();
69 if ("ssh".equals(scheme
))
71 if ("ssh+git".equals(scheme
))
73 if ("git+ssh".equals(scheme
))
75 if (scheme
== null && uri
.getHost() != null && uri
.getPath() != null)
80 OutputStream errStream
;
82 TransportGitSsh(final Repository local
, final URIish uri
) {
87 public FetchConnection
openFetch() throws TransportException
{
88 return new SshFetchConnection();
92 public PushConnection
openPush() throws TransportException
{
93 return new SshPushConnection();
96 private static void sqMinimal(final StringBuilder cmd
, final String val
) {
97 if (val
.matches("^[a-zA-Z0-9._/-]*$")) {
98 // If the string matches only generally safe characters
99 // that the shell is not going to evaluate specially we
100 // should leave the string unquoted. Not all systems
101 // actually run a shell and over-quoting confuses them
102 // when it comes to the command name.
110 private static void sqAlways(final StringBuilder cmd
, final String val
) {
114 private static void sq(final StringBuilder cmd
, final String val
) {
115 if (val
.length() > 0)
116 cmd
.append(QuotedString
.BOURNE
.quote(val
));
120 ChannelExec
exec(final String exe
) throws TransportException
{
124 final ChannelExec channel
= (ChannelExec
) sock
.openChannel("exec");
125 String path
= uri
.getPath();
126 if (uri
.getScheme() != null && uri
.getPath().startsWith("/~"))
127 path
= (uri
.getPath().substring(1));
129 final StringBuilder cmd
= new StringBuilder();
130 final int gitspace
= exe
.indexOf("git ");
132 sqMinimal(cmd
, exe
.substring(0, gitspace
+ 3));
134 sqMinimal(cmd
, exe
.substring(gitspace
+ 4));
139 channel
.setCommand(cmd
.toString());
140 errStream
= createErrorStream();
141 channel
.setErrStream(errStream
, true);
144 } catch (JSchException je
) {
145 throw new TransportException(uri
, je
.getMessage(), je
);
150 * @return the error stream for the channel, the stream is used to detect
151 * specific error reasons for exceptions.
153 private static OutputStream
createErrorStream() {
154 return new OutputStream() {
155 private StringBuilder all
= new StringBuilder();
157 private StringBuilder sb
= new StringBuilder();
159 public String
toString() {
160 String r
= all
.toString();
161 while (r
.endsWith("\n"))
162 r
= r
.substring(0, r
.length() - 1);
167 public void write(final int b
) throws IOException
{
182 NoRemoteRepositoryException
cleanNotFound(NoRemoteRepositoryException nf
) {
183 String why
= errStream
.toString();
184 if (why
== null || why
.length() == 0)
187 String path
= uri
.getPath();
188 if (uri
.getScheme() != null && uri
.getPath().startsWith("/~"))
189 path
= uri
.getPath().substring(1);
191 final StringBuilder pfx
= new StringBuilder();
192 pfx
.append("fatal: ");
195 if (why
.startsWith(pfx
.toString()))
196 why
= why
.substring(pfx
.length());
198 return new NoRemoteRepositoryException(uri
, why
);
201 class SshFetchConnection
extends BasePackFetchConnection
{
202 private ChannelExec channel
;
204 SshFetchConnection() throws TransportException
{
205 super(TransportGitSsh
.this);
207 channel
= exec(getOptionUploadPack());
209 if (channel
.isConnected())
210 init(channel
.getInputStream(), channel
.getOutputStream());
212 throw new TransportException(uri
, errStream
.toString());
214 } catch (TransportException err
) {
217 } catch (IOException err
) {
219 throw new TransportException(uri
,
220 "remote hung up unexpectedly", err
);
224 readAdvertisedRefs();
225 } catch (NoRemoteRepositoryException notFound
) {
226 throw cleanNotFound(notFound
);
231 public void close() {
234 if (channel
!= null) {
236 if (channel
.isConnected())
237 channel
.disconnect();
245 class SshPushConnection
extends BasePackPushConnection
{
246 private ChannelExec channel
;
248 SshPushConnection() throws TransportException
{
249 super(TransportGitSsh
.this);
251 channel
= exec(getOptionReceivePack());
253 if (channel
.isConnected())
254 init(channel
.getInputStream(), channel
.getOutputStream());
256 throw new TransportException(uri
, errStream
.toString());
258 } catch (TransportException err
) {
261 } catch (IOException err
) {
263 throw new TransportException(uri
,
264 "remote hung up unexpectedly", err
);
268 readAdvertisedRefs();
269 } catch (NoRemoteRepositoryException notFound
) {
270 throw cleanNotFound(notFound
);
275 public void close() {
278 if (channel
!= null) {
280 if (channel
.isConnected())
281 channel
.disconnect();