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 java.net.URISyntaxException
113 	 *             one of the URIs within the remote's configuration is invalid.
114 	 */
115 	public static List<RemoteConfig> getAllRemoteConfigs(Config rc)
116 			throws URISyntaxException {
117 		final List<String> names = new ArrayList<>(rc
118 				.getSubsections(SECTION));
119 		Collections.sort(names);
120 
121 		final List<RemoteConfig> result = new ArrayList<>(names
122 				.size());
123 		for (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 java.net.URISyntaxException
161 	 *             one of the URIs within the remote's configuration is invalid.
162 	 */
163 	public RemoteConfig(Config rc, 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<>(vlst.length);
173 		for (String s : vlst) {
174 			uris.add(new URIish(replaceUri(s, insteadOf)));
175 		}
176 		String[] plst = rc.getStringList(SECTION, name, KEY_PUSHURL);
177 		pushURIs = new ArrayList<>(plst.length);
178 		for (String s : plst) {
179 			pushURIs.add(new URIish(s));
180 		}
181 		if (pushURIs.isEmpty()) {
182 			// Would default to the uris. If we have pushinsteadof, we must
183 			// supply rewritten push uris.
184 			Map<String, String> pushInsteadOf = getReplacements(rc,
185 					KEY_PUSHINSTEADOF);
186 			if (!pushInsteadOf.isEmpty()) {
187 				for (String s : vlst) {
188 					String replaced = replaceUri(s, pushInsteadOf);
189 					if (!s.equals(replaced)) {
190 						pushURIs.add(new URIish(replaced));
191 					}
192 				}
193 			}
194 		}
195 		fetch = rc.getRefSpecs(SECTION, name, KEY_FETCH);
196 		push = rc.getRefSpecs(SECTION, name, KEY_PUSH);
197 		val = rc.getString(SECTION, name, KEY_UPLOADPACK);
198 		if (val == null) {
199 			val = DEFAULT_UPLOAD_PACK;
200 		}
201 		uploadpack = val;
202 
203 		val = rc.getString(SECTION, name, KEY_RECEIVEPACK);
204 		if (val == null) {
205 			val = DEFAULT_RECEIVE_PACK;
206 		}
207 		receivepack = val;
208 
209 		try {
210 			val = rc.getString(SECTION, name, KEY_TAGOPT);
211 			tagopt = TagOpt.fromOption(val);
212 		} catch (IllegalArgumentException e) {
213 			// C git silently ignores invalid tagopt values.
214 			tagopt = TagOpt.AUTO_FOLLOW;
215 		}
216 		mirror = rc.getBoolean(SECTION, name, KEY_MIRROR, DEFAULT_MIRROR);
217 		timeout = rc.getInt(SECTION, name, KEY_TIMEOUT, 0);
218 	}
219 
220 	/**
221 	 * Update this remote's definition within the configuration.
222 	 *
223 	 * @param rc
224 	 *            the configuration file to store ourselves into.
225 	 */
226 	public void update(Config rc) {
227 		final List<String> vlst = new ArrayList<>();
228 
229 		vlst.clear();
230 		for (URIish u : getURIs())
231 			vlst.add(u.toPrivateString());
232 		rc.setStringList(SECTION, getName(), KEY_URL, vlst);
233 
234 		vlst.clear();
235 		for (URIish u : getPushURIs())
236 			vlst.add(u.toPrivateString());
237 		rc.setStringList(SECTION, getName(), KEY_PUSHURL, vlst);
238 
239 		vlst.clear();
240 		for (RefSpec u : getFetchRefSpecs())
241 			vlst.add(u.toString());
242 		rc.setStringList(SECTION, getName(), KEY_FETCH, vlst);
243 
244 		vlst.clear();
245 		for (RefSpec u : getPushRefSpecs())
246 			vlst.add(u.toString());
247 		rc.setStringList(SECTION, getName(), KEY_PUSH, vlst);
248 
249 		set(rc, KEY_UPLOADPACK, getUploadPack(), DEFAULT_UPLOAD_PACK);
250 		set(rc, KEY_RECEIVEPACK, getReceivePack(), DEFAULT_RECEIVE_PACK);
251 		set(rc, KEY_TAGOPT, getTagOpt().option(), TagOpt.AUTO_FOLLOW.option());
252 		set(rc, KEY_MIRROR, mirror, DEFAULT_MIRROR);
253 		set(rc, KEY_TIMEOUT, timeout, 0);
254 	}
255 
256 	private void set(final Config rc, final String key,
257 			final String currentValue, final String defaultValue) {
258 		if (defaultValue.equals(currentValue))
259 			unset(rc, key);
260 		else
261 			rc.setString(SECTION, getName(), key, currentValue);
262 	}
263 
264 	private void set(final Config rc, final String key,
265 			final boolean currentValue, final boolean defaultValue) {
266 		if (defaultValue == currentValue)
267 			unset(rc, key);
268 		else
269 			rc.setBoolean(SECTION, getName(), key, currentValue);
270 	}
271 
272 	private void set(final Config rc, final String key, final int currentValue,
273 			final int defaultValue) {
274 		if (defaultValue == currentValue)
275 			unset(rc, key);
276 		else
277 			rc.setInt(SECTION, getName(), key, currentValue);
278 	}
279 
280 	private void unset(Config rc, String key) {
281 		rc.unset(SECTION, getName(), key);
282 	}
283 
284 	private Map<String, String> getReplacements(final Config config,
285 			final String keyName) {
286 		final Map<String, String> replacements = new HashMap<>();
287 		for (String url : config.getSubsections(KEY_URL))
288 			for (String insteadOf : config.getStringList(KEY_URL, url, keyName))
289 				replacements.put(insteadOf, url);
290 		return replacements;
291 	}
292 
293 	private String replaceUri(final String uri,
294 			final Map<String, String> replacements) {
295 		if (replacements.isEmpty()) {
296 			return uri;
297 		}
298 		Entry<String, String> match = null;
299 		for (Entry<String, String> replacement : replacements.entrySet()) {
300 			// Ignore current entry if not longer than previous match
301 			if (match != null
302 					&& match.getKey().length() > replacement.getKey()
303 							.length()) {
304 				continue;
305 			}
306 			if (!uri.startsWith(replacement.getKey())) {
307 				continue;
308 			}
309 			match = replacement;
310 		}
311 		if (match != null) {
312 			return match.getValue() + uri.substring(match.getKey().length());
313 		}
314 		return uri;
315 	}
316 
317 	/**
318 	 * Get the local name this remote configuration is recognized as.
319 	 *
320 	 * @return name assigned by the user to this configuration block.
321 	 */
322 	public String getName() {
323 		return name;
324 	}
325 
326 	/**
327 	 * Get all configured URIs under this remote.
328 	 *
329 	 * @return the set of URIs known to this remote.
330 	 */
331 	public List<URIish> getURIs() {
332 		return Collections.unmodifiableList(uris);
333 	}
334 
335 	/**
336 	 * Add a new URI to the end of the list of URIs.
337 	 *
338 	 * @param toAdd
339 	 *            the new URI to add to this remote.
340 	 * @return true if the URI was added; false if it already exists.
341 	 */
342 	public boolean addURI(URIish toAdd) {
343 		if (uris.contains(toAdd))
344 			return false;
345 		return uris.add(toAdd);
346 	}
347 
348 	/**
349 	 * Remove a URI from the list of URIs.
350 	 *
351 	 * @param toRemove
352 	 *            the URI to remove from this remote.
353 	 * @return true if the URI was added; false if it already exists.
354 	 */
355 	public boolean removeURI(URIish toRemove) {
356 		return uris.remove(toRemove);
357 	}
358 
359 	/**
360 	 * Get all configured push-only URIs under this remote.
361 	 *
362 	 * @return the set of URIs known to this remote.
363 	 */
364 	public List<URIish> getPushURIs() {
365 		return Collections.unmodifiableList(pushURIs);
366 	}
367 
368 	/**
369 	 * Add a new push-only URI to the end of the list of URIs.
370 	 *
371 	 * @param toAdd
372 	 *            the new URI to add to this remote.
373 	 * @return true if the URI was added; false if it already exists.
374 	 */
375 	public boolean addPushURI(URIish toAdd) {
376 		if (pushURIs.contains(toAdd))
377 			return false;
378 		return pushURIs.add(toAdd);
379 	}
380 
381 	/**
382 	 * Remove a push-only URI from the list of URIs.
383 	 *
384 	 * @param toRemove
385 	 *            the URI to remove from this remote.
386 	 * @return true if the URI was added; false if it already exists.
387 	 */
388 	public boolean removePushURI(URIish toRemove) {
389 		return pushURIs.remove(toRemove);
390 	}
391 
392 	/**
393 	 * Remembered specifications for fetching from a repository.
394 	 *
395 	 * @return set of specs used by default when fetching.
396 	 */
397 	public List<RefSpec> getFetchRefSpecs() {
398 		return Collections.unmodifiableList(fetch);
399 	}
400 
401 	/**
402 	 * Add a new fetch RefSpec to this remote.
403 	 *
404 	 * @param s
405 	 *            the new specification to add.
406 	 * @return true if the specification was added; false if it already exists.
407 	 */
408 	public boolean addFetchRefSpec(RefSpec s) {
409 		if (fetch.contains(s))
410 			return false;
411 		return fetch.add(s);
412 	}
413 
414 	/**
415 	 * Override existing fetch specifications with new ones.
416 	 *
417 	 * @param specs
418 	 *            list of fetch specifications to set. List is copied, it can be
419 	 *            modified after this call.
420 	 */
421 	public void setFetchRefSpecs(List<RefSpec> specs) {
422 		fetch.clear();
423 		fetch.addAll(specs);
424 	}
425 
426 	/**
427 	 * Override existing push specifications with new ones.
428 	 *
429 	 * @param specs
430 	 *            list of push specifications to set. List is copied, it can be
431 	 *            modified after this call.
432 	 */
433 	public void setPushRefSpecs(List<RefSpec> specs) {
434 		push.clear();
435 		push.addAll(specs);
436 	}
437 
438 	/**
439 	 * Remove a fetch RefSpec from this remote.
440 	 *
441 	 * @param s
442 	 *            the specification to remove.
443 	 * @return true if the specification existed and was removed.
444 	 */
445 	public boolean removeFetchRefSpec(RefSpec s) {
446 		return fetch.remove(s);
447 	}
448 
449 	/**
450 	 * Remembered specifications for pushing to a repository.
451 	 *
452 	 * @return set of specs used by default when pushing.
453 	 */
454 	public List<RefSpec> getPushRefSpecs() {
455 		return Collections.unmodifiableList(push);
456 	}
457 
458 	/**
459 	 * Add a new push RefSpec to this remote.
460 	 *
461 	 * @param s
462 	 *            the new specification to add.
463 	 * @return true if the specification was added; false if it already exists.
464 	 */
465 	public boolean addPushRefSpec(RefSpec s) {
466 		if (push.contains(s))
467 			return false;
468 		return push.add(s);
469 	}
470 
471 	/**
472 	 * Remove a push RefSpec from this remote.
473 	 *
474 	 * @param s
475 	 *            the specification to remove.
476 	 * @return true if the specification existed and was removed.
477 	 */
478 	public boolean removePushRefSpec(RefSpec s) {
479 		return push.remove(s);
480 	}
481 
482 	/**
483 	 * Override for the location of 'git-upload-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-upload-pack' on the remote system. If no
490 	 *         location has been configured the default of 'git-upload-pack' is
491 	 *         returned instead.
492 	 */
493 	public String getUploadPack() {
494 		return uploadpack;
495 	}
496 
497 	/**
498 	 * Override for the location of 'git-receive-pack' on the remote system.
499 	 * <p>
500 	 * This value is only useful for an SSH style connection, where Git is
501 	 * asking the remote system to execute a program that provides the necessary
502 	 * network protocol.
503 	 *
504 	 * @return location of 'git-receive-pack' on the remote system. If no
505 	 *         location has been configured the default of 'git-receive-pack' is
506 	 *         returned instead.
507 	 */
508 	public String getReceivePack() {
509 		return receivepack;
510 	}
511 
512 	/**
513 	 * Get the description of how annotated tags should be treated during fetch.
514 	 *
515 	 * @return option indicating the behavior of annotated tags in fetch.
516 	 */
517 	public TagOpt getTagOpt() {
518 		return tagopt;
519 	}
520 
521 	/**
522 	 * Set the description of how annotated tags should be treated on fetch.
523 	 *
524 	 * @param option
525 	 *            method to use when handling annotated tags.
526 	 */
527 	public void setTagOpt(TagOpt option) {
528 		tagopt = option != null ? option : TagOpt.AUTO_FOLLOW;
529 	}
530 
531 	/**
532 	 * Whether pushing to the remote automatically deletes remote refs which
533 	 * don't exist on the source side.
534 	 *
535 	 * @return true if pushing to the remote automatically deletes remote refs
536 	 *         which don't exist on the source side.
537 	 */
538 	public boolean isMirror() {
539 		return mirror;
540 	}
541 
542 	/**
543 	 * Set the mirror flag to automatically delete remote refs.
544 	 *
545 	 * @param m
546 	 *            true to automatically delete remote refs during push.
547 	 */
548 	public void setMirror(boolean m) {
549 		mirror = m;
550 	}
551 
552 	/**
553 	 * Get timeout (in seconds) before aborting an IO operation.
554 	 *
555 	 * @return timeout (in seconds) before aborting an IO operation.
556 	 */
557 	public int getTimeout() {
558 		return timeout;
559 	}
560 
561 	/**
562 	 * Set the timeout before willing to abort an IO call.
563 	 *
564 	 * @param seconds
565 	 *            number of seconds to wait (with no data transfer occurring)
566 	 *            before aborting an IO read or write operation with this
567 	 *            remote.  A timeout of 0 will block indefinitely.
568 	 */
569 	public void setTimeout(int seconds) {
570 		timeout = seconds;
571 	}
572 }