Browse Source

Config: Preserve existing case of names in sections

When an application asks for the names in a section, it may want to
see the existing case that was stored by the user.  For example,
Gerrit Code Review wants to store a configuration block like:

  [access "refs/heads/master"]
    label-Code-Review = group Developers

and although the name label-Code-Review is case-insensitive, it wants
to display the case as it appeared in the configuration file.

When enumerating section names or variable names (both of which are
case-insensitive), Config now keeps track of the string that first
appeared, and presents them in file order, permitting applications to
use this information.  To maintain case-insensitive behavior, the
contains() method of the returned Set<String> still performs a
case-insensitive compare.

This is a behavior change if the caller enumerates the returned
Set<String> and copies it to his own Set<String>, and then performs
contains() tests against that, as the strings are now the original
case from the configuration block.  But I don't think anyone actually
does this, as the returned sets are immutable and are cached.

Change-Id: Ie4e060ef7772958b2062679e462c34c506371740
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
stable-0.11
Shawn O. Pearce 14 years ago
parent
commit
b2d528887c
  1. 17
      org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java
  2. 63
      org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java

17
org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ConfigTest.java

@ -58,6 +58,7 @@ import static org.junit.Assert.fail;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.Arrays; import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.Set; import java.util.Set;
@ -386,13 +387,25 @@ public class ConfigTest {
@Test @Test
public void test009_readNamesInSection() throws ConfigInvalidException { public void test009_readNamesInSection() throws ConfigInvalidException {
String configString = "[core]\n" + "repositoryformatversion = 0\n" String configString = "[core]\n" + "repositoryFormatVersion = 0\n"
+ "filemode = false\n" + "logallrefupdates = true\n"; + "filemode = false\n" + "logAllRefUpdates = true\n";
final Config c = parse(configString); final Config c = parse(configString);
Set<String> names = c.getNames("core"); Set<String> names = c.getNames("core");
assertEquals("Core section size", 3, names.size()); assertEquals("Core section size", 3, names.size());
assertTrue("Core section should contain \"filemode\"", names assertTrue("Core section should contain \"filemode\"", names
.contains("filemode")); .contains("filemode"));
assertTrue("Core section should contain \"repositoryFormatVersion\"",
names.contains("repositoryFormatVersion"));
assertTrue("Core section should contain \"repositoryformatversion\"",
names.contains("repositoryformatversion"));
Iterator<String> itr = names.iterator();
assertEquals("repositoryFormatVersion", itr.next());
assertEquals("filemode", itr.next());
assertEquals("logAllRefUpdates", itr.next());
assertFalse(itr.hasNext());
} }
@Test @Test

63
org.eclipse.jgit/src/org/eclipse/jgit/lib/Config.java

@ -52,9 +52,12 @@
package org.eclipse.jgit.lib; package org.eclipse.jgit.lib;
import java.text.MessageFormat; import java.text.MessageFormat;
import java.util.AbstractSet;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
@ -1327,39 +1330,71 @@ public class Config {
} }
public Set<String> parse(Config cfg) { public Set<String> parse(Config cfg) {
final Set<String> result = new HashSet<String>(); final Map<String, String> m = new LinkedHashMap<String, String>();
while (cfg != null) { while (cfg != null) {
for (final Entry e : cfg.state.get().entryList) { for (final Entry e : cfg.state.get().entryList) {
if (e.name != null if (e.name == null)
&& StringUtils.equalsIgnoreCase(e.section, section)) { continue;
if (subsection == null && e.subsection == null) if (!StringUtils.equalsIgnoreCase(section, e.section))
result.add(StringUtils.toLowerCase(e.name)); continue;
else if (e.subsection != null if ((subsection == null && e.subsection == null)
&& e.subsection.equals(subsection)) || (subsection != null && subsection
result.add(StringUtils.toLowerCase(e.name)); .equals(e.subsection))) {
String lc = StringUtils.toLowerCase(e.name);
if (!m.containsKey(lc))
m.put(lc, e.name);
} }
} }
cfg = cfg.baseConfig; cfg = cfg.baseConfig;
} }
return Collections.unmodifiableSet(result); return new CaseFoldingSet(m);
} }
} }
private static class SectionNames implements SectionParser<Set<String>> { private static class SectionNames implements SectionParser<Set<String>> {
public Set<String> parse(Config cfg) { public Set<String> parse(Config cfg) {
final Set<String> result = new HashSet<String>(); final Map<String, String> m = new LinkedHashMap<String, String>();
while (cfg != null) { while (cfg != null) {
for (final Entry e : cfg.state.get().entryList) { for (final Entry e : cfg.state.get().entryList) {
if (e.section != null) if (e.section != null) {
result.add(StringUtils.toLowerCase(e.section)); String lc = StringUtils.toLowerCase(e.section);
if (!m.containsKey(lc))
m.put(lc, e.section);
}
} }
cfg = cfg.baseConfig; cfg = cfg.baseConfig;
} }
return Collections.unmodifiableSet(result); return new CaseFoldingSet(m);
} }
} }
private static class CaseFoldingSet extends AbstractSet<String> {
private final Map<String, String> names;
CaseFoldingSet(Map<String, String> names) {
this.names = Collections.unmodifiableMap(names);
}
@Override
public boolean contains(Object needle) {
if (!(needle instanceof String))
return false;
String n = (String) needle;
return names.containsKey(n)
|| names.containsKey(StringUtils.toLowerCase(n));
}
@Override
public Iterator<String> iterator() {
return names.values().iterator();
}
@Override
public int size() {
return names.size();
}
}
private static class State { private static class State {
final List<Entry> entryList; final List<Entry> entryList;

Loading…
Cancel
Save