As per [1], but limited to absolute paths indeed. No support yet for tilde or $HOME expansion. Support for the --[no-]includes options ([1]) is not part of this commit scope either, but those options' defaults are in effect as described in [1]. [1] https://git-scm.com/docs/git-config Included path can be a config file that includes other path-s in turn. An exception is thrown if too many recursions (circular includes) happen because of ill-specified config files. Change-Id: I700bd7b7e1625eb7de0180f220c707d8e7b0930b Signed-off-by: Marco Miller <marco.miller@ericsson.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>tags/v4.4.0.201605250940-rc1
@@ -51,6 +51,7 @@ Import-Package: com.googlecode.javaewah;version="[0.7.9,0.8.0)", | |||
org.hamcrest;version="[1.1.0,2.0.0)", | |||
org.junit;version="[4.4.0,5.0.0)", | |||
org.junit.experimental.theories;version="[4.4.0,5.0.0)", | |||
org.junit.rules;version="[4.11.0,5.0.0)", | |||
org.junit.runner;version="[4.4.0,5.0.0)", | |||
org.junit.runners;version="[4.11.0,5.0.0)", | |||
org.slf4j;version="[1.7.2,2.0.0)" |
@@ -56,6 +56,9 @@ import static org.junit.Assert.assertSame; | |||
import static org.junit.Assert.assertTrue; | |||
import static org.junit.Assert.fail; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.nio.file.Files; | |||
import java.text.MessageFormat; | |||
import java.util.Arrays; | |||
import java.util.Iterator; | |||
@@ -64,18 +67,29 @@ import java.util.Set; | |||
import org.eclipse.jgit.api.MergeCommand.FastForwardMode; | |||
import org.eclipse.jgit.errors.ConfigInvalidException; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.junit.MockSystemReader; | |||
import org.eclipse.jgit.merge.MergeConfig; | |||
import org.eclipse.jgit.storage.file.FileBasedConfig; | |||
import org.eclipse.jgit.util.FS; | |||
import org.eclipse.jgit.util.SystemReader; | |||
import org.junit.After; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
/** | |||
* Test reading of git config | |||
*/ | |||
public class ConfigTest { | |||
@Rule | |||
public ExpectedException expectedEx = ExpectedException.none(); | |||
@Rule | |||
public TemporaryFolder tmp = new TemporaryFolder(); | |||
@After | |||
public void tearDown() { | |||
SystemReader.setInstance(null); | |||
@@ -745,6 +759,75 @@ public class ConfigTest { | |||
assertTrue(c.getBoolean("foo", "bar", false)); | |||
} | |||
@Test | |||
public void testIncludeInvalidName() throws ConfigInvalidException { | |||
expectedEx.expect(ConfigInvalidException.class); | |||
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); | |||
parse("[include]\nbar\n"); | |||
} | |||
@Test | |||
public void testIncludeNoValue() throws ConfigInvalidException { | |||
expectedEx.expect(ConfigInvalidException.class); | |||
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); | |||
parse("[include]\npath\n"); | |||
} | |||
@Test | |||
public void testIncludeEmptyValue() throws ConfigInvalidException { | |||
expectedEx.expect(ConfigInvalidException.class); | |||
expectedEx.expectMessage(JGitText.get().invalidLineInConfigFile); | |||
parse("[include]\npath=\n"); | |||
} | |||
@Test | |||
public void testIncludeValuePathNotFound() throws ConfigInvalidException { | |||
String notFound = "/not/found"; | |||
expectedEx.expect(ConfigInvalidException.class); | |||
expectedEx.expectMessage(notFound); | |||
parse("[include]\npath=" + notFound + "\n"); | |||
} | |||
@Test | |||
public void testIncludeTooManyRecursions() throws IOException { | |||
File config = tmp.newFile("config"); | |||
String include = "[include]\npath=" + config.toPath() + "\n"; | |||
Files.write(config.toPath(), include.getBytes()); | |||
FileBasedConfig fbConfig = new FileBasedConfig(null, config, | |||
FS.DETECTED); | |||
try { | |||
fbConfig.load(); | |||
fail(); | |||
} catch (ConfigInvalidException cie) { | |||
assertEquals(JGitText.get().tooManyIncludeRecursions, | |||
cie.getCause().getMessage()); | |||
} | |||
} | |||
@Test | |||
public void testInclude() throws IOException, ConfigInvalidException { | |||
File config = tmp.newFile("config"); | |||
File more = tmp.newFile("config.more"); | |||
File other = tmp.newFile("config.other"); | |||
String fooBar = "[foo]\nbar=true\n"; | |||
String includeMore = "[include]\npath=" + more.toPath() + "\n"; | |||
String includeOther = "path=" + other.toPath() + "\n"; | |||
String fooPlus = fooBar + includeMore + includeOther; | |||
Files.write(config.toPath(), fooPlus.getBytes()); | |||
String fooMore = "[foo]\nmore=bar\n"; | |||
Files.write(more.toPath(), fooMore.getBytes()); | |||
String otherMore = "[other]\nmore=bar\n"; | |||
Files.write(other.toPath(), otherMore.getBytes()); | |||
Config parsed = parse("[include]\npath=" + config.toPath() + "\n"); | |||
assertTrue(parsed.getBoolean("foo", "bar", false)); | |||
assertEquals("bar", parsed.getString("foo", null, "more")); | |||
assertEquals("bar", parsed.getString("other", null, "more")); | |||
} | |||
private static void assertReadLong(long exp) throws ConfigInvalidException { | |||
assertReadLong(exp, String.valueOf(exp)); | |||
} |
@@ -337,6 +337,7 @@ invalidId0=Invalid id | |||
invalidIdLength=Invalid id length {0}; should be {1} | |||
invalidIgnoreParamSubmodule=Found invalid ignore param for submodule {0}. | |||
invalidIgnoreRule=Exception caught while parsing ignore rule ''{0}''. | |||
invalidIncludedPathInConfigFile=Invalid included path in config file: {0} | |||
invalidIntegerValue=Invalid integer value: {0}.{1}={2} | |||
invalidKey=Invalid key: {0} | |||
invalidLineInConfigFile=Invalid line in config file | |||
@@ -592,6 +593,7 @@ tagNameInvalid=tag name {0} is invalid | |||
tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD currently not supported | |||
theFactoryMustNotBeNull=The factory must not be null | |||
timerAlreadyTerminated=Timer already terminated | |||
tooManyIncludeRecursions=Too many recursions; circular includes in config file(s)? | |||
topologicalSortRequired=Topological sort required. | |||
transactionAborted=transaction aborted | |||
transportExceptionBadRef=Empty ref: {0}: {1} |
@@ -396,6 +396,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String invalidIdLength; | |||
/***/ public String invalidIgnoreParamSubmodule; | |||
/***/ public String invalidIgnoreRule; | |||
/***/ public String invalidIncludedPathInConfigFile; | |||
/***/ public String invalidIntegerValue; | |||
/***/ public String invalidKey; | |||
/***/ public String invalidLineInConfigFile; | |||
@@ -652,6 +653,7 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String transactionAborted; | |||
/***/ public String theFactoryMustNotBeNull; | |||
/***/ public String timerAlreadyTerminated; | |||
/***/ public String tooManyIncludeRecursions; | |||
/***/ public String topologicalSortRequired; | |||
/***/ public String transportExceptionBadRef; | |||
/***/ public String transportExceptionEmptyRef; |
@@ -51,6 +51,8 @@ | |||
package org.eclipse.jgit.lib; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.text.MessageFormat; | |||
import java.util.ArrayList; | |||
import java.util.Collections; | |||
@@ -64,6 +66,8 @@ import org.eclipse.jgit.events.ConfigChangedListener; | |||
import org.eclipse.jgit.events.ListenerHandle; | |||
import org.eclipse.jgit.events.ListenerList; | |||
import org.eclipse.jgit.internal.JGitText; | |||
import org.eclipse.jgit.util.IO; | |||
import org.eclipse.jgit.util.RawParseUtils; | |||
import org.eclipse.jgit.util.StringUtils; | |||
@@ -75,6 +79,7 @@ public class Config { | |||
private static final long KiB = 1024; | |||
private static final long MiB = 1024 * KiB; | |||
private static final long GiB = 1024 * MiB; | |||
private static final int MAX_DEPTH = 999; | |||
/** the change listeners */ | |||
private final ListenerList listeners = new ListenerList(); | |||
@@ -1027,6 +1032,15 @@ public class Config { | |||
* made to {@code this}. | |||
*/ | |||
public void fromText(final String text) throws ConfigInvalidException { | |||
state.set(newState(fromTextRecurse(text, 1))); | |||
} | |||
private List<ConfigLine> fromTextRecurse(final String text, int depth) | |||
throws ConfigInvalidException { | |||
if (depth > MAX_DEPTH) { | |||
throw new ConfigInvalidException( | |||
JGitText.get().tooManyIncludeRecursions); | |||
} | |||
final List<ConfigLine> newEntries = new ArrayList<ConfigLine>(); | |||
final StringReader in = new StringReader(text); | |||
ConfigLine last = null; | |||
@@ -1085,11 +1099,38 @@ public class Config { | |||
} else | |||
e.value = readValue(in, false, -1); | |||
if (e.section.equals("include")) { //$NON-NLS-1$ | |||
addIncludedConfig(newEntries, e, depth); | |||
} | |||
} else | |||
throw new ConfigInvalidException(JGitText.get().invalidLineInConfigFile); | |||
} | |||
state.set(newState(newEntries)); | |||
return newEntries; | |||
} | |||
private void addIncludedConfig(final List<ConfigLine> newEntries, | |||
ConfigLine line, int depth) throws ConfigInvalidException { | |||
if (!line.name.equals("path") || //$NON-NLS-1$ | |||
line.value == null || line.value.equals(MAGIC_EMPTY_VALUE)) { | |||
throw new ConfigInvalidException( | |||
JGitText.get().invalidLineInConfigFile); | |||
} | |||
File path = new File(line.value); | |||
try { | |||
byte[] bytes = IO.readFully(path); | |||
String decoded; | |||
if (isUtf8(bytes)) { | |||
decoded = RawParseUtils.decode(RawParseUtils.UTF8_CHARSET, | |||
bytes, 3, bytes.length); | |||
} else { | |||
decoded = RawParseUtils.decode(bytes); | |||
} | |||
newEntries.addAll(fromTextRecurse(decoded, depth + 1)); | |||
} catch (IOException ioe) { | |||
throw new ConfigInvalidException(MessageFormat.format( | |||
JGitText.get().invalidIncludedPathInConfigFile, path)); | |||
} | |||
} | |||
private ConfigSnapshot newState() { |