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(Repository dst) {
166 		return new UploadPack(dst);
167 	}
168 
169 	ReceivePack createReceivePack(Repository dst) {
170 		return new ReceivePack(dst);
171 	}
172 
173 	private Repository openRepo() throws TransportException {
174 		try {
175 			return new RepositoryBuilder()
176 					.setFS(local != null ? local.getFS() : FS.DETECTED)
177 					.setGitDir(remoteGitDir).build();
178 		} catch (IOException err) {
179 			throw new TransportException(uri, JGitText.get().notAGitDirectory);
180 		}
181 	}
182 
183 	/** {@inheritDoc} */
184 	@Override
185 	public FetchConnection openFetch() throws TransportException {
186 		final String up = getOptionUploadPack();
187 		if (!"git-upload-pack".equals(up) //$NON-NLS-1$
188 				&& !"git upload-pack".equals(up)) //$NON-NLS-1$
189 			return new ForkLocalFetchConnection();
190 
191 		UploadPackFactory<Void> upf = new UploadPackFactory<Void>() {
192 			@Override
193 			public UploadPack create(Void req, Repository db) {
194 				return createUploadPack(db);
195 			}
196 		};
197 		return new InternalFetchConnection<>(this, upf, null, openRepo());
198 	}
199 
200 	/** {@inheritDoc} */
201 	@Override
202 	public PushConnection openPush() throws TransportException {
203 		final String rp = getOptionReceivePack();
204 		if (!"git-receive-pack".equals(rp) //$NON-NLS-1$
205 				&& !"git receive-pack".equals(rp)) //$NON-NLS-1$
206 			return new ForkLocalPushConnection();
207 
208 		ReceivePackFactory<Void> rpf = new ReceivePackFactory<Void>() {
209 			@Override
210 			public ReceivePack create(Void req, Repository db) {
211 				return createReceivePack(db);
212 			}
213 		};
214 		return new InternalPushConnection<>(this, rpf, null, openRepo());
215 	}
216 
217 	/** {@inheritDoc} */
218 	@Override
219 	public void close() {
220 		// Resources must be established per-connection.
221 	}
222 
223 	/**
224 	 * Spawn process
225 	 *
226 	 * @param cmd
227 	 *            command
228 	 * @return a {@link java.lang.Process} object.
229 	 * @throws org.eclipse.jgit.errors.TransportException
230 	 *             if any.
231 	 */
232 	protected Process spawn(String cmd)
233 			throws TransportException {
234 		try {
235 			String[] args = { "." }; //$NON-NLS-1$
236 			ProcessBuilder proc = local.getFS().runInShell(cmd, args);
237 			proc.directory(remoteGitDir);
238 
239 			// Remove the same variables CGit does.
240 			Map<String, String> env = proc.environment();
241 			env.remove("GIT_ALTERNATE_OBJECT_DIRECTORIES"); //$NON-NLS-1$
242 			env.remove("GIT_CONFIG"); //$NON-NLS-1$
243 			env.remove("GIT_CONFIG_PARAMETERS"); //$NON-NLS-1$
244 			env.remove("GIT_DIR"); //$NON-NLS-1$
245 			env.remove("GIT_WORK_TREE"); //$NON-NLS-1$
246 			env.remove("GIT_GRAFT_FILE"); //$NON-NLS-1$
247 			env.remove("GIT_INDEX_FILE"); //$NON-NLS-1$
248 			env.remove("GIT_NO_REPLACE_OBJECTS"); //$NON-NLS-1$
249 
250 			return proc.start();
251 		} catch (IOException err) {
252 			throw new TransportException(uri, err.getMessage(), err);
253 		}
254 	}
255 
256 	class ForkLocalFetchConnection extends BasePackFetchConnection {
257 		private Process uploadPack;
258 
259 		private Thread errorReaderThread;
260 
261 		ForkLocalFetchConnection() throws TransportException {
262 			super(TransportLocal.this);
263 
264 			final MessageWriter msg = new MessageWriter();
265 			setMessageWriter(msg);
266 
267 			uploadPack = spawn(getOptionUploadPack());
268 
269 			final InputStream upErr = uploadPack.getErrorStream();
270 			errorReaderThread = new StreamCopyThread(upErr, msg.getRawStream());
271 			errorReaderThread.start();
272 
273 			InputStream upIn = uploadPack.getInputStream();
274 			OutputStream upOut = uploadPack.getOutputStream();
275 
276 			upIn = new BufferedInputStream(upIn);
277 			upOut = new BufferedOutputStream(upOut);
278 
279 			init(upIn, upOut);
280 			readAdvertisedRefs();
281 		}
282 
283 		@Override
284 		public void close() {
285 			super.close();
286 
287 			if (uploadPack != null) {
288 				try {
289 					uploadPack.waitFor();
290 				} catch (InterruptedException ie) {
291 					// Stop waiting and return anyway.
292 				} finally {
293 					uploadPack = null;
294 				}
295 			}
296 
297 			if (errorReaderThread != null) {
298 				try {
299 					errorReaderThread.join();
300 				} catch (InterruptedException e) {
301 					// Stop waiting and return anyway.
302 				} finally {
303 					errorReaderThread = null;
304 				}
305 			}
306 		}
307 	}
308 
309 	class ForkLocalPushConnection extends BasePackPushConnection {
310 		private Process receivePack;
311 
312 		private Thread errorReaderThread;
313 
314 		ForkLocalPushConnection() throws TransportException {
315 			super(TransportLocal.this);
316 
317 			final MessageWriter msg = new MessageWriter();
318 			setMessageWriter(msg);
319 
320 			receivePack = spawn(getOptionReceivePack());
321 
322 			final InputStream rpErr = receivePack.getErrorStream();
323 			errorReaderThread = new StreamCopyThread(rpErr, msg.getRawStream());
324 			errorReaderThread.start();
325 
326 			InputStream rpIn = receivePack.getInputStream();
327 			OutputStream rpOut = receivePack.getOutputStream();
328 
329 			rpIn = new BufferedInputStream(rpIn);
330 			rpOut = new BufferedOutputStream(rpOut);
331 
332 			init(rpIn, rpOut);
333 			readAdvertisedRefs();
334 		}
335 
336 		@Override
337 		public void close() {
338 			super.close();
339 
340 			if (receivePack != null) {
341 				try {
342 					receivePack.waitFor();
343 				} catch (InterruptedException ie) {
344 					// Stop waiting and return anyway.
345 				} finally {
346 					receivePack = null;
347 				}
348 			}
349 
350 			if (errorReaderThread != null) {
351 				try {
352 					errorReaderThread.join();
353 				} catch (InterruptedException e) {
354 					// Stop waiting and return anyway.
355 				} finally {
356 					errorReaderThread = null;
357 				}
358 			}
359 		}
360 	}
361 }