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 		Entry<String, String> match = null;
298 		for (Entry<String, String> replacement : replacements.entrySet()) {
299 			// Ignore current entry if not longer than previous match
300 			if (match != null
301 					&& match.getKey().length() > replacement.getKey().length())
302 				continue;
303 			if (!uri.startsWith(replacement.getKey()))
304 				continue;
305 			match = replacement;
306 		}
307 		if (match != null)
308 			return match.getValue() + uri.substring(match.getKey().length());
309 		else
310 			return uri;
311 	}
312 
313 	/**
314 	 * Get the local name this remote configuration is recognized as.
315 	 *
316 	 * @return name assigned by the user to this configuration block.
317 	 */
318 	public String getName() {
319 		return name;
320 	}
321 
322 	/**
323 	 * Get all configured URIs under this remote.
324 	 *
325 	 * @return the set of URIs known to this remote.
326 	 */
327 	public List<URIish> getURIs() {
328 		return Collections.unmodifiableList(uris);
329 	}
330 
331 	/**
332 	 * Add a new URI to the end of the list of URIs.
333 	 *
334 	 * @param toAdd
335 	 *            the new URI to add to this remote.
336 	 * @return true if the URI was added; false if it already exists.
337 	 */
338 	public boolean addURI(URIish toAdd) {
339 		if (uris.contains(toAdd))
340 			return false;
341 		return uris.add(toAdd);
342 	}
343 
344 	/**
345 	 * Remove a URI from the list of URIs.
346 	 *
347 	 * @param toRemove
348 	 *            the URI to remove from this remote.
349 	 * @return true if the URI was added; false if it already exists.
350 	 */
351 	public boolean removeURI(URIish toRemove) {
352 		return uris.remove(toRemove);
353 	}
354 
355 	/**
356 	 * Get all configured push-only URIs under this remote.
357 	 *
358 	 * @return the set of URIs known to this remote.
359 	 */
360 	public List<URIish> getPushURIs() {
361 		return Collections.unmodifiableList(pushURIs);
362 	}
363 
364 	/**
365 	 * Add a new push-only URI to the end of the list of URIs.
366 	 *
367 	 * @param toAdd
368 	 *            the new URI to add to this remote.
369 	 * @return true if the URI was added; false if it already exists.
370 	 */
371 	public boolean addPushURI(URIish toAdd) {
372 		if (pushURIs.contains(toAdd))
373 			return false;
374 		return pushURIs.add(toAdd);
375 	}
376 
377 	/**
378 	 * Remove a push-only URI from the list of URIs.
379 	 *
380 	 * @param toRemove
381 	 *            the URI to remove from this remote.
382 	 * @return true if the URI was added; false if it already exists.
383 	 */
384 	public boolean removePushURI(URIish toRemove) {
385 		return pushURIs.remove(toRemove);
386 	}
387 
388 	/**
389 	 * Remembered specifications for fetching from a repository.
390 	 *
391 	 * @return set of specs used by default when fetching.
392 	 */
393 	public List<RefSpec> getFetchRefSpecs() {
394 		return Collections.unmodifiableList(fetch);
395 	}
396 
397 	/**
398 	 * Add a new fetch RefSpec to this remote.
399 	 *
400 	 * @param s
401 	 *            the new specification to add.
402 	 * @return true if the specification was added; false if it already exists.
403 	 */
404 	public boolean addFetchRefSpec(RefSpec s) {
405 		if (fetch.contains(s))
406 			return false;
407 		return fetch.add(s);
408 	}
409 
410 	/**
411 	 * Override existing fetch specifications with new ones.
412 	 *
413 	 * @param specs
414 	 *            list of fetch specifications to set. List is copied, it can be
415 	 *            modified after this call.
416 	 */
417 	public void setFetchRefSpecs(List<RefSpec> specs) {
418 		fetch.clear();
419 		fetch.addAll(specs);
420 	}
421 
422 	/**
423 	 * Override existing push specifications with new ones.
424 	 *
425 	 * @param specs
426 	 *            list of push specifications to set. List is copied, it can be
427 	 *            modified after this call.
428 	 */
429 	public void setPushRefSpecs(List<RefSpec> specs) {
430 		push.clear();
431 		push.addAll(specs);
432 	}
433 
434 	/**
435 	 * Remove a fetch RefSpec from this remote.
436 	 *
437 	 * @param s
438 	 *            the specification to remove.
439 	 * @return true if the specification existed and was removed.
440 	 */
441 	public boolean removeFetchRefSpec(RefSpec s) {
442 		return fetch.remove(s);
443 	}
444 
445 	/**
446 	 * Remembered specifications for pushing to a repository.
447 	 *
448 	 * @return set of specs used by default when pushing.
449 	 */
450 	public List<RefSpec> getPushRefSpecs() {
451 		return Collections.unmodifiableList(push);
452 	}
453 
454 	/**
455 	 * Add a new push RefSpec to this remote.
456 	 *
457 	 * @param s
458 	 *            the new specification to add.
459 	 * @return true if the specification was added; false if it already exists.
460 	 */
461 	public boolean addPushRefSpec(RefSpec s) {
462 		if (push.contains(s))
463 			return false;
464 		return push.add(s);
465 	}
466 
467 	/**
468 	 * Remove a push RefSpec from this remote.
469 	 *
470 	 * @param s
471 	 *            the specification to remove.
472 	 * @return true if the specification existed and was removed.
473 	 */
474 	public boolean removePushRefSpec(RefSpec s) {
475 		return push.remove(s);
476 	}
477 
478 	/**
479 	 * Override for the location of 'git-upload-pack' on the remote system.
480 	 * <p>
481 	 * This value is only useful for an SSH style connection, where Git is
482 	 * asking the remote system to execute a program that provides the necessary
483 	 * network protocol.
484 	 *
485 	 * @return location of 'git-upload-pack' on the remote system. If no
486 	 *         location has been configured the default of 'git-upload-pack' is
487 	 *         returned instead.
488 	 */
489 	public String getUploadPack() {
490 		return uploadpack;
491 	}
492 
493 	/**
494 	 * Override for the location of 'git-receive-pack' on the remote system.
495 	 * <p>
496 	 * This value is only useful for an SSH style connection, where Git is
497 	 * asking the remote system to execute a program that provides the necessary
498 	 * network protocol.
499 	 *
500 	 * @return location of 'git-receive-pack' on the remote system. If no
501 	 *         location has been configured the default of 'git-receive-pack' is
502 	 *         returned instead.
503 	 */
504 	public String getReceivePack() {
505 		return receivepack;
506 	}
507 
508 	/**
509 	 * Get the description of how annotated tags should be treated during fetch.
510 	 *
511 	 * @return option indicating the behavior of annotated tags in fetch.
512 	 */
513 	public TagOpt getTagOpt() {
514 		return tagopt;
515 	}
516 
517 	/**
518 	 * Set the description of how annotated tags should be treated on fetch.
519 	 *
520 	 * @param option
521 	 *            method to use when handling annotated tags.
522 	 */
523 	public void setTagOpt(TagOpt option) {
524 		tagopt = option != null ? option : TagOpt.AUTO_FOLLOW;
525 	}
526 
527 	/**
528 	 * Whether pushing to the remote automatically deletes remote refs which
529 	 * don't exist on the source side.
530 	 *
531 	 * @return true if pushing to the remote automatically deletes remote refs
532 	 *         which don't exist on the source side.
533 	 */
534 	public boolean isMirror() {
535 		return mirror;
536 	}
537 
538 	/**
539 	 * Set the mirror flag to automatically delete remote refs.
540 	 *
541 	 * @param m
542 	 *            true to automatically delete remote refs during push.
543 	 */
544 	public void setMirror(boolean m) {
545 		mirror = m;
546 	}
547 
548 	/**
549 	 * Get timeout (in seconds) before aborting an IO operation.
550 	 *
551 	 * @return timeout (in seconds) before aborting an IO operation.
552 	 */
553 	public int getTimeout() {
554 		return timeout;
555 	}
556 
557 	/**
558 	 * Set the timeout before willing to abort an IO call.
559 	 *
560 	 * @param seconds
561 	 *            number of seconds to wait (with no data transfer occurring)
562 	 *            before aborting an IO read or write operation with this
563 	 *            remote.  A timeout of 0 will block indefinitely.
564 	 */
565 	public void setTimeout(int seconds) {
566 		timeout = seconds;
567 	}
568 }