Allow filtering log by author This allows specifying an ?author=<author> URL parameter on the log and index page to only show commits by author names which contain <author>. Equivalent to the --author command of git log, except for now it does not support regular expressions. Change-Id: I01e11f12ed5b4245856e1bc4574bfc8452bcdc43
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/AuthorRevFilter.java b/gitiles-servlet/src/main/java/com/google/gitiles/AuthorRevFilter.java new file mode 100644 index 0000000..52c4cc1 --- /dev/null +++ b/gitiles-servlet/src/main/java/com/google/gitiles/AuthorRevFilter.java
@@ -0,0 +1,61 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gitiles; + +import com.google.common.annotations.VisibleForTesting; + +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.errors.StopWalkException; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.revwalk.filter.RevFilter; + +import java.io.IOException; + +/** + * A {@link RevFilter} which only includes {@link RevCommit}s by an author pattern. + * + * Mostly equivalent to {@code git log --author}. + */ +public class AuthorRevFilter extends RevFilter { + private final String authorPattern; + + public AuthorRevFilter(String authorPattern) { + this.authorPattern = authorPattern; + } + + @Override + public boolean include(RevWalk walker, RevCommit commit) throws StopWalkException, + MissingObjectException, IncorrectObjectTypeException, IOException { + return matchesPerson(commit.getAuthorIdent()); + } + + /** @return whether the given person matches the author filter. */ + @VisibleForTesting + boolean matchesPerson(PersonIdent person) { + // Equivalent to --fixed-strings, to avoid pathological performance of Java + // regex matching. + // TODO(kalman): Find/use a port of re2. + return person.getName().contains(authorPattern) + || person.getEmailAddress().contains(authorPattern); + } + + @Override + public RevFilter clone() { + return this; + } +}
diff --git a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java index 5b147b3..1593988 100644 --- a/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java +++ b/gitiles-servlet/src/main/java/com/google/gitiles/LogServlet.java
@@ -211,6 +211,10 @@ PathFilterGroup.createFromStrings(view.getPathPart()), TreeFilter.ANY_DIFF)); } + String author = Iterables.getFirst(view.getParameters().get("author"), null); + if (author != null) { + walk.setRevFilter(new AuthorRevFilter(author)); + } return walk; }
diff --git a/gitiles-servlet/src/test/java/com/google/gitiles/AuthorRevFilterTest.java b/gitiles-servlet/src/test/java/com/google/gitiles/AuthorRevFilterTest.java new file mode 100644 index 0000000..42a4c21 --- /dev/null +++ b/gitiles-servlet/src/test/java/com/google/gitiles/AuthorRevFilterTest.java
@@ -0,0 +1,88 @@ +// Copyright 2014 Google Inc. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package com.google.gitiles; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.junit.TestRepository; +import org.eclipse.jgit.lib.PersonIdent; +import org.eclipse.jgit.revwalk.RevCommit; +import org.junit.Test; + +/** + * Tests for {@link AuthorRevFilter}. + * + * Unfortunately it's not easy to test the Filter using real {@link RevCommit}s + * because {@link TestRepository} hard-codes its author as "J. Author". The next + * best thing is to test a {@link PersonIdent}, those are easy to construct. + * TODO(dborowitz): Fix TestRepository to allow this. + */ +public class AuthorRevFilterTest { + @Test + public void matchesName() throws Exception { + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertTrue(filter.matchesPerson(new PersonIdent("eSt", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("eStablish", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("teSt", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("teSting", "[email protected]"))); + } + + @Test + public void caseSensitiveName() throws Exception { + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertFalse(filter.matchesPerson(new PersonIdent("est", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("Establish", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("tESt", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("tesTing", "[email protected]"))); + } + + @Test + public void matchesEmailLocalPart() throws Exception { + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + } + + @Test + public void caseSensitiveEmailLocalPart() throws Exception { + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + } + + @Test + public void matchesEmailDomain() throws Exception { + // git log --author matches the email domain as well as the enail name. + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertTrue(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + } + + @Test + public void caseSensitiveEmailDomain() throws Exception { + AuthorRevFilter filter = new AuthorRevFilter("eSt"); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + assertFalse(filter.matchesPerson(new PersonIdent("null", "[email protected]"))); + } +}