From 75ddf2a0f4f22f2b509b6077aae4c9f689a03665 Mon Sep 17 00:00:00 2001 From: Robin Stocker Date: Tue, 26 Jun 2012 00:55:39 +0200 Subject: [PATCH] Enable marking entries using TreeFilters in DiffEntry This adds a new optional TreeFilter[] argument to DiffEntry.scan. All filters will be checked during the scan to determine if an entry should be "marked" with regard to that filter. After having called scan, the user can then call isMarked(int) on the entries to find out whether they matched the TreeFilter with the passed index. An example use case for this is in the file diff viewer of EGit's History view, where we'd like to highlight entries that are matching the current filter. See EGit change I03da4b38d1591495cb290909f0e4c6e52270e97f. Bug: 393610 Change-Id: Icf911fe6fca131b2567514f54d66636a44561af1 Signed-off-by: Robin Stocker Signed-off-by: Matthias Sohn --- .../org/eclipse/jgit/diff/DiffEntryTest.java | 70 +++++++++- .../eclipse/jgit/internal/JGitText.properties | 1 + .../src/org/eclipse/jgit/diff/DiffEntry.java | 93 ++++++++++++- .../org/eclipse/jgit/internal/JGitText.java | 3 +- .../treewalk/filter/TreeFilterMarker.java | 122 ++++++++++++++++++ 5 files changed, 285 insertions(+), 4 deletions(-) create mode 100644 org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java index cb7cad834..4c9c54f21 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/DiffEntryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, Dariusz Luksza + * Copyright (C) 2011, 2013 Dariusz Luksza * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -47,6 +47,7 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -65,6 +66,8 @@ import org.eclipse.jgit.treewalk.EmptyTreeIterator; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.PathFilterGroup; +import org.eclipse.jgit.treewalk.filter.TreeFilter; import org.eclipse.jgit.util.FileUtils; import org.junit.Test; @@ -265,6 +268,71 @@ public void shouldListChangesInWorkingTree() throws Exception { assertThat(entry.getNewPath(), is("b.txt")); } + @Test + public void shouldMarkEntriesWhenGivenMarkTreeFilter() throws Exception { + // given + Git git = new Git(db); + RevCommit c1 = git.commit().setMessage("initial commit").call(); + FileUtils.mkdir(new File(db.getWorkTree(), "b")); + writeTrashFile("a.txt", "a"); + writeTrashFile("b/1.txt", "b1"); + writeTrashFile("b/2.txt", "b2"); + writeTrashFile("c.txt", "c"); + git.add().addFilepattern("a.txt").addFilepattern("b") + .addFilepattern("c.txt").call(); + RevCommit c2 = git.commit().setMessage("second commit").call(); + TreeFilter filterA = PathFilterGroup.createFromStrings("a.txt"); + TreeFilter filterB = PathFilterGroup.createFromStrings("b"); + TreeFilter filterB2 = PathFilterGroup.createFromStrings("b/2.txt"); + + // when + TreeWalk walk = new TreeWalk(db); + walk.addTree(c1.getTree()); + walk.addTree(c2.getTree()); + List result = DiffEntry.scan(walk, true, new TreeFilter[] { + filterA, filterB, filterB2 }); + + // then + assertThat(result, notNullValue()); + assertEquals(5, result.size()); + + DiffEntry entryA = result.get(0); + DiffEntry entryB = result.get(1); + DiffEntry entryB1 = result.get(2); + DiffEntry entryB2 = result.get(3); + DiffEntry entryC = result.get(4); + + assertThat(entryA.getNewPath(), is("a.txt")); + assertTrue(entryA.isMarked(0)); + assertFalse(entryA.isMarked(1)); + assertFalse(entryA.isMarked(2)); + assertEquals(1, entryA.getTreeFilterMarks()); + + assertThat(entryB.getNewPath(), is("b")); + assertFalse(entryB.isMarked(0)); + assertTrue(entryB.isMarked(1)); + assertTrue(entryB.isMarked(2)); + assertEquals(6, entryB.getTreeFilterMarks()); + + assertThat(entryB1.getNewPath(), is("b/1.txt")); + assertFalse(entryB1.isMarked(0)); + assertTrue(entryB1.isMarked(1)); + assertFalse(entryB1.isMarked(2)); + assertEquals(2, entryB1.getTreeFilterMarks()); + + assertThat(entryB2.getNewPath(), is("b/2.txt")); + assertFalse(entryB2.isMarked(0)); + assertTrue(entryB2.isMarked(1)); + assertTrue(entryB2.isMarked(2)); + assertEquals(6, entryB2.getTreeFilterMarks()); + + assertThat(entryC.getNewPath(), is("c.txt")); + assertFalse(entryC.isMarked(0)); + assertFalse(entryC.isMarked(1)); + assertFalse(entryC.isMarked(2)); + assertEquals(0, entryC.getTreeFilterMarks()); + } + @Test(expected = IllegalArgumentException.class) public void shouldThrowIAEWhenTreeWalkHasLessThanTwoTrees() throws Exception { diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties index 48cb48754..6673a8cd4 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -477,6 +477,7 @@ transportProtoLocal=Local Git Repository transportProtoSFTP=SFTP transportProtoSSH=SSH treeEntryAlreadyExists=Tree entry "{0}" already exists. +treeFilterMarkerTooManyFilters=Too many markTreeFilters passed, maximum number is {0} (passed {1}) treeIteratorDoesNotSupportRemove=TreeIterator does not support remove() treeWalkMustHaveExactlyTwoTrees=TreeWalk should have exactly two trees. truncatedHunkLinesMissingForAncestor=Truncated hunk, at least {0} lines missing for ancestor {1} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java index cc8d285d9..a3d4e09d7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffEntry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008-2010, Google Inc. + * Copyright (C) 2008-2013, Google Inc. * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -55,6 +55,8 @@ import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.treewalk.filter.TreeFilterMarker; /** A value class representing a change to a file */ public class DiffEntry { @@ -123,7 +125,7 @@ public static List scan(TreeWalk walk) throws IOException { * when {@code includeTrees} parameter is {@code true} it can't * be recursive. * @param includeTrees - * include tree object's. + * include tree objects. * @return headers describing the changed files. * @throws IOException * the repository cannot be accessed. @@ -134,6 +136,36 @@ public static List scan(TreeWalk walk) throws IOException { */ public static List scan(TreeWalk walk, boolean includeTrees) throws IOException { + return scan(walk, includeTrees, null); + } + + /** + * Convert the TreeWalk into DiffEntry headers, depending on + * {@code includeTrees} it will add tree objects into result or not. + * + * @param walk + * the TreeWalk to walk through. Must have exactly two trees and + * when {@code includeTrees} parameter is {@code true} it can't + * be recursive. + * @param includeTrees + * include tree objects. + * @param markTreeFilters + * array of tree filters which will be tested for each entry. If + * an entry matches, the entry will later return true when + * queried through {{@link #isMarked(int)} (with the index from + * this passed array). + * @return headers describing the changed files. + * @throws IOException + * the repository cannot be accessed. + * @throws IllegalArgumentException + * when {@code includeTrees} is true and given TreeWalk is + * recursive. Or when given TreeWalk doesn't have exactly two + * trees + * @since 2.3 + */ + public static List scan(TreeWalk walk, boolean includeTrees, + TreeFilter[] markTreeFilters) + throws IOException { if (walk.getTreeCount() != 2) throw new IllegalArgumentException( JGitText.get().treeWalkMustHaveExactlyTwoTrees); @@ -141,6 +173,12 @@ public static List scan(TreeWalk walk, boolean includeTrees) throw new IllegalArgumentException( JGitText.get().cannotBeRecursiveWhenTreesAreIncluded); + TreeFilterMarker treeFilterMarker; + if (markTreeFilters != null && markTreeFilters.length > 0) + treeFilterMarker = new TreeFilterMarker(markTreeFilters); + else + treeFilterMarker = null; + List r = new ArrayList(); MutableObjectId idBuf = new MutableObjectId(); while (walk.next()) { @@ -156,6 +194,9 @@ public static List scan(TreeWalk walk, boolean includeTrees) entry.newMode = walk.getFileMode(1); entry.newPath = entry.oldPath = walk.getPathString(); + if (treeFilterMarker != null) + entry.treeFilterMarks = treeFilterMarker.getMarks(walk); + if (entry.oldMode == FileMode.MISSING) { entry.oldPath = DiffEntry.DEV_NULL; entry.changeType = ChangeType.ADD; @@ -294,6 +335,12 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst, /** ObjectId listed on the index line for the new (post-image) */ protected AbbreviatedObjectId newId; + /** + * Bitset for marked flags of tree filters passed to + * {@link #scan(TreeWalk, boolean, TreeFilter...)} + */ + private int treeFilterMarks = 0; + /** * Get the old name associated with this file. *

@@ -396,6 +443,48 @@ public AbbreviatedObjectId getNewId() { return newId; } + /** + * Whether the mark tree filter with the specified index matched during scan + * or not, see {@link #scan(TreeWalk, boolean, TreeFilter...)}. Example: + *

+ * + *

+	 * TreeFilter filterA = ...;
+	 * TreeFilter filterB = ...;
+	 * List entries = DiffEntry.scan(walk, false, filterA, filterB);
+	 * DiffEntry entry = entries.get(0);
+	 * boolean filterAMatched = entry.isMarked(0);
+	 * boolean filterBMatched = entry.isMarked(1);
+	 * 
+ *

+ * Note that 0 corresponds to filterA because it was the first filter that + * was passed to scan. + *

+ * To query more than one flag at once, see {@link #getTreeFilterMarks()}. + * + * @param index + * the index of the tree filter to check for (must be between 0 + * and {@link Integer#SIZE}). + * + * @return true, if the tree filter matched; false if not + * @since 2.3 + */ + public boolean isMarked(int index) { + return (treeFilterMarks & (1L << index)) != 0; + } + + /** + * Get the raw tree filter marks, as set during + * {@link #scan(TreeWalk, boolean, TreeFilter...)}. See + * {@link #isMarked(int)} to query each mark individually. + * + * @return the bitset of tree filter marks + * @since 2.3 + */ + public int getTreeFilterMarks() { + return treeFilterMarks; + } + /** * Get the object id. * diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java index 287684392..d402f139f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Sasa Zivkov + * Copyright (C) 2010, 2013 Sasa Zivkov * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -538,6 +538,7 @@ public static JGitText get() { /***/ public String transportProtoSFTP; /***/ public String transportProtoSSH; /***/ public String treeEntryAlreadyExists; + /***/ public String treeFilterMarkerTooManyFilters; /***/ public String treeIteratorDoesNotSupportRemove; /***/ public String treeWalkMustHaveExactlyTwoTrees; /***/ public String truncatedHunkLinesMissingForAncestor; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java new file mode 100644 index 000000000..59515dca5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/filter/TreeFilterMarker.java @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2013, Robin Stocker + * 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 java.io.IOException; +import java.text.MessageFormat; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StopWalkException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.treewalk.TreeWalk; + +/** + * For testing an array of {@link TreeFilter} during a {@link TreeWalk} for each + * entry and returning the result as a bitmask. + * + * @since 2.3 + */ +public class TreeFilterMarker { + + private final TreeFilter[] filters; + + /** + * Construct a TreeFilterMarker. Note that it is stateful and can only be + * used for one walk loop. + * + * @param markTreeFilters + * the filters to use for marking, must not have more elements + * than {@link Integer#SIZE}. + * @throws IllegalArgumentException + * if more tree filters are passed than possible + */ + public TreeFilterMarker(TreeFilter[] markTreeFilters) { + if (markTreeFilters.length > Integer.SIZE) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().treeFilterMarkerTooManyFilters, + Integer.valueOf(Integer.SIZE), + Integer.valueOf(markTreeFilters.length))); + } + filters = new TreeFilter[markTreeFilters.length]; + System.arraycopy(markTreeFilters, 0, filters, 0, markTreeFilters.length); + } + + /** + * Test the filters against the walk. Returns a bitmask where each bit + * represents the result of a call to {@link TreeFilter#include(TreeWalk)}, + * ordered by the index for which the tree filters were passed in the + * constructor. + * + * @param walk + * the walk from which to test the current entry + * @return the marks bitmask + * @throws MissingObjectException + * as thrown by {@link TreeFilter#include(TreeWalk)} + * @throws IncorrectObjectTypeException + * as thrown by {@link TreeFilter#include(TreeWalk)} + * @throws IOException + * as thrown by {@link TreeFilter#include(TreeWalk)} + */ + public int getMarks(TreeWalk walk) throws MissingObjectException, + IncorrectObjectTypeException, IOException { + int marks = 0; + for (int index = 0; index < filters.length; index++) { + TreeFilter filter = filters[index]; + if (filter != null) { + try { + boolean marked = filter.include(walk); + if (marked) + marks |= (1L << index); + } catch (StopWalkException e) { + // Don't check tree filter anymore, it will no longer + // match + filters[index] = null; + } + } + } + return marks; + } + +}