# HG changeset patch # User Artem Tikhomirov # Date 1296767635 -3600 # Node ID 46291ec605a012caf84db6d0004f176178308633 # Parent 67ae317408c95e19752b515b68f3ee084354a3bd Filters to read and initialize according to configuration files diff -r 67ae317408c9 -r 46291ec605a0 TODO --- a/TODO Wed Feb 02 21:19:02 2011 +0100 +++ b/TODO Thu Feb 03 22:13:55 2011 +0100 @@ -33,10 +33,13 @@ * tags * Tags are read and can be queried (cmdline Log does) -- keywords - - filter with context. filters shall have weight (to allow certain filter come first) +* keywords + + filter with context (HgRepository + Path + Direction (to/from repo) + - filters shall have weight (to allow certain filter come first). Would need that once FilterFactories are pluggable -- newlines +* newlines + + \r\n <==> \n + - force translation if inconsistent Proposed: diff -r 67ae317408c9 -r 46291ec605a0 cmdline/org/tmatesoft/hg/console/Remote.java --- a/cmdline/org/tmatesoft/hg/console/Remote.java Wed Feb 02 21:19:02 2011 +0100 +++ b/cmdline/org/tmatesoft/hg/console/Remote.java Thu Feb 03 22:13:55 2011 +0100 @@ -33,6 +33,7 @@ import javax.net.ssl.X509TrustManager; import org.tmatesoft.hg.internal.ConfigFile; +import org.tmatesoft.hg.internal.Internals; /** * WORK IN PROGRESS, DO NOT USE @@ -53,7 +54,7 @@ */ public static void main(String[] args) throws Exception { String nid = "d6d2a630f4a6d670c90a5ca909150f2b426ec88f"; - ConfigFile cfg = new ConfigFile(); + ConfigFile cfg = new Internals().newConfigFile(); cfg.addLocation(new File(System.getProperty("user.home"), ".hgrc")); String svnkitServer = cfg.getSection("paths").get("svnkit"); URL url = new URL(svnkitServer + "?cmd=changegroup&roots=a78c980749e3ccebb47138b547e9b644a22797a9"); diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/ConfigFile.java --- a/src/org/tmatesoft/hg/internal/ConfigFile.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/ConfigFile.java Thu Feb 03 22:13:55 2011 +0100 @@ -36,18 +36,35 @@ private List sections; private List> content; - public ConfigFile() { + ConfigFile() { } public void addLocation(File path) { read(path); } + public boolean hasSection(String sectionName) { + return sections == null ? false : sections.indexOf(sectionName) == -1; + } + + // XXX perhaps, should be moved to subclass HgRepoConfig, as it is not common operation for any config file + public boolean hasEnabledExtension(String extensionName) { + int x = sections != null ? sections.indexOf("extensions") : -1; + if (x == -1) { + return false; + } + String value = content.get(x).get(extensionName); + return value != null && !"!".equals(value); + } + public List getSectionNames() { - return Collections.unmodifiableList(sections); + return sections == null ? Collections.emptyList() : Collections.unmodifiableList(sections); } public Map getSection(String sectionName) { + if (sections == null) { + return Collections.emptyMap(); + } int x = sections.indexOf(sectionName); if (x == -1) { return Collections.emptyMap(); @@ -55,7 +72,25 @@ return Collections.unmodifiableMap(content.get(x)); } + public boolean getBoolean(String sectionName, String key, boolean defaultValue) { + String value = getSection(sectionName).get(key); + if (value == null) { + return defaultValue; + } + for (String s : new String[] { "true", "yes", "on", "1" }) { + if (s.equalsIgnoreCase(value)) { + return true; + } + } + return false; + } + + // TODO handle %include and %unset directives + // TODO "" and lists private void read(File f) { + if (f == null || !f.canRead()) { + return; + } if (sections == null) { sections = new ArrayList(); content = new ArrayList>(); diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/Filter.java --- a/src/org/tmatesoft/hg/internal/Filter.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/Filter.java Thu Feb 03 22:13:55 2011 +0100 @@ -31,14 +31,25 @@ ByteBuffer filter(ByteBuffer src); interface Factory { - Filter create(HgRepository hgRepo, Path path, Options opts); + void initialize(HgRepository hgRepo, ConfigFile cfg); + // may return null if for a given path and/or options this filter doesn't make any sense + Filter create(Path path, Options opts); } enum Direction { FromRepo, ToRepo } - abstract class Options { - abstract Direction getDirection(); + public class Options { + + private final Direction direction; + public Options(Direction dir) { + direction = dir; + } + + Direction getDirection() { + return direction; + } + } } diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/Internals.java --- a/src/org/tmatesoft/hg/internal/Internals.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/Internals.java Thu Feb 03 22:13:55 2011 +0100 @@ -16,10 +16,12 @@ */ package org.tmatesoft.hg.internal; -import static org.tmatesoft.hg.internal.RequiresFile.DOTENCODE; -import static org.tmatesoft.hg.internal.RequiresFile.FNCACHE; -import static org.tmatesoft.hg.internal.RequiresFile.STORE; +import static org.tmatesoft.hg.internal.RequiresFile.*; +import java.util.ArrayList; +import java.util.List; + +import org.tmatesoft.hg.repo.HgRepository; import org.tmatesoft.hg.util.PathRewrite; /** @@ -32,6 +34,11 @@ private int revlogVersion = 0; private int requiresFlags = 0; + private List filterFactories; + + + public Internals() { + } public/*for tests, otherwise pkg*/ void setStorageConfig(int version, int flags) { revlogVersion = version; @@ -59,4 +66,25 @@ }; } } + + public ConfigFile newConfigFile() { + return new ConfigFile(); + } + + public List getFilters(HgRepository hgRepo, ConfigFile cfg) { + if (filterFactories == null) { + filterFactories = new ArrayList(); + if (cfg.hasEnabledExtension("eol")) { + NewlineFilter.Factory ff = new NewlineFilter.Factory(); + ff.initialize(hgRepo, cfg); + filterFactories.add(ff); + } + if (cfg.hasEnabledExtension("keyword")) { + KeywordFilter.Factory ff = new KeywordFilter.Factory(); + ff.initialize(hgRepo, cfg); + filterFactories.add(ff); + } + } + return filterFactories; + } } diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/KeywordFilter.java --- a/src/org/tmatesoft/hg/internal/KeywordFilter.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/KeywordFilter.java Thu Feb 03 22:13:55 2011 +0100 @@ -16,15 +16,13 @@ */ package org.tmatesoft.hg.internal; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Map; import java.util.TreeMap; -import javax.swing.text.html.Option; - import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.repo.Changeset; import org.tmatesoft.hg.repo.HgRepository; /** @@ -34,15 +32,22 @@ */ public class KeywordFilter implements Filter { // present implementation is stateless, however, filter use pattern shall not assume that. In fact, Factory may us that + private final HgRepository repo; private final boolean isExpanding; private final TreeMap keywords; private final int minBufferLen; + private final Path path; + private Changeset latestFileCset; /** * + * @param hgRepo + * @param path * @param expand true to expand keywords, false to shrink */ - private KeywordFilter(boolean expand) { + private KeywordFilter(HgRepository hgRepo, Path p, boolean expand) { + repo = hgRepo; + path = p; isExpanding = expand; keywords = new TreeMap(); keywords.put("Id", "Id"); @@ -183,9 +188,13 @@ if ("Id".equals(keyword)) { rv.put(identityString().getBytes()); } else if ("Revision".equals(keyword)) { - rv.put(revision()); + rv.put(revision().getBytes()); } else if ("Author".equals(keyword)) { rv.put(username().getBytes()); + } else if ("Date".equals(keyword)) { + rv.put(date().getBytes()); + } else { + throw new IllegalStateException(String.format("Keyword %s is not yet supported", keyword)); } } @@ -202,9 +211,10 @@ chars[i] = c; } String kw = new String(chars, 0, i); - System.out.println(keywords.subMap("I", "J")); - System.out.println(keywords.subMap("A", "B")); - System.out.println(keywords.subMap("Au", "B")); +// XXX may use subMap to look up keywords based on few available characters (not waiting till closing $) +// System.out.println(keywords.subMap("I", "J")); +// System.out.println(keywords.subMap("A", "B")); +// System.out.println(keywords.subMap("Au", "B")); return keywords.get(kw); } @@ -235,48 +245,74 @@ } private String identityString() { - return "sample/file.txt, asd"; + return String.format("%s,v %s %s %s", path, revision(), date(), username()); } - private byte[] revision() { - return "1234567890ab".getBytes(); + private String revision() { + // FIXME add cset's nodeid into Changeset class + int csetRev = repo.getFileNode(path).getChangesetLocalRevision(HgRepository.TIP); + return repo.getChangelog().getRevision(csetRev).shortNotation(); } private String username() { - /* ui.py: username() - Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL - and stop searching if one of these is set. - If not found and ui.askusername is True, ask the user, else use - ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname". - */ - return " sample@sample.org"; + return getChangeset().user(); + } + + private String date() { + return String.format("%tY/% patterns = new ArrayList(); + for (Map.Entry e : cfg.getSection("keyword").entrySet()) { + if (!"ignore".equalsIgnoreCase(e.getValue())) { + patterns.add(e.getKey()); + } + } + matcher = new PathGlobMatcher(patterns.toArray(new String[patterns.size()])); + // TODO read and respect keyword patterns from [keywordmaps] + } + + public Filter create(Path path, Options opts) { + if (matcher.accept(path)) { + return new KeywordFilter(repo, path, true); + } + return null; } } - - public static void main(String[] args) throws Exception { - FileInputStream fis = new FileInputStream(new File("/temp/kwoutput.txt")); - FileOutputStream fos = new FileOutputStream(new File("/temp/kwoutput2.txt")); - ByteBuffer b = ByteBuffer.allocate(256); - KeywordFilter kwFilter = new KeywordFilter(false); - while (fis.getChannel().read(b) != -1) { - b.flip(); // get ready to be read - ByteBuffer f = kwFilter.filter(b); - fos.getChannel().write(f); // XXX in fact, f may not be fully consumed - if (b.hasRemaining()) { - b.compact(); - } else { - b.clear(); - } - } - fis.close(); - fos.flush(); - fos.close(); - } +// +// public static void main(String[] args) throws Exception { +// FileInputStream fis = new FileInputStream(new File("/temp/kwoutput.txt")); +// FileOutputStream fos = new FileOutputStream(new File("/temp/kwoutput2.txt")); +// ByteBuffer b = ByteBuffer.allocate(256); +// KeywordFilter kwFilter = new KeywordFilter(false); +// while (fis.getChannel().read(b) != -1) { +// b.flip(); // get ready to be read +// ByteBuffer f = kwFilter.filter(b); +// fos.getChannel().write(f); // XXX in fact, f may not be fully consumed +// if (b.hasRemaining()) { +// b.compact(); +// } else { +// b.clear(); +// } +// } +// fis.close(); +// fos.flush(); +// fos.close(); +// } } diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/NewlineFilter.java --- a/src/org/tmatesoft/hg/internal/NewlineFilter.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/internal/NewlineFilter.java Thu Feb 03 22:13:55 2011 +0100 @@ -24,8 +24,11 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.Map; import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.repo.HgInternals; import org.tmatesoft.hg.repo.HgRepository; /** @@ -154,14 +157,92 @@ } public static class Factory implements Filter.Factory { - private final boolean localIsWin = File.separatorChar == '\\'; // FIXME - private final boolean failIfInconsistent = true; + private boolean failIfInconsistent = true; + private Path.Matcher lfMatcher; + private Path.Matcher crlfMatcher; + private Path.Matcher binMatcher; + private Path.Matcher nativeMatcher; + private String nativeRepoFormat; + private String nativeOSFormat; - public Filter create(HgRepository hgRepo, Path path, Options opts) { - if (opts.getDirection() == FromRepo) { - } else if (opts.getDirection() == ToRepo) { + public void initialize(HgRepository hgRepo, ConfigFile cfg) { + failIfInconsistent = cfg.getBoolean("eol", "only-consistent", true); + File cfgFile = new File(new HgInternals(hgRepo).getRepositoryDir(), ".hgeol"); + if (!cfgFile.canRead()) { + return; } - return new NewlineFilter(failIfInconsistent, 1); + // XXX if .hgeol is not checked out, we may get it from repository +// HgDataFile cfgFileNode = hgRepo.getFileNode(".hgeol"); +// if (!cfgFileNode.exists()) { +// return; +// } + // XXX perhaps, add HgDataFile.hasWorkingCopy and workingCopyContent()? + ConfigFile hgeol = new ConfigFile(); + hgeol.addLocation(cfgFile); + nativeRepoFormat = hgeol.getSection("repository").get("native"); + if (nativeRepoFormat == null) { + nativeRepoFormat = "LF"; + } + final String os = System.getProperty("os.name"); // XXX need centralized set of properties + nativeOSFormat = os.indexOf("Windows") != -1 ? "CRLF" : "LF"; + // I assume pattern ordering in .hgeol is not important + ArrayList lfPatterns = new ArrayList(); + ArrayList crlfPatterns = new ArrayList(); + ArrayList nativePatterns = new ArrayList(); + ArrayList binPatterns = new ArrayList(); + for (Map.Entry e : hgeol.getSection("patterns").entrySet()) { + if ("CRLF".equals(e.getValue())) { + crlfPatterns.add(e.getKey()); + } else if ("LF".equals(e.getValue())) { + lfPatterns.add(e.getKey()); + } else if ("native".equals(e.getValue())) { + nativePatterns.add(e.getKey()); + } else if ("BIN".equals(e.getValue())) { + binPatterns.add(e.getKey()); + } else { + System.out.printf("Can't recognize .hgeol entry: %s for %s", e.getValue(), e.getKey()); // FIXME log warning + } + } + if (!crlfPatterns.isEmpty()) { + crlfMatcher = new PathGlobMatcher(crlfPatterns.toArray(new String[crlfPatterns.size()])); + } + if (!lfPatterns.isEmpty()) { + lfMatcher = new PathGlobMatcher(lfPatterns.toArray(new String[lfPatterns.size()])); + } + if (!binPatterns.isEmpty()) { + binMatcher = new PathGlobMatcher(binPatterns.toArray(new String[binPatterns.size()])); + } + if (!nativePatterns.isEmpty()) { + nativeMatcher = new PathGlobMatcher(nativePatterns.toArray(new String[nativePatterns.size()])); + } + } + + public Filter create(Path path, Options opts) { + if (binMatcher == null && crlfMatcher == null && lfMatcher == null && nativeMatcher == null) { + // not initialized - perhaps, no .hgeol found + return null; + } + if (binMatcher != null && binMatcher.accept(path)) { + return null; + } + if (crlfMatcher != null && crlfMatcher.accept(path)) { + return new NewlineFilter(failIfInconsistent, 1); + } else if (lfMatcher != null && lfMatcher.accept(path)) { + return new NewlineFilter(failIfInconsistent, 0); + } else if (nativeMatcher != null && nativeMatcher.accept(path)) { + if (nativeOSFormat.equals(nativeRepoFormat)) { + return null; + } + if (opts.getDirection() == FromRepo) { + int transform = "CRLF".equals(nativeOSFormat) ? 1 : 0; + return new NewlineFilter(failIfInconsistent, transform); + } else if (opts.getDirection() == ToRepo) { + int transform = "CRLF".equals(nativeOSFormat) ? 0 : 1; + return new NewlineFilter(failIfInconsistent, transform); + } + return null; + } + return null; } } diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/PathGlobMatcher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/PathGlobMatcher.java Thu Feb 03 22:13:55 2011 +0100 @@ -0,0 +1,90 @@ +/* + * 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@svnkit.com + */ +package org.tmatesoft.hg.internal; + +import java.util.regex.PatternSyntaxException; + +import org.tmatesoft.hg.core.Path; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class PathGlobMatcher implements Path.Matcher { + + private final PathRegexpMatcher delegate; + + /** + * + * @param globPatterns + * @throws NullPointerException if argument is null + * @throws IllegalArgumentException if any of the patterns is not valid + */ + public PathGlobMatcher(String... globPatterns) { + String[] regexp = new String[globPatterns.length]; //deliberately let fail with NPE + int i = 0; + for (String s : globPatterns) { + regexp[i] = glob2regexp(s); + } + try { + delegate = new PathRegexpMatcher(regexp); + } catch (PatternSyntaxException ex) { + ex.printStackTrace(); + throw new IllegalArgumentException(ex); + } + } + + + // HgIgnore.glob2regex is similar, but IsIgnore solves slightly different task + // (need to match partial paths, e.g. for glob 'bin' shall match not only 'bin' folder, but also any path below it, + // which is not generally the case + private static String glob2regexp(String glob) { + int end = glob.length() - 1; + boolean needLineEndMatch = glob.charAt(end) != '*'; + while (end > 0 && glob.charAt(end) == '*') end--; // remove trailing * that are useless for Pattern.find() + StringBuilder sb = new StringBuilder(end*2); + for (int i = 0; i <= end; i++) { + char ch = glob.charAt(i); + if (ch == '*') { + if (glob.charAt(i+1) == '*') { // i < end because we've stripped any trailing * earlier + // any char, including path segment separator + sb.append(".*?"); + } else { + // just path segments + sb.append("[^/]*?"); + } + continue; + } else if (ch == '?') { + sb.append("[^/]"); + continue; + } else if (ch == '.' || ch == '\\') { + sb.append('\\'); + } + sb.append(ch); + } + if (needLineEndMatch) { + sb.append('$'); + } + return sb.toString(); + } + + public boolean accept(Path path) { + return delegate.accept(path); + } + +} diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/internal/PathRegexpMatcher.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/org/tmatesoft/hg/internal/PathRegexpMatcher.java Thu Feb 03 22:13:55 2011 +0100 @@ -0,0 +1,64 @@ +/* + * 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@svnkit.com + */ +package org.tmatesoft.hg.internal; + +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; + +import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.core.Path.Matcher; + +/** + * + * @author Artem Tikhomirov + * @author TMate Software Ltd. + */ +public class PathRegexpMatcher implements Matcher { + private Pattern[] patterns; + + // disjunction, matches if any pattern found + // uses pattern.find(), not pattern.matches() + public PathRegexpMatcher(Pattern... p) { + if (p == null) { + throw new IllegalArgumentException(); + } + patterns = p; + } + + public PathRegexpMatcher(String... p) throws PatternSyntaxException { + this(compile(p)); + } + + private static Pattern[] compile(String[] p) throws PatternSyntaxException { + // deliberately do no check for null, let it fail + Pattern[] rv = new Pattern[p.length]; + int i = 0; + for (String s : p) { + rv[i++] = Pattern.compile(s); + } + return rv; + } + + public boolean accept(Path path) { + for (Pattern p : patterns) { + if (p.matcher(path).find()) { + return true; + } + } + return false; + } +} diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/repo/HgIgnore.java --- a/src/org/tmatesoft/hg/repo/HgIgnore.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgIgnore.java Thu Feb 03 22:13:55 2011 +0100 @@ -93,6 +93,7 @@ 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--; @@ -118,6 +119,7 @@ return sb.toString(); } + // TODO use Path and PathGlobMatcher public boolean isIgnored(String path) { for (Pattern p : entries) { if (p.matcher(path).find()) { diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/repo/HgInternals.java --- a/src/org/tmatesoft/hg/repo/HgInternals.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgInternals.java Thu Feb 03 22:13:55 2011 +0100 @@ -16,6 +16,10 @@ */ package org.tmatesoft.hg.repo; +import java.io.File; + +import org.tmatesoft.hg.internal.ConfigFile; + /** * DO NOT USE THIS CLASS, INTENDED FOR TESTING PURPOSES. @@ -46,4 +50,12 @@ } return rv; } + + public File getRepositoryDir() { + return repo.getRepositoryRoot(); + } + + public ConfigFile getRepoConfig() { + return repo.getConfigFile(); + } } diff -r 67ae317408c9 -r 46291ec605a0 src/org/tmatesoft/hg/repo/HgRepository.java --- a/src/org/tmatesoft/hg/repo/HgRepository.java Wed Feb 02 21:19:02 2011 +0100 +++ b/src/org/tmatesoft/hg/repo/HgRepository.java Thu Feb 03 22:13:55 2011 +0100 @@ -19,10 +19,15 @@ import java.io.File; import java.io.IOException; import java.lang.ref.SoftReference; +import java.util.ArrayList; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import org.tmatesoft.hg.core.Path; +import org.tmatesoft.hg.internal.ConfigFile; import org.tmatesoft.hg.internal.DataAccessProvider; +import org.tmatesoft.hg.internal.Filter; import org.tmatesoft.hg.internal.RequiresFile; import org.tmatesoft.hg.internal.RevlogStream; import org.tmatesoft.hg.util.FileWalker; @@ -72,6 +77,7 @@ private final org.tmatesoft.hg.internal.Internals impl = new org.tmatesoft.hg.internal.Internals(); private HgIgnore ignore; + private ConfigFile configFile; HgRepository(String repositoryPath) { repoDir = null; @@ -147,6 +153,7 @@ return normalizePath; } + // local to hide use of io.File. /*package-local*/ File getRepositoryRoot() { return repoDir; } @@ -198,8 +205,44 @@ } return null; // XXX empty stream instead? } + + // can't expose internal class, otherwise seems reasonable to have it in API + /*package-local*/ ConfigFile getConfigFile() { + if (configFile == null) { + configFile = impl.newConfigFile(); + configFile.addLocation(new File(System.getProperty("user.home"), ".hgrc")); + // last one, overrides anything else + // /.hg/hgrc + configFile.addLocation(new File(getRepositoryRoot(), "hgrc")); + } + return configFile; + } + + /*package-local*/ List getFiltersFromRepoToWorkingDir(Path p) { + return instantiateFilters(p, new Filter.Options(Filter.Direction.FromRepo)); + } + + /*package-local*/ List getFiltersFromWorkingDirToRepo(Path p) { + return instantiateFilters(p, new Filter.Options(Filter.Direction.ToRepo)); + } + + private List instantiateFilters(Path p, Filter.Options opts) { + List factories = impl.getFilters(this, getConfigFile()); + if (factories.isEmpty()) { + return Collections.emptyList(); + } + ArrayList rv = new ArrayList(factories.size()); + for (Filter.Factory ff : factories) { + Filter f = ff.create(p, opts); + if (f != null) { + rv.add(f); + } + } + return rv; + } private void parseRequires() { new RequiresFile().parse(impl, new File(repoDir, "requires")); } + }