{"thread":{"id":"20232","subject":"[JGIT PATCH 00/19] More Config class cleanup work","startedAt":"2009-07-25T18:52:43Z","lastAt":"2009-07-25T23:38:13Z","messageCount":26,"participants":["Shawn O. Pearce","Robin Rosenberg"],"isPatch":true,"patchVersion":1,"patchTotal":19},"messages":[{"id":"118745","messageId":"1248547982-4003-1-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":null,"subject":"[JGIT PATCH 00/19] More Config class cleanup work","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:43Z","receivedAt":"2009-07-25T18:52:43Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Yet another series to cleanup the Config class and its\nimplementations to be slightly more useful.\n\nThe later part of the series makes Config thread safe and introduces\ncaching of application-specific model instances as part of the\nConfig, making it more efficient for code to query for multiple\nvalues at once.\n\nThis new caching feature is especially useful for ReceivePack on\nthe server side, where the values aren't expected to change between\nconnections, but connections come in at a steady enough rate that\nreparsing the configuration each time is just a waste of time.\n\nLater I plan to extend the caching by using it RemoteConfig and\nalso for a new SubmoduleConfig.\n\n\nThe entire series applies on top of my 1 patch from yesterday that\nfixes the Turkish locale problems in Config.\n\n\nShawn O. Pearce (19):\n  Cleanup nonstandard references to encoding strings to bytes\n  Delete incorrect Javadoc from Config's getRawString method\n  Make Config.escapeValue a private method\n  Allow a RemoteConfig to use the more generic Config class\n  Use type specific sets when creating a new RepositoryConfig\n  Move SystemReader out of RepositoryConfig\n  Correct user config to be of type FileBasedConfig\n  Extract the test specific SystemReader out of RepositoryTestCase\n  Refactor Config hierarchy to make IO more explicit\n  Test for the config file when creating a new repository\n  Remove the map lookup for values in Config\n  Return base values first from Config.getStringList()\n  Make Config thread safe by using copy-on-write semantics\n  Support cached application models in a Config\n  Cache Config subsection names when requested by application code\n  Refactor author/committer lookup to use cached data\n  Move repository config creation fully into Repository class\n  Use Config SectionParser cache to store daemon enable states\n  Use Config cache for fetch and receive configuration parsing\n\n .../org/spearce/jgit/lib/ConcurrentRepackTest.java |    2 +-\n .../tst/org/spearce/jgit/lib/MockSystemReader.java |   78 ++\n .../org/spearce/jgit/lib/RepositoryConfigTest.java |  193 +++---\n .../org/spearce/jgit/lib/RepositoryTestCase.java   |   51 +--\n .../tst/org/spearce/jgit/lib/T0003_Basic.java      |   16 +-\n .../org/spearce/jgit/revwalk/RevWalkTestCase.java  |    3 +-\n .../spearce/jgit/transport/RemoteConfigTest.java   |  166 ++---\n .../jgit/errors/ConfigInvalidException.java        |   53 ++\n .../src/org/spearce/jgit/lib/BlobBasedConfig.java  |  110 ++--\n .../src/org/spearce/jgit/lib/Config.java           |  743 +++++++++++---------\n .../src/org/spearce/jgit/lib/CoreConfig.java       |   16 +-\n .../src/org/spearce/jgit/lib/FileBasedConfig.java  |   83 ++-\n .../src/org/spearce/jgit/lib/ObjectWriter.java     |    2 +-\n .../src/org/spearce/jgit/lib/Repository.java       |   38 +-\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |  144 +----\n .../src/org/spearce/jgit/lib/TransferConfig.java   |   11 +-\n .../src/org/spearce/jgit/lib/UserConfig.java       |  149 ++++\n .../jgit/revwalk/filter/PatternMatchRevFilter.java |   10 +-\n .../jgit/transport/BasePackFetchConnection.java    |   21 +-\n .../org/spearce/jgit/transport/DaemonService.java  |   30 +-\n .../org/spearce/jgit/transport/ReceivePack.java    |   45 +-\n .../org/spearce/jgit/transport/RemoteConfig.java   |   15 +-\n .../org/spearce/jgit/util/RawSubStringPattern.java |   10 +-\n .../src/org/spearce/jgit/util/SystemReader.java    |   65 ++-\n 24 files changed, 1172 insertions(+), 882 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/MockSystemReader.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/errors/ConfigInvalidException.java\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/UserConfig.java\n"},{"id":"118748","messageId":"1248547982-4003-2-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-1-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 01/19] Cleanup nonstandard references to encoding strings to bytes","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:44Z","receivedAt":"2009-07-25T18:52:44Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/lib/ConcurrentRepackTest.java |    2 +-\n .../org/spearce/jgit/revwalk/RevWalkTestCase.java  |    3 +--\n .../src/org/spearce/jgit/lib/ObjectWriter.java     |    2 +-\n .../jgit/revwalk/filter/PatternMatchRevFilter.java |   10 ++--------\n .../org/spearce/jgit/util/RawSubStringPattern.java |   10 ++--------\n 5 files changed, 7 insertions(+), 20 deletions(-)\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConcurrentRepackTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConcurrentRepackTest.java\nindex fa6345e..bf155cf 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConcurrentRepackTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/ConcurrentRepackTest.java\n@@ -244,7 +244,7 @@ private File fullPackFileName(final ObjectId name, final String suffix) {\n \tprivate RevObject writeBlob(final Repository repo, final String data)\n \t\t\tthrows IOException {\n \t\tfinal RevWalk revWalk = new RevWalk(repo);\n-\t\tfinal byte[] bytes = data.getBytes(Constants.CHARACTER_ENCODING);\n+\t\tfinal byte[] bytes = Constants.encode(data);\n \t\tfinal ObjectWriter ow = new ObjectWriter(repo);\n \t\tfinal ObjectId id = ow.writeBlob(bytes);\n \t\ttry {\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevWalkTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevWalkTestCase.java\nindex befc3d5..9d5a44c 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevWalkTestCase.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/revwalk/RevWalkTestCase.java\n@@ -82,8 +82,7 @@ protected void tick(final int secDelta) {\n \t}\n \n \tprotected RevBlob blob(final String content) throws Exception {\n-\t\treturn rw.lookupBlob(ow.writeBlob(content\n-\t\t\t\t.getBytes(Constants.CHARACTER_ENCODING)));\n+\t\treturn rw.lookupBlob(ow.writeBlob(Constants.encode(content)));\n \t}\n \n \tprotected DirCacheEntry file(final String path, final RevBlob blob)\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java\nindex 546cc68..86b5b09 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/ObjectWriter.java\n@@ -215,7 +215,7 @@ public ObjectId writeCommit(final Commit c) throws IOException {\n \t\tw.flush();\n \t\tos.write('\\n');\n \n-\t\tif (!encoding.equals(\"UTF-8\")) {\n+\t\tif (!encoding.equals(Constants.CHARACTER_ENCODING)) {\n \t\t\tos.write(hencoding);\n \t\t\tos.write(' ');\n \t\t\tos.write(Constants.encodeASCII(encoding));\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/revwalk/filter/PatternMatchRevFilter.java b/org.spearce.jgit/src/org/spearce/jgit/revwalk/filter/PatternMatchRevFilter.java\nindex e0bccf7..f9e7e4a 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/revwalk/filter/PatternMatchRevFilter.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/revwalk/filter/PatternMatchRevFilter.java\n@@ -38,12 +38,12 @@\n package org.spearce.jgit.revwalk.filter;\n \n import java.io.IOException;\n-import java.io.UnsupportedEncodingException;\n import java.util.regex.Matcher;\n import java.util.regex.Pattern;\n \n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n import org.spearce.jgit.errors.MissingObjectException;\n+import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevWalk;\n import org.spearce.jgit.util.RawCharSequence;\n@@ -64,13 +64,7 @@\n \t *         character sequence {@link RawCharSequence}.\n \t */\n \tprotected static final String forceToRaw(final String patternText) {\n-\t\tfinal byte[] b;\n-\t\ttry {\n-\t\t\tb = patternText.getBytes(\"UTF-8\");\n-\t\t} catch (UnsupportedEncodingException e) {\n-\t\t\tthrow new IllegalStateException(\"JVM lacks UTF-8 support.\", e);\n-\t\t}\n-\n+\t\tfinal byte[] b = Constants.encode(patternText);\n \t\tfinal StringBuilder needle = new StringBuilder(b.length);\n \t\tfor (int i = 0; i < b.length; i++)\n \t\t\tneedle.append((char) (b[i] & 0xff));\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/RawSubStringPattern.java b/org.spearce.jgit/src/org/spearce/jgit/util/RawSubStringPattern.java\nindex 5ed071c..1628deb 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/RawSubStringPattern.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/RawSubStringPattern.java\n@@ -37,7 +37,7 @@\n \n package org.spearce.jgit.util;\n \n-import java.io.UnsupportedEncodingException;\n+import org.spearce.jgit.lib.Constants;\n \n /**\n  * Searches text using only substring search.\n@@ -63,13 +63,7 @@ public RawSubStringPattern(final String patternText) {\n \t\t\tthrow new IllegalArgumentException(\"Cannot match on empty string.\");\n \t\tneedleString = patternText;\n \n-\t\tfinal byte[] b;\n-\t\ttry {\n-\t\t\tb = patternText.getBytes(\"UTF-8\");\n-\t\t} catch (UnsupportedEncodingException e) {\n-\t\t\tthrow new IllegalStateException(\"JVM lacks UTF-8 support.\", e);\n-\t\t}\n-\n+\t\tfinal byte[] b = Constants.encode(patternText);\n \t\tneedle = new byte[b.length];\n \t\tfor (int i = 0; i < b.length; i++)\n \t\t\tneedle[i] = lc(b[i]);\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118746","messageId":"1248547982-4003-3-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-2-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 02/19] Delete incorrect Javadoc from Config's getRawString method","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:45Z","receivedAt":"2009-07-25T18:52:45Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"I don't know how this Javadoc got here, but it predates the code\nrefactor done by Constantine Plotnikov in 2564768e63.\n\nThe documentation is incorrect, as the method returns a single string\nbut the summary line says a list.  Since we usually don't document\na private method I'm removing the documentation block rather than\ncorrecting it.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |   11 -----------\n 1 files changed, 0 insertions(+), 11 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 258dba5..76f780f 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@@ -385,17 +385,6 @@ public String getString(final String section, String subsection,\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-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118747","messageId":"1248547982-4003-4-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-3-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 03/19] Make Config.escapeValue a private method","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:46Z","receivedAt":"2009-07-25T18:52:46Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"This method is only used by toText() as it generates the\nvalue for an entry in the file.  We don't want (or need)\nto export it to our subclasses.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |    2 +-\n 1 files changed, 1 insertions(+), 1 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 76f780f..a2f5c6a 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@@ -115,7 +115,7 @@ protected void setFileRead(boolean ok) {\n \t *            the value to escape\n \t * @return the escaped value\n \t */\n-\tprotected static String escapeValue(final String x) {\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-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118751","messageId":"1248547982-4003-5-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-4-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 04/19] Allow a RemoteConfig to use the more generic Config class","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:47Z","receivedAt":"2009-07-25T18:52:47Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"A RemoteConfig can be parsed from any Config type object, not just\nthe RepositoryConfig object.  This change makes it easier to use\nother types of Config storage, such as in unit tests.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/RemoteConfig.java   |   15 ++++++++-------\n 1 files changed, 8 insertions(+), 7 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\nindex 93a5873..ca84acf 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/RemoteConfig.java\n@@ -43,6 +43,7 @@\n import java.util.Collections;\n import java.util.List;\n \n+import org.spearce.jgit.lib.Config;\n import org.spearce.jgit.lib.RepositoryConfig;\n \n /**\n@@ -143,7 +144,7 @@\n \t * @throws URISyntaxException\n \t *             one of the URIs within the remote's configuration is invalid.\n \t */\n-\tpublic RemoteConfig(final RepositoryConfig rc, final String remoteName)\n+\tpublic RemoteConfig(final Config rc, final String remoteName)\n \t\t\tthrows URISyntaxException {\n \t\tname = remoteName;\n \n@@ -192,7 +193,7 @@ public RemoteConfig(final RepositoryConfig rc, final String remoteName)\n \t * @param rc\n \t *            the configuration file to store ourselves into.\n \t */\n-\tpublic void update(final RepositoryConfig rc) {\n+\tpublic void update(final Config rc) {\n \t\tfinal List<String> vlst = new ArrayList<String>();\n \n \t\tvlst.clear();\n@@ -222,7 +223,7 @@ public void update(final RepositoryConfig rc) {\n \t\tset(rc, KEY_TIMEOUT, timeout, 0);\n \t}\n \n-\tprivate void set(final RepositoryConfig rc, final String key,\n+\tprivate void set(final Config rc, final String key,\n \t\t\tfinal String currentValue, final String defaultValue) {\n \t\tif (defaultValue.equals(currentValue))\n \t\t\tunset(rc, key);\n@@ -230,7 +231,7 @@ private void set(final RepositoryConfig rc, final String key,\n \t\t\trc.setString(SECTION, getName(), key, currentValue);\n \t}\n \n-\tprivate void set(final RepositoryConfig rc, final String key,\n+\tprivate void set(final Config rc, final String key,\n \t\t\tfinal boolean currentValue, final boolean defaultValue) {\n \t\tif (defaultValue == currentValue)\n \t\t\tunset(rc, key);\n@@ -238,15 +239,15 @@ private void set(final RepositoryConfig rc, final String key,\n \t\t\trc.setBoolean(SECTION, getName(), key, currentValue);\n \t}\n \n-\tprivate void set(final RepositoryConfig rc, final String key,\n-\t\t\tfinal int currentValue, final int defaultValue) {\n+\tprivate void set(final Config rc, final String key, final int currentValue,\n+\t\t\tfinal int defaultValue) {\n \t\tif (defaultValue == currentValue)\n \t\t\tunset(rc, key);\n \t\telse\n \t\t\trc.setInt(SECTION, getName(), key, currentValue);\n \t}\n \n-\tprivate void unset(final RepositoryConfig rc, final String key) {\n+\tprivate void unset(final Config rc, final String key) {\n \t\trc.unset(SECTION, getName(), key);\n \t}\n \n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118750","messageId":"1248547982-4003-6-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-5-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 05/19] Use type specific sets when creating a new RepositoryConfig","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:48Z","receivedAt":"2009-07-25T18:52:48Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Signed-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |    5 ++---\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 ++--\n 2 files changed, 4 insertions(+), 5 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 1076fe1..98a276b 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -166,9 +166,8 @@ public void create(boolean bare) throws IOException {\n \n \t\tRepositoryConfig cfg = getConfig();\n \t\tcfg.create();\n-\t\tif (bare) {\n-\t\t\tcfg.setString(\"core\", null, \"bare\", \"true\");\n-\t\t}\n+\t\tif (bare)\n+\t\t\tcfg.setBoolean(\"core\", null, \"bare\", true);\n \t\tcfg.save();\n \t}\n \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 85e8738..c80db00 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@@ -203,8 +203,8 @@ private String getUserEmailInternal(String gitVariableKey) {\n \tpublic void create() {\n \t\tclear();\n \t\tsetFileRead(true);\n-\t\tsetString(\"core\", null, \"repositoryformatversion\", \"0\");\n-\t\tsetString(\"core\", null, \"filemode\", \"true\");\n+\t\tsetInt(\"core\", null, \"repositoryformatversion\", 0);\n+\t\tsetBoolean(\"core\", null, \"filemode\", true);\n \n \t\tcore = new CoreConfig(this);\n \t\ttransfer = new TransferConfig(this);\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118755","messageId":"1248547982-4003-7-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-6-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 06/19] Move SystemReader out of RepositoryConfig","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:49Z","receivedAt":"2009-07-25T18:52:49Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Reading basic properties of the JVM has nothing to do with reading\na Git style configuration file for a repository, or for the current\nuser account.  Instead pull all of that logic into its own abstract\nclass, and provide a default implementation available through a\nsingleton pattern.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/lib/RepositoryConfigTest.java |   11 +---\n .../org/spearce/jgit/lib/RepositoryTestCase.java   |    9 ++-\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |   50 +--------------\n .../src/org/spearce/jgit/util/SystemReader.java    |   64 ++++++++++++++++++-\n 4 files changed, 72 insertions(+), 62 deletions(-)\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\nindex 5e2328b..5bb9afb 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n@@ -42,8 +42,6 @@\n \n import java.io.File;\n import java.io.IOException;\n-import java.net.InetAddress;\n-import java.net.UnknownHostException;\n import java.util.Arrays;\n import java.util.LinkedList;\n \n@@ -116,14 +114,7 @@ public void test006_readCaseInsensitive() throws IOException {\n \t}\n \n \tpublic void test007_readUserInfos() throws IOException {\n-\t\tString hostname;\n-\t\ttry {\n-\t\t\tInetAddress localMachine = InetAddress.getLocalHost();\n-\t\t\thostname = localMachine.getCanonicalHostName();\n-\t\t} catch (UnknownHostException e) {\n-\t\t\thostname = \"localhost\";\n-\t\t}\n-\n+\t\tfinal String hostname = FAKE_HOSTNAME;\n \t\tfinal File localConfig = writeTrashFile(\"local.config\", \"\");\n \t\tSystem.clearProperty(Constants.OS_USER_NAME_KEY);\n \ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\nindex 2783180..9dfaeef 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n@@ -81,6 +81,8 @@\n \n \tprotected static final PersonIdent jcommitter;\n \n+\tprotected static final String FAKE_HOSTNAME = \"fake.host.example.com\";\n+\n \tstatic {\n \t\tjauthor = new PersonIdent(\"J. Author\", \"jauthor@example.com\");\n \t\tjcommitter = new PersonIdent(\"J. Committer\", \"jcommitter@example.com\");\n@@ -88,7 +90,7 @@\n \n \tprotected boolean packedGitMMAP;\n \n-\tprotected static class FakeSystemReader implements SystemReader {\n+\tprotected static class FakeSystemReader extends SystemReader {\n \t\tMap<String, String> values = new HashMap<String, String>();\n \t\tRepositoryConfig userGitConfig;\n \t\tpublic String getenv(String variable) {\n@@ -103,6 +105,9 @@ public RepositoryConfig openUserConfig() {\n \t\tpublic void setUserGitConfig(RepositoryConfig userGitConfig) {\n \t\t\tthis.userGitConfig = userGitConfig;\n \t\t}\n+\t\tpublic String getHostname() {\n+\t\t\treturn FAKE_HOSTNAME;\n+\t\t}\n \t}\n \n \t/**\n@@ -114,7 +119,7 @@ public void setUserGitConfig(RepositoryConfig userGitConfig) {\n \n \tstatic {\n \t\tfakeSystemReader = new FakeSystemReader();\n-\t\tRepositoryConfig.setSystemReader(fakeSystemReader);\n+\t\tSystemReader.setInstance(fakeSystemReader);\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 c80db00..9be7c1b 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@@ -43,8 +43,6 @@\n \n import java.io.File;\n import java.io.IOException;\n-import java.net.InetAddress;\n-import java.net.UnknownHostException;\n \n import org.spearce.jgit.util.FS;\n import org.spearce.jgit.util.SystemReader;\n@@ -63,7 +61,7 @@\n \t *         configuration file from their home directory.\n \t */\n \tpublic static RepositoryConfig openUserConfig() {\n-\t\treturn systemReader.openUserConfig();\n+\t\treturn SystemReader.getInstance().openUserConfig();\n \t}\n \n \t/** Section name for a branch configuration. */\n@@ -73,21 +71,6 @@ public static RepositoryConfig openUserConfig() {\n \n \tTransferConfig transfer;\n \n-\tprivate static String hostname;\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-\t\t\treturn System.getenv(variable);\n-\t\t}\n-\t\tpublic String getProperty(String key) {\n-\t\t\treturn System.getProperty(key);\n-\t\t}\n-\t\tpublic RepositoryConfig openUserConfig() {\n-\t\t\treturn new RepositoryConfig(null, new File(FS.userHome(), \".gitconfig\"));\n-\t\t}\n-\t};\n-\n \tRepositoryConfig(final Repository repo) {\n \t\tthis(openUserConfig(), FS.resolve(repo.getDirectory(), \"config\"));\n \t}\n@@ -139,6 +122,7 @@ public String getCommitterName() {\n \t}\n \n \tprivate String getUsernameInternal(String gitVariableKey) {\n+\t\tSystemReader systemReader = SystemReader.getInstance();\n \t\t// try to get the user name from the local and global configurations.\n \t\tString username = getString(\"user\", null, \"name\");\n \n@@ -177,6 +161,7 @@ public String getCommitterEmail() {\n \t}\n \n \tprivate String getUserEmailInternal(String gitVariableKey) {\n+\t\tSystemReader systemReader = SystemReader.getInstance();\n \t\t// try to get the email from the local and global configurations.\n \t\tString email = getString(\"user\", null, \"email\");\n \n@@ -191,7 +176,7 @@ private String getUserEmailInternal(String gitVariableKey) {\n \t\t\tif (username == null){\n \t\t\t\tusername = Constants.UNKNOWN_USER_DEFAULT;\n \t\t\t}\n-\t\t\temail = username + \"@\" + getHostname();\n+\t\t\temail = username + \"@\" + systemReader.getHostname();\n \t\t}\n \n \t\treturn email;\n@@ -216,31 +201,4 @@ public void load() throws IOException {\n \t\tcore = new CoreConfig(this);\n \t\ttransfer = new TransferConfig(this);\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-\t * @return the canonical hostname\n-\t */\n-\tprivate static String getHostname() {\n-\t\tif (hostname == null) {\n-\t\t\ttry {\n-\t\t\t\tInetAddress localMachine = InetAddress.getLocalHost();\n-\t\t\t\thostname = localMachine.getCanonicalHostName();\n-\t\t\t} catch (UnknownHostException e) {\n-\t\t\t\t// we do nothing\n-\t\t\t\thostname = \"localhost\";\n-\t\t\t}\n-\t\t\tassert hostname != null;\n-\t\t}\n-\t\treturn hostname;\n-\t}\n-\n-\t/**\n-\t * Overrides the default system reader by a custom one.\n-\t * @param newSystemReader new system reader\n-\t */\n-\tpublic static void setSystemReader(SystemReader newSystemReader) {\n-\t\tsystemReader = newSystemReader;\n-\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java b/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\nindex 32c2e20..36c188c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\n@@ -37,6 +37,10 @@\n \n package org.spearce.jgit.util;\n \n+import java.io.File;\n+import java.net.InetAddress;\n+import java.net.UnknownHostException;\n+\n import org.spearce.jgit.lib.RepositoryConfig;\n \n /**\n@@ -47,21 +51,73 @@\n  * permits to control the user's global configuration.\n  * </p>\n  */\n-public interface SystemReader {\n+public abstract class SystemReader {\n+\tprivate static SystemReader INSTANCE = new SystemReader() {\n+\t\tprivate volatile String hostname;\n+\n+\t\tpublic String getenv(String variable) {\n+\t\t\treturn System.getenv(variable);\n+\t\t}\n+\n+\t\tpublic String getProperty(String key) {\n+\t\t\treturn System.getProperty(key);\n+\t\t}\n+\n+\t\tpublic RepositoryConfig openUserConfig() {\n+\t\t\tfinal File home = FS.userHome();\n+\t\t\treturn new RepositoryConfig(null, new File(home, \".gitconfig\"));\n+\t\t}\n+\n+\t\tpublic String getHostname() {\n+\t\t\tif (hostname == null) {\n+\t\t\t\ttry {\n+\t\t\t\t\tInetAddress localMachine = InetAddress.getLocalHost();\n+\t\t\t\t\thostname = localMachine.getCanonicalHostName();\n+\t\t\t\t} catch (UnknownHostException e) {\n+\t\t\t\t\t// we do nothing\n+\t\t\t\t\thostname = \"localhost\";\n+\t\t\t\t}\n+\t\t\t\tassert hostname != null;\n+\t\t\t}\n+\t\t\treturn hostname;\n+\t\t}\n+\t};\n+\n+\t/** @return the live instance to read system properties. */\n+\tpublic static SystemReader getInstance() {\n+\t\treturn INSTANCE;\n+\t}\n+\n+\t/**\n+\t * @param newReader\n+\t *            the new instance to use when accessing properties.\n+\t */\n+\tpublic static void setInstance(SystemReader newReader) {\n+\t\tINSTANCE = newReader;\n+\t}\n+\n+\t/**\n+\t * Gets the hostname of the local host. If no hostname can be found, the\n+\t * hostname is set to the default value \"localhost\".\n+\t *\n+\t * @return the canonical hostname\n+\t */\n+\tpublic abstract String getHostname();\n+\n \t/**\n \t * @param variable system variable to read\n \t * @return value of the system variable\n \t */\n-\tString getenv(String variable);\n+\tpublic abstract String getenv(String variable);\n \n \t/**\n \t * @param key of the system property to read\n \t * @return value of the system property\n \t */\n-\tString getProperty(String key);\n+\tpublic abstract String getProperty(String key);\n \n \t/**\n \t * @return the git configuration found in the user home\n \t */\n-\tRepositoryConfig openUserConfig();\n+\tpublic abstract RepositoryConfig openUserConfig();\n }\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118759","messageId":"1248547982-4003-8-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-7-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 07/19] Correct user config to be of type FileBasedConfig","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:50Z","receivedAt":"2009-07-25T18:52:50Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The user's ~/.gitconfig file is a file, but not a repository specific\nfile.  Since RepositoryConfig has been refactored into base classes\nthat handle file based configuration, we no longer need to use the\nRepositoryConfig type to access ~/.gitconfig.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |    4 ++--\n .../src/org/spearce/jgit/util/SystemReader.java    |    5 +++--\n 2 files changed, 5 insertions(+), 4 deletions(-)\n\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 9be7c1b..5c912b7 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@@ -60,7 +60,7 @@\n \t * @return a new configuration instance to read the user's global\n \t *         configuration file from their home directory.\n \t */\n-\tpublic static RepositoryConfig openUserConfig() {\n+\tpublic static FileBasedConfig openUserConfig() {\n \t\treturn SystemReader.getInstance().openUserConfig();\n \t}\n \n@@ -85,7 +85,7 @@ public static RepositoryConfig openUserConfig() {\n \t * @param cfgLocation\n \t *            path of the file to load (or save).\n \t */\n-\tpublic RepositoryConfig(final RepositoryConfig base, final File cfgLocation) {\n+\tpublic RepositoryConfig(final Config base, final File cfgLocation) {\n \t\tsuper(base, cfgLocation);\n \t}\n \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java b/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\nindex 36c188c..a30dfef 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/util/SystemReader.java\n@@ -41,6 +41,7 @@\n import java.net.InetAddress;\n import java.net.UnknownHostException;\n \n+import org.spearce.jgit.lib.FileBasedConfig;\n import org.spearce.jgit.lib.RepositoryConfig;\n \n /**\n@@ -63,7 +64,7 @@ public String getProperty(String key) {\n \t\t\treturn System.getProperty(key);\n \t\t}\n \n-\t\tpublic RepositoryConfig openUserConfig() {\n+\t\tpublic FileBasedConfig openUserConfig() {\n \t\t\tfinal File home = FS.userHome();\n \t\t\treturn new RepositoryConfig(null, new File(home, \".gitconfig\"));\n \t\t}\n@@ -119,5 +120,5 @@ public static void setInstance(SystemReader newReader) {\n \t/**\n \t * @return the git configuration found in the user home\n \t */\n-\tpublic abstract RepositoryConfig openUserConfig();\n+\tpublic abstract FileBasedConfig openUserConfig();\n }\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118749","messageId":"1248547982-4003-9-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-8-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 08/19] Extract the test specific SystemReader out of RepositoryTestCase","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:51Z","receivedAt":"2009-07-25T18:52:51Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"We may need this in tests that don't extend off RepositoryTestCase,\nas not all tests require a local Git repository to be created in\nthe host filesystem.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../tst/org/spearce/jgit/lib/MockSystemReader.java |   78 ++++++++++++++++++++\n .../org/spearce/jgit/lib/RepositoryConfigTest.java |   49 ++++++------\n .../org/spearce/jgit/lib/RepositoryTestCase.java   |   56 +-------------\n 3 files changed, 107 insertions(+), 76 deletions(-)\n create mode 100644 org.spearce.jgit.test/tst/org/spearce/jgit/lib/MockSystemReader.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/MockSystemReader.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/MockSystemReader.java\nnew file mode 100644\nindex 0000000..62862d1\n--- /dev/null\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/MockSystemReader.java\n@@ -0,0 +1,78 @@\n+/*\n+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>\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+\n+package org.spearce.jgit.lib;\n+\n+import java.util.HashMap;\n+import java.util.Map;\n+\n+import org.spearce.jgit.util.SystemReader;\n+\n+class MockSystemReader extends SystemReader {\n+\tfinal Map<String, String> values = new HashMap<String, String>();\n+\n+\tFileBasedConfig userGitConfig;\n+\n+\tMockSystemReader() {\n+\t\tinit(Constants.OS_USER_NAME_KEY);\n+\t\tinit(Constants.GIT_AUTHOR_NAME_KEY);\n+\t\tinit(Constants.GIT_AUTHOR_EMAIL_KEY);\n+\t\tinit(Constants.GIT_COMMITTER_NAME_KEY);\n+\t\tinit(Constants.GIT_COMMITTER_EMAIL_KEY);\n+\t\tuserGitConfig = new FileBasedConfig(null, null);\n+\t}\n+\n+\tprivate void init(final String n) {\n+\t\tvalues.put(n, n);\n+\t}\n+\n+\tpublic String getenv(String variable) {\n+\t\treturn values.get(variable);\n+\t}\n+\n+\tpublic String getProperty(String key) {\n+\t\treturn values.get(key);\n+\t}\n+\n+\tpublic FileBasedConfig openUserConfig() {\n+\t\treturn userGitConfig;\n+\t}\n+\n+\tpublic String getHostname() {\n+\t\treturn \"fake.host.example.com\";\n+\t}\n+}\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\nindex 5bb9afb..e320679 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n@@ -45,6 +45,8 @@\n import java.util.Arrays;\n import java.util.LinkedList;\n \n+import org.spearce.jgit.util.SystemReader;\n+\n /**\n  * Test reading of git config\n  */\n@@ -113,59 +115,58 @@ public void test006_readCaseInsensitive() throws IOException {\n \t\tassertEquals(\"\", repositoryConfig.getString(\"foo\", null, \"bar\"));\n \t}\n \n-\tpublic void test007_readUserInfos() throws IOException {\n-\t\tfinal String hostname = FAKE_HOSTNAME;\n-\t\tfinal File localConfig = writeTrashFile(\"local.config\", \"\");\n-\t\tSystem.clearProperty(Constants.OS_USER_NAME_KEY);\n-\n-\t\tRepositoryConfig localRepositoryConfig = new RepositoryConfig(userGitConfig, localConfig);\n-\t\tfakeSystemReader.values.clear();\n+\tpublic void test007_readUserConfig() {\n+\t\tfinal MockSystemReader mockSystemReader = (MockSystemReader)SystemReader.getInstance();\n+\t\tfinal String hostname = mockSystemReader.getHostname();\n+\t\tfinal Config userGitConfig = mockSystemReader.userGitConfig;\n+\t\tfinal RepositoryConfig localConfig = db.getConfig();\n+\t\tmockSystemReader.values.clear();\n \n \t\tString authorName;\n \t\tString authorEmail;\n \n \t\t// no values defined nowhere\n-\t\tauthorName = localRepositoryConfig.getAuthorName();\n-\t\tauthorEmail = localRepositoryConfig.getAuthorEmail();\n+\t\tauthorName = localConfig.getAuthorName();\n+\t\tauthorEmail = localConfig.getAuthorEmail();\n \t\tassertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName);\n \t\tassertEquals(Constants.UNKNOWN_USER_DEFAULT + \"@\" + hostname, authorEmail);\n \n \t\t// the system user name is defined\n-\t\tfakeSystemReader.values.put(Constants.OS_USER_NAME_KEY, \"os user name\");\n-\t\tauthorName = localRepositoryConfig.getAuthorName();\n+\t\tmockSystemReader.values.put(Constants.OS_USER_NAME_KEY, \"os user name\");\n+\t\tauthorName = localConfig.getAuthorName();\n \t\tassertEquals(\"os user name\", authorName);\n \n \t\tif (hostname != null && hostname.length() != 0) {\n-\t\t\tauthorEmail = localRepositoryConfig.getAuthorEmail();\n+\t\t\tauthorEmail = localConfig.getAuthorEmail();\n \t\t\tassertEquals(\"os user name@\" + hostname, authorEmail);\n \t\t}\n \n \t\t// the git environment variables are defined\n-\t\tfakeSystemReader.values.put(Constants.GIT_AUTHOR_NAME_KEY, \"git author name\");\n-\t\tfakeSystemReader.values.put(Constants.GIT_AUTHOR_EMAIL_KEY, \"author@email\");\n-\t\tauthorName = localRepositoryConfig.getAuthorName();\n-\t\tauthorEmail = localRepositoryConfig.getAuthorEmail();\n+\t\tmockSystemReader.values.put(Constants.GIT_AUTHOR_NAME_KEY, \"git author name\");\n+\t\tmockSystemReader.values.put(Constants.GIT_AUTHOR_EMAIL_KEY, \"author@email\");\n+\t\tauthorName = localConfig.getAuthorName();\n+\t\tauthorEmail = localConfig.getAuthorEmail();\n \t\tassertEquals(\"git author name\", authorName);\n \t\tassertEquals(\"author@email\", authorEmail);\n \n \t\t// the values are defined in the global configuration\n \t\tuserGitConfig.setString(\"user\", null, \"name\", \"global username\");\n \t\tuserGitConfig.setString(\"user\", null, \"email\", \"author@globalemail\");\n-\t\tauthorName = localRepositoryConfig.getAuthorName();\n-\t\tauthorEmail = localRepositoryConfig.getAuthorEmail();\n+\t\tauthorName = localConfig.getAuthorName();\n+\t\tauthorEmail = localConfig.getAuthorEmail();\n \t\tassertEquals(\"global username\", authorName);\n \t\tassertEquals(\"author@globalemail\", authorEmail);\n \n \t\t// the values are defined in the local configuration\n-\t\tlocalRepositoryConfig.setString(\"user\", null, \"name\", \"local username\");\n-\t\tlocalRepositoryConfig.setString(\"user\", null, \"email\", \"author@localemail\");\n-\t\tauthorName = localRepositoryConfig.getAuthorName();\n-\t\tauthorEmail = localRepositoryConfig.getAuthorEmail();\n+\t\tlocalConfig.setString(\"user\", null, \"name\", \"local username\");\n+\t\tlocalConfig.setString(\"user\", null, \"email\", \"author@localemail\");\n+\t\tauthorName = localConfig.getAuthorName();\n+\t\tauthorEmail = localConfig.getAuthorEmail();\n \t\tassertEquals(\"local username\", authorName);\n \t\tassertEquals(\"author@localemail\", authorEmail);\n \n-\t\tauthorName = localRepositoryConfig.getCommitterName();\n-\t\tauthorEmail = localRepositoryConfig.getCommitterEmail();\n+\t\tauthorName = localConfig.getCommitterName();\n+\t\tauthorEmail = localConfig.getCommitterEmail();\n \t\tassertEquals(\"local username\", authorName);\n \t\tassertEquals(\"author@localemail\", authorEmail);\n \t}\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\nindex 9dfaeef..24a99ca 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryTestCase.java\n@@ -47,9 +47,7 @@\n import java.io.OutputStreamWriter;\n import java.io.Reader;\n import java.util.ArrayList;\n-import java.util.HashMap;\n import java.util.List;\n-import java.util.Map;\n \n import junit.framework.TestCase;\n \n@@ -81,8 +79,6 @@\n \n \tprotected static final PersonIdent jcommitter;\n \n-\tprotected static final String FAKE_HOSTNAME = \"fake.host.example.com\";\n-\n \tstatic {\n \t\tjauthor = new PersonIdent(\"J. Author\", \"jauthor@example.com\");\n \t\tjcommitter = new PersonIdent(\"J. Committer\", \"jcommitter@example.com\");\n@@ -90,38 +86,6 @@\n \n \tprotected boolean packedGitMMAP;\n \n-\tprotected static class FakeSystemReader extends SystemReader {\n-\t\tMap<String, String> values = new HashMap<String, String>();\n-\t\tRepositoryConfig userGitConfig;\n-\t\tpublic String getenv(String variable) {\n-\t\t\treturn values.get(variable);\n-\t\t}\n-\t\tpublic String getProperty(String key) {\n-\t\t\treturn values.get(key);\n-\t\t}\n-\t\tpublic RepositoryConfig openUserConfig() {\n-\t\t\treturn userGitConfig;\n-\t\t}\n-\t\tpublic void setUserGitConfig(RepositoryConfig userGitConfig) {\n-\t\t\tthis.userGitConfig = userGitConfig;\n-\t\t}\n-\t\tpublic String getHostname() {\n-\t\t\treturn FAKE_HOSTNAME;\n-\t\t}\n-\t}\n-\n-\t/**\n-\t * Simulates the reading of system variables and properties.\n-\t * Unit test can control the returned values by manipulating\n-\t * {@link FakeSystemReader#values}.\n-\t */\n-\tprotected static FakeSystemReader fakeSystemReader;\n-\n-\tstatic {\n-\t\tfakeSystemReader = new FakeSystemReader();\n-\t\tSystemReader.setInstance(fakeSystemReader);\n-\t}\n-\n \t/**\n \t * Configure JGit before setting up test repositories.\n \t */\n@@ -241,12 +205,6 @@ protected static void checkFile(File f, final String checkData)\n \n \tprotected Repository db;\n \n-\t/**\n-\t * mock user's global configuration used instead ~/.gitconfig.\n-\t * This configuration can be modified by the tests without any\n-\t * effect for ~/.gitconfig.\n-\t */\n-\tprotected RepositoryConfig userGitConfig;\n \tprivate static Thread shutdownhook;\n \tprivate static List<Runnable> shutDownCleanups = new ArrayList<Runnable>();\n \tprivate static int testcount;\n@@ -278,9 +236,10 @@ public void run() {\n \t\t\tRuntime.getRuntime().addShutdownHook(shutdownhook);\n \t\t}\n \n-\t\tfinal File userGitConfigFile = new File(trash_git, \"usergitconfig\").getAbsoluteFile();\n-\t\tuserGitConfig = new RepositoryConfig(null, userGitConfigFile);\n-\t\tfakeSystemReader.setUserGitConfig(userGitConfig);\n+\t\tfinal MockSystemReader mockSystemReader = new MockSystemReader();\n+\t\tmockSystemReader.userGitConfig = new FileBasedConfig(null, new File(\n+\t\t\t\ttrash_git, \"usergitconfig\"));\n+\t\tSystemReader.setInstance(mockSystemReader);\n \n \t\tdb = new Repository(trash_git);\n \t\tdb.create();\n@@ -302,13 +261,6 @@ public void run() {\n \t\t}\n \n \t\tcopyFile(JGitTestUtil.getTestResourceFile(\"packed-refs\"), new File(trash_git,\"packed-refs\"));\n-\n-\t\tfakeSystemReader.values.clear();\n-\t\tfakeSystemReader.values.put(Constants.OS_USER_NAME_KEY, Constants.OS_USER_NAME_KEY);\n-\t\tfakeSystemReader.values.put(Constants.GIT_AUTHOR_NAME_KEY, Constants.GIT_AUTHOR_NAME_KEY);\n-\t\tfakeSystemReader.values.put(Constants.GIT_AUTHOR_EMAIL_KEY, Constants.GIT_AUTHOR_EMAIL_KEY);\n-\t\tfakeSystemReader.values.put(Constants.GIT_COMMITTER_NAME_KEY, Constants.GIT_COMMITTER_NAME_KEY);\n-\t\tfakeSystemReader.values.put(Constants.GIT_COMMITTER_EMAIL_KEY, Constants.GIT_COMMITTER_EMAIL_KEY);\n \t}\n \n \tprotected void tearDown() throws Exception {\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118758","messageId":"1248547982-4003-10-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-9-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 09/19] Refactor Config hierarchy to make IO more explicit","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:52Z","receivedAt":"2009-07-25T18:52:52Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Accessing a config file may or may not require IO, e.g. if we are\nreading a file that was already loaded from a blob in the repository\nthen there is no additional IO required.  Moving the IO portions out\nof the base Config class helps to isolate it to only where we really\nknow we have to do IO, making it easier to handle the IO conditions.\n\nThis makes it easier to deal with test cases for config files, as\nwe no longer need to perform local disk IO just to test the parse\nand formatting logic.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\nCC: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n .../org/spearce/jgit/lib/RepositoryConfigTest.java |  138 +++----\n .../tst/org/spearce/jgit/lib/T0003_Basic.java      |   16 +-\n .../spearce/jgit/transport/RemoteConfigTest.java   |  166 ++++-----\n .../jgit/errors/ConfigInvalidException.java        |   53 +++\n .../src/org/spearce/jgit/lib/BlobBasedConfig.java  |  110 ++----\n .../src/org/spearce/jgit/lib/Config.java           |  410 ++++++++++----------\n .../src/org/spearce/jgit/lib/FileBasedConfig.java  |   83 +++--\n .../src/org/spearce/jgit/lib/Repository.java       |   24 +-\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |   19 +-\n 9 files changed, 507 insertions(+), 512 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/errors/ConfigInvalidException.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\nindex e320679..d4a6dd2 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n@@ -40,86 +40,77 @@\n \n package org.spearce.jgit.lib;\n \n-import java.io.File;\n-import java.io.IOException;\n import java.util.Arrays;\n import java.util.LinkedList;\n \n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.SystemReader;\n \n /**\n  * Test reading of git config\n  */\n-public class RepositoryConfigTest extends RepositoryTestCase {\n-\t/**\n-\t * Read config item with no value from a section without a subsection.\n-\t *\n-\t * @throws IOException\n-\t */\n-\tpublic void test001_ReadBareKey() throws IOException {\n-\t\tfinal RepositoryConfig repositoryConfig = read(\"[foo]\\nbar\\n\");\n-\t\tassertEquals(true, repositoryConfig.getBoolean(\"foo\", null, \"bar\", false));\n-\t\tassertEquals(\"\", repositoryConfig.getString(\"foo\", null, \"bar\"));\n+public class RepositoryConfigTest extends TestCase {\n+\tpublic void test001_ReadBareKey() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[foo]\\nbar\\n\");\n+\t\tassertEquals(true, c.getBoolean(\"foo\", null, \"bar\", false));\n+\t\tassertEquals(\"\", c.getString(\"foo\", null, \"bar\"));\n \t}\n \n-\t/**\n-\t * Read various data from a subsection.\n-\t *\n-\t * @throws IOException\n-\t */\n-\tpublic void test002_ReadWithSubsection() throws IOException {\n-\t\tfinal RepositoryConfig repositoryConfig = read(\"[foo \\\"zip\\\"]\\nbar\\n[foo \\\"zap\\\"]\\nbar=false\\nn=3\\n\");\n-\t\tassertEquals(true, repositoryConfig.getBoolean(\"foo\", \"zip\", \"bar\", false));\n-\t\tassertEquals(\"\", repositoryConfig.getString(\"foo\",\"zip\", \"bar\"));\n-\t\tassertEquals(false, repositoryConfig.getBoolean(\"foo\", \"zap\", \"bar\", true));\n-\t\tassertEquals(\"false\", repositoryConfig.getString(\"foo\", \"zap\", \"bar\"));\n-\t\tassertEquals(3, repositoryConfig.getInt(\"foo\", \"zap\", \"n\", 4));\n-\t\tassertEquals(4, repositoryConfig.getInt(\"foo\", \"zap\",\"m\", 4));\n+\tpublic void test002_ReadWithSubsection() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[foo \\\"zip\\\"]\\nbar\\n[foo \\\"zap\\\"]\\nbar=false\\nn=3\\n\");\n+\t\tassertEquals(true, c.getBoolean(\"foo\", \"zip\", \"bar\", false));\n+\t\tassertEquals(\"\", c.getString(\"foo\",\"zip\", \"bar\"));\n+\t\tassertEquals(false, c.getBoolean(\"foo\", \"zap\", \"bar\", true));\n+\t\tassertEquals(\"false\", c.getString(\"foo\", \"zap\", \"bar\"));\n+\t\tassertEquals(3, c.getInt(\"foo\", \"zap\", \"n\", 4));\n+\t\tassertEquals(4, c.getInt(\"foo\", \"zap\",\"m\", 4));\n \t}\n \n-\tpublic void test003_PutRemote() throws IOException {\n-\t\tFile cfgFile = writeTrashFile(\"config_003\", \"\");\n-\t\tRepositoryConfig repositoryConfig = new RepositoryConfig(null, cfgFile);\n-\t\trepositoryConfig.setString(\"sec\", \"ext\", \"name\", \"value\");\n-\t\trepositoryConfig.setString(\"sec\", \"ext\", \"name2\", \"value2\");\n-\t\trepositoryConfig.save();\n-\t\tcheckFile(cfgFile, \"[sec \\\"ext\\\"]\\n\\tname = value\\n\\tname2 = value2\\n\");\n+\tpublic void test003_PutRemote() {\n+\t\tfinal Config c = new Config();\n+\t\tc.setString(\"sec\", \"ext\", \"name\", \"value\");\n+\t\tc.setString(\"sec\", \"ext\", \"name2\", \"value2\");\n+\t\tfinal String expText = \"[sec \\\"ext\\\"]\\n\\tname = value\\n\\tname2 = value2\\n\";\n+\t\tassertEquals(expText, c.toText());\n \t}\n \n-\tpublic void test004_PutGetSimple() throws IOException {\n-\t\tFile cfgFile = writeTrashFile(\"config_004\", \"\");\n-\t\tRepositoryConfig repositoryConfig = new RepositoryConfig(null, cfgFile);\n-\t\trepositoryConfig.setString(\"my\", null, \"somename\", \"false\");\n-\t\trepositoryConfig.save();\n-\t\tcheckFile(cfgFile, \"[my]\\n\\tsomename = false\\n\");\n-\t\tassertEquals(\"false\", repositoryConfig\n-\t\t\t\t.getString(\"my\", null, \"somename\"));\n+\tpublic void test004_PutGetSimple() {\n+\t\tConfig c = new Config();\n+\t\tc.setString(\"my\", null, \"somename\", \"false\");\n+\t\tassertEquals(\"false\", c.getString(\"my\", null, \"somename\"));\n+\t\tassertEquals(\"[my]\\n\\tsomename = false\\n\", c.toText());\n \t}\n \n-\tpublic void test005_PutGetStringList() throws IOException {\n-\t\tFile cfgFile = writeTrashFile(\"config_005\", \"\");\n-\t\tRepositoryConfig repositoryConfig = new RepositoryConfig(null, cfgFile);\n+\tpublic void test005_PutGetStringList() {\n+\t\tConfig c = new Config();\n \t\tfinal LinkedList<String> values = new LinkedList<String>();\n \t\tvalues.add(\"value1\");\n \t\tvalues.add(\"value2\");\n-\t\trepositoryConfig.setStringList(\"my\", null, \"somename\", values);\n-\t\trepositoryConfig.save();\n-\t\tassertTrue(Arrays.equals(values.toArray(), repositoryConfig\n-\t\t\t\t.getStringList(\"my\", null, \"somename\")));\n-\t\tcheckFile(cfgFile, \"[my]\\n\\tsomename = value1\\n\\tsomename = value2\\n\");\n+\t\tc.setStringList(\"my\", null, \"somename\", values);\t\t\n+\n+\t\tfinal Object[] expArr = values.toArray();\n+\t\tfinal String[] actArr = c.getStringList(\"my\", null, \"somename\");\n+\t\tassertTrue(Arrays.equals(expArr, actArr));\n+\n+\t\tfinal String expText = \"[my]\\n\\tsomename = value1\\n\\tsomename = value2\\n\";\n+\t\tassertEquals(expText, c.toText());\n \t}\n \n-\tpublic void test006_readCaseInsensitive() throws IOException {\n-\t\tfinal RepositoryConfig repositoryConfig = read(\"[Foo]\\nBar\\n\");\n-\t\tassertEquals(true, repositoryConfig.getBoolean(\"foo\", null, \"bar\", false));\n-\t\tassertEquals(\"\", repositoryConfig.getString(\"foo\", null, \"bar\"));\n+\tpublic void test006_readCaseInsensitive() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[Foo]\\nBar\\n\");\n+\t\tassertEquals(true, c.getBoolean(\"foo\", null, \"bar\", false));\n+\t\tassertEquals(\"\", c.getString(\"foo\", null, \"bar\"));\n \t}\n \n \tpublic void test007_readUserConfig() {\n-\t\tfinal MockSystemReader mockSystemReader = (MockSystemReader)SystemReader.getInstance();\n+\t\tfinal MockSystemReader mockSystemReader = new MockSystemReader();\n+\t\tSystemReader.setInstance(mockSystemReader);\n \t\tfinal String hostname = mockSystemReader.getHostname();\n \t\tfinal Config userGitConfig = mockSystemReader.userGitConfig;\n-\t\tfinal RepositoryConfig localConfig = db.getConfig();\n+\t\tfinal RepositoryConfig localConfig = new RepositoryConfig(userGitConfig, null);\n+\t\tlocalConfig.create();\n \t\tmockSystemReader.values.clear();\n \n \t\tString authorName;\n@@ -171,8 +162,8 @@ public void test007_readUserConfig() {\n \t\tassertEquals(\"author@localemail\", authorEmail);\n \t}\n \n-\tpublic void testReadBoolean_TrueFalse1() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = true\\nb = false\\n\");\n+\tpublic void testReadBoolean_TrueFalse1() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = true\\nb = false\\n\");\n \t\tassertEquals(\"true\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"false\", c.getString(\"s\", null, \"b\"));\n \n@@ -180,8 +171,8 @@ public void testReadBoolean_TrueFalse1() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadBoolean_TrueFalse2() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = TrUe\\nb = fAlSe\\n\");\n+\tpublic void testReadBoolean_TrueFalse2() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = TrUe\\nb = fAlSe\\n\");\n \t\tassertEquals(\"TrUe\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"fAlSe\", c.getString(\"s\", null, \"b\"));\n \n@@ -189,8 +180,8 @@ public void testReadBoolean_TrueFalse2() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadBoolean_YesNo1() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = yes\\nb = no\\n\");\n+\tpublic void testReadBoolean_YesNo1() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = yes\\nb = no\\n\");\n \t\tassertEquals(\"yes\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"no\", c.getString(\"s\", null, \"b\"));\n \n@@ -198,8 +189,8 @@ public void testReadBoolean_YesNo1() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadBoolean_YesNo2() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = yEs\\nb = NO\\n\");\n+\tpublic void testReadBoolean_YesNo2() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = yEs\\nb = NO\\n\");\n \t\tassertEquals(\"yEs\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"NO\", c.getString(\"s\", null, \"b\"));\n \n@@ -207,8 +198,8 @@ public void testReadBoolean_YesNo2() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadBoolean_OnOff1() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = on\\nb = off\\n\");\n+\tpublic void testReadBoolean_OnOff1() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = on\\nb = off\\n\");\n \t\tassertEquals(\"on\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"off\", c.getString(\"s\", null, \"b\"));\n \n@@ -216,8 +207,8 @@ public void testReadBoolean_OnOff1() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadBoolean_OnOff2() throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = ON\\nb = OFF\\n\");\n+\tpublic void testReadBoolean_OnOff2() throws ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = ON\\nb = OFF\\n\");\n \t\tassertEquals(\"ON\", c.getString(\"s\", null, \"a\"));\n \t\tassertEquals(\"OFF\", c.getString(\"s\", null, \"b\"));\n \n@@ -225,7 +216,7 @@ public void testReadBoolean_OnOff2() throws IOException {\n \t\tassertFalse(c.getBoolean(\"s\", \"b\", true));\n \t}\n \n-\tpublic void testReadLong() throws IOException {\n+\tpublic void testReadLong() throws ConfigInvalidException {\n \t\tassertReadLong(1L);\n \t\tassertReadLong(-1L);\n \t\tassertReadLong(Long.MIN_VALUE);\n@@ -242,18 +233,19 @@ public void testReadLong() throws IOException {\n \t\t}\n \t}\n \n-\tprivate void assertReadLong(long exp) throws IOException {\n+\tprivate void assertReadLong(long exp) throws ConfigInvalidException {\n \t\tassertReadLong(exp, String.valueOf(exp));\n \t}\n \n-\tprivate void assertReadLong(long exp, String act) throws IOException {\n-\t\tfinal RepositoryConfig c = read(\"[s]\\na = \" + act + \"\\n\");\n+\tprivate void assertReadLong(long exp, String act)\n+\t\t\tthrows ConfigInvalidException {\n+\t\tfinal Config c = parse(\"[s]\\na = \" + act + \"\\n\");\n \t\tassertEquals(exp, c.getLong(\"s\", null, \"a\", 0L));\n \t}\n \n-\tprivate RepositoryConfig read(final String content) throws IOException {\n-\t\tfinal File p = writeTrashFile(getName() + \".config\", content);\n-\t\tfinal RepositoryConfig c = new RepositoryConfig(null, p);\n+\tprivate Config parse(final String content) throws ConfigInvalidException {\n+\t\tfinal Config c = new Config(null);\n+\t\tc.fromText(content);\n \t\treturn c;\n \t}\n }\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/T0003_Basic.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/T0003_Basic.java\nindex df55b4f..3660b45 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/T0003_Basic.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/T0003_Basic.java\n@@ -47,6 +47,8 @@\n import java.io.IOException;\n import java.io.PrintWriter;\n \n+import org.spearce.jgit.errors.ConfigInvalidException;\n+\n public class T0003_Basic extends RepositoryTestCase {\n \tpublic void test001_Initalize() {\n \t\tfinal File gitdir = new File(trash, \".git\");\n@@ -118,7 +120,7 @@ public void test003_WriteShouldBeEmptyTree() throws IOException {\n \t\tassertTrue(\"Read-only \" + o, !o.canWrite());\n \t}\n \n-\tpublic void test004_CheckNewConfig() throws IOException {\n+\tpublic void test004_CheckNewConfig() {\n \t\tfinal RepositoryConfig c = db.getConfig();\n \t\tassertNotNull(c);\n \t\tassertEquals(\"0\", c.getString(\"core\", null, \"repositoryformatversion\"));\n@@ -126,13 +128,11 @@ public void test004_CheckNewConfig() throws IOException {\n \t\tassertEquals(\"true\", c.getString(\"core\", null, \"filemode\"));\n \t\tassertEquals(\"true\", c.getString(\"cOrE\", null, \"fIlEModE\"));\n \t\tassertNull(c.getString(\"notavalue\", null, \"reallyNotAValue\"));\n-\t\tc.load();\n \t}\n \n-\tpublic void test005_ReadSimpleConfig() throws IOException {\n+\tpublic void test005_ReadSimpleConfig() {\n \t\tfinal RepositoryConfig c = db.getConfig();\n \t\tassertNotNull(c);\n-\t\tc.load();\n \t\tassertEquals(\"0\", c.getString(\"core\", null, \"repositoryformatversion\"));\n \t\tassertEquals(\"0\", c.getString(\"CoRe\", null, \"REPOSITORYFoRmAtVeRsIoN\"));\n \t\tassertEquals(\"true\", c.getString(\"core\", null, \"filemode\"));\n@@ -140,7 +140,8 @@ public void test005_ReadSimpleConfig() throws IOException {\n \t\tassertNull(c.getString(\"notavalue\", null, \"reallyNotAValue\"));\n \t}\n \n-\tpublic void test006_ReadUglyConfig() throws IOException {\n+\tpublic void test006_ReadUglyConfig() throws IOException,\n+\t\t\tConfigInvalidException {\n \t\tfinal RepositoryConfig c = db.getConfig();\n \t\tfinal File cfg = new File(db.getDirectory(), \"config\");\n \t\tfinal FileWriter pw = new FileWriter(cfg);\n@@ -192,7 +193,8 @@ public void test008_FailOnWrongVersion() throws IOException {\n \t\t}\n \t}\n \n-\tpublic void test009_CreateCommitOldFormat() throws IOException {\n+\tpublic void test009_CreateCommitOldFormat() throws IOException,\n+\t\t\tConfigInvalidException {\n \t\twriteTrashFile(\".git/config\", \"[core]\\n\" + \"legacyHeaders=1\\n\");\n \t\tdb.getConfig().load();\n \n@@ -417,8 +419,6 @@ public void test025_computeSha1NoStore() throws IOException {\n \t}\n \n \tpublic void test026_CreateCommitMultipleparents() throws IOException {\n-\t\tdb.getConfig().load();\n-\n \t\tfinal Tree t = new Tree(db);\n \t\tfinal FileTreeEntry f = t.addFile(\"i-am-a-file\");\n \t\twriteTrashFile(f.getName(), \"and this is the data in me\\n\");\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\nindex 3965bdb..752019d 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/transport/RemoteConfigTest.java\n@@ -38,26 +38,28 @@\n \n package org.spearce.jgit.transport;\n \n-import java.io.File;\n-import java.io.FileNotFoundException;\n-import java.io.FileOutputStream;\n-import java.io.IOException;\n-import java.io.UnsupportedEncodingException;\n import java.util.List;\n \n-import org.spearce.jgit.lib.RepositoryTestCase;\n-\n-public class RemoteConfigTest extends RepositoryTestCase {\n-\tprivate void writeConfig(final String dat) throws FileNotFoundException,\n-\t\t\tIOException, UnsupportedEncodingException {\n-\t\tfinal File file = new File(db.getDirectory(), \"config\");\n-\t\tfinal FileOutputStream stream = new FileOutputStream(file, true);\n-\t\ttry {\n-\t\t\tstream.write(dat.getBytes(\"UTF-8\"));\n-\t\t} finally {\n-\t\t\tstream.close();\n-\t\t}\n-\t\tdb.getConfig().load();\n+import junit.framework.TestCase;\n+\n+import org.spearce.jgit.errors.ConfigInvalidException;\n+import org.spearce.jgit.lib.Config;\n+\n+public class RemoteConfigTest extends TestCase {\n+\tprivate Config config;\n+\n+\tprotected void setUp() throws Exception {\n+\t\tsuper.setUp();\n+\t\tconfig = new Config();\n+\t}\n+\n+\tprivate void readConfig(final String dat) throws ConfigInvalidException {\n+\t\tconfig = new Config();\n+\t\tconfig.fromText(dat);\n+\t}\n+\n+\tprivate void checkConfig(final String exp) {\n+\t\tassertEquals(exp, config.toText());\n \t}\n \n \tprivate static void assertEquals(final String exp, final URIish act) {\n@@ -65,11 +67,11 @@ private static void assertEquals(final String exp, final URIish act) {\n \t}\n \n \tpublic void testSimple() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tfinal List<URIish> allURIs = rc.getURIs();\n \t\tRefSpec spec;\n \n@@ -95,30 +97,30 @@ public void testSimple() throws Exception {\n \t}\n \n \tpublic void testSimpleNoTags() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\"\n \t\t\t\t+ \"tagopt = --no-tags\\n\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tassertSame(TagOpt.NO_TAGS, rc.getTagOpt());\n \t}\n \n \tpublic void testSimpleAlwaysTags() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\"\n \t\t\t\t+ \"tagopt = --tags\\n\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tassertSame(TagOpt.FETCH_TAGS, rc.getTagOpt());\n \t}\n \n \tpublic void testMirror() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/heads/*\\n\"\n \t\t\t\t+ \"fetch = refs/tags/*:refs/tags/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tfinal List<URIish> allURIs = rc.getURIs();\n \t\tRefSpec spec;\n \n@@ -148,13 +150,13 @@ public void testMirror() throws Exception {\n \t}\n \n \tpublic void testBackup() throws Exception {\n-\t\twriteConfig(\"[remote \\\"backup\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"backup\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"url = user@repo.or.cz:/srv/git/egit.git\\n\"\n \t\t\t\t+ \"push = +refs/heads/*:refs/heads/*\\n\"\n \t\t\t\t+ \"push = refs/tags/*:refs/tags/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tfinal List<URIish> allURIs = rc.getURIs();\n \t\tRefSpec spec;\n \n@@ -184,13 +186,13 @@ public void testBackup() throws Exception {\n \t}\n \n \tpublic void testUploadPack() throws Exception {\n-\t\twriteConfig(\"[remote \\\"example\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"example\\\"]\\n\"\n \t\t\t\t+ \"url = user@example.com:egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/example/*\\n\"\n \t\t\t\t+ \"uploadpack = /path/to/git/git-upload-pack\\n\"\n \t\t\t\t+ \"receivepack = /path/to/git/git-receive-pack\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"example\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"example\");\n \t\tfinal List<URIish> allURIs = rc.getURIs();\n \t\tRefSpec spec;\n \n@@ -216,9 +218,9 @@ public void testUploadPack() throws Exception {\n \t}\n \n \tpublic void testUnknown() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertEquals(0, rc.getURIs().size());\n \t\tassertEquals(0, rc.getFetchRefSpecs().size());\n \t\tassertEquals(0, rc.getPushRefSpecs().size());\n@@ -227,10 +229,10 @@ public void testUnknown() throws Exception {\n \t}\n \n \tpublic void testAddURI() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n \t\tfinal URIish uri = new URIish(\"/some/dir\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertEquals(0, rc.getURIs().size());\n \n \t\tassertTrue(rc.addURI(uri));\n@@ -242,12 +244,12 @@ public void testAddURI() throws Exception {\n \t}\n \n \tpublic void testRemoveFirstURI() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n \t\tfinal URIish a = new URIish(\"/some/dir\");\n \t\tfinal URIish b = new URIish(\"/another/dir\");\n \t\tfinal URIish c = new URIish(\"/more/dirs\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertTrue(rc.addURI(a));\n \t\tassertTrue(rc.addURI(b));\n \t\tassertTrue(rc.addURI(c));\n@@ -264,12 +266,12 @@ public void testRemoveFirstURI() throws Exception {\n \t}\n \n \tpublic void testRemoveMiddleURI() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n \t\tfinal URIish a = new URIish(\"/some/dir\");\n \t\tfinal URIish b = new URIish(\"/another/dir\");\n \t\tfinal URIish c = new URIish(\"/more/dirs\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertTrue(rc.addURI(a));\n \t\tassertTrue(rc.addURI(b));\n \t\tassertTrue(rc.addURI(c));\n@@ -286,12 +288,12 @@ public void testRemoveMiddleURI() throws Exception {\n \t}\n \n \tpublic void testRemoveLastURI() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n \t\tfinal URIish a = new URIish(\"/some/dir\");\n \t\tfinal URIish b = new URIish(\"/another/dir\");\n \t\tfinal URIish c = new URIish(\"/more/dirs\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertTrue(rc.addURI(a));\n \t\tassertTrue(rc.addURI(b));\n \t\tassertTrue(rc.addURI(c));\n@@ -308,10 +310,10 @@ public void testRemoveLastURI() throws Exception {\n \t}\n \n \tpublic void testRemoveOnlyURI() throws Exception {\n-\t\twriteConfig(\"\");\n+\t\treadConfig(\"\");\n \n \t\tfinal URIish a = new URIish(\"/some/dir\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"backup\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"backup\");\n \t\tassertTrue(rc.addURI(a));\n \n \t\tassertEquals(1, rc.getURIs().size());\n@@ -322,130 +324,102 @@ public void testRemoveOnlyURI() throws Exception {\n \t}\n \n \tpublic void testCreateOrigin() throws Exception {\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"origin\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"origin\");\n \t\trc.addURI(new URIish(\"/some/dir\"));\n \t\trc.addFetchRefSpec(new RefSpec(\"+refs/heads/*:refs/remotes/\"\n \t\t\t\t+ rc.getName() + \"/*\"));\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\");\n \t}\n \n \tpublic void testSaveAddURI() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\trc.addURI(new URIish(\"/some/dir\"));\n \t\tassertEquals(2, rc.getURIs().size());\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"spearce\\\"]\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"\\turl = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \t}\n \n \tpublic void testSaveRemoveLastURI() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"url = /some/dir\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tassertEquals(2, rc.getURIs().size());\n \t\trc.removeURI(new URIish(\"/some/dir\"));\n \t\tassertEquals(1, rc.getURIs().size());\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"spearce\\\"]\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"\\turl = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \t}\n \n \tpublic void testSaveRemoveFirstURI() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"url = /some/dir\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tassertEquals(2, rc.getURIs().size());\n \t\trc.removeURI(new URIish(\"http://www.spearce.org/egit.git\"));\n \t\tassertEquals(1, rc.getURIs().size());\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"spearce\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"spearce\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/spearce/*\\n\");\n \t}\n \n \tpublic void testSaveNoTags() throws Exception {\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"origin\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"origin\");\n \t\trc.addURI(new URIish(\"/some/dir\"));\n \t\trc.addFetchRefSpec(new RefSpec(\"+refs/heads/*:refs/remotes/\"\n \t\t\t\t+ rc.getName() + \"/*\"));\n \t\trc.setTagOpt(TagOpt.NO_TAGS);\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\"\n \t\t\t\t+ \"\\ttagopt = --no-tags\\n\");\n \t}\n \n \tpublic void testSaveAllTags() throws Exception {\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"origin\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"origin\");\n \t\trc.addURI(new URIish(\"/some/dir\"));\n \t\trc.addFetchRefSpec(new RefSpec(\"+refs/heads/*:refs/remotes/\"\n \t\t\t\t+ rc.getName() + \"/*\"));\n \t\trc.setTagOpt(TagOpt.FETCH_TAGS);\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\"\n \t\t\t\t+ \"\\ttagopt = --tags\\n\");\n \t}\n \n \tpublic void testSimpleTimeout() throws Exception {\n-\t\twriteConfig(\"[remote \\\"spearce\\\"]\\n\"\n+\t\treadConfig(\"[remote \\\"spearce\\\"]\\n\"\n \t\t\t\t+ \"url = http://www.spearce.org/egit.git\\n\"\n \t\t\t\t+ \"fetch = +refs/heads/*:refs/remotes/spearce/*\\n\"\n \t\t\t\t+ \"timeout = 12\\n\");\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"spearce\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"spearce\");\n \t\tassertEquals(12, rc.getTimeout());\n \t}\n \n \tpublic void testSaveTimeout() throws Exception {\n-\t\tfinal RemoteConfig rc = new RemoteConfig(db.getConfig(), \"origin\");\n+\t\tfinal RemoteConfig rc = new RemoteConfig(config, \"origin\");\n \t\trc.addURI(new URIish(\"/some/dir\"));\n \t\trc.addFetchRefSpec(new RefSpec(\"+refs/heads/*:refs/remotes/\"\n \t\t\t\t+ rc.getName() + \"/*\"));\n \t\trc.setTimeout(60);\n-\t\trc.update(db.getConfig());\n-\t\tdb.getConfig().save();\n-\n-\t\tcheckFile(new File(db.getDirectory(), \"config\"), \"[core]\\n\"\n-\t\t\t\t+ \"\\trepositoryformatversion = 0\\n\" + \"\\tfilemode = true\\n\"\n-\t\t\t\t+ \"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n+\t\trc.update(config);\n+\t\tcheckConfig(\"[remote \\\"origin\\\"]\\n\" + \"\\turl = /some/dir\\n\"\n \t\t\t\t+ \"\\tfetch = +refs/heads/*:refs/remotes/origin/*\\n\"\n \t\t\t\t+ \"\\ttimeout = 60\\n\");\n \t}\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/errors/ConfigInvalidException.java b/org.spearce.jgit/src/org/spearce/jgit/errors/ConfigInvalidException.java\nnew file mode 100644\nindex 0000000..ae6ad58\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/errors/ConfigInvalidException.java\n@@ -0,0 +1,53 @@\n+/*\n+ * Copyright (C) 2009, Google Inc.\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+\n+package org.spearce.jgit.errors;\n+\n+/** Indicates a text string is not a valid Git style configuration. */\n+public class ConfigInvalidException extends Exception {\n+\tprivate static final long serialVersionUID = 1L;\n+\n+\t/**\n+\t * Construct an invalid configuration error.\n+\t * \n+\t * @param message\n+\t *            why the configuration is invalid.\n+\t */\n+\tpublic ConfigInvalidException(final String message) {\n+\t\tsuper(message);\n+\t}\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 8763c6c..e97d797 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@@ -36,111 +36,85 @@\n  */\n package org.spearce.jgit.lib;\n \n-import java.io.ByteArrayInputStream;\n import java.io.FileNotFoundException;\n import java.io.IOException;\n-import java.io.InputStream;\n-import java.util.concurrent.Callable;\n \n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.treewalk.TreeWalk;\n+import org.spearce.jgit.util.RawParseUtils;\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 * \n \t * @param base\n \t *            the base configuration file\n \t * @param blob\n-\t *            the byte array\n+\t *            the byte array, should be UTF-8 encoded text.\n+\t * @throws ConfigInvalidException\n+\t *             the byte array is not a valid configuration format.\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+\tpublic BlobBasedConfig(Config base, final byte[] blob)\n+\t\t\tthrows ConfigInvalidException {\n+\t\tsuper(base);\n+\t\tfromText(RawParseUtils.decode(blob));\n \t}\n \n \t/**\n \t * The constructor from object identifier\n-\t *\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 * @throws IOException\n+\t *             the blob cannot be read from the repository.\n+\t * @throws ConfigInvalidException\n+\t *             the blob is not a valid configuration format.\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\t\tfinal ObjectId objectId) throws IOException, ConfigInvalidException {\n+\t\tsuper(base);\n+\t\tfinal ObjectLoader loader = r.openBlob(objectId);\n+\t\tif (loader == null)\n+\t\t\tthrow new IOException(\"Blob not found: \" + objectId);\n+\t\tfromText(RawParseUtils.decode(loader.getBytes()));\n \t}\n \n \t/**\n \t * The constructor from commit and path\n-\t *\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 * @throws FileNotFoundException\n+\t *             the path does not exist in the commit's tree.\n+\t * @throws IOException\n+\t *             the tree and/or blob cannot be accessed.\n+\t * @throws ConfigInvalidException\n+\t *             the blob is not a valid configuration format.\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 FileNotFoundException(\"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 (Exception 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+\tpublic BlobBasedConfig(Config base, final Commit commit, final String path)\n+\t\t\tthrows FileNotFoundException, IOException, ConfigInvalidException {\n+\t\tsuper(base);\n+\t\tfinal ObjectId treeId = commit.getTreeId();\n+\t\tfinal Repository r = commit.getRepository();\n+\t\tfinal TreeWalk tree = TreeWalk.forPath(r, path, treeId);\n+\t\tif (tree == null)\n+\t\t\tthrow new FileNotFoundException(\"Entry not found by path: \" + path);\n+\t\tfinal ObjectId blobId = tree.getObjectId(0);\n+\t\tfinal ObjectLoader loader = tree.getRepository().openBlob(blobId);\n+\t\tif (loader == null)\n+\t\t\tthrow new IOException(\"Blob not found: \" + blobId + \" for path: \"\n+\t\t\t\t\t+ path);\n+\t\tfromText(RawParseUtils.decode(loader.getBytes()));\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 a2f5c6a..d63e926 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@@ -41,12 +41,6 @@\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@@ -55,19 +49,18 @@\n import java.util.Map;\n import java.util.Set;\n \n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.StringUtils;\n \n+\n /**\n- * The configuration file stored in the format similar to the \".git/config\"\n- * file.\n+ * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.\n  */\n-public abstract class Config {\n+public class Config {\n \tprivate static final long KiB = 1024;\n \tprivate static final long MiB = 1024 * KiB;\n \tprivate static final long GiB = 1024 * MiB;\n \n-\tprivate boolean fileRead;\n-\n \tprivate List<Entry> entries;\n \n \tfinal Config baseConfig;\n@@ -86,26 +79,21 @@\n \t */\n \tprivate static final String MAGIC_EMPTY_VALUE = new String();\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-\tprotected Config(Config base) {\n-\t\tbaseConfig = base;\n-\t\tclear();\n+\t/** Create a configuration with no default fallback. */\n+\tpublic Config() {\n+\t\tthis(null);\n \t}\n \n \t/**\n-\t * Set file read indicator\n+\t * Create an empty configuration with a fallback for missing keys.\n \t *\n-\t * @param ok\n-\t *            true if file does not need loading\n+\t * @param defaultConfig\n+\t *            the base configuration to be consulted when a key is missing\n+\t *            from this configuration instance.\n \t */\n-\tprotected void setFileRead(boolean ok) {\n-\t\tfileRead = ok;\n+\tpublic Config(Config defaultConfig) {\n+\t\tbaseConfig = defaultConfig;\n+\t\tclear();\n \t}\n \n \t/**\n@@ -373,8 +361,6 @@ 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 (StringUtils.equalsIgnoreCase(section, e.section)\n \t\t\t\t\t&& e.subsection != null)\n@@ -398,22 +384,8 @@ private String getRawString(final String section, final String subsection,\n \t\t\treturn null;\n \t}\n \n-\tprivate void ensureLoaded() {\n-\t\tif (!fileRead) {\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-\t}\n-\n \tprivate Object getRawEntry(final String section, final String subsection,\n \t\t\tfinal String name) {\n-\t\tensureLoaded();\n \t\treturn byName.get(concatenateKey(section, subsection, name));\n \t}\n \n@@ -678,129 +650,116 @@ private int findSectionEnd(final String section, final String subsection) {\n \t}\n \n \t/**\n-\t * Print configuration file to the PrintWriter\n-\t *\n-\t * @param r\n-\t *             stream to write the configuration to.\n+\t * @return this configuration, formatted as a Git style text file.\n \t */\n-\tprotected void printConfig(final PrintWriter r) {\n+\tpublic String toText() {\n+\t\tfinal StringBuilder out = new StringBuilder();\n \t\tfor (final Entry e : entries) {\n-\t\t\tif (e.prefix != null) {\n-\t\t\t\tr.print(e.prefix);\n-\t\t\t}\n+\t\t\tif (e.prefix != null)\n+\t\t\t\tout.append(e.prefix);\t\t\t\n \t\t\tif (e.section != null && e.name == null) {\n-\t\t\t\tr.print('[');\n-\t\t\t\tr.print(e.section);\n+\t\t\t\tout.append('[');\n+\t\t\t\tout.append(e.section);\n \t\t\t\tif (e.subsection != null) {\n-\t\t\t\t\tr.print(' ');\n-\t\t\t\t\tr.print('\"');\n-\t\t\t\t\tr.print(escapeValue(e.subsection));\n-\t\t\t\t\tr.print('\"');\n+\t\t\t\t\tout.append(' ');\n+\t\t\t\t\tout.append('\"');\n+\t\t\t\t\tout.append(escapeValue(e.subsection));\n+\t\t\t\t\tout.append('\"');\n \t\t\t\t}\n-\t\t\t\tr.print(']');\n+\t\t\t\tout.append(']');\n \t\t\t} else if (e.section != 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.prefix == null || \"\".equals(e.prefix))\n+\t\t\t\t\tout.append('\\t');\n+\t\t\t\tout.append(e.name);\n \t\t\t\tif (e.value != null) {\n \t\t\t\t\tif (MAGIC_EMPTY_VALUE != 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\tout.append(\" = \");\n+\t\t\t\t\t\tout.append(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\tif (e.suffix != null)\n+\t\t\t\t\tout.append(' ');\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.print('\\n');\n+\t\t\tif (e.suffix != null)\n+\t\t\t\tout.append(e.suffix);\n+\t\t\tout.append('\\n');\n \t\t}\n+\t\treturn out.toString();\n \t}\n \n \t/**\n-\t * Read the config file\n+\t * Clear this configuration and reset to the contents of the parsed string.\n \t *\n-\t * @throws IOException in case of IO error\n+\t * @param text\n+\t *            Git style text file listing configuration properties.\n+\t * @throws ConfigInvalidException\n+\t *             the text supplied is not formatted correctly. No changes were\n+\t *             made to {@code this}.\n \t */\n-\tpublic void load() throws IOException {\n-\t\tclear();\n-\t\tfileRead = 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.section != 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.section == 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 section header.\n-\t\t\t\t\te.section = readSectionName(r);\n-\t\t\t\t\tinput = r.read();\n-\t\t\t\t\tif ('\"' == input) {\n-\t\t\t\t\t\te.subsection = 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.section = last.section;\n-\t\t\t\t\te.subsection = last.subsection;\n-\t\t\t\t\tr.reset();\n-\t\t\t\t\te.name = readKeyName(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+\tpublic void fromText(final String text) throws ConfigInvalidException {\n+\t\tentries = new ArrayList<Entry>();\n+\t\tbyName = new HashMap<String, Object>();\n+\n+\t\tfinal StringReader in = new StringReader(text);\n+\t\tEntry last = null;\n+\t\tEntry e = new Entry();\n+\t\tfor (;;) {\n+\t\t\tint input = in.read();\n+\t\t\tif (-1 == input)\n+\t\t\t\tbreak;\n+\n+\t\t\tfinal char c = (char) input;\n+\t\t\tif ('\\n' == c) {\n+\t\t\t\t// End of this entry.\n+\t\t\t\tadd(e);\n+\t\t\t\tif (e.section != null)\n+\t\t\t\t\tlast = e;\n+\t\t\t\te = new Entry();\n+\n+\t\t\t} else if (e.suffix != null) {\n+\t\t\t\t// Everything up until the end-of-line is in the suffix.\n+\t\t\t\te.suffix += c;\n+\n+\t\t\t} else if (';' == c || '#' == c) {\n+\t\t\t\t// The rest of this line is a comment; put into suffix.\n+\t\t\t\te.suffix = String.valueOf(c);\n+\n+\t\t\t} else if (e.section == null && Character.isWhitespace(c)) {\n+\t\t\t\t// Save the leading whitespace (if any).\n+\t\t\t\tif (e.prefix == null)\n+\t\t\t\t\te.prefix = \"\";\n+\t\t\t\te.prefix += c;\n+\n+\t\t\t} else if ('[' == c) {\n+\t\t\t\t// This is a section header.\n+\t\t\t\te.section = readSectionName(in);\n+\t\t\t\tinput = in.read();\n+\t\t\t\tif ('\"' == input) {\n+\t\t\t\t\te.subsection = readValue(in, true, '\"');\n+\t\t\t\t\tinput = in.read();\n \t\t\t\t}\n-\t\t\t}\n-\t\t} finally {\n-\t\t\tr.close();\n+\t\t\t\tif (']' != input)\n+\t\t\t\t\tthrow new ConfigInvalidException(\"Bad group header\");\n+\t\t\t\te.suffix = \"\";\n+\n+\t\t\t} else if (last != null) {\n+\t\t\t\t// Read a value.\n+\t\t\t\te.section = last.section;\n+\t\t\t\te.subsection = last.subsection;\n+\t\t\t\tin.reset();\n+\t\t\t\te.name = readKeyName(in);\n+\t\t\t\tif (e.name.endsWith(\"\\n\")) {\n+\t\t\t\t\te.name = e.name.substring(0, e.name.length() - 1);\n+\t\t\t\t\te.value = MAGIC_EMPTY_VALUE;\n+\t\t\t\t} else\n+\t\t\t\t\te.value = readValue(in, false, -1);\n+\n+\t\t\t} else\n+\t\t\t\tthrow new ConfigInvalidException(\"Invalid line in config file\");\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@@ -827,132 +786,136 @@ private void add(final Entry e) {\n \t\t}\n \t}\n \n-\tprivate static String readSectionName(final BufferedReader r)\n-\t\t\tthrows IOException {\n-\t\tfinal StringBuffer name = new StringBuffer();\n+\tprivate static String readSectionName(final StringReader in)\n+\t\t\tthrows ConfigInvalidException {\n+\t\tfinal StringBuilder name = new StringBuilder();\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\tint c = in.read();\n+\t\t\tif (c < 0)\n+\t\t\t\tthrow new ConfigInvalidException(\"Unexpected end of config file\");\n+\n+\t\t\tif (']' == c) {\n+\t\t\t\tin.reset();\n \t\t\t\tbreak;\n-\t\t\t} else if (' ' == c || '\\t' == c) {\n+\t\t\t}\n+\n+\t\t\tif (' ' == 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\tc = in.read();\n+\t\t\t\t\tif (c < 0)\n+\t\t\t\t\t\tthrow new ConfigInvalidException(\"Unexpected end of config file\");\n+\n+\t\t\t\t\tif ('\"' == c) {\n+\t\t\t\t\t\tin.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 section entry. : \" + name\n-\t\t\t\t\t\t\t\t+ \",\" + c);\n \t\t\t\t\t}\n+\n+\t\t\t\t\tif (' ' == c || '\\t' == c)\n+\t\t\t\t\t\tcontinue; // Skipped...\n+\t\t\t\t\tthrow new ConfigInvalidException(\"Bad section entry: \" + name);\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\tname.append((char) c);\n-\t\t\t} else {\n-\t\t\t\tthrow new IOException(\"Bad section entry. : \" + name + \", \" + c);\n \t\t\t}\n+\n+\t\t\tif (Character.isLetterOrDigit((char) c) || '.' == c || '-' == c)\n+\t\t\t\tname.append((char) c);\n+\t\t\telse\n+\t\t\t\tthrow new ConfigInvalidException(\"Bad section entry: \" + name);\n \t\t}\n \t\treturn name.toString();\n \t}\n \n-\tprivate static String readKeyName(final BufferedReader r)\n-\t\t\tthrows IOException {\n+\tprivate static String readKeyName(final StringReader in)\n+\t\t\tthrows ConfigInvalidException {\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\tint c = in.read();\n+\t\t\tif (c < 0)\n+\t\t\t\tthrow new ConfigInvalidException(\"Unexpected end of config file\");\n+\n+\t\t\tif ('=' == c)\n \t\t\t\tbreak;\n-\t\t\t} else if (' ' == c || '\\t' == c) {\n+\n+\t\t\tif (' ' == 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\tc = in.read();\n+\t\t\t\t\tif (c < 0)\n+\t\t\t\t\t\tthrow new ConfigInvalidException(\"Unexpected end of config file\");\n+\n+\t\t\t\t\tif ('=' == 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+\n+\t\t\t\t\tif (';' == c || '#' == c || '\\n' == c) {\n+\t\t\t\t\t\tin.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+\n+\t\t\t\t\tif (' ' == c || '\\t' == c) \n+\t\t\t\t\t\tcontinue; // Skipped...\n+\t\t\t\t\tthrow new ConfigInvalidException(\"Bad entry delimiter\");\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}\n+\n+\t\t\tif (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\tin.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\t} else\n+\t\t\t\tthrow new ConfigInvalidException(\"Bad entry name: \" + name);\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+\tprivate static String readValue(final StringReader in, boolean quote,\n+\t\t\tfinal int eol) throws ConfigInvalidException {\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\tint c = in.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\t\tthrow new ConfigInvalidException(\"Unexpected end of config file\");\n \t\t\t\tbreak;\n \t\t\t}\n+\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\tif (quote)\n+\t\t\t\t\tthrow new ConfigInvalidException(\"Newline in quotes not allowed\");\n+\t\t\t\tin.reset();\n \t\t\t\tbreak;\n \t\t\t}\n-\t\t\tif (eol == c) {\n+\n+\t\t\tif (eol == c)\n \t\t\t\tbreak;\n-\t\t\t}\n+\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\tin.reset();\n \t\t\t\t\tbreak;\n \t\t\t\t}\n \t\t\t}\n+\n \t\t\tif (space) {\n-\t\t\t\tif (value.length() > 0) {\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+\n \t\t\tif ('\\\\' == c) {\n-\t\t\t\tc = r.read();\n+\t\t\t\tc = in.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\t\tthrow new ConfigInvalidException(\"End of file in escape\");\n \t\t\t\tcase '\\n':\n \t\t\t\t\tcontinue;\n \t\t\t\tcase 't':\n@@ -971,13 +934,15 @@ private static String readValue(final BufferedReader r, boolean quote,\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\tthrow new ConfigInvalidException(\"Bad escape: \" + ((char) c));\n \t\t\t\t}\n \t\t\t}\n+\n \t\t\tif ('\"' == c) {\n \t\t\t\tquote = !quote;\n \t\t\t\tcontinue;\n \t\t\t}\n+\n \t\t\tvalue.append((char) c);\n \t\t}\n \t\treturn value.length() > 0 ? value.toString() : null;\n@@ -1040,4 +1005,27 @@ private static boolean eqSameCase(final String a, final String b) {\n \t\t\treturn a.equals(b);\n \t\t}\n \t}\n+\n+\tprivate static class StringReader {\n+\t\tprivate final char[] buf;\n+\n+\t\tprivate int pos;\n+\n+\t\tStringReader(final String in) {\n+\t\t\tbuf = in.toCharArray();\n+\t\t}\n+\n+\t\tint read() {\n+\t\t\ttry {\n+\t\t\t\treturn buf[pos++];\n+\t\t\t} catch (ArrayIndexOutOfBoundsException e) {\n+\t\t\t\tpos = buf.length;\n+\t\t\t\treturn -1;\n+\t\t\t}\n+\t\t}\n+\n+\t\tvoid reset() {\n+\t\t\tpos--;\n+\t\t}\n+\t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\nindex aa1dbee..a419e7f 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/FileBasedConfig.java\n@@ -40,20 +40,18 @@\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.FileNotFoundException;\n import java.io.IOException;\n-import java.io.InputStream;\n-import java.io.OutputStreamWriter;\n-import java.io.PrintWriter;\n+\n+import org.spearce.jgit.errors.ConfigInvalidException;\n+import org.spearce.jgit.util.NB;\n+import org.spearce.jgit.util.RawParseUtils;\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@@ -69,47 +67,58 @@ public FileBasedConfig(Config base, File cfgLocation) {\n \t\tconfigFile = cfgLocation;\n \t}\n \n+\t/** @return location of the configuration file on disk */\n+\tpublic final File getFile() {\n+\t\treturn configFile;\n+\t}\n+\n \t/**\n-\t * Save config data to the git config file\n+\t * Load the configuration as a Git text style configuration file.\n+\t * <p>\n+\t * If the file does not exist, this configuration is cleared, and thus\n+\t * behaves the same as though the file exists, but is empty.\n \t *\n \t * @throws IOException\n+\t *             the file could not be read (but does exist).\n+\t * @throws ConfigInvalidException\n+\t *             the file is not a properly formatted configuration file.\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\tboolean ok = false;\n+\tpublic void load() throws IOException, ConfigInvalidException {\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\tfromText(RawParseUtils.decode(NB.readFully(getFile())));\n+\t\t} catch (FileNotFoundException noFile) {\n+\t\t\tclear();\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+\t * Save the configuration as a Git text style configuration file.\n+\t * <p>\n+\t * <b>Warning:</b> Although this method uses the traditional Git file\n+\t * locking approach to protect against concurrent writes of the\n+\t * configuration file, it does not ensure that the file has not been\n+\t * modified since the last read, which means updates performed by other\n+\t * objects accessing the same backing file may be lost.\n+\t *\n+\t * @throws IOException\n+\t *             the file could not be written.\n+\t */\n+\tpublic void save() throws IOException {\n+\t\tfinal byte[] out = Constants.encode(toText());\n+\t\tfinal LockFile lf = new LockFile(getFile());\n+\t\tif (!lf.lock())\n+\t\t\tthrow new IOException(\"Cannot lock \" + getFile());\n+\t\ttry {\n+\t\t\tlf.write(out);\n+\t\t\tif (!lf.commit())\n+\t\t\t\tthrow new IOException(\"Cannot commit write to \" + getFile());\n+\t\t} finally {\n+\t\t\tlf.unlock();\n+\t\t}\n \t}\n \n \t@Override\n \tpublic String toString() {\n-\t\treturn getClass().getSimpleName() + \"[\" + configFile.getPath() + \"]\";\n+\t\treturn getClass().getSimpleName() + \"[\" + getFile().getPath() + \"]\";\n \t}\n }\n\\ No newline at end of file\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 98a276b..4e987e1 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -56,9 +56,11 @@\n import java.util.Vector;\n import java.util.concurrent.atomic.AtomicInteger;\n \n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.errors.IncorrectObjectTypeException;\n import org.spearce.jgit.errors.RevisionSyntaxException;\n import org.spearce.jgit.util.FS;\n+import org.spearce.jgit.util.SystemReader;\n \n /**\n  * Represents a Git repository. A repository holds all objects and refs used for\n@@ -114,10 +116,28 @@ public Repository(final File d) throws IOException {\n \t\tgitDir = d.getAbsoluteFile();\n \t\trefs = new RefDatabase(this);\n \t\tobjectDatabase = new ObjectDirectory(FS.resolve(gitDir, \"objects\"));\n-\t\tconfig = new RepositoryConfig(this);\n+\n+\t\tfinal FileBasedConfig userConfig;\n+\t\tuserConfig = SystemReader.getInstance().openUserConfig();\n+\t\ttry {\n+\t\t\tuserConfig.load();\n+\t\t} catch (ConfigInvalidException e1) {\n+\t\t\tIOException e2 = new IOException(\"User config file \"\n+\t\t\t\t\t+ userConfig.getFile().getAbsolutePath() + \" invalid: \"\n+\t\t\t\t\t+ e1);\n+\t\t\te2.initCause(e1);\n+\t\t\tthrow e2;\n+\t\t}\n+\t\tconfig = new RepositoryConfig(userConfig, FS.resolve(gitDir, \"config\"));\n \n \t\tif (objectDatabase.exists()) {\n-\t\t\tgetConfig().load();\n+\t\t\ttry {\n+\t\t\t\tgetConfig().load();\n+\t\t\t} catch (ConfigInvalidException e1) {\n+\t\t\t\tIOException e2 = new IOException(\"Unknown repository format\");\n+\t\t\t\te2.initCause(e1);\n+\t\t\t\tthrow e2;\n+\t\t\t}\n \t\t\tfinal String repositoryFormatVersion = getConfig().getString(\n \t\t\t\t\t\"core\", null, \"repositoryFormatVersion\");\n \t\t\tif (!\"0\".equals(repositoryFormatVersion)) {\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 5c912b7..fb97747 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@@ -44,7 +44,7 @@\n import java.io.File;\n import java.io.IOException;\n \n-import org.spearce.jgit.util.FS;\n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.SystemReader;\n \n /**\n@@ -54,16 +54,6 @@\n  * file depending on how it is instantiated.\n  */\n public class RepositoryConfig extends FileBasedConfig {\n-\t/**\n-\t * Obtain a new configuration instance for ~/.gitconfig.\n-\t *\n-\t * @return a new configuration instance to read the user's global\n-\t *         configuration file from their home directory.\n-\t */\n-\tpublic static FileBasedConfig openUserConfig() {\n-\t\treturn SystemReader.getInstance().openUserConfig();\n-\t}\n-\n \t/** Section name for a branch configuration. */\n \tpublic static final String BRANCH_SECTION = \"branch\";\n \n@@ -71,10 +61,6 @@ public static FileBasedConfig openUserConfig() {\n \n \tTransferConfig transfer;\n \n-\tRepositoryConfig(final Repository repo) {\n-\t\tthis(openUserConfig(), FS.resolve(repo.getDirectory(), \"config\"));\n-\t}\n-\n \t/**\n \t * Create a Git configuration file reader/writer/cache for a specific file.\n \t *\n@@ -187,7 +173,6 @@ private String getUserEmailInternal(String gitVariableKey) {\n \t */\n \tpublic void create() {\n \t\tclear();\n-\t\tsetFileRead(true);\n \t\tsetInt(\"core\", null, \"repositoryformatversion\", 0);\n \t\tsetBoolean(\"core\", null, \"filemode\", true);\n \n@@ -196,7 +181,7 @@ public void create() {\n \t}\n \n \t@Override\n-\tpublic void load() throws IOException {\n+\tpublic void load() throws IOException, ConfigInvalidException {\n \t\tsuper.load();\n \t\tcore = new CoreConfig(this);\n \t\ttransfer = new TransferConfig(this);\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118753","messageId":"1248547982-4003-11-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-10-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 10/19] Test for the config file when creating a new repository","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:53Z","receivedAt":"2009-07-25T18:52:53Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"If the configuration file already exists then we should assume that\nthe repository also exists.  Rather than computing the file name\nwe should rely upon the computation done in the constructor, whose\nresult is held in the RepositoryConfig's file property.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |    4 ++--\n 1 files changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 4e987e1..7fb1ef7 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -171,7 +171,8 @@ public synchronized void create() throws IOException {\n \t *             in case of IO problem\n \t */\n \tpublic void create(boolean bare) throws IOException {\n-\t\tif ((bare ? new File(gitDir, \"config\") : gitDir).exists()) {\n+\t\tfinal RepositoryConfig cfg = getConfig();\n+\t\tif (cfg.getFile().exists()) {\n \t\t\tthrow new IllegalStateException(\"Repository already exists: \"\n \t\t\t\t\t+ gitDir);\n \t\t}\n@@ -184,7 +185,6 @@ public void create(boolean bare) throws IOException {\n \t\tfinal String master = Constants.R_HEADS + Constants.MASTER;\n \t\trefs.link(Constants.HEAD, master);\n \n-\t\tRepositoryConfig cfg = getConfig();\n \t\tcfg.create();\n \t\tif (bare)\n \t\t\tcfg.setBoolean(\"core\", null, \"bare\", true);\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118752","messageId":"1248547982-4003-12-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-11-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 11/19] Remove the map lookup for values in Config","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:54Z","receivedAt":"2009-07-25T18:52:54Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Update operations are already O(N) time in the config file, as we\nscan through every entry in the file, in order, looking to see if\nit matches the key we need to mutate.  As most configurations are\nrather small in size the running time is trivial.  Doing the same\nfor lookup simplifies our code considerably.\n\nApplications really want a different type of fast lookup strategy.\nInstead of by key lookups they want something like CoreConfig\nor RemoteConfig to be cached and quickly obtainable on demand,\nwhere the all relevant values have been parsed from their string\ncontent representation into the application specific data types.\nThis change does not provide that functionality, but rather assumes\nwe will do so in the near future.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |  129 +++++---------------\n 1 files changed, 30 insertions(+), 99 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 d63e926..0f91412 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@@ -43,10 +43,8 @@\n \n import java.util.ArrayList;\n import java.util.Collections;\n-import java.util.HashMap;\n import java.util.HashSet;\n import java.util.List;\n-import java.util.Map;\n import java.util.Set;\n \n import org.spearce.jgit.errors.ConfigInvalidException;\n@@ -66,11 +64,6 @@\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 * <p>\n \t * This value is tested for reference equality in some contexts, so we\n@@ -334,19 +327,9 @@ public String getString(final String section, String subsection,\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\tr[i] = ((Entry) lst.get(i)).value;\n-\t\t\treturn r;\n-\t\t}\n-\n-\t\tif (o instanceof Entry) {\n-\t\t\treturn new String[] { ((Entry) o).value };\n-\t\t}\n-\n+\t\tfinal List<String> lst = getRawStringList(section, subsection, name);\n+\t\tif (lst != null)\n+\t\t\treturn lst.toArray(new String[lst.size()]);\n \t\tif (baseConfig != null)\n \t\t\treturn baseConfig.getStringList(section, subsection, name);\n \t\treturn new String[0];\n@@ -373,43 +356,36 @@ public String getString(final String section, String subsection,\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\tfinal List<String> lst = getRawStringList(section, subsection, name);\n+\t\tif (lst != null)\n+\t\t\treturn lst.get(0);\n+\t\telse 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\treturn byName.get(concatenateKey(section, subsection, name));\n+\tprivate List<String> getRawStringList(final String section,\n+\t\t\tfinal String subsection, final String name) {\n+\t\tList<String> r = null;\n+\t\tfor (final Entry e : entries) {\n+\t\t\tif (e.match(section, subsection, name))\n+\t\t\t\tr = add(r, e.value);\n+\t\t}\n+\t\treturn r;\n \t}\n \n-\t/**\n-\t * Create simple a key name from the key components\n-\t *\n-\t * @param section\n-\t *            the section name\n-\t * @param subsection\n-\t *            the subsection name\n-\t * @param name\n-\t *            the key name\n-\t * @return a simple key name that have all components concatenated and the\n-\t *         case converted\n-\t */\n-\tprivate static String concatenateKey(final String section,\n-\t\t\tfinal String subsection, final String name) {\n-\t\tString ss;\n-\t\tif (subsection != null)\n-\t\t\tss = \".\" + subsection;\n-\t\telse\n-\t\t\tss = \"\";\n-\t\treturn StringUtils.toLowerCase(section) + ss + \".\"\n-\t\t\t\t+ StringUtils.toLowerCase(name);\n+\tprivate static List<String> add(final List<String> curr, final String value) {\n+\t\tif (curr == null)\n+\t\t\treturn Collections.singletonList(value);\n+\t\tif (curr.size() == 1) {\n+\t\t\tfinal List<String> r = new ArrayList<String>(2);\n+\t\t\tr.add(curr.get(0));\n+\t\t\tr.add(value);\n+\t\t\treturn r;\n+\t\t}\n+\t\tcurr.add(value);\n+\t\treturn curr;\n \t}\n \n \t/**\n@@ -551,31 +527,6 @@ public void unset(final String section, final String subsection,\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 = concatenateKey(section, subsection, name);\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.section = section;\n-\t\t\te.subsection = 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.section = section;\n-\t\t\t\te.subsection = 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@@ -697,9 +648,7 @@ public String toText() {\n \t *             made to {@code this}.\n \t */\n \tpublic void fromText(final String text) throws ConfigInvalidException {\n-\t\tentries = new ArrayList<Entry>();\n-\t\tbyName = new HashMap<String, Object>();\n-\n+\t\tfinal List<Entry> newEntries = new ArrayList<Entry>();\n \t\tfinal StringReader in = new StringReader(text);\n \t\tEntry last = null;\n \t\tEntry e = new Entry();\n@@ -711,7 +660,7 @@ public void fromText(final String text) throws ConfigInvalidException {\n \t\t\tfinal char c = (char) input;\n \t\t\tif ('\\n' == c) {\n \t\t\t\t// End of this entry.\n-\t\t\t\tadd(e);\n+\t\t\t\tnewEntries.add(e);\n \t\t\t\tif (e.section != null)\n \t\t\t\t\tlast = e;\n \t\t\t\te = new Entry();\n@@ -757,6 +706,8 @@ public void fromText(final String text) throws ConfigInvalidException {\n \t\t\t} else\n \t\t\t\tthrow new ConfigInvalidException(\"Invalid line in config file\");\n \t\t}\n+\n+\t\tentries = newEntries;\n \t}\n \n \t/**\n@@ -764,26 +715,6 @@ public void fromText(final String text) throws ConfigInvalidException {\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.section != null && e.name != null) {\n-\t\t\tfinal String key = concatenateKey(e.section, e.subsection, e.name);\n-\t\t\tfinal Object o = byName.get(key);\n-\t\t\tif (o == null) {\n-\t\t\t\tbyName.put(key, e);\n-\t\t\t} else if (o instanceof Entry) {\n-\t\t\t\tfinal ArrayList<Object> l = new ArrayList<Object>();\n-\t\t\t\tl.add(o);\n-\t\t\t\tl.add(e);\n-\t\t\t\tbyName.put(key, l);\n-\t\t\t} else if (o instanceof List) {\n-\t\t\t\t((List<Entry>) o).add(e);\n-\t\t\t}\n-\t\t}\n \t}\n \n \tprivate static String readSectionName(final StringReader in)\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118754","messageId":"1248547982-4003-13-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-12-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 12/19] Return base values first from Config.getStringList()","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:55Z","receivedAt":"2009-07-25T18:52:55Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"C Git's `git config --get-all foo.bar` returns the base configuration\nvalues for foo.bar (aka those found in /etc/gitconfig or ~/.gitconfig)\nbefore the repository specific configuration values for foo.bar.  To\nbetter match C Git behavior when processing a multi-value key we must\ndo the same in our getStringList() method.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |   24 +++++++++++++++----\n 1 files changed, 19 insertions(+), 5 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 0f91412..f4e78f3 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@@ -55,6 +55,7 @@\n  * Git style {@code .config}, {@code .gitconfig}, {@code .gitmodules} file.\n  */\n public class Config {\n+\tprivate static final String[] EMPTY_STRING_ARRAY = {};\n \tprivate static final long KiB = 1024;\n \tprivate static final long MiB = 1024 * KiB;\n \tprivate static final long GiB = 1024 * MiB;\n@@ -316,6 +317,9 @@ public String getString(final String section, String subsection,\n \n \t/**\n \t * Get a list of string values\n+\t * <p>\n+\t * If this instance was created with a base, the base's values are returned\n+\t * first (if any).\n \t *\n \t * @param section\n \t *            the section\n@@ -327,12 +331,22 @@ public String getString(final String section, String subsection,\n \t */\n \tpublic String[] getStringList(final String section, String subsection,\n \t\t\tfinal String name) {\n-\t\tfinal List<String> lst = getRawStringList(section, subsection, name);\n-\t\tif (lst != null)\n-\t\t\treturn lst.toArray(new String[lst.size()]);\n+\t\tfinal String[] baseList;\n \t\tif (baseConfig != null)\n-\t\t\treturn baseConfig.getStringList(section, subsection, name);\n-\t\treturn new String[0];\n+\t\t\tbaseList = baseConfig.getStringList(section, subsection, name);\n+\t\telse\n+\t\t\tbaseList = EMPTY_STRING_ARRAY;\n+\n+\t\tfinal List<String> lst = getRawStringList(section, subsection, name);\n+\t\tif (lst != null) {\n+\t\t\tfinal String[] res = new String[baseList.length + lst.size()];\n+\t\t\tint idx = baseList.length;\n+\t\t\tSystem.arraycopy(baseList, 0, res, 0, idx);\n+\t\t\tfor (final String val : lst)\n+\t\t\t\tres[idx++] = val;\n+\t\t\treturn res;\n+\t\t}\n+\t\treturn baseList;\n \t}\n \n \t/**\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118756","messageId":"1248547982-4003-14-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-13-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 13/19] Make Config thread safe by using copy-on-write semantics","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:56Z","receivedAt":"2009-07-25T18:52:56Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"It is relatively rare that the Config instance is modified from\napplication code.  When it is, we usually are working on a small\nset of values read from .git/config or ~/.gitconfig.  Making the\nclass thread-safe through basic copy-on-write semantics for its\ninner collection of entries simplifies the locking and permits us\nto optimize for the much more common read path.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |   85 +++++++++++++++++---\n 1 files changed, 72 insertions(+), 13 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 f4e78f3..4d4b315 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@@ -46,6 +46,7 @@\n import java.util.HashSet;\n import java.util.List;\n import java.util.Set;\n+import java.util.concurrent.atomic.AtomicReference;\n \n import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.StringUtils;\n@@ -60,9 +61,15 @@\n \tprivate static final long MiB = 1024 * KiB;\n \tprivate static final long GiB = 1024 * MiB;\n \n-\tprivate List<Entry> entries;\n+\t/**\n+\t * Immutable current state of the configuration data.\n+\t * <p>\n+\t * This state is copy-on-write. It should always contain an immutable list\n+\t * of the configuration keys/values.\n+\t */\n+\tprivate final AtomicReference<State> state;\n \n-\tfinal Config baseConfig;\n+\tprivate final Config baseConfig;\n \n \t/**\n \t * Magic value indicating a missing entry.\n@@ -87,7 +94,7 @@ public Config() {\n \t */\n \tpublic Config(Config defaultConfig) {\n \t\tbaseConfig = defaultConfig;\n-\t\tclear();\n+\t\tstate = new AtomicReference<State>(newState());\n \t}\n \n \t/**\n@@ -358,7 +365,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\tfor (final Entry e : entries) {\n+\t\tfor (final Entry e : state.get().entryList) {\n \t\t\tif (StringUtils.equalsIgnoreCase(section, e.section)\n \t\t\t\t\t&& e.subsection != null)\n \t\t\t\tresult.add(e.subsection);\n@@ -382,7 +389,7 @@ else if (baseConfig != null)\n \tprivate List<String> getRawStringList(final String section,\n \t\t\tfinal String subsection, final String name) {\n \t\tList<String> r = null;\n-\t\tfor (final Entry e : entries) {\n+\t\tfor (final Entry e : state.get().entryList) {\n \t\t\tif (e.match(section, subsection, name))\n \t\t\t\tr = add(r, e.value);\n \t\t}\n@@ -541,6 +548,17 @@ public void unset(final String section, final String subsection,\n \t */\n \tpublic void setStringList(final String section, final String subsection,\n \t\t\tfinal String name, final List<String> values) {\n+\t\tState src, res;\n+\t\tdo {\n+\t\t\tsrc = state.get();\n+\t\t\tres = replaceStringList(src, section, subsection, name, values);\n+\t\t} while (!state.compareAndSet(src, res));\n+\t}\n+\n+\tprivate State replaceStringList(final State srcState,\n+\t\t\tfinal String section, final String subsection, final String name,\n+\t\t\tfinal List<String> values) {\n+\t\tfinal List<Entry> entries = copy(srcState, values);\n \t\tint entryIndex = 0;\n \t\tint valueIndex = 0;\n \t\tint insertPosition = -1;\n@@ -548,11 +566,12 @@ public void setStringList(final String section, final String subsection,\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\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\tentries.set(entryIndex, e.forValue(values.get(valueIndex++)));\n+\t\t\t\tinsertPosition = entryIndex + 1;\n \t\t\t}\n+\t\t\tentryIndex++;\n \t\t}\n \n \t\t// Remove any extra Entry objects that we no longer need.\n@@ -573,7 +592,7 @@ public void setStringList(final String section, final String subsection,\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\tinsertPosition = findSectionEnd(entries, 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@@ -594,9 +613,22 @@ public void setStringList(final String section, final String subsection,\n \t\t\t\tentries.add(insertPosition++, e);\n \t\t\t}\n \t\t}\n+\n+\t\treturn newState(entries);\n+\t}\n+\n+\tprivate static List<Entry> copy(final State src, final List<String> values) {\n+\t\t// At worst we need to insert 1 line for each value, plus 1 line\n+\t\t// for a new section header. Assume that and allocate the space.\n+\t\t//\n+\t\tfinal int max = src.entryList.size() + values.size() + 1;\n+\t\tfinal ArrayList<Entry> r = new ArrayList<Entry>(max);\n+\t\tr.addAll(src.entryList);\n+\t\treturn r;\n \t}\n \n-\tprivate int findSectionEnd(final String section, final String subsection) {\n+\tprivate static int findSectionEnd(final List<Entry> entries,\n+\t\t\tfinal 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@@ -619,7 +651,7 @@ private int findSectionEnd(final String section, final String subsection) {\n \t */\n \tpublic String toText() {\n \t\tfinal StringBuilder out = new StringBuilder();\n-\t\tfor (final Entry e : entries) {\n+\t\tfor (final Entry e : state.get().entryList) {\n \t\t\tif (e.prefix != null)\n \t\t\t\tout.append(e.prefix);\t\t\t\n \t\t\tif (e.section != null && e.name == null) {\n@@ -721,14 +753,22 @@ public void fromText(final String text) throws ConfigInvalidException {\n \t\t\t\tthrow new ConfigInvalidException(\"Invalid line in config file\");\n \t\t}\n \n-\t\tentries = newEntries;\n+\t\tstate.set(newState(newEntries));\n+\t}\n+\n+\tprivate State newState() {\n+\t\treturn new State(Collections.<Entry> emptyList());\n+\t}\n+\n+\tprivate State newState(final List<Entry> entries) {\n+\t\treturn new State(Collections.unmodifiableList(entries));\n \t}\n \n \t/**\n \t * Clear the configuration file\n \t */\n \tprotected void clear() {\n-\t\tentries = new ArrayList<Entry>();\n+\t\tstate.set(newState());\n \t}\n \n \tprivate static String readSectionName(final StringReader in)\n@@ -893,6 +933,14 @@ private static String readValue(final StringReader in, boolean quote,\n \t\treturn value.length() > 0 ? value.toString() : null;\n \t}\n \n+\tprivate static class State {\n+\t\tfinal List<Entry> entryList;\n+\n+\t\tState(List<Entry> entries) {\n+\t\t\tentryList = entries;\n+\t\t}\n+\t}\n+\n \t/**\n \t * The configuration file entry\n \t */\n@@ -927,6 +975,17 @@ private static String readValue(final StringReader in, boolean quote,\n \t\t */\n \t\tString suffix;\n \n+\t\tEntry forValue(final String newValue) {\n+\t\t\tfinal Entry e = new Entry();\n+\t\t\te.prefix = prefix;\n+\t\t\te.section = section;\n+\t\t\te.subsection = subsection;\n+\t\t\te.name = name;\n+\t\t\te.value = newValue;\n+\t\t\te.suffix = suffix;\n+\t\t\treturn e;\n+\t\t}\n+\n \t\tboolean match(final String aSection, final String aSubsection,\n \t\t\t\tfinal String aKey) {\n \t\t\treturn eqIgnoreCase(section, aSection)\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118757","messageId":"1248547982-4003-15-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-14-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 14/19] Support cached application models in a Config","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:57Z","receivedAt":"2009-07-25T18:52:57Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The Config.SectionParser acts as a key into a cache of model object\ninstances, but also knows how to create its model instance if the\ncache does not contain the corresponding instance.\n\nBecause we don't keep track of which keys were accessed by a parser\nwhen it created its model, we discard the entire cache anytime any\nkey is modified, forcing all models to reparse on the next access.\n\nBoth CoreConfig and TransferConfig are trivial to switch to this\nmethod of access, although the typical get path is quite a bit longer\nas we now need to do a hash lookup instead of a getfield instruction.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\nCC: Constantine Plotnikov <constantine.plotnikov@gmail.com>\n---\n .../src/org/spearce/jgit/lib/Config.java           |   92 +++++++++++++++++++-\n .../src/org/spearce/jgit/lib/CoreConfig.java       |   16 +++-\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |   20 +----\n .../src/org/spearce/jgit/lib/TransferConfig.java   |   11 ++-\n 4 files changed, 112 insertions(+), 27 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 4d4b315..6036c4c 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@@ -45,7 +45,9 @@\n import java.util.Collections;\n import java.util.HashSet;\n import java.util.List;\n+import java.util.Map;\n import java.util.Set;\n+import java.util.concurrent.ConcurrentHashMap;\n import java.util.concurrent.atomic.AtomicReference;\n \n import org.spearce.jgit.errors.ConfigInvalidException;\n@@ -375,6 +377,43 @@ public String getString(final String section, String subsection,\n \t\treturn result;\n \t}\n \n+\t/**\n+\t * Obtain a handle to a parsed set of configuration values.\n+\t *\n+\t * @param <T>\n+\t *            type of configuration model to return.\n+\t * @param parser\n+\t *            parser which can create the model if it is not already\n+\t *            available in this configuration file. The parser is also used\n+\t *            as the key into a cache and must obey the hashCode and equals\n+\t *            contract in order to reuse a parsed model.\n+\t * @return the parsed object instance, which is cached inside this config.\n+\t */\n+\t@SuppressWarnings(\"unchecked\")\n+\tpublic <T> T get(final SectionParser<T> parser) {\n+\t\tfinal State myState = getState();\n+\t\tT obj = (T) myState.cache.get(parser);\n+\t\tif (obj == null) {\n+\t\t\tobj = parser.parse(this);\n+\t\t\tmyState.cache.put(parser, obj);\n+\t\t}\n+\t\treturn obj;\n+\t}\n+\n+\t/**\n+\t * Remove a cached configuration object.\n+\t * <p>\n+\t * If the associated configuration object has not yet been cached, this\n+\t * method has no effect.\n+\t *\n+\t * @param parser\n+\t *            parser used to obtain the configuration object.\n+\t * @see #get(SectionParser)\n+\t */\n+\tpublic void uncache(final SectionParser<?> parser) {\n+\t\tstate.get().cache.remove(parser);\n+\t}\n+\n \tprivate String getRawString(final String section, final String subsection,\n \t\t\tfinal String name) {\n \t\tfinal List<String> lst = getRawStringList(section, subsection, name);\n@@ -409,6 +448,22 @@ else if (baseConfig != null)\n \t\treturn curr;\n \t}\n \n+\tprivate State getState() {\n+\t\tState cur, upd;\n+\t\tdo {\n+\t\t\tcur = state.get();\n+\t\t\tfinal State base = getBaseState();\n+\t\t\tif (cur.baseState == base)\n+\t\t\t\treturn cur;\n+\t\t\tupd = new State(cur.entryList, base);\n+\t\t} while (!state.compareAndSet(cur, upd));\n+\t\treturn upd;\n+\t}\n+\n+\tprivate State getBaseState() {\n+\t\treturn baseConfig != null ? baseConfig.getState() : null;\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@@ -757,11 +812,11 @@ public void fromText(final String text) throws ConfigInvalidException {\n \t}\n \n \tprivate State newState() {\n-\t\treturn new State(Collections.<Entry> emptyList());\n+\t\treturn new State(Collections.<Entry> emptyList(), getBaseState());\n \t}\n \n \tprivate State newState(final List<Entry> entries) {\n-\t\treturn new State(Collections.unmodifiableList(entries));\n+\t\treturn new State(Collections.unmodifiableList(entries), getBaseState());\n \t}\n \n \t/**\n@@ -933,11 +988,42 @@ private static String readValue(final StringReader in, boolean quote,\n \t\treturn value.length() > 0 ? value.toString() : null;\n \t}\n \n+\t/**\n+\t * Parses a section of the configuration into an application model object.\n+\t * <p>\n+\t * Instances must implement hashCode and equals such that model objects can\n+\t * be cached by using the {@code SectionParser} as a key of a HashMap.\n+\t * <p>\n+\t * As the {@code SectionParser} itself is used as the key of the internal\n+\t * HashMap applications should be careful to ensure the SectionParser key\n+\t * does not retain unnecessary application state which may cause memory to\n+\t * be held longer than expected.\n+\t *\n+\t * @param <T>\n+\t *            type of the application model created by the parser.\n+\t */\n+\tpublic static interface SectionParser<T> {\n+\t\t/**\n+\t\t * Create a model object from a configuration.\n+\t\t *\n+\t\t * @param cfg\n+\t\t *            the configuration to read values from.\n+\t\t * @return the application model instance.\n+\t\t */\n+\t\tT parse(Config cfg);\n+\t}\n+\n \tprivate static class State {\n \t\tfinal List<Entry> entryList;\n \n-\t\tState(List<Entry> entries) {\n+\t\tfinal Map<Object, Object> cache;\n+\n+\t\tfinal State baseState;\n+\n+\t\tState(List<Entry> entries, State base) {\n \t\t\tentryList = entries;\n+\t\t\tcache = new ConcurrentHashMap<Object, Object>(16, 0.75f, 1);\n+\t\t\tbaseState = base;\n \t\t}\n \t}\n \ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/CoreConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/CoreConfig.java\nindex e98e0bc..ed3827b 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/CoreConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/CoreConfig.java\n@@ -38,22 +38,28 @@\n \n package org.spearce.jgit.lib;\n \n-import java.util.zip.Deflater;\n+import static java.util.zip.Deflater.DEFAULT_COMPRESSION;\n+\n+import org.spearce.jgit.lib.Config.SectionParser;\n \n /**\n  * This class keeps git repository core parameters.\n  */\n public class CoreConfig {\n-\tprivate static final int DEFAULT_COMPRESSION = Deflater.DEFAULT_COMPRESSION;\n-\tprivate static final int DEFAULT_INDEXVERSION = 2;\n+\t/** Key for {@link Config#get(SectionParser)}. */\n+\tpublic static final Config.SectionParser<CoreConfig> KEY = new SectionParser<CoreConfig>() {\n+\t\tpublic CoreConfig parse(final Config cfg) {\n+\t\t\treturn new CoreConfig(cfg);\n+\t\t}\n+\t};\n \n \tprivate final int compression;\n \n \tprivate final int packIndexVersion;\n \n-\tCoreConfig(final RepositoryConfig rc) {\n+\tprivate CoreConfig(final Config rc) {\n \t\tcompression = rc.getInt(\"core\", \"compression\", DEFAULT_COMPRESSION);\n-\t\tpackIndexVersion = rc.getInt(\"pack\", \"indexversion\", DEFAULT_INDEXVERSION);\n+\t\tpackIndexVersion = rc.getInt(\"pack\", \"indexversion\", 2);\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 fb97747..c6a13b6 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@@ -42,9 +42,7 @@\n package org.spearce.jgit.lib;\n \n import java.io.File;\n-import java.io.IOException;\n \n-import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.SystemReader;\n \n /**\n@@ -57,10 +55,6 @@\n \t/** Section name for a branch configuration. */\n \tpublic static final String BRANCH_SECTION = \"branch\";\n \n-\tCoreConfig core;\n-\n-\tTransferConfig transfer;\n-\n \t/**\n \t * Create a Git configuration file reader/writer/cache for a specific file.\n \t *\n@@ -79,14 +73,14 @@ public RepositoryConfig(final Config base, final File cfgLocation) {\n \t * @return Core configuration values\n \t */\n \tpublic CoreConfig getCore() {\n-\t\treturn core;\n+\t\treturn get(CoreConfig.KEY);\n \t}\n \n \t/**\n \t * @return transfer, fetch and receive configuration values\n \t */\n \tpublic TransferConfig getTransfer() {\n-\t\treturn transfer;\n+\t\treturn get(TransferConfig.KEY);\n \t}\n \n \t/**\n@@ -175,15 +169,5 @@ public void create() {\n \t\tclear();\n \t\tsetInt(\"core\", null, \"repositoryformatversion\", 0);\n \t\tsetBoolean(\"core\", null, \"filemode\", true);\n-\n-\t\tcore = new CoreConfig(this);\n-\t\ttransfer = new TransferConfig(this);\n-\t}\n-\n-\t@Override\n-\tpublic void load() throws IOException, ConfigInvalidException {\n-\t\tsuper.load();\n-\t\tcore = new CoreConfig(this);\n-\t\ttransfer = new TransferConfig(this);\n \t}\n }\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/TransferConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/TransferConfig.java\nindex ff3a5eb..8760103 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/TransferConfig.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/TransferConfig.java\n@@ -37,13 +37,22 @@\n \n package org.spearce.jgit.lib;\n \n+import org.spearce.jgit.lib.Config.SectionParser;\n+\n /**\n  * The standard \"transfer\", \"fetch\" and \"receive\" configuration parameters.\n  */\n public class TransferConfig {\n+\t/** Key for {@link Config#get(SectionParser)}. */\n+\tpublic static final Config.SectionParser<TransferConfig> KEY = new SectionParser<TransferConfig>() {\n+\t\tpublic TransferConfig parse(final Config cfg) {\n+\t\t\treturn new TransferConfig(cfg);\n+\t\t}\n+\t};\n+\n \tprivate final boolean fsckObjects;\n \n-\tTransferConfig(final RepositoryConfig rc) {\n+\tprivate TransferConfig(final Config rc) {\n \t\tfsckObjects = rc.getBoolean(\"receive\", \"fsckobjects\", false);\n \t}\n \n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118762","messageId":"1248547982-4003-16-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-15-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 15/19] Cache Config subsection names when requested by application code","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:58Z","receivedAt":"2009-07-25T18:52:58Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Using the SectionParser based cache allows us to read through the\nconfiguration once to produce the set of available subsections,\nbut then reuse that set in the future.  This makes lookups like\n\"find all remotes\" more efficient.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Config.java           |   42 +++++++++++++++----\n 1 files changed, 33 insertions(+), 9 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 6036c4c..1668a3f 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@@ -366,15 +366,7 @@ public String getString(final String section, String subsection,\n \t *         subsection exists.\n \t */\n \tpublic Set<String> getSubsections(final String section) {\n-\t\tfinal Set<String> result = new HashSet<String>();\n-\t\tfor (final Entry e : state.get().entryList) {\n-\t\t\tif (StringUtils.equalsIgnoreCase(section, e.section)\n-\t\t\t\t\t&& e.subsection != null)\n-\t\t\t\tresult.add(e.subsection);\n-\t\t}\n-\t\tif (baseConfig != null)\n-\t\t\tresult.addAll(baseConfig.getSubsections(section));\n-\t\treturn result;\n+\t\treturn get(new SubsectionNames(section));\n \t}\n \n \t/**\n@@ -1013,6 +1005,38 @@ private static String readValue(final StringReader in, boolean quote,\n \t\tT parse(Config cfg);\n \t}\n \n+\tprivate static class SubsectionNames implements SectionParser<Set<String>> {\n+\t\tprivate final String section;\n+\n+\t\tSubsectionNames(final String sectionName) {\n+\t\t\tsection = sectionName;\n+\t\t}\n+\n+\t\tpublic int hashCode() {\n+\t\t\treturn section.hashCode();\n+\t\t}\n+\n+\t\tpublic boolean equals(Object other) {\n+\t\t\tif (other instanceof SubsectionNames) {\n+\t\t\t\treturn section.equals(((SubsectionNames) other).section);\n+\t\t\t}\n+\t\t\treturn false;\n+\t\t}\n+\n+\t\tpublic Set<String> parse(Config cfg) {\n+\t\t\tfinal Set<String> result = new HashSet<String>();\n+\t\t\twhile (cfg != null) {\n+\t\t\t\tfor (final Entry e : cfg.state.get().entryList) {\n+\t\t\t\t\tif (e.subsection != null && e.name == null\n+\t\t\t\t\t\t\t&& StringUtils.equalsIgnoreCase(section, e.section))\n+\t\t\t\t\t\tresult.add(e.subsection);\n+\t\t\t\t}\n+\t\t\t\tcfg = cfg.baseConfig;\n+\t\t\t}\n+\t\t\treturn Collections.unmodifiableSet(result);\n+\t\t}\n+\t}\n+\n \tprivate static class State {\n \t\tfinal List<Entry> entryList;\n \n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118764","messageId":"1248547982-4003-17-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-16-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 16/19] Refactor author/committer lookup to use cached data","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:52:59Z","receivedAt":"2009-07-25T18:52:59Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"The author and committer don't typically change when processing,\nso we can maintain them in a cached entity just like we do with\nthe TransferConfig and the CoreConfig.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/lib/RepositoryConfigTest.java |   29 ++--\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |   56 ++------\n .../src/org/spearce/jgit/lib/UserConfig.java       |  149 ++++++++++++++++++++\n 3 files changed, 173 insertions(+), 61 deletions(-)\n create mode 100644 org.spearce.jgit/src/org/spearce/jgit/lib/UserConfig.java\n\ndiff --git a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\nindex d4a6dd2..67d9964 100644\n--- a/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n+++ b/org.spearce.jgit.test/tst/org/spearce/jgit/lib/RepositoryConfigTest.java\n@@ -109,55 +109,56 @@ public void test007_readUserConfig() {\n \t\tSystemReader.setInstance(mockSystemReader);\n \t\tfinal String hostname = mockSystemReader.getHostname();\n \t\tfinal Config userGitConfig = mockSystemReader.userGitConfig;\n-\t\tfinal RepositoryConfig localConfig = new RepositoryConfig(userGitConfig, null);\n-\t\tlocalConfig.create();\n+\t\tfinal Config localConfig = new Config(userGitConfig);\n \t\tmockSystemReader.values.clear();\n \n \t\tString authorName;\n \t\tString authorEmail;\n \n \t\t// no values defined nowhere\n-\t\tauthorName = localConfig.getAuthorName();\n-\t\tauthorEmail = localConfig.getAuthorEmail();\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getAuthorName();\n+\t\tauthorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();\n \t\tassertEquals(Constants.UNKNOWN_USER_DEFAULT, authorName);\n \t\tassertEquals(Constants.UNKNOWN_USER_DEFAULT + \"@\" + hostname, authorEmail);\n \n \t\t// the system user name is defined\n \t\tmockSystemReader.values.put(Constants.OS_USER_NAME_KEY, \"os user name\");\n-\t\tauthorName = localConfig.getAuthorName();\n+\t\tlocalConfig.uncache(UserConfig.KEY);\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getAuthorName();\n \t\tassertEquals(\"os user name\", authorName);\n \n \t\tif (hostname != null && hostname.length() != 0) {\n-\t\t\tauthorEmail = localConfig.getAuthorEmail();\n+\t\t\tauthorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();\n \t\t\tassertEquals(\"os user name@\" + hostname, authorEmail);\n \t\t}\n \n \t\t// the git environment variables are defined\n \t\tmockSystemReader.values.put(Constants.GIT_AUTHOR_NAME_KEY, \"git author name\");\n \t\tmockSystemReader.values.put(Constants.GIT_AUTHOR_EMAIL_KEY, \"author@email\");\n-\t\tauthorName = localConfig.getAuthorName();\n-\t\tauthorEmail = localConfig.getAuthorEmail();\n+\t\tlocalConfig.uncache(UserConfig.KEY);\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getAuthorName();\n+\t\tauthorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();\n \t\tassertEquals(\"git author name\", authorName);\n \t\tassertEquals(\"author@email\", authorEmail);\n \n \t\t// the values are defined in the global configuration\n \t\tuserGitConfig.setString(\"user\", null, \"name\", \"global username\");\n \t\tuserGitConfig.setString(\"user\", null, \"email\", \"author@globalemail\");\n-\t\tauthorName = localConfig.getAuthorName();\n-\t\tauthorEmail = localConfig.getAuthorEmail();\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getAuthorName();\n+\t\tauthorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();\n \t\tassertEquals(\"global username\", authorName);\n \t\tassertEquals(\"author@globalemail\", authorEmail);\n \n \t\t// the values are defined in the local configuration\n \t\tlocalConfig.setString(\"user\", null, \"name\", \"local username\");\n \t\tlocalConfig.setString(\"user\", null, \"email\", \"author@localemail\");\n-\t\tauthorName = localConfig.getAuthorName();\n-\t\tauthorEmail = localConfig.getAuthorEmail();\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getAuthorName();\n+\t\tauthorEmail = localConfig.get(UserConfig.KEY).getAuthorEmail();\n \t\tassertEquals(\"local username\", authorName);\n \t\tassertEquals(\"author@localemail\", authorEmail);\n \n-\t\tauthorName = localConfig.getCommitterName();\n-\t\tauthorEmail = localConfig.getCommitterEmail();\n+\t\tauthorName = localConfig.get(UserConfig.KEY).getCommitterName();\n+\t\tauthorEmail = localConfig.get(UserConfig.KEY).getCommitterEmail();\n \t\tassertEquals(\"local username\", authorName);\n \t\tassertEquals(\"author@localemail\", authorEmail);\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 c6a13b6..15fe9de 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@@ -43,8 +43,6 @@\n \n import java.io.File;\n \n-import org.spearce.jgit.util.SystemReader;\n-\n /**\n  * An object representing the Git config file.\n  *\n@@ -83,13 +81,18 @@ public TransferConfig getTransfer() {\n \t\treturn get(TransferConfig.KEY);\n \t}\n \n+\t/** @return standard user configuration data */\n+\tpublic UserConfig getUserConfig() {\n+\t\treturn get(UserConfig.KEY);\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 \t */\n \tpublic String getAuthorName() {\n-\t\treturn getUsernameInternal(Constants.GIT_AUTHOR_NAME_KEY);\n+\t\treturn getUserConfig().getAuthorName();\n \t}\n \n \t/**\n@@ -98,26 +101,7 @@ public String getAuthorName() {\n \t *         to use the system user name instead.\n \t */\n \tpublic String getCommitterName() {\n-\t\treturn getUsernameInternal(Constants.GIT_COMMITTER_NAME_KEY);\n-\t}\n-\n-\tprivate String getUsernameInternal(String gitVariableKey) {\n-\t\tSystemReader systemReader = SystemReader.getInstance();\n-\t\t// try to get the user name from the local and global configurations.\n-\t\tString username = getString(\"user\", null, \"name\");\n-\n-\t\tif (username == null) {\n-\t\t\t// try to get the user name for the system property GIT_XXX_NAME\n-\t\t\tusername = systemReader.getenv(gitVariableKey);\n-\t\t}\n-\t\tif (username == null) {\n-\t\t\t// get the system user name\n-\t\t\tusername = systemReader.getProperty(Constants.OS_USER_NAME_KEY);\n-\t\t}\n-\t\tif (username == null) {\n-\t\t\tusername = Constants.UNKNOWN_USER_DEFAULT;\n-\t\t}\n-\t\treturn username;\n+\t\treturn getUserConfig().getCommitterName();\n \t}\n \n \t/**\n@@ -127,7 +111,7 @@ private String getUsernameInternal(String gitVariableKey) {\n \t *         host name.\n \t */\n \tpublic String getAuthorEmail() {\n-\t\treturn getUserEmailInternal(Constants.GIT_AUTHOR_EMAIL_KEY);\n+\t\treturn getUserConfig().getAuthorEmail();\n \t}\n \n \t/**\n@@ -137,29 +121,7 @@ public String getAuthorEmail() {\n \t *         host name.\n \t */\n \tpublic String getCommitterEmail() {\n-\t\treturn getUserEmailInternal(Constants.GIT_COMMITTER_EMAIL_KEY);\n-\t}\n-\n-\tprivate String getUserEmailInternal(String gitVariableKey) {\n-\t\tSystemReader systemReader = SystemReader.getInstance();\n-\t\t// try to get the email from the local and global configurations.\n-\t\tString email = getString(\"user\", null, \"email\");\n-\n-\t\tif (email == null) {\n-\t\t\t// try to get the email for the system property GIT_XXX_EMAIL\n-\t\t\temail = systemReader.getenv(gitVariableKey);\n-\t\t}\n-\n-\t\tif (email == null) {\n-\t\t\t// try to construct an email\n-\t\t\tString username = systemReader.getProperty(Constants.OS_USER_NAME_KEY);\n-\t\t\tif (username == null){\n-\t\t\t\tusername = Constants.UNKNOWN_USER_DEFAULT;\n-\t\t\t}\n-\t\t\temail = username + \"@\" + systemReader.getHostname();\n-\t\t}\n-\n-\t\treturn email;\n+\t\treturn getUserConfig().getCommitterEmail();\n \t}\n \n \t/**\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/UserConfig.java b/org.spearce.jgit/src/org/spearce/jgit/lib/UserConfig.java\nnew file mode 100644\nindex 0000000..27c7d69\n--- /dev/null\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/UserConfig.java\n@@ -0,0 +1,149 @@\n+/*\n+ * Copyright (C) 2009, Yann Simon <yann.simon.fr@gmail.com>\n+ * Copyright (C) 2009, Google Inc.\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+\n+package org.spearce.jgit.lib;\n+\n+import org.spearce.jgit.lib.Config.SectionParser;\n+import org.spearce.jgit.util.SystemReader;\n+\n+/** The standard \"user\" configuration parameters. */\n+public class UserConfig {\n+\t/** Key for {@link Config#get(SectionParser)}. */\n+\tpublic static final Config.SectionParser<UserConfig> KEY = new SectionParser<UserConfig>() {\n+\t\tpublic UserConfig parse(final Config cfg) {\n+\t\t\treturn new UserConfig(cfg);\n+\t\t}\n+\t};\n+\n+\tprivate final String authorName;\n+\n+\tprivate final String authorEmail;\n+\n+\tprivate final String committerName;\n+\n+\tprivate final String committerEmail;\n+\n+\tprivate UserConfig(final Config rc) {\n+\t\tauthorName = getNameInternal(rc, Constants.GIT_AUTHOR_NAME_KEY);\n+\t\tauthorEmail = getEmailInternal(rc, Constants.GIT_AUTHOR_EMAIL_KEY);\n+\n+\t\tcommitterName = getNameInternal(rc, Constants.GIT_COMMITTER_NAME_KEY);\n+\t\tcommitterEmail = getEmailInternal(rc, Constants.GIT_COMMITTER_EMAIL_KEY);\n+\t}\n+\n+\t/**\n+\t * @return the author name as defined in the git variables and\n+\t *         configurations. If no name could be found, try to use the system\n+\t *         user name instead.\n+\t */\n+\tpublic String getAuthorName() {\n+\t\treturn authorName;\n+\t}\n+\n+\t/**\n+\t * @return the committer name as defined in the git variables and\n+\t *         configurations. If no name could be found, try to use the system\n+\t *         user name instead.\n+\t */\n+\tpublic String getCommitterName() {\n+\t\treturn committerName;\n+\t}\n+\n+\t/**\n+\t * @return the author email as defined in git variables and\n+\t *         configurations. If no email could be found, try to\n+\t *         propose one default with the user name and the\n+\t *         host name.\n+\t */\n+\tpublic String getAuthorEmail() {\n+\t\treturn authorEmail;\n+\t}\n+\n+\t/**\n+\t * @return the committer email as defined in git variables and\n+\t *         configurations. If no email could be found, try to\n+\t *         propose one default with the user name and the\n+\t *         host name.\n+\t */\n+\tpublic String getCommitterEmail() {\n+\t\treturn committerEmail;\n+\t}\n+\n+\tprivate static String getNameInternal(Config rc, String envKey) {\n+\t\t// try to get the user name from the local and global configurations.\n+\t\tString username = rc.getString(\"user\", null, \"name\");\n+\n+\t\tif (username == null) {\n+\t\t\t// try to get the user name for the system property GIT_XXX_NAME\n+\t\t\tusername = system().getenv(envKey);\n+\t\t}\n+\t\tif (username == null) {\n+\t\t\t// get the system user name\n+\t\t\tusername = system().getProperty(Constants.OS_USER_NAME_KEY);\n+\t\t}\n+\t\tif (username == null) {\n+\t\t\tusername = Constants.UNKNOWN_USER_DEFAULT;\n+\t\t}\n+\t\treturn username;\n+\t}\n+\n+\tprivate static String getEmailInternal(Config rc, String envKey) {\n+\t\t// try to get the email from the local and global configurations.\n+\t\tString email = rc.getString(\"user\", null, \"email\");\n+\n+\t\tif (email == null) {\n+\t\t\t// try to get the email for the system property GIT_XXX_EMAIL\n+\t\t\temail = system().getenv(envKey);\n+\t\t}\n+\n+\t\tif (email == null) {\n+\t\t\t// try to construct an email\n+\t\t\tString username = system().getProperty(Constants.OS_USER_NAME_KEY);\n+\t\t\tif (username == null){\n+\t\t\t\tusername = Constants.UNKNOWN_USER_DEFAULT;\n+\t\t\t}\n+\t\t\temail = username + \"@\" + system().getHostname();\n+\t\t}\n+\n+\t\treturn email;\n+\t}\n+\n+\tprivate static SystemReader system() {\n+\t\treturn SystemReader.getInstance();\n+\t}\n+}\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118761","messageId":"1248547982-4003-18-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-17-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 17/19] Move repository config creation fully into Repository class","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:53:00Z","receivedAt":"2009-07-25T18:53:00Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Rather than having the core properties initialized in two different\nlocations its cleaner to do all of the setup work here in one place,\nin case we need to make any further changes to the repository create\ncode path.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../src/org/spearce/jgit/lib/Repository.java       |    5 ++---\n .../src/org/spearce/jgit/lib/RepositoryConfig.java |    9 ---------\n 2 files changed, 2 insertions(+), 12 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\nindex 7fb1ef7..468cf4c 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/lib/Repository.java\n@@ -144,8 +144,6 @@ public Repository(final File d) throws IOException {\n \t\t\t\tthrow new IOException(\"Unknown repository format \\\"\"\n \t\t\t\t\t\t+ repositoryFormatVersion + \"\\\"; expected \\\"0\\\".\");\n \t\t\t}\n-\t\t} else {\n-\t\t\tgetConfig().create();\n \t\t}\n \t}\n \n@@ -185,7 +183,8 @@ public void create(boolean bare) throws IOException {\n \t\tfinal String master = Constants.R_HEADS + Constants.MASTER;\n \t\trefs.link(Constants.HEAD, master);\n \n-\t\tcfg.create();\n+\t\tcfg.setInt(\"core\", null, \"repositoryformatversion\", 0);\n+\t\tcfg.setBoolean(\"core\", null, \"filemode\", true);\n \t\tif (bare)\n \t\t\tcfg.setBoolean(\"core\", null, \"bare\", true);\n \t\tcfg.save();\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 15fe9de..ccfe184 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@@ -123,13 +123,4 @@ public String getAuthorEmail() {\n \tpublic String getCommitterEmail() {\n \t\treturn getUserConfig().getCommitterEmail();\n \t}\n-\n-\t/**\n-\t * Create a new default config\n-\t */\n-\tpublic void create() {\n-\t\tclear();\n-\t\tsetInt(\"core\", null, \"repositoryformatversion\", 0);\n-\t\tsetBoolean(\"core\", null, \"filemode\", true);\n-\t}\n }\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118763","messageId":"1248547982-4003-19-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-18-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 18/19] Use Config SectionParser cache to store daemon enable states","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:53:01Z","receivedAt":"2009-07-25T18:53:01Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Rather than looking up the boolean each time we start a new connection\nin a repository we now cache it in the configuration cache under a key\nthat is unique to the daemon service.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../org/spearce/jgit/transport/DaemonService.java  |   30 ++++++++++++++++----\n 1 files changed, 24 insertions(+), 6 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\nindex 817aeee..b7198c7 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/DaemonService.java\n@@ -39,13 +39,15 @@\n \n import java.io.IOException;\n \n+import org.spearce.jgit.lib.Config;\n import org.spearce.jgit.lib.Repository;\n+import org.spearce.jgit.lib.Config.SectionParser;\n \n /** A service exposed by {@link Daemon} over anonymous <code>git://</code>. */\n public abstract class DaemonService {\n \tprivate final String command;\n \n-\tprivate final String config;\n+\tprivate final SectionParser<ServiceConfig> configKey;\n \n \tprivate boolean enabled;\n \n@@ -53,10 +55,23 @@\n \n \tDaemonService(final String cmdName, final String cfgName) {\n \t\tcommand = cmdName.startsWith(\"git-\") ? cmdName : \"git-\" + cmdName;\n-\t\tconfig = cfgName;\n+\t\tconfigKey = new SectionParser<ServiceConfig>() {\n+\t\t\tpublic ServiceConfig parse(final Config cfg) {\n+\t\t\t\treturn new ServiceConfig(DaemonService.this, cfg, cfgName);\n+\t\t\t}\n+\t\t};\n \t\toverridable = true;\n \t}\n \n+\tprivate static class ServiceConfig {\n+\t\tfinal boolean enabled;\n+\n+\t\tServiceConfig(final DaemonService service, final Config cfg,\n+\t\t\t\tfinal String name) {\n+\t\t\tenabled = cfg.getBoolean(\"daemon\", name, service.isEnabled());\n+\t\t}\n+\t}\n+\n \t/** @return is this service enabled for invocation? */\n \tpublic boolean isEnabled() {\n \t\treturn enabled;\n@@ -109,16 +124,19 @@ void execute(final DaemonClient client, final String commandLine)\n \t\tif (db == null)\n \t\t\treturn;\n \t\ttry {\n-\t\t\tboolean on = isEnabled();\n-\t\t\tif (isOverridable())\n-\t\t\t\ton = db.getConfig().getBoolean(\"daemon\", config, on);\n-\t\t\tif (on)\n+\t\t\tif (isEnabledFor(db))\n \t\t\t\texecute(client, db);\n \t\t} finally {\n \t\t\tdb.close();\n \t\t}\n \t}\n \n+\tprivate boolean isEnabledFor(final Repository db) {\n+\t\tif (isOverridable())\n+\t\t\treturn db.getConfig().get(configKey).enabled;\n+\t\treturn isEnabled();\n+\t}\n+\n \tabstract void execute(DaemonClient client, Repository db)\n \t\t\tthrows IOException;\n }\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118760","messageId":"1248547982-4003-20-git-send-email-spearce@spearce.org","threadId":"20232","inReplyTo":"1248547982-4003-19-git-send-email-spearce@spearce.org","subject":"[JGIT PATCH 19/19] Use Config cache for fetch and receive configuration parsing","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T18:53:02Z","receivedAt":"2009-07-25T18:53:02Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Rather than parsing the fields on each transfer we now parse the\nfields once and cache them in the Config object, under a unique\nsection key for the fetch or receive direction.  This permits the\nkeys to be scanned only once, rather than per-request.\n\nSigned-off-by: Shawn O. Pearce <spearce@spearce.org>\n---\n .../jgit/transport/BasePackFetchConnection.java    |   21 ++++++++-\n .../org/spearce/jgit/transport/ReceivePack.java    |   45 ++++++++++++++++----\n 2 files changed, 55 insertions(+), 11 deletions(-)\n\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\nindex 7163e02..dea6181 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/BasePackFetchConnection.java\n@@ -46,12 +46,13 @@\n \n import org.spearce.jgit.errors.TransportException;\n import org.spearce.jgit.lib.AnyObjectId;\n+import org.spearce.jgit.lib.Config;\n import org.spearce.jgit.lib.MutableObjectId;\n import org.spearce.jgit.lib.ObjectId;\n import org.spearce.jgit.lib.PackLock;\n import org.spearce.jgit.lib.ProgressMonitor;\n import org.spearce.jgit.lib.Ref;\n-import org.spearce.jgit.lib.RepositoryConfig;\n+import org.spearce.jgit.lib.Config.SectionParser;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevCommitList;\n import org.spearce.jgit.revwalk.RevFlag;\n@@ -146,10 +147,10 @@\n \tBasePackFetchConnection(final PackTransport packTransport) {\n \t\tsuper(packTransport);\n \n-\t\tfinal RepositoryConfig cfg = local.getConfig();\n+\t\tfinal FetchConfig cfg = local.getConfig().get(FetchConfig.KEY);\n \t\tincludeTags = transport.getTagOpt() != TagOpt.NO_TAGS;\n \t\tthinPack = transport.isFetchThin();\n-\t\tallowOfsDelta = cfg.getBoolean(\"repack\", \"usedeltabaseoffset\", true);\n+\t\tallowOfsDelta = cfg.allowOfsDelta;\n \n \t\twalk = new RevWalk(local);\n \t\treachableCommits = new RevCommitList<RevCommit>();\n@@ -162,6 +163,20 @@\n \t\twalk.carry(ADVERTISED);\n \t}\n \n+\tprivate static class FetchConfig {\n+\t\tstatic final SectionParser<FetchConfig> KEY = new SectionParser<FetchConfig>() {\n+\t\t\tpublic FetchConfig parse(final Config cfg) {\n+\t\t\t\treturn new FetchConfig(cfg);\n+\t\t\t}\n+\t\t};\n+\n+\t\tfinal boolean allowOfsDelta;\n+\n+\t\tFetchConfig(final Config c) {\n+\t\t\tallowOfsDelta = c.getBoolean(\"repack\", \"usedeltabaseoffset\", true);\n+\t\t}\n+\t}\n+\n \tpublic final void fetch(final ProgressMonitor monitor,\n \t\t\tfinal Collection<Ref> want, final Set<ObjectId> have)\n \t\t\tthrows TransportException {\ndiff --git a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\nindex 38f0b5c..ca4a7ec 100644\n--- a/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n+++ b/org.spearce.jgit/src/org/spearce/jgit/transport/ReceivePack.java\n@@ -54,6 +54,7 @@\n \n import org.spearce.jgit.errors.MissingObjectException;\n import org.spearce.jgit.errors.PackProtocolException;\n+import org.spearce.jgit.lib.Config;\n import org.spearce.jgit.lib.Constants;\n import org.spearce.jgit.lib.NullProgressMonitor;\n import org.spearce.jgit.lib.ObjectId;\n@@ -62,7 +63,7 @@\n import org.spearce.jgit.lib.Ref;\n import org.spearce.jgit.lib.RefUpdate;\n import org.spearce.jgit.lib.Repository;\n-import org.spearce.jgit.lib.RepositoryConfig;\n+import org.spearce.jgit.lib.Config.SectionParser;\n import org.spearce.jgit.revwalk.ObjectWalk;\n import org.spearce.jgit.revwalk.RevCommit;\n import org.spearce.jgit.revwalk.RevFlag;\n@@ -158,17 +159,45 @@ public ReceivePack(final Repository into) {\n \t\tdb = into;\n \t\twalk = new RevWalk(db);\n \n-\t\tfinal RepositoryConfig cfg = db.getConfig();\n-\t\tcheckReceivedObjects = cfg.getBoolean(\"receive\", \"fsckobjects\", false);\n-\t\tallowCreates = true;\n-\t\tallowDeletes = !cfg.getBoolean(\"receive\", \"denydeletes\", false);\n-\t\tallowNonFastForwards = !cfg.getBoolean(\"receive\",\n-\t\t\t\t\"denynonfastforwards\", false);\n-\t\tallowOfsDelta = cfg.getBoolean(\"repack\", \"usedeltabaseoffset\", true);\n+\t\tfinal ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);\n+\t\tcheckReceivedObjects = cfg.checkReceivedObjects;\n+\t\tallowCreates = cfg.allowCreates;\n+\t\tallowDeletes = cfg.allowDeletes;\n+\t\tallowNonFastForwards = cfg.allowNonFastForwards;\n+\t\tallowOfsDelta = cfg.allowOfsDelta;\n \t\tpreReceive = PreReceiveHook.NULL;\n \t\tpostReceive = PostReceiveHook.NULL;\n \t}\n \n+\tprivate static class ReceiveConfig {\n+\t\tstatic final SectionParser<ReceiveConfig> KEY = new SectionParser<ReceiveConfig>() {\n+\t\t\tpublic ReceiveConfig parse(final Config cfg) {\n+\t\t\t\treturn new ReceiveConfig(cfg);\n+\t\t\t}\n+\t\t};\n+\n+\t\tfinal boolean checkReceivedObjects;\n+\n+\t\tfinal boolean allowCreates;\n+\n+\t\tfinal boolean allowDeletes;\n+\n+\t\tfinal boolean allowNonFastForwards;\n+\n+\t\tfinal boolean allowOfsDelta;\n+\n+\t\tReceiveConfig(final Config config) {\n+\t\t\tcheckReceivedObjects = config.getBoolean(\"receive\", \"fsckobjects\",\n+\t\t\t\t\tfalse);\n+\t\t\tallowCreates = true;\n+\t\t\tallowDeletes = !config.getBoolean(\"receive\", \"denydeletes\", false);\n+\t\t\tallowNonFastForwards = !config.getBoolean(\"receive\",\n+\t\t\t\t\t\"denynonfastforwards\", false);\n+\t\t\tallowOfsDelta = config.getBoolean(\"repack\", \"usedeltabaseoffset\",\n+\t\t\t\t\ttrue);\n+\t\t}\n+\t}\n+\n \t/** @return the repository this receive completes into. */\n \tpublic final Repository getRepository() {\n \t\treturn db;\n-- \n1.6.4.rc2.216.g769fa\n"},{"id":"118775","messageId":"200907252232.30712.robin.rosenberg.lists@dewire.com","threadId":"20232","inReplyTo":"1248547982-4003-3-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 02/19] Delete incorrect Javadoc from Config's getRawString method","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-07-25T20:32:30Z","receivedAt":"2009-07-25T20:32:30Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"lördag 25 juli 2009 20:52:45 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> I don't know how this Javadoc got here, but it predates the code\n> refactor done by Constantine Plotnikov in 2564768e63.\n\nWhy do you think so? The getRawString before his patch did not have any\njavadoc. It was added there in that patch.\n\n-- robin\n"},{"id":"118777","messageId":"20090725203340.GE11191@spearce.org","threadId":"20232","inReplyTo":"200907252232.30712.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 02/19] Delete incorrect Javadoc from Config's getRawString method","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T20:33:40Z","receivedAt":"2009-07-25T20:33:40Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> l?rdag 25 juli 2009 20:52:45 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> > I don't know how this Javadoc got here, but it predates the code\n> > refactor done by Constantine Plotnikov in 2564768e63.\n> \n> Why do you think so? The getRawString before his patch did not have any\n> javadoc. It was added there in that patch.\n\nOh.  I failed to look at the history, I just assumed he carried it\nover in the refactoring.  Well, it still should die.\n\n-- \nShawn.\n"},{"id":"118800","messageId":"200907260054.06037.robin.rosenberg.lists@dewire.com","threadId":"20232","inReplyTo":"1248547982-4003-10-git-send-email-spearce@spearce.org","subject":"Re: [JGIT PATCH 09/19] Refactor Config hierarchy to make IO more explicit","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-07-25T22:54:05Z","receivedAt":"2009-07-25T22:54:05Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"\nThis one does not apply for the Config class here.\n\n-- robin\n"},{"id":"118801","messageId":"20090725225504.GG11191@spearce.org","threadId":"20232","inReplyTo":"200907260054.06037.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 09/19] Refactor Config hierarchy to make IO more explicit","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T22:55:04Z","receivedAt":"2009-07-25T22:55:04Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> \n> This one does not apply for the Config class here.\n\nDid you apply with --whitespace=strip or something and fall afoul\nof a merge conflict due to changed context?\n\n-- \nShawn.\n"},{"id":"118808","messageId":"200907260134.10820.robin.rosenberg.lists@dewire.com","threadId":"20232","inReplyTo":"20090725225504.GG11191@spearce.org","subject":"Re: [JGIT PATCH 09/19] Refactor Config hierarchy to make IO more explicit","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2009-07-25T23:34:10Z","receivedAt":"2009-07-25T23:34:10Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"söndag 26 juli 2009 00:55:04 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > \n> > This one does not apply for the Config class here.\n> \n> Did you apply with --whitespace=strip or something and fall afoul\n> of a merge conflict due to changed context?\n> \n\nI tried both ways.\n\n\nConfig.java looks (partially) like this:\n\n--------------------------\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n\n----------------------------\n\nand the patch looks (partially again) like this:\n\n@@ -55,19 +49,18 @@\n import java.util.Map;\n import java.util.Set;\n\n+import org.spearce.jgit.errors.ConfigInvalidException;\n import org.spearce.jgit.util.StringUtils;\n\n+\n /**\n----------------\n\nSo the import among other things, does not match.\n\n-- robin\n"},{"id":"118809","messageId":"20090725233813.GI11191@spearce.org","threadId":"20232","inReplyTo":"200907260134.10820.robin.rosenberg.lists@dewire.com","subject":"Re: [JGIT PATCH 09/19] Refactor Config hierarchy to make IO more explicit","fromName":"Shawn O. Pearce","fromEmail":"spearce@spearce.org","sentAt":"2009-07-25T23:38:13Z","receivedAt":"2009-07-25T23:38:13Z","isPatch":true,"sender":{"key":"spearce@spearce.org","avatar":"https://avatars.githubusercontent.com/u/34844?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> s?ndag 26 juli 2009 00:55:04 skrev \"Shawn O. Pearce\" <spearce@spearce.org>:\n> > Robin Rosenberg <robin.rosenberg.lists@dewire.com> wrote:\n> > > \n> > > This one does not apply for the Config class here.\n> > \n> > Did you apply with --whitespace=strip or something and fall afoul\n> > of a merge conflict due to changed context?\n> \n> Config.java looks (partially) like this:\n> \n> --------------------------\n> import java.util.Map;\n> import java.util.Set;\n> \n> /**\n> \n> ----------------------------\n> \n> and the patch looks (partially again) like this:\n> \n> @@ -55,19 +49,18 @@\n>  import java.util.Map;\n>  import java.util.Set;\n> \n> +import org.spearce.jgit.errors.ConfigInvalidException;\n>  import org.spearce.jgit.util.StringUtils;\n\nYou missed the patch where I added StringUtils.  See \"[PATCH]\nEnsure Config readers handle case insensitive names correctly\"\nfrom Friday Jul 24.\n \n-- \nShawn.\n"}]}