View Javadoc
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.
8    *
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
13   *
14   * All rights reserved.
15   *
16   * Redistribution and use in source and binary forms, with or
17   * without modification, are permitted provided that the following
18   * conditions are met:
19   *
20   * - Redistributions of source code must retain the above copyright
21   *   notice, this list of conditions and the following disclaimer.
22   *
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.
27   *
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.
32   *
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.
46   */
47  
48  package org.eclipse.jgit.transport;
49  
50  import java.io.BufferedInputStream;
51  import java.io.File;
52  import java.io.IOException;
53  import java.io.InputStream;
54  import java.io.OutputStream;
55  import java.util.Collections;
56  import java.util.Map;
57  import java.util.Set;
58  
59  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
60  import org.eclipse.jgit.errors.NotSupportedException;
61  import org.eclipse.jgit.errors.TransportException;
62  import org.eclipse.jgit.internal.JGitText;
63  import org.eclipse.jgit.lib.Repository;
64  import org.eclipse.jgit.lib.RepositoryBuilder;
65  import org.eclipse.jgit.lib.RepositoryCache;
66  import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
67  import org.eclipse.jgit.transport.resolver.UploadPackFactory;
68  import org.eclipse.jgit.util.FS;
69  import org.eclipse.jgit.util.io.MessageWriter;
70  import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
71  import org.eclipse.jgit.util.io.StreamCopyThread;
72  
73  /**
74   * Transport to access a local directory as though it were a remote peer.
75   * <p>
76   * This transport is suitable for use on the local system, where the caller has
77   * direct read or write access to the "remote" repository.
78   * <p>
79   * By default this transport works by spawning a helper thread within the same
80   * JVM, and processes the data transfer using a shared memory buffer between the
81   * calling thread and the helper thread. This is a pure-Java implementation
82   * which does not require forking an external process.
83   * <p>
84   * However, during {@link #openFetch()}, if the Transport has configured
85   * {@link Transport#getOptionUploadPack()} to be anything other than
86   * <code>"git-upload-pack"</code> or <code>"git upload-pack"</code>, this
87   * implementation will fork and execute the external process, using an operating
88   * system pipe to transfer data.
89   * <p>
90   * Similarly, during {@link #openPush()}, if the Transport has configured
91   * {@link Transport#getOptionReceivePack()} to be anything other than
92   * <code>"git-receive-pack"</code> or <code>"git receive-pack"</code>, this
93   * implementation will fork and execute the external process, using an operating
94   * system pipe to transfer data.
95   */
96  class TransportLocal extends Transport implements PackTransport {
97  	static final TransportProtocol PROTO_LOCAL = new TransportProtocol() {
98  		@Override
99  		public String getName() {
100 			return JGitText.get().transportProtoLocal;
101 		}
102 
103 		public Set<String> getSchemes() {
104 			return Collections.singleton("file"); //$NON-NLS-1$
105 		}
106 
107 		@Override
108 		public boolean canHandle(URIish uri, Repository local, String remoteName) {
109 			if (uri.getPath() == null
110 					|| uri.getPort() > 0
111 					|| uri.getUser() != null
112 					|| uri.getPass() != null
113 					|| uri.getHost() != null
114 					|| (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
115 				return false;
116 			return true;
117 		}
118 
119 		@Override
120 		public Transport open(URIish uri, Repository local, String remoteName)
121 				throws NoRemoteRepositoryException {
122 			File localPath = local.isBare() ? local.getDirectory() : local.getWorkTree();
123 			File path = local.getFS().resolve(localPath, uri.getPath());
124 			// If the reference is to a local file, C Git behavior says
125 			// assume this is a bundle, since repositories are directories.
126 			if (path.isFile())
127 				return new TransportBundleFile(local, uri, path);
128 
129 			File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS());
130 			if (gitDir == null)
131 				throw new NoRemoteRepositoryException(uri, JGitText.get().notFound);
132 			return new TransportLocal(local, uri, gitDir);
133 		}
134 
135 		public Transport open(URIish uri) throws NotSupportedException,
136 				TransportException {
137 			File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$
138 			// If the reference is to a local file, C Git behavior says
139 			// assume this is a bundle, since repositories are directories.
140 			if (path.isFile())
141 				return new TransportBundleFile(uri, path);
142 
143 			File gitDir = RepositoryCache.FileKey.resolve(path, FS.DETECTED);
144 			if (gitDir == null)
145 				throw new NoRemoteRepositoryException(uri,
146 						JGitText.get().notFound);
147 			return new TransportLocal(uri, gitDir);
148 		}
149 	};
150 
151 	private final File remoteGitDir;
152 
153 	TransportLocal(Repository local, URIish uri, File gitDir) {
154 		super(local, uri);
155 		remoteGitDir = gitDir;
156 	}
157 
158 	TransportLocal(URIish uri, File gitDir) {
159 		super(uri);
160 		remoteGitDir = gitDir;
161 	}
162 
163 	UploadPack createUploadPack(final Repository dst) {
164 		return new UploadPack(dst);
165 	}
166 
167 	ReceivePack createReceivePack(final Repository dst) {
168 		return new ReceivePack(dst);
169 	}
170 
171 	private Repository openRepo() throws TransportException {
172 		try {
173 			return new RepositoryBuilder().setGitDir(remoteGitDir).build();
174 		} catch (IOException err) {
175 			throw new TransportException(uri, JGitText.get().notAGitDirectory);
176 		}
177 	}
178 
179 	@Override
180 	public FetchConnection openFetch() throws TransportException {
181 		final String up = getOptionUploadPack();
182 		if (!"git-upload-pack".equals(up) //$NON-NLS-1$
183 				&& !"git upload-pack".equals(up)) //$NON-NLS-1$
184 			return new ForkLocalFetchConnection();
185 
186 		UploadPackFactory<Void> upf = new UploadPackFactory<Void>() {
187 			@Override
188 			public UploadPack create(Void req, Repository db) {
189 				return createUploadPack(db);
190 			}
191 		};
192 		return new InternalFetchConnection<Void>(this, upf, null, openRepo());
193 	}
194 
195 	@Override
196 	public PushConnection openPush() throws TransportException {
197 		final String rp = getOptionReceivePack();
198 		if (!"git-receive-pack".equals(rp) //$NON-NLS-1$
199 				&& !"git receive-pack".equals(rp)) //$NON-NLS-1$
200 			return new ForkLocalPushConnection();
201 
202 		ReceivePackFactory<Void> rpf = new ReceivePackFactory<Void>() {
203 			@Override
204 			public ReceivePack create(Void req, Repository db) {
205 				return createReceivePack(db);
206 			}
207 		};
208 		return new InternalPushConnection<Void>(this, rpf, null, openRepo());
209 	}
210 
211 	@Override
212 	public void close() {
213 		// Resources must be established per-connection.
214 	}
215 
216 	protected Process spawn(final String cmd)
217 			throws TransportException {
218 		try {
219 			String[] args = { "." }; //$NON-NLS-1$
220 			ProcessBuilder proc = local.getFS().runInShell(cmd, args);
221 			proc.directory(remoteGitDir);
222 
223 			// Remove the same variables CGit does.
224 			Map<String, String> env = proc.environment();
225 			env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$
226 			env.remove("GIT_CONFIG"); //$NON-NLS-1$
227 			env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
228 			env.remove("GIT_DIR"); //$NON-NLS-1$
229 			env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
230 			env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
231 			env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
232 			env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$
233 
234 			return proc.start();
235 		} catch (IOException err) {
236 			throw new TransportException(uri, err.getMessage(), err);
237 		}
238 	}
239 
240 	class ForkLocalFetchConnection extends BasePackFetchConnection {
241 		private Process uploadPack;
242 
243 		private Thread errorReaderThread;
244 
245 		ForkLocalFetchConnection() throws TransportException {
246 			super(TransportLocal.this);
247 
248 			final MessageWriter msg = new MessageWriter();
249 			setMessageWriter(msg);
250 
251 			uploadPack = spawn(getOptionUploadPack());
252 
253 			final InputStream upErr = uploadPack.getErrorStream();
254 			errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
255 			errorReaderThread.start();
256 
257 			InputStream upIn = uploadPack.getInputStream();
258 			OutputStream upOut = uploadPack.getOutputStream();
259 
260 			upIn = new BufferedInputStream(upIn);
261 			upOut = new SafeBufferedOutputStream(upOut);
262 
263 			init(upIn, upOut);
264 			readAdvertisedRefs();
265 		}
266 
267 		@Override
268 		public void close() {
269 			super.close();
270 
271 			if (uploadPack != null) {
272 				try {
273 					uploadPack.waitFor();
274 				} catch (InterruptedException ie) {
275 					// Stop waiting and return anyway.
276 				} finally {
277 					uploadPack = null;
278 				}
279 			}
280 
281 			if (errorReaderThread != null) {
282 				try {
283 					errorReaderThread.join();
284 				} catch (InterruptedException e) {
285 					// Stop waiting and return anyway.
286 				} finally {
287 					errorReaderThread = null;
288 				}
289 			}
290 		}
291 	}
292 
293 	class ForkLocalPushConnection extends BasePackPushConnection {
294 		private Process receivePack;
295 
296 		private Thread errorReaderThread;
297 
298 		ForkLocalPushConnection() throws TransportException {
299 			super(TransportLocal.this);
300 
301 			final MessageWriter msg = new MessageWriter();
302 			setMessageWriter(msg);
303 
304 			receivePack = spawn(getOptionReceivePack());
305 
306 			final InputStream rpErr = receivePack.getErrorStream();
307 			errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
308 			errorReaderThread.start();
309 
310 			InputStream rpIn = receivePack.getInputStream();
311 			OutputStream rpOut = receivePack.getOutputStream();
312 
313 			rpIn = new BufferedInputStream(rpIn);
314 			rpOut = new SafeBufferedOutputStream(rpOut);
315 
316 			init(rpIn, rpOut);
317 			readAdvertisedRefs();
318 		}
319 
320 		@Override
321 		public void close() {
322 			super.close();
323 
324 			if (receivePack != null) {
325 				try {
326 					receivePack.waitFor();
327 				} catch (InterruptedException ie) {
328 					// Stop waiting and return anyway.
329 				} finally {
330 					receivePack = null;
331 				}
332 			}
333 
334 			if (errorReaderThread != null) {
335 				try {
336 					errorReaderThread.join();
337 				} catch (InterruptedException e) {
338 					// Stop waiting and return anyway.
339 				} finally {
340 					errorReaderThread = null;
341 				}
342 			}
343 		}
344 	}
345 }