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