Capture non-progress side band #2 messages and put in result
[jgit/MarioXXX.git] / org.eclipse.jgit / src / org / eclipse / jgit / transport / TransportLocal.java
bloba9bdcd809155170fcc9b5d5918efc712257e2584
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.File;
51 import java.io.IOException;
52 import java.io.InputStream;
53 import java.io.OutputStream;
54 import java.io.PipedInputStream;
55 import java.io.PipedOutputStream;
57 import org.eclipse.jgit.errors.NotSupportedException;
58 import org.eclipse.jgit.errors.TransportException;
59 import org.eclipse.jgit.lib.Constants;
60 import org.eclipse.jgit.lib.Repository;
61 import org.eclipse.jgit.util.FS;
62 import org.eclipse.jgit.util.io.MessageWriter;
63 import org.eclipse.jgit.util.io.StreamCopyThread;
65 /**
66 * Transport to access a local directory as though it were a remote peer.
67 * <p>
68 * This transport is suitable for use on the local system, where the caller has
69 * direct read or write access to the "remote" repository.
70 * <p>
71 * By default this transport works by spawning a helper thread within the same
72 * JVM, and processes the data transfer using a shared memory buffer between the
73 * calling thread and the helper thread. This is a pure-Java implementation
74 * which does not require forking an external process.
75 * <p>
76 * However, during {@link #openFetch()}, if the Transport has configured
77 * {@link Transport#getOptionUploadPack()} to be anything other than
78 * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
79 * implementation will fork and execute the external process, using an operating
80 * system pipe to transfer data.
81 * <p>
82 * Similarly, during {@link #openPush()}, if the Transport has configured
83 * {@link Transport#getOptionReceivePack()} to be anything other than
84 * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
85 * implementation will fork and execute the external process, using an operating
86 * system pipe to transfer data.
88 class TransportLocal extends Transport implements PackTransport {
89 private static final String PWD = ".";
91 static boolean canHandle(final URIish uri) {
92 if (uri.getHost() != null || uri.getPort() > 0 || uri.getUser() != null
93 || uri.getPass() != null || uri.getPath() == null)
94 return false;
96 if ("file".equals(uri.getScheme()) || uri.getScheme() == null)
97 return FS.resolve(new File(PWD), uri.getPath()).isDirectory();
98 return false;
101 private final File remoteGitDir;
103 TransportLocal(final Repository local, final URIish uri) {
104 super(local, uri);
106 File d = FS.resolve(new File(PWD), uri.getPath()).getAbsoluteFile();
107 if (new File(d, Constants.DOT_GIT).isDirectory())
108 d = new File(d, Constants.DOT_GIT);
109 remoteGitDir = d;
112 @Override
113 public FetchConnection openFetch() throws TransportException {
114 final String up = getOptionUploadPack();
115 if ("git-upload-pack".equals(up) || "git upload-pack".equals(up))
116 return new InternalLocalFetchConnection();
117 return new ForkLocalFetchConnection();
120 @Override
121 public PushConnection openPush() throws NotSupportedException,
122 TransportException {
123 final String rp = getOptionReceivePack();
124 if ("git-receive-pack".equals(rp) || "git receive-pack".equals(rp))
125 return new InternalLocalPushConnection();
126 return new ForkLocalPushConnection();
129 @Override
130 public void close() {
131 // Resources must be established per-connection.
134 protected Process spawn(final String cmd)
135 throws TransportException {
136 try {
137 final String[] args;
139 if (cmd.startsWith("git-")) {
140 args = new String[] { "git", cmd.substring(4), PWD };
141 } else {
142 final int gitspace = cmd.indexOf("git ");
143 if (gitspace >= 0) {
144 final String git = cmd.substring(0, gitspace + 3);
145 final String subcmd = cmd.substring(gitspace + 4);
146 args = new String[] { git, subcmd, PWD };
147 } else {
148 args = new String[] { cmd, PWD };
152 return Runtime.getRuntime().exec(args, null, remoteGitDir);
153 } catch (IOException err) {
154 throw new TransportException(uri, err.getMessage(), err);
158 class InternalLocalFetchConnection extends BasePackFetchConnection {
159 private Thread worker;
161 InternalLocalFetchConnection() throws TransportException {
162 super(TransportLocal.this);
164 final Repository dst;
165 try {
166 dst = new Repository(remoteGitDir);
167 } catch (IOException err) {
168 throw new TransportException(uri, "not a git directory");
171 final PipedInputStream in_r;
172 final PipedOutputStream in_w;
174 final PipedInputStream out_r;
175 final PipedOutputStream out_w;
176 try {
177 in_r = new PipedInputStream();
178 in_w = new PipedOutputStream(in_r);
180 out_r = new PipedInputStream() {
181 // The client (BasePackFetchConnection) can write
182 // a huge burst before it reads again. We need to
183 // force the buffer to be big enough, otherwise it
184 // will deadlock both threads.
186 buffer = new byte[MIN_CLIENT_BUFFER];
189 out_w = new PipedOutputStream(out_r);
190 } catch (IOException err) {
191 dst.close();
192 throw new TransportException(uri, "cannot connect pipes", err);
195 worker = new Thread("JGit-Upload-Pack") {
196 public void run() {
197 try {
198 final UploadPack rp = new UploadPack(dst);
199 rp.upload(out_r, in_w, null);
200 } catch (IOException err) {
201 // Client side of the pipes should report the problem.
202 err.printStackTrace();
203 } catch (RuntimeException err) {
204 // Clients side will notice we went away, and report.
205 err.printStackTrace();
206 } finally {
207 try {
208 out_r.close();
209 } catch (IOException e2) {
210 // Ignore close failure, we probably crashed above.
213 try {
214 in_w.close();
215 } catch (IOException e2) {
216 // Ignore close failure, we probably crashed above.
219 dst.close();
223 worker.start();
225 init(in_r, out_w);
226 readAdvertisedRefs();
229 @Override
230 public void close() {
231 super.close();
233 if (worker != null) {
234 try {
235 worker.join();
236 } catch (InterruptedException ie) {
237 // Stop waiting and return anyway.
238 } finally {
239 worker = null;
245 class ForkLocalFetchConnection extends BasePackFetchConnection {
246 private Process uploadPack;
248 private Thread errorReaderThread;
250 ForkLocalFetchConnection() throws TransportException {
251 super(TransportLocal.this);
253 final MessageWriter msg = new MessageWriter();
254 setMessageWriter(msg);
256 uploadPack = spawn(getOptionUploadPack());
258 final InputStream upErr = uploadPack.getErrorStream();
259 errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
260 errorReaderThread.start();
262 final InputStream upIn = uploadPack.getInputStream();
263 final OutputStream upOut = uploadPack.getOutputStream();
264 init(upIn, upOut);
265 readAdvertisedRefs();
268 @Override
269 public void close() {
270 super.close();
272 if (uploadPack != null) {
273 try {
274 uploadPack.waitFor();
275 } catch (InterruptedException ie) {
276 // Stop waiting and return anyway.
277 } finally {
278 uploadPack = null;
282 if (errorReaderThread != null) {
283 try {
284 errorReaderThread.join();
285 } catch (InterruptedException e) {
286 // Stop waiting and return anyway.
287 } finally {
288 errorReaderThread = null;
294 class InternalLocalPushConnection extends BasePackPushConnection {
295 private Thread worker;
297 InternalLocalPushConnection() throws TransportException {
298 super(TransportLocal.this);
300 final Repository dst;
301 try {
302 dst = new Repository(remoteGitDir);
303 } catch (IOException err) {
304 throw new TransportException(uri, "not a git directory");
307 final PipedInputStream in_r;
308 final PipedOutputStream in_w;
310 final PipedInputStream out_r;
311 final PipedOutputStream out_w;
312 try {
313 in_r = new PipedInputStream();
314 in_w = new PipedOutputStream(in_r);
316 out_r = new PipedInputStream();
317 out_w = new PipedOutputStream(out_r);
318 } catch (IOException err) {
319 dst.close();
320 throw new TransportException(uri, "cannot connect pipes", err);
323 worker = new Thread("JGit-Receive-Pack") {
324 public void run() {
325 try {
326 final ReceivePack rp = new ReceivePack(dst);
327 rp.receive(out_r, in_w, System.err);
328 } catch (IOException err) {
329 // Client side of the pipes should report the problem.
330 } catch (RuntimeException err) {
331 // Clients side will notice we went away, and report.
332 } finally {
333 try {
334 out_r.close();
335 } catch (IOException e2) {
336 // Ignore close failure, we probably crashed above.
339 try {
340 in_w.close();
341 } catch (IOException e2) {
342 // Ignore close failure, we probably crashed above.
345 dst.close();
349 worker.start();
351 init(in_r, out_w);
352 readAdvertisedRefs();
355 @Override
356 public void close() {
357 super.close();
359 if (worker != null) {
360 try {
361 worker.join();
362 } catch (InterruptedException ie) {
363 // Stop waiting and return anyway.
364 } finally {
365 worker = null;
371 class ForkLocalPushConnection extends BasePackPushConnection {
372 private Process receivePack;
374 private Thread errorReaderThread;
376 ForkLocalPushConnection() throws TransportException {
377 super(TransportLocal.this);
379 final MessageWriter msg = new MessageWriter();
380 setMessageWriter(msg);
382 receivePack = spawn(getOptionReceivePack());
384 final InputStream rpErr = receivePack.getErrorStream();
385 errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
386 errorReaderThread.start();
388 final InputStream rpIn = receivePack.getInputStream();
389 final OutputStream rpOut = receivePack.getOutputStream();
390 init(rpIn, rpOut);
391 readAdvertisedRefs();
394 @Override
395 public void close() {
396 super.close();
398 if (receivePack != null) {
399 try {
400 receivePack.waitFor();
401 } catch (InterruptedException ie) {
402 // Stop waiting and return anyway.
403 } finally {
404 receivePack = null;
408 if (errorReaderThread != null) {
409 try {
410 errorReaderThread.join();
411 } catch (InterruptedException e) {
412 // Stop waiting and return anyway.
413 } finally {
414 errorReaderThread = null;