The core.autocrlf variable can take on three values: false, true, and input. Parsing it as a boolean is wrong, we instead need to parse a tri-state enumeration. Add support for parsing and setting enum values from Java from and to the text based configuration file, and use that to handle the autocrlf variable. Bug: 301775 Change-Id: I81b9e33087a33d2ef2eac89ba93b9e83b7ecc223 Signed-off-by: Shawn O. Pearce <spearce@spearce.org>tags/v0.9.1
@@ -229,6 +229,38 @@ public class ConfigTest extends TestCase { | |||
assertFalse(c.getBoolean("s", "b", true)); | |||
} | |||
static enum TestEnum { | |||
ONE_TWO; | |||
} | |||
public void testGetEnum() throws ConfigInvalidException { | |||
Config c = parse("[s]\na = ON\nb = input\nc = true\nd = off\n"); | |||
assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "a", | |||
CoreConfig.AutoCRLF.FALSE)); | |||
assertSame(CoreConfig.AutoCRLF.INPUT, c.getEnum("s", null, "b", | |||
CoreConfig.AutoCRLF.FALSE)); | |||
assertSame(CoreConfig.AutoCRLF.TRUE, c.getEnum("s", null, "c", | |||
CoreConfig.AutoCRLF.FALSE)); | |||
assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d", | |||
CoreConfig.AutoCRLF.TRUE)); | |||
c = new Config(); | |||
assertSame(CoreConfig.AutoCRLF.FALSE, c.getEnum("s", null, "d", | |||
CoreConfig.AutoCRLF.FALSE)); | |||
c = parse("[s \"b\"]\n\tc = one two\n"); | |||
assertSame(TestEnum.ONE_TWO, c.getEnum("s", "b", "c", TestEnum.ONE_TWO)); | |||
} | |||
public void testSetEnum() { | |||
final Config c = new Config(); | |||
c.setEnum("s", "b", "c", TestEnum.ONE_TWO); | |||
assertEquals("[s \"b\"]\n\tc = one two\n", c.toText()); | |||
} | |||
public void testReadLong() throws ConfigInvalidException { | |||
assertReadLong(1L); | |||
assertReadLong(-1L); |
@@ -52,6 +52,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException; | |||
import org.eclipse.jgit.lib.Constants; | |||
import org.eclipse.jgit.lib.FileMode; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||
public class AbstractTreeIteratorTest extends TestCase { | |||
@@ -62,7 +63,7 @@ public class AbstractTreeIteratorTest extends TestCase { | |||
public class FakeTreeIterator extends WorkingTreeIterator { | |||
public FakeTreeIterator(String pathName, FileMode fileMode) { | |||
super(prefix(pathName), new WorkingTreeOptions(false)); | |||
super(prefix(pathName), new WorkingTreeOptions(AutoCRLF.FALSE)); | |||
mode = fileMode.getBits(); | |||
final int s = pathName.lastIndexOf('/'); |
@@ -48,6 +48,7 @@ import java.util.TreeSet; | |||
import org.eclipse.jgit.lib.ObjectReader; | |||
import org.eclipse.jgit.lib.Repository; | |||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||
import org.eclipse.jgit.util.FS; | |||
/** | |||
@@ -87,7 +88,7 @@ public class FileTreeIteratorWithTimeControl extends FileTreeIterator { | |||
public FileTreeIteratorWithTimeControl(File f, FS fs, | |||
TreeSet<Long> modTimes) { | |||
super(f, fs, new WorkingTreeOptions(false)); | |||
super(f, fs, new WorkingTreeOptions(AutoCRLF.FALSE)); | |||
this.modTimes = modTimes; | |||
} | |||
@@ -136,6 +136,9 @@ emptyPathNotPermitted=Empty path not permitted. | |||
encryptionError=Encryption error: {0} | |||
endOfFileInEscape=End of file in escape | |||
entryNotFoundByPath=Entry not found by path: {0} | |||
enumValueNotSupported2=Invalid value: {0}.{1}={2} | |||
enumValueNotSupported3=Invalid value: {0}.{1}.{2}={3} | |||
enumValuesNotAvailable=Enumerated values of type {0} not available | |||
errorDecodingFromFile=Error decoding from file {0} | |||
errorEncodingFromFile=Error encoding from file {0} | |||
errorInBase64CodeReadingStream=Error in Base64 code reading stream. |
@@ -196,6 +196,9 @@ public class JGitText extends TranslationBundle { | |||
/***/ public String encryptionError; | |||
/***/ public String endOfFileInEscape; | |||
/***/ public String entryNotFoundByPath; | |||
/***/ public String enumValueNotSupported2; | |||
/***/ public String enumValueNotSupported3; | |||
/***/ public String enumValuesNotAvailable; | |||
/***/ public String errorDecodingFromFile; | |||
/***/ public String errorEncodingFromFile; | |||
/***/ public String errorInBase64CodeReadingStream; |
@@ -333,6 +333,95 @@ public class Config { | |||
} | |||
} | |||
/** | |||
* Parse an enumeration from the configuration. | |||
* | |||
* @param <T> | |||
* type of the enumeration object. | |||
* @param section | |||
* section the key is grouped within. | |||
* @param subsection | |||
* subsection name, such a remote or branch name. | |||
* @param name | |||
* name of the key to get. | |||
* @param defaultValue | |||
* default value to return if no value was present. | |||
* @return the selected enumeration value, or {@code defaultValue}. | |||
*/ | |||
public <T extends Enum<?>> T getEnum(final String section, | |||
final String subsection, final String name, final T defaultValue) { | |||
final T[] all = allValuesOf(defaultValue); | |||
return getEnum(all, section, subsection, name, defaultValue); | |||
} | |||
@SuppressWarnings("unchecked") | |||
private static <T> T[] allValuesOf(final T value) { | |||
try { | |||
return (T[]) value.getClass().getMethod("values").invoke(null); | |||
} catch (Exception err) { | |||
String typeName = value.getClass().getName(); | |||
String msg = MessageFormat.format( | |||
JGitText.get().enumValuesNotAvailable, typeName); | |||
throw new IllegalArgumentException(msg, err); | |||
} | |||
} | |||
/** | |||
* Parse an enumeration from the configuration. | |||
* | |||
* @param <T> | |||
* type of the enumeration object. | |||
* @param all | |||
* all possible values in the enumeration which should be | |||
* recognized. Typically {@code EnumType.values()}. | |||
* @param section | |||
* section the key is grouped within. | |||
* @param subsection | |||
* subsection name, such a remote or branch name. | |||
* @param name | |||
* name of the key to get. | |||
* @param defaultValue | |||
* default value to return if no value was present. | |||
* @return the selected enumeration value, or {@code defaultValue}. | |||
*/ | |||
public <T extends Enum<?>> T getEnum(final T[] all, final String section, | |||
final String subsection, final String name, final T defaultValue) { | |||
String value = getString(section, subsection, name); | |||
if (value == null) | |||
return defaultValue; | |||
String n = value.replace(' ', '_'); | |||
T trueState = null; | |||
T falseState = null; | |||
for (T e : all) { | |||
if (StringUtils.equalsIgnoreCase(e.name(), n)) | |||
return e; | |||
else if (StringUtils.equalsIgnoreCase(e.name(), "TRUE")) | |||
trueState = e; | |||
else if (StringUtils.equalsIgnoreCase(e.name(), "FALSE")) | |||
falseState = e; | |||
} | |||
// This is an odd little fallback. C Git sometimes allows boolean | |||
// values in a tri-state with other things. If we have both a true | |||
// and a false value in our enumeration, assume its one of those. | |||
// | |||
if (trueState != null && falseState != null) { | |||
try { | |||
return StringUtils.toBoolean(n) ? trueState : falseState; | |||
} catch (IllegalArgumentException err) { | |||
// Fall through and use our custom error below. | |||
} | |||
} | |||
if (subsection != null) | |||
throw new IllegalArgumentException(MessageFormat.format(JGitText | |||
.get().enumValueNotSupported3, section, name, value)); | |||
else | |||
throw new IllegalArgumentException(MessageFormat.format(JGitText | |||
.get().enumValueNotSupported2, section, name, value)); | |||
} | |||
/** | |||
* Get string value | |||
* | |||
@@ -625,6 +714,32 @@ public class Config { | |||
setString(section, subsection, name, value ? "true" : "false"); | |||
} | |||
/** | |||
* Add or modify a configuration value. The parameters will result in a | |||
* configuration entry like this. | |||
* | |||
* <pre> | |||
* [section "subsection"] | |||
* name = value | |||
* </pre> | |||
* | |||
* @param <T> | |||
* type of the enumeration object. | |||
* @param section | |||
* section name, e.g "branch" | |||
* @param subsection | |||
* optional subsection value, e.g. a branch name | |||
* @param name | |||
* parameter name, e.g. "filemode" | |||
* @param value | |||
* parameter value | |||
*/ | |||
public <T extends Enum<?>> void setEnum(final String section, | |||
final String subsection, final String name, final T value) { | |||
String n = value.name().toLowerCase().replace('_', ' '); | |||
setString(section, subsection, name, n); | |||
} | |||
/** | |||
* Add or modify a configuration value. The parameters will result in a | |||
* configuration entry like this. |
@@ -61,19 +61,31 @@ public class CoreConfig { | |||
} | |||
}; | |||
/** Permissible values for {@code core.autocrlf}. */ | |||
public static enum AutoCRLF { | |||
/** Automatic CRLF->LF conversion is disabled. */ | |||
FALSE, | |||
/** Automatic CRLF->LF conversion is enabled. */ | |||
TRUE, | |||
/** CRLF->LF performed, but no LF->CRLF. */ | |||
INPUT; | |||
} | |||
private final int compression; | |||
private final int packIndexVersion; | |||
private final boolean logAllRefUpdates; | |||
private final boolean autoCRLF; | |||
private final AutoCRLF autoCRLF; | |||
private CoreConfig(final Config rc) { | |||
compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION); | |||
packIndexVersion = rc.getInt("pack", "indexversion", 2); | |||
logAllRefUpdates = rc.getBoolean("core", "logallrefupdates", true); | |||
autoCRLF = rc.getBoolean("core", "autocrlf", false); | |||
autoCRLF = rc.getEnum("core", null, "autocrlf", AutoCRLF.FALSE); | |||
} | |||
/** | |||
@@ -101,7 +113,7 @@ public class CoreConfig { | |||
/** | |||
* @return whether automatic CRLF conversion has been configured | |||
*/ | |||
public boolean isAutoCRLF() { | |||
public AutoCRLF getAutoCRLF() { | |||
return autoCRLF; | |||
} | |||
} |
@@ -299,7 +299,15 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { | |||
} | |||
private boolean mightNeedCleaning(Entry entry) { | |||
return options.isAutoCRLF(); | |||
switch (options.getAutoCRLF()) { | |||
case FALSE: | |||
default: | |||
return false; | |||
case TRUE: | |||
case INPUT: | |||
return true; | |||
} | |||
} | |||
private boolean isBinary(Entry entry, byte[] content, int sz) { |
@@ -44,6 +44,7 @@ package org.eclipse.jgit.treewalk; | |||
import org.eclipse.jgit.lib.Config; | |||
import org.eclipse.jgit.lib.CoreConfig; | |||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||
/** | |||
* Contains options used by the WorkingTreeIterator. | |||
@@ -57,7 +58,7 @@ public class WorkingTreeOptions { | |||
* @return created working tree options | |||
*/ | |||
public static WorkingTreeOptions createDefaultInstance() { | |||
return new WorkingTreeOptions(false); | |||
return new WorkingTreeOptions(AutoCRLF.FALSE); | |||
} | |||
/** | |||
@@ -69,14 +70,14 @@ public class WorkingTreeOptions { | |||
* @return created working tree options | |||
*/ | |||
public static WorkingTreeOptions createConfigurationInstance(Config config) { | |||
return new WorkingTreeOptions(config.get(CoreConfig.KEY).isAutoCRLF()); | |||
return new WorkingTreeOptions(config.get(CoreConfig.KEY).getAutoCRLF()); | |||
} | |||
/** | |||
* Indicates whether EOLs of text files should be converted to '\n' before | |||
* calculating the blob ID. | |||
**/ | |||
private final boolean autoCRLF; | |||
private final AutoCRLF autoCRLF; | |||
/** | |||
* Creates new options. | |||
@@ -85,7 +86,7 @@ public class WorkingTreeOptions { | |||
* indicates whether EOLs of text files should be converted to | |||
* '\n' before calculating the blob ID. | |||
*/ | |||
public WorkingTreeOptions(boolean autoCRLF) { | |||
public WorkingTreeOptions(AutoCRLF autoCRLF) { | |||
this.autoCRLF = autoCRLF; | |||
} | |||
@@ -95,7 +96,7 @@ public class WorkingTreeOptions { | |||
* | |||
* @return true if EOLs should be canonicalized. | |||
*/ | |||
public boolean isAutoCRLF() { | |||
public AutoCRLF getAutoCRLF() { | |||
return autoCRLF; | |||
} | |||
} |