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
assertFalse(c.getBoolean("s", "b", true)); | 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 { | public void testReadLong() throws ConfigInvalidException { | ||||
assertReadLong(1L); | assertReadLong(1L); | ||||
assertReadLong(-1L); | assertReadLong(-1L); |
import org.eclipse.jgit.lib.Constants; | import org.eclipse.jgit.lib.Constants; | ||||
import org.eclipse.jgit.lib.FileMode; | import org.eclipse.jgit.lib.FileMode; | ||||
import org.eclipse.jgit.lib.ObjectReader; | import org.eclipse.jgit.lib.ObjectReader; | ||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||||
public class AbstractTreeIteratorTest extends TestCase { | public class AbstractTreeIteratorTest extends TestCase { | ||||
public class FakeTreeIterator extends WorkingTreeIterator { | public class FakeTreeIterator extends WorkingTreeIterator { | ||||
public FakeTreeIterator(String pathName, FileMode fileMode) { | public FakeTreeIterator(String pathName, FileMode fileMode) { | ||||
super(prefix(pathName), new WorkingTreeOptions(false)); | |||||
super(prefix(pathName), new WorkingTreeOptions(AutoCRLF.FALSE)); | |||||
mode = fileMode.getBits(); | mode = fileMode.getBits(); | ||||
final int s = pathName.lastIndexOf('/'); | final int s = pathName.lastIndexOf('/'); |
import org.eclipse.jgit.lib.ObjectReader; | import org.eclipse.jgit.lib.ObjectReader; | ||||
import org.eclipse.jgit.lib.Repository; | import org.eclipse.jgit.lib.Repository; | ||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||||
import org.eclipse.jgit.util.FS; | import org.eclipse.jgit.util.FS; | ||||
/** | /** | ||||
public FileTreeIteratorWithTimeControl(File f, FS fs, | public FileTreeIteratorWithTimeControl(File f, FS fs, | ||||
TreeSet<Long> modTimes) { | TreeSet<Long> modTimes) { | ||||
super(f, fs, new WorkingTreeOptions(false)); | |||||
super(f, fs, new WorkingTreeOptions(AutoCRLF.FALSE)); | |||||
this.modTimes = modTimes; | this.modTimes = modTimes; | ||||
} | } | ||||
encryptionError=Encryption error: {0} | encryptionError=Encryption error: {0} | ||||
endOfFileInEscape=End of file in escape | endOfFileInEscape=End of file in escape | ||||
entryNotFoundByPath=Entry not found by path: {0} | 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} | errorDecodingFromFile=Error decoding from file {0} | ||||
errorEncodingFromFile=Error encoding from file {0} | errorEncodingFromFile=Error encoding from file {0} | ||||
errorInBase64CodeReadingStream=Error in Base64 code reading stream. | errorInBase64CodeReadingStream=Error in Base64 code reading stream. |
/***/ public String encryptionError; | /***/ public String encryptionError; | ||||
/***/ public String endOfFileInEscape; | /***/ public String endOfFileInEscape; | ||||
/***/ public String entryNotFoundByPath; | /***/ public String entryNotFoundByPath; | ||||
/***/ public String enumValueNotSupported2; | |||||
/***/ public String enumValueNotSupported3; | |||||
/***/ public String enumValuesNotAvailable; | |||||
/***/ public String errorDecodingFromFile; | /***/ public String errorDecodingFromFile; | ||||
/***/ public String errorEncodingFromFile; | /***/ public String errorEncodingFromFile; | ||||
/***/ public String errorInBase64CodeReadingStream; | /***/ public String errorInBase64CodeReadingStream; |
} | } | ||||
} | } | ||||
/** | |||||
* 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 | * Get string value | ||||
* | * | ||||
setString(section, subsection, name, value ? "true" : "false"); | 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 | * Add or modify a configuration value. The parameters will result in a | ||||
* configuration entry like this. | * configuration entry like this. |
} | } | ||||
}; | }; | ||||
/** 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 compression; | ||||
private final int packIndexVersion; | private final int packIndexVersion; | ||||
private final boolean logAllRefUpdates; | private final boolean logAllRefUpdates; | ||||
private final boolean autoCRLF; | |||||
private final AutoCRLF autoCRLF; | |||||
private CoreConfig(final Config rc) { | private CoreConfig(final Config rc) { | ||||
compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION); | compression = rc.getInt("core", "compression", DEFAULT_COMPRESSION); | ||||
packIndexVersion = rc.getInt("pack", "indexversion", 2); | packIndexVersion = rc.getInt("pack", "indexversion", 2); | ||||
logAllRefUpdates = rc.getBoolean("core", "logallrefupdates", true); | logAllRefUpdates = rc.getBoolean("core", "logallrefupdates", true); | ||||
autoCRLF = rc.getBoolean("core", "autocrlf", false); | |||||
autoCRLF = rc.getEnum("core", null, "autocrlf", AutoCRLF.FALSE); | |||||
} | } | ||||
/** | /** | ||||
/** | /** | ||||
* @return whether automatic CRLF conversion has been configured | * @return whether automatic CRLF conversion has been configured | ||||
*/ | */ | ||||
public boolean isAutoCRLF() { | |||||
public AutoCRLF getAutoCRLF() { | |||||
return autoCRLF; | return autoCRLF; | ||||
} | } | ||||
} | } |
} | } | ||||
private boolean mightNeedCleaning(Entry entry) { | 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) { | private boolean isBinary(Entry entry, byte[] content, int sz) { |
import org.eclipse.jgit.lib.Config; | import org.eclipse.jgit.lib.Config; | ||||
import org.eclipse.jgit.lib.CoreConfig; | import org.eclipse.jgit.lib.CoreConfig; | ||||
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; | |||||
/** | /** | ||||
* Contains options used by the WorkingTreeIterator. | * Contains options used by the WorkingTreeIterator. | ||||
* @return created working tree options | * @return created working tree options | ||||
*/ | */ | ||||
public static WorkingTreeOptions createDefaultInstance() { | public static WorkingTreeOptions createDefaultInstance() { | ||||
return new WorkingTreeOptions(false); | |||||
return new WorkingTreeOptions(AutoCRLF.FALSE); | |||||
} | } | ||||
/** | /** | ||||
* @return created working tree options | * @return created working tree options | ||||
*/ | */ | ||||
public static WorkingTreeOptions createConfigurationInstance(Config config) { | 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 | * Indicates whether EOLs of text files should be converted to '\n' before | ||||
* calculating the blob ID. | * calculating the blob ID. | ||||
**/ | **/ | ||||
private final boolean autoCRLF; | |||||
private final AutoCRLF autoCRLF; | |||||
/** | /** | ||||
* Creates new options. | * Creates new options. | ||||
* indicates whether EOLs of text files should be converted to | * indicates whether EOLs of text files should be converted to | ||||
* '\n' before calculating the blob ID. | * '\n' before calculating the blob ID. | ||||
*/ | */ | ||||
public WorkingTreeOptions(boolean autoCRLF) { | |||||
public WorkingTreeOptions(AutoCRLF autoCRLF) { | |||||
this.autoCRLF = autoCRLF; | this.autoCRLF = autoCRLF; | ||||
} | } | ||||
* | * | ||||
* @return true if EOLs should be canonicalized. | * @return true if EOLs should be canonicalized. | ||||
*/ | */ | ||||
public boolean isAutoCRLF() { | |||||
public AutoCRLF getAutoCRLF() { | |||||
return autoCRLF; | return autoCRLF; | ||||
} | } | ||||
} | } |