View Javadoc
1   /*
2    * Copyright (C) 2009, Google Inc.
3    * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>
4    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
5    * and other copyright owners as documented in the project's IP log.
6    *
7    * This program and the accompanying materials are made available
8    * under the terms of the Eclipse Distribution License v1.0 which
9    * accompanies this distribution, is reproduced below, and is
10   * available at http://www.eclipse.org/org/documents/edl-v10.php
11   *
12   * All rights reserved.
13   *
14   * Redistribution and use in source and binary forms, with or
15   * without modification, are permitted provided that the following
16   * conditions are met:
17   *
18   * - Redistributions of source code must retain the above copyright
19   *   notice, this list of conditions and the following disclaimer.
20   *
21   * - Redistributions in binary form must reproduce the above
22   *   copyright notice, this list of conditions and the following
23   *   disclaimer in the documentation and/or other materials provided
24   *   with the distribution.
25   *
26   * - Neither the name of the Eclipse Foundation, Inc. nor the
27   *   names of its contributors may be used to endorse or promote
28   *   products derived from this software without specific prior
29   *   written permission.
30   *
31   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32   * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33   * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36   * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38   * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43   * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44   */
45  
46  package org.eclipse.jgit.transport;
47  
48  import java.io.Serializable;
49  import java.net.URISyntaxException;
50  import java.util.ArrayList;
51  import java.util.Collections;
52  import java.util.HashMap;
53  import java.util.List;
54  import java.util.Map;
55  import java.util.Map.Entry;
56  
57  import org.eclipse.jgit.lib.Config;
58  
59  /**
60   * A remembered remote repository, including URLs and RefSpecs.
61   * <p>
62   * A remote configuration remembers one or more URLs for a frequently accessed
63   * remote repository as well as zero or more fetch and push specifications
64   * describing how refs should be transferred between this repository and the
65   * remote repository.
66   */
67  public class RemoteConfig implements Serializable {
68  	private static final long serialVersionUID = 1L;
69  
70  	private static final String SECTION = "remote"; //$NON-NLS-1$
71  
72  	private static final String KEY_URL = "url"; //$NON-NLS-1$
73  
74  	private static final String KEY_PUSHURL = "pushurl"; //$NON-NLS-1$
75  
76  	private static final String KEY_FETCH = "fetch"; //$NON-NLS-1$
77  
78  	private static final String KEY_PUSH = "push"; //$NON-NLS-1$
79  
80  	private static final String KEY_UPLOADPACK = "uploadpack"; //$NON-NLS-1$
81  
82  	private static final String KEY_RECEIVEPACK = "receivepack"; //$NON-NLS-1$
83  
84  	private static final String KEY_TAGOPT = "tagopt"; //$NON-NLS-1$
85  
86  	private static final String KEY_MIRROR = "mirror"; //$NON-NLS-1$
87  
88  	private static final String KEY_TIMEOUT = "timeout"; //$NON-NLS-1$
89  
90  	private static final String KEY_INSTEADOF = "insteadof"; //$NON-NLS-1$
91  
92  	private static final String KEY_PUSHINSTEADOF = "pushinsteadof"; //$NON-NLS-1$
93  
94  	private static final boolean DEFAULT_MIRROR = false;
95  
96  	/** Default value for {@link #getUploadPack()} if not specified. */
97  	public static final String DEFAULT_UPLOAD_PACK = "git-upload-pack"; //$NON-NLS-1$
98  
99  	/** Default value for {@link #getReceivePack()} if not specified. */
100 	public static final String DEFAULT_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$
101 
102 	/**
103 	 * Parse all remote blocks in an existing configuration file, looking for
104 	 * remotes configuration.
105 	 *
106 	 * @param rc
107 	 *            the existing configuration to get the remote settings from.
108 	 *            The configuration must already be loaded into memory.
109 	 * @return all remotes configurations existing in provided repository
110 	 *         configuration. Returned configurations are ordered
111 	 *         lexicographically by names.
112 	 * @throws URISyntaxException
113 	 *             one of the URIs within the remote's configuration is invalid.
114 	 */
115 	public static List<RemoteConfig> getAllRemoteConfigs(final Config rc)
116 			throws URISyntaxException {
117 		final List<String> names = new ArrayList<String>(rc
118 				.getSubsections(SECTION));
119 		Collections.sort(names);
120 
121 		final List<RemoteConfig> result = new ArrayList<RemoteConfig>(names
122 				.size());
123 		for (final String name : names)
124 			result.add(new RemoteConfig(rc, name));
125 		return result;
126 	}
127 
128 	private String name;
129 
130 	private List<URIish> uris;
131 
132 	private List<URIish> pushURIs;
133 
134 	private List<RefSpec> fetch;
135 
136 	private List<RefSpec> push;
137 
138 	private String uploadpack;
139 
140 	private String receivepack;
141 
142 	private TagOpt tagopt;
143 
144 	private boolean mirror;
145 
146 	private int timeout;
147 
148 	/**
149 	 * Parse a remote block from an existing configuration file.
150 	 * <p>
151 	 * This constructor succeeds even if the requested remote is not defined
152 	 * within the supplied configuration file. If that occurs then there will be
153 	 * no URIs and no ref specifications known to the new instance.
154 	 *
155 	 * @param rc
156 	 *            the existing configuration to get the remote settings from.
157 	 *            The configuration must already be loaded into memory.
158 	 * @param remoteName
159 	 *            subsection key indicating the name of this remote.
160 	 * @throws URISyntaxException
161 	 *             one of the URIs within the remote's configuration is invalid.
162 	 */
163 	public RemoteConfig(final Config rc, final String remoteName)
164 			throws URISyntaxException {
165 		name = remoteName;
166 
167 		String[] vlst;
168 		String val;
169 
170 		vlst = rc.getStringList(SECTION, name, KEY_URL);
171 		Map<String, String> insteadOf = getReplacements(rc, KEY_INSTEADOF);
172 		uris = new ArrayList<URIish>(vlst.length);
173 		for (final String s : vlst)
174 			uris.add(new URIish(replaceUri(s, insteadOf)));
175 
176 		Map<String, String> pushInsteadOf = getReplacements(rc,
177 				KEY_PUSHINSTEADOF);
178 		vlst = rc.getStringList(SECTION, name, KEY_PUSHURL);
179 		pushURIs = new ArrayList<URIish>(vlst.length);
180 		for (final String s : vlst)
181 			pushURIs.add(new URIish(replaceUri(s, pushInsteadOf)));
182 
183 		vlst = rc.getStringList(SECTION, name, KEY_FETCH);
184 		fetch = new ArrayList<RefSpec>(vlst.length);
185 		for (final String s : vlst)
186 			fetch.add(new RefSpec(s));
187 
188 		vlst = rc.getStringList(SECTION, name, KEY_PUSH);
189 		push = new ArrayList<RefSpec>(vlst.length);
190 		for (final String s : vlst)
191 			push.add(new RefSpec(s));
192 
193 		val = rc.getString(SECTION, name, KEY_UPLOADPACK);
194 		if (val == null)
195 			val = DEFAULT_UPLOAD_PACK;
196 		uploadpack = val;
197 
198 		val = rc.getString(SECTION, name, KEY_RECEIVEPACK);
199 		if (val == null)
200 			val = DEFAULT_RECEIVE_PACK;
201 		receivepack = val;
202 
203 		val = rc.getString(SECTION, name, KEY_TAGOPT);
204 		tagopt = TagOpt.fromOption(val);
205 		mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR);
206 		timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0);
207 	}
208 
209 	/**
210 	 * Update this remote's definition within the configuration.
211 	 *
212 	 * @param rc
213 	 *            the configuration file to store ourselves into.
214 	 */
215 	public void update(final Config rc) {
216 		final List<String> vlst = new ArrayList<String>();
217 
218 		vlst.clear();
219 		for (final URIish u : getURIs())
220 			vlst.add(u.toPrivateString());
221 		rc.setStringList(SECTION, getName(), KEY_URL, vlst);
222 
223 		vlst.clear();
224 		for (final URIish u : getPushURIs())
225 			vlst.add(u.toPrivateString());
226 		rc.setStringList(SECTION, getName(), KEY_PUSHURL, vlst);
227 
228 		vlst.clear();
229 		for (final RefSpec u : getFetchRefSpecs())
230 			vlst.add(u.toString());
231 		rc.setStringList(SECTION, getName(), KEY_FETCH, vlst);
232 
233 		vlst.clear();
234 		for (final RefSpec u : getPushRefSpecs())
235 			vlst.add(u.toString());
236 		rc.setStringList(SECTION, getName(), KEY_PUSH, vlst);
237 
238 		set(rc, KEY_UPLOADPACK, getUploadPack(), DEFAULT_UPLOAD_PACK);
239 		set(rc, KEY_RECEIVEPACK, getReceivePack(), DEFAULT_RECEIVE_PACK);
240 		set(rc, KEY_TAGOPT, getTagOpt().option(), TagOpt.AUTO_FOLLOW.option());
241 		set(rc, KEY_MIRROR, mirror, DEFAULT_MIRROR);
242 		set(rc, KEY_TIMEOUT, timeout, 0);
243 	}
244 
245 	private void set(final Config rc, final String key,
246 			final String currentValue, final String defaultValue) {
247 		if (defaultValue.equals(currentValue))
248 			unset(rc, key);
249 		else
250 			rc.setString(SECTION, getName(), key, currentValue);
251 	}
252 
253 	private void set(final Config rc, final String key,
254 			final boolean currentValue, final boolean defaultValue) {
255 		if (defaultValue == currentValue)
256 			unset(rc, key);
257 		else
258 			rc.setBoolean(SECTION, getName(), key, currentValue);
259 	}
260 
261 	private void set(final Config rc, final String key, final int currentValue,
262 			final int defaultValue) {
263 		if (defaultValue == currentValue)
264 			unset(rc, key);
265 		else
266 			rc.setInt(SECTION, getName(), key, currentValue);
267 	}
268 
269 	private void unset(final Config rc, final String key) {
270 		rc.unset(SECTION, getName(), key);
271 	}
272 
273 	private Map<String, String> getReplacements(final Config config,
274 			final String keyName) {
275 		final Map<String, String> replacements = new HashMap<String, String>();
276 		for (String url : config.getSubsections(KEY_URL))
277 			for (String insteadOf : config.getStringList(KEY_URL, url, keyName))
278 				replacements.put(insteadOf, url);
279 		return replacements;
280 	}
281 
282 	private String replaceUri(final String uri,
283 			final Map<String, String> replacements) {
284 		if (replacements.isEmpty())
285 			return uri;
286 		Entry<String, String> match = null;
287 		for (Entry<String, String> replacement : replacements.entrySet()) {
288 			// Ignore current entry if not longer than previous match
289 			if (match != null
290 					&& match.getKey().length() > replacement.getKey().length())
291 				continue;
292 			if (!uri.startsWith(replacement.getKey()))
293 				continue;
294 			match = replacement;
295 		}
296 		if (match != null)
297 			return match.getValue() + uri.substring(match.getKey().length());
298 		else
299 			return uri;
300 	}
301 
302 	/**
303 	 * Get the local name this remote configuration is recognized as.
304 	 *
305 	 * @return name assigned by the user to this configuration block.
306 	 */
307 	public String getName() {
308 		return name;
309 	}
310 
311 	/**
312 	 * Get all configured URIs under this remote.
313 	 *
314 	 * @return the set of URIs known to this remote.
315 	 */
316 	public List<URIish> getURIs() {
317 		return Collections.unmodifiableList(uris);
318 	}
319 
320 	/**
321 	 * Add a new URI to the end of the list of URIs.
322 	 *
323 	 * @param toAdd
324 	 *            the new URI to add to this remote.
325 	 * @return true if the URI was added; false if it already exists.
326 	 */
327 	public boolean addURI(final URIish toAdd) {
328 		if (uris.contains(toAdd))
329 			return false;
330 		return uris.add(toAdd);
331 	}
332 
333 	/**
334 	 * Remove a URI from the list of URIs.
335 	 *
336 	 * @param toRemove
337 	 *            the URI to remove from this remote.
338 	 * @return true if the URI was added; false if it already exists.
339 	 */
340 	public boolean removeURI(final URIish toRemove) {
341 		return uris.remove(toRemove);
342 	}
343 
344 	/**
345 	 * Get all configured push-only URIs under this remote.
346 	 *
347 	 * @return the set of URIs known to this remote.
348 	 */
349 	public List<URIish> getPushURIs() {
350 		return Collections.unmodifiableList(pushURIs);
351 	}
352 
353 	/**
354 	 * Add a new push-only URI to the end of the list of URIs.
355 	 *
356 	 * @param toAdd
357 	 *            the new URI to add to this remote.
358 	 * @return true if the URI was added; false if it already exists.
359 	 */
360 	public boolean addPushURI(final URIish toAdd) {
361 		if (pushURIs.contains(toAdd))
362 			return false;
363 		return pushURIs.add(toAdd);
364 	}
365 
366 	/**
367 	 * Remove a push-only URI from the list of URIs.
368 	 *
369 	 * @param toRemove
370 	 *            the URI to remove from this remote.
371 	 * @return true if the URI was added; false if it already exists.
372 	 */
373 	public boolean removePushURI(final URIish toRemove) {
374 		return pushURIs.remove(toRemove);
375 	}
376 
377 	/**
378 	 * Remembered specifications for fetching from a repository.
379 	 *
380 	 * @return set of specs used by default when fetching.
381 	 */
382 	public List<RefSpec> getFetchRefSpecs() {
383 		return Collections.unmodifiableList(fetch);
384 	}
385 
386 	/**
387 	 * Add a new fetch RefSpec to this remote.
388 	 *
389 	 * @param s
390 	 *            the new specification to add.
391 	 * @return true if the specification was added; false if it already exists.
392 	 */
393 	public boolean addFetchRefSpec(final RefSpec s) {
394 		if (fetch.contains(s))
395 			return false;
396 		return fetch.add(s);
397 	}
398 
399 	/**
400 	 * Override existing fetch specifications with new ones.
401 	 *
402 	 * @param specs
403 	 *            list of fetch specifications to set. List is copied, it can be
404 	 *            modified after this call.
405 	 */
406 	public void setFetchRefSpecs(final List<RefSpec> specs) {
407 		fetch.clear();
408 		fetch.addAll(specs);
409 	}
410 
411 	/**
412 	 * Override existing push specifications with new ones.
413 	 *
414 	 * @param specs
415 	 *            list of push specifications to set. List is copied, it can be
416 	 *            modified after this call.
417 	 */
418 	public void setPushRefSpecs(final List<RefSpec> specs) {
419 		push.clear();
420 		push.addAll(specs);
421 	}
422 
423 	/**
424 	 * Remove a fetch RefSpec from this remote.
425 	 *
426 	 * @param s
427 	 *            the specification to remove.
428 	 * @return true if the specification existed and was removed.
429 	 */
430 	public boolean removeFetchRefSpec(final RefSpec s) {
431 		return fetch.remove(s);
432 	}
433 
434 	/**
435 	 * Remembered specifications for pushing to a repository.
436 	 *
437 	 * @return set of specs used by default when pushing.
438 	 */
439 	public List<RefSpec> getPushRefSpecs() {
440 		return Collections.unmodifiableList(push);
441 	}
442 
443 	/**
444 	 * Add a new push RefSpec to this remote.
445 	 *
446 	 * @param s
447 	 *            the new specification to add.
448 	 * @return true if the specification was added; false if it already exists.
449 	 */
450 	public boolean addPushRefSpec(final RefSpec s) {
451 		if (push.contains(s))
452 			return false;
453 		return push.add(s);
454 	}
455 
456 	/**
457 	 * Remove a push RefSpec from this remote.
458 	 *
459 	 * @param s
460 	 *            the specification to remove.
461 	 * @return true if the specification existed and was removed.
462 	 */
463 	public boolean removePushRefSpec(final RefSpec s) {
464 		return push.remove(s);
465 	}
466 
467 	/**
468 	 * Override for the location of 'git-upload-pack' on the remote system.
469 	 * <p>
470 	 * This value is only useful for an SSH style connection, where Git is
471 	 * asking the remote system to execute a program that provides the necessary
472 	 * network protocol.
473 	 *
474 	 * @return location of 'git-upload-pack' on the remote system. If no
475 	 *         location has been configured the default of 'git-upload-pack' is
476 	 *         returned instead.
477 	 */
478 	public String getUploadPack() {
479 		return uploadpack;
480 	}
481 
482 	/**
483 	 * Override for the location of 'git-receive-pack' on the remote system.
484 	 * <p>
485 	 * This value is only useful for an SSH style connection, where Git is
486 	 * asking the remote system to execute a program that provides the necessary
487 	 * network protocol.
488 	 *
489 	 * @return location of 'git-receive-pack' on the remote system. If no
490 	 *         location has been configured the default of 'git-receive-pack' is
491 	 *         returned instead.
492 	 */
493 	public String getReceivePack() {
494 		return receivepack;
495 	}
496 
497 	/**
498 	 * Get the description of how annotated tags should be treated during fetch.
499 	 *
500 	 * @return option indicating the behavior of annotated tags in fetch.
501 	 */
502 	public TagOpt getTagOpt() {
503 		return tagopt;
504 	}
505 
506 	/**
507 	 * Set the description of how annotated tags should be treated on fetch.
508 	 *
509 	 * @param option
510 	 *            method to use when handling annotated tags.
511 	 */
512 	public void setTagOpt(final TagOpt option) {
513 		tagopt = option != null ? option : TagOpt.AUTO_FOLLOW;
514 	}
515 
516 	/**
517 	 * @return true if pushing to the remote automatically deletes remote refs
518 	 *         which don't exist on the source side.
519 	 */
520 	public boolean isMirror() {
521 		return mirror;
522 	}
523 
524 	/**
525 	 * Set the mirror flag to automatically delete remote refs.
526 	 *
527 	 * @param m
528 	 *            true to automatically delete remote refs during push.
529 	 */
530 	public void setMirror(final boolean m) {
531 		mirror = m;
532 	}
533 
534 	/** @return timeout (in seconds) before aborting an IO operation. */
535 	public int getTimeout() {
536 		return timeout;
537 	}
538 
539 	/**
540 	 * Set the timeout before willing to abort an IO call.
541 	 *
542 	 * @param seconds
543 	 *            number of seconds to wait (with no data transfer occurring)
544 	 *            before aborting an IO read or write operation with this
545 	 *            remote.  A timeout of 0 will block indefinitely.
546 	 */
547 	public void setTimeout(final int seconds) {
548 		timeout = seconds;
549 	}
550 }