You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

TransportGitSsh.java 10KB

Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
Capture non-progress side band #2 messages and put in result Any messages received on side band #2 that aren't scraped as a progress message into our ProgressMonitor are now forwarded to a buffer which is later included into the OperationResult object. Application callers can use this buffer to present the additional messages from the remote peer after the push or fetch operation has concluded. The smart push connections using the native send-pack/receive-pack protocol now request side-band-64k capability if it is available and forward any messages received through that channel onto this message buffer. This makes hook messages available over smart HTTP, or even over SSH. The SSH transport was modified to redirect the remote command's stderr stream into the message buffer, interleaved with any data received over side band #2. Due to buffering between these two different channels in the SSH channel mux itself the order of any writes between the two cannot be ensured, but it tries to stay close. The local fork transport was also modified to redirect the local receive-pack's stderr into the message buffer, rather than going to the invoking JVM's System.err. This gives applications a chance to log the local error messages, rather than needing to redirect their JVM's stderr before startup. To keep things simple, the application has to wait for the entire operation to complete before it can see the messages. This may be a downside if the user is trying to debug a remote hook that is blocking indefinitely, the user would need to abort the connection before they can inspect the message buffer in any sort of UI built on top of JGit. Change-Id: Ibc215f4569e63071da5b7e5c6674ce924ae39e11 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
14 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. /*
  2. * Copyright (C) 2008-2010, Google Inc.
  3. * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
  4. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
  5. * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
  6. * and other copyright owners as documented in the project's IP log.
  7. *
  8. * This program and the accompanying materials are made available
  9. * under the terms of the Eclipse Distribution License v1.0 which
  10. * accompanies this distribution, is reproduced below, and is
  11. * available at http://www.eclipse.org/org/documents/edl-v10.php
  12. *
  13. * All rights reserved.
  14. *
  15. * Redistribution and use in source and binary forms, with or
  16. * without modification, are permitted provided that the following
  17. * conditions are met:
  18. *
  19. * - Redistributions of source code must retain the above copyright
  20. * notice, this list of conditions and the following disclaimer.
  21. *
  22. * - Redistributions in binary form must reproduce the above
  23. * copyright notice, this list of conditions and the following
  24. * disclaimer in the documentation and/or other materials provided
  25. * with the distribution.
  26. *
  27. * - Neither the name of the Eclipse Foundation, Inc. nor the
  28. * names of its contributors may be used to endorse or promote
  29. * products derived from this software without specific prior
  30. * written permission.
  31. *
  32. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  33. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  34. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  35. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  36. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  37. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  38. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  39. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  40. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  41. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  42. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  43. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  44. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  45. */
  46. package org.eclipse.jgit.transport;
  47. import java.io.IOException;
  48. import java.io.InputStream;
  49. import java.io.OutputStream;
  50. import java.io.PipedInputStream;
  51. import java.io.PipedOutputStream;
  52. import org.eclipse.jgit.errors.NoRemoteRepositoryException;
  53. import org.eclipse.jgit.errors.TransportException;
  54. import org.eclipse.jgit.lib.Repository;
  55. import org.eclipse.jgit.util.QuotedString;
  56. import org.eclipse.jgit.util.io.MessageWriter;
  57. import org.eclipse.jgit.util.io.StreamCopyThread;
  58. import com.jcraft.jsch.ChannelExec;
  59. import com.jcraft.jsch.JSchException;
  60. /**
  61. * Transport through an SSH tunnel.
  62. * <p>
  63. * The SSH transport requires the remote side to have Git installed, as the
  64. * transport logs into the remote system and executes a Git helper program on
  65. * the remote side to read (or write) the remote repository's files.
  66. * <p>
  67. * This transport does not support direct SCP style of copying files, as it
  68. * assumes there are Git specific smarts on the remote side to perform object
  69. * enumeration, save file modification and hook execution.
  70. */
  71. public class TransportGitSsh extends SshTransport implements PackTransport {
  72. static boolean canHandle(final URIish uri) {
  73. if (!uri.isRemote())
  74. return false;
  75. final String scheme = uri.getScheme();
  76. if ("ssh".equals(scheme))
  77. return true;
  78. if ("ssh+git".equals(scheme))
  79. return true;
  80. if ("git+ssh".equals(scheme))
  81. return true;
  82. if (scheme == null && uri.getHost() != null && uri.getPath() != null)
  83. return true;
  84. return false;
  85. }
  86. TransportGitSsh(final Repository local, final URIish uri) {
  87. super(local, uri);
  88. }
  89. @Override
  90. public FetchConnection openFetch() throws TransportException {
  91. return new SshFetchConnection();
  92. }
  93. @Override
  94. public PushConnection openPush() throws TransportException {
  95. return new SshPushConnection();
  96. }
  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.
  104. //
  105. cmd.append(val);
  106. } else {
  107. sq(cmd, val);
  108. }
  109. }
  110. private static void sqAlways(final StringBuilder cmd, final String val) {
  111. sq(cmd, val);
  112. }
  113. private static void sq(final StringBuilder cmd, final String val) {
  114. if (val.length() > 0)
  115. cmd.append(QuotedString.BOURNE.quote(val));
  116. }
  117. private String commandFor(final String exe) {
  118. String path = uri.getPath();
  119. if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
  120. path = (uri.getPath().substring(1));
  121. final StringBuilder cmd = new StringBuilder();
  122. final int gitspace = exe.indexOf("git ");
  123. if (gitspace >= 0) {
  124. sqMinimal(cmd, exe.substring(0, gitspace + 3));
  125. cmd.append(' ');
  126. sqMinimal(cmd, exe.substring(gitspace + 4));
  127. } else
  128. sqMinimal(cmd, exe);
  129. cmd.append(' ');
  130. sqAlways(cmd, path);
  131. return cmd.toString();
  132. }
  133. ChannelExec exec(final String exe) throws TransportException {
  134. initSession();
  135. final int tms = getTimeout() > 0 ? getTimeout() * 1000 : 0;
  136. try {
  137. final ChannelExec channel = (ChannelExec) sock.openChannel("exec");
  138. channel.setCommand(commandFor(exe));
  139. channel.connect(tms);
  140. return channel;
  141. } catch (JSchException je) {
  142. throw new TransportException(uri, je.getMessage(), je);
  143. }
  144. }
  145. void checkExecFailure(int status, String exe, String why)
  146. throws TransportException {
  147. if (status == 127) {
  148. IOException cause = null;
  149. if (why != null && why.length() > 0)
  150. cause = new IOException(why);
  151. throw new TransportException(uri, "cannot execute: "
  152. + commandFor(exe), cause);
  153. }
  154. }
  155. NoRemoteRepositoryException cleanNotFound(NoRemoteRepositoryException nf,
  156. String why) {
  157. if (why == null || why.length() == 0)
  158. return nf;
  159. String path = uri.getPath();
  160. if (uri.getScheme() != null && uri.getPath().startsWith("/~"))
  161. path = uri.getPath().substring(1);
  162. final StringBuilder pfx = new StringBuilder();
  163. pfx.append("fatal: ");
  164. sqAlways(pfx, path);
  165. pfx.append(": ");
  166. if (why.startsWith(pfx.toString()))
  167. why = why.substring(pfx.length());
  168. return new NoRemoteRepositoryException(uri, why);
  169. }
  170. // JSch won't let us interrupt writes when we use our InterruptTimer to
  171. // break out of a long-running write operation. To work around that we
  172. // spawn a background thread to shuttle data through a pipe, as we can
  173. // issue an interrupted write out of that. Its slower, so we only use
  174. // this route if there is a timeout.
  175. //
  176. private OutputStream outputStream(ChannelExec channel) throws IOException {
  177. final OutputStream out = channel.getOutputStream();
  178. if (getTimeout() <= 0)
  179. return out;
  180. final PipedInputStream pipeIn = new PipedInputStream();
  181. final StreamCopyThread copyThread = new StreamCopyThread(pipeIn, out);
  182. final PipedOutputStream pipeOut = new PipedOutputStream(pipeIn) {
  183. @Override
  184. public void flush() throws IOException {
  185. super.flush();
  186. copyThread.flush();
  187. }
  188. @Override
  189. public void close() throws IOException {
  190. super.close();
  191. try {
  192. copyThread.join(getTimeout() * 1000);
  193. } catch (InterruptedException e) {
  194. // Just wake early, the thread will terminate anyway.
  195. }
  196. }
  197. };
  198. copyThread.start();
  199. return pipeOut;
  200. }
  201. class SshFetchConnection extends BasePackFetchConnection {
  202. private ChannelExec channel;
  203. private Thread errorThread;
  204. private int exitStatus;
  205. SshFetchConnection() throws TransportException {
  206. super(TransportGitSsh.this);
  207. try {
  208. final MessageWriter msg = new MessageWriter();
  209. setMessageWriter(msg);
  210. channel = exec(getOptionUploadPack());
  211. if (!channel.isConnected())
  212. throw new TransportException(uri, "connection failed");
  213. final InputStream upErr = channel.getErrStream();
  214. errorThread = new StreamCopyThread(upErr, msg.getRawStream());
  215. errorThread.start();
  216. init(channel.getInputStream(), outputStream(channel));
  217. } catch (TransportException err) {
  218. close();
  219. throw err;
  220. } catch (IOException err) {
  221. close();
  222. throw new TransportException(uri,
  223. "remote hung up unexpectedly", err);
  224. }
  225. try {
  226. readAdvertisedRefs();
  227. } catch (NoRemoteRepositoryException notFound) {
  228. final String msgs = getMessages();
  229. checkExecFailure(exitStatus, getOptionUploadPack(), msgs);
  230. throw cleanNotFound(notFound, msgs);
  231. }
  232. }
  233. @Override
  234. public void close() {
  235. endOut();
  236. if (errorThread != null) {
  237. try {
  238. errorThread.join();
  239. } catch (InterruptedException e) {
  240. // Stop waiting and return anyway.
  241. } finally {
  242. errorThread = null;
  243. }
  244. }
  245. super.close();
  246. if (channel != null) {
  247. try {
  248. exitStatus = channel.getExitStatus();
  249. if (channel.isConnected())
  250. channel.disconnect();
  251. } finally {
  252. channel = null;
  253. }
  254. }
  255. }
  256. }
  257. class SshPushConnection extends BasePackPushConnection {
  258. private ChannelExec channel;
  259. private Thread errorThread;
  260. private int exitStatus;
  261. SshPushConnection() throws TransportException {
  262. super(TransportGitSsh.this);
  263. try {
  264. final MessageWriter msg = new MessageWriter();
  265. setMessageWriter(msg);
  266. channel = exec(getOptionReceivePack());
  267. if (!channel.isConnected())
  268. throw new TransportException(uri, "connection failed");
  269. final InputStream rpErr = channel.getErrStream();
  270. errorThread = new StreamCopyThread(rpErr, msg.getRawStream());
  271. errorThread.start();
  272. init(channel.getInputStream(), outputStream(channel));
  273. } catch (TransportException err) {
  274. close();
  275. throw err;
  276. } catch (IOException err) {
  277. close();
  278. throw new TransportException(uri,
  279. "remote hung up unexpectedly", err);
  280. }
  281. try {
  282. readAdvertisedRefs();
  283. } catch (NoRemoteRepositoryException notFound) {
  284. final String msgs = getMessages();
  285. checkExecFailure(exitStatus, getOptionReceivePack(), msgs);
  286. throw cleanNotFound(notFound, msgs);
  287. }
  288. }
  289. @Override
  290. public void close() {
  291. endOut();
  292. if (errorThread != null) {
  293. try {
  294. errorThread.join();
  295. } catch (InterruptedException e) {
  296. // Stop waiting and return anyway.
  297. } finally {
  298. errorThread = null;
  299. }
  300. }
  301. super.close();
  302. if (channel != null) {
  303. try {
  304. exitStatus = channel.getExitStatus();
  305. if (channel.isConnected())
  306. channel.disconnect();
  307. } finally {
  308. channel = null;
  309. }
  310. }
  311. }
  312. }
  313. }