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. /**
  42.  * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.
  43.  */
  44. public class Config {

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

  46.     static final long KiB = 1024;
  47.     static final long MiB = 1024 * KiB;
  48.     static final long GiB = 1024 * MiB;
  49.     private static final int MAX_DEPTH = 10;

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

  51.     private static TypedConfigGetter typedGetter = DEFAULT_GETTER;

  52.     /** the change listeners */
  53.     private final ListenerList listeners = new ListenerList();

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

  61.     private final Config baseConfig;

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

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

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

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

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

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

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

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

  154.             case '\n':
  155.                 r.append('\\').append('n');
  156.                 break;

  157.             case '\t':
  158.                 r.append('\\').append('t');
  159.                 break;

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

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

  169.             case '"':
  170.                 r.append('\\').append('"');
  171.                 break;

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

  177.             default:
  178.                 r.append(c);
  179.                 break;
  180.             }
  181.         }

  182.         return needQuote ? '"' + r.toString() + '"' : r.toString();
  183.     }

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

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

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

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

  200.             case '\\':
  201.             case '"':
  202.                 r.append('\\').append(c);
  203.                 break;

  204.             default:
  205.                 r.append(c);
  206.                 break;
  207.             }
  208.         }

  209.         return r.append('"').toString();
  210.     }

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

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

  244.     /**
  245.      * Obtain an integer value from the configuration.
  246.      *
  247.      * @param section
  248.      *            section the key is grouped within.
  249.      * @param name
  250.      *            name of the key to get.
  251.      * @param defaultValue
  252.      *            default value to return if no value was present.
  253.      * @return an integer value from the configuration, or defaultValue.
  254.      */
  255.     public long getLong(String section, String name, long defaultValue) {
  256.         return typedGetter.getLong(this, section, null, name, defaultValue);
  257.     }

  258.     /**
  259.      * Obtain an integer value from the configuration.
  260.      *
  261.      * @param section
  262.      *            section the key is grouped within.
  263.      * @param subsection
  264.      *            subsection name, such a remote or branch name.
  265.      * @param name
  266.      *            name of the key to get.
  267.      * @param defaultValue
  268.      *            default value to return if no value was present.
  269.      * @return an integer value from the configuration, or defaultValue.
  270.      */
  271.     public long getLong(final String section, String subsection,
  272.             final String name, final long defaultValue) {
  273.         return typedGetter.getLong(this, section, subsection, name,
  274.                 defaultValue);
  275.     }

  276.     /**
  277.      * Get a boolean value from the git config
  278.      *
  279.      * @param section
  280.      *            section the key is grouped within.
  281.      * @param name
  282.      *            name of the key to get.
  283.      * @param defaultValue
  284.      *            default value to return if no value was present.
  285.      * @return true if any value or defaultValue is true, false for missing or
  286.      *         explicit false
  287.      */
  288.     public boolean getBoolean(final String section, final String name,
  289.             final boolean defaultValue) {
  290.         return typedGetter.getBoolean(this, section, null, name, defaultValue);
  291.     }

  292.     /**
  293.      * Get a boolean value from the git config
  294.      *
  295.      * @param section
  296.      *            section the key is grouped within.
  297.      * @param subsection
  298.      *            subsection name, such a remote or branch name.
  299.      * @param name
  300.      *            name of the key to get.
  301.      * @param defaultValue
  302.      *            default value to return if no value was present.
  303.      * @return true if any value or defaultValue is true, false for missing or
  304.      *         explicit false
  305.      */
  306.     public boolean getBoolean(final String section, String subsection,
  307.             final String name, final boolean defaultValue) {
  308.         return typedGetter.getBoolean(this, section, subsection, name,
  309.                 defaultValue);
  310.     }

  311.     /**
  312.      * Parse an enumeration from the configuration.
  313.      *
  314.      * @param section
  315.      *            section the key is grouped within.
  316.      * @param subsection
  317.      *            subsection name, such a remote or branch name.
  318.      * @param name
  319.      *            name of the key to get.
  320.      * @param defaultValue
  321.      *            default value to return if no value was present.
  322.      * @return the selected enumeration value, or {@code defaultValue}.
  323.      */
  324.     public <T extends Enum<?>> T getEnum(final String section,
  325.             final String subsection, final String name, final T defaultValue) {
  326.         final T[] all = allValuesOf(defaultValue);
  327.         return typedGetter.getEnum(this, all, section, subsection, name,
  328.                 defaultValue);
  329.     }

  330.     @SuppressWarnings("unchecked")
  331.     private static <T> T[] allValuesOf(T value) {
  332.         try {
  333.             return (T[]) value.getClass().getMethod("values").invoke(null); //$NON-NLS-1$
  334.         } catch (Exception err) {
  335.             String typeName = value.getClass().getName();
  336.             String msg = MessageFormat.format(
  337.                     JGitText.get().enumValuesNotAvailable, typeName);
  338.             throw new IllegalArgumentException(msg, err);
  339.         }
  340.     }

  341.     /**
  342.      * Parse an enumeration from the configuration.
  343.      *
  344.      * @param all
  345.      *            all possible values in the enumeration which should be
  346.      *            recognized. Typically {@code EnumType.values()}.
  347.      * @param section
  348.      *            section the key is grouped within.
  349.      * @param subsection
  350.      *            subsection name, such a remote or branch name.
  351.      * @param name
  352.      *            name of the key to get.
  353.      * @param defaultValue
  354.      *            default value to return if no value was present.
  355.      * @return the selected enumeration value, or {@code defaultValue}.
  356.      */
  357.     public <T extends Enum<?>> T getEnum(final T[] all, final String section,
  358.             final String subsection, final String name, final T defaultValue) {
  359.         return typedGetter.getEnum(this, all, section, subsection, name,
  360.                 defaultValue);
  361.     }

  362.     /**
  363.      * Get string value or null if not found.
  364.      *
  365.      * @param section
  366.      *            the section
  367.      * @param subsection
  368.      *            the subsection for the value
  369.      * @param name
  370.      *            the key name
  371.      * @return a String value from the config, <code>null</code> if not found
  372.      */
  373.     public String getString(final String section, String subsection,
  374.             final String name) {
  375.         return getRawString(section, subsection, name);
  376.     }

  377.     /**
  378.      * Get a list of string values
  379.      * <p>
  380.      * If this instance was created with a base, the base's values are returned
  381.      * first (if any).
  382.      *
  383.      * @param section
  384.      *            the section
  385.      * @param subsection
  386.      *            the subsection for the value
  387.      * @param name
  388.      *            the key name
  389.      * @return array of zero or more values from the configuration.
  390.      */
  391.     public String[] getStringList(final String section, String subsection,
  392.             final String name) {
  393.         String[] base;
  394.         if (baseConfig != null)
  395.             base = baseConfig.getStringList(section, subsection, name);
  396.         else
  397.             base = EMPTY_STRING_ARRAY;

  398.         String[] self = getRawStringList(section, subsection, name);
  399.         if (self == null)
  400.             return base;
  401.         if (base.length == 0)
  402.             return self;
  403.         String[] res = new String[base.length + self.length];
  404.         int n = base.length;
  405.         System.arraycopy(base, 0, res, 0, n);
  406.         System.arraycopy(self, 0, res, n, self.length);
  407.         return res;
  408.     }

  409.     /**
  410.      * Parse a numerical time unit, such as "1 minute", from the configuration.
  411.      *
  412.      * @param section
  413.      *            section the key is in.
  414.      * @param subsection
  415.      *            subsection the key is in, or null if not in a subsection.
  416.      * @param name
  417.      *            the key name.
  418.      * @param defaultValue
  419.      *            default value to return if no value was present.
  420.      * @param wantUnit
  421.      *            the units of {@code defaultValue} and the return value, as
  422.      *            well as the units to assume if the value does not contain an
  423.      *            indication of the units.
  424.      * @return the value, or {@code defaultValue} if not set, expressed in
  425.      *         {@code units}.
  426.      * @since 4.5
  427.      */
  428.     public long getTimeUnit(String section, String subsection, String name,
  429.             long defaultValue, TimeUnit wantUnit) {
  430.         return typedGetter.getTimeUnit(this, section, subsection, name,
  431.                 defaultValue, wantUnit);
  432.     }

  433.     /**
  434.      * Parse a string value and treat it as a file path, replacing a ~/ prefix
  435.      * by the user's home directory.
  436.      * <p>
  437.      * <b>Note:</b> this may throw {@link InvalidPathException} if the string is
  438.      * not a valid path.
  439.      * </p>
  440.      *
  441.      * @param section
  442.      *            section the key is in.
  443.      * @param subsection
  444.      *            subsection the key is in, or null if not in a subsection.
  445.      * @param name
  446.      *            the key name.
  447.      * @param fs
  448.      *            to use to convert the string into a path.
  449.      * @param resolveAgainst
  450.      *            directory to resolve the path against if it is a relative
  451.      *            path; {@code null} to use the Java process's current
  452.      *            directory.
  453.      * @param defaultValue
  454.      *            to return if no value was present
  455.      * @return the {@link Path}, or {@code defaultValue} if not set
  456.      * @since 5.10
  457.      */
  458.     public Path getPath(String section, String subsection, String name,
  459.             @NonNull FS fs, File resolveAgainst, Path defaultValue) {
  460.         return typedGetter.getPath(this, section, subsection, name, fs,
  461.                 resolveAgainst, defaultValue);
  462.     }

  463.     /**
  464.      * Parse a list of {@link org.eclipse.jgit.transport.RefSpec}s from the
  465.      * configuration.
  466.      *
  467.      * @param section
  468.      *            section the key is in.
  469.      * @param subsection
  470.      *            subsection the key is in, or null if not in a subsection.
  471.      * @param name
  472.      *            the key name.
  473.      * @return a possibly empty list of
  474.      *         {@link org.eclipse.jgit.transport.RefSpec}s
  475.      * @since 4.9
  476.      */
  477.     public List<RefSpec> getRefSpecs(String section, String subsection,
  478.             String name) {
  479.         return typedGetter.getRefSpecs(this, section, subsection, name);
  480.     }

  481.     /**
  482.      * Get set of all subsections of specified section within this configuration
  483.      * and its base configuration
  484.      *
  485.      * @param section
  486.      *            section to search for.
  487.      * @return set of all subsections of specified section within this
  488.      *         configuration and its base configuration; may be empty if no
  489.      *         subsection exists. The set's iterator returns sections in the
  490.      *         order they are declared by the configuration starting from this
  491.      *         instance and progressing through the base.
  492.      */
  493.     public Set<String> getSubsections(String section) {
  494.         return getState().getSubsections(section);
  495.     }

  496.     /**
  497.      * Get the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  498.      *
  499.      * @return the sections defined in this {@link org.eclipse.jgit.lib.Config}.
  500.      *         The set's iterator returns sections in the order they are
  501.      *         declared by the configuration starting from this instance and
  502.      *         progressing through the base.
  503.      */
  504.     public Set<String> getSections() {
  505.         return getState().getSections();
  506.     }

  507.     /**
  508.      * Get the list of names defined for this section
  509.      *
  510.      * @param section
  511.      *            the section
  512.      * @return the list of names defined for this section
  513.      */
  514.     public Set<String> getNames(String section) {
  515.         return getNames(section, null);
  516.     }

  517.     /**
  518.      * Get the list of names defined for this subsection
  519.      *
  520.      * @param section
  521.      *            the section
  522.      * @param subsection
  523.      *            the subsection
  524.      * @return the list of names defined for this subsection
  525.      */
  526.     public Set<String> getNames(String section, String subsection) {
  527.         return getState().getNames(section, subsection);
  528.     }

  529.     /**
  530.      * Get the list of names defined for this section
  531.      *
  532.      * @param section
  533.      *            the section
  534.      * @param recursive
  535.      *            if {@code true} recursively adds the names defined in all base
  536.      *            configurations
  537.      * @return the list of names defined for this section
  538.      * @since 3.2
  539.      */
  540.     public Set<String> getNames(String section, boolean recursive) {
  541.         return getState().getNames(section, null, recursive);
  542.     }

  543.     /**
  544.      * Get the list of names defined for this section
  545.      *
  546.      * @param section
  547.      *            the section
  548.      * @param subsection
  549.      *            the subsection
  550.      * @param recursive
  551.      *            if {@code true} recursively adds the names defined in all base
  552.      *            configurations
  553.      * @return the list of names defined for this subsection
  554.      * @since 3.2
  555.      */
  556.     public Set<String> getNames(String section, String subsection,
  557.             boolean recursive) {
  558.         return getState().getNames(section, subsection, recursive);
  559.     }

  560.     /**
  561.      * Obtain a handle to a parsed set of configuration values.
  562.      *
  563.      * @param <T>
  564.      *            type of configuration model to return.
  565.      * @param parser
  566.      *            parser which can create the model if it is not already
  567.      *            available in this configuration file. The parser is also used
  568.      *            as the key into a cache and must obey the hashCode and equals
  569.      *            contract in order to reuse a parsed model.
  570.      * @return the parsed object instance, which is cached inside this config.
  571.      */
  572.     @SuppressWarnings("unchecked")
  573.     public <T> T get(SectionParser<T> parser) {
  574.         final ConfigSnapshot myState = getState();
  575.         T obj = (T) myState.cache.get(parser);
  576.         if (obj == null) {
  577.             obj = parser.parse(this);
  578.             myState.cache.put(parser, obj);
  579.         }
  580.         return obj;
  581.     }

  582.     /**
  583.      * Remove a cached configuration object.
  584.      * <p>
  585.      * If the associated configuration object has not yet been cached, this
  586.      * method has no effect.
  587.      *
  588.      * @param parser
  589.      *            parser used to obtain the configuration object.
  590.      * @see #get(SectionParser)
  591.      */
  592.     public void uncache(SectionParser<?> parser) {
  593.         state.get().cache.remove(parser);
  594.     }

  595.     /**
  596.      * Adds a listener to be notified about changes.
  597.      * <p>
  598.      * Clients are supposed to remove the listeners after they are done with
  599.      * them using the {@link org.eclipse.jgit.events.ListenerHandle#remove()}
  600.      * method
  601.      *
  602.      * @param listener
  603.      *            the listener
  604.      * @return the handle to the registered listener
  605.      */
  606.     public ListenerHandle addChangeListener(ConfigChangedListener listener) {
  607.         return listeners.addConfigChangedListener(listener);
  608.     }

  609.     /**
  610.      * Determine whether to issue change events for transient changes.
  611.      * <p>
  612.      * If <code>true</code> is returned (which is the default behavior),
  613.      * {@link #fireConfigChangedEvent()} will be called upon each change.
  614.      * <p>
  615.      * Subclasses that override this to return <code>false</code> are
  616.      * responsible for issuing {@link #fireConfigChangedEvent()} calls
  617.      * themselves.
  618.      *
  619.      * @return <code></code>
  620.      */
  621.     protected boolean notifyUponTransientChanges() {
  622.         return true;
  623.     }

  624.     /**
  625.      * Notifies the listeners
  626.      */
  627.     protected void fireConfigChangedEvent() {
  628.         listeners.dispatch(new ConfigChangedEvent());
  629.     }

  630.     String getRawString(final String section, final String subsection,
  631.             final String name) {
  632.         String[] lst = getRawStringList(section, subsection, name);
  633.         if (lst != null) {
  634.             return lst[lst.length - 1];
  635.         } else if (baseConfig != null) {
  636.             return baseConfig.getRawString(section, subsection, name);
  637.         } else {
  638.             return null;
  639.         }
  640.     }

  641.     private String[] getRawStringList(String section, String subsection,
  642.             String name) {
  643.         return state.get().get(section, subsection, name);
  644.     }

  645.     private ConfigSnapshot getState() {
  646.         ConfigSnapshot cur, upd;
  647.         do {
  648.             cur = state.get();
  649.             final ConfigSnapshot base = getBaseState();
  650.             if (cur.baseState == base)
  651.                 return cur;
  652.             upd = new ConfigSnapshot(cur.entryList, base);
  653.         } while (!state.compareAndSet(cur, upd));
  654.         return upd;
  655.     }

  656.     private ConfigSnapshot getBaseState() {
  657.         return baseConfig != null ? baseConfig.getState() : null;
  658.     }

  659.     /**
  660.      * Add or modify a configuration value. The parameters will result in a
  661.      * configuration entry like this.
  662.      *
  663.      * <pre>
  664.      * [section &quot;subsection&quot;]
  665.      *         name = value
  666.      * </pre>
  667.      *
  668.      * @param section
  669.      *            section name, e.g "branch"
  670.      * @param subsection
  671.      *            optional subsection value, e.g. a branch name
  672.      * @param name
  673.      *            parameter name, e.g. "filemode"
  674.      * @param value
  675.      *            parameter value
  676.      */
  677.     public void setInt(final String section, final String subsection,
  678.             final String name, final int value) {
  679.         setLong(section, subsection, name, value);
  680.     }

  681.     /**
  682.      * Add or modify a configuration value. The parameters will result in a
  683.      * configuration entry like this.
  684.      *
  685.      * <pre>
  686.      * [section &quot;subsection&quot;]
  687.      *         name = value
  688.      * </pre>
  689.      *
  690.      * @param section
  691.      *            section name, e.g "branch"
  692.      * @param subsection
  693.      *            optional subsection value, e.g. a branch name
  694.      * @param name
  695.      *            parameter name, e.g. "filemode"
  696.      * @param value
  697.      *            parameter value
  698.      */
  699.     public void setLong(final String section, final String subsection,
  700.             final String name, final long value) {
  701.         final String s;

  702.         if (value >= GiB && (value % GiB) == 0)
  703.             s = String.valueOf(value / GiB) + "g"; //$NON-NLS-1$
  704.         else if (value >= MiB && (value % MiB) == 0)
  705.             s = String.valueOf(value / MiB) + "m"; //$NON-NLS-1$
  706.         else if (value >= KiB && (value % KiB) == 0)
  707.             s = String.valueOf(value / KiB) + "k"; //$NON-NLS-1$
  708.         else
  709.             s = String.valueOf(value);

  710.         setString(section, subsection, name, s);
  711.     }

  712.     /**
  713.      * Add or modify a configuration value. The parameters will result in a
  714.      * configuration entry like this.
  715.      *
  716.      * <pre>
  717.      * [section &quot;subsection&quot;]
  718.      *         name = value
  719.      * </pre>
  720.      *
  721.      * @param section
  722.      *            section name, e.g "branch"
  723.      * @param subsection
  724.      *            optional subsection value, e.g. a branch name
  725.      * @param name
  726.      *            parameter name, e.g. "filemode"
  727.      * @param value
  728.      *            parameter value
  729.      */
  730.     public void setBoolean(final String section, final String subsection,
  731.             final String name, final boolean value) {
  732.         setString(section, subsection, name, value ? "true" : "false"); //$NON-NLS-1$ //$NON-NLS-2$
  733.     }

  734.     /**
  735.      * Add or modify a configuration value. The parameters will result in a
  736.      * configuration entry like this.
  737.      *
  738.      * <pre>
  739.      * [section &quot;subsection&quot;]
  740.      *         name = value
  741.      * </pre>
  742.      *
  743.      * @param section
  744.      *            section name, e.g "branch"
  745.      * @param subsection
  746.      *            optional subsection value, e.g. a branch name
  747.      * @param name
  748.      *            parameter name, e.g. "filemode"
  749.      * @param value
  750.      *            parameter value
  751.      */
  752.     public <T extends Enum<?>> void setEnum(final String section,
  753.             final String subsection, final String name, final T value) {
  754.         String n;
  755.         if (value instanceof ConfigEnum)
  756.             n = ((ConfigEnum) value).toConfigValue();
  757.         else
  758.             n = value.name().toLowerCase(Locale.ROOT).replace('_', ' ');
  759.         setString(section, subsection, name, n);
  760.     }

  761.     /**
  762.      * Add or modify a configuration value. The parameters will result in a
  763.      * configuration entry like this.
  764.      *
  765.      * <pre>
  766.      * [section &quot;subsection&quot;]
  767.      *         name = value
  768.      * </pre>
  769.      *
  770.      * @param section
  771.      *            section name, e.g "branch"
  772.      * @param subsection
  773.      *            optional subsection value, e.g. a branch name
  774.      * @param name
  775.      *            parameter name, e.g. "filemode"
  776.      * @param value
  777.      *            parameter value, e.g. "true"
  778.      */
  779.     public void setString(final String section, final String subsection,
  780.             final String name, final String value) {
  781.         setStringList(section, subsection, name, Collections
  782.                 .singletonList(value));
  783.     }

  784.     /**
  785.      * Remove a configuration value.
  786.      *
  787.      * @param section
  788.      *            section name, e.g "branch"
  789.      * @param subsection
  790.      *            optional subsection value, e.g. a branch name
  791.      * @param name
  792.      *            parameter name, e.g. "filemode"
  793.      */
  794.     public void unset(final String section, final String subsection,
  795.             final String name) {
  796.         setStringList(section, subsection, name, Collections
  797.                 .<String> emptyList());
  798.     }

  799.     /**
  800.      * Remove all configuration values under a single section.
  801.      *
  802.      * @param section
  803.      *            section name, e.g "branch"
  804.      * @param subsection
  805.      *            optional subsection value, e.g. a branch name
  806.      */
  807.     public void unsetSection(String section, String subsection) {
  808.         ConfigSnapshot src, res;
  809.         do {
  810.             src = state.get();
  811.             res = unsetSection(src, section, subsection);
  812.         } while (!state.compareAndSet(src, res));
  813.     }

  814.     private ConfigSnapshot unsetSection(final ConfigSnapshot srcState,
  815.             final String section,
  816.             final String subsection) {
  817.         final int max = srcState.entryList.size();
  818.         final ArrayList<ConfigLine> r = new ArrayList<>(max);

  819.         boolean lastWasMatch = false;
  820.         for (ConfigLine e : srcState.entryList) {
  821.             if (e.includedFrom == null && e.match(section, subsection)) {
  822.                 // Skip this record, it's for the section we are removing.
  823.                 lastWasMatch = true;
  824.                 continue;
  825.             }

  826.             if (lastWasMatch && e.section == null && e.subsection == null)
  827.                 continue; // skip this padding line in the section.
  828.             r.add(e);
  829.         }

  830.         return newState(r);
  831.     }

  832.     /**
  833.      * Set a configuration value.
  834.      *
  835.      * <pre>
  836.      * [section &quot;subsection&quot;]
  837.      *         name = value1
  838.      *         name = value2
  839.      * </pre>
  840.      *
  841.      * @param section
  842.      *            section name, e.g "branch"
  843.      * @param subsection
  844.      *            optional subsection value, e.g. a branch name
  845.      * @param name
  846.      *            parameter name, e.g. "filemode"
  847.      * @param values
  848.      *            list of zero or more values for this key.
  849.      */
  850.     public void setStringList(final String section, final String subsection,
  851.             final String name, final List<String> values) {
  852.         ConfigSnapshot src, res;
  853.         do {
  854.             src = state.get();
  855.             res = replaceStringList(src, section, subsection, name, values);
  856.         } while (!state.compareAndSet(src, res));
  857.         if (notifyUponTransientChanges())
  858.             fireConfigChangedEvent();
  859.     }

  860.     private ConfigSnapshot replaceStringList(final ConfigSnapshot srcState,
  861.             final String section, final String subsection, final String name,
  862.             final List<String> values) {
  863.         final List<ConfigLine> entries = copy(srcState, values);
  864.         int entryIndex = 0;
  865.         int valueIndex = 0;
  866.         int insertPosition = -1;

  867.         // Reset the first n Entry objects that match this input name.
  868.         //
  869.         while (entryIndex < entries.size() && valueIndex < values.size()) {
  870.             final ConfigLine e = entries.get(entryIndex);
  871.             if (e.includedFrom == null && e.match(section, subsection, name)) {
  872.                 entries.set(entryIndex, e.forValue(values.get(valueIndex++)));
  873.                 insertPosition = entryIndex + 1;
  874.             }
  875.             entryIndex++;
  876.         }

  877.         // Remove any extra Entry objects that we no longer need.
  878.         //
  879.         if (valueIndex == values.size() && entryIndex < entries.size()) {
  880.             while (entryIndex < entries.size()) {
  881.                 final ConfigLine e = entries.get(entryIndex++);
  882.                 if (e.includedFrom == null
  883.                         && e.match(section, subsection, name))
  884.                     entries.remove(--entryIndex);
  885.             }
  886.         }

  887.         // Insert new Entry objects for additional/new values.
  888.         //
  889.         if (valueIndex < values.size() && entryIndex == entries.size()) {
  890.             if (insertPosition < 0) {
  891.                 // We didn't find a matching key above, but maybe there
  892.                 // is already a section available that matches. Insert
  893.                 // after the last key of that section.
  894.                 //
  895.                 insertPosition = findSectionEnd(entries, section, subsection,
  896.                         true);
  897.             }
  898.             if (insertPosition < 0) {
  899.                 // We didn't find any matching section header for this key,
  900.                 // so we must create a new section header at the end.
  901.                 //
  902.                 final ConfigLine e = new ConfigLine();
  903.                 e.section = section;
  904.                 e.subsection = subsection;
  905.                 entries.add(e);
  906.                 insertPosition = entries.size();
  907.             }
  908.             while (valueIndex < values.size()) {
  909.                 final ConfigLine e = new ConfigLine();
  910.                 e.section = section;
  911.                 e.subsection = subsection;
  912.                 e.name = name;
  913.                 e.value = values.get(valueIndex++);
  914.                 entries.add(insertPosition++, e);
  915.             }
  916.         }

  917.         return newState(entries);
  918.     }

  919.     private static List<ConfigLine> copy(final ConfigSnapshot src,
  920.             final List<String> values) {
  921.         // At worst we need to insert 1 line for each value, plus 1 line
  922.         // for a new section header. Assume that and allocate the space.
  923.         //
  924.         final int max = src.entryList.size() + values.size() + 1;
  925.         final ArrayList<ConfigLine> r = new ArrayList<>(max);
  926.         r.addAll(src.entryList);
  927.         return r;
  928.     }

  929.     private static int findSectionEnd(final List<ConfigLine> entries,
  930.             final String section, final String subsection,
  931.             boolean skipIncludedLines) {
  932.         for (int i = 0; i < entries.size(); i++) {
  933.             ConfigLine e = entries.get(i);
  934.             if (e.includedFrom != null && skipIncludedLines) {
  935.                 continue;
  936.             }

  937.             if (e.match(section, subsection, null)) {
  938.                 i++;
  939.                 while (i < entries.size()) {
  940.                     e = entries.get(i);
  941.                     if (e.match(section, subsection, e.name))
  942.                         i++;
  943.                     else
  944.                         break;
  945.                 }
  946.                 return i;
  947.             }
  948.         }
  949.         return -1;
  950.     }

  951.     /**
  952.      * Get this configuration, formatted as a Git style text file.
  953.      *
  954.      * @return this configuration, formatted as a Git style text file.
  955.      */
  956.     public String toText() {
  957.         final StringBuilder out = new StringBuilder();
  958.         for (ConfigLine e : state.get().entryList) {
  959.             if (e.includedFrom != null)
  960.                 continue;
  961.             if (e.prefix != null)
  962.                 out.append(e.prefix);
  963.             if (e.section != null && e.name == null) {
  964.                 out.append('[');
  965.                 out.append(e.section);
  966.                 if (e.subsection != null) {
  967.                     out.append(' ');
  968.                     String escaped = escapeValue(e.subsection);
  969.                     // make sure to avoid double quotes here
  970.                     boolean quoted = escaped.startsWith("\"") //$NON-NLS-1$
  971.                             && escaped.endsWith("\""); //$NON-NLS-1$
  972.                     if (!quoted)
  973.                         out.append('"');
  974.                     out.append(escaped);
  975.                     if (!quoted)
  976.                         out.append('"');
  977.                 }
  978.                 out.append(']');
  979.             } else if (e.section != null && e.name != null) {
  980.                 if (e.prefix == null || "".equals(e.prefix)) //$NON-NLS-1$
  981.                     out.append('\t');
  982.                 out.append(e.name);
  983.                 if (!isMissing(e.value)) {
  984.                     out.append(" ="); //$NON-NLS-1$
  985.                     if (e.value != null) {
  986.                         out.append(' ');
  987.                         out.append(escapeValue(e.value));
  988.                     }
  989.                 }
  990.                 if (e.suffix != null)
  991.                     out.append(' ');
  992.             }
  993.             if (e.suffix != null)
  994.                 out.append(e.suffix);
  995.             out.append('\n');
  996.         }
  997.         return out.toString();
  998.     }

  999.     /**
  1000.      * Clear this configuration and reset to the contents of the parsed string.
  1001.      *
  1002.      * @param text
  1003.      *            Git style text file listing configuration properties.
  1004.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1005.      *             the text supplied is not formatted correctly. No changes were
  1006.      *             made to {@code this}.
  1007.      */
  1008.     public void fromText(String text) throws ConfigInvalidException {
  1009.         state.set(newState(fromTextRecurse(text, 1, null)));
  1010.     }

  1011.     private List<ConfigLine> fromTextRecurse(String text, int depth,
  1012.             String includedFrom) throws ConfigInvalidException {
  1013.         if (depth > MAX_DEPTH) {
  1014.             throw new ConfigInvalidException(
  1015.                     JGitText.get().tooManyIncludeRecursions);
  1016.         }
  1017.         final List<ConfigLine> newEntries = new ArrayList<>();
  1018.         final StringReader in = new StringReader(text);
  1019.         ConfigLine last = null;
  1020.         ConfigLine e = new ConfigLine();
  1021.         e.includedFrom = includedFrom;
  1022.         for (;;) {
  1023.             int input = in.read();
  1024.             if (-1 == input) {
  1025.                 if (e.section != null)
  1026.                     newEntries.add(e);
  1027.                 break;
  1028.             }

  1029.             final char c = (char) input;
  1030.             if ('\n' == c) {
  1031.                 // End of this entry.
  1032.                 newEntries.add(e);
  1033.                 if (e.section != null)
  1034.                     last = e;
  1035.                 e = new ConfigLine();
  1036.                 e.includedFrom = includedFrom;
  1037.             } else if (e.suffix != null) {
  1038.                 // Everything up until the end-of-line is in the suffix.
  1039.                 e.suffix += c;

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

  1043.             } else if (e.section == null && Character.isWhitespace(c)) {
  1044.                 // Save the leading whitespace (if any).
  1045.                 if (e.prefix == null)
  1046.                     e.prefix = ""; //$NON-NLS-1$
  1047.                 e.prefix += c;

  1048.             } else if ('[' == c) {
  1049.                 // This is a section header.
  1050.                 e.section = readSectionName(in);
  1051.                 input = in.read();
  1052.                 if ('"' == input) {
  1053.                     e.subsection = readSubsectionName(in);
  1054.                     input = in.read();
  1055.                 }
  1056.                 if (']' != input)
  1057.                     throw new ConfigInvalidException(JGitText.get().badGroupHeader);
  1058.                 e.suffix = ""; //$NON-NLS-1$

  1059.             } else if (last != null) {
  1060.                 // Read a value.
  1061.                 e.section = last.section;
  1062.                 e.subsection = last.subsection;
  1063.                 in.reset();
  1064.                 e.name = readKeyName(in);
  1065.                 if (e.name.endsWith("\n")) { //$NON-NLS-1$
  1066.                     e.name = e.name.substring(0, e.name.length() - 1);
  1067.                     e.value = MISSING_ENTRY;
  1068.                 } else
  1069.                     e.value = readValue(in);

  1070.                 if (e.section.equalsIgnoreCase("include")) { //$NON-NLS-1$
  1071.                     addIncludedConfig(newEntries, e, depth);
  1072.                 }
  1073.             } else
  1074.                 throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile);
  1075.         }

  1076.         return newEntries;
  1077.     }

  1078.     /**
  1079.      * Read the included config from the specified (possibly) relative path
  1080.      *
  1081.      * @param relPath
  1082.      *            possibly relative path to the included config, as specified in
  1083.      *            this config
  1084.      * @return the read bytes, or null if the included config should be ignored
  1085.      * @throws org.eclipse.jgit.errors.ConfigInvalidException
  1086.      *             if something went wrong while reading the config
  1087.      * @since 4.10
  1088.      */
  1089.     protected byte[] readIncludedConfig(String relPath)
  1090.             throws ConfigInvalidException {
  1091.         return null;
  1092.     }

  1093.     private void addIncludedConfig(final List<ConfigLine> newEntries,
  1094.             ConfigLine line, int depth) throws ConfigInvalidException {
  1095.         if (!line.name.equalsIgnoreCase("path") || //$NON-NLS-1$
  1096.                 line.value == null || line.value.equals(MISSING_ENTRY)) {
  1097.             throw new ConfigInvalidException(MessageFormat.format(
  1098.                     JGitText.get().invalidLineInConfigFileWithParam, line));
  1099.         }
  1100.         byte[] bytes = readIncludedConfig(line.value);
  1101.         if (bytes == null) {
  1102.             return;
  1103.         }

  1104.         String decoded;
  1105.         if (isUtf8(bytes)) {
  1106.             decoded = RawParseUtils.decode(UTF_8, bytes, 3, bytes.length);
  1107.         } else {
  1108.             decoded = RawParseUtils.decode(bytes);
  1109.         }
  1110.         try {
  1111.             newEntries.addAll(fromTextRecurse(decoded, depth + 1, line.value));
  1112.         } catch (ConfigInvalidException e) {
  1113.             throw new ConfigInvalidException(MessageFormat
  1114.                     .format(JGitText.get().cannotReadFile, line.value), e);
  1115.         }
  1116.     }

  1117.     private ConfigSnapshot newState() {
  1118.         return new ConfigSnapshot(Collections.<ConfigLine> emptyList(),
  1119.                 getBaseState());
  1120.     }

  1121.     private ConfigSnapshot newState(List<ConfigLine> entries) {
  1122.         return new ConfigSnapshot(Collections.unmodifiableList(entries),
  1123.                 getBaseState());
  1124.     }

  1125.     /**
  1126.      * Clear the configuration file
  1127.      */
  1128.     protected void clear() {
  1129.         state.set(newState());
  1130.     }

  1131.     /**
  1132.      * Check if bytes should be treated as UTF-8 or not.
  1133.      *
  1134.      * @param bytes
  1135.      *            the bytes to check encoding for.
  1136.      * @return true if bytes should be treated as UTF-8, false otherwise.
  1137.      * @since 4.4
  1138.      */
  1139.     protected boolean isUtf8(final byte[] bytes) {
  1140.         return bytes.length >= 3 && bytes[0] == (byte) 0xEF
  1141.                 && bytes[1] == (byte) 0xBB && bytes[2] == (byte) 0xBF;
  1142.     }

  1143.     private static String readSectionName(StringReader in)
  1144.             throws ConfigInvalidException {
  1145.         final StringBuilder name = new StringBuilder();
  1146.         for (;;) {
  1147.             int c = in.read();
  1148.             if (c < 0)
  1149.                 throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1150.             if (']' == c) {
  1151.                 in.reset();
  1152.                 break;
  1153.             }

  1154.             if (' ' == c || '\t' == c) {
  1155.                 for (;;) {
  1156.                     c = in.read();
  1157.                     if (c < 0)
  1158.                         throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1159.                     if ('"' == c) {
  1160.                         in.reset();
  1161.                         break;
  1162.                     }

  1163.                     if (' ' == c || '\t' == c)
  1164.                         continue; // Skipped...
  1165.                     throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1166.                 }
  1167.                 break;
  1168.             }

  1169.             if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)
  1170.                 name.append((char) c);
  1171.             else
  1172.                 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badSectionEntry, name));
  1173.         }
  1174.         return name.toString();
  1175.     }

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

  1183.             if ('=' == c)
  1184.                 break;

  1185.             if (' ' == c || '\t' == c) {
  1186.                 for (;;) {
  1187.                     c = in.read();
  1188.                     if (c < 0)
  1189.                         throw new ConfigInvalidException(JGitText.get().unexpectedEndOfConfigFile);

  1190.                     if ('=' == c)
  1191.                         break;

  1192.                     if (';' == c || '#' == c || '\n' == c) {
  1193.                         in.reset();
  1194.                         break;
  1195.                     }

  1196.                     if (' ' == c || '\t' == c)
  1197.                         continue; // Skipped...
  1198.                     throw new ConfigInvalidException(JGitText.get().badEntryDelimiter);
  1199.                 }
  1200.                 break;
  1201.             }

  1202.             if (Character.isLetterOrDigit((char) c) || c == '-') {
  1203.                 // From the git-config man page:
  1204.                 // The variable names are case-insensitive and only
  1205.                 // alphanumeric characters and - are allowed.
  1206.                 name.append((char) c);
  1207.             } else if ('\n' == c) {
  1208.                 in.reset();
  1209.                 name.append((char) c);
  1210.                 break;
  1211.             } else
  1212.                 throw new ConfigInvalidException(MessageFormat.format(JGitText.get().badEntryName, name));
  1213.         }
  1214.         return name.toString();
  1215.     }

  1216.     private static String readSubsectionName(StringReader in)
  1217.             throws ConfigInvalidException {
  1218.         StringBuilder r = new StringBuilder();
  1219.         for (;;) {
  1220.             int c = in.read();
  1221.             if (c < 0) {
  1222.                 break;
  1223.             }

  1224.             if ('\n' == c) {
  1225.                 throw new ConfigInvalidException(
  1226.                         JGitText.get().newlineInQuotesNotAllowed);
  1227.             }
  1228.             if ('\\' == c) {
  1229.                 c = in.read();
  1230.                 switch (c) {
  1231.                 case -1:
  1232.                     throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);

  1233.                 case '\\':
  1234.                 case '"':
  1235.                     r.append((char) c);
  1236.                     continue;

  1237.                 default:
  1238.                     // C git simply drops backslashes if the escape sequence is not
  1239.                     // recognized.
  1240.                     r.append((char) c);
  1241.                     continue;
  1242.                 }
  1243.             }
  1244.             if ('"' == c) {
  1245.                 break;
  1246.             }

  1247.             r.append((char) c);
  1248.         }
  1249.         return r.toString();
  1250.     }

  1251.     private static String readValue(StringReader in)
  1252.             throws ConfigInvalidException {
  1253.         StringBuilder value = new StringBuilder();
  1254.         StringBuilder trailingSpaces = null;
  1255.         boolean quote = false;
  1256.         boolean inLeadingSpace = true;

  1257.         for (;;) {
  1258.             int c = in.read();
  1259.             if (c < 0) {
  1260.                 break;
  1261.             }
  1262.             if ('\n' == c) {
  1263.                 if (quote) {
  1264.                     throw new ConfigInvalidException(
  1265.                             JGitText.get().newlineInQuotesNotAllowed);
  1266.                 }
  1267.                 in.reset();
  1268.                 break;
  1269.             }

  1270.             if (!quote && (';' == c || '#' == c)) {
  1271.                 if (trailingSpaces != null) {
  1272.                     trailingSpaces.setLength(0);
  1273.                 }
  1274.                 in.reset();
  1275.                 break;
  1276.             }

  1277.             char cc = (char) c;
  1278.             if (Character.isWhitespace(cc)) {
  1279.                 if (inLeadingSpace) {
  1280.                     continue;
  1281.                 }
  1282.                 if (trailingSpaces == null) {
  1283.                     trailingSpaces = new StringBuilder();
  1284.                 }
  1285.                 trailingSpaces.append(cc);
  1286.                 continue;
  1287.             }
  1288.             inLeadingSpace = false;
  1289.             if (trailingSpaces != null) {
  1290.                 value.append(trailingSpaces);
  1291.                 trailingSpaces.setLength(0);
  1292.             }

  1293.             if ('\\' == c) {
  1294.                 c = in.read();
  1295.                 switch (c) {
  1296.                 case -1:
  1297.                     throw new ConfigInvalidException(JGitText.get().endOfFileInEscape);
  1298.                 case '\n':
  1299.                     continue;
  1300.                 case 't':
  1301.                     value.append('\t');
  1302.                     continue;
  1303.                 case 'b':
  1304.                     value.append('\b');
  1305.                     continue;
  1306.                 case 'n':
  1307.                     value.append('\n');
  1308.                     continue;
  1309.                 case '\\':
  1310.                     value.append('\\');
  1311.                     continue;
  1312.                 case '"':
  1313.                     value.append('"');
  1314.                     continue;
  1315.                 case '\r': {
  1316.                     int next = in.read();
  1317.                     if (next == '\n') {
  1318.                         continue; // CR-LF
  1319.                     } else if (next >= 0) {
  1320.                         in.reset();
  1321.                     }
  1322.                     break;
  1323.                 }
  1324.                 default:
  1325.                     break;
  1326.                 }
  1327.                 throw new ConfigInvalidException(
  1328.                         MessageFormat.format(JGitText.get().badEscape,
  1329.                                 Character.isAlphabetic(c)
  1330.                                         ? Character.valueOf(((char) c))
  1331.                                         : toUnicodeLiteral(c)));
  1332.             }

  1333.             if ('"' == c) {
  1334.                 quote = !quote;
  1335.                 continue;
  1336.             }

  1337.             value.append(cc);
  1338.         }
  1339.         return value.length() > 0 ? value.toString() : null;
  1340.     }

  1341.     private static String toUnicodeLiteral(int c) {
  1342.         return String.format("\\u%04x", //$NON-NLS-1$
  1343.                 Integer.valueOf(c));
  1344.     }

  1345.     /**
  1346.      * Parses a section of the configuration into an application model object.
  1347.      * <p>
  1348.      * Instances must implement hashCode and equals such that model objects can
  1349.      * be cached by using the {@code SectionParser} as a key of a HashMap.
  1350.      * <p>
  1351.      * As the {@code SectionParser} itself is used as the key of the internal
  1352.      * HashMap applications should be careful to ensure the SectionParser key
  1353.      * does not retain unnecessary application state which may cause memory to
  1354.      * be held longer than expected.
  1355.      *
  1356.      * @param <T>
  1357.      *            type of the application model created by the parser.
  1358.      */
  1359.     public static interface SectionParser<T> {
  1360.         /**
  1361.          * Create a model object from a configuration.
  1362.          *
  1363.          * @param cfg
  1364.          *            the configuration to read values from.
  1365.          * @return the application model instance.
  1366.          */
  1367.         T parse(Config cfg);
  1368.     }

  1369.     private static class StringReader {
  1370.         private final char[] buf;

  1371.         private int pos;

  1372.         StringReader(String in) {
  1373.             buf = in.toCharArray();
  1374.         }

  1375.         int read() {
  1376.             if (pos >= buf.length) {
  1377.                 return -1;
  1378.             }
  1379.             return buf[pos++];
  1380.         }

  1381.         void reset() {
  1382.             pos--;
  1383.         }
  1384.     }

  1385.     /**
  1386.      * Converts enumeration values into configuration options and vice-versa,
  1387.      * allowing to match a config option with an enum value.
  1388.      *
  1389.      */
  1390.     public static interface ConfigEnum {
  1391.         /**
  1392.          * Converts enumeration value into a string to be save in config.
  1393.          *
  1394.          * @return the enum value as config string
  1395.          */
  1396.         String toConfigValue();

  1397.         /**
  1398.          * Checks if the given string matches with enum value.
  1399.          *
  1400.          * @param in
  1401.          *            the string to match
  1402.          * @return true if the given string matches enum value, false otherwise
  1403.          */
  1404.         boolean matchConfigValue(String in);
  1405.     }
  1406. }