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
.net
.ConnectException
;
44 import java
.net
.UnknownHostException
;
46 import org
.spearce
.jgit
.errors
.TransportException
;
47 import org
.spearce
.jgit
.lib
.Repository
;
49 import com
.jcraft
.jsch
.ChannelExec
;
50 import com
.jcraft
.jsch
.JSchException
;
51 import com
.jcraft
.jsch
.Session
;
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 class TransportGitSsh
extends 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 final SshSessionFactory sch
;
82 TransportGitSsh(final Repository local
, final URIish uri
) {
84 sch
= SshSessionFactory
.getInstance();
88 public FetchConnection
openFetch() throws TransportException
{
89 return new SshFetchConnection();
93 public PushConnection
openPush() throws TransportException
{
94 return new SshPushConnection();
97 private static void sqMinimal(final StringBuilder cmd
, final String val
) {
98 if (val
.matches("^[a-zA-Z0-9._/-]*$")) {
99 // If the string matches only generally safe characters
100 // that the shell is not going to evaluate specially we
101 // should leave the string unquoted. Not all systems
102 // actually run a shell and over-quoting confuses them
103 // when it comes to the command name.
111 private static void sqAlways(final StringBuilder cmd
, final String val
) {
115 private static void sq(final StringBuilder cmd
, final String val
) {
118 if (val
.length() == 0)
120 if (val
.matches("^~[A-Za-z0-9_-]+$")) {
121 // If the string is just "~user" we can assume they
122 // mean "~user/" and evaluate it within the shell.
129 if (val
.matches("^~[A-Za-z0-9_-]*/.*$")) {
130 // If the string is of "~/path" or "~user/path"
131 // we must not escape ~/ or ~user/ from the shell
132 // as we need that portion to be evaluated.
134 i
= val
.indexOf('/') + 1;
135 cmd
.append(val
.substring(0, i
));
136 if (i
== val
.length())
141 for (; i
< val
.length(); i
++) {
142 final char c
= val
.charAt(i
);
153 Session
openSession() throws TransportException
{
154 final String user
= uri
.getUser();
155 final String pass
= uri
.getPass();
156 final String host
= uri
.getHost();
157 final int port
= uri
.getPort();
159 final Session session
;
160 session
= sch
.getSession(user
, pass
, host
, port
);
161 if (!session
.isConnected())
164 } catch (JSchException je
) {
165 final String us
= uri
.toString();
166 final Throwable c
= je
.getCause();
167 if (c
instanceof UnknownHostException
)
168 throw new TransportException(us
+ ": Unknown host");
169 if (c
instanceof ConnectException
)
170 throw new TransportException(us
+ ": " + c
.getMessage());
171 throw new TransportException(us
+ ": " + je
.getMessage(), je
);
175 ChannelExec
exec(final Session sock
, final String exe
)
176 throws TransportException
{
178 final ChannelExec channel
= (ChannelExec
) sock
.openChannel("exec");
179 String path
= uri
.getPath();
180 if (uri
.getScheme() != null && uri
.getPath().startsWith("/~"))
181 path
= (uri
.getPath().substring(1));
183 final StringBuilder cmd
= new StringBuilder();
187 channel
.setCommand(cmd
.toString());
188 channel
.setErrStream(System
.err
);
191 } catch (JSchException je
) {
192 throw new TransportException(uri
.toString() + ": "
193 + je
.getMessage(), je
);
197 class SshFetchConnection
extends BasePackFetchConnection
{
198 private Session session
;
200 private ChannelExec channel
;
202 SshFetchConnection() throws TransportException
{
203 super(TransportGitSsh
.this);
205 session
= openSession();
206 channel
= exec(session
, getOptionUploadPack());
207 init(channel
.getInputStream(), channel
.getOutputStream());
208 } catch (TransportException err
) {
211 } catch (IOException err
) {
213 throw new TransportException(uri
.toString()
214 + ": remote hung up unexpectedly", err
);
216 readAdvertisedRefs();
220 public void close() {
223 if (channel
!= null) {
225 if (channel
.isConnected())
226 channel
.disconnect();
232 if (session
!= null) {
234 sch
.releaseSession(session
);
242 class SshPushConnection
extends BasePackPushConnection
{
243 private Session session
;
245 private ChannelExec channel
;
247 SshPushConnection() throws TransportException
{
248 super(TransportGitSsh
.this);
250 session
= openSession();
251 channel
= exec(session
, getOptionReceivePack());
252 init(channel
.getInputStream(), channel
.getOutputStream());
253 } catch (TransportException err
) {
256 } catch (IOException err
) {
258 throw new TransportException(uri
.toString()
259 + ": remote hung up unexpectedly", err
);
261 readAdvertisedRefs();
265 public void close() {
268 if (channel
!= null) {
270 if (channel
.isConnected())
271 channel
.disconnect();
277 if (session
!= null) {
279 sch
.releaseSession(session
);