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.BufferedOutputStream;
52  import java.io.File;
53  import java.io.IOException;
54  import java.io.InputStream;
55  import java.io.OutputStream;
56  import java.util.Collections;
57  import java.util.Map;
58  import java.util.Set;
59  
60  import org.eclipse.jgit.errors.NoRemoteRepositoryException;
61  import org.eclipse.jgit.errors.NotSupportedException;
62  import org.eclipse.jgit.errors.TransportException;
63  import org.eclipse.jgit.internal.JGitText;
64  import org.eclipse.jgit.lib.Repository;
65  import org.eclipse.jgit.lib.RepositoryBuilder;
66  import org.eclipse.jgit.lib.RepositoryCache;
67  import org.eclipse.jgit.transport.resolver.ReceivePackFactory;
68  import org.eclipse.jgit.transport.resolver.UploadPackFactory;
69  import org.eclipse.jgit.util.FS;
70  import org.eclipse.jgit.util.io.MessageWriter;
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 		@Override
104 		public Set<String> getSchemes() {
105 			return Collections.singleton("file"); //$NON-NLS-1$
106 		}
107 
108 		@Override
109 		public boolean canHandle(URIish uri, Repository local, String remoteName) {
110 			if (uri.getPath() == null
111 					|| uri.getPort() > 0
112 					|| uri.getUser() != null
113 					|| uri.getPass() != null
114 					|| uri.getHost() != null
115 					|| (uri.getScheme() != null && !getSchemes().contains(uri.getScheme())))
116 				return false;
117 			return true;
118 		}
119 
120 		@Override
121 		public Transport open(URIish uri, Repository local, String remoteName)
122 				throws NoRemoteRepositoryException {
123 			File localPath = local.isBare() ? local.getDirectory() : local.getWorkTree();
124 			File path = local.getFS().resolve(localPath, uri.getPath());
125 			// If the reference is to a local file, C Git behavior says
126 			// assume this is a bundle, since repositories are directories.
127 			if (path.isFile())
128 				return new TransportBundleFile(local, uri, path);
129 
130 			File gitDir = RepositoryCache.FileKey.resolve(path, local.getFS());
131 			if (gitDir == null)
132 				throw new NoRemoteRepositoryException(uri, JGitText.get().notFound);
133 			return new TransportLocal(local, uri, gitDir);
134 		}
135 
136 		@Override
137 		public Transport open(URIish uri) throws NotSupportedException,
138 				TransportException {
139 			File path = FS.DETECTED.resolve(new File("."), uri.getPath()); //$NON-NLS-1$
140 			// If the reference is to a local file, C Git behavior says
141 			// assume this is a bundle, since repositories are directories.
142 			if (path.isFile())
143 				return new TransportBundleFile(uri, path);
144 
145 			File gitDir = RepositoryCache.FileKey.resolve(path, FS.DETECTED);
146 			if (gitDir == null)
147 				throw new NoRemoteRepositoryException(uri,
148 						JGitText.get().notFound);
149 			return new TransportLocal(uri, gitDir);
150 		}
151 	};
152 
153 	private final File remoteGitDir;
154 
155 	TransportLocal(Repository local, URIish uri, File gitDir) {
156 		super(local, uri);
157 		remoteGitDir = gitDir;
158 	}
159 
160 	TransportLocal(URIish uri, File gitDir) {
161 		super(uri);
162 		remoteGitDir = gitDir;
163 	}
164 
165 	UploadPack createUploadPack(final Repository dst) {
166 		return new UploadPack(dst);
167 	}
168 
169 	ReceivePack createReceivePack(final Repository dst) {
170 		return new ReceivePack(dst);
171 	}
172 
173 	private Repository openRepo() throws TransportException {
174 		try {
175 			return new RepositoryBuilder().setGitDir(remoteGitDir).build();
176 		} catch (IOException err) {
177 			throw new TransportException(uri, JGitText.get().notAGitDirectory);
178 		}
179 	}
180 
181 	/** {@inheritDoc} */
182 	@Override
183 	public FetchConnection openFetch() throws TransportException {
184 		final String up = getOptionUploadPack();
185 		if (!"git-upload-pack".equals(up) //$NON-NLS-1$
186 				&& !"git upload-pack".equals(up)) //$NON-NLS-1$
187 			return new ForkLocalFetchConnection();
188 
189 		UploadPackFactory<Void> upf = new UploadPackFactory<Void>() {
190 			@Override
191 			public UploadPack create(Void req, Repository db) {
192 				return createUploadPack(db);
193 			}
194 		};
195 		return new InternalFetchConnection<>(this, upf, null, openRepo());
196 	}
197 
198 	/** {@inheritDoc} */
199 	@Override
200 	public PushConnection openPush() throws TransportException {
201 		final String rp = getOptionReceivePack();
202 		if (!"git-receive-pack".equals(rp) //$NON-NLS-1$
203 				&& !"git receive-pack".equals(rp)) //$NON-NLS-1$
204 			return new ForkLocalPushConnection();
205 
206 		ReceivePackFactory<Void> rpf = new ReceivePackFactory<Void>() {
207 			@Override
208 			public ReceivePack create(Void req, Repository db) {
209 				return createReceivePack(db);
210 			}
211 		};
212 		return new InternalPushConnection<>(this, rpf, null, openRepo());
213 	}
214 
215 	/** {@inheritDoc} */
216 	@Override
217 	public void close() {
218 		// Resources must be established per-connection.
219 	}
220 
221 	/**
222 	 * Spawn process
223 	 *
224 	 * @param cmd
225 	 *            command
226 	 * @return a {@link java.lang.Process} object.
227 	 * @throws org.eclipse.jgit.errors.TransportException
228 	 *             if any.
229 	 */
230 	protected Process spawn(final String cmd)
231 			throws TransportException {
232 		try {
233 			String[] args = { "." }; //$NON-NLS-1$
234 			ProcessBuilder proc = local.getFS().runInShell(cmd, args);
235 			proc.directory(remoteGitDir);
236 
237 			// Remove the same variables CGit does.
238 			Map<String, String> env = proc.environment();
239 			env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$
240 			env.remove("GIT_CONFIG"); //$NON-NLS-1$
241 			env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
242 			env.remove("GIT_DIR"); //$NON-NLS-1$
243 			env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
244 			env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
245 			env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
246 			env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$
247 
248 			return proc.start();
249 		} catch (IOException err) {
250 			throw new TransportException(uri, err.getMessage(), err);
251 		}
252 	}
253 
254 	class ForkLocalFetchConnection extends BasePackFetchConnection {
255 		private Process uploadPack;
256 
257 		private Thread errorReaderThread;
258 
259 		ForkLocalFetchConnection() throws TransportException {
260 			super(TransportLocal.this);
261 
262 			final MessageWriter msg = new MessageWriter();
263 			setMessageWriter(msg);
264 
265 			uploadPack = spawn(getOptionUploadPack());
266 
267 			final InputStream upErr = uploadPack.getErrorStream();
268 			errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
269 			errorReaderThread.start();
270 
271 			InputStream upIn = uploadPack.getInputStream();
272 			OutputStream upOut = uploadPack.getOutputStream();
273 
274 			upIn = new BufferedInputStream(upIn);
275 			upOut = new BufferedOutputStream(upOut);
276 
277 			init(upIn, upOut);
278 			readAdvertisedRefs();
279 		}
280 
281 		@Override
282 		public void close() {
283 			super.close();
284 
285 			if (uploadPack != null) {
286 				try {
287 					uploadPack.waitFor();
288 				} catch (InterruptedException ie) {
289 					// Stop waiting and return anyway.
290 				} finally {
291 					uploadPack = null;
292 				}
293 			}
294 
295 			if (errorReaderThread != null) {
296 				try {
297 					errorReaderThread.join();
298 				} catch (InterruptedException e) {
299 					// Stop waiting and return anyway.
300 				} finally {
301 					errorReaderThread = null;
302 				}
303 			}
304 		}
305 	}
306 
307 	class ForkLocalPushConnection extends BasePackPushConnection {
308 		private Process receivePack;
309 
310 		private Thread errorReaderThread;
311 
312 		ForkLocalPushConnection() throws TransportException {
313 			super(TransportLocal.this);
314 
315 			final MessageWriter msg = new MessageWriter();
316 			setMessageWriter(msg);
317 
318 			receivePack = spawn(getOptionReceivePack());
319 
320 			final InputStream rpErr = receivePack.getErrorStream();
321 			errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
322 			errorReaderThread.start();
323 
324 			InputStream rpIn = receivePack.getInputStream();
325 			OutputStream rpOut = receivePack.getOutputStream();
326 
327 			rpIn = new BufferedInputStream(rpIn);
328 			rpOut = new BufferedOutputStream(rpOut);
329 
330 			init(rpIn, rpOut);
331 			readAdvertisedRefs();
332 		}
333 
334 		@Override
335 		public void close() {
336 			super.close();
337 
338 			if (receivePack != null) {
339 				try {
340 					receivePack.waitFor();
341 				} catch (InterruptedException ie) {
342 					// Stop waiting and return anyway.
343 				} finally {
344 					receivePack = null;
345 				}
346 			}
347 
348 			if (errorReaderThread != null) {
349 				try {
350 					errorReaderThread.join();
351 				} catch (InterruptedException e) {
352 					// Stop waiting and return anyway.
353 				} finally {
354 					errorReaderThread = null;
355 				}
356 			}
357 		}
358 	}
359 }