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
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
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
;
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
;
66 * Transport to access a local directory as though it were a remote peer.
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.
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.
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.
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)
96 if ("file".equals(uri
.getScheme()) || uri
.getScheme() == null)
97 return FS
.resolve(new File(PWD
), uri
.getPath()).isDirectory();
101 private final File remoteGitDir
;
103 TransportLocal(final Repository local
, final URIish 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
);
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();
121 public PushConnection
openPush() throws NotSupportedException
,
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();
130 public void close() {
131 // Resources must be established per-connection.
134 protected Process
spawn(final String cmd
)
135 throws TransportException
{
139 if (cmd
.startsWith("git-")) {
140 args
= new String
[] { "git", cmd
.substring(4), PWD
};
142 final int gitspace
= cmd
.indexOf("git ");
144 final String git
= cmd
.substring(0, gitspace
+ 3);
145 final String subcmd
= cmd
.substring(gitspace
+ 4);
146 args
= new String
[] { git
, subcmd
, PWD
};
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
;
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
;
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
) {
192 throw new TransportException(uri
, "cannot connect pipes", err
);
195 worker
= new Thread("JGit-Upload-Pack") {
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();
209 } catch (IOException e2
) {
210 // Ignore close failure, we probably crashed above.
215 } catch (IOException e2
) {
216 // Ignore close failure, we probably crashed above.
226 readAdvertisedRefs();
230 public void close() {
233 if (worker
!= null) {
236 } catch (InterruptedException ie
) {
237 // Stop waiting and return anyway.
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();
265 readAdvertisedRefs();
269 public void close() {
272 if (uploadPack
!= null) {
274 uploadPack
.waitFor();
275 } catch (InterruptedException ie
) {
276 // Stop waiting and return anyway.
282 if (errorReaderThread
!= null) {
284 errorReaderThread
.join();
285 } catch (InterruptedException e
) {
286 // Stop waiting and return anyway.
288 errorReaderThread
= null;
294 class InternalLocalPushConnection
extends BasePackPushConnection
{
295 private Thread worker
;
297 InternalLocalPushConnection() throws TransportException
{
298 super(TransportLocal
.this);
300 final Repository dst
;
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
;
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
) {
320 throw new TransportException(uri
, "cannot connect pipes", err
);
323 worker
= new Thread("JGit-Receive-Pack") {
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.
335 } catch (IOException e2
) {
336 // Ignore close failure, we probably crashed above.
341 } catch (IOException e2
) {
342 // Ignore close failure, we probably crashed above.
352 readAdvertisedRefs();
356 public void close() {
359 if (worker
!= null) {
362 } catch (InterruptedException ie
) {
363 // Stop waiting and return anyway.
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();
391 readAdvertisedRefs();
395 public void close() {
398 if (receivePack
!= null) {
400 receivePack
.waitFor();
401 } catch (InterruptedException ie
) {
402 // Stop waiting and return anyway.
408 if (errorReaderThread
!= null) {
410 errorReaderThread
.join();
411 } catch (InterruptedException e
) {
412 // Stop waiting and return anyway.
414 errorReaderThread
= null;