Transport* - general support for push() and implementations
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / transport / TransportGitSsh.java
blob55be4f630410419b1b9ed59768589113880c767d
1 /*
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>
6 * All rights reserved.
8 * Redistribution and use in source and binary forms, with or
9 * without modification, are permitted provided that the following
10 * conditions are met:
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
23 * written permission.
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;
53 /**
54 * Transport through an SSH tunnel.
55 * <p>
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.
59 * <p>
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) {
66 if (!uri.isRemote())
67 return false;
68 final String scheme = uri.getScheme();
69 if ("ssh".equals(scheme))
70 return true;
71 if ("ssh+git".equals(scheme))
72 return true;
73 if ("git+ssh".equals(scheme))
74 return true;
75 if (scheme == null && uri.getHost() != null && uri.getPath() != null)
76 return true;
77 return false;
80 final SshSessionFactory sch;
82 TransportGitSsh(final Repository local, final URIish uri) {
83 super(local, uri);
84 sch = SshSessionFactory.getInstance();
87 @Override
88 public FetchConnection openFetch() throws TransportException {
89 return new SshFetchConnection();
92 @Override
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.
105 cmd.append(val);
106 } else {
107 sq(cmd, val);
111 private static void sqAlways(final StringBuilder cmd, final String val) {
112 sq(cmd, val);
115 private static void sq(final StringBuilder cmd, final String val) {
116 int i = 0;
118 if (val.length() == 0)
119 return;
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.
124 cmd.append(val);
125 cmd.append('/');
126 return;
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())
137 return;
140 cmd.append('\'');
141 for (; i < val.length(); i++) {
142 final char c = val.charAt(i);
143 if (c == '\'')
144 cmd.append("'\\''");
145 else if (c == '!')
146 cmd.append("'\\!'");
147 else
148 cmd.append(c);
150 cmd.append('\'');
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();
158 try {
159 final Session session;
160 session = sch.getSession(user, pass, host, port);
161 if (!session.isConnected())
162 session.connect();
163 return session;
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 {
177 try {
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();
184 sqMinimal(cmd, exe);
185 cmd.append(' ');
186 sqAlways(cmd, path);
187 channel.setCommand(cmd.toString());
188 channel.setErrStream(System.err);
189 channel.connect();
190 return channel;
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);
204 try {
205 session = openSession();
206 channel = exec(session, getOptionUploadPack());
207 init(channel.getInputStream(), channel.getOutputStream());
208 } catch (TransportException err) {
209 close();
210 throw err;
211 } catch (IOException err) {
212 close();
213 throw new TransportException(uri.toString()
214 + ": remote hung up unexpectedly", err);
216 readAdvertisedRefs();
219 @Override
220 public void close() {
221 super.close();
223 if (channel != null) {
224 try {
225 if (channel.isConnected())
226 channel.disconnect();
227 } finally {
228 channel = null;
232 if (session != null) {
233 try {
234 sch.releaseSession(session);
235 } finally {
236 session = null;
242 class SshPushConnection extends BasePackPushConnection {
243 private Session session;
245 private ChannelExec channel;
247 SshPushConnection() throws TransportException {
248 super(TransportGitSsh.this);
249 try {
250 session = openSession();
251 channel = exec(session, getOptionReceivePack());
252 init(channel.getInputStream(), channel.getOutputStream());
253 } catch (TransportException err) {
254 close();
255 throw err;
256 } catch (IOException err) {
257 close();
258 throw new TransportException(uri.toString()
259 + ": remote hung up unexpectedly", err);
261 readAdvertisedRefs();
264 @Override
265 public void close() {
266 super.close();
268 if (channel != null) {
269 try {
270 if (channel.isConnected())
271 channel.disconnect();
272 } finally {
273 channel = null;
277 if (session != null) {
278 try {
279 sch.releaseSession(session);
280 } finally {
281 session = null;