From 9f61c615e858c038a754dc512476ccf215385d8f Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Tue, 7 Sep 2010 17:14:27 -0700 Subject: [PATCH] Support core.autocrlf = input 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 --- .../tst/org/eclipse/jgit/lib/ConfigTest.java | 32 +++++ .../treewalk/AbstractTreeIteratorTest.java | 3 +- .../FileTreeIteratorWithTimeControl.java | 3 +- .../org/eclipse/jgit/JGitText.properties | 3 + .../src/org/eclipse/jgit/JGitText.java | 3 + .../src/org/eclipse/jgit/lib/Config.java | 115 ++++++++++++++++++ .../src/org/eclipse/jgit/lib/CoreConfig.java | 18 ++- .../jgit/treewalk/WorkingTreeIterator.java | 10 +- .../jgit/treewalk/WorkingTreeOptions.java | 11 +- 9 files changed, 187 insertions(+), 11 deletions(-) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java index e12e869ec..eb9f03c55 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java @@ -229,6 +229,38 @@ public void testReadBoolean_OnOff2() throws ConfigInvalidException { 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); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java index 72354e4e9..cc7d0ad9e 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/AbstractTreeIteratorTest.java @@ -52,6 +52,7 @@ 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 @@ private static String prefix(String path) { 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('/'); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java index 58fb5297a..f15454ba0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorWithTimeControl.java @@ -48,6 +48,7 @@ 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 FileTreeIteratorWithTimeControl(Repository repo, public FileTreeIteratorWithTimeControl(File f, FS fs, TreeSet modTimes) { - super(f, fs, new WorkingTreeOptions(false)); + super(f, fs, new WorkingTreeOptions(AutoCRLF.FALSE)); this.modTimes = modTimes; } diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties index cd932c6d8..e8ec148bd 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/JGitText.properties @@ -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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java index 7fa265847..c7b5a9ee6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/JGitText.java @@ -196,6 +196,9 @@ public static JGitText get() { /***/ 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; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java index 884f49845..daad67e29 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java @@ -333,6 +333,95 @@ public boolean getBoolean(final String section, String subsection, } } + /** + * Parse an enumeration from the configuration. + * + * @param + * 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 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[] 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 + * 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 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 void setBoolean(final String section, final String subsection, setString(section, subsection, name, value ? "true" : "false"); } + /** + * Add or modify a configuration value. The parameters will result in a + * configuration entry like this. + * + *
+	 * [section "subsection"]
+	 *         name = value
+	 * 
+ * + * @param + * 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 > 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. diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java index 249b95ec3..f644d2c69 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/CoreConfig.java @@ -61,19 +61,31 @@ public CoreConfig parse(final Config cfg) { } }; + /** 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 boolean isLogAllRefUpdates() { /** * @return whether automatic CRLF conversion has been configured */ - public boolean isAutoCRLF() { + public AutoCRLF getAutoCRLF() { return autoCRLF; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 5cc061bbb..b292c3cdd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -299,7 +299,15 @@ private static void safeClose(final InputStream in) { } 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) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java index 50da3302d..6929046c2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeOptions.java @@ -44,6 +44,7 @@ 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 static WorkingTreeOptions createDefaultInstance() { * @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 static WorkingTreeOptions createConfigurationInstance(Config config) { * 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 WorkingTreeOptions(boolean autoCRLF) { * * @return true if EOLs should be canonicalized. */ - public boolean isAutoCRLF() { + public AutoCRLF getAutoCRLF() { return autoCRLF; } }