Subject: [JGIT PATCH 1/3] Extracted functionality independent from .git/config from RepositoryConfig

From: Constantine Plotnikov <[email-masked]>
Date: Wed, 24 Jun 2009 21:48:03 +0400

RepositoryConfig now contains only .git/config specific logic.
The storage independent logic has been extracted to the class Config,
and the file storage dependent logic was extracted to the class
FileBasedConfig. This organization allows to read files with format
similar to .git/config file from other sources (like blobs in the
repository). The example of the file that also uses the same format
is the file .gitmodules that holds information about submodules.

Signed-off-by: Constantine Plotnikov <[email-masked]>
---
 .../src/org/spearce/jgit/lib/Config.java           | 1013 ++++++++++++++++++++
 .../src/org/spearce/jgit/lib/FileBasedConfig.java  |  120 +++
 .../src/org/spearce/jgit/lib/RepositoryConfig.java |  939 +------------------
 3 files changed, 1143 insertions(+), 929 deletions(-)
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/Config.java
 create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java

diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java
new file mode 100644
index 0000000..62daef3
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java
@@ -0,0 +1,1013 @@
+/*
+ * Copyright (C) 2007, Dave Watson <[email-masked]>
+ * Copyright (C) 2008, Robin Rosenberg <[email-masked]>
+ * Copyright (C) 2008, Shawn O. Pearce <[email-masked]>
+ * Copyright (C) 2008, Thad Hughes <[email-masked]>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.spearce.jgit.lib;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The configuration file stored in the format similar to the ".git/config"
+ * file.
+ */
+public abstract class Config {
+
+	private boolean readFile;
+
+	private List<Entry> entries;
+
+	final Config baseConfig;
+
+	/**
+	 * Map from name to values
+	 */
+	private Map<String, Object> byName;
+
+	/**
+	 * Magic value indicating a missing entry
+	 */
+	private static final String MAGIC_EMPTY_VALUE = "%%magic%%empty%%";
+
+	/**
+	 * The constructor for configuration file
+	 *
+	 * @param base
+	 *            the base configuration file to be consulted when value is
+	 *            missing from this file
+	 */
+	public Config(Config base) {
+		baseConfig = base;
+	}
+
+	/**
+	 * Set file read indicator
+	 *
+	 * @param ok
+	 *            true if file does not need loading
+	 */
+	protected void setFileRead(boolean ok) {
+		readFile = ok;
+	}
+
+	/**
+	 * Escape the value before saving
+	 *
+	 * @param x
+	 *            the value to escape
+	 * @return the escaped value
+	 */
+	protected static String escapeValue(final String x) {
+		boolean inquote = false;
+		int lineStart = 0;
+		final StringBuffer r = new StringBuffer(x.length());
+		for (int k = 0; k < x.length(); k++) {
+			final char c = x.charAt(k);
+			switch (c) {
+			case '\n':
+				if (inquote) {
+					r.append('"');
+					inquote = false;
+				}
+				r.append("\\n\\\n");
+				lineStart = r.length();
+				break;
+
+			case '\t':
+				r.append("\\t");
+				break;
+
+			case '\b':
+				r.append("\\b");
+				break;
+
+			case '\\':
+				r.append("\\\\");
+				break;
+
+			case '"':
+				r.append("\\\"");
+				break;
+
+			case ';':
+			case '#':
+				if (!inquote) {
+					r.insert(lineStart, '"');
+					inquote = true;
+				}
+				r.append(c);
+				break;
+
+			case ' ':
+				if (!inquote && r.length() > 0
+						&& r.charAt(r.length() - 1) == ' ') {
+					r.insert(lineStart, '"');
+					inquote = true;
+				}
+				r.append(' ');
+				break;
+
+			default:
+				r.append(c);
+				break;
+			}
+		}
+		if (inquote) {
+			r.append('"');
+		}
+		return r.toString();
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 */
+	public int getInt(final String section, final String name,
+			final int defaultValue) {
+		return getInt(section, null, name, defaultValue);
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 */
+	public int getInt(final String section, String subsection,
+			final String name, final int defaultValue) {
+		final long val = getLong(section, subsection, name, defaultValue);
+		if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
+			return (int) val;
+		throw new IllegalArgumentException("Integer value " + section + "."
+				+ name + " out of range");
+	}
+
+	/**
+	 * Obtain an integer value from the configuration.
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return an integer value from the configuration, or defaultValue.
+	 */
+	public long getLong(final String section, String subsection,
+			final String name, final long defaultValue) {
+		final String str = getString(section, subsection, name);
+		if (str == null)
+			return defaultValue;
+
+		String n = str.trim();
+		if (n.length() == 0)
+			return defaultValue;
+
+		long mul = 1;
+		switch (Character.toLowerCase(n.charAt(n.length() - 1))) {
+		case 'g':
+			mul = 1024 * 1024 * 1024;
+			break;
+		case 'm':
+			mul = 1024 * 1024;
+			break;
+		case 'k':
+			mul = 1024;
+			break;
+		}
+		if (mul > 1)
+			n = n.substring(0, n.length() - 1).trim();
+		if (n.length() == 0)
+			return defaultValue;
+
+		try {
+			return mul * Long.parseLong(n);
+		} catch (NumberFormatException nfe) {
+			throw new IllegalArgumentException("Invalid integer value: "
+					+ section + "." + name + "=" + str);
+		}
+	}
+
+	/**
+	 * Get a boolean value from the git config
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return true if any value or defaultValue is true, false for missing or
+	 *         explicit false
+	 */
+	public boolean getBoolean(final String section, final String name,
+			final boolean defaultValue) {
+		return getBoolean(section, null, name, defaultValue);
+	}
+
+	/**
+	 * Get a boolean value from the git config
+	 *
+	 * @param section
+	 *            section the key is grouped within.
+	 * @param subsection
+	 *            subsection name, such a remote or branch name.
+	 * @param name
+	 *            name of the key to get.
+	 * @param defaultValue
+	 *            default value to return if no value was present.
+	 * @return true if any value or defaultValue is true, false for missing or
+	 *         explicit false
+	 */
+	public boolean getBoolean(final String section, String subsection,
+			final String name, final boolean defaultValue) {
+		String n = getRawString(section, subsection, name);
+		if (n == null)
+			return defaultValue;
+
+		if (MAGIC_EMPTY_VALUE.equals(n) || "yes".equalsIgnoreCase(n)
+				|| "true".equalsIgnoreCase(n) || "1".equals(n)
+				|| "on".equalsIgnoreCase(n)) {
+			return true;
+
+		} else if ("no".equalsIgnoreCase(n) || "false".equalsIgnoreCase(n)
+				|| "0".equals(n) || "off".equalsIgnoreCase(n)) {
+			return false;
+
+		} else {
+			throw new IllegalArgumentException("Invalid boolean value: "
+					+ section + "." + name + "=" + n);
+		}
+	}
+
+	/**
+	 * Get string value
+	 *
+	 * @param section
+	 *            the section
+	 * @param subsection
+	 *            the subsection for the value
+	 * @param name
+	 *            the key name
+	 * @return a String value from git config.
+	 */
+	public String getString(final String section, String subsection,
+			final String name) {
+		String val = getRawString(section, subsection, name);
+		if (MAGIC_EMPTY_VALUE.equals(val)) {
+			return "";
+		}
+		return val;
+	}
+
+	/**
+	 * Get a list of string values
+	 *
+	 * @param section
+	 *            the section
+	 * @param subsection
+	 *            the subsection for the value
+	 * @param name
+	 *            the key name
+	 * @return array of zero or more values from the configuration.
+	 */
+	public String[] getStringList(final String section, String subsection,
+			final String name) {
+		final Object o = getRawEntry(section, subsection, name);
+		if (o instanceof List) {
+			final List lst = (List) o;
+			final String[] r = new String[lst.size()];
+			for (int i = 0; i < r.length; i++) {
+				final String val = ((Entry) lst.get(i)).value;
+				r[i] = MAGIC_EMPTY_VALUE.equals(val) ? "" : val;
+			}
+			return r;
+		}
+
+		if (o instanceof Entry) {
+			final String val = ((Entry) o).value;
+			return new String[] { MAGIC_EMPTY_VALUE.equals(val) ? "" : val };
+		}
+
+		if (baseConfig != null)
+			return baseConfig.getStringList(section, subsection, name);
+		return new String[0];
+	}
+
+	/**
+	 * @param section
+	 *            section to search for.
+	 * @return set of all subsections of specified section within this
+	 *         configuration and its base configuration; may be empty if no
+	 *         subsection exists.
+	 */
+	public Set<String> getSubsections(final String section) {
+		final Set<String> result = new HashSet<String>();
+
+		for (final Entry e : entries) {
+			if (section.equalsIgnoreCase(e.base) && e.extendedBase != null)
+				result.add(e.extendedBase);
+		}
+		if (baseConfig != null)
+			result.addAll(baseConfig.getSubsections(section));
+		return result;
+	}
+
+	/**
+	 * Get a list of string values
+	 *
+	 * @param section
+	 *            the section
+	 * @param subsection
+	 *            the subsection for the value
+	 * @param name
+	 *            the key name
+	 * @return a raw string value as it is stored in configuration file
+	 */
+	private String getRawString(final String section, final String subsection,
+			final String name) {
+		final Object o = getRawEntry(section, subsection, name);
+		if (o instanceof List) {
+			return ((Entry) ((List) o).get(0)).value;
+		} else if (o instanceof Entry) {
+			return ((Entry) o).value;
+		} else if (baseConfig != null)
+			return baseConfig.getRawString(section, subsection, name);
+		else
+			return null;
+	}
+
+	private Object getRawEntry(final String section, final String subsection,
+			final String name) {
+		if (!readFile) {
+			try {
+				load();
+			} catch (FileNotFoundException err) {
+				// Oh well. No sense in complaining about it.
+				//
+			} catch (IOException err) {
+				err.printStackTrace();
+			}
+		}
+
+		String ss;
+		if (subsection != null)
+			ss = "." + subsection.toLowerCase();
+		else
+			ss = "";
+		final Object o;
+		o = byName.get(section.toLowerCase() + ss + "." + name.toLowerCase());
+		return o;
+	}
+
+	/**
+	 * Add or modify a configuration value. The parameters will result in a
+	 * configuration entry like this.
+	 *
+	 * <pre>
+	 * [section &quot;subsection&quot;]
+	 *         name = value
+	 * </pre>
+	 *
+	 * @param section
+	 *            section name, e.g "branch"
+	 * @param subsection
+	 *            optional subsection value, e.g. a branch name
+	 * @param name
+	 *            parameter name, e.g. "filemode"
+	 * @param value
+	 *            parameter value
+	 */
+	public void setInt(final String section, final String subsection,
+			final String name, final int value) {
+		final String s;
+
+		if ((value % (1024 * 1024 * 1024)) == 0)
+			s = String.valueOf(value / (1024 * 1024 * 1024)) + " g";
+		else if ((value % (1024 * 1024)) == 0)
+			s = String.valueOf(value / (1024 * 1024)) + " m";
+		else if ((value % 1024) == 0)
+			s = String.valueOf(value / 1024) + " k";
+		else
+			s = String.valueOf(value);
+
+		setString(section, subsection, name, s);
+	}
+
+	/**
+	 * Add or modify a configuration value. The parameters will result in a
+	 * configuration entry like this.
+	 *
+	 * <pre>
+	 * [section &quot;subsection&quot;]
+	 *         name = value
+	 * </pre>
+	 *
+	 * @param section
+	 *            section name, e.g "branch"
+	 * @param subsection
+	 *            optional subsection value, e.g. a branch name
+	 * @param name
+	 *            parameter name, e.g. "filemode"
+	 * @param value
+	 *            parameter value
+	 */
+	public void setBoolean(final String section, final String subsection,
+			final String name, final boolean value) {
+		setString(section, subsection, name, value ? "true" : "false");
+	}
+
+	/**
+	 * Add or modify a configuration value. The parameters will result in a
+	 * configuration entry like this.
+	 *
+	 * <pre>
+	 * [section &quot;subsection&quot;]
+	 *         name = value
+	 * </pre>
+	 *
+	 * @param section
+	 *            section name, e.g "branch"
+	 * @param subsection
+	 *            optional subsection value, e.g. a branch name
+	 * @param name
+	 *            parameter name, e.g. "filemode"
+	 * @param value
+	 *            parameter value, e.g. "true"
+	 */
+	public void setString(final String section, final String subsection,
+			final String name, final String value) {
+		setStringList(section, subsection, name, Collections
+				.singletonList(value));
+	}
+
+	/**
+	 * Remove a configuration value.
+	 *
+	 * @param section
+	 *            section name, e.g "branch"
+	 * @param subsection
+	 *            optional subsection value, e.g. a branch name
+	 * @param name
+	 *            parameter name, e.g. "filemode"
+	 */
+	public void unsetString(final String section, final String subsection,
+			final String name) {
+		setStringList(section, subsection, name, Collections
+				.<String> emptyList());
+	}
+
+	/**
+	 * Set a configuration value.
+	 *
+	 * <pre>
+	 * [section &quot;subsection&quot;]
+	 *         name = value
+	 * </pre>
+	 *
+	 * @param section
+	 *            section name, e.g "branch"
+	 * @param subsection
+	 *            optional subsection value, e.g. a branch name
+	 * @param name
+	 *            parameter name, e.g. "filemode"
+	 * @param values
+	 *            list of zero or more values for this key.
+	 */
+	public void setStringList(final String section, final String subsection,
+			final String name, final List<String> values) {
+		// Update our parsed cache of values for future reference.
+		//
+		String key = section.toLowerCase();
+		if (subsection != null)
+			key += "." + subsection.toLowerCase();
+		key += "." + name.toLowerCase();
+		if (values.size() == 0)
+			byName.remove(key);
+		else if (values.size() == 1) {
+			final Entry e = new Entry();
+			e.base = section;
+			e.extendedBase = subsection;
+			e.name = name;
+			e.value = values.get(0);
+			byName.put(key, e);
+		} else {
+			final ArrayList<Entry> eList = new ArrayList<Entry>(values.size());
+			for (final String v : values) {
+				final Entry e = new Entry();
+				e.base = section;
+				e.extendedBase = subsection;
+				e.name = name;
+				e.value = v;
+				eList.add(e);
+			}
+			byName.put(key, eList);
+		}
+
+		int entryIndex = 0;
+		int valueIndex = 0;
+		int insertPosition = -1;
+
+		// Reset the first n Entry objects that match this input name.
+		//
+		while (entryIndex < entries.size() && valueIndex < values.size()) {
+			final Entry e = entries.get(entryIndex++);
+			if (e.match(section, subsection, name)) {
+				e.value = values.get(valueIndex++);
+				insertPosition = entryIndex;
+			}
+		}
+
+		// Remove any extra Entry objects that we no longer need.
+		//
+		if (valueIndex == values.size() && entryIndex < entries.size()) {
+			while (entryIndex < entries.size()) {
+				final Entry e = entries.get(entryIndex++);
+				if (e.match(section, subsection, name))
+					entries.remove(--entryIndex);
+			}
+		}
+
+		// Insert new Entry objects for additional/new values.
+		//
+		if (valueIndex < values.size() && entryIndex == entries.size()) {
+			if (insertPosition < 0) {
+				// We didn't find a matching key above, but maybe there
+				// is already a section available that matches. Insert
+				// after the last key of that section.
+				//
+				insertPosition = findSectionEnd(section, subsection);
+			}
+			if (insertPosition < 0) {
+				// We didn't find any matching section header for this key,
+				// so we must create a new section header at the end.
+				//
+				final Entry e = new Entry();
+				e.prefix = null;
+				e.suffix = null;
+				e.base = section;
+				e.extendedBase = subsection;
+				entries.add(e);
+				insertPosition = entries.size();
+			}
+			while (valueIndex < values.size()) {
+				final Entry e = new Entry();
+				e.prefix = null;
+				e.suffix = null;
+				e.base = section;
+				e.extendedBase = subsection;
+				e.name = name;
+				e.value = values.get(valueIndex++);
+				entries.add(insertPosition++, e);
+			}
+		}
+	}
+
+	private int findSectionEnd(final String section, final String subsection) {
+		for (int i = 0; i < entries.size(); i++) {
+			Entry e = entries.get(i);
+			if (e.match(section, subsection, null)) {
+				i++;
+				while (i < entries.size()) {
+					e = entries.get(i);
+					if (e.match(section, subsection, e.name))
+						i++;
+					else
+						break;
+				}
+				return i;
+			}
+		}
+		return -1;
+	}
+
+	/**
+	 * Print configuration file to the PrintWriter
+	 *
+	 * @param r
+	 *             the print writer (it must use '\n' as new line separator).
+	 */
+	protected void printConfig(final PrintWriter r) {
+		final Iterator<Entry> i = entries.iterator();
+		while (i.hasNext()) {
+			final Entry e = i.next();
+			if (e.prefix != null) {
+				r.print(e.prefix);
+			}
+			if (e.base != null && e.name == null) {
+				r.print('[');
+				r.print(e.base);
+				if (e.extendedBase != null) {
+					r.print(' ');
+					r.print('"');
+					r.print(escapeValue(e.extendedBase));
+					r.print('"');
+				}
+				r.print(']');
+			} else if (e.base != null && e.name != null) {
+				if (e.prefix == null || "".equals(e.prefix)) {
+					r.print('\t');
+				}
+				r.print(e.name);
+				if (e.value != null) {
+					if (!MAGIC_EMPTY_VALUE.equals(e.value)) {
+						r.print(" = ");
+						r.print(escapeValue(e.value));
+					}
+				}
+				if (e.suffix != null) {
+					r.print(' ');
+				}
+			}
+			if (e.suffix != null) {
+				r.print(e.suffix);
+			}
+			r.println();
+		}
+	}
+
+	/**
+	 * Read the config file
+	 *
+	 * @throws IOException in case of IO error
+	 */
+	public void load() throws IOException {
+		clear();
+		readFile = true;
+		final BufferedReader r = new BufferedReader(new InputStreamReader(
+				openInputStream(), Constants.CHARSET));
+		try {
+			Entry last = null;
+			Entry e = new Entry();
+			for (;;) {
+				r.mark(1);
+				int input = r.read();
+				final char in = (char) input;
+				if (-1 == input) {
+					break;
+				} else if ('\n' == in) {
+					// End of this entry.
+					add(e);
+					if (e.base != null) {
+						last = e;
+					}
+					e = new Entry();
+				} else if (e.suffix != null) {
+					// Everything up until the end-of-line is in the suffix.
+					e.suffix += in;
+				} else if (';' == in || '#' == in) {
+					// The rest of this line is a comment; put into suffix.
+					e.suffix = String.valueOf(in);
+				} else if (e.base == null && Character.isWhitespace(in)) {
+					// Save the leading whitespace (if any).
+					if (e.prefix == null) {
+						e.prefix = "";
+					}
+					e.prefix += in;
+				} else if ('[' == in) {
+					// This is a group header line.
+					e.base = readBase(r);
+					input = r.read();
+					if ('"' == input) {
+						e.extendedBase = readValue(r, true, '"');
+						input = r.read();
+					}
+					if (']' != input) {
+						throw new IOException("Bad group header.");
+					}
+					e.suffix = "";
+				} else if (last != null) {
+					// Read a value.
+					e.base = last.base;
+					e.extendedBase = last.extendedBase;
+					r.reset();
+					e.name = readName(r);
+					if (e.name.endsWith("\n")) {
+						e.name = e.name.substring(0, e.name.length() - 1);
+						e.value = MAGIC_EMPTY_VALUE;
+					} else
+						e.value = readValue(r, false, -1);
+				} else {
+					throw new IOException("Invalid line in config file.");
+				}
+			}
+		} finally {
+			r.close();
+		}
+	}
+
+	/**
+	 * Open input stream for configuration file. It is used during the
+	 * {@link #load()} method.
+	 *
+	 * @return input stream for the configuration file.
+	 * @throws IOException
+	 *             if the stream cannot be created
+	 */
+	protected abstract InputStream openInputStream() throws IOException;
+
+	/**
+	 * Clear the configuration file
+	 */
+	protected void clear() {
+		entries = new ArrayList<Entry>();
+		byName = new HashMap<String, Object>();
+	}
+
+	@SuppressWarnings("unchecked")
+	private void add(final Entry e) {
+		entries.add(e);
+		if (e.base != null) {
+			final String b = e.base.toLowerCase();
+			final String group;
+			if (e.extendedBase != null) {
+				group = b + "." + e.extendedBase;
+			} else {
+				group = b;
+			}
+			if (e.name != null) {
+				final String n = e.name.toLowerCase();
+				final String key = group + "." + n;
+				final Object o = byName.get(key);
+				if (o == null) {
+					byName.put(key, e);
+				} else if (o instanceof Entry) {
+					final ArrayList<Object> l = new ArrayList<Object>();
+					l.add(o);
+					l.add(e);
+					byName.put(key, l);
+				} else if (o instanceof List) {
+					((List<Entry>) o).add(e);
+				}
+			}
+		}
+	}
+
+	private static String readBase(final BufferedReader r) throws IOException {
+		final StringBuffer base = new StringBuffer();
+		for (;;) {
+			r.mark(1);
+			int c = r.read();
+			if (c < 0) {
+				throw new IOException("Unexpected end of config file.");
+			} else if (']' == c) {
+				r.reset();
+				break;
+			} else if (' ' == c || '\t' == c) {
+				for (;;) {
+					r.mark(1);
+					c = r.read();
+					if (c < 0) {
+						throw new IOException("Unexpected end of config file.");
+					} else if ('"' == c) {
+						r.reset();
+						break;
+					} else if (' ' == c || '\t' == c) {
+						// Skipped...
+					} else {
+						throw new IOException("Bad base entry. : " + base + ","
+								+ c);
+					}
+				}
+				break;
+			} else if (Character.isLetterOrDigit((char) c) || '.' == c
+					|| '-' == c) {
+				base.append((char) c);
+			} else {
+				throw new IOException("Bad base entry. : " + base + ", " + c);
+			}
+		}
+		return base.toString();
+	}
+
+	private static String readName(final BufferedReader r) throws IOException {
+		final StringBuffer name = new StringBuffer();
+		for (;;) {
+			r.mark(1);
+			int c = r.read();
+			if (c < 0) {
+				throw new IOException("Unexpected end of config file.");
+			} else if ('=' == c) {
+				break;
+			} else if (' ' == c || '\t' == c) {
+				for (;;) {
+					r.mark(1);
+					c = r.read();
+					if (c < 0) {
+						throw new IOException("Unexpected end of config file.");
+					} else if ('=' == c) {
+						break;
+					} else if (';' == c || '#' == c || '\n' == c) {
+						r.reset();
+						break;
+					} else if (' ' == c || '\t' == c) {
+						// Skipped...
+					} else {
+						throw new IOException("Bad entry delimiter.");
+					}
+				}
+				break;
+			} else if (Character.isLetterOrDigit((char) c) || c == '-') {
+				// From the git-config man page:
+				// The variable names are case-insensitive and only
+				// alphanumeric characters and - are allowed.
+				name.append((char) c);
+			} else if ('\n' == c) {
+				r.reset();
+				name.append((char) c);
+				break;
+			} else {
+				throw new IOException("Bad config entry name: " + name
+						+ (char) c);
+			}
+		}
+		return name.toString();
+	}
+
+	private static String readValue(final BufferedReader r, boolean quote,
+			final int eol) throws IOException {
+		final StringBuffer value = new StringBuffer();
+		boolean space = false;
+		for (;;) {
+			r.mark(1);
+			int c = r.read();
+			if (c < 0) {
+				if (value.length() == 0)
+					throw new IOException("Unexpected end of config file.");
+				break;
+			}
+			if ('\n' == c) {
+				if (quote) {
+					throw new IOException("Newline in quotes not allowed.");
+				}
+				r.reset();
+				break;
+			}
+			if (eol == c) {
+				break;
+			}
+			if (!quote) {
+				if (Character.isWhitespace((char) c)) {
+					space = true;
+					continue;
+				}
+				if (';' == c || '#' == c) {
+					r.reset();
+					break;
+				}
+			}
+			if (space) {
+				if (value.length() > 0) {
+					value.append(' ');
+				}
+				space = false;
+			}
+			if ('\\' == c) {
+				c = r.read();
+				switch (c) {
+				case -1:
+					throw new IOException("End of file in escape.");
+				case '\n':
+					continue;
+				case 't':
+					value.append('\t');
+					continue;
+				case 'b':
+					value.append('\b');
+					continue;
+				case 'n':
+					value.append('\n');
+					continue;
+				case '\\':
+					value.append('\\');
+					continue;
+				case '"':
+					value.append('"');
+					continue;
+				default:
+					throw new IOException("Bad escape: " + ((char) c));
+				}
+			}
+			if ('"' == c) {
+				quote = !quote;
+				continue;
+			}
+			value.append((char) c);
+		}
+		return value.length() > 0 ? value.toString() : null;
+	}
+
+	/**
+	 * The configuration file entry
+	 */
+	private static class Entry {
+		/**
+		 * The text content before entry
+		 */
+		String prefix;
+
+		/**
+		 * The section name for the entry
+		 */
+		String base;
+
+		/**
+		 * Subsection name
+		 */
+		String extendedBase;
+
+		/**
+		 * The key name
+		 */
+		String name;
+
+		/**
+		 * The value
+		 */
+		String value;
+
+		/**
+		 * The text content after entry
+		 */
+		String suffix;
+
+		boolean match(final String aBase, final String aExtendedBase,
+				final String aName) {
+			return eq(base, aBase) && eq(extendedBase, aExtendedBase)
+					&& eq(name, aName);
+		}
+
+		private static boolean eq(final String a, final String b) {
+			if (a == null && b == null)
+				return true;
+			if (a == null || b == null)
+				return false;
+			return a.equalsIgnoreCase(b);
+		}
+	}
+
+}
\ No newline at end of file
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java
new file mode 100644
index 0000000..75e88f6
--- /dev/null
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2007, Dave Watson <[email-masked]>
+ * Copyright (C) 2008, Robin Rosenberg <[email-masked]>
+ * Copyright (C) 2008, Shawn O. Pearce <[email-masked]>
+ * Copyright (C) 2008, Thad Hughes <[email-masked]>
+ * Copyright (C) 2009, JetBrains s.r.o.
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Git Development Community nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.spearce.jgit.lib;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+
+/**
+ * The configuration file that is stored in the file of the file system.
+ */
+public class FileBasedConfig extends Config {
+
+	private final File configFile;
+
+	/**
+	 * The constructor
+	 *
+	 * @param base
+	 *            the base configuration file
+	 * @param cfgLocation
+	 *            the location of the configuration file on the file system
+	 */
+	public FileBasedConfig(Config base, File cfgLocation) {
+		super(base);
+		configFile = cfgLocation;
+	}
+
+	/**
+	 * Save config data to the git config file
+	 *
+	 * @throws IOException
+	 */
+	public void save() throws IOException {
+		final File tmp = new File(configFile.getParentFile(), configFile
+				.getName()
+				+ ".lock");
+		final PrintWriter r = new PrintWriter(new BufferedWriter(
+				new OutputStreamWriter(new FileOutputStream(tmp),
+						Constants.CHARSET))) {
+			@Override
+			public void println() {
+				print('\n');
+			}
+		};
+		boolean ok = false;
+		try {
+			printConfig(r);
+			ok = true;
+			r.close();
+			if (!tmp.renameTo(configFile)) {
+				configFile.delete();
+				if (!tmp.renameTo(configFile))
+					throw new IOException("Cannot save config file "
+							+ configFile + ", rename failed");
+			}
+		} finally {
+			r.close();
+			if (tmp.exists() && !tmp.delete()) {
+				System.err
+						.println("(warning) failed to delete tmp config file: "
+								+ tmp);
+			}
+		}
+		setFileRead(ok);
+	}
+
+	@Override
+	protected InputStream openInputStream() throws IOException {
+		return new FileInputStream(configFile);
+	}
+
+	@Override
+	public String toString() {
+		return getClass().getSimpleName() + "[" + configFile.getPath() + "]";
+	}
+}
\ No newline at end of file
diff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
index a339514..a139386 100644
--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java
@@ -3,6 +3,7 @@
  * Copyright (C) 2008, Robin Rosenberg <[email-masked]>
  * Copyright (C) 2008, Shawn O. Pearce <[email-masked]>
  * Copyright (C) 2008, Thad Hughes <[email-masked]>
+ * Copyright (C) 2009, JetBrains s.r.o.
  *
  * All rights reserved.
  *
@@ -40,26 +41,10 @@
 
 package org.spearce.jgit.lib;
 
-import java.io.BufferedReader;
-import java.io.BufferedWriter;
 import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
 import java.io.IOException;
-import java.io.InputStreamReader;
-import java.io.OutputStreamWriter;
-import java.io.PrintWriter;
 import java.net.InetAddress;
 import java.net.UnknownHostException;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 import org.spearce.jgit.util.FS;
 import org.spearce.jgit.util.SystemReader;
@@ -70,7 +55,7 @@
  * This can be either the repository specific file or the user global
  * file depending on how it is instantiated.
  */
-public class RepositoryConfig {
+public class RepositoryConfig extends FileBasedConfig {
 	/**
 	 * Obtain a new configuration instance for ~/.gitconfig.
 	 *
@@ -81,7 +66,6 @@ public static RepositoryConfig openUserConfig() {
 		return systemReader.openUserConfig();
 	}
 
-	private final RepositoryConfig baseConfig;
 
 	/** Section name for a remote configuration */
 	public static final String REMOTE_SECTION = "remote";
@@ -89,22 +73,12 @@ public static RepositoryConfig openUserConfig() {
 	/** Section name for a branch configuration. */
 	public static final String BRANCH_SECTION = "branch";
 
-	private final File configFile;
+	CoreConfig core;
 
-	private boolean readFile;
-
-	private CoreConfig core;
-
-	private TransferConfig transfer;
-
-	private List<Entry> entries;
-
-	private Map<String, Object> byName;
+	TransferConfig transfer;
 
 	private static String hostname;
 
-	private static final String MAGIC_EMPTY_VALUE = "%%magic%%empty%%";
-
 	// default system reader gets the value from the system
 	private static SystemReader systemReader = new SystemReader() {
 		public String getenv(String variable) {
@@ -133,8 +107,7 @@ RepositoryConfig(final Repository repo) {
 	 *            path of the file to load (or save).
 	 */
 	public RepositoryConfig(final RepositoryConfig base, final File cfgLocation) {
-		baseConfig = base;
-		configFile = cfgLocation;
+		super(base, cfgLocation);
 		clear();
 	}
 
@@ -153,210 +126,6 @@ public TransferConfig getTransfer() {
 	}
 
 	/**
-	 * Obtain an integer value from the configuration.
-	 *
-	 * @param section
-	 *            section the key is grouped within.
-	 * @param name
-	 *            name of the key to get.
-	 * @param defaultValue
-	 *            default value to return if no value was present.
-	 * @return an integer value from the configuration, or defaultValue.
-	 */
-	public int getInt(final String section, final String name,
-			final int defaultValue) {
-		return getInt(section, null, name, defaultValue);
-	}
-
-	/**
-	 * Obtain an integer value from the configuration.
-	 *
-	 * @param section
-	 *            section the key is grouped within.
-	 * @param subsection
-	 *            subsection name, such a remote or branch name.
-	 * @param name
-	 *            name of the key to get.
-	 * @param defaultValue
-	 *            default value to return if no value was present.
-	 * @return an integer value from the configuration, or defaultValue.
-	 */
-	public int getInt(final String section, String subsection,
-			final String name, final int defaultValue) {
-		final long val = getLong(section, subsection, name, defaultValue);
-		if (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)
-			return (int) val;
-		throw new IllegalArgumentException("Integer value " + section + "."
-				+ name + " out of range");
-	}
-
-	/**
-	 * Obtain an integer value from the configuration.
-	 *
-	 * @param section
-	 *            section the key is grouped within.
-	 * @param subsection
-	 *            subsection name, such a remote or branch name.
-	 * @param name
-	 *            name of the key to get.
-	 * @param defaultValue
-	 *            default value to return if no value was present.
-	 * @return an integer value from the configuration, or defaultValue.
-	 */
-	public long getLong(final String section, String subsection,
-			final String name, final long defaultValue) {
-		final String str = getString(section, subsection, name);
-		if (str == null)
-			return defaultValue;
-
-		String n = str.trim();
-		if (n.length() == 0)
-			return defaultValue;
-
-		long mul = 1;
-		switch (Character.toLowerCase(n.charAt(n.length() - 1))) {
-		case 'g':
-			mul = 1024 * 1024 * 1024;
-			break;
-		case 'm':
-			mul = 1024 * 1024;
-			break;
-		case 'k':
-			mul = 1024;
-			break;
-		}
-		if (mul > 1)
-			n = n.substring(0, n.length() - 1).trim();
-		if (n.length() == 0)
-			return defaultValue;
-
-		try {
-			return mul * Long.parseLong(n);
-		} catch (NumberFormatException nfe) {
-			throw new IllegalArgumentException("Invalid integer value: "
-					+ section + "." + name + "=" + str);
-		}
-	}
-
-	/**
-	 * Get a boolean value from the git config
-	 *
-	 * @param section
-	 *            section the key is grouped within.
-	 * @param name
-	 *            name of the key to get.
-	 * @param defaultValue
-	 *            default value to return if no value was present.
-	 * @return true if any value or defaultValue is true, false for missing or
-	 *         explicit false
-	 */
-	public boolean getBoolean(final String section, final String name,
-			final boolean defaultValue) {
-		return getBoolean(section, null, name, defaultValue);
-	}
-
-	/**
-	 * Get a boolean value from the git config
-	 *
-	 * @param section
-	 *            section the key is grouped within.
-	 * @param subsection
-	 *            subsection name, such a remote or branch name.
-	 * @param name
-	 *            name of the key to get.
-	 * @param defaultValue
-	 *            default value to return if no value was present.
-	 * @return true if any value or defaultValue is true, false for missing or
-	 *         explicit false
-	 */
-	public boolean getBoolean(final String section, String subsection,
-			final String name, final boolean defaultValue) {
-		String n = getRawString(section, subsection, name);
-		if (n == null)
-			return defaultValue;
-
-		if (MAGIC_EMPTY_VALUE.equals(n)
-				|| "yes".equalsIgnoreCase(n)
-				|| "true".equalsIgnoreCase(n)
-				|| "1".equals(n)
-				|| "on".equalsIgnoreCase(n)) {
-			return true;
-
-		} else if ("no".equalsIgnoreCase(n)
-				|| "false".equalsIgnoreCase(n)
-				|| "0".equals(n)
-				|| "off".equalsIgnoreCase(n)) {
-			return false;
-
-		} else {
-			throw new IllegalArgumentException("Invalid boolean value: "
-					+ section + "." + name + "=" + n);
-		}
-	}
-
-	/**
-	 * @param section
-	 * @param subsection
-	 * @param name
-	 * @return a String value from git config.
-	 */
-	public String getString(final String section, String subsection, final String name) {
-		String val = getRawString(section, subsection, name);
-		if (MAGIC_EMPTY_VALUE.equals(val)) {
-			return "";
-		}
-		return val;
-	}
-
-	/**
-	 * @param section
-	 * @param subsection
-	 * @param name
-	 * @return array of zero or more values from the configuration.
-	 */
-	public String[] getStringList(final String section, String subsection,
-			final String name) {
-		final Object o = getRawEntry(section, subsection, name);
-		if (o instanceof List) {
-			final List lst = (List) o;
-			final String[] r = new String[lst.size()];
-			for (int i = 0; i < r.length; i++) {
-				final String val = ((Entry) lst.get(i)).value;
-				r[i] = MAGIC_EMPTY_VALUE.equals(val) ? "" : val;
-			}
-			return r;
-		}
-
-		if (o instanceof Entry) {
-			final String val = ((Entry) o).value;
-			return new String[] { MAGIC_EMPTY_VALUE.equals(val) ? "" : val };
-		}
-
-		if (baseConfig != null)
-			return baseConfig.getStringList(section, subsection, name);
-		return new String[0];
-	}
-
-	/**
-	 * @param section
-	 *            section to search for.
-	 * @return set of all subsections of specified section within this
-	 *         configuration and its base configuration; may be empty if no
-	 *         subsection exists.
-	 */
-	public Set<String> getSubsections(final String section) {
-		final Set<String> result = new HashSet<String>();
-
-		for (final Entry e : entries) {
-			if (section.equalsIgnoreCase(e.base) && e.extendedBase != null)
-				result.add(e.extendedBase);
-		}
-		if (baseConfig != null)
-			result.addAll(baseConfig.getSubsections(section));
-		return result;
-	}
-
-	/**
 	 * @return the author name as defined in the git variables
 	 *         and configurations. If no name could be found, try
 	 *         to use the system user name instead.
@@ -433,713 +202,26 @@ private String getUserEmailInternal(String gitVariableKey) {
 		return email;
 	}
 
-	private String getRawString(final String section, final String subsection,
-			final String name) {
-		final Object o = getRawEntry(section, subsection, name);
-		if (o instanceof List) {
-			return ((Entry) ((List) o).get(0)).value;
-		} else if (o instanceof Entry) {
-			return ((Entry) o).value;
-		} else if (baseConfig != null)
-			return baseConfig.getRawString(section, subsection, name);
-		else
-			return null;
-	}
-
-	private Object getRawEntry(final String section, final String subsection,
-			final String name) {
-		if (!readFile) {
-			try {
-				load();
-			} catch (FileNotFoundException err) {
-				// Oh well. No sense in complaining about it.
-				//
-			} catch (IOException err) {
-				err.printStackTrace();
-			}
-		}
-
-		String ss;
-		if (subsection != null)
-			ss = "."+subsection.toLowerCase();
-		else
-			ss = "";
-		final Object o;
-		o = byName.get(section.toLowerCase() + ss + "." + name.toLowerCase());
-		return o;
-	}
-
-	/**
-	 * Add or modify a configuration value. The parameters will result in a
-	 * configuration entry like this.
-	 *
-	 * <pre>
-	 * [section &quot;subsection&quot;]
-	 *         name = value
-	 * </pre>
-	 *
-	 * @param section
-	 *            section name, e.g "branch"
-	 * @param subsection
-	 *            optional subsection value, e.g. a branch name
-	 * @param name
-	 *            parameter name, e.g. "filemode"
-	 * @param value
-	 *            parameter value
-	 */
-	public void setInt(final String section, final String subsection,
-			final String name, final int value) {
-		final String s;
-
-		if ((value % (1024 * 1024 * 1024)) == 0)
-			s = String.valueOf(value / (1024 * 1024 * 1024)) + " g";
-		else if ((value % (1024 * 1024)) == 0)
-			s = String.valueOf(value / (1024 * 1024)) + " m";
-		else if ((value % 1024) == 0)
-			s = String.valueOf(value / 1024) + " k";
-		else
-			s = String.valueOf(value);
-
-		setString(section, subsection, name, s);
-	}
-
-	/**
-	 * Add or modify a configuration value. The parameters will result in a
-	 * configuration entry like this.
-	 *
-	 * <pre>
-	 * [section &quot;subsection&quot;]
-	 *         name = value
-	 * </pre>
-	 *
-	 * @param section
-	 *            section name, e.g "branch"
-	 * @param subsection
-	 *            optional subsection value, e.g. a branch name
-	 * @param name
-	 *            parameter name, e.g. "filemode"
-	 * @param value
-	 *            parameter value
-	 */
-	public void setBoolean(final String section, final String subsection,
-			final String name, final boolean value) {
-		setString(section, subsection, name, value ? "true" : "false");
-	}
-
-	/**
-	 * Add or modify a configuration value. The parameters will result in a
-	 * configuration entry like this.
-	 *
-	 * <pre>
-	 * [section &quot;subsection&quot;]
-	 *         name = value
-	 * </pre>
-	 *
-	 * @param section
-	 *            section name, e.g "branch"
-	 * @param subsection
-	 *            optional subsection value, e.g. a branch name
-	 * @param name
-	 *            parameter name, e.g. "filemode"
-	 * @param value
-	 *            parameter value, e.g. "true"
-	 */
-	public void setString(final String section, final String subsection,
-			final String name, final String value) {
-		setStringList(section, subsection, name, Collections
-				.singletonList(value));
-	}
-
-	/**
-	 * Remove a configuration value.
-	 * 
-	 * @param section
-	 *            section name, e.g "branch"
-	 * @param subsection
-	 *            optional subsection value, e.g. a branch name
-	 * @param name
-	 *            parameter name, e.g. "filemode"
-	 */
-	public void unsetString(final String section, final String subsection,
-			final String name) {
-		setStringList(section, subsection, name, Collections
-				.<String> emptyList());
-	}
-
-	/**
-	 * Set a configuration value.
-	 * 
-	 * <pre>
-	 * [section &quot;subsection&quot;]
-	 *         name = value
-	 * </pre>
-	 * 
-	 * @param section
-	 *            section name, e.g "branch"
-	 * @param subsection
-	 *            optional subsection value, e.g. a branch name
-	 * @param name
-	 *            parameter name, e.g. "filemode"
-	 * @param values
-	 *            list of zero or more values for this key.
-	 */
-	public void setStringList(final String section, final String subsection,
-			final String name, final List<String> values) {
-		// Update our parsed cache of values for future reference.
-		//
-		String key = section.toLowerCase();
-		if (subsection != null)
-			key += "." + subsection.toLowerCase();
-		key += "." + name.toLowerCase();
-		if (values.size() == 0)
-			byName.remove(key);
-		else if (values.size() == 1) {
-			final Entry e = new Entry();
-			e.base = section;
-			e.extendedBase = subsection;
-			e.name = name;
-			e.value = values.get(0);
-			byName.put(key, e);
-		} else {
-			final ArrayList<Entry> eList = new ArrayList<Entry>(values.size());
-			for (final String v : values) {
-				final Entry e = new Entry();
-				e.base = section;
-				e.extendedBase = subsection;
-				e.name = name;
-				e.value = v;
-				eList.add(e);
-			}
-			byName.put(key, eList);
-		}
-
-		int entryIndex = 0;
-		int valueIndex = 0;
-		int insertPosition = -1;
-
-		// Reset the first n Entry objects that match this input name.
-		//
-		while (entryIndex < entries.size() && valueIndex < values.size()) {
-			final Entry e = entries.get(entryIndex++);
-			if (e.match(section, subsection, name)) {
-				e.value = values.get(valueIndex++);
-				insertPosition = entryIndex;
-			}
-		}
-
-		// Remove any extra Entry objects that we no longer need.
-		//
-		if (valueIndex == values.size() && entryIndex < entries.size()) {
-			while (entryIndex < entries.size()) {
-				final Entry e = entries.get(entryIndex++);
-				if (e.match(section, subsection, name))
-					entries.remove(--entryIndex);
-			}
-		}
-
-		// Insert new Entry objects for additional/new values.
-		//
-		if (valueIndex < values.size() && entryIndex == entries.size()){
-			if (insertPosition < 0) {
-				// We didn't find a matching key above, but maybe there
-				// is already a section available that matches.  Insert
-				// after the last key of that section.
-				//
-				insertPosition = findSectionEnd(section, subsection);
-			}
-			if (insertPosition < 0) {
-				// We didn't find any matching section header for this key,
-				// so we must create a new section header at the end.
-				//
-				final Entry e = new Entry();
-				e.prefix = null;
-				e.suffix = null;
-				e.base = section;
-				e.extendedBase = subsection;
-				entries.add(e);
-				insertPosition = entries.size();
-			}
-			while (valueIndex < values.size()) {
-				final Entry e = new Entry();
-				e.prefix = null;
-				e.suffix = null;
-				e.base = section;
-				e.extendedBase = subsection;
-				e.name = name;
-				e.value = values.get(valueIndex++);
-				entries.add(insertPosition++, e);
-			}
-		}
-	}
-
-	private int findSectionEnd(final String section, final String subsection) {
-		for (int i = 0; i < entries.size(); i++) {
-			Entry e = entries.get(i);
-			if (e.match(section, subsection, null)) {
-				i++;
-				while (i < entries.size()) {
-					e = entries.get(i);
-					if (e.match(section, subsection, e.name))
-						i++;
-					else
-						break;
-				}
-				return i;
-			}
-		}
-		return -1;
-	}
-
 	/**
 	 * Create a new default config
 	 */
 	public void create() {
-		Entry e;
-
 		clear();
-		readFile = true;
-
-		e = new Entry();
-		e.base = "core";
-		add(e);
-
-		e = new Entry();
-		e.base = "core";
-		e.name = "repositoryformatversion";
-		e.value = "0";
-		add(e);
-
-		e = new Entry();
-		e.base = "core";
-		e.name = "filemode";
-		e.value = "true";
-		add(e);
+		setFileRead(true);
+		setString("core", null, "repositoryformatversion", "0");
+		setString("core", null, "filemode", "true");
 
 		core = new CoreConfig(this);
 		transfer = new TransferConfig(this);
 	}
 
-	/**
-	 * Save config data to the git config file
-	 *
-	 * @throws IOException
-	 */
-	public void save() throws IOException {
-		final File tmp = new File(configFile.getParentFile(), configFile
-				.getName()
-				+ ".lock");
-		final PrintWriter r = new PrintWriter(new BufferedWriter(
-				new OutputStreamWriter(new FileOutputStream(tmp),
-						Constants.CHARSET))) {
-			@Override
-			public void println() {
-				print('\n');
-			}
-		};
-		boolean ok = false;
-		try {
-			final Iterator<Entry> i = entries.iterator();
-			while (i.hasNext()) {
-				final Entry e = i.next();
-				if (e.prefix != null) {
-					r.print(e.prefix);
-				}
-				if (e.base != null && e.name == null) {
-					r.print('[');
-					r.print(e.base);
-					if (e.extendedBase != null) {
-						r.print(' ');
-						r.print('"');
-						r.print(escapeValue(e.extendedBase));
-						r.print('"');
-					}
-					r.print(']');
-				} else if (e.base != null && e.name != null) {
-					if (e.prefix == null || "".equals(e.prefix)) {
-						r.print('\t');
-					}
-					r.print(e.name);
-					if (e.value != null) {
-						if (!MAGIC_EMPTY_VALUE.equals(e.value)) {
-							r.print(" = ");
-							r.print(escapeValue(e.value));
-						}
-					}
-					if (e.suffix != null) {
-						r.print(' ');
-					}
-				}
-				if (e.suffix != null) {
-					r.print(e.suffix);
-				}
-				r.println();
-			}
-			ok = true;
-			r.close();
-			if (!tmp.renameTo(configFile)) {
-				configFile.delete();
-				if (!tmp.renameTo(configFile))
-					throw new IOException("Cannot save config file " + configFile + ", rename failed");
-			}
-		} finally {
-			r.close();
-			if (tmp.exists() && !tmp.delete()) {
-				System.err.println("(warning) failed to delete tmp config file: " + tmp);
-			}
-		}
-		readFile = ok;
-	}
-
-	/**
-	 * Read the config file
-	 * @throws IOException
-	 */
+	@Override
 	public void load() throws IOException {
-		clear();
-		readFile = true;
-		final BufferedReader r = new BufferedReader(new InputStreamReader(
-				new FileInputStream(configFile), Constants.CHARSET));
-		try {
-			Entry last = null;
-			Entry e = new Entry();
-			for (;;) {
-				r.mark(1);
-				int input = r.read();
-				final char in = (char) input;
-				if (-1 == input) {
-					break;
-				} else if ('\n' == in) {
-					// End of this entry.
-					add(e);
-					if (e.base != null) {
-						last = e;
-					}
-					e = new Entry();
-				} else if (e.suffix != null) {
-					// Everything up until the end-of-line is in the suffix.
-					e.suffix += in;
-				} else if (';' == in || '#' == in) {
-					// The rest of this line is a comment; put into suffix.
-					e.suffix = String.valueOf(in);
-				} else if (e.base == null && Character.isWhitespace(in)) {
-					// Save the leading whitespace (if any).
-					if (e.prefix == null) {
-						e.prefix = "";
-					}
-					e.prefix += in;
-				} else if ('[' == in) {
-					// This is a group header line.
-					e.base = readBase(r);
-					input = r.read();
-					if ('"' == input) {
-						e.extendedBase = readValue(r, true, '"');
-						input = r.read();
-					}
-					if (']' != input) {
-						throw new IOException("Bad group header.");
-					}
-					e.suffix = "";
-				} else if (last != null) {
-					// Read a value.
-					e.base = last.base;
-					e.extendedBase = last.extendedBase;
-					r.reset();
-					e.name = readName(r);
-					if (e.name.endsWith("\n")) {
-						e.name = e.name.substring(0, e.name.length()-1);
-						e.value = MAGIC_EMPTY_VALUE;
-					} else 
-						e.value = readValue(r, false, -1);
-				} else {
-					throw new IOException("Invalid line in config file.");
-				}
-			}
-		} finally {
-			r.close();
-		}
-
+		super.load();
 		core = new CoreConfig(this);
 		transfer = new TransferConfig(this);
 	}
 
-	private void clear() {
-		entries = new ArrayList<Entry>();
-		byName = new HashMap<String, Object>();
-	}
-
-	@SuppressWarnings("unchecked")
-	private void add(final Entry e) {
-		entries.add(e);
-		if (e.base != null) {
-			final String b = e.base.toLowerCase();
-			final String group;
-			if (e.extendedBase != null) {
-				group = b + "." + e.extendedBase;
-			} else {
-				group = b;
-			}
-			if (e.name != null) {
-				final String n = e.name.toLowerCase();
-				final String key = group + "." + n;
-				final Object o = byName.get(key);
-				if (o == null) {
-					byName.put(key, e);
-				} else if (o instanceof Entry) {
-					final ArrayList<Object> l = new ArrayList<Object>();
-					l.add(o);
-					l.add(e);
-					byName.put(key, l);
-				} else if (o instanceof List) {
-					((List<Entry>) o).add(e);
-				}
-			}
-		}
-	}
-
-	private static String escapeValue(final String x) {
-		boolean inquote = false;
-		int lineStart = 0;
-		final StringBuffer r = new StringBuffer(x.length());
-		for (int k = 0; k < x.length(); k++) {
-			final char c = x.charAt(k);
-			switch (c) {
-			case '\n':
-				if (inquote) {
-					r.append('"');
-					inquote = false;
-				}
-				r.append("\\n\\\n");
-				lineStart = r.length();
-				break;
-
-			case '\t':
-				r.append("\\t");
-				break;
-
-			case '\b':
-				r.append("\\b");
-				break;
-
-			case '\\':
-				r.append("\\\\");
-				break;
-
-			case '"':
-				r.append("\\\"");
-				break;
-
-			case ';':
-			case '#':
-				if (!inquote) {
-					r.insert(lineStart, '"');
-					inquote = true;
-				}
-				r.append(c);
-				break;
-
-			case ' ':
-				if (!inquote && r.length() > 0
-						&& r.charAt(r.length() - 1) == ' ') {
-					r.insert(lineStart, '"');
-					inquote = true;
-				}
-				r.append(' ');
-				break;
-
-			default:
-				r.append(c);
-				break;
-			}
-		}
-		if (inquote) {
-			r.append('"');
-		}
-		return r.toString();
-	}
-
-	private static String readBase(final BufferedReader r) throws IOException {
-		final StringBuffer base = new StringBuffer();
-		for (;;) {
-			r.mark(1);
-			int c = r.read();
-			if (c < 0) {
-				throw new IOException("Unexpected end of config file.");
-			} else if (']' == c) {
-				r.reset();
-				break;
-			} else if (' ' == c || '\t' == c) {
-				for (;;) {
-					r.mark(1);
-					c = r.read();
-					if (c < 0) {
-						throw new IOException("Unexpected end of config file.");
-					} else if ('"' == c) {
-						r.reset();
-						break;
-					} else if (' ' == c || '\t' == c) {
-						// Skipped...
-					} else {
-						throw new IOException("Bad base entry. : " + base + "," + c);
-					}
-				}
-				break;
-			} else if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c) {
-				base.append((char) c);
-			} else {
-				throw new IOException("Bad base entry. : " + base + ", " + c);
-			}
-		}
-		return base.toString();
-	}
-
-	private static String readName(final BufferedReader r) throws IOException {
-		final StringBuffer name = new StringBuffer();
-		for (;;) {
-			r.mark(1);
-			int c = r.read();
-			if (c < 0) {
-				throw new IOException("Unexpected end of config file.");
-			} else if ('=' == c) {
-				break;
-			} else if (' ' == c || '\t' == c) {
-				for (;;) {
-					r.mark(1);
-					c = r.read();
-					if (c < 0) {
-						throw new IOException("Unexpected end of config file.");
-					} else if ('=' == c) {
-						break;
-					} else if (';' == c || '#' == c || '\n' == c) {
-						r.reset();
-						break;
-					} else if (' ' == c || '\t' == c) {
-						// Skipped...
-					} else {
-						throw new IOException("Bad entry delimiter.");
-					}
-				}
-				break;
-			} else if (Character.isLetterOrDigit((char) c) || c == '-') {
-				// From the git-config man page:
-				//     The variable names are case-insensitive and only
-				//     alphanumeric characters and - are allowed.
-				name.append((char) c);
-			} else if ('\n' == c) {
-				r.reset();
-				name.append((char) c);
-				break;
-			} else {
-				throw new IOException("Bad config entry name: " + name + (char) c);
-			}
-		}
-		return name.toString();
-	}
-
-	private static String readValue(final BufferedReader r, boolean quote,
-			final int eol) throws IOException {
-		final StringBuffer value = new StringBuffer();
-		boolean space = false;
-		for (;;) {
-			r.mark(1);
-			int c = r.read();
-			if (c < 0) {
-				if (value.length() == 0)
-					throw new IOException("Unexpected end of config file.");
-				break;
-			}
-			if ('\n' == c) {
-				if (quote) {
-					throw new IOException("Newline in quotes not allowed.");
-				}
-				r.reset();
-				break;
-			}
-			if (eol == c) {
-				break;
-			}
-			if (!quote) {
-				if (Character.isWhitespace((char) c)) {
-					space = true;
-					continue;
-				}
-				if (';' == c || '#' == c) {
-					r.reset();
-					break;
-				}
-			}
-			if (space) {
-				if (value.length() > 0) {
-					value.append(' ');
-				}
-				space = false;
-			}
-			if ('\\' == c) {
-				c = r.read();
-				switch (c) {
-				case -1:
-					throw new IOException("End of file in escape.");
-				case '\n':
-					continue;
-				case 't':
-					value.append('\t');
-					continue;
-				case 'b':
-					value.append('\b');
-					continue;
-				case 'n':
-					value.append('\n');
-					continue;
-				case '\\':
-					value.append('\\');
-					continue;
-				case '"':
-					value.append('"');
-					continue;
-				default:
-					throw new IOException("Bad escape: " + ((char) c));
-				}
-			}
-			if ('"' == c) {
-				quote = !quote;
-				continue;
-			}
-			value.append((char) c);
-		}
-		return value.length() > 0 ? value.toString() : null;
-	}
-
-	public String toString() {
-		return "RepositoryConfig[" + configFile.getPath() + "]";
-	}
-
-	static class Entry {
-		String prefix;
-
-		String base;
-
-		String extendedBase;
-
-		String name;
-
-		String value;
-
-		String suffix;
-
-		boolean match(final String aBase, final String aExtendedBase,
-				final String aName) {
-			return eq(base, aBase) && eq(extendedBase, aExtendedBase)
-					&& eq(name, aName);
-		}
-
-		private static boolean eq(final String a, final String b) {
-			if (a == null && b == null)
-				return true;
-			if (a == null || b == null)
-				return false;
-			return a.equalsIgnoreCase(b);
-		}
-	}
-
 	/**
 	 * Gets the hostname of the local host.
 	 * If no hostname can be found, the hostname is set to the default value "localhost".
@@ -1166,5 +248,4 @@ private static String getHostname() {
 	public static void setSystemReader(SystemReader newSystemReader) {
 		systemReader = newSystemReader;
 	}
-
 }
-- 
1.6.1.2