{"thread":{"id":"19919","subject":"[JGIT PATCH 0/3] Support for loading .gitmodules from blobs","startedAt":"2009-06-24T17:48:02Z","lastAt":"2009-06-25T00:26:34Z","messageCount":5,"participants":["Constantine Plotnikov","Shawn O. Pearce"],"isPatch":true,"patchVersion":1,"patchTotal":3},"messages":[{"id":"116873","messageId":"1245865685-1288-1-git-send-email-constantine.plotnikov@gmail.com","threadId":"19919","inReplyTo":null,"subject":"[JGIT PATCH 0/3] Support for loading .gitmodules from blobs","fromName":"Constantine Plotnikov","fromEmail":"constantine.plotnikov@gmail.com","sentAt":"2009-06-24T17:48:02Z","receivedAt":"2009-06-24T17:48:02Z","isPatch":true,"sender":{"key":"constantine.plotnikov@gmail.com","avatar":null},"body":"This series of patches is developed to support accessing \n.gitmodules file stored inside the repository. This functionality\nis needed for server-side processing of submodules.\n\nConstantine Plotnikov (3):\n Extracted functionality independent from .git/config from RepositoryConfig\n Config.getSubsections(...) now loads the file if it has not been loaded\n Added BlobBasedConfig that allows accessing configuration files stored in the blobs\n\n .../src/org/spearce/jgit/lib/BlobBasedConfig.java  |  145 +++\n .../src/org/spearce/jgit/lib/Commit.java           |    7 +\n .../src/org/spearce/jgit/lib/Config.java           | 1018 ++++++++++++++++++++\n .../src/org/spearce/jgit/lib/FileBasedConfig.java  |  120 +++\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |  939 +------------------\n .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    2 +-\n 6 files changed, 1301 insertions(+), 930 deletions(-)\n"},{"id":"116876","messageId":"1245865685-1288-2-git-send-email-constantine.plotnikov@gmail.com","threadId":"19919","inReplyTo":"1245865685-1288-1-git-send-email-constantine.plotnikov@gmail.com","subject":"[JGIT PATCH 1/3] Extracted functionality independent from .git/config from RepositoryConfig","fromName":"Constantine Plotnikov","fromEmail":"constantine.plotnikov@gmail.com","sentAt":"2009-06-24T17:48:03Z","receivedAt":"2009-06-24T17:48:03Z","isPatch":true,"sender":{"key":"constantine.plotnikov@gmail.com","avatar":null},"body":"RepositoryConfig now contains only .git/config specific logic.\nThe storage independent logic has been extracted to the class Config,\nand the file storage dependent logic was extracted to the class\nFileBasedConfig. This organization allows to read files with format\nsimilar to .git/config file from other sources (like blobs in the\nrepository). The example of the file that also uses the same format\nis the file .gitmodules that holds information about submodules.\n\nSigned-off-by: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n .../src/org/spearce/jgit/lib/Config.java           | 1013 ++++++++++++++++++++\n .../src/org/spearce/jgit/lib/FileBasedConfig.java  |  120 +++\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |  939 +------------------\n 3 files changed, 1143 insertions(+), 929 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\nnew file mode 100644\nindex 0000000..62daef3\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n@@ -0,0 +1,1013 @@\n+/*\n+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\n+ * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>\n+ * Copyright (C) 2009, JetBrains s.r.o.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+package org.spearce.jgit.lib;\n+\n+import java.io.BufferedReader;\n+import java.io.FileNotFoundException;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.InputStreamReader;\n+import java.io.PrintWriter;\n+import java.util.ArrayList;\n+import java.util.Collections;\n+import java.util.HashMap;\n+import java.util.HashSet;\n+import java.util.Iterator;\n+import java.util.List;\n+import java.util.Map;\n+import java.util.Set;\n+\n+/**\n+ * The configuration file stored in the format similar to the \".git/config\"\n+ * file.\n+ */\n+public abstract class Config {\n+\n+\tprivate boolean readFile;\n+\n+\tprivate List<Entry> entries;\n+\n+\tfinal Config baseConfig;\n+\n+\t/**\n+\t * Map from name to values\n+\t */\n+\tprivate Map<String, Object> byName;\n+\n+\t/**\n+\t * Magic value indicating a missing entry\n+\t */\n+\tprivate static final String MAGIC_EMPTY_VALUE = \"%%magic%%empty%%\";\n+\n+\t/**\n+\t * The constructor for configuration file\n+\t *\n+\t * @param base\n+\t *            the base configuration file to be consulted when value is\n+\t *            missing from this file\n+\t */\n+\tpublic Config(Config base) {\n+\t\tbaseConfig = base;\n+\t}\n+\n+\t/**\n+\t * Set file read indicator\n+\t *\n+\t * @param ok\n+\t *            true if file does not need loading\n+\t */\n+\tprotected void setFileRead(boolean ok) {\n+\t\treadFile = ok;\n+\t}\n+\n+\t/**\n+\t * Escape the value before saving\n+\t *\n+\t * @param x\n+\t *            the value to escape\n+\t * @return the escaped value\n+\t */\n+\tprotected static String escapeValue(final String x) {\n+\t\tboolean inquote = false;\n+\t\tint lineStart = 0;\n+\t\tfinal StringBuffer r = new StringBuffer(x.length());\n+\t\tfor (int k = 0; k < x.length(); k++) {\n+\t\t\tfinal char c = x.charAt(k);\n+\t\t\tswitch (c) {\n+\t\t\tcase '\\n':\n+\t\t\t\tif (inquote) {\n+\t\t\t\t\tr.append('\"');\n+\t\t\t\t\tinquote = false;\n+\t\t\t\t}\n+\t\t\t\tr.append(\"\\\\n\\\\\\n\");\n+\t\t\t\tlineStart = r.length();\n+\t\t\t\tbreak;\n+\n+\t\t\tcase '\\t':\n+\t\t\t\tr.append(\"\\\\t\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase '\\b':\n+\t\t\t\tr.append(\"\\\\b\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase '\\\\':\n+\t\t\t\tr.append(\"\\\\\\\\\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase '\"':\n+\t\t\t\tr.append(\"\\\\\\\"\");\n+\t\t\t\tbreak;\n+\n+\t\t\tcase ';':\n+\t\t\tcase '#':\n+\t\t\t\tif (!inquote) {\n+\t\t\t\t\tr.insert(lineStart, '\"');\n+\t\t\t\t\tinquote = true;\n+\t\t\t\t}\n+\t\t\t\tr.append(c);\n+\t\t\t\tbreak;\n+\n+\t\t\tcase ' ':\n+\t\t\t\tif (!inquote && r.length() > 0\n+\t\t\t\t\t\t&& r.charAt(r.length() - 1) == ' ') {\n+\t\t\t\t\tr.insert(lineStart, '\"');\n+\t\t\t\t\tinquote = true;\n+\t\t\t\t}\n+\t\t\t\tr.append(' ');\n+\t\t\t\tbreak;\n+\n+\t\t\tdefault:\n+\t\t\t\tr.append(c);\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n+\t\tif (inquote) {\n+\t\t\tr.append('\"');\n+\t\t}\n+\t\treturn r.toString();\n+\t}\n+\n+\t/**\n+\t * Obtain an integer value from the configuration.\n+\t *\n+\t * @param section\n+\t *            section the key is grouped within.\n+\t * @param name\n+\t *            name of the key to get.\n+\t * @param defaultValue\n+\t *            default value to return if no value was present.\n+\t * @return an integer value from the configuration, or defaultValue.\n+\t */\n+\tpublic int getInt(final String section, final String name,\n+\t\t\tfinal int defaultValue) {\n+\t\treturn getInt(section, null, name, defaultValue);\n+\t}\n+\n+\t/**\n+\t * Obtain an integer value from the configuration.\n+\t *\n+\t * @param section\n+\t *            section the key is grouped within.\n+\t * @param subsection\n+\t *            subsection name, such a remote or branch name.\n+\t * @param name\n+\t *            name of the key to get.\n+\t * @param defaultValue\n+\t *            default value to return if no value was present.\n+\t * @return an integer value from the configuration, or defaultValue.\n+\t */\n+\tpublic int getInt(final String section, String subsection,\n+\t\t\tfinal String name, final int defaultValue) {\n+\t\tfinal long val = getLong(section, subsection, name, defaultValue);\n+\t\tif (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)\n+\t\t\treturn (int) val;\n+\t\tthrow new IllegalArgumentException(\"Integer value \" + section + \".\"\n+\t\t\t\t+ name + \" out of range\");\n+\t}\n+\n+\t/**\n+\t * Obtain an integer value from the configuration.\n+\t *\n+\t * @param section\n+\t *            section the key is grouped within.\n+\t * @param subsection\n+\t *            subsection name, such a remote or branch name.\n+\t * @param name\n+\t *            name of the key to get.\n+\t * @param defaultValue\n+\t *            default value to return if no value was present.\n+\t * @return an integer value from the configuration, or defaultValue.\n+\t */\n+\tpublic long getLong(final String section, String subsection,\n+\t\t\tfinal String name, final long defaultValue) {\n+\t\tfinal String str = getString(section, subsection, name);\n+\t\tif (str == null)\n+\t\t\treturn defaultValue;\n+\n+\t\tString n = str.trim();\n+\t\tif (n.length() == 0)\n+\t\t\treturn defaultValue;\n+\n+\t\tlong mul = 1;\n+\t\tswitch (Character.toLowerCase(n.charAt(n.length() - 1))) {\n+\t\tcase 'g':\n+\t\t\tmul = 1024 * 1024 * 1024;\n+\t\t\tbreak;\n+\t\tcase 'm':\n+\t\t\tmul = 1024 * 1024;\n+\t\t\tbreak;\n+\t\tcase 'k':\n+\t\t\tmul = 1024;\n+\t\t\tbreak;\n+\t\t}\n+\t\tif (mul > 1)\n+\t\t\tn = n.substring(0, n.length() - 1).trim();\n+\t\tif (n.length() == 0)\n+\t\t\treturn defaultValue;\n+\n+\t\ttry {\n+\t\t\treturn mul * Long.parseLong(n);\n+\t\t} catch (NumberFormatException nfe) {\n+\t\t\tthrow new IllegalArgumentException(\"Invalid integer value: \"\n+\t\t\t\t\t+ section + \".\" + name + \"=\" + str);\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Get a boolean value from the git config\n+\t *\n+\t * @param section\n+\t *            section the key is grouped within.\n+\t * @param name\n+\t *            name of the key to get.\n+\t * @param defaultValue\n+\t *            default value to return if no value was present.\n+\t * @return true if any value or defaultValue is true, false for missing or\n+\t *         explicit false\n+\t */\n+\tpublic boolean getBoolean(final String section, final String name,\n+\t\t\tfinal boolean defaultValue) {\n+\t\treturn getBoolean(section, null, name, defaultValue);\n+\t}\n+\n+\t/**\n+\t * Get a boolean value from the git config\n+\t *\n+\t * @param section\n+\t *            section the key is grouped within.\n+\t * @param subsection\n+\t *            subsection name, such a remote or branch name.\n+\t * @param name\n+\t *            name of the key to get.\n+\t * @param defaultValue\n+\t *            default value to return if no value was present.\n+\t * @return true if any value or defaultValue is true, false for missing or\n+\t *         explicit false\n+\t */\n+\tpublic boolean getBoolean(final String section, String subsection,\n+\t\t\tfinal String name, final boolean defaultValue) {\n+\t\tString n = getRawString(section, subsection, name);\n+\t\tif (n == null)\n+\t\t\treturn defaultValue;\n+\n+\t\tif (MAGIC_EMPTY_VALUE.equals(n) || \"yes\".equalsIgnoreCase(n)\n+\t\t\t\t|| \"true\".equalsIgnoreCase(n) || \"1\".equals(n)\n+\t\t\t\t|| \"on\".equalsIgnoreCase(n)) {\n+\t\t\treturn true;\n+\n+\t\t} else if (\"no\".equalsIgnoreCase(n) || \"false\".equalsIgnoreCase(n)\n+\t\t\t\t|| \"0\".equals(n) || \"off\".equalsIgnoreCase(n)) {\n+\t\t\treturn false;\n+\n+\t\t} else {\n+\t\t\tthrow new IllegalArgumentException(\"Invalid boolean value: \"\n+\t\t\t\t\t+ section + \".\" + name + \"=\" + n);\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Get string value\n+\t *\n+\t * @param section\n+\t *            the section\n+\t * @param subsection\n+\t *            the subsection for the value\n+\t * @param name\n+\t *            the key name\n+\t * @return a String value from git config.\n+\t */\n+\tpublic String getString(final String section, String subsection,\n+\t\t\tfinal String name) {\n+\t\tString val = getRawString(section, subsection, name);\n+\t\tif (MAGIC_EMPTY_VALUE.equals(val)) {\n+\t\t\treturn \"\";\n+\t\t}\n+\t\treturn val;\n+\t}\n+\n+\t/**\n+\t * Get a list of string values\n+\t *\n+\t * @param section\n+\t *            the section\n+\t * @param subsection\n+\t *            the subsection for the value\n+\t * @param name\n+\t *            the key name\n+\t * @return array of zero or more values from the configuration.\n+\t */\n+\tpublic String[] getStringList(final String section, String subsection,\n+\t\t\tfinal String name) {\n+\t\tfinal Object o = getRawEntry(section, subsection, name);\n+\t\tif (o instanceof List) {\n+\t\t\tfinal List lst = (List) o;\n+\t\t\tfinal String[] r = new String[lst.size()];\n+\t\t\tfor (int i = 0; i < r.length; i++) {\n+\t\t\t\tfinal String val = ((Entry) lst.get(i)).value;\n+\t\t\t\tr[i] = MAGIC_EMPTY_VALUE.equals(val) ? \"\" : val;\n+\t\t\t}\n+\t\t\treturn r;\n+\t\t}\n+\n+\t\tif (o instanceof Entry) {\n+\t\t\tfinal String val = ((Entry) o).value;\n+\t\t\treturn new String[] { MAGIC_EMPTY_VALUE.equals(val) ? \"\" : val };\n+\t\t}\n+\n+\t\tif (baseConfig != null)\n+\t\t\treturn baseConfig.getStringList(section, subsection, name);\n+\t\treturn new String[0];\n+\t}\n+\n+\t/**\n+\t * @param section\n+\t *            section to search for.\n+\t * @return set of all subsections of specified section within this\n+\t *         configuration and its base configuration; may be empty if no\n+\t *         subsection exists.\n+\t */\n+\tpublic Set<String> getSubsections(final String section) {\n+\t\tfinal Set<String> result = new HashSet<String>();\n+\n+\t\tfor (final Entry e : entries) {\n+\t\t\tif (section.equalsIgnoreCase(e.base) && e.extendedBase != null)\n+\t\t\t\tresult.add(e.extendedBase);\n+\t\t}\n+\t\tif (baseConfig != null)\n+\t\t\tresult.addAll(baseConfig.getSubsections(section));\n+\t\treturn result;\n+\t}\n+\n+\t/**\n+\t * Get a list of string values\n+\t *\n+\t * @param section\n+\t *            the section\n+\t * @param subsection\n+\t *            the subsection for the value\n+\t * @param name\n+\t *            the key name\n+\t * @return a raw string value as it is stored in configuration file\n+\t */\n+\tprivate String getRawString(final String section, final String subsection,\n+\t\t\tfinal String name) {\n+\t\tfinal Object o = getRawEntry(section, subsection, name);\n+\t\tif (o instanceof List) {\n+\t\t\treturn ((Entry) ((List) o).get(0)).value;\n+\t\t} else if (o instanceof Entry) {\n+\t\t\treturn ((Entry) o).value;\n+\t\t} else if (baseConfig != null)\n+\t\t\treturn baseConfig.getRawString(section, subsection, name);\n+\t\telse\n+\t\t\treturn null;\n+\t}\n+\n+\tprivate Object getRawEntry(final String section, final String subsection,\n+\t\t\tfinal String name) {\n+\t\tif (!readFile) {\n+\t\t\ttry {\n+\t\t\t\tload();\n+\t\t\t} catch (FileNotFoundException err) {\n+\t\t\t\t// Oh well. No sense in complaining about it.\n+\t\t\t\t//\n+\t\t\t} catch (IOException err) {\n+\t\t\t\terr.printStackTrace();\n+\t\t\t}\n+\t\t}\n+\n+\t\tString ss;\n+\t\tif (subsection != null)\n+\t\t\tss = \".\" + subsection.toLowerCase();\n+\t\telse\n+\t\t\tss = \"\";\n+\t\tfinal Object o;\n+\t\to = byName.get(section.toLowerCase() + ss + \".\" + name.toLowerCase());\n+\t\treturn o;\n+\t}\n+\n+\t/**\n+\t * Add or modify a configuration value. The parameters will result in a\n+\t * configuration entry like this.\n+\t *\n+\t * <pre>\n+\t * [section &quot;subsection&quot;]\n+\t *         name = value\n+\t * </pre>\n+\t *\n+\t * @param section\n+\t *            section name, e.g \"branch\"\n+\t * @param subsection\n+\t *            optional subsection value, e.g. a branch name\n+\t * @param name\n+\t *            parameter name, e.g. \"filemode\"\n+\t * @param value\n+\t *            parameter value\n+\t */\n+\tpublic void setInt(final String section, final String subsection,\n+\t\t\tfinal String name, final int value) {\n+\t\tfinal String s;\n+\n+\t\tif ((value % (1024 * 1024 * 1024)) == 0)\n+\t\t\ts = String.valueOf(value / (1024 * 1024 * 1024)) + \" g\";\n+\t\telse if ((value % (1024 * 1024)) == 0)\n+\t\t\ts = String.valueOf(value / (1024 * 1024)) + \" m\";\n+\t\telse if ((value % 1024) == 0)\n+\t\t\ts = String.valueOf(value / 1024) + \" k\";\n+\t\telse\n+\t\t\ts = String.valueOf(value);\n+\n+\t\tsetString(section, subsection, name, s);\n+\t}\n+\n+\t/**\n+\t * Add or modify a configuration value. The parameters will result in a\n+\t * configuration entry like this.\n+\t *\n+\t * <pre>\n+\t * [section &quot;subsection&quot;]\n+\t *         name = value\n+\t * </pre>\n+\t *\n+\t * @param section\n+\t *            section name, e.g \"branch\"\n+\t * @param subsection\n+\t *            optional subsection value, e.g. a branch name\n+\t * @param name\n+\t *            parameter name, e.g. \"filemode\"\n+\t * @param value\n+\t *            parameter value\n+\t */\n+\tpublic void setBoolean(final String section, final String subsection,\n+\t\t\tfinal String name, final boolean value) {\n+\t\tsetString(section, subsection, name, value ? \"true\" : \"false\");\n+\t}\n+\n+\t/**\n+\t * Add or modify a configuration value. The parameters will result in a\n+\t * configuration entry like this.\n+\t *\n+\t * <pre>\n+\t * [section &quot;subsection&quot;]\n+\t *         name = value\n+\t * </pre>\n+\t *\n+\t * @param section\n+\t *            section name, e.g \"branch\"\n+\t * @param subsection\n+\t *            optional subsection value, e.g. a branch name\n+\t * @param name\n+\t *            parameter name, e.g. \"filemode\"\n+\t * @param value\n+\t *            parameter value, e.g. \"true\"\n+\t */\n+\tpublic void setString(final String section, final String subsection,\n+\t\t\tfinal String name, final String value) {\n+\t\tsetStringList(section, subsection, name, Collections\n+\t\t\t\t.singletonList(value));\n+\t}\n+\n+\t/**\n+\t * Remove a configuration value.\n+\t *\n+\t * @param section\n+\t *            section name, e.g \"branch\"\n+\t * @param subsection\n+\t *            optional subsection value, e.g. a branch name\n+\t * @param name\n+\t *            parameter name, e.g. \"filemode\"\n+\t */\n+\tpublic void unsetString(final String section, final String subsection,\n+\t\t\tfinal String name) {\n+\t\tsetStringList(section, subsection, name, Collections\n+\t\t\t\t.<String> emptyList());\n+\t}\n+\n+\t/**\n+\t * Set a configuration value.\n+\t *\n+\t * <pre>\n+\t * [section &quot;subsection&quot;]\n+\t *         name = value\n+\t * </pre>\n+\t *\n+\t * @param section\n+\t *            section name, e.g \"branch\"\n+\t * @param subsection\n+\t *            optional subsection value, e.g. a branch name\n+\t * @param name\n+\t *            parameter name, e.g. \"filemode\"\n+\t * @param values\n+\t *            list of zero or more values for this key.\n+\t */\n+\tpublic void setStringList(final String section, final String subsection,\n+\t\t\tfinal String name, final List<String> values) {\n+\t\t// Update our parsed cache of values for future reference.\n+\t\t//\n+\t\tString key = section.toLowerCase();\n+\t\tif (subsection != null)\n+\t\t\tkey += \".\" + subsection.toLowerCase();\n+\t\tkey += \".\" + name.toLowerCase();\n+\t\tif (values.size() == 0)\n+\t\t\tbyName.remove(key);\n+\t\telse if (values.size() == 1) {\n+\t\t\tfinal Entry e = new Entry();\n+\t\t\te.base = section;\n+\t\t\te.extendedBase = subsection;\n+\t\t\te.name = name;\n+\t\t\te.value = values.get(0);\n+\t\t\tbyName.put(key, e);\n+\t\t} else {\n+\t\t\tfinal ArrayList<Entry> eList = new ArrayList<Entry>(values.size());\n+\t\t\tfor (final String v : values) {\n+\t\t\t\tfinal Entry e = new Entry();\n+\t\t\t\te.base = section;\n+\t\t\t\te.extendedBase = subsection;\n+\t\t\t\te.name = name;\n+\t\t\t\te.value = v;\n+\t\t\t\teList.add(e);\n+\t\t\t}\n+\t\t\tbyName.put(key, eList);\n+\t\t}\n+\n+\t\tint entryIndex = 0;\n+\t\tint valueIndex = 0;\n+\t\tint insertPosition = -1;\n+\n+\t\t// Reset the first n Entry objects that match this input name.\n+\t\t//\n+\t\twhile (entryIndex < entries.size() && valueIndex < values.size()) {\n+\t\t\tfinal Entry e = entries.get(entryIndex++);\n+\t\t\tif (e.match(section, subsection, name)) {\n+\t\t\t\te.value = values.get(valueIndex++);\n+\t\t\t\tinsertPosition = entryIndex;\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Remove any extra Entry objects that we no longer need.\n+\t\t//\n+\t\tif (valueIndex == values.size() && entryIndex < entries.size()) {\n+\t\t\twhile (entryIndex < entries.size()) {\n+\t\t\t\tfinal Entry e = entries.get(entryIndex++);\n+\t\t\t\tif (e.match(section, subsection, name))\n+\t\t\t\t\tentries.remove(--entryIndex);\n+\t\t\t}\n+\t\t}\n+\n+\t\t// Insert new Entry objects for additional/new values.\n+\t\t//\n+\t\tif (valueIndex < values.size() && entryIndex == entries.size()) {\n+\t\t\tif (insertPosition < 0) {\n+\t\t\t\t// We didn't find a matching key above, but maybe there\n+\t\t\t\t// is already a section available that matches. Insert\n+\t\t\t\t// after the last key of that section.\n+\t\t\t\t//\n+\t\t\t\tinsertPosition = findSectionEnd(section, subsection);\n+\t\t\t}\n+\t\t\tif (insertPosition < 0) {\n+\t\t\t\t// We didn't find any matching section header for this key,\n+\t\t\t\t// so we must create a new section header at the end.\n+\t\t\t\t//\n+\t\t\t\tfinal Entry e = new Entry();\n+\t\t\t\te.prefix = null;\n+\t\t\t\te.suffix = null;\n+\t\t\t\te.base = section;\n+\t\t\t\te.extendedBase = subsection;\n+\t\t\t\tentries.add(e);\n+\t\t\t\tinsertPosition = entries.size();\n+\t\t\t}\n+\t\t\twhile (valueIndex < values.size()) {\n+\t\t\t\tfinal Entry e = new Entry();\n+\t\t\t\te.prefix = null;\n+\t\t\t\te.suffix = null;\n+\t\t\t\te.base = section;\n+\t\t\t\te.extendedBase = subsection;\n+\t\t\t\te.name = name;\n+\t\t\t\te.value = values.get(valueIndex++);\n+\t\t\t\tentries.add(insertPosition++, e);\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate int findSectionEnd(final String section, final String subsection) {\n+\t\tfor (int i = 0; i < entries.size(); i++) {\n+\t\t\tEntry e = entries.get(i);\n+\t\t\tif (e.match(section, subsection, null)) {\n+\t\t\t\ti++;\n+\t\t\t\twhile (i < entries.size()) {\n+\t\t\t\t\te = entries.get(i);\n+\t\t\t\t\tif (e.match(section, subsection, e.name))\n+\t\t\t\t\t\ti++;\n+\t\t\t\t\telse\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t\treturn i;\n+\t\t\t}\n+\t\t}\n+\t\treturn -1;\n+\t}\n+\n+\t/**\n+\t * Print configuration file to the PrintWriter\n+\t *\n+\t * @param r\n+\t *             the print writer (it must use '\\n' as new line separator).\n+\t */\n+\tprotected void printConfig(final PrintWriter r) {\n+\t\tfinal Iterator<Entry> i = entries.iterator();\n+\t\twhile (i.hasNext()) {\n+\t\t\tfinal Entry e = i.next();\n+\t\t\tif (e.prefix != null) {\n+\t\t\t\tr.print(e.prefix);\n+\t\t\t}\n+\t\t\tif (e.base != null && e.name == null) {\n+\t\t\t\tr.print('[');\n+\t\t\t\tr.print(e.base);\n+\t\t\t\tif (e.extendedBase != null) {\n+\t\t\t\t\tr.print(' ');\n+\t\t\t\t\tr.print('\"');\n+\t\t\t\t\tr.print(escapeValue(e.extendedBase));\n+\t\t\t\t\tr.print('\"');\n+\t\t\t\t}\n+\t\t\t\tr.print(']');\n+\t\t\t} else if (e.base != null && e.name != null) {\n+\t\t\t\tif (e.prefix == null || \"\".equals(e.prefix)) {\n+\t\t\t\t\tr.print('\\t');\n+\t\t\t\t}\n+\t\t\t\tr.print(e.name);\n+\t\t\t\tif (e.value != null) {\n+\t\t\t\t\tif (!MAGIC_EMPTY_VALUE.equals(e.value)) {\n+\t\t\t\t\t\tr.print(\" = \");\n+\t\t\t\t\t\tr.print(escapeValue(e.value));\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tif (e.suffix != null) {\n+\t\t\t\t\tr.print(' ');\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (e.suffix != null) {\n+\t\t\t\tr.print(e.suffix);\n+\t\t\t}\n+\t\t\tr.println();\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Read the config file\n+\t *\n+\t * @throws IOException in case of IO error\n+\t */\n+\tpublic void load() throws IOException {\n+\t\tclear();\n+\t\treadFile = true;\n+\t\tfinal BufferedReader r = new BufferedReader(new InputStreamReader(\n+\t\t\t\topenInputStream(), Constants.CHARSET));\n+\t\ttry {\n+\t\t\tEntry last = null;\n+\t\t\tEntry e = new Entry();\n+\t\t\tfor (;;) {\n+\t\t\t\tr.mark(1);\n+\t\t\t\tint input = r.read();\n+\t\t\t\tfinal char in = (char) input;\n+\t\t\t\tif (-1 == input) {\n+\t\t\t\t\tbreak;\n+\t\t\t\t} else if ('\\n' == in) {\n+\t\t\t\t\t// End of this entry.\n+\t\t\t\t\tadd(e);\n+\t\t\t\t\tif (e.base != null) {\n+\t\t\t\t\t\tlast = e;\n+\t\t\t\t\t}\n+\t\t\t\t\te = new Entry();\n+\t\t\t\t} else if (e.suffix != null) {\n+\t\t\t\t\t// Everything up until the end-of-line is in the suffix.\n+\t\t\t\t\te.suffix += in;\n+\t\t\t\t} else if (';' == in || '#' == in) {\n+\t\t\t\t\t// The rest of this line is a comment; put into suffix.\n+\t\t\t\t\te.suffix = String.valueOf(in);\n+\t\t\t\t} else if (e.base == null && Character.isWhitespace(in)) {\n+\t\t\t\t\t// Save the leading whitespace (if any).\n+\t\t\t\t\tif (e.prefix == null) {\n+\t\t\t\t\t\te.prefix = \"\";\n+\t\t\t\t\t}\n+\t\t\t\t\te.prefix += in;\n+\t\t\t\t} else if ('[' == in) {\n+\t\t\t\t\t// This is a group header line.\n+\t\t\t\t\te.base = readBase(r);\n+\t\t\t\t\tinput = r.read();\n+\t\t\t\t\tif ('\"' == input) {\n+\t\t\t\t\t\te.extendedBase = readValue(r, true, '\"');\n+\t\t\t\t\t\tinput = r.read();\n+\t\t\t\t\t}\n+\t\t\t\t\tif (']' != input) {\n+\t\t\t\t\t\tthrow new IOException(\"Bad group header.\");\n+\t\t\t\t\t}\n+\t\t\t\t\te.suffix = \"\";\n+\t\t\t\t} else if (last != null) {\n+\t\t\t\t\t// Read a value.\n+\t\t\t\t\te.base = last.base;\n+\t\t\t\t\te.extendedBase = last.extendedBase;\n+\t\t\t\t\tr.reset();\n+\t\t\t\t\te.name = readName(r);\n+\t\t\t\t\tif (e.name.endsWith(\"\\n\")) {\n+\t\t\t\t\t\te.name = e.name.substring(0, e.name.length() - 1);\n+\t\t\t\t\t\te.value = MAGIC_EMPTY_VALUE;\n+\t\t\t\t\t} else\n+\t\t\t\t\t\te.value = readValue(r, false, -1);\n+\t\t\t\t} else {\n+\t\t\t\t\tthrow new IOException(\"Invalid line in config file.\");\n+\t\t\t\t}\n+\t\t\t}\n+\t\t} finally {\n+\t\t\tr.close();\n+\t\t}\n+\t}\n+\n+\t/**\n+\t * Open input stream for configuration file. It is used during the\n+\t * {@link #load()} method.\n+\t *\n+\t * @return input stream for the configuration file.\n+\t * @throws IOException\n+\t *             if the stream cannot be created\n+\t */\n+\tprotected abstract InputStream openInputStream() throws IOException;\n+\n+\t/**\n+\t * Clear the configuration file\n+\t */\n+\tprotected void clear() {\n+\t\tentries = new ArrayList<Entry>();\n+\t\tbyName = new HashMap<String, Object>();\n+\t}\n+\n+\t@SuppressWarnings(\"unchecked\")\n+\tprivate void add(final Entry e) {\n+\t\tentries.add(e);\n+\t\tif (e.base != null) {\n+\t\t\tfinal String b = e.base.toLowerCase();\n+\t\t\tfinal String group;\n+\t\t\tif (e.extendedBase != null) {\n+\t\t\t\tgroup = b + \".\" + e.extendedBase;\n+\t\t\t} else {\n+\t\t\t\tgroup = b;\n+\t\t\t}\n+\t\t\tif (e.name != null) {\n+\t\t\t\tfinal String n = e.name.toLowerCase();\n+\t\t\t\tfinal String key = group + \".\" + n;\n+\t\t\t\tfinal Object o = byName.get(key);\n+\t\t\t\tif (o == null) {\n+\t\t\t\t\tbyName.put(key, e);\n+\t\t\t\t} else if (o instanceof Entry) {\n+\t\t\t\t\tfinal ArrayList<Object> l = new ArrayList<Object>();\n+\t\t\t\t\tl.add(o);\n+\t\t\t\t\tl.add(e);\n+\t\t\t\t\tbyName.put(key, l);\n+\t\t\t\t} else if (o instanceof List) {\n+\t\t\t\t\t((List<Entry>) o).add(e);\n+\t\t\t\t}\n+\t\t\t}\n+\t\t}\n+\t}\n+\n+\tprivate static String readBase(final BufferedReader r) throws IOException {\n+\t\tfinal StringBuffer base = new StringBuffer();\n+\t\tfor (;;) {\n+\t\t\tr.mark(1);\n+\t\t\tint c = r.read();\n+\t\t\tif (c < 0) {\n+\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n+\t\t\t} else if (']' == c) {\n+\t\t\t\tr.reset();\n+\t\t\t\tbreak;\n+\t\t\t} else if (' ' == c || '\\t' == c) {\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\tr.mark(1);\n+\t\t\t\t\tc = r.read();\n+\t\t\t\t\tif (c < 0) {\n+\t\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n+\t\t\t\t\t} else if ('\"' == c) {\n+\t\t\t\t\t\tr.reset();\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t} else if (' ' == c || '\\t' == c) {\n+\t\t\t\t\t\t// Skipped...\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tthrow new IOException(\"Bad base entry. : \" + base + \",\"\n+\t\t\t\t\t\t\t\t+ c);\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tbreak;\n+\t\t\t} else if (Character.isLetterOrDigit((char) c) || '.' == c\n+\t\t\t\t\t|| '-' == c) {\n+\t\t\t\tbase.append((char) c);\n+\t\t\t} else {\n+\t\t\t\tthrow new IOException(\"Bad base entry. : \" + base + \", \" + c);\n+\t\t\t}\n+\t\t}\n+\t\treturn base.toString();\n+\t}\n+\n+\tprivate static String readName(final BufferedReader r) throws IOException {\n+\t\tfinal StringBuffer name = new StringBuffer();\n+\t\tfor (;;) {\n+\t\t\tr.mark(1);\n+\t\t\tint c = r.read();\n+\t\t\tif (c < 0) {\n+\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n+\t\t\t} else if ('=' == c) {\n+\t\t\t\tbreak;\n+\t\t\t} else if (' ' == c || '\\t' == c) {\n+\t\t\t\tfor (;;) {\n+\t\t\t\t\tr.mark(1);\n+\t\t\t\t\tc = r.read();\n+\t\t\t\t\tif (c < 0) {\n+\t\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n+\t\t\t\t\t} else if ('=' == c) {\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t} else if (';' == c || '#' == c || '\\n' == c) {\n+\t\t\t\t\t\tr.reset();\n+\t\t\t\t\t\tbreak;\n+\t\t\t\t\t} else if (' ' == c || '\\t' == c) {\n+\t\t\t\t\t\t// Skipped...\n+\t\t\t\t\t} else {\n+\t\t\t\t\t\tthrow new IOException(\"Bad entry delimiter.\");\n+\t\t\t\t\t}\n+\t\t\t\t}\n+\t\t\t\tbreak;\n+\t\t\t} else if (Character.isLetterOrDigit((char) c) || c == '-') {\n+\t\t\t\t// From the git-config man page:\n+\t\t\t\t// The variable names are case-insensitive and only\n+\t\t\t\t// alphanumeric characters and - are allowed.\n+\t\t\t\tname.append((char) c);\n+\t\t\t} else if ('\\n' == c) {\n+\t\t\t\tr.reset();\n+\t\t\t\tname.append((char) c);\n+\t\t\t\tbreak;\n+\t\t\t} else {\n+\t\t\t\tthrow new IOException(\"Bad config entry name: \" + name\n+\t\t\t\t\t\t+ (char) c);\n+\t\t\t}\n+\t\t}\n+\t\treturn name.toString();\n+\t}\n+\n+\tprivate static String readValue(final BufferedReader r, boolean quote,\n+\t\t\tfinal int eol) throws IOException {\n+\t\tfinal StringBuffer value = new StringBuffer();\n+\t\tboolean space = false;\n+\t\tfor (;;) {\n+\t\t\tr.mark(1);\n+\t\t\tint c = r.read();\n+\t\t\tif (c < 0) {\n+\t\t\t\tif (value.length() == 0)\n+\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif ('\\n' == c) {\n+\t\t\t\tif (quote) {\n+\t\t\t\t\tthrow new IOException(\"Newline in quotes not allowed.\");\n+\t\t\t\t}\n+\t\t\t\tr.reset();\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (eol == c) {\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t\tif (!quote) {\n+\t\t\t\tif (Character.isWhitespace((char) c)) {\n+\t\t\t\t\tspace = true;\n+\t\t\t\t\tcontinue;\n+\t\t\t\t}\n+\t\t\t\tif (';' == c || '#' == c) {\n+\t\t\t\t\tr.reset();\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (space) {\n+\t\t\t\tif (value.length() > 0) {\n+\t\t\t\t\tvalue.append(' ');\n+\t\t\t\t}\n+\t\t\t\tspace = false;\n+\t\t\t}\n+\t\t\tif ('\\\\' == c) {\n+\t\t\t\tc = r.read();\n+\t\t\t\tswitch (c) {\n+\t\t\t\tcase -1:\n+\t\t\t\t\tthrow new IOException(\"End of file in escape.\");\n+\t\t\t\tcase '\\n':\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 't':\n+\t\t\t\t\tvalue.append('\\t');\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'b':\n+\t\t\t\t\tvalue.append('\\b');\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase 'n':\n+\t\t\t\t\tvalue.append('\\n');\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase '\\\\':\n+\t\t\t\t\tvalue.append('\\\\');\n+\t\t\t\t\tcontinue;\n+\t\t\t\tcase '\"':\n+\t\t\t\t\tvalue.append('\"');\n+\t\t\t\t\tcontinue;\n+\t\t\t\tdefault:\n+\t\t\t\t\tthrow new IOException(\"Bad escape: \" + ((char) c));\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif ('\"' == c) {\n+\t\t\t\tquote = !quote;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\tvalue.append((char) c);\n+\t\t}\n+\t\treturn value.length() > 0 ? value.toString() : null;\n+\t}\n+\n+\t/**\n+\t * The configuration file entry\n+\t */\n+\tprivate static class Entry {\n+\t\t/**\n+\t\t * The text content before entry\n+\t\t */\n+\t\tString prefix;\n+\n+\t\t/**\n+\t\t * The section name for the entry\n+\t\t */\n+\t\tString base;\n+\n+\t\t/**\n+\t\t * Subsection name\n+\t\t */\n+\t\tString extendedBase;\n+\n+\t\t/**\n+\t\t * The key name\n+\t\t */\n+\t\tString name;\n+\n+\t\t/**\n+\t\t * The value\n+\t\t */\n+\t\tString value;\n+\n+\t\t/**\n+\t\t * The text content after entry\n+\t\t */\n+\t\tString suffix;\n+\n+\t\tboolean match(final String aBase, final String aExtendedBase,\n+\t\t\t\tfinal String aName) {\n+\t\t\treturn eq(base, aBase) && eq(extendedBase, aExtendedBase)\n+\t\t\t\t\t&& eq(name, aName);\n+\t\t}\n+\n+\t\tprivate static boolean eq(final String a, final String b) {\n+\t\t\tif (a == null && b == null)\n+\t\t\t\treturn true;\n+\t\t\tif (a == null || b == null)\n+\t\t\t\treturn false;\n+\t\t\treturn a.equalsIgnoreCase(b);\n+\t\t}\n+\t}\n+\n+}\n\\ No newline at end of file\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\nnew file mode 100644\nindex 0000000..75e88f6\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\n@@ -0,0 +1,120 @@\n+/*\n+ * Copyright (C) 2007, Dave Watson <dwatson@mimvista.com>\n+ * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n+ * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\n+ * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>\n+ * Copyright (C) 2009, JetBrains s.r.o.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+package org.spearce.jgit.lib;\n+\n+import java.io.BufferedWriter;\n+import java.io.File;\n+import java.io.FileInputStream;\n+import java.io.FileOutputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.io.OutputStreamWriter;\n+import java.io.PrintWriter;\n+\n+/**\n+ * The configuration file that is stored in the file of the file system.\n+ */\n+public class FileBasedConfig extends Config {\n+\n+\tprivate final File configFile;\n+\n+\t/**\n+\t * The constructor\n+\t *\n+\t * @param base\n+\t *            the base configuration file\n+\t * @param cfgLocation\n+\t *            the location of the configuration file on the file system\n+\t */\n+\tpublic FileBasedConfig(Config base, File cfgLocation) {\n+\t\tsuper(base);\n+\t\tconfigFile = cfgLocation;\n+\t}\n+\n+\t/**\n+\t * Save config data to the git config file\n+\t *\n+\t * @throws IOException\n+\t */\n+\tpublic void save() throws IOException {\n+\t\tfinal File tmp = new File(configFile.getParentFile(), configFile\n+\t\t\t\t.getName()\n+\t\t\t\t+ \".lock\");\n+\t\tfinal PrintWriter r = new PrintWriter(new BufferedWriter(\n+\t\t\t\tnew OutputStreamWriter(new FileOutputStream(tmp),\n+\t\t\t\t\t\tConstants.CHARSET))) {\n+\t\t\t@Override\n+\t\t\tpublic void println() {\n+\t\t\t\tprint('\\n');\n+\t\t\t}\n+\t\t};\n+\t\tboolean ok = false;\n+\t\ttry {\n+\t\t\tprintConfig(r);\n+\t\t\tok = true;\n+\t\t\tr.close();\n+\t\t\tif (!tmp.renameTo(configFile)) {\n+\t\t\t\tconfigFile.delete();\n+\t\t\t\tif (!tmp.renameTo(configFile))\n+\t\t\t\t\tthrow new IOException(\"Cannot save config file \"\n+\t\t\t\t\t\t\t+ configFile + \", rename failed\");\n+\t\t\t}\n+\t\t} finally {\n+\t\t\tr.close();\n+\t\t\tif (tmp.exists() && !tmp.delete()) {\n+\t\t\t\tSystem.err\n+\t\t\t\t\t\t.println(\"(warning) failed to delete tmp config file: \"\n+\t\t\t\t\t\t\t\t+ tmp);\n+\t\t\t}\n+\t\t}\n+\t\tsetFileRead(ok);\n+\t}\n+\n+\t@Override\n+\tprotected InputStream openInputStream() throws IOException {\n+\t\treturn new FileInputStream(configFile);\n+\t}\n+\n+\t@Override\n+\tpublic String toString() {\n+\t\treturn getClass().getSimpleName() + \"[\" + configFile.getPath() + \"]\";\n+\t}\n+}\n\\ No newline at end of file\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\nindex a339514..a139386 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n@@ -3,6 +3,7 @@\n  * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com>\n  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>\n  * Copyright (C) 2008, Thad Hughes <thadh@thad.corp.google.com>\n+ * Copyright (C) 2009, JetBrains s.r.o.\n  *\n  * All rights reserved.\n  *\n@@ -40,26 +41,10 @@\n \n package org.spearce.jgit.lib;\n \n-import java.io.BufferedReader;\n-import java.io.BufferedWriter;\n import java.io.File;\n-import java.io.FileInputStream;\n-import java.io.FileNotFoundException;\n-import java.io.FileOutputStream;\n import java.io.IOException;\n-import java.io.InputStreamReader;\n-import java.io.OutputStreamWriter;\n-import java.io.PrintWriter;\n import java.net.InetAddress;\n import java.net.UnknownHostException;\n-import java.util.ArrayList;\n-import java.util.Collections;\n-import java.util.HashMap;\n-import java.util.HashSet;\n-import java.util.Iterator;\n-import java.util.List;\n-import java.util.Map;\n-import java.util.Set;\n \n import org.spearce.jgit.util.FS;\n import org.spearce.jgit.util.SystemReader;\n@@ -70,7 +55,7 @@\n  * This can be either the repository specific file or the user global\n  * file depending on how it is instantiated.\n  */\n-public class RepositoryConfig {\n+public class RepositoryConfig extends FileBasedConfig {\n \t/**\n \t * Obtain a new configuration instance for ~/.gitconfig.\n \t *\n@@ -81,7 +66,6 @@ public static RepositoryConfig openUserConfig() {\n \t\treturn systemReader.openUserConfig();\n \t}\n \n-\tprivate final RepositoryConfig baseConfig;\n \n \t/** Section name for a remote configuration */\n \tpublic static final String REMOTE_SECTION = \"remote\";\n@@ -89,22 +73,12 @@ public static RepositoryConfig openUserConfig() {\n \t/** Section name for a branch configuration. */\n \tpublic static final String BRANCH_SECTION = \"branch\";\n \n-\tprivate final File configFile;\n+\tCoreConfig core;\n \n-\tprivate boolean readFile;\n-\n-\tprivate CoreConfig core;\n-\n-\tprivate TransferConfig transfer;\n-\n-\tprivate List<Entry> entries;\n-\n-\tprivate Map<String, Object> byName;\n+\tTransferConfig transfer;\n \n \tprivate static String hostname;\n \n-\tprivate static final String MAGIC_EMPTY_VALUE = \"%%magic%%empty%%\";\n-\n \t// default system reader gets the value from the system\n \tprivate static SystemReader systemReader = new SystemReader() {\n \t\tpublic String getenv(String variable) {\n@@ -133,8 +107,7 @@ RepositoryConfig(final Repository repo) {\n \t *            path of the file to load (or save).\n \t */\n \tpublic RepositoryConfig(final RepositoryConfig base, final File cfgLocation) {\n-\t\tbaseConfig = base;\n-\t\tconfigFile = cfgLocation;\n+\t\tsuper(base, cfgLocation);\n \t\tclear();\n \t}\n \n@@ -153,210 +126,6 @@ public TransferConfig getTransfer() {\n \t}\n \n \t/**\n-\t * Obtain an integer value from the configuration.\n-\t *\n-\t * @param section\n-\t *            section the key is grouped within.\n-\t * @param name\n-\t *            name of the key to get.\n-\t * @param defaultValue\n-\t *            default value to return if no value was present.\n-\t * @return an integer value from the configuration, or defaultValue.\n-\t */\n-\tpublic int getInt(final String section, final String name,\n-\t\t\tfinal int defaultValue) {\n-\t\treturn getInt(section, null, name, defaultValue);\n-\t}\n-\n-\t/**\n-\t * Obtain an integer value from the configuration.\n-\t *\n-\t * @param section\n-\t *            section the key is grouped within.\n-\t * @param subsection\n-\t *            subsection name, such a remote or branch name.\n-\t * @param name\n-\t *            name of the key to get.\n-\t * @param defaultValue\n-\t *            default value to return if no value was present.\n-\t * @return an integer value from the configuration, or defaultValue.\n-\t */\n-\tpublic int getInt(final String section, String subsection,\n-\t\t\tfinal String name, final int defaultValue) {\n-\t\tfinal long val = getLong(section, subsection, name, defaultValue);\n-\t\tif (Integer.MIN_VALUE <= val && val <= Integer.MAX_VALUE)\n-\t\t\treturn (int) val;\n-\t\tthrow new IllegalArgumentException(\"Integer value \" + section + \".\"\n-\t\t\t\t+ name + \" out of range\");\n-\t}\n-\n-\t/**\n-\t * Obtain an integer value from the configuration.\n-\t *\n-\t * @param section\n-\t *            section the key is grouped within.\n-\t * @param subsection\n-\t *            subsection name, such a remote or branch name.\n-\t * @param name\n-\t *            name of the key to get.\n-\t * @param defaultValue\n-\t *            default value to return if no value was present.\n-\t * @return an integer value from the configuration, or defaultValue.\n-\t */\n-\tpublic long getLong(final String section, String subsection,\n-\t\t\tfinal String name, final long defaultValue) {\n-\t\tfinal String str = getString(section, subsection, name);\n-\t\tif (str == null)\n-\t\t\treturn defaultValue;\n-\n-\t\tString n = str.trim();\n-\t\tif (n.length() == 0)\n-\t\t\treturn defaultValue;\n-\n-\t\tlong mul = 1;\n-\t\tswitch (Character.toLowerCase(n.charAt(n.length() - 1))) {\n-\t\tcase 'g':\n-\t\t\tmul = 1024 * 1024 * 1024;\n-\t\t\tbreak;\n-\t\tcase 'm':\n-\t\t\tmul = 1024 * 1024;\n-\t\t\tbreak;\n-\t\tcase 'k':\n-\t\t\tmul = 1024;\n-\t\t\tbreak;\n-\t\t}\n-\t\tif (mul > 1)\n-\t\t\tn = n.substring(0, n.length() - 1).trim();\n-\t\tif (n.length() == 0)\n-\t\t\treturn defaultValue;\n-\n-\t\ttry {\n-\t\t\treturn mul * Long.parseLong(n);\n-\t\t} catch (NumberFormatException nfe) {\n-\t\t\tthrow new IllegalArgumentException(\"Invalid integer value: \"\n-\t\t\t\t\t+ section + \".\" + name + \"=\" + str);\n-\t\t}\n-\t}\n-\n-\t/**\n-\t * Get a boolean value from the git config\n-\t *\n-\t * @param section\n-\t *            section the key is grouped within.\n-\t * @param name\n-\t *            name of the key to get.\n-\t * @param defaultValue\n-\t *            default value to return if no value was present.\n-\t * @return true if any value or defaultValue is true, false for missing or\n-\t *         explicit false\n-\t */\n-\tpublic boolean getBoolean(final String section, final String name,\n-\t\t\tfinal boolean defaultValue) {\n-\t\treturn getBoolean(section, null, name, defaultValue);\n-\t}\n-\n-\t/**\n-\t * Get a boolean value from the git config\n-\t *\n-\t * @param section\n-\t *            section the key is grouped within.\n-\t * @param subsection\n-\t *            subsection name, such a remote or branch name.\n-\t * @param name\n-\t *            name of the key to get.\n-\t * @param defaultValue\n-\t *            default value to return if no value was present.\n-\t * @return true if any value or defaultValue is true, false for missing or\n-\t *         explicit false\n-\t */\n-\tpublic boolean getBoolean(final String section, String subsection,\n-\t\t\tfinal String name, final boolean defaultValue) {\n-\t\tString n = getRawString(section, subsection, name);\n-\t\tif (n == null)\n-\t\t\treturn defaultValue;\n-\n-\t\tif (MAGIC_EMPTY_VALUE.equals(n)\n-\t\t\t\t|| \"yes\".equalsIgnoreCase(n)\n-\t\t\t\t|| \"true\".equalsIgnoreCase(n)\n-\t\t\t\t|| \"1\".equals(n)\n-\t\t\t\t|| \"on\".equalsIgnoreCase(n)) {\n-\t\t\treturn true;\n-\n-\t\t} else if (\"no\".equalsIgnoreCase(n)\n-\t\t\t\t|| \"false\".equalsIgnoreCase(n)\n-\t\t\t\t|| \"0\".equals(n)\n-\t\t\t\t|| \"off\".equalsIgnoreCase(n)) {\n-\t\t\treturn false;\n-\n-\t\t} else {\n-\t\t\tthrow new IllegalArgumentException(\"Invalid boolean value: \"\n-\t\t\t\t\t+ section + \".\" + name + \"=\" + n);\n-\t\t}\n-\t}\n-\n-\t/**\n-\t * @param section\n-\t * @param subsection\n-\t * @param name\n-\t * @return a String value from git config.\n-\t */\n-\tpublic String getString(final String section, String subsection, final String name) {\n-\t\tString val = getRawString(section, subsection, name);\n-\t\tif (MAGIC_EMPTY_VALUE.equals(val)) {\n-\t\t\treturn \"\";\n-\t\t}\n-\t\treturn val;\n-\t}\n-\n-\t/**\n-\t * @param section\n-\t * @param subsection\n-\t * @param name\n-\t * @return array of zero or more values from the configuration.\n-\t */\n-\tpublic String[] getStringList(final String section, String subsection,\n-\t\t\tfinal String name) {\n-\t\tfinal Object o = getRawEntry(section, subsection, name);\n-\t\tif (o instanceof List) {\n-\t\t\tfinal List lst = (List) o;\n-\t\t\tfinal String[] r = new String[lst.size()];\n-\t\t\tfor (int i = 0; i < r.length; i++) {\n-\t\t\t\tfinal String val = ((Entry) lst.get(i)).value;\n-\t\t\t\tr[i] = MAGIC_EMPTY_VALUE.equals(val) ? \"\" : val;\n-\t\t\t}\n-\t\t\treturn r;\n-\t\t}\n-\n-\t\tif (o instanceof Entry) {\n-\t\t\tfinal String val = ((Entry) o).value;\n-\t\t\treturn new String[] { MAGIC_EMPTY_VALUE.equals(val) ? \"\" : val };\n-\t\t}\n-\n-\t\tif (baseConfig != null)\n-\t\t\treturn baseConfig.getStringList(section, subsection, name);\n-\t\treturn new String[0];\n-\t}\n-\n-\t/**\n-\t * @param section\n-\t *            section to search for.\n-\t * @return set of all subsections of specified section within this\n-\t *         configuration and its base configuration; may be empty if no\n-\t *         subsection exists.\n-\t */\n-\tpublic Set<String> getSubsections(final String section) {\n-\t\tfinal Set<String> result = new HashSet<String>();\n-\n-\t\tfor (final Entry e : entries) {\n-\t\t\tif (section.equalsIgnoreCase(e.base) && e.extendedBase != null)\n-\t\t\t\tresult.add(e.extendedBase);\n-\t\t}\n-\t\tif (baseConfig != null)\n-\t\t\tresult.addAll(baseConfig.getSubsections(section));\n-\t\treturn result;\n-\t}\n-\n-\t/**\n \t * @return the author name as defined in the git variables\n \t *         and configurations. If no name could be found, try\n \t *         to use the system user name instead.\n@@ -433,713 +202,26 @@ private String getUserEmailInternal(String gitVariableKey) {\n \t\treturn email;\n \t}\n \n-\tprivate String getRawString(final String section, final String subsection,\n-\t\t\tfinal String name) {\n-\t\tfinal Object o = getRawEntry(section, subsection, name);\n-\t\tif (o instanceof List) {\n-\t\t\treturn ((Entry) ((List) o).get(0)).value;\n-\t\t} else if (o instanceof Entry) {\n-\t\t\treturn ((Entry) o).value;\n-\t\t} else if (baseConfig != null)\n-\t\t\treturn baseConfig.getRawString(section, subsection, name);\n-\t\telse\n-\t\t\treturn null;\n-\t}\n-\n-\tprivate Object getRawEntry(final String section, final String subsection,\n-\t\t\tfinal String name) {\n-\t\tif (!readFile) {\n-\t\t\ttry {\n-\t\t\t\tload();\n-\t\t\t} catch (FileNotFoundException err) {\n-\t\t\t\t// Oh well. No sense in complaining about it.\n-\t\t\t\t//\n-\t\t\t} catch (IOException err) {\n-\t\t\t\terr.printStackTrace();\n-\t\t\t}\n-\t\t}\n-\n-\t\tString ss;\n-\t\tif (subsection != null)\n-\t\t\tss = \".\"+subsection.toLowerCase();\n-\t\telse\n-\t\t\tss = \"\";\n-\t\tfinal Object o;\n-\t\to = byName.get(section.toLowerCase() + ss + \".\" + name.toLowerCase());\n-\t\treturn o;\n-\t}\n-\n-\t/**\n-\t * Add or modify a configuration value. The parameters will result in a\n-\t * configuration entry like this.\n-\t *\n-\t * <pre>\n-\t * [section &quot;subsection&quot;]\n-\t *         name = value\n-\t * </pre>\n-\t *\n-\t * @param section\n-\t *            section name, e.g \"branch\"\n-\t * @param subsection\n-\t *            optional subsection value, e.g. a branch name\n-\t * @param name\n-\t *            parameter name, e.g. \"filemode\"\n-\t * @param value\n-\t *            parameter value\n-\t */\n-\tpublic void setInt(final String section, final String subsection,\n-\t\t\tfinal String name, final int value) {\n-\t\tfinal String s;\n-\n-\t\tif ((value % (1024 * 1024 * 1024)) == 0)\n-\t\t\ts = String.valueOf(value / (1024 * 1024 * 1024)) + \" g\";\n-\t\telse if ((value % (1024 * 1024)) == 0)\n-\t\t\ts = String.valueOf(value / (1024 * 1024)) + \" m\";\n-\t\telse if ((value % 1024) == 0)\n-\t\t\ts = String.valueOf(value / 1024) + \" k\";\n-\t\telse\n-\t\t\ts = String.valueOf(value);\n-\n-\t\tsetString(section, subsection, name, s);\n-\t}\n-\n-\t/**\n-\t * Add or modify a configuration value. The parameters will result in a\n-\t * configuration entry like this.\n-\t *\n-\t * <pre>\n-\t * [section &quot;subsection&quot;]\n-\t *         name = value\n-\t * </pre>\n-\t *\n-\t * @param section\n-\t *            section name, e.g \"branch\"\n-\t * @param subsection\n-\t *            optional subsection value, e.g. a branch name\n-\t * @param name\n-\t *            parameter name, e.g. \"filemode\"\n-\t * @param value\n-\t *            parameter value\n-\t */\n-\tpublic void setBoolean(final String section, final String subsection,\n-\t\t\tfinal String name, final boolean value) {\n-\t\tsetString(section, subsection, name, value ? \"true\" : \"false\");\n-\t}\n-\n-\t/**\n-\t * Add or modify a configuration value. The parameters will result in a\n-\t * configuration entry like this.\n-\t *\n-\t * <pre>\n-\t * [section &quot;subsection&quot;]\n-\t *         name = value\n-\t * </pre>\n-\t *\n-\t * @param section\n-\t *            section name, e.g \"branch\"\n-\t * @param subsection\n-\t *            optional subsection value, e.g. a branch name\n-\t * @param name\n-\t *            parameter name, e.g. \"filemode\"\n-\t * @param value\n-\t *            parameter value, e.g. \"true\"\n-\t */\n-\tpublic void setString(final String section, final String subsection,\n-\t\t\tfinal String name, final String value) {\n-\t\tsetStringList(section, subsection, name, Collections\n-\t\t\t\t.singletonList(value));\n-\t}\n-\n-\t/**\n-\t * Remove a configuration value.\n-\t * \n-\t * @param section\n-\t *            section name, e.g \"branch\"\n-\t * @param subsection\n-\t *            optional subsection value, e.g. a branch name\n-\t * @param name\n-\t *            parameter name, e.g. \"filemode\"\n-\t */\n-\tpublic void unsetString(final String section, final String subsection,\n-\t\t\tfinal String name) {\n-\t\tsetStringList(section, subsection, name, Collections\n-\t\t\t\t.<String> emptyList());\n-\t}\n-\n-\t/**\n-\t * Set a configuration value.\n-\t * \n-\t * <pre>\n-\t * [section &quot;subsection&quot;]\n-\t *         name = value\n-\t * </pre>\n-\t * \n-\t * @param section\n-\t *            section name, e.g \"branch\"\n-\t * @param subsection\n-\t *            optional subsection value, e.g. a branch name\n-\t * @param name\n-\t *            parameter name, e.g. \"filemode\"\n-\t * @param values\n-\t *            list of zero or more values for this key.\n-\t */\n-\tpublic void setStringList(final String section, final String subsection,\n-\t\t\tfinal String name, final List<String> values) {\n-\t\t// Update our parsed cache of values for future reference.\n-\t\t//\n-\t\tString key = section.toLowerCase();\n-\t\tif (subsection != null)\n-\t\t\tkey += \".\" + subsection.toLowerCase();\n-\t\tkey += \".\" + name.toLowerCase();\n-\t\tif (values.size() == 0)\n-\t\t\tbyName.remove(key);\n-\t\telse if (values.size() == 1) {\n-\t\t\tfinal Entry e = new Entry();\n-\t\t\te.base = section;\n-\t\t\te.extendedBase = subsection;\n-\t\t\te.name = name;\n-\t\t\te.value = values.get(0);\n-\t\t\tbyName.put(key, e);\n-\t\t} else {\n-\t\t\tfinal ArrayList<Entry> eList = new ArrayList<Entry>(values.size());\n-\t\t\tfor (final String v : values) {\n-\t\t\t\tfinal Entry e = new Entry();\n-\t\t\t\te.base = section;\n-\t\t\t\te.extendedBase = subsection;\n-\t\t\t\te.name = name;\n-\t\t\t\te.value = v;\n-\t\t\t\teList.add(e);\n-\t\t\t}\n-\t\t\tbyName.put(key, eList);\n-\t\t}\n-\n-\t\tint entryIndex = 0;\n-\t\tint valueIndex = 0;\n-\t\tint insertPosition = -1;\n-\n-\t\t// Reset the first n Entry objects that match this input name.\n-\t\t//\n-\t\twhile (entryIndex < entries.size() && valueIndex < values.size()) {\n-\t\t\tfinal Entry e = entries.get(entryIndex++);\n-\t\t\tif (e.match(section, subsection, name)) {\n-\t\t\t\te.value = values.get(valueIndex++);\n-\t\t\t\tinsertPosition = entryIndex;\n-\t\t\t}\n-\t\t}\n-\n-\t\t// Remove any extra Entry objects that we no longer need.\n-\t\t//\n-\t\tif (valueIndex == values.size() && entryIndex < entries.size()) {\n-\t\t\twhile (entryIndex < entries.size()) {\n-\t\t\t\tfinal Entry e = entries.get(entryIndex++);\n-\t\t\t\tif (e.match(section, subsection, name))\n-\t\t\t\t\tentries.remove(--entryIndex);\n-\t\t\t}\n-\t\t}\n-\n-\t\t// Insert new Entry objects for additional/new values.\n-\t\t//\n-\t\tif (valueIndex < values.size() && entryIndex == entries.size()){\n-\t\t\tif (insertPosition < 0) {\n-\t\t\t\t// We didn't find a matching key above, but maybe there\n-\t\t\t\t// is already a section available that matches.  Insert\n-\t\t\t\t// after the last key of that section.\n-\t\t\t\t//\n-\t\t\t\tinsertPosition = findSectionEnd(section, subsection);\n-\t\t\t}\n-\t\t\tif (insertPosition < 0) {\n-\t\t\t\t// We didn't find any matching section header for this key,\n-\t\t\t\t// so we must create a new section header at the end.\n-\t\t\t\t//\n-\t\t\t\tfinal Entry e = new Entry();\n-\t\t\t\te.prefix = null;\n-\t\t\t\te.suffix = null;\n-\t\t\t\te.base = section;\n-\t\t\t\te.extendedBase = subsection;\n-\t\t\t\tentries.add(e);\n-\t\t\t\tinsertPosition = entries.size();\n-\t\t\t}\n-\t\t\twhile (valueIndex < values.size()) {\n-\t\t\t\tfinal Entry e = new Entry();\n-\t\t\t\te.prefix = null;\n-\t\t\t\te.suffix = null;\n-\t\t\t\te.base = section;\n-\t\t\t\te.extendedBase = subsection;\n-\t\t\t\te.name = name;\n-\t\t\t\te.value = values.get(valueIndex++);\n-\t\t\t\tentries.add(insertPosition++, e);\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tprivate int findSectionEnd(final String section, final String subsection) {\n-\t\tfor (int i = 0; i < entries.size(); i++) {\n-\t\t\tEntry e = entries.get(i);\n-\t\t\tif (e.match(section, subsection, null)) {\n-\t\t\t\ti++;\n-\t\t\t\twhile (i < entries.size()) {\n-\t\t\t\t\te = entries.get(i);\n-\t\t\t\t\tif (e.match(section, subsection, e.name))\n-\t\t\t\t\t\ti++;\n-\t\t\t\t\telse\n-\t\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t\treturn i;\n-\t\t\t}\n-\t\t}\n-\t\treturn -1;\n-\t}\n-\n \t/**\n \t * Create a new default config\n \t */\n \tpublic void create() {\n-\t\tEntry e;\n-\n \t\tclear();\n-\t\treadFile = true;\n-\n-\t\te = new Entry();\n-\t\te.base = \"core\";\n-\t\tadd(e);\n-\n-\t\te = new Entry();\n-\t\te.base = \"core\";\n-\t\te.name = \"repositoryformatversion\";\n-\t\te.value = \"0\";\n-\t\tadd(e);\n-\n-\t\te = new Entry();\n-\t\te.base = \"core\";\n-\t\te.name = \"filemode\";\n-\t\te.value = \"true\";\n-\t\tadd(e);\n+\t\tsetFileRead(true);\n+\t\tsetString(\"core\", null, \"repositoryformatversion\", \"0\");\n+\t\tsetString(\"core\", null, \"filemode\", \"true\");\n \n \t\tcore = new CoreConfig(this);\n \t\ttransfer = new TransferConfig(this);\n \t}\n \n-\t/**\n-\t * Save config data to the git config file\n-\t *\n-\t * @throws IOException\n-\t */\n-\tpublic void save() throws IOException {\n-\t\tfinal File tmp = new File(configFile.getParentFile(), configFile\n-\t\t\t\t.getName()\n-\t\t\t\t+ \".lock\");\n-\t\tfinal PrintWriter r = new PrintWriter(new BufferedWriter(\n-\t\t\t\tnew OutputStreamWriter(new FileOutputStream(tmp),\n-\t\t\t\t\t\tConstants.CHARSET))) {\n-\t\t\t@Override\n-\t\t\tpublic void println() {\n-\t\t\t\tprint('\\n');\n-\t\t\t}\n-\t\t};\n-\t\tboolean ok = false;\n-\t\ttry {\n-\t\t\tfinal Iterator<Entry> i = entries.iterator();\n-\t\t\twhile (i.hasNext()) {\n-\t\t\t\tfinal Entry e = i.next();\n-\t\t\t\tif (e.prefix != null) {\n-\t\t\t\t\tr.print(e.prefix);\n-\t\t\t\t}\n-\t\t\t\tif (e.base != null && e.name == null) {\n-\t\t\t\t\tr.print('[');\n-\t\t\t\t\tr.print(e.base);\n-\t\t\t\t\tif (e.extendedBase != null) {\n-\t\t\t\t\t\tr.print(' ');\n-\t\t\t\t\t\tr.print('\"');\n-\t\t\t\t\t\tr.print(escapeValue(e.extendedBase));\n-\t\t\t\t\t\tr.print('\"');\n-\t\t\t\t\t}\n-\t\t\t\t\tr.print(']');\n-\t\t\t\t} else if (e.base != null && e.name != null) {\n-\t\t\t\t\tif (e.prefix == null || \"\".equals(e.prefix)) {\n-\t\t\t\t\t\tr.print('\\t');\n-\t\t\t\t\t}\n-\t\t\t\t\tr.print(e.name);\n-\t\t\t\t\tif (e.value != null) {\n-\t\t\t\t\t\tif (!MAGIC_EMPTY_VALUE.equals(e.value)) {\n-\t\t\t\t\t\t\tr.print(\" = \");\n-\t\t\t\t\t\t\tr.print(escapeValue(e.value));\n-\t\t\t\t\t\t}\n-\t\t\t\t\t}\n-\t\t\t\t\tif (e.suffix != null) {\n-\t\t\t\t\t\tr.print(' ');\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\t\t\t\tif (e.suffix != null) {\n-\t\t\t\t\tr.print(e.suffix);\n-\t\t\t\t}\n-\t\t\t\tr.println();\n-\t\t\t}\n-\t\t\tok = true;\n-\t\t\tr.close();\n-\t\t\tif (!tmp.renameTo(configFile)) {\n-\t\t\t\tconfigFile.delete();\n-\t\t\t\tif (!tmp.renameTo(configFile))\n-\t\t\t\t\tthrow new IOException(\"Cannot save config file \" + configFile + \", rename failed\");\n-\t\t\t}\n-\t\t} finally {\n-\t\t\tr.close();\n-\t\t\tif (tmp.exists() && !tmp.delete()) {\n-\t\t\t\tSystem.err.println(\"(warning) failed to delete tmp config file: \" + tmp);\n-\t\t\t}\n-\t\t}\n-\t\treadFile = ok;\n-\t}\n-\n-\t/**\n-\t * Read the config file\n-\t * @throws IOException\n-\t */\n+\t@Override\n \tpublic void load() throws IOException {\n-\t\tclear();\n-\t\treadFile = true;\n-\t\tfinal BufferedReader r = new BufferedReader(new InputStreamReader(\n-\t\t\t\tnew FileInputStream(configFile), Constants.CHARSET));\n-\t\ttry {\n-\t\t\tEntry last = null;\n-\t\t\tEntry e = new Entry();\n-\t\t\tfor (;;) {\n-\t\t\t\tr.mark(1);\n-\t\t\t\tint input = r.read();\n-\t\t\t\tfinal char in = (char) input;\n-\t\t\t\tif (-1 == input) {\n-\t\t\t\t\tbreak;\n-\t\t\t\t} else if ('\\n' == in) {\n-\t\t\t\t\t// End of this entry.\n-\t\t\t\t\tadd(e);\n-\t\t\t\t\tif (e.base != null) {\n-\t\t\t\t\t\tlast = e;\n-\t\t\t\t\t}\n-\t\t\t\t\te = new Entry();\n-\t\t\t\t} else if (e.suffix != null) {\n-\t\t\t\t\t// Everything up until the end-of-line is in the suffix.\n-\t\t\t\t\te.suffix += in;\n-\t\t\t\t} else if (';' == in || '#' == in) {\n-\t\t\t\t\t// The rest of this line is a comment; put into suffix.\n-\t\t\t\t\te.suffix = String.valueOf(in);\n-\t\t\t\t} else if (e.base == null && Character.isWhitespace(in)) {\n-\t\t\t\t\t// Save the leading whitespace (if any).\n-\t\t\t\t\tif (e.prefix == null) {\n-\t\t\t\t\t\te.prefix = \"\";\n-\t\t\t\t\t}\n-\t\t\t\t\te.prefix += in;\n-\t\t\t\t} else if ('[' == in) {\n-\t\t\t\t\t// This is a group header line.\n-\t\t\t\t\te.base = readBase(r);\n-\t\t\t\t\tinput = r.read();\n-\t\t\t\t\tif ('\"' == input) {\n-\t\t\t\t\t\te.extendedBase = readValue(r, true, '\"');\n-\t\t\t\t\t\tinput = r.read();\n-\t\t\t\t\t}\n-\t\t\t\t\tif (']' != input) {\n-\t\t\t\t\t\tthrow new IOException(\"Bad group header.\");\n-\t\t\t\t\t}\n-\t\t\t\t\te.suffix = \"\";\n-\t\t\t\t} else if (last != null) {\n-\t\t\t\t\t// Read a value.\n-\t\t\t\t\te.base = last.base;\n-\t\t\t\t\te.extendedBase = last.extendedBase;\n-\t\t\t\t\tr.reset();\n-\t\t\t\t\te.name = readName(r);\n-\t\t\t\t\tif (e.name.endsWith(\"\\n\")) {\n-\t\t\t\t\t\te.name = e.name.substring(0, e.name.length()-1);\n-\t\t\t\t\t\te.value = MAGIC_EMPTY_VALUE;\n-\t\t\t\t\t} else \n-\t\t\t\t\t\te.value = readValue(r, false, -1);\n-\t\t\t\t} else {\n-\t\t\t\t\tthrow new IOException(\"Invalid line in config file.\");\n-\t\t\t\t}\n-\t\t\t}\n-\t\t} finally {\n-\t\t\tr.close();\n-\t\t}\n-\n+\t\tsuper.load();\n \t\tcore = new CoreConfig(this);\n \t\ttransfer = new TransferConfig(this);\n \t}\n \n-\tprivate void clear() {\n-\t\tentries = new ArrayList<Entry>();\n-\t\tbyName = new HashMap<String, Object>();\n-\t}\n-\n-\t@SuppressWarnings(\"unchecked\")\n-\tprivate void add(final Entry e) {\n-\t\tentries.add(e);\n-\t\tif (e.base != null) {\n-\t\t\tfinal String b = e.base.toLowerCase();\n-\t\t\tfinal String group;\n-\t\t\tif (e.extendedBase != null) {\n-\t\t\t\tgroup = b + \".\" + e.extendedBase;\n-\t\t\t} else {\n-\t\t\t\tgroup = b;\n-\t\t\t}\n-\t\t\tif (e.name != null) {\n-\t\t\t\tfinal String n = e.name.toLowerCase();\n-\t\t\t\tfinal String key = group + \".\" + n;\n-\t\t\t\tfinal Object o = byName.get(key);\n-\t\t\t\tif (o == null) {\n-\t\t\t\t\tbyName.put(key, e);\n-\t\t\t\t} else if (o instanceof Entry) {\n-\t\t\t\t\tfinal ArrayList<Object> l = new ArrayList<Object>();\n-\t\t\t\t\tl.add(o);\n-\t\t\t\t\tl.add(e);\n-\t\t\t\t\tbyName.put(key, l);\n-\t\t\t\t} else if (o instanceof List) {\n-\t\t\t\t\t((List<Entry>) o).add(e);\n-\t\t\t\t}\n-\t\t\t}\n-\t\t}\n-\t}\n-\n-\tprivate static String escapeValue(final String x) {\n-\t\tboolean inquote = false;\n-\t\tint lineStart = 0;\n-\t\tfinal StringBuffer r = new StringBuffer(x.length());\n-\t\tfor (int k = 0; k < x.length(); k++) {\n-\t\t\tfinal char c = x.charAt(k);\n-\t\t\tswitch (c) {\n-\t\t\tcase '\\n':\n-\t\t\t\tif (inquote) {\n-\t\t\t\t\tr.append('\"');\n-\t\t\t\t\tinquote = false;\n-\t\t\t\t}\n-\t\t\t\tr.append(\"\\\\n\\\\\\n\");\n-\t\t\t\tlineStart = r.length();\n-\t\t\t\tbreak;\n-\n-\t\t\tcase '\\t':\n-\t\t\t\tr.append(\"\\\\t\");\n-\t\t\t\tbreak;\n-\n-\t\t\tcase '\\b':\n-\t\t\t\tr.append(\"\\\\b\");\n-\t\t\t\tbreak;\n-\n-\t\t\tcase '\\\\':\n-\t\t\t\tr.append(\"\\\\\\\\\");\n-\t\t\t\tbreak;\n-\n-\t\t\tcase '\"':\n-\t\t\t\tr.append(\"\\\\\\\"\");\n-\t\t\t\tbreak;\n-\n-\t\t\tcase ';':\n-\t\t\tcase '#':\n-\t\t\t\tif (!inquote) {\n-\t\t\t\t\tr.insert(lineStart, '\"');\n-\t\t\t\t\tinquote = true;\n-\t\t\t\t}\n-\t\t\t\tr.append(c);\n-\t\t\t\tbreak;\n-\n-\t\t\tcase ' ':\n-\t\t\t\tif (!inquote && r.length() > 0\n-\t\t\t\t\t\t&& r.charAt(r.length() - 1) == ' ') {\n-\t\t\t\t\tr.insert(lineStart, '\"');\n-\t\t\t\t\tinquote = true;\n-\t\t\t\t}\n-\t\t\t\tr.append(' ');\n-\t\t\t\tbreak;\n-\n-\t\t\tdefault:\n-\t\t\t\tr.append(c);\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t}\n-\t\tif (inquote) {\n-\t\t\tr.append('\"');\n-\t\t}\n-\t\treturn r.toString();\n-\t}\n-\n-\tprivate static String readBase(final BufferedReader r) throws IOException {\n-\t\tfinal StringBuffer base = new StringBuffer();\n-\t\tfor (;;) {\n-\t\t\tr.mark(1);\n-\t\t\tint c = r.read();\n-\t\t\tif (c < 0) {\n-\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n-\t\t\t} else if (']' == c) {\n-\t\t\t\tr.reset();\n-\t\t\t\tbreak;\n-\t\t\t} else if (' ' == c || '\\t' == c) {\n-\t\t\t\tfor (;;) {\n-\t\t\t\t\tr.mark(1);\n-\t\t\t\t\tc = r.read();\n-\t\t\t\t\tif (c < 0) {\n-\t\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n-\t\t\t\t\t} else if ('\"' == c) {\n-\t\t\t\t\t\tr.reset();\n-\t\t\t\t\t\tbreak;\n-\t\t\t\t\t} else if (' ' == c || '\\t' == c) {\n-\t\t\t\t\t\t// Skipped...\n-\t\t\t\t\t} else {\n-\t\t\t\t\t\tthrow new IOException(\"Bad base entry. : \" + base + \",\" + c);\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\t\t\t\tbreak;\n-\t\t\t} else if (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c) {\n-\t\t\t\tbase.append((char) c);\n-\t\t\t} else {\n-\t\t\t\tthrow new IOException(\"Bad base entry. : \" + base + \", \" + c);\n-\t\t\t}\n-\t\t}\n-\t\treturn base.toString();\n-\t}\n-\n-\tprivate static String readName(final BufferedReader r) throws IOException {\n-\t\tfinal StringBuffer name = new StringBuffer();\n-\t\tfor (;;) {\n-\t\t\tr.mark(1);\n-\t\t\tint c = r.read();\n-\t\t\tif (c < 0) {\n-\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n-\t\t\t} else if ('=' == c) {\n-\t\t\t\tbreak;\n-\t\t\t} else if (' ' == c || '\\t' == c) {\n-\t\t\t\tfor (;;) {\n-\t\t\t\t\tr.mark(1);\n-\t\t\t\t\tc = r.read();\n-\t\t\t\t\tif (c < 0) {\n-\t\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n-\t\t\t\t\t} else if ('=' == c) {\n-\t\t\t\t\t\tbreak;\n-\t\t\t\t\t} else if (';' == c || '#' == c || '\\n' == c) {\n-\t\t\t\t\t\tr.reset();\n-\t\t\t\t\t\tbreak;\n-\t\t\t\t\t} else if (' ' == c || '\\t' == c) {\n-\t\t\t\t\t\t// Skipped...\n-\t\t\t\t\t} else {\n-\t\t\t\t\t\tthrow new IOException(\"Bad entry delimiter.\");\n-\t\t\t\t\t}\n-\t\t\t\t}\n-\t\t\t\tbreak;\n-\t\t\t} else if (Character.isLetterOrDigit((char) c) || c == '-') {\n-\t\t\t\t// From the git-config man page:\n-\t\t\t\t//     The variable names are case-insensitive and only\n-\t\t\t\t//     alphanumeric characters and - are allowed.\n-\t\t\t\tname.append((char) c);\n-\t\t\t} else if ('\\n' == c) {\n-\t\t\t\tr.reset();\n-\t\t\t\tname.append((char) c);\n-\t\t\t\tbreak;\n-\t\t\t} else {\n-\t\t\t\tthrow new IOException(\"Bad config entry name: \" + name + (char) c);\n-\t\t\t}\n-\t\t}\n-\t\treturn name.toString();\n-\t}\n-\n-\tprivate static String readValue(final BufferedReader r, boolean quote,\n-\t\t\tfinal int eol) throws IOException {\n-\t\tfinal StringBuffer value = new StringBuffer();\n-\t\tboolean space = false;\n-\t\tfor (;;) {\n-\t\t\tr.mark(1);\n-\t\t\tint c = r.read();\n-\t\t\tif (c < 0) {\n-\t\t\t\tif (value.length() == 0)\n-\t\t\t\t\tthrow new IOException(\"Unexpected end of config file.\");\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tif ('\\n' == c) {\n-\t\t\t\tif (quote) {\n-\t\t\t\t\tthrow new IOException(\"Newline in quotes not allowed.\");\n-\t\t\t\t}\n-\t\t\t\tr.reset();\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tif (eol == c) {\n-\t\t\t\tbreak;\n-\t\t\t}\n-\t\t\tif (!quote) {\n-\t\t\t\tif (Character.isWhitespace((char) c)) {\n-\t\t\t\t\tspace = true;\n-\t\t\t\t\tcontinue;\n-\t\t\t\t}\n-\t\t\t\tif (';' == c || '#' == c) {\n-\t\t\t\t\tr.reset();\n-\t\t\t\t\tbreak;\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tif (space) {\n-\t\t\t\tif (value.length() > 0) {\n-\t\t\t\t\tvalue.append(' ');\n-\t\t\t\t}\n-\t\t\t\tspace = false;\n-\t\t\t}\n-\t\t\tif ('\\\\' == c) {\n-\t\t\t\tc = r.read();\n-\t\t\t\tswitch (c) {\n-\t\t\t\tcase -1:\n-\t\t\t\t\tthrow new IOException(\"End of file in escape.\");\n-\t\t\t\tcase '\\n':\n-\t\t\t\t\tcontinue;\n-\t\t\t\tcase 't':\n-\t\t\t\t\tvalue.append('\\t');\n-\t\t\t\t\tcontinue;\n-\t\t\t\tcase 'b':\n-\t\t\t\t\tvalue.append('\\b');\n-\t\t\t\t\tcontinue;\n-\t\t\t\tcase 'n':\n-\t\t\t\t\tvalue.append('\\n');\n-\t\t\t\t\tcontinue;\n-\t\t\t\tcase '\\\\':\n-\t\t\t\t\tvalue.append('\\\\');\n-\t\t\t\t\tcontinue;\n-\t\t\t\tcase '\"':\n-\t\t\t\t\tvalue.append('\"');\n-\t\t\t\t\tcontinue;\n-\t\t\t\tdefault:\n-\t\t\t\t\tthrow new IOException(\"Bad escape: \" + ((char) c));\n-\t\t\t\t}\n-\t\t\t}\n-\t\t\tif ('\"' == c) {\n-\t\t\t\tquote = !quote;\n-\t\t\t\tcontinue;\n-\t\t\t}\n-\t\t\tvalue.append((char) c);\n-\t\t}\n-\t\treturn value.length() > 0 ? value.toString() : null;\n-\t}\n-\n-\tpublic String toString() {\n-\t\treturn \"RepositoryConfig[\" + configFile.getPath() + \"]\";\n-\t}\n-\n-\tstatic class Entry {\n-\t\tString prefix;\n-\n-\t\tString base;\n-\n-\t\tString extendedBase;\n-\n-\t\tString name;\n-\n-\t\tString value;\n-\n-\t\tString suffix;\n-\n-\t\tboolean match(final String aBase, final String aExtendedBase,\n-\t\t\t\tfinal String aName) {\n-\t\t\treturn eq(base, aBase) && eq(extendedBase, aExtendedBase)\n-\t\t\t\t\t&& eq(name, aName);\n-\t\t}\n-\n-\t\tprivate static boolean eq(final String a, final String b) {\n-\t\t\tif (a == null && b == null)\n-\t\t\t\treturn true;\n-\t\t\tif (a == null || b == null)\n-\t\t\t\treturn false;\n-\t\t\treturn a.equalsIgnoreCase(b);\n-\t\t}\n-\t}\n-\n \t/**\n \t * Gets the hostname of the local host.\n \t * If no hostname can be found, the hostname is set to the default value \"localhost\".\n@@ -1166,5 +248,4 @@ private static String getHostname() {\n \tpublic static void setSystemReader(SystemReader newSystemReader) {\n \t\tsystemReader = newSystemReader;\n \t}\n-\n }\n-- \n1.6.1.2\n"},{"id":"116874","messageId":"1245865685-1288-3-git-send-email-constantine.plotnikov@gmail.com","threadId":"19919","inReplyTo":"1245865685-1288-2-git-send-email-constantine.plotnikov@gmail.com","subject":"[JGIT PATCH 2/3] Config.getSubsections(...) now loads the file if it has not been loaded","fromName":"Constantine Plotnikov","fromEmail":"constantine.plotnikov@gmail.com","sentAt":"2009-06-24T17:48:04Z","receivedAt":"2009-06-24T17:48:04Z","isPatch":true,"sender":{"key":"constantine.plotnikov@gmail.com","avatar":null},"body":"If method getSubsections() is invoked before any entry in the config\nfile is accessed via get*(section, subsection, name) methods,\nNPE is thrown because file is not yet loaded. This patch fixes the\nproblem by ensuring that file is loaded before iterating entries\nin this method.\n\nSigned-off-by: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n .../src/org/spearce/jgit/lib/Config.java           |    9 +++++++--\n 1 files changed, 7 insertions(+), 2 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\nindex 62daef3..4220c37 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n@@ -367,6 +367,7 @@ public String getString(final String section, String subsection,\n \t */\n \tpublic Set<String> getSubsections(final String section) {\n \t\tfinal Set<String> result = new HashSet<String>();\n+\t\tensureLoaded();\n \n \t\tfor (final Entry e : entries) {\n \t\t\tif (section.equalsIgnoreCase(e.base) && e.extendedBase != null)\n@@ -401,8 +402,7 @@ private String getRawString(final String section, final String subsection,\n \t\t\treturn null;\n \t}\n \n-\tprivate Object getRawEntry(final String section, final String subsection,\n-\t\t\tfinal String name) {\n+\tprivate void ensureLoaded() {\n \t\tif (!readFile) {\n \t\t\ttry {\n \t\t\t\tload();\n@@ -413,6 +413,11 @@ private Object getRawEntry(final String section, final String subsection,\n \t\t\t\terr.printStackTrace();\n \t\t\t}\n \t\t}\n+\t}\n+\n+\tprivate Object getRawEntry(final String section, final String subsection,\n+\t\t\tfinal String name) {\n+\t\tensureLoaded();\n \n \t\tString ss;\n \t\tif (subsection != null)\n-- \n1.6.1.2\n"},{"id":"116875","messageId":"1245865685-1288-4-git-send-email-constantine.plotnikov@gmail.com","threadId":"19919","inReplyTo":"1245865685-1288-3-git-send-email-constantine.plotnikov@gmail.com","subject":"[JGIT PATCH 3/3] Added BlobBasedConfig that allows accessing configuration files stored in the blobs","fromName":"Constantine Plotnikov","fromEmail":"constantine.plotnikov@gmail.com","sentAt":"2009-06-24T17:48:05Z","receivedAt":"2009-06-24T17:48:05Z","isPatch":true,"sender":{"key":"constantine.plotnikov@gmail.com","avatar":null},"body":"The class BlobBasedConfig allows loading files stored in blobs inside\nthe repository. This class can be used to access .gitmodules file stored\ninside the repository. To simplify API for this class the method\nCommit.getRepository() is added. And the type of the last argument in\nTreeWalk.forPath(db, path, trees) is changed from AnyObjectId[] to\nAnyObjectId...\n\nSigned-off-by: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n\nNote that after this patch the method TreeWalk.forPath(Repository,\nString, RevTree) is needed for binary backward compatibility only. \nIt can be removed without breaking source compatibility.\n \n .../src/org/spearce/jgit/lib/BlobBasedConfig.java  |  145 ++++++++++++++++++++\n .../src/org/spearce/jgit/lib/Commit.java           |    7 +\n .../src/org/spearce/jgit/treewalk/TreeWalk.java    |    2 +-\n 3 files changed, 153 insertions(+), 1 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\nnew file mode 100644\nindex 0000000..0385a3b\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\n@@ -0,0 +1,145 @@\n+/*\n+ * Copyright (C) 2009, JetBrains s.r.o.\n+ *\n+ * All rights reserved.\n+ *\n+ * Redistribution and use in source and binary forms, with or\n+ * without modification, are permitted provided that the following\n+ * conditions are met:\n+ *\n+ * - Redistributions of source code must retain the above copyright\n+ *   notice, this list of conditions and the following disclaimer.\n+ *\n+ * - Redistributions in binary form must reproduce the above\n+ *   copyright notice, this list of conditions and the following\n+ *   disclaimer in the documentation and/or other materials provided\n+ *   with the distribution.\n+ *\n+ * - Neither the name of the Git Development Community nor the\n+ *   names of its contributors may be used to endorse or promote\n+ *   products derived from this software without specific prior\n+ *   written permission.\n+ *\n+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND\n+ * CONTRIBUTORS \"AS IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES,\n+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES\n+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE\n+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,\n+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT\n+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\n+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER\n+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,\n+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)\n+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF\n+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n+ */\n+package org.spearce.jgit.lib;\n+\n+import java.io.ByteArrayInputStream;\n+import java.io.IOException;\n+import java.io.InputStream;\n+import java.util.concurrent.Callable;\n+\n+import org.spearce.jgit.treewalk.TreeWalk;\n+\n+/**\n+ * The configuration file based on the blobs stored in the repository\n+ */\n+public class BlobBasedConfig extends Config {\n+\tprivate Callable<byte[]> blobProvider;\n+\n+\t/**\n+\t * The constructor for blob based config\n+\t *\n+\t * @param base\n+\t *            the base configuration file\n+\t * @param blob\n+\t *            the provider for blobs\n+\t */\n+\tpublic BlobBasedConfig(Config base, Callable<byte[]> blob) {\n+\t\tsuper(base);\n+\t\tblobProvider = blob;\n+\t}\n+\n+\t/**\n+\t * The constructor from a byte array\n+\t *\n+\t * @param base\n+\t *            the base configuration file\n+\t * @param blob\n+\t *            the byte array\n+\t */\n+\tpublic BlobBasedConfig(Config base, final byte[] blob) {\n+\t\tthis(base, new Callable<byte[]>() {\n+\t\t\tpublic byte[] call() throws Exception {\n+\t\t\t\treturn blob;\n+\t\t\t}\n+\t\t});\n+\t}\n+\n+\t/**\n+\t * The constructor from object identifier\n+\t *\n+\t * @param base\n+\t *            the base configuration file\n+\t * @param r\n+\t *            the repository\n+\t * @param objectId\n+\t *            the object identifier\n+\t */\n+\tpublic BlobBasedConfig(Config base, final Repository r,\n+\t\t\tfinal ObjectId objectId) {\n+\t\tthis(base, new Callable<byte[]>() {\n+\t\t\tpublic byte[] call() throws Exception {\n+\t\t\t\tObjectLoader loader = r.openBlob(objectId);\n+\t\t\t\tif (loader == null) {\n+\t\t\t\t\tthrow new IOException(\"Blob not found: \" + objectId);\n+\t\t\t\t}\n+\t\t\t\treturn loader.getBytes();\n+\t\t\t}\n+\t\t});\n+\t}\n+\n+\t/**\n+\t * The constructor from commit and path\n+\t *\n+\t * @param base\n+\t *            the base configuration file\n+\t * @param commit\n+\t *            the commit that contains the object\n+\t * @param path\n+\t *            the path within the tree of the commit\n+\t */\n+\tpublic BlobBasedConfig(Config base, final Commit commit, final String path) {\n+\t\tthis(base, new Callable<byte[]>() {\n+\t\t\tpublic byte[] call() throws Exception {\n+\t\t\t\tfinal ObjectId treeId = commit.getTreeId();\n+\t\t\t\tfinal Repository r = commit.getRepository();\n+\t\t\t\tfinal TreeWalk tree = TreeWalk.forPath(r, path, treeId);\n+\t\t\t\tif (tree == null) {\n+\t\t\t\t\tthrow new IOException(\"Entry not found by path: \" + path);\n+\t\t\t\t}\n+\t\t\t\tObjectId blobId = tree.getObjectId(0);\n+\t\t\t\tObjectLoader loader = tree.getRepository().openBlob(blobId);\n+\t\t\t\tif (loader == null) {\n+\t\t\t\t\tthrow new IOException(\"Blob not found: \" + blobId + \" for path: \" + path);\n+\t\t\t\t}\n+\t\t\t\treturn loader.getBytes();\n+\t\t\t}\n+\t\t});\n+\t}\n+\n+\t@Override\n+\tprotected InputStream openInputStream() throws IOException {\n+\t\ttry {\n+\t\t\treturn new ByteArrayInputStream(blobProvider.call());\n+\t\t} catch (IOException e) {\n+\t\t\tthrow e;\n+\t\t} catch (RuntimeException e) {\n+\t\t\tthrow e;\n+\t\t} catch (Exception e) {\n+\t\t\tthrow new RuntimeException(e);\n+\t\t}\n+\t}\n+}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Commit.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Commit.java\nindex 085e2b9..030d4a4 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Commit.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Commit.java\n@@ -149,6 +149,13 @@ public Commit(final Repository db, final ObjectId id, final byte[] raw) {\n \t}\n \n \t/**\n+\t * @return get repository for the commit\n+\t */\n+\tpublic Repository getRepository() {\n+\t\treturn objdb;\n+\t}\n+\n+\t/**\n \t * @return The commit object id\n \t */\n \tpublic ObjectId getCommitId() {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java\nindex 250b213..5705936 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/treewalk/TreeWalk.java\n@@ -103,7 +103,7 @@\n \t *             a tree object was not found.\n \t */\n \tpublic static TreeWalk forPath(final Repository db, final String path,\n-\t\t\tfinal AnyObjectId[] trees) throws MissingObjectException,\n+\t\t\tfinal AnyObjectId... trees) throws MissingObjectException,\n \t\t\tIncorrectObjectTypeException, CorruptObjectException, IOException {\n \t\tfinal TreeWalk r = new TreeWalk(db);\n \t\tr.setFilter(PathFilterGroup.createFromStrings(Collections\n-- \n1.6.1.2\n"},{"id":"116895","messageId":"20090625002634.GI11191@spearce.org","threadId":"19919","inReplyTo":"1245865685-1288-1-git-send-email-constantine.plotnikov@gmail.com","subject":"Re: [JGIT PATCH 0/3] Support for loading .gitmodules from blobs","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-06-25T00:26:34Z","receivedAt":"2009-06-25T00:26:34Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Constantine Plotnikov <constantine.plotnikov@gmail.com> wrote:\n> This series of patches is developed to support accessing \n> .gitmodules file stored inside the repository. This functionality\n> is needed for server-side processing of submodules.\n> \n> Constantine Plotnikov (3):\n>  Extracted functionality independent from .git/config from RepositoryConfig\n>  Config.getSubsections(...) now loads the file if it has not been loaded\n>  Added BlobBasedConfig that allows accessing configuration files stored in the blobs\n\nThanks, pushed, with a couple of minor edits that were just easier\nto squash in than to ask you to fix and respin:\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\nindex 0385a3b..1f44994 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/BlobBasedConfig.java\n@@ -136,10 +136,10 @@ protected InputStream openInputStream() throws IOException {\n \t\t\treturn new ByteArrayInputStream(blobProvider.call());\n \t\t} catch (IOException e) {\n \t\t\tthrow e;\n-\t\t} catch (RuntimeException e) {\n-\t\t\tthrow e;\n \t\t} catch (Exception e) {\n-\t\t\tthrow new RuntimeException(e);\n+\t\t\tfinal IOException e2 = new IOException(\"Unable to read config\");\n+\t\t\te2.initCause(e);\n+\t\t\tthrow e2;\n \t\t}\n \t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\nindex 4220c37..c2d5c6e 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Config.java\n@@ -84,8 +84,9 @@\n \t *            the base configuration file to be consulted when value is\n \t *            missing from this file\n \t */\n-\tpublic Config(Config base) {\n+\tprotected Config(Config base) {\n \t\tbaseConfig = base;\n+\t\tclear();\n \t}\n \n \t/**\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\nindex a139386..9d2c9a3 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/RepositoryConfig.java\n@@ -108,7 +108,6 @@ public RepositoryConfig openUserConfig() {\n \t */\n \tpublic RepositoryConfig(final RepositoryConfig base, final File cfgLocation) {\n \t\tsuper(base, cfgLocation);\n-\t\tclear();\n \t}\n \n \t/**\n \n-- \nShawn.\n"}]}