Make TransportLocal.StreamRewritingThread static
[jgit.git] / org.spearce.jgit / src / org / spearce / jgit / transport / TransportLocal.java
blobcffdba1f9e42312c26771735b17451bdff6cd37a
1 /*
2 * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
3 * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5 * Copyright (C) 2008, Marek Zawirski <marek.zawirski@gmail.com>
7 * All rights reserved.
9 * Redistribution and use in source and binary forms, with or
10 * without modification, are permitted provided that the following
11 * conditions are met:
13 * - Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
16 * - Redistributions in binary form must reproduce the above
17 * copyright notice, this list of conditions and the following
18 * disclaimer in the documentation and/or other materials provided
19 * with the distribution.
21 * - Neither the name of the Git Development Community nor the
22 * names of its contributors may be used to endorse or promote
23 * products derived from this software without specific prior
24 * written permission.
26 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
27 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
28 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
29 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
30 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
31 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
32 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
33 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
34 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
35 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
36 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
37 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
38 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
41 package org.spearce.jgit.transport;
43 import java.io.File;
44 import java.io.IOException;
45 import java.io.InputStream;
46 import java.io.PipedInputStream;
47 import java.io.PipedOutputStream;
49 import org.spearce.jgit.errors.NotSupportedException;
50 import org.spearce.jgit.errors.TransportException;
51 import org.spearce.jgit.lib.Repository;
52 import org.spearce.jgit.util.FS;
54 /**
55 * Transport to access a local directory as though it were a remote peer.
56 * <p>
57 * This transport is suitable for use on the local system, where the caller has
58 * direct read or write access to the "remote" repository.
59 * <p>
60 * By default this transport works by spawning a helper thread within the same
61 * JVM, and processes the data transfer using a shared memory buffer between the
62 * calling thread and the helper thread. This is a pure-Java implementation
63 * which does not require forking an external process.
64 * <p>
65 * However, during {@link #openFetch()}, if the Transport has configured
66 * {@link Transport#getOptionUploadPack()} to be anything other than
67 * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
68 * implementation will fork and execute the external process, using an operating
69 * system pipe to transfer data.
70 * <p>
71 * Similarly, during {@link #openPush()}, if the Transport has configured
72 * {@link Transport#getOptionReceivePack()} to be anything other than
73 * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
74 * implementation will fork and execute the external process, using an operating
75 * system pipe to transfer data.
77 class TransportLocal extends Transport implements PackTransport {
78 private static final String PWD = ".";
80 static boolean canHandle(final URIish uri) {
81 if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
82 || uri.getPass() != null || uri.getPath() == null)
83 return false;
85 if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
86 return FS.resolve(new File(PWD), uri.getPath()).isDirectory();
87 return false;
90 private final File remoteGitDir;
92 TransportLocal(final Repository local, final URIish uri) {
93 super(local, uri);
95 File d = FS.resolve(new File(PWD), uri.getPath()).getAbsoluteFile();
96 if (new File(d, ".git").isDirectory())
97 d = new File(d, ".git");
98 remoteGitDir = d;
101 @Override
102 public FetchConnection openFetch() throws TransportException {
103 final String up = getOptionUploadPack();
104 if ("git-upload-pack".equals(up) || "git upload-pack".equals(up))
105 return new InternalLocalFetchConnection();
106 return new ForkLocalFetchConnection();
109 @Override
110 public PushConnection openPush() throws NotSupportedException,
111 TransportException {
112 final String rp = getOptionReceivePack();
113 if ("git-receive-pack".equals(rp) || "git receive-pack".equals(rp))
114 return new InternalLocalPushConnection();
115 return new ForkLocalPushConnection();
118 @Override
119 public void close() {
120 // Resources must be established per-connection.
123 protected Process startProcessWithErrStream(final String cmd)
124 throws TransportException {
125 try {
126 final String[] args;
127 final Process proc;
129 if (cmd.startsWith("git-")) {
130 args = new String[] { "git", cmd.substring(4), PWD };
131 } else {
132 final int gitspace = cmd.indexOf("git ");
133 if (gitspace >= 0) {
134 final String git = cmd.substring(0, gitspace + 3);
135 final String subcmd = cmd.substring(gitspace + 4);
136 args = new String[] { git, subcmd, PWD };
137 } else {
138 args = new String[] { cmd, PWD };
142 proc = Runtime.getRuntime().exec(args, null, remoteGitDir);
143 new StreamRewritingThread(cmd, proc.getErrorStream()).start();
144 return proc;
145 } catch (IOException err) {
146 throw new TransportException(uri, err.getMessage(), err);
150 class InternalLocalFetchConnection extends BasePackFetchConnection {
151 private Thread worker;
153 InternalLocalFetchConnection() throws TransportException {
154 super(TransportLocal.this);
156 final Repository dst;
157 try {
158 dst = new Repository(remoteGitDir);
159 } catch (IOException err) {
160 throw new TransportException(uri, "not a git directory");
163 final PipedInputStream in_r;
164 final PipedOutputStream in_w;
166 final PipedInputStream out_r;
167 final PipedOutputStream out_w;
168 try {
169 in_r = new PipedInputStream();
170 in_w = new PipedOutputStream(in_r);
172 out_r = new PipedInputStream() {
173 // The client (BasePackFetchConnection) can write
174 // a huge burst before it reads again. We need to
175 // force the buffer to be big enough, otherwise it
176 // will deadlock both threads.
178 buffer = new byte[MAX_CLIENT_BUFFER];
181 out_w = new PipedOutputStream(out_r);
182 } catch (IOException err) {
183 dst.close();
184 throw new TransportException(uri, "cannot connect pipes", err);
187 worker = new Thread("JGit-Upload-Pack") {
188 public void run() {
189 try {
190 final UploadPack rp = new UploadPack(dst);
191 rp.upload(out_r, in_w, null);
192 } catch (IOException err) {
193 // Client side of the pipes should report the problem.
194 err.printStackTrace();
195 } catch (RuntimeException err) {
196 // Clients side will notice we went away, and report.
197 err.printStackTrace();
198 } finally {
199 try {
200 out_r.close();
201 } catch (IOException e2) {
202 // Ignore close failure, we probably crashed above.
205 try {
206 in_w.close();
207 } catch (IOException e2) {
208 // Ignore close failure, we probably crashed above.
211 dst.close();
215 worker.start();
217 init(in_r, out_w);
218 readAdvertisedRefs();
221 @Override
222 public void close() {
223 super.close();
225 if (worker != null) {
226 try {
227 worker.join();
228 } catch (InterruptedException ie) {
229 // Stop waiting and return anyway.
230 } finally {
231 worker = null;
237 class ForkLocalFetchConnection extends BasePackFetchConnection {
238 private Process uploadPack;
240 ForkLocalFetchConnection() throws TransportException {
241 super(TransportLocal.this);
242 uploadPack = startProcessWithErrStream(getOptionUploadPack());
243 init(uploadPack.getInputStream(), uploadPack.getOutputStream());
244 readAdvertisedRefs();
247 @Override
248 public void close() {
249 super.close();
251 if (uploadPack != null) {
252 try {
253 uploadPack.waitFor();
254 } catch (InterruptedException ie) {
255 // Stop waiting and return anyway.
256 } finally {
257 uploadPack = null;
263 class InternalLocalPushConnection extends BasePackPushConnection {
264 private Thread worker;
266 InternalLocalPushConnection() throws TransportException {
267 super(TransportLocal.this);
269 final Repository dst;
270 try {
271 dst = new Repository(remoteGitDir);
272 } catch (IOException err) {
273 throw new TransportException(uri, "not a git directory");
276 final PipedInputStream in_r;
277 final PipedOutputStream in_w;
279 final PipedInputStream out_r;
280 final PipedOutputStream out_w;
281 try {
282 in_r = new PipedInputStream();
283 in_w = new PipedOutputStream(in_r);
285 out_r = new PipedInputStream();
286 out_w = new PipedOutputStream(out_r);
287 } catch (IOException err) {
288 dst.close();
289 throw new TransportException(uri, "cannot connect pipes", err);
292 worker = new Thread("JGit-Receive-Pack") {
293 public void run() {
294 try {
295 final ReceivePack rp = new ReceivePack(dst);
296 rp.receive(out_r, in_w, System.err);
297 } catch (IOException err) {
298 // Client side of the pipes should report the problem.
299 } catch (RuntimeException err) {
300 // Clients side will notice we went away, and report.
301 } finally {
302 try {
303 out_r.close();
304 } catch (IOException e2) {
305 // Ignore close failure, we probably crashed above.
308 try {
309 in_w.close();
310 } catch (IOException e2) {
311 // Ignore close failure, we probably crashed above.
314 dst.close();
318 worker.start();
320 init(in_r, out_w);
321 readAdvertisedRefs();
324 @Override
325 public void close() {
326 super.close();
328 if (worker != null) {
329 try {
330 worker.join();
331 } catch (InterruptedException ie) {
332 // Stop waiting and return anyway.
333 } finally {
334 worker = null;
340 class ForkLocalPushConnection extends BasePackPushConnection {
341 private Process receivePack;
343 ForkLocalPushConnection() throws TransportException {
344 super(TransportLocal.this);
345 receivePack = startProcessWithErrStream(getOptionReceivePack());
346 init(receivePack.getInputStream(), receivePack.getOutputStream());
347 readAdvertisedRefs();
350 @Override
351 public void close() {
352 super.close();
354 if (receivePack != null) {
355 try {
356 receivePack.waitFor();
357 } catch (InterruptedException ie) {
358 // Stop waiting and return anyway.
359 } finally {
360 receivePack = null;
366 static class StreamRewritingThread extends Thread {
367 private final InputStream in;
369 StreamRewritingThread(final String cmd, final InputStream in) {
370 super("JGit " + cmd + " Errors");
371 this.in = in;
374 public void run() {
375 final byte[] tmp = new byte[512];
376 try {
377 for (;;) {
378 final int n = in.read(tmp);
379 if (n < 0)
380 break;
381 System.err.write(tmp, 0, n);
382 System.err.flush();
384 } catch (IOException err) {
385 // Ignore errors reading errors.
386 } finally {
387 try {
388 in.close();
389 } catch (IOException err2) {
390 // Ignore errors closing the pipe.