diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java index c028ca300..2c51f7b9b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitAndLogCommandTest.java @@ -40,6 +40,7 @@ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ + package org.eclipse.jgit.api; import static java.nio.charset.StandardCharsets.UTF_8; @@ -50,14 +51,10 @@ import static org.junit.Assume.assumeFalse; import java.io.File; -import java.io.IOException; import java.io.PrintWriter; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoMessageException; -import org.eclipse.jgit.errors.IncorrectObjectTypeException; -import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -76,13 +73,12 @@ */ public class CommitAndLogCommandTest extends RepositoryTestCase { @Test - public void testSomeCommits() throws JGitInternalException, IOException, - GitAPIException { - + public void testSomeCommits() throws Exception { // do 4 commits try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); - git.commit().setMessage("second commit").setCommitter(committer).call(); + git.commit().setMessage("second commit").setCommitter(committer) + .call(); git.commit().setMessage("third commit").setAuthor(author).call(); git.commit().setMessage("fourth commit").setAuthor(author) .setCommitter(committer).call(); @@ -90,79 +86,28 @@ public void testSomeCommits() throws JGitInternalException, IOException, // check that all commits came in correctly PersonIdent defaultCommitter = new PersonIdent(db); - PersonIdent expectedAuthors[] = new PersonIdent[] { defaultCommitter, - committer, author, author }; + PersonIdent expectedAuthors[] = new PersonIdent[] { + defaultCommitter, committer, author, author }; PersonIdent expectedCommitters[] = new PersonIdent[] { defaultCommitter, committer, defaultCommitter, committer }; String expectedMessages[] = new String[] { "initial commit", "second commit", "third commit", "fourth commit" }; int l = expectedAuthors.length - 1; for (RevCommit c : commits) { - assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() - .getName()); - assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() - .getName()); + assertEquals(expectedAuthors[l].getName(), + c.getAuthorIdent().getName()); + assertEquals(expectedCommitters[l].getName(), + c.getCommitterIdent().getName()); assertEquals(c.getFullMessage(), expectedMessages[l]); l--; } assertEquals(l, -1); ReflogReader reader = db.getReflogReader(Constants.HEAD); - assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); + assertTrue( + reader.getLastEntry().getComment().startsWith("commit:")); reader = db.getReflogReader(db.getBranch()); - assertTrue(reader.getLastEntry().getComment().startsWith("commit:")); - } - } - - @Test - public void testLogWithFilter() throws IOException, JGitInternalException, - GitAPIException { - - try (Git git = new Git(db)) { - // create first file - File file = new File(db.getWorkTree(), "a.txt"); - FileUtils.createNewFile(file); - try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { - writer.print("content1"); - } - - // First commit - a.txt file - git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("commit1").setCommitter(committer).call(); - - // create second file - file = new File(db.getWorkTree(), "b.txt"); - FileUtils.createNewFile(file); - try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { - writer.print("content2"); - } - - // Second commit - b.txt file - git.add().addFilepattern("b.txt").call(); - git.commit().setMessage("commit2").setCommitter(committer).call(); - - // First log - a.txt filter - int count = 0; - for (RevCommit c : git.log().addPath("a.txt").call()) { - assertEquals("commit1", c.getFullMessage()); - count++; - } - assertEquals(1, count); - - // Second log - b.txt filter - count = 0; - for (RevCommit c : git.log().addPath("b.txt").call()) { - assertEquals("commit2", c.getFullMessage()); - count++; - } - assertEquals(1, count); - - // Third log - without filter - count = 0; - for (RevCommit c : git.log().call()) { - assertEquals(committer, c.getCommitterIdent()); - count++; - } - assertEquals(2, count); + assertTrue( + reader.getLastEntry().getComment().startsWith("commit:")); } } @@ -204,19 +149,20 @@ public void testMultipleInvocations() throws GitAPIException { } @Test - public void testMergeEmptyBranches() throws IOException, - JGitInternalException, GitAPIException { + public void testMergeEmptyBranches() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("initial commit").call(); RefUpdate r = db.updateRef("refs/heads/side"); r.setNewObjectId(db.resolve(Constants.HEAD)); assertEquals(r.forceUpdate(), RefUpdate.Result.NEW); - RevCommit second = git.commit().setMessage("second commit").setCommitter(committer).call(); + RevCommit second = git.commit().setMessage("second commit") + .setCommitter(committer).call(); db.updateRef(Constants.HEAD).link("refs/heads/side"); - RevCommit firstSide = git.commit().setMessage("first side commit").setAuthor(author).call(); + RevCommit firstSide = git.commit().setMessage("first side commit") + .setAuthor(author).call(); - write(new File(db.getDirectory(), Constants.MERGE_HEAD), ObjectId - .toString(db.resolve("refs/heads/master"))); + write(new File(db.getDirectory(), Constants.MERGE_HEAD), + ObjectId.toString(db.resolve("refs/heads/master"))); write(new File(db.getDirectory(), Constants.MERGE_MSG), "merging"); RevCommit commit = git.commit().call(); @@ -228,8 +174,7 @@ public void testMergeEmptyBranches() throws IOException, } @Test - public void testAddUnstagedChanges() throws IOException, - JGitInternalException, GitAPIException { + public void testAddUnstagedChanges() throws Exception { File file = new File(db.getWorkTree(), "a.txt"); FileUtils.createNewFile(file); try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { @@ -260,7 +205,7 @@ public void testAddUnstagedChanges() throws IOException, } @Test - public void testModeChange() throws IOException, GitAPIException { + public void testModeChange() throws Exception { assumeFalse(System.getProperty("os.name").startsWith("Windows"));// SKIP try (Git git = new Git(db)) { // create file @@ -278,7 +223,8 @@ public void testModeChange() throws IOException, GitAPIException { FS fs = db.getFS(); fs.setExecute(file, true); git.add().addFilepattern("a.txt").call(); - git.commit().setMessage("mode change").setCommitter(committer).call(); + git.commit().setMessage("mode change").setCommitter(committer) + .call(); // pure mode change should be committable with -o option fs.setExecute(file, false); @@ -289,34 +235,32 @@ public void testModeChange() throws IOException, GitAPIException { } @Test - public void testCommitRange() throws GitAPIException, - JGitInternalException, MissingObjectException, - IncorrectObjectTypeException { + public void testCommitRange() throws Exception { // do 4 commits and set the range to the second and fourth one try (Git git = new Git(db)) { git.commit().setMessage("first commit").call(); RevCommit second = git.commit().setMessage("second commit") .setCommitter(committer).call(); git.commit().setMessage("third commit").setAuthor(author).call(); - RevCommit last = git.commit().setMessage("fourth commit").setAuthor( - author) - .setCommitter(committer).call(); - Iterable commits = git.log().addRange(second.getId(), - last.getId()).call(); + RevCommit last = git.commit().setMessage("fourth commit") + .setAuthor(author).setCommitter(committer).call(); + Iterable commits = git.log() + .addRange(second.getId(), last.getId()).call(); // check that we have the third and fourth commit PersonIdent defaultCommitter = new PersonIdent(db); - PersonIdent expectedAuthors[] = new PersonIdent[] { author, author }; + PersonIdent expectedAuthors[] = new PersonIdent[] { author, + author }; PersonIdent expectedCommitters[] = new PersonIdent[] { defaultCommitter, committer }; String expectedMessages[] = new String[] { "third commit", "fourth commit" }; int l = expectedAuthors.length - 1; for (RevCommit c : commits) { - assertEquals(expectedAuthors[l].getName(), c.getAuthorIdent() - .getName()); - assertEquals(expectedCommitters[l].getName(), c.getCommitterIdent() - .getName()); + assertEquals(expectedAuthors[l].getName(), + c.getAuthorIdent().getName()); + assertEquals(expectedCommitters[l].getName(), + c.getCommitterIdent().getName()); assertEquals(c.getFullMessage(), expectedMessages[l]); l--; } @@ -325,8 +269,7 @@ public void testCommitRange() throws GitAPIException, } @Test - public void testCommitAmend() throws JGitInternalException, IOException, - GitAPIException { + public void testCommitAmend() throws Exception { try (Git git = new Git(db)) { git.commit().setMessage("first comit").call(); // typo git.commit().setAmend(true).setMessage("first commit").call(); @@ -348,15 +291,14 @@ public void testCommitAmend() throws JGitInternalException, IOException, } @Test - public void testInsertChangeId() throws JGitInternalException, - GitAPIException { + public void testInsertChangeId() throws Exception { try (Git git = new Git(db)) { String messageHeader = "Some header line\n\nSome detail explanation\n"; String changeIdTemplate = "\nChange-Id: I" + ObjectId.zeroId().getName() + "\n"; String messageFooter = "Some foooter lines\nAnother footer line\n"; - RevCommit commit = git.commit().setMessage( - messageHeader + messageFooter) + RevCommit commit = git.commit() + .setMessage(messageHeader + messageFooter) .setInsertChangeId(true).call(); // we should find a real change id (at the end of the file) byte[] chars = commit.getFullMessage().getBytes(UTF_8); @@ -364,11 +306,12 @@ public void testInsertChangeId() throws JGitInternalException, String lastLine = RawParseUtils.decode(chars, lastLineBegin + 1, chars.length); assertTrue(lastLine.contains("Change-Id:")); - assertFalse(lastLine.contains( - "Change-Id: I" + ObjectId.zeroId().getName())); + assertFalse(lastLine + .contains("Change-Id: I" + ObjectId.zeroId().getName())); - commit = git.commit().setMessage( - messageHeader + changeIdTemplate + messageFooter) + commit = git.commit() + .setMessage( + messageHeader + changeIdTemplate + messageFooter) .setInsertChangeId(true).call(); // we should find a real change id (in the line as dictated by the // template) @@ -383,11 +326,12 @@ public void testInsertChangeId() throws JGitInternalException, String line = RawParseUtils.decode(chars, lineStart, lineEnd); assertTrue(line.contains("Change-Id:")); - assertFalse(line.contains( - "Change-Id: I" + ObjectId.zeroId().getName())); + assertFalse(line + .contains("Change-Id: I" + ObjectId.zeroId().getName())); - commit = git.commit().setMessage( - messageHeader + changeIdTemplate + messageFooter) + commit = git.commit() + .setMessage( + messageHeader + changeIdTemplate + messageFooter) .setInsertChangeId(false).call(); // we should find the untouched template chars = commit.getFullMessage().getBytes(UTF_8); @@ -400,8 +344,8 @@ public void testInsertChangeId() throws JGitInternalException, line = RawParseUtils.decode(chars, lineStart, lineEnd); - assertTrue(commit.getFullMessage().contains( - "Change-Id: I" + ObjectId.zeroId().getName())); + assertTrue(commit.getFullMessage() + .contains("Change-Id: I" + ObjectId.zeroId().getName())); } } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogFilterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogFilterTest.java new file mode 100644 index 000000000..988ca5890 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/LogFilterTest.java @@ -0,0 +1,221 @@ +/* + * Copyright (C) 2019, John Tipper + * 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.api; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.junit.Assert.assertEquals; + +import java.io.File; +import java.io.PrintWriter; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Iterator; + +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.util.FileUtils; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Testing the log command with include and exclude filters + */ +public class LogFilterTest extends RepositoryTestCase { + private Git git; + + @Before + public void setup() throws Exception { + super.setUp(); + git = new Git(db); + + // create first file + File file = new File(db.getWorkTree(), "a.txt"); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { + writer.print("content1"); + } + + // First commit - a.txt file + git.add().addFilepattern("a.txt").call(); + git.commit().setMessage("commit1").setCommitter(committer).call(); + + // create second file + file = new File(db.getWorkTree(), "b.txt"); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { + writer.print("content2"); + } + + // Second commit - b.txt file + git.add().addFilepattern("b.txt").call(); + git.commit().setMessage("commit2").setCommitter(committer).call(); + + // create third file + Path includeSubdir = Paths.get(db.getWorkTree().toString(), + "subdir-include"); + includeSubdir.toFile().mkdirs(); + file = Paths.get(includeSubdir.toString(), "c.txt").toFile(); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { + writer.print("content3"); + } + + // Third commit - c.txt file + git.add().addFilepattern("subdir-include").call(); + git.commit().setMessage("commit3").setCommitter(committer).call(); + + // create fourth file + Path excludeSubdir = Paths.get(db.getWorkTree().toString(), + "subdir-exclude"); + excludeSubdir.toFile().mkdirs(); + file = Paths.get(excludeSubdir.toString(), "d.txt").toFile(); + FileUtils.createNewFile(file); + try (PrintWriter writer = new PrintWriter(file, UTF_8.name())) { + writer.print("content4"); + } + + // Fourth commit - d.txt file + git.add().addFilepattern("subdir-exclude").call(); + git.commit().setMessage("commit4").setCommitter(committer).call(); + } + + @After + @Override + public void tearDown() throws Exception { + git.close(); + super.tearDown(); + } + + @Test + public void testLogWithFilterCanDistinguishFilesByPath() throws Exception { + int count = 0; + for (RevCommit c : git.log().addPath("a.txt").call()) { + assertEquals("commit1", c.getFullMessage()); + count++; + } + assertEquals(1, count); + + count = 0; + for (RevCommit c : git.log().addPath("b.txt").call()) { + assertEquals("commit2", c.getFullMessage()); + count++; + } + assertEquals(1, count); + } + + @Test + public void testLogWithFilterCanIncludeFilesInDirectory() throws Exception { + int count = 0; + for (RevCommit c : git.log().addPath("subdir-include").call()) { + assertEquals("commit3", c.getFullMessage()); + count++; + } + assertEquals(1, count); + } + + @Test + public void testLogWithFilterCanExcludeFilesInDirectory() throws Exception { + int count = 0; + Iterator it = git.log().excludePath("subdir-exclude").call().iterator(); + while (it.hasNext()) { + it.next(); + count++; + } + // of all the commits, we expect to filter out only d.txt + assertEquals(3, count); + } + + @Test + public void testLogWithoutFilter() throws Exception { + int count = 0; + for (RevCommit c : git.log().call()) { + assertEquals(committer, c.getCommitterIdent()); + count++; + } + assertEquals(4, count); + } + + @Test + public void testLogWithFilterCanExcludeAndIncludeFilesInDifferentDirectories() + throws Exception { + int count = 0; + Iterator it = git.log().addPath("subdir-include") + .excludePath("subdir-exclude").call().iterator(); + while (it.hasNext()) { + it.next(); + count++; + } + // we expect to include c.txt + assertEquals(1, count); + } + + @Test + public void testLogWithFilterExcludeAndIncludeSameFileIncludesNothing() + throws Exception { + int count = 0; + Iterator it = git.log().addPath("subdir-exclude") + .excludePath("subdir-exclude").call().iterator(); + + while (it.hasNext()) { + it.next(); + count++; + } + // we expect the exclude to trump everything + assertEquals(0, count); + } + + @Test + public void testLogWithFilterCanExcludeFileAndDirectory() throws Exception { + int count = 0; + Iterator it = git.log().excludePath("b.txt") + .excludePath("subdir-exclude").call().iterator(); + + while (it.hasNext()) { + it.next(); + count++; + } + // we expect a.txt and c.txt + assertEquals(2, count); + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java index 66de8ae13..5ea6015af 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/LogCommand.java @@ -64,10 +64,7 @@ import org.eclipse.jgit.revwalk.filter.MaxCountRevFilter; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.revwalk.filter.SkipRevFilter; -import org.eclipse.jgit.treewalk.filter.AndTreeFilter; -import org.eclipse.jgit.treewalk.filter.PathFilter; -import org.eclipse.jgit.treewalk.filter.PathFilterGroup; -import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.treewalk.filter.*; /** * A class used to execute a {@code Log} command. It has setters for all @@ -105,6 +102,7 @@ public class LogCommand extends GitCommand> { private RevFilter revFilter; private final List pathFilters = new ArrayList<>(); + private final List excludeTreeFilters = new ArrayList<>(); private int maxCount = -1; @@ -133,9 +131,22 @@ protected LogCommand(Repository repo) { @Override public Iterable call() throws GitAPIException, NoHeadException { checkCallable(); - if (!pathFilters.isEmpty()) - walk.setTreeFilter(AndTreeFilter.create( - PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF)); + List filters = new ArrayList<>(); + if (!pathFilters.isEmpty()) { + filters.add(AndTreeFilter.create(PathFilterGroup.create(pathFilters), TreeFilter.ANY_DIFF)); + } + if (!excludeTreeFilters.isEmpty()) { + for (TreeFilter f : excludeTreeFilters) { + filters.add(AndTreeFilter.create(f, TreeFilter.ANY_DIFF)); + } + } + if (!filters.isEmpty()) { + if (filters.size() == 1) { + filters.add(TreeFilter.ANY_DIFF); + } + walk.setTreeFilter(AndTreeFilter.create(filters)); + + } if (skip > -1 && maxCount > -1) walk.setRevFilter(AndRevFilter.create(SkipRevFilter.create(skip), MaxCountRevFilter.create(maxCount))); @@ -309,6 +320,24 @@ public LogCommand addPath(String path) { return this; } + /** + * Show all commits that are not within any of the specified paths. The path + * must either name a file or a directory exactly and use / + * (slash) as separator. Note that regular expressions or wildcards are not + * yet supported. If a path is both added and excluded from the search, then + * the exclusion wins. + * + * @param path + * a repository-relative path (with / as separator) + * @return {@code this} + * @since 5.6 + */ + public LogCommand excludePath(String path) { + checkCallable(); + excludeTreeFilters.add(PathFilter.create(path).negate()); + return this; + } + /** * Skip the number of commits before starting to show the commit output. *