# HG changeset patch # User Artem Tikhomirov # Date 1314136058 -7200 # Node ID 7af843ecc3781e1f30ecade1d1c758df835488b9 # Parent c5980f287cc4ef86ea306554053c3cb120248175 Respect glob pattern with alternatives {a,b} diff -r c5980f287cc4 -r 7af843ecc378 src/org/tmatesoft/hg/repo/HgIgnore.java --- a/src/org/tmatesoft/hg/repo/HgIgnore.java Tue Aug 23 22:30:56 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgIgnore.java Tue Aug 23 23:47:38 2011 +0200 @@ -45,27 +45,41 @@ if (!hgignoreFile.exists()) { return; } + BufferedReader fr = new BufferedReader(new FileReader(hgignoreFile)); + try { + read(fr); + } finally { + fr.close(); + } + } + + /* package-local */void read(BufferedReader content) throws IOException { ArrayList result = new ArrayList(entries); // start with existing - String syntax = "regex"; // or "glob" - BufferedReader fr = new BufferedReader(new FileReader(hgignoreFile)); + String syntax = "regexp"; // or "glob" String line; - while ((line = fr.readLine()) != null) { + while ((line = content.readLine()) != null) { line = line.trim(); if (line.startsWith("syntax:")) { syntax = line.substring("syntax:".length()).trim(); - if (!"regex".equals(syntax) && !"glob".equals(syntax)) { + if (!"regexp".equals(syntax) && !"glob".equals(syntax)) { throw new IllegalStateException(line); } } else if (line.length() > 0) { // shall I account for local paths in the file (i.e. // back-slashed on windows)? - int x; - if ((x = line.indexOf('#')) >= 0) { - line = line.substring(0, x).trim(); - if (line.length() == 0) { - continue; + int x, s = 0; + while ((x = line.indexOf('#', s)) >= 0) { + if (x > 0 && line.charAt(x-1) == '\\') { + // remove escape char + line = line.substring(0, x-1).concat(line.substring(x)); + s = x; // with exclusion of char at [x], s now points to what used to be at [x+1] + } else { + line = line.substring(0, x).trim(); } } + if (line.length() == 0) { + continue; + } if ("glob".equals(syntax)) { // hgignore(5) // (http://www.selenic.com/mercurial/hgignore.5.html) says slashes '\' are escape characters, @@ -76,7 +90,6 @@ result.add(Pattern.compile(line)); // case-sensitive } } - fr.close(); result.trimToSize(); entries = result; } @@ -94,13 +107,19 @@ private String glob2regex(String line) { assert line.length() > 0; StringBuilder sb = new StringBuilder(line.length() + 10); - sb.append('^'); // help avoid matcher.find() to match 'bin' pattern in the middle of the filename + if (line.charAt(0) != '*') { + sb.append('^'); // help avoid matcher.find() to match 'bin' pattern in the middle of the filename + } int start = 0, end = line.length() - 1; // '*' at the beginning and end of a line are useless for Pattern // XXX although how about **.txt - such globs can be seen in a config, are they valid for HgIgnore? while (start <= end && line.charAt(start) == '*') start++; while (end > start && line.charAt(end) == '*') end--; + if (line.endsWith(".so")) { + System.out.println(); + } + int inCurly = 0; for (int i = start; i <= end; i++) { char ch = line.charAt(i); if (ch == '.' || ch == '\\') { @@ -117,6 +136,21 @@ } else if (ch == '*') { sb.append("[^/]*?"); continue; + } else if (ch == '{') { + // XXX in fact, need to respect if last char was escaping ('\\'), then don't need to treat this as special + // see link at javadoc above for reasonable example + inCurly++; + sb.append('('); + continue; + } else if (ch == '}') { + if (inCurly > 0) { + inCurly--; + sb.append(')'); + continue; + } + } else if (ch == ',' && inCurly > 0) { + sb.append('|'); + continue; } sb.append(ch); } diff -r c5980f287cc4 -r 7af843ecc378 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Tue Aug 23 22:30:56 2011 +0200 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Tue Aug 23 23:47:38 2011 +0200 @@ -18,7 +18,10 @@ import static org.tmatesoft.hg.repo.HgRepository.*; +import java.io.BufferedReader; import java.io.File; +import java.io.IOException; +import java.io.Reader; import java.net.InetAddress; import java.net.UnknownHostException; @@ -69,6 +72,14 @@ public ConfigFile getRepoConfig() { return repo.getConfigFile(); } + + public static HgIgnore newHgIgnore(Reader source) throws IOException { + HgIgnore hgIgnore = new HgIgnore(); + BufferedReader br = source instanceof BufferedReader ? (BufferedReader) source : new BufferedReader(source); + hgIgnore.read(br); + br.close(); + return hgIgnore; + } // in fact, need a setter for this anyway, shall move to internal.Internals perhaps? public String getNextCommitUsername() { diff -r c5980f287cc4 -r 7af843ecc378 test-data/mercurial.hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test-data/mercurial.hgignore Tue Aug 23 23:47:38 2011 +0200 @@ -0,0 +1,49 @@ +syntax: glob + +*.elc +*.orig +*.rej +*~ +*.mergebackup +*.o +*.so +*.dll +*.pyd +*.pyc +*.pyo +*$py.class +*.swp +*.prof +\#*\# +.\#* +tests/.coverage* +tests/annotated +tests/*.err +build +contrib/hgsh/hgsh +dist +doc/*.[0-9] +doc/*.[0-9].gendoc.txt +doc/*.[0-9].{x,ht}ml +MANIFEST +patches +mercurial/__version__.py +mercurial.egg-info +Output/Mercurial-*.exe +.DS_Store +tags +cscope.* +i18n/hg.pot +locale/*/LC_MESSAGES/hg.mo + +# files installed with a local --pure build +mercurial/base85.py +mercurial/bdiff.py +mercurial/diffhelpers.py +mercurial/mpatch.py +mercurial/osutil.py +mercurial/parsers.py + +syntax: regexp +^\.pc/ +^\.(pydev)?project diff -r c5980f287cc4 -r 7af843ecc378 test/org/tmatesoft/hg/test/Configuration.java --- a/test/org/tmatesoft/hg/test/Configuration.java Tue Aug 23 22:30:56 2011 +0200 +++ b/test/org/tmatesoft/hg/test/Configuration.java Tue Aug 23 23:47:38 2011 +0200 @@ -39,6 +39,7 @@ private final HgLookup lookup; private File tempDir; private List remoteServers; + private File testDataDir; private Configuration() { lookup = new HgLookup(); @@ -98,4 +99,11 @@ } return tempDir; } + + public File getTestDataDir() { + if (testDataDir == null) { + testDataDir = new File(System.getProperty("user.dir"), "test-data"); + } + return testDataDir; + } } diff -r c5980f287cc4 -r 7af843ecc378 test/org/tmatesoft/hg/test/ErrorCollectorExt.java --- a/test/org/tmatesoft/hg/test/ErrorCollectorExt.java Tue Aug 23 22:30:56 2011 +0200 +++ b/test/org/tmatesoft/hg/test/ErrorCollectorExt.java Tue Aug 23 23:47:38 2011 +0200 @@ -21,6 +21,7 @@ import java.util.concurrent.Callable; import org.hamcrest.Matcher; +import org.junit.internal.runners.model.MultipleFailureException; import org.junit.rules.ErrorCollector; /** @@ -31,7 +32,14 @@ */ final class ErrorCollectorExt extends ErrorCollector { public void verify() throws Throwable { - super.verify(); + try { + super.verify(); + } catch (MultipleFailureException ex) { + for (Throwable t : ex.getFailures()) { + t.printStackTrace(); + } + throw ex; + } } public void checkThat(final String reason, final T value, final Matcher matcher) { @@ -42,4 +50,13 @@ } }); } + + public void assertTrue(final String reason, final boolean value) { + checkSucceeds(new Callable() { + public Object call() throws Exception { + org.junit.Assert.assertTrue(reason, value); + return null; + } + }); + } } \ No newline at end of file diff -r c5980f287cc4 -r 7af843ecc378 test/org/tmatesoft/hg/test/TestIgnore.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/org/tmatesoft/hg/test/TestIgnore.java Tue Aug 23 23:47:38 2011 +0200 @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2011 TMate Software Ltd + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * For information on how to redistribute this software under + * the terms of a license other than GNU General Public License + * contact TMate Software at support@hg4j.com + */ +package org.tmatesoft.hg.test; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.io.StringReader; + +import org.junit.Rule; +import org.junit.Test; +import org.tmatesoft.hg.repo.HgIgnore; +import org.tmatesoft.hg.repo.HgInternals; +import org.tmatesoft.hg.util.Path; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class TestIgnore { + + @Rule + public ErrorCollectorExt errorCollector = new ErrorCollectorExt(); + + public static void main(String[] args) throws Throwable { + TestIgnore test = new TestIgnore(); + test.testGlobWithAlternatives(); + test.testComplexFileParse(); + test.errorCollector.verify(); + } + + @Test + public void testGlobWithAlternatives() throws Exception { + String content = "syntax:glob\ndoc/*.[0-9].{x,ht}ml"; + HgIgnore hgIgnore = HgInternals.newHgIgnore(new StringReader(content)); + final Path p1 = Path.create("doc/asd.2.xml"); + final Path p2 = Path.create("doc/zxc.6.html"); + errorCollector.assertTrue(p1.toString(), hgIgnore.isIgnored(p1)); + errorCollector.assertTrue(p2.toString(), hgIgnore.isIgnored(p2)); + } + + @Test + public void testComplexFileParse() throws Exception { + BufferedReader br = new BufferedReader(new FileReader(new File(Configuration.get().getTestDataDir(), "mercurial.hgignore"))); + HgIgnore hgIgnore = HgInternals.newHgIgnore(br); + br.close(); + Path[] toCheck = new Path[] { + Path.create("file.so"), + Path.create("a/b/file.so"), + Path.create("#abc#"), + Path.create(".#abc"), + Path.create("locale/en/LC_MESSAGES/hg.mo"), + }; + for (Path p : toCheck) { + errorCollector.assertTrue(p.toString(), hgIgnore.isIgnored(p)); + } + } + +}