Reduce multi-level buffered streams in transport code
[jgit/MarioXXX.git] / org.eclipse.jgit / src / org / eclipse / jgit / transport / TransportLocal.java
blobb9b9dbd0012e9d00209cad9385d2c57cc7e95f54
1 /*
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008-2010, Google Inc.
4 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
5 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
6 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
7 * and other copyright owners as documented in the project's IP log.
9 * This program and the accompanying materials are made available
10 * under the terms of the Eclipse Distribution License v1.0 which
11 * accompanies this distribution, is reproduced below, and is
12 * available at http://www.eclipse.org/org/documents/edl-v10.php
14 * All rights reserved.
16 * Redistribution and use in source and binary forms, with or
17 * without modification, are permitted provided that the following
18 * conditions are met:
20 * - Redistributions of source code must retain the above copyright
21 * notice, this list of conditions and the following disclaimer.
23 * - Redistributions in binary form must reproduce the above
24 * copyright notice, this list of conditions and the following
25 * disclaimer in the documentation and/or other materials provided
26 * with the distribution.
28 * - Neither the name of the Eclipse Foundation, Inc. nor the
29 * names of its contributors may be used to endorse or promote
30 * products derived from this software without specific prior
31 * written permission.
33 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
34 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
35 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
36 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
37 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
38 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
39 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
40 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
41 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
42 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
43 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
44 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
45 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
48 package org.eclipse.jgit.transport;
50 import java.io.BufferedInputStream;
51 import java.io.BufferedOutputStream;
52 import java.io.File;
53 import java.io.IOException;
54 import java.io.InputStream;
55 import java.io.OutputStream;
56 import java.io.PipedInputStream;
57 import java.io.PipedOutputStream;
59 import org.eclipse.jgit.errors.NotSupportedException;
60 import org.eclipse.jgit.errors.TransportException;
61 import org.eclipse.jgit.lib.Constants;
62 import org.eclipse.jgit.lib.Repository;
63 import org.eclipse.jgit.util.FS;
64 import org.eclipse.jgit.util.io.MessageWriter;
65 import org.eclipse.jgit.util.io.StreamCopyThread;
67 /**
68 * Transport to access a local directory as though it were a remote peer.
69 * <p>
70 * This transport is suitable for use on the local system, where the caller has
71 * direct read or write access to the "remote" repository.
72 * <p>
73 * By default this transport works by spawning a helper thread within the same
74 * JVM, and processes the data transfer using a shared memory buffer between the
75 * calling thread and the helper thread. This is a pure-Java implementation
76 * which does not require forking an external process.
77 * <p>
78 * However, during {@link #openFetch()}, if the Transport has configured
79 * {@link Transport#getOptionUploadPack()} to be anything other than
80 * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
81 * implementation will fork and execute the external process, using an operating
82 * system pipe to transfer data.
83 * <p>
84 * Similarly, during {@link #openPush()}, if the Transport has configured
85 * {@link Transport#getOptionReceivePack()} to be anything other than
86 * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
87 * implementation will fork and execute the external process, using an operating
88 * system pipe to transfer data.
90 class TransportLocal extends Transport implements PackTransport {
91 private static final String PWD = ".";
93 static boolean canHandle(final URIish uri) {
94 if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
95 || uri.getPass() != null || uri.getPath() == null)
96 return false;
98 if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
99 return FS.resolve(new File(PWD), uri.getPath()).isDirectory();
100 return false;
103 private final File remoteGitDir;
105 TransportLocal(final Repository local, final URIish uri) {
106 super(local, uri);
108 File d = FS.resolve(new File(PWD), uri.getPath()).getAbsoluteFile();
109 if (new File(d, Constants.DOT_GIT).isDirectory())
110 d = new File(d, Constants.DOT_GIT);
111 remoteGitDir = d;
114 @Override
115 public FetchConnection openFetch() throws TransportException {
116 final String up = getOptionUploadPack();
117 if ("git-upload-pack".equals(up) || "git upload-pack".equals(up))
118 return new InternalLocalFetchConnection();
119 return new ForkLocalFetchConnection();
122 @Override
123 public PushConnection openPush() throws NotSupportedException,
124 TransportException {
125 final String rp = getOptionReceivePack();
126 if ("git-receive-pack".equals(rp) || "git receive-pack".equals(rp))
127 return new InternalLocalPushConnection();
128 return new ForkLocalPushConnection();
131 @Override
132 public void close() {
133 // Resources must be established per-connection.
136 protected Process spawn(final String cmd)
137 throws TransportException {
138 try {
139 final String[] args;
141 if (cmd.startsWith("git-")) {
142 args = new String[] { "git", cmd.substring(4), PWD };
143 } else {
144 final int gitspace = cmd.indexOf("git ");
145 if (gitspace >= 0) {
146 final String git = cmd.substring(0, gitspace + 3);
147 final String subcmd = cmd.substring(gitspace + 4);
148 args = new String[] { git, subcmd, PWD };
149 } else {
150 args = new String[] { cmd, PWD };
154 return Runtime.getRuntime().exec(args, null, remoteGitDir);
155 } catch (IOException err) {
156 throw new TransportException(uri, err.getMessage(), err);
160 class InternalLocalFetchConnection extends BasePackFetchConnection {
161 private Thread worker;
163 InternalLocalFetchConnection() throws TransportException {
164 super(TransportLocal.this);
166 final Repository dst;
167 try {
168 dst = new Repository(remoteGitDir);
169 } catch (IOException err) {
170 throw new TransportException(uri, "not a git directory");
173 final PipedInputStream in_r;
174 final PipedOutputStream in_w;
176 final PipedInputStream out_r;
177 final PipedOutputStream out_w;
178 try {
179 in_r = new PipedInputStream();
180 in_w = new PipedOutputStream(in_r);
182 out_r = new PipedInputStream() {
183 // The client (BasePackFetchConnection) can write
184 // a huge burst before it reads again. We need to
185 // force the buffer to be big enough, otherwise it
186 // will deadlock both threads.
188 buffer = new byte[MIN_CLIENT_BUFFER];
191 out_w = new PipedOutputStream(out_r);
192 } catch (IOException err) {
193 dst.close();
194 throw new TransportException(uri, "cannot connect pipes", err);
197 worker = new Thread("JGit-Upload-Pack") {
198 public void run() {
199 try {
200 final UploadPack rp = new UploadPack(dst);
201 rp.upload(out_r, in_w, null);
202 } catch (IOException err) {
203 // Client side of the pipes should report the problem.
204 err.printStackTrace();
205 } catch (RuntimeException err) {
206 // Clients side will notice we went away, and report.
207 err.printStackTrace();
208 } finally {
209 try {
210 out_r.close();
211 } catch (IOException e2) {
212 // Ignore close failure, we probably crashed above.
215 try {
216 in_w.close();
217 } catch (IOException e2) {
218 // Ignore close failure, we probably crashed above.
221 dst.close();
225 worker.start();
227 init(in_r, out_w);
228 readAdvertisedRefs();
231 @Override
232 public void close() {
233 super.close();
235 if (worker != null) {
236 try {
237 worker.join();
238 } catch (InterruptedException ie) {
239 // Stop waiting and return anyway.
240 } finally {
241 worker = null;
247 class ForkLocalFetchConnection extends BasePackFetchConnection {
248 private Process uploadPack;
250 private Thread errorReaderThread;
252 ForkLocalFetchConnection() throws TransportException {
253 super(TransportLocal.this);
255 final MessageWriter msg = new MessageWriter();
256 setMessageWriter(msg);
258 uploadPack = spawn(getOptionUploadPack());
260 final InputStream upErr = uploadPack.getErrorStream();
261 errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
262 errorReaderThread.start();
264 InputStream upIn = uploadPack.getInputStream();
265 OutputStream upOut = uploadPack.getOutputStream();
267 upIn = new BufferedInputStream(upIn);
268 upOut = new BufferedOutputStream(upOut);
270 init(upIn, upOut);
271 readAdvertisedRefs();
274 @Override
275 public void close() {
276 super.close();
278 if (uploadPack != null) {
279 try {
280 uploadPack.waitFor();
281 } catch (InterruptedException ie) {
282 // Stop waiting and return anyway.
283 } finally {
284 uploadPack = null;
288 if (errorReaderThread != null) {
289 try {
290 errorReaderThread.join();
291 } catch (InterruptedException e) {
292 // Stop waiting and return anyway.
293 } finally {
294 errorReaderThread = null;
300 class InternalLocalPushConnection extends BasePackPushConnection {
301 private Thread worker;
303 InternalLocalPushConnection() throws TransportException {
304 super(TransportLocal.this);
306 final Repository dst;
307 try {
308 dst = new Repository(remoteGitDir);
309 } catch (IOException err) {
310 throw new TransportException(uri, "not a git directory");
313 final PipedInputStream in_r;
314 final PipedOutputStream in_w;
316 final PipedInputStream out_r;
317 final PipedOutputStream out_w;
318 try {
319 in_r = new PipedInputStream();
320 in_w = new PipedOutputStream(in_r);
322 out_r = new PipedInputStream();
323 out_w = new PipedOutputStream(out_r);
324 } catch (IOException err) {
325 dst.close();
326 throw new TransportException(uri, "cannot connect pipes", err);
329 worker = new Thread("JGit-Receive-Pack") {
330 public void run() {
331 try {
332 final ReceivePack rp = new ReceivePack(dst);
333 rp.receive(out_r, in_w, System.err);
334 } catch (IOException err) {
335 // Client side of the pipes should report the problem.
336 } catch (RuntimeException err) {
337 // Clients side will notice we went away, and report.
338 } finally {
339 try {
340 out_r.close();
341 } catch (IOException e2) {
342 // Ignore close failure, we probably crashed above.
345 try {
346 in_w.close();
347 } catch (IOException e2) {
348 // Ignore close failure, we probably crashed above.
351 dst.close();
355 worker.start();
357 init(in_r, out_w);
358 readAdvertisedRefs();
361 @Override
362 public void close() {
363 super.close();
365 if (worker != null) {
366 try {
367 worker.join();
368 } catch (InterruptedException ie) {
369 // Stop waiting and return anyway.
370 } finally {
371 worker = null;
377 class ForkLocalPushConnection extends BasePackPushConnection {
378 private Process receivePack;
380 private Thread errorReaderThread;
382 ForkLocalPushConnection() throws TransportException {
383 super(TransportLocal.this);
385 final MessageWriter msg = new MessageWriter();
386 setMessageWriter(msg);
388 receivePack = spawn(getOptionReceivePack());
390 final InputStream rpErr = receivePack.getErrorStream();
391 errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
392 errorReaderThread.start();
394 InputStream rpIn = receivePack.getInputStream();
395 OutputStream rpOut = receivePack.getOutputStream();
397 rpIn = new BufferedInputStream(rpIn);
398 rpOut = new BufferedOutputStream(rpOut);
400 init(rpIn, rpOut);
401 readAdvertisedRefs();
404 @Override
405 public void close() {
406 super.close();
408 if (receivePack != null) {
409 try {
410 receivePack.waitFor();
411 } catch (InterruptedException ie) {
412 // Stop waiting and return anyway.
413 } finally {
414 receivePack = null;
418 if (errorReaderThread != null) {
419 try {
420 errorReaderThread.join();
421 } catch (InterruptedException e) {
422 // Stop waiting and return anyway.
423 } finally {
424 errorReaderThread = null;