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