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
;
50 import java
.io
.BufferedInputStream
;
51 import java
.io
.BufferedOutputStream
;
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
;
68 * Transport to access a local directory as though it were a remote peer.
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.
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.
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.
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)
98 if ("file".equals(uri
.getScheme()) || uri
.getScheme() == null)
99 return FS
.resolve(new File(PWD
), uri
.getPath()).isDirectory();
103 private final File remoteGitDir
;
105 TransportLocal(final Repository local
, final URIish 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
);
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();
123 public PushConnection
openPush() throws NotSupportedException
,
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();
132 public void close() {
133 // Resources must be established per-connection.
136 protected Process
spawn(final String cmd
)
137 throws TransportException
{
141 if (cmd
.startsWith("git-")) {
142 args
= new String
[] { "git", cmd
.substring(4), PWD
};
144 final int gitspace
= cmd
.indexOf("git ");
146 final String git
= cmd
.substring(0, gitspace
+ 3);
147 final String subcmd
= cmd
.substring(gitspace
+ 4);
148 args
= new String
[] { git
, subcmd
, PWD
};
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
;
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
;
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
) {
194 throw new TransportException(uri
, "cannot connect pipes", err
);
197 worker
= new Thread("JGit-Upload-Pack") {
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();
211 } catch (IOException e2
) {
212 // Ignore close failure, we probably crashed above.
217 } catch (IOException e2
) {
218 // Ignore close failure, we probably crashed above.
228 readAdvertisedRefs();
232 public void close() {
235 if (worker
!= null) {
238 } catch (InterruptedException ie
) {
239 // Stop waiting and return anyway.
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
);
271 readAdvertisedRefs();
275 public void close() {
278 if (uploadPack
!= null) {
280 uploadPack
.waitFor();
281 } catch (InterruptedException ie
) {
282 // Stop waiting and return anyway.
288 if (errorReaderThread
!= null) {
290 errorReaderThread
.join();
291 } catch (InterruptedException e
) {
292 // Stop waiting and return anyway.
294 errorReaderThread
= null;
300 class InternalLocalPushConnection
extends BasePackPushConnection
{
301 private Thread worker
;
303 InternalLocalPushConnection() throws TransportException
{
304 super(TransportLocal
.this);
306 final Repository dst
;
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
;
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
) {
326 throw new TransportException(uri
, "cannot connect pipes", err
);
329 worker
= new Thread("JGit-Receive-Pack") {
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.
341 } catch (IOException e2
) {
342 // Ignore close failure, we probably crashed above.
347 } catch (IOException e2
) {
348 // Ignore close failure, we probably crashed above.
358 readAdvertisedRefs();
362 public void close() {
365 if (worker
!= null) {
368 } catch (InterruptedException ie
) {
369 // Stop waiting and return anyway.
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
);
401 readAdvertisedRefs();
405 public void close() {
408 if (receivePack
!= null) {
410 receivePack
.waitFor();
411 } catch (InterruptedException ie
) {
412 // Stop waiting and return anyway.
418 if (errorReaderThread
!= null) {
420 errorReaderThread
.join();
421 } catch (InterruptedException e
) {
422 // Stop waiting and return anyway.
424 errorReaderThread
= null;