]> source.dussan.org Git - jgit.git/commitdiff
Correct the boolean logic for filtering paths 32/91432/6
authorMagnus Vigerlöf <magnus.vigerlof@gmail.com>
Sat, 18 Feb 2017 18:28:39 +0000 (19:28 +0100)
committerMatthias Sohn <matthias.sohn@sap.com>
Tue, 28 Feb 2017 22:56:33 +0000 (23:56 +0100)
The TreeWalk filtering classes need to support the three different
meanings of the return value the path comparison generates.
A new path comparison method (isPathMatch) is created with
three distinct return values (isPathPrefix use value '0' to
encode two of these) which will makes it possible for the logical
operators (especially NOT) to aggregate a correct verdict.

A filter like: AND(Path("path"), NOT(Path("path/to/other")))
Should filter out 'path/to/other/file', but not 'path/to/my/file'.

The path-limiting feature when testing path/to/my/file, would
result to run test for the following paths:

    path
    path/to
    path/to/my
    path/to/my/file

isPathPrefix('path/to/other') will return '0' for the first two
and since there is no way for NOT to distinguish between an exact
match and a match indicating that the tested path is a 'parent',
it will incorrectly return false and thus remove everything below
'path' immediately.
isPathMatch has a distinguished value for 'parent' matches that
will be preserved through the logic operators and should not
cause an over-eager removal of paths.

The functionality of isPathPrefix is required by other parts
and is untouched.

Unit tests are included to ensure that the logical functionality
is correct and can be preserved.

