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>
9 * Redistribution and use in source and binary forms, with or
10 * without modification, are permitted provided that the following
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
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
;
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
;
55 * Transport to access a local directory as though it were a remote peer.
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.
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.
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.
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)
85 if ("file".equals(uri
.getScheme()) || uri
.getScheme() == null)
86 return FS
.resolve(new File(PWD
), uri
.getPath()).isDirectory();
90 private final File remoteGitDir
;
92 TransportLocal(final Repository local
, final URIish 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");
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();
110 public PushConnection
openPush() throws NotSupportedException
,
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();
119 public void close() {
120 // Resources must be established per-connection.
123 protected Process
startProcessWithErrStream(final String cmd
)
124 throws TransportException
{
129 if (cmd
.startsWith("git-")) {
130 args
= new String
[] { "git", cmd
.substring(4), PWD
};
132 final int gitspace
= cmd
.indexOf("git ");
134 final String git
= cmd
.substring(0, gitspace
+ 3);
135 final String subcmd
= cmd
.substring(gitspace
+ 4);
136 args
= new String
[] { git
, subcmd
, PWD
};
138 args
= new String
[] { cmd
, PWD
};
142 proc
= Runtime
.getRuntime().exec(args
, null, remoteGitDir
);
143 new StreamRewritingThread(cmd
, proc
.getErrorStream()).start();
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
;
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
;
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
) {
184 throw new TransportException(uri
, "cannot connect pipes", err
);
187 worker
= new Thread("JGit-Upload-Pack") {
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();
201 } catch (IOException e2
) {
202 // Ignore close failure, we probably crashed above.
207 } catch (IOException e2
) {
208 // Ignore close failure, we probably crashed above.
218 readAdvertisedRefs();
222 public void close() {
225 if (worker
!= null) {
228 } catch (InterruptedException ie
) {
229 // Stop waiting and return anyway.
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();
248 public void close() {
251 if (uploadPack
!= null) {
253 uploadPack
.waitFor();
254 } catch (InterruptedException ie
) {
255 // Stop waiting and return anyway.
263 class InternalLocalPushConnection
extends BasePackPushConnection
{
264 private Thread worker
;
266 InternalLocalPushConnection() throws TransportException
{
267 super(TransportLocal
.this);
269 final Repository dst
;
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
;
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
) {
289 throw new TransportException(uri
, "cannot connect pipes", err
);
292 worker
= new Thread("JGit-Receive-Pack") {
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.
304 } catch (IOException e2
) {
305 // Ignore close failure, we probably crashed above.
310 } catch (IOException e2
) {
311 // Ignore close failure, we probably crashed above.
321 readAdvertisedRefs();
325 public void close() {
328 if (worker
!= null) {
331 } catch (InterruptedException ie
) {
332 // Stop waiting and return anyway.
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();
351 public void close() {
354 if (receivePack
!= null) {
356 receivePack
.waitFor();
357 } catch (InterruptedException ie
) {
358 // Stop waiting and return anyway.
366 static class StreamRewritingThread
extends Thread
{
367 private final InputStream in
;
369 StreamRewritingThread(final String cmd
, final InputStream in
) {
370 super("JGit " + cmd
+ " Errors");
375 final byte[] tmp
= new byte[512];
378 final int n
= in
.read(tmp
);
381 System
.err
.write(tmp
, 0, n
);
384 } catch (IOException err
) {
385 // Ignore errors reading errors.
389 } catch (IOException err2
) {
390 // Ignore errors closing the pipe.