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