Change-Id: Ice2ca9406f09f1b179569e99b86a0e5d77baa20d
Signed-off-by: Magnus Vigerlöf <magnus.vigerlof@gmail.com>
org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java [new file with mode: 0644]
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/AndTreeFilter.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/NotTreeFilter.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/OrTreeFilter.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/PathFilter.java
org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilter.java

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/filter/PathFilterLogicTest.java
new file mode 100644 (file)
index 0000000..7c819c5
--- /dev/null
@@ -0,0 +1,360 @@
+/*
+ * Copyright (C) 2017 Magnus Vigerlöf (magnus.vigerlof@gmail.com)
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ *   notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ *   copyright notice, this list of conditions and the following
+ *   disclaimer in the documentation and/or other materials provided
+ *   with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ *   names of its contributors may be used to endorse or promote
+ *   products derived from this software without specific prior
+ *   written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.treewalk.filter;
+
+import org.eclipse.jgit.dircache.DirCache;
+import org.eclipse.jgit.dircache.DirCacheBuilder;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lib.FileMode;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+public class PathFilterLogicTest extends RepositoryTestCase {
+
+       private ObjectId treeId;
+
+       @Before
+       public void setup() throws IOException {
+               String[] paths = new String[] {
+                               "a.txt",
+                               "sub1.txt",
+                               "sub1/suba/a.txt",
+                               "sub1/subb/b.txt",
+                               "sub2/suba/a.txt"
+               };
+               treeId = createTree(paths);
+       }
+
+       @Test
+       public void testSinglePath() throws IOException {
+               List<String> expected = Arrays.asList("sub1/suba/a.txt",
+                               "sub1/subb/b.txt");
+
+               TreeFilter tf = PathFilter.create("sub1");
+               List<String> paths = getMatchingPaths(treeId, tf);
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testSingleSubPath() throws IOException {
+               List<String> expected = Collections.singletonList("sub1/suba/a.txt");
+
+               TreeFilter tf = PathFilter.create("sub1/suba");
+               List<String> paths = getMatchingPaths(treeId, tf);
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testSinglePathNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+                               "sub2/suba/a.txt");
+
+               TreeFilter tf = PathFilter.create("sub1").negate();
+               List<String> paths = getMatchingPaths(treeId, tf);
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testSingleSubPathNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+                               "sub1/subb/b.txt", "sub2/suba/a.txt");
+
+               TreeFilter tf = PathFilter.create("sub1/suba").negate();
+               List<String> paths = getMatchingPaths(treeId, tf);
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiTwoPath() throws IOException {
+               List<String> expected = Arrays.asList("sub1/suba/a.txt",
+                               "sub1/subb/b.txt", "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub2")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiThreePath() throws IOException {
+               List<String> expected = Arrays.asList("sub1.txt", "sub1/suba/a.txt",
+                               "sub1/subb/b.txt", "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub2"), PathFilter.create("sub1.txt")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiTwoSubPath() throws IOException {
+               List<String> expected = Arrays.asList("sub1/subb/b.txt",
+                               "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"),
+                               PathFilter.create("sub2/suba")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiTwoMixSubPath() throws IOException {
+               List<String> expected = Arrays.asList("sub1/subb/b.txt",
+                               "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/subb"),
+                               PathFilter.create("sub2")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiTwoMixSubPathNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+                               "sub1/suba/a.txt", "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(),
+                               PathFilter.create("sub1/suba")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testOrMultiThreeMixSubPathNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+                               "sub1/suba/a.txt", "sub2/suba/a.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(),
+                               PathFilter.create("sub1/suba"), PathFilter.create("no/path")};
+               List<String> paths = getMatchingPaths(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testPatternParentFileMatch() throws IOException {
+               List<String> expected = Collections.emptyList();
+
+               TreeFilter tf = PathFilter.create("a.txt/test/path");
+               List<String> paths = getMatchingPaths(treeId, tf);
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiPath() throws IOException {
+               List<String> expected = Collections.emptyList();
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub2")};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiPathNegate() throws IOException {
+               List<String> expected = Arrays.asList("sub1/suba/a.txt",
+                               "sub1/subb/b.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub2").negate()};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiSubPathDualNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt",
+                               "sub1/subb/b.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/suba").negate(),
+                               PathFilter.create("sub2").negate()};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiSubPath() throws IOException {
+               List<String> expected = Collections.emptyList();
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub2/suba")};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiSubPathNegate() throws IOException {
+               List<String> expected = Collections.singletonList("sub1/subb/b.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1"),
+                               PathFilter.create("sub1/suba").negate()};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testAndMultiThreeSubPathNegate() throws IOException {
+               List<String> expected = Collections.singletonList("sub1/subb/b.txt");
+
+               TreeFilter[] tf = new TreeFilter[]{PathFilter.create("sub1"),
+                               PathFilter.create("sub1/suba").negate(),
+                               PathFilter.create("no/path").negate()};
+               List<String> paths = getMatchingPaths(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testTopAndMultiPathDualNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1").negate(),
+                               PathFilter.create("sub2").negate()};
+               List<String> paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testTopAndMultiSubPathDualNegate() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt", "sub1");
+
+               // Filter on 'sub1/suba' is kind of silly for a non-recursive walk.
+               // The result is interesting though as the 'sub1' path should be
+               // returned, due to the fact that there may be hits once the pattern
+               // is tested with one of the leaf paths.
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1/suba").negate(),
+                               PathFilter.create("sub2").negate()};
+               List<String> paths = getMatchingPathsFlat(treeId, AndTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testTopOrMultiPathDual() throws IOException {
+               List<String> expected = Arrays.asList("sub1.txt", "sub2");
+
+               TreeFilter[] tf = new TreeFilter[] {PathFilter.create("sub1.txt"),
+                               PathFilter.create("sub2")};
+               List<String> paths = getMatchingPathsFlat(treeId, OrTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       @Test
+       public void testTopNotPath() throws IOException {
+               List<String> expected = Arrays.asList("a.txt", "sub1.txt", "sub2");
+
+               TreeFilter tf = PathFilter.create("sub1");
+               List<String> paths = getMatchingPathsFlat(treeId, NotTreeFilter.create(tf));
+
+               assertEquals(expected, paths);
+       }
+
+       private List<String> getMatchingPaths(final ObjectId objId,
+                       TreeFilter tf) throws IOException {
+               return getMatchingPaths(objId, tf, true);
+       }
+
+       private List<String> getMatchingPathsFlat(final ObjectId objId,
+                       TreeFilter tf) throws IOException {
+               return getMatchingPaths(objId, tf, false);
+       }
+
+       private List<String> getMatchingPaths(final ObjectId objId,
+                       TreeFilter tf, boolean recursive) throws IOException {
+               try (TreeWalk tw = new TreeWalk(db)) {
+                       tw.setFilter(tf);
+                       tw.setRecursive(recursive);
+                       tw.addTree(objId);
+
+                       List<String> paths = new ArrayList<>();
+                       while (tw.next()) {
+                               paths.add(tw.getPathString());
+                       }
+                       return paths;
+               }
+       }
+
+       private ObjectId createTree(String... paths) throws IOException {
+               final ObjectInserter odi = db.newObjectInserter();
+               final DirCache dc = db.readDirCache();
+               final DirCacheBuilder builder = dc.builder();
+               for (String path : paths) {
+                       DirCacheEntry entry = createEntry(path, FileMode.REGULAR_FILE);
+                       builder.add(entry);
+               }
+               builder.finish();
+               final ObjectId objId = dc.writeTree(odi);
+               odi.flush();
+               return objId;
+       }
+}
+
index 1ed9468159a7bbdd8774d339f425357a8ff711df..c54e1484caf62135fd383d6802464f716f500a66 100644 (file)
@@ -826,7 +826,7 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                                }
 
                                currentHead = t;
-                               if (!filter.include(this)) {
+                               if (filter.matchFilter(this) == 1) {
                                        skipEntriesEqual();
                                        continue;
                                }
@@ -1059,6 +1059,60 @@ public class TreeWalk implements AutoCloseable, AttributesProvider {
                return currentHead.pathLen;
        }
 
+       /**
+        * Test if the supplied path matches the current entry's path.
+        * <p>
+        * This method detects if the supplied path is equal to, a subtree of, or
+        * not similar at all to the current entry. It is faster to use this
+        * method than to use {@link #getPathString()} to first create a String
+        * object, then test <code>startsWith</code> or some other type of string
+        * match function.
+        * <p>
+        * If the current entry is a subtree, then all paths within the subtree
+        * are considered to match it.
+        *
+        * @param p
+        *            path buffer to test. Callers should ensure the path does not
+        *            end with '/' prior to invocation.
+        * @param pLen
+        *            number of bytes from <code>buf</code> to test.
+        * @return -1 if the current path is a parent to p; 0 if p matches the current
+        *         path; 1 if the current path is different and will never match
+        *         again on this tree walk.
+        * @since 4.7
+        */
+       public int isPathMatch(final byte[] p, final int pLen) {
+               final AbstractTreeIterator t = currentHead;
+               final byte[] c = t.path;
+               final int cLen = t.pathLen;
+               int ci;
+
+               for (ci = 0; ci < cLen && ci < pLen; ci++) {
+                       final int c_value = (c[ci] & 0xff) - (p[ci] & 0xff);
+                       if (c_value != 0) {
+                               // Paths do not and will never match
+                               return 1;
+                       }
+               }
+
+               if (ci < cLen) {
+                       // Ran out of pattern but we still had current data.
+                       // If c[ci] == '/' then pattern matches the subtree.
+                       // Otherwise it is a partial match == miss
+                       return c[ci] == '/' ? 0 : 1;
+               }
+
+               if (ci < pLen) {
+                       // Ran out of current, but we still have pattern data.
+                       // If p[ci] == '/' then this subtree is a parent in the pattern,
+                       // otherwise it's a miss.
+                       return p[ci] == '/' && FileMode.TREE.equals(t.mode) ? -1 : 1;
+               }
+
+               // Both strings are identical.
+               return 0;
+       }
+
        /**
         * Test if the supplied path matches the current entry's path.
         * <p>
index d5e7464d4fbfd8ae658a55c65a4d35d24a66c070..9658166a85e63f560da446284dcb07064abb07a7 100644 (file)
@@ -128,7 +128,25 @@ public abstract class AndTreeFilter extends TreeFilter {
                public boolean include(final TreeWalk walker)
                                throws MissingObjectException, IncorrectObjectTypeException,
                                IOException {
-                       return a.include(walker) && b.include(walker);
+                       return matchFilter(walker) <= 0;
+               }
+
+               @Override
+               public int matchFilter(TreeWalk walker)
+                               throws MissingObjectException, IncorrectObjectTypeException,
+                               IOException {
+                       final int ra = a.matchFilter(walker);
+                       if (ra == 1) {
+                               return 1;
+                       }
+                       final int rb = b.matchFilter(walker);
+                       if (rb == 1) {
+                               return 1;
+                       }
+                       if (ra == -1 || rb == -1) {
+                               return -1;
+                       }
+                       return 0;
                }
 
                @Override
@@ -159,11 +177,24 @@ public abstract class AndTreeFilter extends TreeFilter {
                public boolean include(final TreeWalk walker)
                                throws MissingObjectException, IncorrectObjectTypeException,
                                IOException {
+                       return matchFilter(walker) <= 0;
+               }
+
+               @Override
+               public int matchFilter(TreeWalk walker)
+                               throws MissingObjectException, IncorrectObjectTypeException,
+                               IOException {
+                       int m = 0;
                        for (final TreeFilter f : subfilters) {
-                               if (!f.include(walker))
-                                       return false;
+                               int r = f.matchFilter(walker);
+                               if (r == 1) {
+                                       return 1;
+                               }
+                               if (r == -1) {
+                                       m = -1;
+                               }
                        }
-                       return true;
+                       return m;
                }
 
                @Override
index 8ec04bb32db6b69cd52359aefa8a4cd07cde4f61..80c0b87e1c41c8120d8df39d58ba82f37e94b6a3 100644 (file)
@@ -78,7 +78,23 @@ public class NotTreeFilter extends TreeFilter {
        public boolean include(final TreeWalk walker)
                        throws MissingObjectException, IncorrectObjectTypeException,
                        IOException {
-               return !a.include(walker);
+               return matchFilter(walker) == 0;
+       }
+
+       @Override
+       public int matchFilter(TreeWalk walker)
+                       throws MissingObjectException, IncorrectObjectTypeException,
+                       IOException {
+               final int r = a.matchFilter(walker);
+               // switch 0 and 1, keep -1 as that defines a subpath that must be
+               // traversed before a final verdict can be made.
+               if (r == 0) {
+                       return 1;
+               }
+               if (r == 1) {
+                       return 0;
+               }
+               return -1;
        }
 
        @Override
index 270633ce63d7c2e1b849429f9c32610a06e2d2ff..2c1a9d4388d32feaa476c8f6d041e9e76f431222 100644 (file)
@@ -126,7 +126,25 @@ public abstract class OrTreeFilter extends TreeFilter {
                public boolean include(final TreeWalk walker)
                                throws MissingObjectException, IncorrectObjectTypeException,
                                IOException {
-                       return a.include(walker) || b.include(walker);
+                       return matchFilter(walker) <= 0;
+               }
+
+               @Override
+               public int matchFilter(TreeWalk walker)
+                               throws MissingObjectException, IncorrectObjectTypeException,
+                               IOException {
+                       final int ra = a.matchFilter(walker);
+                       if (ra == 0) {
+                               return 0;
+                       }
+                       final int rb = b.matchFilter(walker);
+                       if (rb == 0) {
+                               return 0;
+                       }
+                       if (ra == -1 || rb == -1) {
+                               return -1;
+                       }
+                       return 1;
                }
 
                @Override
@@ -157,11 +175,24 @@ public abstract class OrTreeFilter extends TreeFilter {
                public boolean include(final TreeWalk walker)
                                throws MissingObjectException, IncorrectObjectTypeException,
                                IOException {
+                       return matchFilter(walker) <= 0;
+               }
+
+               @Override
+               public int matchFilter(TreeWalk walker)
+                               throws MissingObjectException, IncorrectObjectTypeException,
+                               IOException {
+                       int m = 1;
                        for (final TreeFilter f : subfilters) {
-                               if (f.include(walker))
-                                       return true;
+                               int r = f.matchFilter(walker);
+                               if (r == 0) {
+                                       return 0;
+                               }
+                               if (r == -1) {
+                                       m = -1;
+                               }
                        }
-                       return false;
+                       return m;
                }
 
                @Override
index 103b0e227077869c00714b80a6706795c90a1eb6..445ba157ea3d40e17ba5240b550531742ab5da8f 100644 (file)
@@ -97,7 +97,12 @@ public class PathFilter extends TreeFilter {
 
        @Override
        public boolean include(final TreeWalk walker) {
-               return walker.isPathPrefix(pathRaw, pathRaw.length) == 0;
+               return matchFilter(walker) <= 0;
+       }
+
+       @Override
+       public int matchFilter(final TreeWalk walker) {
+               return walker.isPathMatch(pathRaw, pathRaw.length);
        }
 
        @Override
index 9f468ec3f60b87c80ead26fd15f690ea771ddc79..2c2fb4746389a585664d582ae4767892fd1b62ce 100644 (file)
@@ -198,6 +198,34 @@ public abstract class TreeFilter {
                        throws MissingObjectException, IncorrectObjectTypeException,
                        IOException;
 
+       /**
+        * Determine if the current entry is a parent, a match, or no match.
+        * <p>
+        * This method extends the result returned by {@link #include(TreeWalk)}
+        * with a third option (-1), splitting the value true. This gives the
+        * application a possibility to distinguish between an exact match
+        * and the case when a subtree to the current entry might be a match.
+        *
+        * @param walker
+        *            the walker the filter needs to examine.
+        * @return -1 if the current entry is a parent of the filter but no
+        *         exact match has been made; 0 if the current entry should
+        *         be seen by the application; 1 if it should be hidden.
+        * @throws MissingObjectException
+        *             as thrown by {@link #include(TreeWalk)}
+        * @throws IncorrectObjectTypeException
+        *             as thrown by {@link #include(TreeWalk)}
+        * @throws IOException
+        *             as thrown by {@link #include(TreeWalk)}
+        * @since 4.7
+        */
+       public int matchFilter(final TreeWalk walker)
+                       throws MissingObjectException, IncorrectObjectTypeException,
+                       IOException
+       {
+               return include(walker) ? 0 : 1;
+       }
+
        /**
         * Does this tree filter require a recursive walk to match everything?
         * <p>