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 }