View Javadoc
1   /*
2    * Copyright (C) 2010, Mathias Kinzler <mathias.kinzler@sap.com>
3    * Copyright (C) 2009, Constantine Plotnikov <constantine.plotnikov@gmail.com>
4    * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>
5    * Copyright (C) 2008-2010, Google Inc.
6    * Copyright (C) 2009, Google, Inc.
7    * Copyright (C) 2009, JetBrains s.r.o.
8    * Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
9    * Copyright (C) 2006-2008, Shawn O. Pearce <spearce@spearce.org>
10   * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com> and others
11   *
12   * This program and the accompanying materials are made available under the
13   * terms of the Eclipse Distribution License v. 1.0 which is available at
14   * https://www.eclipse.org/org/documents/edl-v10.php.
15   *
16   * SPDX-License-Identifier: BSD-3-Clause
17   */
18  
19  package org.eclipse.jgit.lib;
20  
21  import static java.nio.charset.StandardCharsets.UTF_8;
22  
23  import java.io.File;
24  import java.nio.file.InvalidPathException;
25  import java.nio.file.Path;
26  import java.text.MessageFormat;
27  import java.util.ArrayList;
28  import java.util.Collections;
29  import java.util.List;
30  import java.util.Locale;
31  import java.util.Set;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.atomic.AtomicReference;
34  
35  import org.eclipse.jgit.annotations.NonNull;
36  import org.eclipse.jgit.errors.ConfigInvalidException;
37  import org.eclipse.jgit.events.ConfigChangedEvent;
38  import org.eclipse.jgit.events.ConfigChangedListener;
39  import org.eclipse.jgit.events.ListenerHandle;
40  import org.eclipse.jgit.events.ListenerList;
41  import org.eclipse.jgit.internal.JGitText;
42  import org.eclipse.jgit.transport.RefSpec;
43  import org.eclipse.jgit.util.FS;
44  import org.eclipse.jgit.util.RawParseUtils;
45  import org.eclipse.jgit.util.StringUtils;
46  
47  /**
48   * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
49   */
50  public class Config {
51  
52  	private static final String[] EMPTY_STRING_ARRAY = {};
53  
54  	private static final int MAX_DEPTH = 10;
55  
56  	private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();
57  
58  	private static TypedConfigGetter typedGetter = DEFAULT_GETTER;
59  
60  	/** the change listeners */
61  	private final ListenerList listeners = new ListenerList();
62  
63  	/**
64  	 * Immutable current state of the configuration data.
65  	 * <p>
66  	 * This state is copy-on-write. It should always contain an immutable list
67  	 * of the configuration keys/values.
68  	 */
69  	private final AtomicReference<ConfigSnapshot> state;
70  
71  	private final Config baseConfig;
72  
73  	/**
74  	 * Magic value indicating a missing entry.
75  	 * <p>
76  	 * This value is tested for reference equality in some contexts, so we
77  	 * must ensure it is a special copy of the empty string.  It also must
78  	 * be treated like the empty string.
79  	 */
80  	private static final String MISSING_ENTRY = new String();
81  
82  	/**
83  	 * Create a configuration with no default fallback.
84  	 */
85  	public Config() {
86  		this(null);
87  	}
88  
89  	/**
90  	 * Create an empty configuration with a fallback for missing keys.
91  	 *
92  	 * @param defaultConfig
93  	 *            the base configuration to be consulted when a key is missing
94  	 *            from this configuration instance.
95  	 */
96  	public Config(Config defaultConfig) {
97  		baseConfig = defaultConfig;
98  		state = new AtomicReference<>(newState());
99  	}
100 
101 	/**
102 	 * Retrieves this config's base config.
103 	 *
104 	 * @return the base configuration of this config.
105 	 *
106 	 * @since 5.5.2
107 	 */
108 	public Config getBaseConfig() {
109 		return baseConfig;
110 	}
111 
112 	/**
113 	 * Check if a given string is the "missing" value.
114 	 *
115 	 * @param value
116 	 *            string to be checked.
117 	 * @return true if the given string is the "missing" value.
118 	 * @since 5.4
119 	 */
120 	@SuppressWarnings({ "ReferenceEquality", "StringEquality" })
121 	public static boolean isMissing(String value) {
122 		return value == MISSING_ENTRY;
123 	}
124 
125 	/**
126 	 * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
127 	 * subsequently used to read typed values from all git configs.
128 	 *
129 	 * @param getter
130 	 *            to use; if {@code null} use the default getter.
131 	 * @since 4.9
132 	 */
133 	public static void setTypedConfigGetter(TypedConfigGetter getter) {
134 		typedGetter = getter == null ? DEFAULT_GETTER : getter;
135 	}
136 
137 	/**
138 	 * Escape the value before saving
139 	 *
140 	 * @param x
141 	 *            the value to escape
142 	 * @return the escaped value
143 	 */
144 	static String escapeValue(String x) {
145 		if (x.isEmpty()) {
146 			return ""; //$NON-NLS-1$
147 		}
148 
149 		boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
150 		StringBuilder r = new StringBuilder(x.length());
151 		for (int k = 0; k < x.length(); k++) {
152 			char c = x.charAt(k);
153 			// git-config(1) lists the limited set of supported escape sequences, but
154 			// the documentation is otherwise not especially normative. In particular,
155 			// which ones of these produce and/or require escaping and/or quoting
156 			// around them is not documented and was discovered by trial and error.
157 			// In summary:
158 			//
159 			// * Quotes are only required if there is leading/trailing whitespace or a
160 			//   comment character.
161 			// * Bytes that have a supported escape sequence are escaped, except for
162 			//   \b for some reason which isn't.
163 			// * Needing an escape sequence is not sufficient reason to quote the
164 			//   value.
165 			switch (c) {
166 			case '\0':
167 				// Unix command line calling convention cannot pass a '\0' as an
168 				// argument, so there is no equivalent way in C git to store a null byte
169 				// in a config value.
170 				throw new IllegalArgumentException(
171 						JGitText.get().configValueContainsNullByte);
172 
173 			case '\n':
174 				r.append('\\').append('n');
175 				break;
176 
177 			case '\t':
178 				r.append('\\').append('t');
179 				break;
180 
181 			case '\b':
182 				// Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the
183 				// \x08, but since both escaped and unescaped forms are readable, we'll
184 				// prefer internal consistency here.
185 				r.append('\\').append('b');
186 				break;
187 
188 			case '\\':
189 				r.append('\\').append('\\');
190 				break;
191 
192 			case '"':
193 				r.append('\\').append('"');
194 				break;
195 
196 			case '#':
197 			case ';':
198 				needQuote = true;
199 				r.append(c);
200 				break;
201 
202 			default:
203 				r.append(c);
204 				break;
205 			}
206 		}
207 
208 		return needQuote ? '"' + r.toString() + '"' : r.toString();
209 	}
210 
211 	static String escapeSubsection(String x) {
212 		if (x.isEmpty()) {
213 			return "\"\""; //$NON-NLS-1$
214 		}
215 
216 		StringBuilder r = new StringBuilder(x.length() + 2).append('"');
217 		for (int k = 0; k < x.length(); k++) {
218 			char c = x.charAt(k);
219 
220 			// git-config(1) lists the limited set of supported escape sequences
221 			// (which is even more limited for subsection names than for values).
222 			switch (c) {
223 			case '\0':
224 				throw new IllegalArgumentException(
225 						JGitText.get().configSubsectionContainsNullByte);
226 
227 			case '\n':
228 				throw new IllegalArgumentException(
229 						JGitText.get().configSubsectionContainsNewline);
230 
231 			case '\\':
232 			case '"':
233 				r.append('\\').append(c);
234 				break;
235 
236 			default:
237 				r.append(c);
238 				break;
239 			}
240 		}
241 
242 		return r.append('"').toString();
243 	}
244 
245 	/**
246 	 * Obtain an integer value from the configuration.
247 	 *
248 	 * @param section
249 	 *            section the key is grouped within.
250 	 * @param name
251 	 *            name of the key to get.
252 	 * @param defaultValue
253 	 *            default value to return if no value was present.
254 	 * @return an integer value from the configuration, or defaultValue.
255 	 */
256 	public int getInt(final String section, final String name,
257 			final int defaultValue) {
258 		return typedGetter.getInt(this, section, null, name, defaultValue);
259 	}
260 
261 	/**
262 	 * Obtain an integer value from the configuration.
263 	 *
264 	 * @param section
265 	 *            section the key is grouped within.
266 	 * @param subsection
267 	 *            subsection name, such a remote or branch name.
268 	 * @param name
269 	 *            name of the key to get.
270 	 * @param defaultValue
271 	 *            default value to return if no value was present.
272 	 * @return an integer value from the configuration, or defaultValue.
273 	 */
274 	public int getInt(final String section, String subsection,
275 			final String name, final int defaultValue) {
276 		return typedGetter.getInt(this, section, subsection, name,
277 				defaultValue);
278 	}
279 
280 	/**
281 	 * Obtain an integer value from the configuration which must be inside given
282 	 * range.
283 	 *
284 	 * @param section
285 	 *            section the key is grouped within.
286 	 * @param name
287 	 *            name of the key to get.
288 	 * @param minValue
289 	 *            minimum value
290 	 * @param maxValue
291 	 *            maximum value
292 	 * @param defaultValue
293 	 *            default value to return if no value was present.
294 	 * @return an integer value from the configuration, or defaultValue.
295 	 * @since 6.1
296 	 */
297 	public int getIntInRange(String section, String name, int minValue,
298 			int maxValue, int defaultValue) {
299 		return typedGetter.getIntInRange(this, section, null, name, minValue,
300 				maxValue, defaultValue);
301 	}
302 
303 	/**
304 	 * Obtain an integer value from the configuration which must be inside given
305 	 * range.
306 	 *
307 	 * @param section
308 	 *            section the key is grouped within.
309 	 * @param subsection
310 	 *            subsection name, such a remote or branch name.
311 	 * @param name
312 	 *            name of the key to get.
313 	 * @param minValue
314 	 *            minimum value
315 	 * @param maxValue
316 	 *            maximum value
317 	 * @param defaultValue
318 	 *            default value to return if no value was present.
319 	 * @return an integer value from the configuration, or defaultValue.
320 	 * @since 6.1
321 	 */
322 	public int getIntInRange(String section, String subsection, String name,
323 			int minValue, int maxValue, int defaultValue) {
324 		return typedGetter.getIntInRange(this, section, subsection, name,
325 				minValue, maxValue, defaultValue);
326 	}
327 
328 	/**
329 	 * Obtain an integer value from the configuration.
330 	 *
331 	 * @param section
332 	 *            section the key is grouped within.
333 	 * @param name
334 	 *            name of the key to get.
335 	 * @param defaultValue
336 	 *            default value to return if no value was present.
337 	 * @return an integer value from the configuration, or defaultValue.
338 	 */
339 	public long getLong(String section, String name, long defaultValue) {
340 		return typedGetter.getLong(this, section, null, name, defaultValue);
341 	}
342 
343 	/**
344 	 * Obtain an integer value from the configuration.
345 	 *
346 	 * @param section
347 	 *            section the key is grouped within.
348 	 * @param subsection
349 	 *            subsection name, such a remote or branch name.
350 	 * @param name
351 	 *            name of the key to get.
352 	 * @param defaultValue
353 	 *            default value to return if no value was present.
354 	 * @return an integer value from the configuration, or defaultValue.
355 	 */
356 	public long getLong(final String section, String subsection,
357 			final String name, final long defaultValue) {
358 		return typedGetter.getLong(this, section, subsection, name,
359 				defaultValue);
360 	}
361 
362 	/**
363 	 * Get a boolean value from the git config
364 	 *
365 	 * @param section
366 	 *            section the key is grouped within.
367 	 * @param name
368 	 *            name of the key to get.
369 	 * @param defaultValue
370 	 *            default value to return if no value was present.
371 	 * @return true if any value or defaultValue is true, false for missing or
372 	 *         explicit false
373 	 */
374 	public boolean getBoolean(final String section, final String name,
375 			final boolean defaultValue) {
376 		return typedGetter.getBoolean(this, section, null, name, defaultValue);
377 	}
378 
379 	/**
380 	 * Get a boolean value from the git config
381 	 *
382 	 * @param section
383 	 *            section the key is grouped within.
384 	 * @param subsection
385 	 *            subsection name, such a remote or branch name.
386 	 * @param name
387 	 *            name of the key to get.
388 	 * @param defaultValue
389 	 *            default value to return if no value was present.
390 	 * @return true if any value or defaultValue is true, false for missing or
391 	 *         explicit false
392 	 */
393 	public boolean getBoolean(final String section, String subsection,
394 			final String name, final boolean defaultValue) {
395 		return typedGetter.getBoolean(this, section, subsection, name,
396 				defaultValue);
397 	}
398 
399 	/**
400 	 * Parse an enumeration from the configuration.
401 	 *
402 	 * @param section
403 	 *            section the key is grouped within.
404 	 * @param subsection
405 	 *            subsection name, such a remote or branch name.
406 	 * @param name
407 	 *            name of the key to get.
408 	 * @param defaultValue
409 	 *            default value to return if no value was present.
410 	 * @return the selected enumeration value, or {@code defaultValue}.
411 	 */
412 	public <T extends Enum<?>> T getEnum(final String section,
413 			final String subsection, final String name, final T defaultValue) {
414 		final T[] all = allValuesOf(defaultValue);
415 		return typedGetter.getEnum(this, all, section, subsection, name,
416 				defaultValue);
417 	}
418 
419 	@SuppressWarnings("unchecked")
420 	private static <T> T[] allValuesOf(T value) {
421 		try {
422 			return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
423 		} catch (Exception err) {
424 			String typeName = value.getClass().getName();
425 			String msg = MessageFormat.format(
426 					JGitText.get().enumValuesNotAvailable, typeName);
427 			throw new IllegalArgumentException(msg, err);
428 		}
429 	}
430 
431 	/**
432 	 * Parse an enumeration from the configuration.
433 	 *
434 	 * @param all
435 	 *            all possible values in the enumeration which should be
436 	 *            recognized. Typically {@code EnumType.values()}.
437 	 * @param section
438 	 *            section the key is grouped within.
439 	 * @param subsection
440 	 *            subsection name, such a remote or branch name.
441 	 * @param name
442 	 *            name of the key to get.
443 	 * @param defaultValue
444 	 *            default value to return if no value was present.
445 	 * @return the selected enumeration value, or {@code defaultValue}.
446 	 */
447 	public <T extends Enum<?>> T getEnum(final T[] all, final String section,
448 			final String subsection, final String name, final T defaultValue) {
449 		return typedGetter.getEnum(this, all, section, subsection, name,
450 				defaultValue);
451 	}
452 
453 	/**
454 	 * Get string value or null if not found.
455 	 *
456 	 * @param section
457 	 *            the section
458 	 * @param subsection
459 	 *            the subsection for the value
460 	 * @param name
461 	 *            the key name
462 	 * @return a String value from the config, <code>null</code> if not found
463 	 */
464 	public String getString(final String section, String subsection,
465 			final String name) {
466 		return getRawString(section, subsection, name);
467 	}
468 
469 	/**
470 	 * Get a list of string values
471 	 * <p>
472 	 * If this instance was created with a base, the base's values are returned
473 	 * first (if any).
474 	 *
475 	 * @param section
476 	 *            the section
477 	 * @param subsection
478 	 *            the subsection for the value
479 	 * @param name
480 	 *            the key name
481 	 * @return array of zero or more values from the configuration.
482 	 */
483 	public String[] getStringList(final String section, String subsection,
484 			final String name) {
485 		String[] base;
486 		if (baseConfig != null)
487 			base = baseConfig.getStringList(section, subsection, name);
488 		else
489 			base = EMPTY_STRING_ARRAY;
490 
491 		String[] self = getRawStringList(section, subsection, name);
492 		if (self == null)
493 			return base;
494 		if (base.length == 0)
495 			return self;
496 		String[] res = new String[base.length + self.length];
497 		int n = base.length;
498 		System.arraycopy(base, 0, res, 0, n);
499 		System.arraycopy(self, 0, res, n, self.length);
500 		return res;
501 	}
502 
503 	/**
504 	 * Parse a numerical time unit, such as "1 minute", from the configuration.
505 	 *
506 	 * @param section
507 	 *            section the key is in.
508 	 * @param subsection
509 	 *            subsection the key is in, or null if not in a subsection.
510 	 * @param name
511 	 *            the key name.
512 	 * @param defaultValue
513 	 *            default value to return if no value was present.
514 	 * @param wantUnit
515 	 *            the units of {@code defaultValue} and the return value, as
516 	 *            well as the units to assume if the value does not contain an
517 	 *            indication of the units.
518 	 * @return the value, or {@code defaultValue} if not set, expressed in
519 	 *         {@code units}.
520 	 * @since 4.5
521 	 */
522 	public long getTimeUnit(String section, String subsection, String name,
523 			long defaultValue, TimeUnit wantUnit) {
524 		return typedGetter.getTimeUnit(this, section, subsection, name,
525 				defaultValue, wantUnit);
526 	}
527 
528 	/**
529 	 * Parse a string value and treat it as a file path, replacing a ~/ prefix
530 	 * by the user's home directory.
531 	 * <p>
532 	 * <b>Note:</b> this may throw {@link InvalidPathException} if the string is
533 	 * not a valid path.
534 	 * </p>
535 	 *
536 	 * @param section
537 	 *            section the key is in.
538 	 * @param subsection
539 	 *            subsection the key is in, or null if not in a subsection.
540 	 * @param name
541 	 *            the key name.
542 	 * @param fs
543 	 *            to use to convert the string into a path.
544 	 * @param resolveAgainst
545 	 *            directory to resolve the path against if it is a relative
546 	 *            path; {@code null} to use the Java process's current
547 	 *            directory.
548 	 * @param defaultValue
549 	 *            to return if no value was present
550 	 * @return the {@link Path}, or {@code defaultValue} if not set
551 	 * @since 5.10
552 	 */
553 	public Path getPath(String section, String subsection, String name,
554 			@NonNull FS fs, File resolveAgainst, Path defaultValue) {
555 		return typedGetter.getPath(this, section, subsection, name, fs,
556 				resolveAgainst, defaultValue);
557 	}
558 
559 	/**
560 	 * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
561 	 * configuration.
562 	 *
563 	 * @param section
564 	 *            section the key is in.
565 	 * @param subsection
566 	 *            subsection the key is in, or null if not in a subsection.
567 	 * @param name
568 	 *            the key name.
569 	 * @return a possibly empty list of
570 	 *         {@link org.eclipse.jgit.transport.RefSpec}s
571 	 * @since 4.9
572 	 */
573 	public List<RefSpec> getRefSpecs(String section, String subsection,
574 			String name) {
575 		return typedGetter.getRefSpecs(this, section, subsection, name);
576 	}
577 
578 	/**
579 	 * Get set of all subsections of specified section within this configuration
580 	 * and its base configuration
581 	 *
582 	 * @param section
583 	 *            section to search for.
584 	 * @return set of all subsections of specified section within this
585 	 *         configuration and its base configuration; may be empty if no
586 	 *         subsection exists. The set's iterator returns sections in the
587 	 *         order they are declared by the configuration starting from this
588 	 *         instance and progressing through the base.
589 	 */
590 	public Set<String> getSubsections(String section) {
591 		return getState().getSubsections(section);
592 	}
593 
594 	/**
595 	 * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
596 	 *
597 	 * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
598 	 *         The set's iterator returns sections in the order they are
599 	 *         declared by the configuration starting from this instance and
600 	 *         progressing through the base.
601 	 */
602 	public Set<String> getSections() {
603 		return getState().getSections();
604 	}
605 
606 	/**
607 	 * Get the list of names defined for this section
608 	 *
609 	 * @param section
610 	 *            the section
611 	 * @return the list of names defined for this section
612 	 */
613 	public Set<String> getNames(String section) {
614 		return getNames(section, null);
615 	}
616 
617 	/**
618 	 * Get the list of names defined for this subsection
619 	 *
620 	 * @param section
621 	 *            the section
622 	 * @param subsection
623 	 *            the subsection
624 	 * @return the list of names defined for this subsection
625 	 */
626 	public Set<String> getNames(String section, String subsection) {
627 		return getState().getNames(section, subsection);
628 	}
629 
630 	/**
631 	 * Get the list of names defined for this section
632 	 *
633 	 * @param section
634 	 *            the section
635 	 * @param recursive
636 	 *            if {@code true} recursively adds the names defined in all base
637 	 *            configurations
638 	 * @return the list of names defined for this section
639 	 * @since 3.2
640 	 */
641 	public Set<String> getNames(String section, boolean recursive) {
642 		return getState().getNames(section, null, recursive);
643 	}
644 
645 	/**
646 	 * Get the list of names defined for this section
647 	 *
648 	 * @param section
649 	 *            the section
650 	 * @param subsection
651 	 *            the subsection
652 	 * @param recursive
653 	 *            if {@code true} recursively adds the names defined in all base
654 	 *            configurations
655 	 * @return the list of names defined for this subsection
656 	 * @since 3.2
657 	 */
658 	public Set<String> getNames(String section, String subsection,
659 			boolean recursive) {
660 		return getState().getNames(section, subsection, recursive);
661 	}
662 
663 	/**
664 	 * Obtain a handle to a parsed set of configuration values.
665 	 *
666 	 * @param <T>
667 	 *            type of configuration model to return.
668 	 * @param parser
669 	 *            parser which can create the model if it is not already
670 	 *            available in this configuration file. The parser is also used
671 	 *            as the key into a cache and must obey the hashCode and equals
672 	 *            contract in order to reuse a parsed model.
673 	 * @return the parsed object instance, which is cached inside this config.
674 	 */
675 	@SuppressWarnings("unchecked")
676 	public <T> T get(SectionParser<T> parser) {
677 		final ConfigSnapshot myState = getState();
678 		T obj = (T) myState.cache.get(parser);
679 		if (obj == null) {
680 			obj = parser.parse(this);
681 			myState.cache.put(parser, obj);
682 		}
683 		return obj;
684 	}
685 
686 	/**
687 	 * Remove a cached configuration object.
688 	 * <p>
689 	 * If the associated configuration object has not yet been cached, this
690 	 * method has no effect.
691 	 *
692 	 * @param parser
693 	 *            parser used to obtain the configuration object.
694 	 * @see #get(SectionParser)
695 	 */
696 	public void uncache(SectionParser<?> parser) {
697 		state.get().cache.remove(parser);
698 	}
699 
700 	/**
701 	 * Adds a listener to be notified about changes.
702 	 * <p>
703 	 * Clients are supposed to remove the listeners after they are done with
704 	 * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
705 	 * method
706 	 *
707 	 * @param listener
708 	 *            the listener
709 	 * @return the handle to the registered listener
710 	 */
711 	public ListenerHandle addChangeListener(ConfigChangedListener listener) {
712 		return listeners.addConfigChangedListener(listener);
713 	}
714 
715 	/**
716 	 * Determine whether to issue change events for transient changes.
717 	 * <p>
718 	 * If <code>true</code> is returned (which is the default behavior),
719 	 * {@link #fireConfigChangedEvent()} will be called upon each change.
720 	 * <p>
721 	 * Subclasses that override this to return <code>false</code> are
722 	 * responsible for issuing {@link #fireConfigChangedEvent()} calls
723 	 * themselves.
724 	 *
725 	 * @return <code></code>
726 	 */
727 	protected boolean notifyUponTransientChanges() {
728 		return true;
729 	}
730 
731 	/**
732 	 * Notifies the listeners
733 	 */
734 	protected void fireConfigChangedEvent() {
735 		listeners.dispatch(new ConfigChangedEvent());
736 	}
737 
738 	String getRawString(final String section, final String subsection,
739 			final String name) {
740 		String[] lst = getRawStringList(section, subsection, name);
741 		if (lst != null) {
742 			return lst[lst.length - 1];
743 		} else if (baseConfig != null) {
744 			return baseConfig.getRawString(section, subsection, name);
745 		} else {
746 			return null;
747 		}
748 	}
749 
750 	private String[] getRawStringList(String section, String subsection,
751 			String name) {
752 		return state.get().get(section, subsection, name);
753 	}
754 
755 	private ConfigSnapshot getState() {
756 		ConfigSnapshot cur, upd;
757 		do {
758 			cur = state.get();
759 			final ConfigSnapshot base = getBaseState();
760 			if (cur.baseState == base)
761 				return cur;
762 			upd = new ConfigSnapshot(cur.entryList, base);
763 		} while (!state.compareAndSet(cur, upd));
764 		return upd;
765 	}
766 
767 	private ConfigSnapshot getBaseState() {
768 		return baseConfig != null ? baseConfig.getState() : null;
769 	}
770 
771 	/**
772 	 * Add or modify a configuration value. The parameters will result in a
773 	 * configuration entry like this.
774 	 *
775 	 * <pre>
776 	 * [section &quot;subsection&quot;]
777 	 *         name = value
778 	 * </pre>
779 	 *
780 	 * @param section
781 	 *            section name, e.g "branch"
782 	 * @param subsection
783 	 *            optional subsection value, e.g. a branch name
784 	 * @param name
785 	 *            parameter name, e.g. "filemode"
786 	 * @param value
787 	 *            parameter value
788 	 */
789 	public void setInt(final String section, final String subsection,
790 			final String name, final int value) {
791 		setLong(section, subsection, name, value);
792 	}
793 
794 	/**
795 	 * Add or modify a configuration value. The parameters will result in a
796 	 * configuration entry like this.
797 	 *
798 	 * <pre>
799 	 * [section &quot;subsection&quot;]
800 	 *         name = value
801 	 * </pre>
802 	 *
803 	 * @param section
804 	 *            section name, e.g "branch"
805 	 * @param subsection
806 	 *            optional subsection value, e.g. a branch name
807 	 * @param name
808 	 *            parameter name, e.g. "filemode"
809 	 * @param value
810 	 *            parameter value
811 	 */
812 	public void setLong(final String section, final String subsection,
813 			final String name, final long value) {
814 		setString(section, subsection, name,
815 				StringUtils.formatWithSuffix(value));
816 	}
817 
818 	/**
819 	 * Add or modify a configuration value. The parameters will result in a
820 	 * configuration entry like this.
821 	 *
822 	 * <pre>
823 	 * [section &quot;subsection&quot;]
824 	 *         name = value
825 	 * </pre>
826 	 *
827 	 * @param section
828 	 *            section name, e.g "branch"
829 	 * @param subsection
830 	 *            optional subsection value, e.g. a branch name
831 	 * @param name
832 	 *            parameter name, e.g. "filemode"
833 	 * @param value
834 	 *            parameter value
835 	 */
836 	public void setBoolean(final String section, final String subsection,
837 			final String name, final boolean value) {
838 		setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
839 	}
840 
841 	/**
842 	 * Add or modify a configuration value. The parameters will result in a
843 	 * configuration entry like this.
844 	 *
845 	 * <pre>
846 	 * [section &quot;subsection&quot;]
847 	 *         name = value
848 	 * </pre>
849 	 *
850 	 * @param section
851 	 *            section name, e.g "branch"
852 	 * @param subsection
853 	 *            optional subsection value, e.g. a branch name
854 	 * @param name
855 	 *            parameter name, e.g. "filemode"
856 	 * @param value
857 	 *            parameter value
858 	 */
859 	public <T extends Enum<?>> void setEnum(final String section,
860 			final String subsection, final String name, final T value) {
861 		String n;
862 		if (value instanceof ConfigEnum)
863 			n = ((ConfigEnum) value).toConfigValue();
864 		else
865 			n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
866 		setString(section, subsection, name, n);
867 	}
868 
869 	/**
870 	 * Add or modify a configuration value. The parameters will result in a
871 	 * configuration entry like this.
872 	 *
873 	 * <pre>
874 	 * [section &quot;subsection&quot;]
875 	 *         name = value
876 	 * </pre>
877 	 *
878 	 * @param section
879 	 *            section name, e.g "branch"
880 	 * @param subsection
881 	 *            optional subsection value, e.g. a branch name
882 	 * @param name
883 	 *            parameter name, e.g. "filemode"
884 	 * @param value
885 	 *            parameter value, e.g. "true"
886 	 */
887 	public void setString(final String section, final String subsection,
888 			final String name, final String value) {
889 		setStringList(section, subsection, name, Collections
890 				.singletonList(value));
891 	}
892 
893 	/**
894 	 * Remove a configuration value.
895 	 *
896 	 * @param section
897 	 *            section name, e.g "branch"
898 	 * @param subsection
899 	 *            optional subsection value, e.g. a branch name
900 	 * @param name
901 	 *            parameter name, e.g. "filemode"
902 	 */
903 	public void unset(final String section, final String subsection,
904 			final String name) {
905 		setStringList(section, subsection, name, Collections
906 				.<String> emptyList());
907 	}
908 
909 	/**
910 	 * Remove all configuration values under a single section.
911 	 *
912 	 * @param section
913 	 *            section name, e.g "branch"
914 	 * @param subsection
915 	 *            optional subsection value, e.g. a branch name
916 	 */
917 	public void unsetSection(String section, String subsection) {
918 		ConfigSnapshot src, res;
919 		do {
920 			src = state.get();
921 			res = unsetSection(src, section, subsection);
922 		} while (!state.compareAndSet(src, res));
923 	}
924 
925 	private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
926 			final String section,
927 			final String subsection) {
928 		final int max = srcState.entryList.size();
929 		final ArrayList<ConfigLine> r = new ArrayList<>(max);
930 
931 		boolean lastWasMatch = false;
932 		for (ConfigLine e : srcState.entryList) {
933 			if (e.includedFrom == null && e.match(section, subsection)) {
934 				// Skip this record, it's for the section we are removing.
935 				lastWasMatch = true;
936 				continue;
937 			}
938 
939 			if (lastWasMatch && e.section == null && e.subsection == null)
940 				continue; // skip this padding line in the section.
941 			r.add(e);
942 		}
943 
944 		return newState(r);
945 	}
946 
947 	/**
948 	 * Set a configuration value.
949 	 *
950 	 * <pre>
951 	 * [section &quot;subsection&quot;]
952 	 *         name = value1
953 	 *         name = value2
954 	 * </pre>
955 	 *
956 	 * @param section
957 	 *            section name, e.g "branch"
958 	 * @param subsection
959 	 *            optional subsection value, e.g. a branch name
960 	 * @param name
961 	 *            parameter name, e.g. "filemode"
962 	 * @param values
963 	 *            list of zero or more values for this key.
964 	 */
965 	public void setStringList(final String section, final String subsection,
966 			final String name, final List<String> values) {
967 		ConfigSnapshot src, res;
968 		do {
969 			src = state.get();
970 			res = replaceStringList(src, section, subsection, name, values);
971 		} while (!state.compareAndSet(src, res));
972 		if (notifyUponTransientChanges())
973 			fireConfigChangedEvent();
974 	}
975 
976 	private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
977 			final String section, final String subsection, final String name,
978 			final List<String> values) {
979 		final List<ConfigLine> entries = copy(srcState, values);
980 		int entryIndex = 0;
981 		int valueIndex = 0;
982 		int insertPosition = -1;
983 
984 		// Reset the first n Entry objects that match this input name.
985 		//
986 		while (entryIndex < entries.size() && valueIndex < values.size()) {
987 			final ConfigLine e = entries.get(entryIndex);
988 			if (e.includedFrom == null && e.match(section, subsection, name)) {
989 				entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
990 				insertPosition = entryIndex + 1;
991 			}
992 			entryIndex++;
993 		}
994 
995 		// Remove any extra Entry objects that we no longer need.
996 		//
997 		if (valueIndex == values.size() && entryIndex < entries.size()) {
998 			while (entryIndex < entries.size()) {
999 				final ConfigLine e = entries.get(entryIndex++);
1000 				if (e.includedFrom == null
1001 						&& e.match(section, subsection, name))
1002 					entries.remove(--entryIndex);
1003 			}
1004 		}
1005 
1006 		// Insert new Entry objects for additional/new values.
1007 		//
1008 		if (valueIndex < values.size() && entryIndex == entries.size()) {
1009 			if (insertPosition < 0) {
1010 				// We didn't find a matching key above, but maybe there
1011 				// is already a section available that matches. Insert
1012 				// after the last key of that section.
1013 				//
1014 				insertPosition = findSectionEnd(entries, section, subsection,
1015 						true);
1016 			}
1017 			if (insertPosition < 0) {
1018 				// We didn't find any matching section header for this key,
1019 				// so we must create a new section header at the end.
1020 				//
1021 				final ConfigLine e = new ConfigLine();
1022 				e.section = section;
1023 				e.subsection = subsection;
1024 				entries.add(e);
1025 				insertPosition = entries.size();
1026 			}
1027 			while (valueIndex < values.size()) {
1028 				final ConfigLine e = new ConfigLine();
1029 				e.section = section;
1030 				e.subsection = subsection;
1031 				e.name = name;
1032 				e.value = values.get(valueIndex++);
1033 				entries.add(insertPosition++, e);
1034 			}
1035 		}
1036 
1037 		return newState(entries);
1038 	}
1039 
1040 	private static List<ConfigLine> copy(final ConfigSnapshot src,
1041 			final List<String> values) {
1042 		// At worst we need to insert 1 line for each value, plus 1 line
1043 		// for a new section header. Assume that and allocate the space.
1044 		//
1045 		final int max = src.entryList.size() + values.size() + 1;
1046 		final ArrayList<ConfigLine> r = new ArrayList<>(max);
1047 		r.addAll(src.entryList);
1048 		return r;
1049 	}
1050 
1051 	private static int findSectionEnd(final List<ConfigLine> entries,
1052 			final String section, final String subsection,
1053 			boolean skipIncludedLines) {
1054 		for (int i = 0; i < entries.size(); i++) {
1055 			ConfigLine e = entries.get(i);
1056 			if (e.includedFrom != null && skipIncludedLines) {
1057 				continue;
1058 			}
1059 
1060 			if (e.match(section, subsection, null)) {
1061 				i++;
1062 				while (i < entries.size()) {
1063 					e = entries.get(i);
1064 					if (e.match(section, subsection, e.name))
1065 						i++;
1066 					else
1067 						break;
1068 				}
1069 				return i;
1070 			}
1071 		}
1072 		return -1;
1073 	}
1074 
1075 	/**
1076 	 * Get this configuration, formatted as a Git style text file.
1077 	 *
1078 	 * @return this configuration, formatted as a Git style text file.
1079 	 */
1080 	public String toText() {
1081 		final StringBuilder out = new StringBuilder();
1082 		for (ConfigLine e : state.get().entryList) {
1083 			if (e.includedFrom != null)
1084 				continue;
1085 			if (e.prefix != null)
1086 				out.append(e.prefix);
1087 			if (e.section != null && e.name == null) {
1088 				out.append('[');
1089 				out.append(e.section);
1090 				if (e.subsection != null) {
1091 					out.append(' ');
1092 					String escaped = escapeValue(e.subsection);
1093 					// make sure to avoid double quotes here
1094 					boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
1095 							&& escaped.endsWith("\""); //$NON-NLS-1$
1096 					if (!quoted)
1097 						out.append('"');
1098 					out.append(escaped);
1099 					if (!quoted)
1100 						out.append('"');
1101 				}
1102 				out.append(']');
1103 			} else if (e.section != null && e.name != null) {
1104 				if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
1105 					out.append('\t');
1106 				out.append(e.name);
1107 				if (!isMissing(e.value)) {
1108 					out.append(" ="); //$NON-NLS-1$
1109 					if (e.value != null) {
1110 						out.append(' ');
1111 						out.append(escapeValue(e.value));
1112 					}
1113 				}
1114 				if (e.suffix != null)
1115 					out.append(' ');
1116 			}
1117 			if (e.suffix != null)
1118 				out.append(e.suffix);
1119 			out.append('\n');
1120 		}
1121 		return out.toString();
1122 	}
1123 
1124 	/**
1125 	 * Clear this configuration and reset to the contents of the parsed string.
1126 	 *
1127 	 * @param text
1128 	 *            Git style text file listing configuration properties.
1129 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
1130 	 *             the text supplied is not formatted correctly. No changes were
1131 	 *             made to {@code this}.
1132 	 */
1133 	public void fromText(String text) throws ConfigInvalidException {
1134 		state.set(newState(fromTextRecurse(text, 1, null)));
1135 	}
1136 
1137 	private List<ConfigLine> fromTextRecurse(String text, int depth,
1138 			String includedFrom) throws ConfigInvalidException {
1139 		if (depth > MAX_DEPTH) {
1140 			throw new ConfigInvalidException(
1141 					JGitText.get().tooManyIncludeRecursions);
1142 		}
1143 		final List<ConfigLine> newEntries = new ArrayList<>();
1144 		final StringReader in = new StringReader(text);
1145 		ConfigLine last = null;
1146 		ConfigLine e = new ConfigLine();
1147 		e.includedFrom = includedFrom;
1148 		for (;;) {
1149 			int input = in.read();
1150 			if (-1 == input) {
1151 				if (e.section != null)
1152 					newEntries.add(e);
1153 				break;
1154 			}
1155 
1156 			final char c = (char) input;
1157 			if ('\n' == c) {
1158 				// End of this entry.
1159 				newEntries.add(e);
1160 				if (e.section != null)
1161 					last = e;
1162 				e = new ConfigLine();
1163 				e.includedFrom = includedFrom;
1164 			} else if (e.suffix != null) {
1165 				// Everything up until the end-of-line is in the suffix.
1166 				e.suffix += c;
1167 
1168 			} else if (';' == c || '#' == c) {
1169 				// The rest of this line is a comment; put into suffix.
1170 				e.suffix = String.valueOf(c);
1171 
1172 			} else if (e.section == null && Character.isWhitespace(c)) {
1173 				// Save the leading whitespace (if any).
1174 				if (e.prefix == null)
1175 					e.prefix = ""; //$NON-NLS-1$
1176 				e.prefix += c;
1177 
1178 			} else if ('[' == c) {
1179 				// This is a section header.
1180 				e.section = readSectionName(in);
1181 				input = in.read();
1182 				if ('"' == input) {
1183 					e.subsection = readSubsectionName(in);
1184 					input = in.read();
1185 				}
1186 				if (']' != input)
1187 					throw new ConfigInvalidException(JGitText.get().badGroupHeader);
1188 				e.suffix = ""; //$NON-NLS-1$
1189 
1190 			} else if (last != null) {
1191 				// Read a value.
1192 				e.section = last.section;
1193 				e.subsection = last.subsection;
1194 				in.reset();
1195 				e.name = readKeyName(in);
1196 				if (e.name.endsWith("\n")) { //$NON-NLS-1$
1197 					e.name = e.name.substring(0, e.name.length() - 1);
1198 					e.value = MISSING_ENTRY;
1199 				} else
1200 					e.value = readValue(in);
1201 
1202 				if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
1203 					addIncludedConfig(newEntries, e, depth);
1204 				}
1205 			} else
1206 				throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
1207 		}
1208 
1209 		return newEntries;
1210 	}
1211 
1212 	/**
1213 	 * Read the included config from the specified (possibly) relative path
1214 	 *
1215 	 * @param relPath
1216 	 *            possibly relative path to the included config, as specified in
1217 	 *            this config
1218 	 * @return the read bytes, or null if the included config should be ignored
1219 	 * @throws org.eclipse.jgit.errors.ConfigInvalidException
1220 	 *             if something went wrong while reading the config
1221 	 * @since 4.10
1222 	 */
1223 	protected byte[] readIncludedConfig(String relPath)
1224 			throws ConfigInvalidException {
1225 		return null;
1226 	}
1227 
1228 	private void addIncludedConfig(final List<ConfigLine> newEntries,
1229 			ConfigLine line, int depth) throws ConfigInvalidException {
1230 		if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
1231 				line.value == null || line.value.equals(MISSING_ENTRY)) {
1232 			throw new ConfigInvalidException(MessageFormat.format(
1233 					JGitText.get().invalidLineInConfigFileWithParam, line));
1234 		}
1235 		byte[] bytes = readIncludedConfig(line.value);
1236 		if (bytes == null) {
1237 			return;
1238 		}
1239 
1240 		String decoded;
1241 		if (isUtf8(bytes)) {
1242 			decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
1243 		} else {
1244 			decoded = RawParseUtils.decode(bytes);
1245 		}
1246 		try {
1247 			newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
1248 		} catch (ConfigInvalidException e) {
1249 			throw new ConfigInvalidException(MessageFormat
1250 					.format(JGitText.get().cannotReadFile, line.value), e);
1251 		}
1252 	}
1253 
1254 	private ConfigSnapshot newState() {
1255 		return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
1256 				getBaseState());
1257 	}
1258 
1259 	private ConfigSnapshot newState(List<ConfigLine> entries) {
1260 		return new ConfigSnapshot(Collections.unmodifiableList(entries),
1261 				getBaseState());
1262 	}
1263 
1264 	/**
1265 	 * Clear the configuration file
1266 	 */
1267 	protected void clear() {
1268 		state.set(newState());
1269 	}
1270 
1271 	/**
1272 	 * Check if bytes should be treated as UTF-8 or not.
1273 	 *
1274 	 * @param bytes
1275 	 *            the bytes to check encoding for.
1276 	 * @return true if bytes should be treated as UTF-8, false otherwise.
1277 	 * @since 4.4
1278 	 */
1279 	protected boolean isUtf8(final byte[] bytes) {
1280 		return bytes.length >= 3 && bytes[0] == (byte) 0xEF
1281 				&& bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
1282 	}
1283 
1284 	private static String readSectionName(StringReader in)
1285 			throws ConfigInvalidException {
1286 		final StringBuilder name = new StringBuilder();
1287 		for (;;) {
1288 			int c = in.read();
1289 			if (c < 0)
1290 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1291 
1292 			if (']' == c) {
1293 				in.reset();
1294 				break;
1295 			}
1296 
1297 			if (' ' == c || '\t' == c) {
1298 				for (;;) {
1299 					c = in.read();
1300 					if (c < 0)
1301 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1302 
1303 					if ('"' == c) {
1304 						in.reset();
1305 						break;
1306 					}
1307 
1308 					if (' ' == c || '\t' == c)
1309 						continue; // Skipped...
1310 					throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1311 				}
1312 				break;
1313 			}
1314 
1315 			if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
1316 				name.append((char) c);
1317 			else
1318 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
1319 		}
1320 		return name.toString();
1321 	}
1322 
1323 	private static String readKeyName(StringReader in)
1324 			throws ConfigInvalidException {
1325 		final StringBuilder name = new StringBuilder();
1326 		for (;;) {
1327 			int c = in.read();
1328 			if (c < 0)
1329 				throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1330 
1331 			if ('=' == c)
1332 				break;
1333 
1334 			if (' ' == c || '\t' == c) {
1335 				for (;;) {
1336 					c = in.read();
1337 					if (c < 0)
1338 						throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);
1339 
1340 					if ('=' == c)
1341 						break;
1342 
1343 					if (';' == c || '#' == c || '\n' == c) {
1344 						in.reset();
1345 						break;
1346 					}
1347 
1348 					if (' ' == c || '\t' == c)
1349 						continue; // Skipped...
1350 					throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
1351 				}
1352 				break;
1353 			}
1354 
1355 			if (Character.isLetterOrDigit((char) c) || c == '-') {
1356 				// From the git-config man page:
1357 				// The variable names are case-insensitive and only
1358 				// alphanumeric characters and - are allowed.
1359 				name.append((char) c);
1360 			} else if ('\n' == c) {
1361 				in.reset();
1362 				name.append((char) c);
1363 				break;
1364 			} else
1365 				throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
1366 		}
1367 		return name.toString();
1368 	}
1369 
1370 	private static String readSubsectionName(StringReader in)
1371 			throws ConfigInvalidException {
1372 		StringBuilder r = new StringBuilder();
1373 		for (;;) {
1374 			int c = in.read();
1375 			if (c < 0) {
1376 				break;
1377 			}
1378 
1379 			if ('\n' == c) {
1380 				throw new ConfigInvalidException(
1381 						JGitText.get().newlineInQuotesNotAllowed);
1382 			}
1383 			if ('\\' == c) {
1384 				c = in.read();
1385 				switch (c) {
1386 				case -1:
1387 					throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1388 
1389 				case '\\':
1390 				case '"':
1391 					r.append((char) c);
1392 					continue;
1393 
1394 				default:
1395 					// C git simply drops backslashes if the escape sequence is not
1396 					// recognized.
1397 					r.append((char) c);
1398 					continue;
1399 				}
1400 			}
1401 			if ('"' == c) {
1402 				break;
1403 			}
1404 
1405 			r.append((char) c);
1406 		}
1407 		return r.toString();
1408 	}
1409 
1410 	private static String readValue(StringReader in)
1411 			throws ConfigInvalidException {
1412 		StringBuilder value = new StringBuilder();
1413 		StringBuilder trailingSpaces = null;
1414 		boolean quote = false;
1415 		boolean inLeadingSpace = true;
1416 
1417 		for (;;) {
1418 			int c = in.read();
1419 			if (c < 0) {
1420 				break;
1421 			}
1422 			if ('\n' == c) {
1423 				if (quote) {
1424 					throw new ConfigInvalidException(
1425 							JGitText.get().newlineInQuotesNotAllowed);
1426 				}
1427 				in.reset();
1428 				break;
1429 			}
1430 
1431 			if (!quote && (';' == c || '#' == c)) {
1432 				if (trailingSpaces != null) {
1433 					trailingSpaces.setLength(0);
1434 				}
1435 				in.reset();
1436 				break;
1437 			}
1438 
1439 			char cc = (char) c;
1440 			if (Character.isWhitespace(cc)) {
1441 				if (inLeadingSpace) {
1442 					continue;
1443 				}
1444 				if (trailingSpaces == null) {
1445 					trailingSpaces = new StringBuilder();
1446 				}
1447 				trailingSpaces.append(cc);
1448 				continue;
1449 			}
1450 			inLeadingSpace = false;
1451 			if (trailingSpaces != null) {
1452 				value.append(trailingSpaces);
1453 				trailingSpaces.setLength(0);
1454 			}
1455 
1456 			if ('\\' == c) {
1457 				c = in.read();
1458 				switch (c) {
1459 				case -1:
1460 					throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
1461 				case '\n':
1462 					continue;
1463 				case 't':
1464 					value.append('\t');
1465 					continue;
1466 				case 'b':
1467 					value.append('\b');
1468 					continue;
1469 				case 'n':
1470 					value.append('\n');
1471 					continue;
1472 				case '\\':
1473 					value.append('\\');
1474 					continue;
1475 				case '"':
1476 					value.append('"');
1477 					continue;
1478 				case '\r': {
1479 					int next = in.read();
1480 					if (next == '\n') {
1481 						continue; // CR-LF
1482 					} else if (next >= 0) {
1483 						in.reset();
1484 					}
1485 					break;
1486 				}
1487 				default:
1488 					break;
1489 				}
1490 				throw new ConfigInvalidException(
1491 						MessageFormat.format(JGitText.get().badEscape,
1492 								Character.isAlphabetic(c)
1493 										? Character.valueOf(((char) c))
1494 										: toUnicodeLiteral(c)));
1495 			}
1496 
1497 			if ('"' == c) {
1498 				quote = !quote;
1499 				continue;
1500 			}
1501 
1502 			value.append(cc);
1503 		}
1504 		return value.length() > 0 ? value.toString() : null;
1505 	}
1506 
1507 	private static String toUnicodeLiteral(int c) {
1508 		return String.format("\\u%04x", //$NON-NLS-1$
1509 				Integer.valueOf(c));
1510 	}
1511 
1512 	/**
1513 	 * Parses a section of the configuration into an application model object.
1514 	 * <p>
1515 	 * Instances must implement hashCode and equals such that model objects can
1516 	 * be cached by using the {@code SectionParser} as a key of a HashMap.
1517 	 * <p>
1518 	 * As the {@code SectionParser} itself is used as the key of the internal
1519 	 * HashMap applications should be careful to ensure the SectionParser key
1520 	 * does not retain unnecessary application state which may cause memory to
1521 	 * be held longer than expected.
1522 	 *
1523 	 * @param <T>
1524 	 *            type of the application model created by the parser.
1525 	 */
1526 	public static interface SectionParser<T> {
1527 		/**
1528 		 * Create a model object from a configuration.
1529 		 *
1530 		 * @param cfg
1531 		 *            the configuration to read values from.
1532 		 * @return the application model instance.
1533 		 */
1534 		T parse(Config cfg);
1535 	}
1536 
1537 	private static class StringReader {
1538 		private final char[] buf;
1539 
1540 		private int pos;
1541 
1542 		StringReader(String in) {
1543 			buf = in.toCharArray();
1544 		}
1545 
1546 		int read() {
1547 			if (pos >= buf.length) {
1548 				return -1;
1549 			}
1550 			return buf[pos++];
1551 		}
1552 
1553 		void reset() {
1554 			pos--;
1555 		}
1556 	}
1557 
1558 	/**
1559 	 * Converts enumeration values into configuration options and vice-versa,
1560 	 * allowing to match a config option with an enum value.
1561 	 *
1562 	 */
1563 	public static interface ConfigEnum {
1564 		/**
1565 		 * Converts enumeration value into a string to be save in config.
1566 		 *
1567 		 * @return the enum value as config string
1568 		 */
1569 		String toConfigValue();
1570 
1571 		/**
1572 		 * Checks if the given string matches with enum value.
1573 		 *
1574 		 * @param in
1575 		 *            the string to match
1576 		 * @return true if the given string matches enum value, false otherwise
1577 		 */
1578 		boolean matchConfigValue(String in);
1579 	}
1580 }