Config.java

  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. package org.eclipse.jgit.lib;

  19. import static java.nio.charset.StandardCharsets.UTF_8;

  20. import java.io.File;
  21. import java.nio.file.InvalidPathException;
  22. import java.nio.file.Path;
  23. import java.text.MessageFormat;
  24. import java.util.ArrayList;
  25. import java.util.Collections;
  26. import java.util.List;
  27. import java.util.Locale;
  28. import java.util.Set;
  29. import java.util.concurrent.TimeUnit;
  30. import java.util.concurrent.atomic.AtomicReference;

  31. import org.eclipse.jgit.annotations.NonNull;
  32. import org.eclipse.jgit.errors.ConfigInvalidException;
  33. import org.eclipse.jgit.events.ConfigChangedEvent;
  34. import org.eclipse.jgit.events.ConfigChangedListener;
  35. import org.eclipse.jgit.events.ListenerHandle;
  36. import org.eclipse.jgit.events.ListenerList;
  37. import org.eclipse.jgit.internal.JGitText;
  38. import org.eclipse.jgit.transport.RefSpec;
  39. import org.eclipse.jgit.util.FS;
  40. import org.eclipse.jgit.util.RawParseUtils;
  41. import org.eclipse.jgit.util.StringUtils;

  42. /**
  43.  * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
  44.  */
  45. public class Config {

  46.     private static final String[] EMPTY_STRING_ARRAY = {};

  47.     private static final int MAX_DEPTH = 10;

  48.     private static final TypedConfigGetter DEFAULT_GETTER = new DefaultTypedConfigGetter();

  49.     private static TypedConfigGetter typedGetter = DEFAULT_GETTER;

  50.     /** the change listeners */
  51.     private final ListenerList listeners = new ListenerList();

  52.     /**
  53.      * Immutable current state of the configuration data.
  54.      * <p>
  55.      * This state is copy-on-write. It should always contain an immutable list
  56.      * of the configuration keys/values.
  57.      */
  58.     private final AtomicReference<ConfigSnapshot> state;

  59.     private final Config baseConfig;

  60.     /**
  61.      * Magic value indicating a missing entry.
  62.      * <p>
  63.      * This value is tested for reference equality in some contexts, so we
  64.      * must ensure it is a special copy of the empty string.  It also must
  65.      * be treated like the empty string.
  66.      */
  67.     private static final String MISSING_ENTRY = new String();

  68.     /**
  69.      * Create a configuration with no default fallback.
  70.      */
  71.     public Config() {
  72.         this(null);
  73.     }

  74.     /**
  75.      * Create an empty configuration with a fallback for missing keys.
  76.      *
  77.      * @param defaultConfig
  78.      *            the base configuration to be consulted when a key is missing
  79.      *            from this configuration instance.
  80.      */
  81.     public Config(Config defaultConfig) {
  82.         baseConfig = defaultConfig;
  83.         state = new AtomicReference<>(newState());
  84.     }

  85.     /**
  86.      * Retrieves this config's base config.
  87.      *
  88.      * @return the base configuration of this config.
  89.      *
  90.      * @since 5.5.2
  91.      */
  92.     public Config getBaseConfig() {
  93.         return baseConfig;
  94.     }

  95.     /**
  96.      * Check if a given string is the "missing" value.
  97.      *
  98.      * @param value
  99.      *            string to be checked.
  100.      * @return true if the given string is the "missing" value.
  101.      * @since 5.4
  102.      */
  103.     @SuppressWarnings({ "ReferenceEquality", "StringEquality" })
  104.     public static boolean isMissing(String value) {
  105.         return value == MISSING_ENTRY;
  106.     }

  107.     /**
  108.      * Globally sets a {@link org.eclipse.jgit.lib.TypedConfigGetter} that is
  109.      * subsequently used to read typed values from all git configs.
  110.      *
  111.      * @param getter
  112.      *            to use; if {@code null} use the default getter.
  113.      * @since 4.9
  114.      */
  115.     public static void setTypedConfigGetter(TypedConfigGetter getter) {
  116.         typedGetter = getter == null ? DEFAULT_GETTER : getter;
  117.     }

  118.     /**
  119.      * Escape the value before saving
  120.      *
  121.      * @param x
  122.      *            the value to escape
  123.      * @return the escaped value
  124.      */
  125.     static String escapeValue(String x) {
  126.         if (x.isEmpty()) {
  127.             return ""; //$NON-NLS-1$
  128.         }

  129.         boolean needQuote = x.charAt(0) == ' ' || x.charAt(x.length() - 1) == ' ';
  130.         StringBuilder r = new StringBuilder(x.length());
  131.         for (int k = 0; k < x.length(); k++) {
  132.             char c = x.charAt(k);
  133.             // git-config(1) lists the limited set of supported escape sequences, but
  134.             // the documentation is otherwise not especially normative. In particular,
  135.             // which ones of these produce and/or require escaping and/or quoting
  136.             // around them is not documented and was discovered by trial and error.
  137.             // In summary:
  138.             //
  139.             // * Quotes are only required if there is leading/trailing whitespace or a
  140.             //   comment character.
  141.             // * Bytes that have a supported escape sequence are escaped, except for
  142.             //   \b for some reason which isn't.
  143.             // * Needing an escape sequence is not sufficient reason to quote the
  144.             //   value.
  145.             switch (c) {
  146.             case '\0':
  147.                 // Unix command line calling convention cannot pass a '\0' as an
  148.                 // argument, so there is no equivalent way in C git to store a null byte
  149.                 // in a config value.
  150.                 throw new IllegalArgumentException(
  151.                         JGitText.get().configValueContainsNullByte);

  152.             case '\n':
  153.                 r.append('\\').append('n');
  154.                 break;

  155.             case '\t':
  156.                 r.append('\\').append('t');
  157.                 break;

  158.             case '\b':
  159.                 // Doesn't match `git config foo.bar $'x\by'`, which doesn't escape the
  160.                 // \x08, but since both escaped and unescaped forms are readable, we'll
  161.                 // prefer internal consistency here.
  162.                 r.append('\\').append('b');
  163.                 break;

  164.             case '\\':
  165.                 r.append('\\').append('\\');
  166.                 break;

  167.             case '"':
  168.                 r.append('\\').append('"');
  169.                 break;

  170.             case '#':
  171.             case ';':
  172.                 needQuote = true;
  173.                 r.append(c);
  174.                 break;

  175.             default:
  176.                 r.append(c);
  177.                 break;
  178.             }
  179.         }

  180.         return needQuote ? '"' + r.toString() + '"' : r.toString();
  181.     }

  182.     static String escapeSubsection(String x) {
  183.         if (x.isEmpty()) {
  184.             return "\"\""; //$NON-NLS-1$
  185.         }

  186.         StringBuilder r = new StringBuilder(x.length() + 2).append('"');
  187.         for (int k = 0; k < x.length(); k++) {
  188.             char c = x.charAt(k);

  189.             // git-config(1) lists the limited set of supported escape sequences
  190.             // (which is even more limited for subsection names than for values).
  191.             switch (c) {
  192.             case '\0':
  193.                 throw new IllegalArgumentException(
  194.                         JGitText.get().configSubsectionContainsNullByte);

  195.             case '\n':
  196.                 throw new IllegalArgumentException(
  197.                         JGitText.get().configSubsectionContainsNewline);

  198.             case '\\':
  199.             case '"':
  200.                 r.append('\\').append(c);
  201.                 break;

  202.             default:
  203.                 r.append(c);
  204.                 break;
  205.             }
  206.         }

  207.         return r.append('"').toString();
  208.     }

  209.     /**
  210.      * Obtain an integer value from the configuration.
  211.      *
  212.      * @param section
  213.      *            section the key is grouped within.
  214.      * @param name
  215.      *            name of the key to get.
  216.      * @param defaultValue
  217.      *            default value to return if no value was present.
  218.      * @return an integer value from the configuration, or defaultValue.
  219.      */
  220.     public int getInt(final String section, final String name,
  221.             final int defaultValue) {
  222.         return typedGetter.getInt(this, section, null, name, defaultValue);
  223.     }

  224.     /**
  225.      * Obtain an integer value from the configuration.
  226.      *
  227.      * @param section
  228.      *            section the key is grouped within.
  229.      * @param subsection
  230.      *            subsection name, such a remote or branch name.
  231.      * @param name
  232.      *            name of the key to get.
  233.      * @param defaultValue
  234.      *            default value to return if no value was present.
  235.      * @return an integer value from the configuration, or defaultValue.
  236.      */
  237.     public int getInt(final String section, String subsection,
  238.             final String name, final int defaultValue) {
  239.         return typedGetter.getInt(this, section, subsection, name,
  240.                 defaultValue);
  241.     }

  242.     /**
  243.      * Obtain an integer value from the configuration which must be inside given
  244.      * range.
  245.      *
  246.      * @param section
  247.      *            section the key is grouped within.
  248.      * @param name
  249.      *            name of the key to get.
  250.      * @param minValue
  251.      *            minimum value
  252.      * @param maxValue
  253.      *            maximum value
  254.      * @param defaultValue
  255.      *            default value to return if no value was present.
  256.      * @return an integer value from the configuration, or defaultValue.
  257.      * @since 6.1
  258.      */
  259.     public int getIntInRange(String section, String name, int minValue,
  260.             int maxValue, int defaultValue) {
  261.         return typedGetter.getIntInRange(this, section, null, name, minValue,
  262.                 maxValue, defaultValue);
  263.     }

  264.     /**
  265.      * Obtain an integer value from the configuration which must be inside given
  266.      * range.
  267.      *
  268.      * @param section
  269.      *            section the key is grouped within.
  270.      * @param subsection
  271.      *            subsection name, such a remote or branch name.
  272.      * @param name
  273.      *            name of the key to get.
  274.      * @param minValue
  275.      *            minimum value
  276.      * @param maxValue
  277.      *            maximum value
  278.      * @param defaultValue
  279.      *            default value to return if no value was present.
  280.      * @return an integer value from the configuration, or defaultValue.
  281.      * @since 6.1
  282.      */
  283.     public int getIntInRange(String section, String subsection, String name,
  284.             int minValue, int maxValue, int defaultValue) {
  285.         return typedGetter.getIntInRange(this, section, subsection, name,
  286.                 minValue, maxValue, defaultValue);
  287.     }

  288.     /**
  289.      * Obtain an integer value from the configuration.
  290.      *
  291.      * @param section
  292.      *            section the key is grouped within.
  293.      * @param name
  294.      *            name of the key to get.
  295.      * @param defaultValue
  296.      *            default value to return if no value was present.
  297.      * @return an integer value from the configuration, or defaultValue.
  298.      */
  299.     public long getLong(String section, String name, long defaultValue) {
  300.         return typedGetter.getLong(this, section, null, name, defaultValue);
  301.     }

  302.     /**
  303.      * Obtain an integer value from the configuration.
  304.      *
  305.      * @param section
  306.      *            section the key is grouped within.
  307.      * @param subsection
  308.      *            subsection name, such a remote or branch name.
  309.      * @param name
  310.      *            name of the key to get.
  311.      * @param defaultValue
  312.      *            default value to return if no value was present.
  313.      * @return an integer value from the configuration, or defaultValue.
  314.      */
  315.     public long getLong(final String section, String subsection,
  316.             final String name, final long defaultValue) {
  317.         return typedGetter.getLong(this, section, subsection, name,
  318.                 defaultValue);
  319.     }

  320.     /**
  321.      * Get a boolean value from the git config
  322.      *
  323.      * @param section
  324.      *            section the key is grouped within.
  325.      * @param name
  326.      *            name of the key to get.
  327.      * @param defaultValue
  328.      *            default value to return if no value was present.
  329.      * @return true if any value or defaultValue is true, false for missing or
  330.      *         explicit false
  331.      */
  332.     public boolean getBoolean(final String section, final String name,
  333.             final boolean defaultValue) {
  334.         return typedGetter.getBoolean(this, section, null, name, defaultValue);
  335.     }

  336.     /**
  337.      * Get a boolean value from the git config
  338.      *
  339.      * @param section
  340.      *            section the key is grouped within.
  341.      * @param subsection
  342.      *            subsection name, such a remote or branch name.
  343.      * @param name
  344.      *            name of the key to get.
  345.      * @param defaultValue
  346.      *            default value to return if no value was present.
  347.      * @return true if any value or defaultValue is true, false for missing or
  348.      *         explicit false
  349.      */
  350.     public boolean getBoolean(final String section, String subsection,
  351.             final String name, final boolean defaultValue) {
  352.         return typedGetter.getBoolean(this, section, subsection, name,
  353.                 defaultValue);
  354.     }

  355.     /**
  356.      * Parse an enumeration from the configuration.
  357.      *
  358.      * @param section
  359.      *            section the key is grouped within.
  360.      * @param subsection
  361.      *            subsection name, such a remote or branch name.
  362.      * @param name
  363.      *            name of the key to get.
  364.      * @param defaultValue
  365.      *            default value to return if no value was present.
  366.      * @return the selected enumeration value, or {@code defaultValue}.
  367.      */
  368.     public <T extends Enum<?>> T getEnum(final String section,
  369.             final String subsection, final String name, final T defaultValue) {
  370.         final T[] all = allValuesOf(defaultValue);
  371.         return typedGetter.getEnum(this, all, section, subsection, name,
  372.                 defaultValue);
  373.     }

  374.     @SuppressWarnings("unchecked")
  375.     private static <T> T[] allValuesOf(T value) {
  376.         try {
  377.             return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
  378.         } catch (Exception err) {
  379.             String typeName = value.getClass().getName();
  380.             String msg = MessageFormat.format(
  381.                     JGitText.get().enumValuesNotAvailable, typeName);
  382.             throw new IllegalArgumentException(msg, err);
  383.         }
  384.     }

  385.     /**
  386.      * Parse an enumeration from the configuration.
  387.      *
  388.      * @param all
  389.      *            all possible values in the enumeration which should be
  390.      *            recognized. Typically {@code EnumType.values()}.
  391.      * @param section
  392.      *            section the key is grouped within.
  393.      * @param subsection
  394.      *            subsection name, such a remote or branch name.
  395.      * @param name
  396.      *            name of the key to get.
  397.      * @param defaultValue
  398.      *            default value to return if no value was present.
  399.      * @return the selected enumeration value, or {@code defaultValue}.
  400.      */
  401.     public <T extends Enum<?>> T getEnum(final T[] all, final String section,
  402.             final String subsection, final String name, final T defaultValue) {
  403.         return typedGetter.getEnum(this, all, section, subsection, name,
  404.                 defaultValue);
  405.     }

  406.     /**
  407.      * Get string value or null if not found.
  408.      *
  409.      * @param section
  410.      *            the section
  411.      * @param subsection
  412.      *            the subsection for the value
  413.      * @param name
  414.      *            the key name
  415.      * @return a String value from the config, <code>null</code> if not found
  416.      */
  417.     public String getString(final String section, String subsection,
  418.             final String name) {
  419.         return getRawString(section, subsection, name);
  420.     }

  421.     /**
  422.      * Get a list of string values
  423.      * <p>
  424.      * If this instance was created with a base, the base's values are returned
  425.      * first (if any).
  426.      *
  427.      * @param section
  428.      *            the section
  429.      * @param subsection
  430.      *            the subsection for the value
  431.      * @param name
  432.      *            the key name
  433.      * @return array of zero or more values from the configuration.
  434.      */
  435.     public String[] getStringList(final String section, String subsection,
  436.             final String name) {
  437.         String[] base;
  438.         if (baseConfig != null)
  439.             base = baseConfig.getStringList(section, subsection, name);
  440.         else
  441.             base = EMPTY_STRING_ARRAY;

  442.         String[] self = getRawStringList(section, subsection, name);
  443.         if (self == null)
  444.             return base;
  445.         if (base.length == 0)
  446.             return self;
  447.         String[] res = new String[base.length + self.length];
  448.         int n = base.length;
  449.         System.arraycopy(base, 0, res, 0, n);
  450.         System.arraycopy(self, 0, res, n, self.length);
  451.         return res;
  452.     }

  453.     /**
  454.      * Parse a numerical time unit, such as "1 minute", from the configuration.
  455.      *
  456.      * @param section
  457.      *            section the key is in.
  458.      * @param subsection
  459.      *            subsection the key is in, or null if not in a subsection.
  460.      * @param name
  461.      *            the key name.
  462.      * @param defaultValue
  463.      *            default value to return if no value was present.
  464.      * @param wantUnit
  465.      *            the units of {@code defaultValue} and the return value, as
  466.      *            well as the units to assume if the value does not contain an
  467.      *            indication of the units.
  468.      * @return the value, or {@code defaultValue} if not set, expressed in
  469.      *         {@code units}.
  470.      * @since 4.5
  471.      */
  472.     public long getTimeUnit(String section, String subsection, String name,
  473.             long defaultValue, TimeUnit wantUnit) {
  474.         return typedGetter.getTimeUnit(this, section, subsection, name,
  475.                 defaultValue, wantUnit);
  476.     }

  477.     /**
  478.      * Parse a string value and treat it as a file path, replacing a ~/ prefix
  479.      * by the user's home directory.
  480.      * <p>
  481.      * <b>Note:</b> this may throw {@link InvalidPathException} if the string is
  482.      * not a valid path.
  483.      * </p>
  484.      *
  485.      * @param section
  486.      *            section the key is in.
  487.      * @param subsection
  488.      *            subsection the key is in, or null if not in a subsection.
  489.      * @param name
  490.      *            the key name.
  491.      * @param fs
  492.      *            to use to convert the string into a path.
  493.      * @param resolveAgainst
  494.      *            directory to resolve the path against if it is a relative
  495.      *            path; {@code null} to use the Java process's current
  496.      *            directory.
  497.      * @param defaultValue
  498.      *            to return if no value was present
  499.      * @return the {@link Path}, or {@code defaultValue} if not set
  500.      * @since 5.10
  501.      */
  502.     public Path getPath(String section, String subsection, String name,
  503.             @NonNull FS fs, File resolveAgainst, Path defaultValue) {
  504.         return typedGetter.getPath(this, section, subsection, name, fs,
  505.                 resolveAgainst, defaultValue);
  506.     }

  507.     /**
  508.      * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
  509.      * configuration.
  510.      *
  511.      * @param section
  512.      *            section the key is in.
  513.      * @param subsection
  514.      *            subsection the key is in, or null if not in a subsection.
  515.      * @param name
  516.      *            the key name.
  517.      * @return a possibly empty list of
  518.      *         {@link org.eclipse.jgit.transport.RefSpec}s
  519.      * @since 4.9
  520.      */
  521.     public List<RefSpec> getRefSpecs(String section, String subsection,
  522.             String name) {
  523.         return typedGetter.getRefSpecs(this, section, subsection, name);
  524.     }

  525.     /**
  526.      * Get set of all subsections of specified section within this configuration
  527.      * and its base configuration
  528.      *
  529.      * @param section
  530.      *            section to search for.
  531.      * @return set of all subsections of specified section within this
  532.      *         configuration and its base configuration; may be empty if no
  533.      *         subsection exists. The set's iterator returns sections in the
  534.      *         order they are declared by the configuration starting from this
  535.      *         instance and progressing through the base.
  536.      */
  537.     public Set<String> getSubsections(String section) {
  538.         return getState().getSubsections(section);
  539.     }

  540.     /**
  541.      * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  542.      *
  543.      * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  544.      *         The set's iterator returns sections in the order they are
  545.      *         declared by the configuration starting from this instance and
  546.      *         progressing through the base.
  547.      */
  548.     public Set<String> getSections() {
  549.         return getState().getSections();
  550.     }

  551.     /**
  552.      * Get the list of names defined for this section
  553.      *
  554.      * @param section
  555.      *            the section
  556.      * @return the list of names defined for this section
  557.      */
  558.     public Set<String> getNames(String section) {
  559.         return getNames(section, null);
  560.     }

  561.     /**
  562.      * Get the list of names defined for this subsection
  563.      *
  564.      * @param section
  565.      *            the section
  566.      * @param subsection
  567.      *            the subsection
  568.      * @return the list of names defined for this subsection
  569.      */
  570.     public Set<String> getNames(String section, String subsection) {
  571.         return getState().getNames(section, subsection);
  572.     }

  573.     /**
  574.      * Get the list of names defined for this section
  575.      *
  576.      * @param section
  577.      *            the section
  578.      * @param recursive
  579.      *            if {@code true} recursively adds the names defined in all base
  580.      *            configurations
  581.      * @return the list of names defined for this section
  582.      * @since 3.2
  583.      */
  584.     public Set<String> getNames(String section, boolean recursive) {
  585.         return getState().getNames(section, null, recursive);
  586.     }

  587.     /**
  588.      * Get the list of names defined for this section
  589.      *
  590.      * @param section
  591.      *            the section
  592.      * @param subsection
  593.      *            the subsection
  594.      * @param recursive
  595.      *            if {@code true} recursively adds the names defined in all base
  596.      *            configurations
  597.      * @return the list of names defined for this subsection
  598.      * @since 3.2
  599.      */
  600.     public Set<String> getNames(String section, String subsection,
  601.             boolean recursive) {
  602.         return getState().getNames(section, subsection, recursive);
  603.     }

  604.     /**
  605.      * Obtain a handle to a parsed set of configuration values.
  606.      *
  607.      * @param <T>
  608.      *            type of configuration model to return.
  609.      * @param parser
  610.      *            parser which can create the model if it is not already
  611.      *            available in this configuration file. The parser is also used
  612.      *            as the key into a cache and must obey the hashCode and equals
  613.      *            contract in order to reuse a parsed model.
  614.      * @return the parsed object instance, which is cached inside this config.
  615.      */
  616.     @SuppressWarnings("unchecked")
  617.     public <T> T get(SectionParser<T> parser) {
  618.         final ConfigSnapshot myState = getState();
  619.         T obj = (T) myState.cache.get(parser);
  620.         if (obj == null) {
  621.             obj = parser.parse(this);
  622.             myState.cache.put(parser, obj);
  623.         }
  624.         return obj;
  625.     }

  626.     /**
  627.      * Remove a cached configuration object.
  628.      * <p>
  629.      * If the associated configuration object has not yet been cached, this
  630.      * method has no effect.
  631.      *
  632.      * @param parser
  633.      *            parser used to obtain the configuration object.
  634.      * @see #get(SectionParser)
  635.      */
  636.     public void uncache(SectionParser<?> parser) {
  637.         state.get().cache.remove(parser);
  638.     }

  639.     /**
  640.      * Adds a listener to be notified about changes.
  641.      * <p>
  642.      * Clients are supposed to remove the listeners after they are done with
  643.      * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
  644.      * method
  645.      *
  646.      * @param listener
  647.      *            the listener
  648.      * @return the handle to the registered listener
  649.      */
  650.     public ListenerHandle addChangeListener(ConfigChangedListener listener) {
  651.         return listeners.addConfigChangedListener(listener);
  652.     }

  653.     /**
  654.      * Determine whether to issue change events for transient changes.
  655.      * <p>
  656.      * If <code>true</code> is returned (which is the default behavior),
  657.      * {@link #fireConfigChangedEvent()} will be called upon each change.
  658.      * <p>
  659.      * Subclasses that override this to return <code>false</code> are
  660.      * responsible for issuing {@link #fireConfigChangedEvent()} calls
  661.      * themselves.
  662.      *
  663.      * @return <code></code>
  664.      */
  665.     protected boolean notifyUponTransientChanges() {
  666.         return true;
  667.     }

  668.     /**
  669.      * Notifies the listeners
  670.      */
  671.     protected void fireConfigChangedEvent() {
  672.         listeners.dispatch(new ConfigChangedEvent());
  673.     }

  674.     String getRawString(final String section, final String subsection,
  675.             final String name) {
  676.         String[] lst = getRawStringList(section, subsection, name);
  677.         if (lst != null) {
  678.             return lst[lst.length - 1];
  679.         } else if (baseConfig != null) {
  680.             return baseConfig.getRawString(section, subsection, name);
  681.         } else {
  682.             return null;
  683.         }
  684.     }

  685.     private String[] getRawStringList(String section, String subsection,
  686.             String name) {
  687.         return state.get().get(section, subsection, name);
  688.     }

  689.     private ConfigSnapshot getState() {
  690.         ConfigSnapshot cur, upd;
  691.         do {
  692.             cur = state.get();
  693.             final ConfigSnapshot base = getBaseState();
  694.             if (cur.baseState == base)
  695.                 return cur;
  696.             upd = new ConfigSnapshot(cur.entryList, base);
  697.         } while (!state.compareAndSet(cur, upd));
  698.         return upd;
  699.     }

  700.     private ConfigSnapshot getBaseState() {
  701.         return baseConfig != null ? baseConfig.getState() : null;
  702.     }

  703.     /**
  704.      * Add or modify a configuration value. The parameters will result in a
  705.      * configuration entry like this.
  706.      *
  707.      * <pre>
  708.      * [section &quot;subsection&quot;]
  709.      *         name = value
  710.      * </pre>
  711.      *
  712.      * @param section
  713.      *            section name, e.g "branch"
  714.      * @param subsection
  715.      *            optional subsection value, e.g. a branch name
  716.      * @param name
  717.      *            parameter name, e.g. "filemode"
  718.      * @param value
  719.      *            parameter value
  720.      */
  721.     public void setInt(final String section, final String subsection,
  722.             final String name, final int value) {
  723.         setLong(section, subsection, name, value);
  724.     }

  725.     /**
  726.      * Add or modify a configuration value. The parameters will result in a
  727.      * configuration entry like this.
  728.      *
  729.      * <pre>
  730.      * [section &quot;subsection&quot;]
  731.      *         name = value
  732.      * </pre>
  733.      *
  734.      * @param section
  735.      *            section name, e.g "branch"
  736.      * @param subsection
  737.      *            optional subsection value, e.g. a branch name
  738.      * @param name
  739.      *            parameter name, e.g. "filemode"
  740.      * @param value
  741.      *            parameter value
  742.      */
  743.     public void setLong(final String section, final String subsection,
  744.             final String name, final long value) {
  745.         setString(section, subsection, name,
  746.                 StringUtils.formatWithSuffix(value));
  747.     }

  748.     /**
  749.      * Add or modify a configuration value. The parameters will result in a
  750.      * configuration entry like this.
  751.      *
  752.      * <pre>
  753.      * [section &quot;subsection&quot;]
  754.      *         name = value
  755.      * </pre>
  756.      *
  757.      * @param section
  758.      *            section name, e.g "branch"
  759.      * @param subsection
  760.      *            optional subsection value, e.g. a branch name
  761.      * @param name
  762.      *            parameter name, e.g. "filemode"
  763.      * @param value
  764.      *            parameter value
  765.      */
  766.     public void setBoolean(final String section, final String subsection,
  767.             final String name, final boolean value) {
  768.         setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  769.     }

  770.     /**
  771.      * Add or modify a configuration value. The parameters will result in a
  772.      * configuration entry like this.
  773.      *
  774.      * <pre>
  775.      * [section &quot;subsection&quot;]
  776.      *         name = value
  777.      * </pre>
  778.      *
  779.      * @param section
  780.      *            section name, e.g "branch"
  781.      * @param subsection
  782.      *            optional subsection value, e.g. a branch name
  783.      * @param name
  784.      *            parameter name, e.g. "filemode"
  785.      * @param value
  786.      *            parameter value
  787.      */
  788.     public <T extends Enum<?>> void setEnum(final String section,
  789.             final String subsection, final String name, final T value) {
  790.         String n;
  791.         if (value instanceof ConfigEnum)
  792.             n = ((ConfigEnum) value).toConfigValue();
  793.         else
  794.             n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
  795.         setString(section, subsection, name, n);
  796.     }

  797.     /**
  798.      * Add or modify a configuration value. The parameters will result in a
  799.      * configuration entry like this.
  800.      *
  801.      * <pre>
  802.      * [section &quot;subsection&quot;]
  803.      *         name = value
  804.      * </pre>
  805.      *
  806.      * @param section
  807.      *            section name, e.g "branch"
  808.      * @param subsection
  809.      *            optional subsection value, e.g. a branch name
  810.      * @param name
  811.      *            parameter name, e.g. "filemode"
  812.      * @param value
  813.      *            parameter value, e.g. "true"
  814.      */
  815.     public void setString(final String section, final String subsection,
  816.             final String name, final String value) {
  817.         setStringList(section, subsection, name, Collections
  818.                 .singletonList(value));
  819.     }

  820.     /**
  821.      * Remove a configuration value.
  822.      *
  823.      * @param section
  824.      *            section name, e.g "branch"
  825.      * @param subsection
  826.      *            optional subsection value, e.g. a branch name
  827.      * @param name
  828.      *            parameter name, e.g. "filemode"
  829.      */
  830.     public void unset(final String section, final String subsection,
  831.             final String name) {
  832.         setStringList(section, subsection, name, Collections
  833.                 .<String> emptyList());
  834.     }

  835.     /**
  836.      * Remove all configuration values under a single section.
  837.      *
  838.      * @param section
  839.      *            section name, e.g "branch"
  840.      * @param subsection
  841.      *            optional subsection value, e.g. a branch name
  842.      */
  843.     public void unsetSection(String section, String subsection) {
  844.         ConfigSnapshot src, res;
  845.         do {
  846.             src = state.get();
  847.             res = unsetSection(src, section, subsection);
  848.         } while (!state.compareAndSet(src, res));
  849.     }

  850.     private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
  851.             final String section,
  852.             final String subsection) {
  853.         final int max = srcState.entryList.size();
  854.         final ArrayList<ConfigLine> r = new ArrayList<>(max);

  855.         boolean lastWasMatch = false;
  856.         for (ConfigLine e : srcState.entryList) {
  857.             if (e.includedFrom == null && e.match(section, subsection)) {
  858.                 // Skip this record, it's for the section we are removing.
  859.                 lastWasMatch = true;
  860.                 continue;
  861.             }

  862.             if (lastWasMatch && e.section == null && e.subsection == null)
  863.                 continue; // skip this padding line in the section.
  864.             r.add(e);
  865.         }

  866.         return newState(r);
  867.     }

  868.     /**
  869.      * Set a configuration value.
  870.      *
  871.      * <pre>
  872.      * [section &quot;subsection&quot;]
  873.      *         name = value1
  874.      *         name = value2
  875.      * </pre>
  876.      *
  877.      * @param section
  878.      *            section name, e.g "branch"
  879.      * @param subsection
  880.      *            optional subsection value, e.g. a branch name
  881.      * @param name
  882.      *            parameter name, e.g. "filemode"
  883.      * @param values
  884.      *            list of zero or more values for this key.
  885.      */
  886.     public void setStringList(final String section, final String subsection,
  887.             final String name, final List<String> values) {
  888.         ConfigSnapshot src, res;
  889.         do {
  890.             src = state.get();
  891.             res = replaceStringList(src, section, subsection, name, values);
  892.         } while (!state.compareAndSet(src, res));
  893.         if (notifyUponTransientChanges())
  894.             fireConfigChangedEvent();
  895.     }

  896.     private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
  897.             final String section, final String subsection, final String name,
  898.             final List<String> values) {
  899.         final List<ConfigLine> entries = copy(srcState, values);
  900.         int entryIndex = 0;
  901.         int valueIndex = 0;
  902.         int insertPosition = -1;

  903.         // Reset the first n Entry objects that match this input name.
  904.         //
  905.         while (entryIndex < entries.size() && valueIndex < values.size()) {
  906.             final ConfigLine e = entries.get(entryIndex);
  907.             if (e.includedFrom == null && e.match(section, subsection, name)) {
  908.                 entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
  909.                 insertPosition = entryIndex + 1;
  910.             }
  911.             entryIndex++;
  912.         }

  913.         // Remove any extra Entry objects that we no longer need.
  914.         //
  915.         if (valueIndex == values.size() && entryIndex < entries.size()) {
  916.             while (entryIndex < entries.size()) {
  917.                 final ConfigLine e = entries.get(entryIndex++);
  918.                 if (e.includedFrom == null
  919.                         && e.match(section, subsection, name))
  920.                     entries.remove(--entryIndex);
  921.             }
  922.         }

  923.         // Insert new Entry objects for additional/new values.
  924.         //
  925.         if (valueIndex < values.size() && entryIndex == entries.size()) {
  926.             if (insertPosition < 0) {
  927.                 // We didn't find a matching key above, but maybe there
  928.                 // is already a section available that matches. Insert
  929.                 // after the last key of that section.
  930.                 //
  931.                 insertPosition = findSectionEnd(entries, section, subsection,
  932.                         true);
  933.             }
  934.             if (insertPosition < 0) {
  935.                 // We didn't find any matching section header for this key,
  936.                 // so we must create a new section header at the end.
  937.                 //
  938.                 final ConfigLine e = new ConfigLine();
  939.                 e.section = section;
  940.                 e.subsection = subsection;
  941.                 entries.add(e);
  942.                 insertPosition = entries.size();
  943.             }
  944.             while (valueIndex < values.size()) {
  945.                 final ConfigLine e = new ConfigLine();
  946.                 e.section = section;
  947.                 e.subsection = subsection;
  948.                 e.name = name;
  949.                 e.value = values.get(valueIndex++);
  950.                 entries.add(insertPosition++, e);
  951.             }
  952.         }

  953.         return newState(entries);
  954.     }

  955.     private static List<ConfigLine> copy(final ConfigSnapshot src,
  956.             final List<String> values) {
  957.         // At worst we need to insert 1 line for each value, plus 1 line
  958.         // for a new section header. Assume that and allocate the space.
  959.         //
  960.         final int max = src.entryList.size() + values.size() + 1;
  961.         final ArrayList<ConfigLine> r = new ArrayList<>(max);
  962.         r.addAll(src.entryList);
  963.         return r;
  964.     }

  965.     private static int findSectionEnd(final List<ConfigLine> entries,
  966.             final String section, final String subsection,
  967.             boolean skipIncludedLines) {
  968.         for (int i = 0; i < entries.size(); i++) {
  969.             ConfigLine e = entries.get(i);
  970.             if (e.includedFrom != null && skipIncludedLines) {
  971.                 continue;
  972.             }

  973.             if (e.match(section, subsection, null)) {
  974.                 i++;
  975.                 while (i < entries.size()) {
  976.                     e = entries.get(i);
  977.                     if (e.match(section, subsection, e.name))
  978.                         i++;
  979.                     else
  980.                         break;
  981.                 }
  982.                 return i;
  983.             }
  984.         }
  985.         return -1;
  986.     }

  987.     /**
  988.      * Get this configuration, formatted as a Git style text file.
  989.      *
  990.      * @return this configuration, formatted as a Git style text file.
  991.      */
  992.     public String toText() {
  993.         final StringBuilder out = new StringBuilder();
  994.         for (ConfigLine e : state.get().entryList) {
  995.             if (e.includedFrom != null)
  996.                 continue;
  997.             if (e.prefix != null)
  998.                 out.append(e.prefix);
  999.             if (e.section != null && e.name == null) {
  1000.                 out.append('[');
  1001.                 out.append(e.section);
  1002.                 if (e.subsection != null) {
  1003.                     out.append(' ');
  1004.                     String escaped = escapeValue(e.subsection);
  1005.                     // make sure to avoid double quotes here
  1006.                     boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
  1007.                             && escaped.endsWith("\""); //$NON-NLS-1$
  1008.                     if (!quoted)
  1009.                         out.append('"');
  1010.                     out.append(escaped);
  1011.                     if (!quoted)
  1012.                         out.append('"');
  1013.                 }
  1014.                 out.append(']');
  1015.             } else if (e.section != null && e.name != null) {
  1016.                 if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
  1017.                     out.append('\t');
  1018.                 out.append(e.name);
  1019.                 if (!isMissing(e.value)) {
  1020.                     out.append(" ="); //$NON-NLS-1$
  1021.                     if (e.value != null) {
  1022.                         out.append(' ');
  1023.                         out.append(escapeValue(e.value));
  1024.                     }
  1025.                 }
  1026.                 if (e.suffix != null)
  1027.                     out.append(' ');
  1028.             }
  1029.             if (e.suffix != null)
  1030.                 out.append(e.suffix);
  1031.             out.append('\n');
  1032.         }
  1033.         return out.toString();
  1034.     }

  1035.     /**
  1036.      * Clear this configuration and reset to the contents of the parsed string.
  1037.      *
  1038.      * @param text
  1039.      *            Git style text file listing configuration properties.
  1040.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1041.      *             the text supplied is not formatted correctly. No changes were
  1042.      *             made to {@code this}.
  1043.      */
  1044.     public void fromText(String text) throws ConfigInvalidException {
  1045.         state.set(newState(fromTextRecurse(text, 1, null)));
  1046.     }

  1047.     private List<ConfigLine> fromTextRecurse(String text, int depth,
  1048.             String includedFrom) throws ConfigInvalidException {
  1049.         if (depth > MAX_DEPTH) {
  1050.             throw new ConfigInvalidException(
  1051.                     JGitText.get().tooManyIncludeRecursions);
  1052.         }
  1053.         final List<ConfigLine> newEntries = new ArrayList<>();
  1054.         final StringReader in = new StringReader(text);
  1055.         ConfigLine last = null;
  1056.         ConfigLine e = new ConfigLine();
  1057.         e.includedFrom = includedFrom;
  1058.         for (;;) {
  1059.             int input = in.read();
  1060.             if (-1 == input) {
  1061.                 if (e.section != null)
  1062.                     newEntries.add(e);
  1063.                 break;
  1064.             }

  1065.             final char c = (char) input;
  1066.             if ('\n' == c) {
  1067.                 // End of this entry.
  1068.                 newEntries.add(e);
  1069.                 if (e.section != null)
  1070.                     last = e;
  1071.                 e = new ConfigLine();
  1072.                 e.includedFrom = includedFrom;
  1073.             } else if (e.suffix != null) {
  1074.                 // Everything up until the end-of-line is in the suffix.
  1075.                 e.suffix += c;

  1076.             } else if (';' == c || '#' == c) {
  1077.                 // The rest of this line is a comment; put into suffix.
  1078.                 e.suffix = String.valueOf(c);

  1079.             } else if (e.section == null && Character.isWhitespace(c)) {
  1080.                 // Save the leading whitespace (if any).
  1081.                 if (e.prefix == null)
  1082.                     e.prefix = ""; //$NON-NLS-1$
  1083.                 e.prefix += c;

  1084.             } else if ('[' == c) {
  1085.                 // This is a section header.
  1086.                 e.section = readSectionName(in);
  1087.                 input = in.read();
  1088.                 if ('"' == input) {
  1089.                     e.subsection = readSubsectionName(in);
  1090.                     input = in.read();
  1091.                 }
  1092.                 if (']' != input)
  1093.                     throw new ConfigInvalidException(JGitText.get().badGroupHeader);
  1094.                 e.suffix = ""; //$NON-NLS-1$

  1095.             } else if (last != null) {
  1096.                 // Read a value.
  1097.                 e.section = last.section;
  1098.                 e.subsection = last.subsection;
  1099.                 in.reset();
  1100.                 e.name = readKeyName(in);
  1101.                 if (e.name.endsWith("\n")) { //$NON-NLS-1$
  1102.                     e.name = e.name.substring(0, e.name.length() - 1);
  1103.                     e.value = MISSING_ENTRY;
  1104.                 } else
  1105.                     e.value = readValue(in);

  1106.                 if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
  1107.                     addIncludedConfig(newEntries, e, depth);
  1108.                 }
  1109.             } else
  1110.                 throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
  1111.         }

  1112.         return newEntries;
  1113.     }

  1114.     /**
  1115.      * Read the included config from the specified (possibly) relative path
  1116.      *
  1117.      * @param relPath
  1118.      *            possibly relative path to the included config, as specified in
  1119.      *            this config
  1120.      * @return the read bytes, or null if the included config should be ignored
  1121.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1122.      *             if something went wrong while reading the config
  1123.      * @since 4.10
  1124.      */
  1125.     protected byte[] readIncludedConfig(String relPath)
  1126.             throws ConfigInvalidException {
  1127.         return null;
  1128.     }

  1129.     private void addIncludedConfig(final List<ConfigLine> newEntries,
  1130.             ConfigLine line, int depth) throws ConfigInvalidException {
  1131.         if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
  1132.                 line.value == null || line.value.equals(MISSING_ENTRY)) {
  1133.             throw new ConfigInvalidException(MessageFormat.format(
  1134.                     JGitText.get().invalidLineInConfigFileWithParam, line));
  1135.         }
  1136.         byte[] bytes = readIncludedConfig(line.value);
  1137.         if (bytes == null) {
  1138.             return;
  1139.         }

  1140.         String decoded;
  1141.         if (isUtf8(bytes)) {
  1142.             decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
  1143.         } else {
  1144.             decoded = RawParseUtils.decode(bytes);
  1145.         }
  1146.         try {
  1147.             newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
  1148.         } catch (ConfigInvalidException e) {
  1149.             throw new ConfigInvalidException(MessageFormat
  1150.                     .format(JGitText.get().cannotReadFile, line.value), e);
  1151.         }
  1152.     }

  1153.     private ConfigSnapshot newState() {
  1154.         return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
  1155.                 getBaseState());
  1156.     }

  1157.     private ConfigSnapshot newState(List<ConfigLine> entries) {
  1158.         return new ConfigSnapshot(Collections.unmodifiableList(entries),
  1159.                 getBaseState());
  1160.     }

  1161.     /**
  1162.      * Clear the configuration file
  1163.      */
  1164.     protected void clear() {
  1165.         state.set(newState());
  1166.     }

  1167.     /**
  1168.      * Check if bytes should be treated as UTF-8 or not.
  1169.      *
  1170.      * @param bytes
  1171.      *            the bytes to check encoding for.
  1172.      * @return true if bytes should be treated as UTF-8, false otherwise.
  1173.      * @since 4.4
  1174.      */
  1175.     protected boolean isUtf8(final byte[] bytes) {
  1176.         return bytes.length >= 3 && bytes[0] == (byte) 0xEF
  1177.                 && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
  1178.     }

  1179.     private static String readSectionName(StringReader in)
  1180.             throws ConfigInvalidException {
  1181.         final StringBuilder name = new StringBuilder();
  1182.         for (;;) {
  1183.             int c = in.read();
  1184.             if (c < 0)
  1185.                 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1186.             if (']' == c) {
  1187.                 in.reset();
  1188.                 break;
  1189.             }

  1190.             if (' ' == c || '\t' == c) {
  1191.                 for (;;) {
  1192.                     c = in.read();
  1193.                     if (c < 0)
  1194.                         throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1195.                     if ('"' == c) {
  1196.                         in.reset();
  1197.                         break;
  1198.                     }

  1199.                     if (' ' == c || '\t' == c)
  1200.                         continue; // Skipped...
  1201.                     throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1202.                 }
  1203.                 break;
  1204.             }

  1205.             if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  1206.                 name.append((char) c);
  1207.             else
  1208.                 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1209.         }
  1210.         return name.toString();
  1211.     }

  1212.     private static String readKeyName(StringReader in)
  1213.             throws ConfigInvalidException {
  1214.         final StringBuilder name = new StringBuilder();
  1215.         for (;;) {
  1216.             int c = in.read();
  1217.             if (c < 0)
  1218.                 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1219.             if ('=' == c)
  1220.                 break;

  1221.             if (' ' == c || '\t' == c) {
  1222.                 for (;;) {
  1223.                     c = in.read();
  1224.                     if (c < 0)
  1225.                         throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1226.                     if ('=' == c)
  1227.                         break;

  1228.                     if (';' == c || '#' == c || '\n' == c) {
  1229.                         in.reset();
  1230.                         break;
  1231.                     }

  1232.                     if (' ' == c || '\t' == c)
  1233.                         continue; // Skipped...
  1234.                     throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
  1235.                 }
  1236.                 break;
  1237.             }

  1238.             if (Character.isLetterOrDigit((char) c) || c == '-') {
  1239.                 // From the git-config man page:
  1240.                 // The variable names are case-insensitive and only
  1241.                 // alphanumeric characters and - are allowed.
  1242.                 name.append((char) c);
  1243.             } else if ('\n' == c) {
  1244.                 in.reset();
  1245.                 name.append((char) c);
  1246.                 break;
  1247.             } else
  1248.                 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
  1249.         }
  1250.         return name.toString();
  1251.     }

  1252.     private static String readSubsectionName(StringReader in)
  1253.             throws ConfigInvalidException {
  1254.         StringBuilder r = new StringBuilder();
  1255.         for (;;) {
  1256.             int c = in.read();
  1257.             if (c < 0) {
  1258.                 break;
  1259.             }

  1260.             if ('\n' == c) {
  1261.                 throw new ConfigInvalidException(
  1262.                         JGitText.get().newlineInQuotesNotAllowed);
  1263.             }
  1264.             if ('\\' == c) {
  1265.                 c = in.read();
  1266.                 switch (c) {
  1267.                 case -1:
  1268.                     throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);

  1269.                 case '\\':
  1270.                 case '"':
  1271.                     r.append((char) c);
  1272.                     continue;

  1273.                 default:
  1274.                     // C git simply drops backslashes if the escape sequence is not
  1275.                     // recognized.
  1276.                     r.append((char) c);
  1277.                     continue;
  1278.                 }
  1279.             }
  1280.             if ('"' == c) {
  1281.                 break;
  1282.             }

  1283.             r.append((char) c);
  1284.         }
  1285.         return r.toString();
  1286.     }

  1287.     private static String readValue(StringReader in)
  1288.             throws ConfigInvalidException {
  1289.         StringBuilder value = new StringBuilder();
  1290.         StringBuilder trailingSpaces = null;
  1291.         boolean quote = false;
  1292.         boolean inLeadingSpace = true;

  1293.         for (;;) {
  1294.             int c = in.read();
  1295.             if (c < 0) {
  1296.                 break;
  1297.             }
  1298.             if ('\n' == c) {
  1299.                 if (quote) {
  1300.                     throw new ConfigInvalidException(
  1301.                             JGitText.get().newlineInQuotesNotAllowed);
  1302.                 }
  1303.                 in.reset();
  1304.                 break;
  1305.             }

  1306.             if (!quote && (';' == c || '#' == c)) {
  1307.                 if (trailingSpaces != null) {
  1308.                     trailingSpaces.setLength(0);
  1309.                 }
  1310.                 in.reset();
  1311.                 break;
  1312.             }

  1313.             char cc = (char) c;
  1314.             if (Character.isWhitespace(cc)) {
  1315.                 if (inLeadingSpace) {
  1316.                     continue;
  1317.                 }
  1318.                 if (trailingSpaces == null) {
  1319.                     trailingSpaces = new StringBuilder();
  1320.                 }
  1321.                 trailingSpaces.append(cc);
  1322.                 continue;
  1323.             }
  1324.             inLeadingSpace = false;
  1325.             if (trailingSpaces != null) {
  1326.                 value.append(trailingSpaces);
  1327.                 trailingSpaces.setLength(0);
  1328.             }

  1329.             if ('\\' == c) {
  1330.                 c = in.read();
  1331.                 switch (c) {
  1332.                 case -1:
  1333.                     throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1334.                 case '\n':
  1335.                     continue;
  1336.                 case 't':
  1337.                     value.append('\t');
  1338.                     continue;
  1339.                 case 'b':
  1340.                     value.append('\b');
  1341.                     continue;
  1342.                 case 'n':
  1343.                     value.append('\n');
  1344.                     continue;
  1345.                 case '\\':
  1346.                     value.append('\\');
  1347.                     continue;
  1348.                 case '"':
  1349.                     value.append('"');
  1350.                     continue;
  1351.                 case '\r': {
  1352.                     int next = in.read();
  1353.                     if (next == '\n') {
  1354.                         continue; // CR-LF
  1355.                     } else if (next >= 0) {
  1356.                         in.reset();
  1357.                     }
  1358.                     break;
  1359.                 }
  1360.                 default:
  1361.                     break;
  1362.                 }
  1363.                 throw new ConfigInvalidException(
  1364.                         MessageFormat.format(JGitText.get().badEscape,
  1365.                                 Character.isAlphabetic(c)
  1366.                                         ? Character.valueOf(((char) c))
  1367.                                         : toUnicodeLiteral(c)));
  1368.             }

  1369.             if ('"' == c) {
  1370.                 quote = !quote;
  1371.                 continue;
  1372.             }

  1373.             value.append(cc);
  1374.         }
  1375.         return value.length() > 0 ? value.toString() : null;
  1376.     }

  1377.     private static String toUnicodeLiteral(int c) {
  1378.         return String.format("\\u%04x", //$NON-NLS-1$
  1379.                 Integer.valueOf(c));
  1380.     }

  1381.     /**
  1382.      * Parses a section of the configuration into an application model object.
  1383.      * <p>
  1384.      * Instances must implement hashCode and equals such that model objects can
  1385.      * be cached by using the {@code SectionParser} as a key of a HashMap.
  1386.      * <p>
  1387.      * As the {@code SectionParser} itself is used as the key of the internal
  1388.      * HashMap applications should be careful to ensure the SectionParser key
  1389.      * does not retain unnecessary application state which may cause memory to
  1390.      * be held longer than expected.
  1391.      *
  1392.      * @param <T>
  1393.      *            type of the application model created by the parser.
  1394.      */
  1395.     public static interface SectionParser<T> {
  1396.         /**
  1397.          * Create a model object from a configuration.
  1398.          *
  1399.          * @param cfg
  1400.          *            the configuration to read values from.
  1401.          * @return the application model instance.
  1402.          */
  1403.         T parse(Config cfg);
  1404.     }

  1405.     private static class StringReader {
  1406.         private final char[] buf;

  1407.         private int pos;

  1408.         StringReader(String in) {
  1409.             buf = in.toCharArray();
  1410.         }

  1411.         int read() {
  1412.             if (pos >= buf.length) {
  1413.                 return -1;
  1414.             }
  1415.             return buf[pos++];
  1416.         }

  1417.         void reset() {
  1418.             pos--;
  1419.         }
  1420.     }

  1421.     /**
  1422.      * Converts enumeration values into configuration options and vice-versa,
  1423.      * allowing to match a config option with an enum value.
  1424.      *
  1425.      */
  1426.     public static interface ConfigEnum {
  1427.         /**
  1428.          * Converts enumeration value into a string to be save in config.
  1429.          *
  1430.          * @return the enum value as config string
  1431.          */
  1432.         String toConfigValue();

  1433.         /**
  1434.          * Checks if the given string matches with enum value.
  1435.          *
  1436.          * @param in
  1437.          *            the string to match
  1438.          * @return true if the given string matches enum value, false otherwise
  1439.          */
  1440.         boolean matchConfigValue(String in);
  1441.     }
  1442. }