Merge changes Ibe956014,I4222b894,Iac4ea144,I803717e8,I39e97dcd, ...
* changes:
Support updating multiple VersionedMetaDatas in a BatchRefUpdate
Refactor byChange() into two methods: drafts and published
Add draft comments to PatchLineCommentsUtil
Fix bug for comments with no range
Resolve issue with naming of drafts ref
Add method to parse an AccountId out of a RefName
Improve PatchSet.Id.fromRef performance and avoid double-parsing
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
index 004b7cf..d4a5b3d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/GetCommitIT.java
@@ -14,85 +14,147 @@
package com.google.gerrit.acceptance.rest.project;
-import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNull;
+import com.google.common.collect.Iterables;
import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.PushOneCommit;
import com.google.gerrit.acceptance.RestResponse;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.restapi.IdString;
-import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.project.ListBranches.BranchInfo;
+import com.google.gerrit.server.project.ProjectCache;
import com.google.inject.Inject;
import org.apache.http.HttpStatus;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.After;
+import org.junit.Before;
import org.junit.Test;
-import java.io.IOException;
-
public class GetCommitIT extends AbstractDaemonTest {
@Inject
private AllProjectsName allProjects;
- @Test
- public void getCommit() throws Exception {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
+ @Inject
+ private GitRepositoryManager repoManager;
- allow(Permission.READ, ANONYMOUS_USERS, branchInfo.ref);
+ @Inject
+ private ProjectCache projectCache;
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- CommitInfo commitInfo =
- newGson().fromJson(r.getReader(), CommitInfo.class);
- assertEquals(branchInfo.revision, commitInfo.commit);
- assertEquals("Created project", commitInfo.subject);
- assertEquals("Created project\n", commitInfo.message);
- assertNotNull(commitInfo.author);
- assertEquals("Administrator", commitInfo.author.name);
- assertNotNull(commitInfo.committer);
- assertEquals("Gerrit Code Review", commitInfo.committer.name);
- assertTrue(commitInfo.parents.isEmpty());
- }
+ private TestRepository<Repository> repo;
- @Test
- public void getNonExistingCommit_NotFound() throws IOException {
- RestResponse r = adminSession.get("/projects/" + project.get() + "/commits/"
- + ObjectId.zeroId().name());
- assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
- }
+ @Before
+ public void setUp() throws Exception {
+ repo = new TestRepository<>(repoManager.openRepository(project));
- @Test
- public void getNonVisibleCommit_NotFound() throws Exception {
- RestResponse r =
- adminSession.get("/projects/" + project.get() + "/branches/"
- + IdString.fromDecoded(RefNames.REFS_CONFIG).encoded());
- assertEquals(HttpStatus.SC_OK, r.getStatusCode());
- BranchInfo branchInfo =
- newGson().fromJson(r.getReader(), BranchInfo.class);
- r.consume();
-
- ProjectConfig cfg = projectCache.checkedGet(allProjects).getConfig();
- for (AccessSection sec : cfg.getAccessSections()) {
+ ProjectConfig pc = projectCache.checkedGet(allProjects).getConfig();
+ for (AccessSection sec : pc.getAccessSections()) {
sec.removePermission(Permission.READ);
}
- saveProjectConfig(allProjects, cfg);
+ saveProjectConfig(allProjects, pc);
+ }
- r = adminSession.get("/projects/" + project.get() + "/commits/"
- + branchInfo.revision);
+ @After
+ public void tearDown() throws Exception {
+ if (repo != null) {
+ repo.getRepository().close();
+ }
+ }
+
+ @Test
+ public void getNonExistingCommit_NotFound() throws Exception {
+ assertNotFound(
+ ObjectId.fromString("deadbeefdeadbeefdeadbeefdeadbeefdeadbeef"));
+ }
+
+ @Test
+ public void getMergedCommit_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+
+ CommitInfo info = getCommit(commit);
+ assertEquals(commit.name(), info.commit);
+ assertEquals("Create", info.subject);
+ assertEquals("Create\n\nNew commit\n", info.message);
+ assertEquals("J. Author", info.author.name);
+ assertEquals("jauthor@example.com", info.author.email);
+ assertEquals("J. Committer", info.committer.name);
+ assertEquals("jcommitter@example.com", info.committer.email);
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertEquals(commit.getParent(0).name(), parent.commit);
+ assertEquals("Initial empty repository", parent.subject);
+ assertNull(parent.message);
+ assertNull(parent.author);
+ assertNull(parent.committer);
+ }
+
+ @Test
+ public void getMergedCommit_NotFound() throws Exception {
+ RevCommit commit = repo.parseBody(repo.branch("master")
+ .commit()
+ .message("Create\n\nNew commit\n")
+ .create());
+ assertNotFound(commit);
+ }
+
+ @Test
+ public void getOpenChange_Found() throws Exception {
+ allow(Permission.READ, REGISTERED_USERS, "refs/heads/*");
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+
+ CommitInfo info = getCommit(r.getCommitId());
+ assertEquals(r.getCommitId().name(), info.commit);
+ assertEquals("test commit", info.subject);
+ assertEquals("test commit\n\nChange-Id: " + r.getChangeId() + "\n",
+ info.message);
+ assertEquals("admin", info.author.name);
+ assertEquals("admin@example.com", info.author.email);
+ assertEquals("admin", info.committer.name);
+ assertEquals("admin@example.com", info.committer.email);
+
+ CommitInfo parent = Iterables.getOnlyElement(info.parents);
+ assertEquals(r.getCommit().getParent(0).name(), parent.commit);
+ assertEquals("Initial empty repository", parent.subject);
+ assertNull(parent.message);
+ assertNull(parent.author);
+ assertNull(parent.committer);
+ }
+
+ @Test
+ public void getOpenChange_NotFound() throws Exception {
+ PushOneCommit.Result r = pushFactory.create(db, admin.getIdent())
+ .to(git, "refs/for/master");
+ r.assertOkStatus();
+ assertNotFound(r.getCommitId());
+ }
+
+ private void assertNotFound(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
assertEquals(HttpStatus.SC_NOT_FOUND, r.getStatusCode());
}
+
+ private CommitInfo getCommit(ObjectId id) throws Exception {
+ RestResponse r = userSession.get(
+ "/projects/" + project.get() + "/commits/" + id.name());
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ CommitInfo result = newGson().fromJson(r.getReader(), CommitInfo.class);
+ r.consume();
+ return result;
+ }
}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
index 923d752..bb3d2e7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ListProjectsIT.java
@@ -121,6 +121,11 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?p=some&r=.*").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?p=some&m=some").getStatusCode());
+
RestResponse r = GET("/projects/?p=some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
@@ -138,13 +143,22 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=[.*some").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=.*&p=s").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?r=.*&m=s").getStatusCode());
+
RestResponse r = GET("/projects/?r=.*some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
assertProjects(Arrays.asList(projectAwesome), result.values());
- r = GET("/projects/?r=[.*some");
- assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ r = GET("/projects/?r=some-project$");
+ assertEquals(HttpStatus.SC_OK, r.getStatusCode());
+ result = toProjectInfoMap(r);
+ assertProjects(Arrays.asList(someProject), result.values());
r = GET("/projects/?r=.*");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
@@ -181,6 +195,11 @@
Project.NameKey projectAwesome = new Project.NameKey("project-awesome");
createProject(sshSession, projectAwesome.get());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?m=some&r=.*").getStatusCode());
+ assertEquals(HttpStatus.SC_BAD_REQUEST,
+ GET("/projects/?m=some&p=some").getStatusCode());
+
RestResponse r = GET("/projects/?m=some");
assertEquals(HttpStatus.SC_OK, r.getStatusCode());
Map<String, ProjectInfo> result = toProjectInfoMap(r);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
new file mode 100644
index 0000000..09c2ea6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CommonConverters.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.gerrit.server;
+
+import com.google.gerrit.extensions.common.GitPerson;
+
+import org.eclipse.jgit.lib.PersonIdent;
+
+import java.sql.Timestamp;
+
+/**
+ * Converters to classes in {@link com.google.gerrit.extensions.common}.
+ * <p>
+ * The server frequently needs to convert internal types to types exposed in the
+ * extension API, but the converters themselves are not part of this API. This
+ * class contains such converters as static utility methods.
+ */
+public class CommonConverters {
+ public static GitPerson toGitPerson(PersonIdent ident) {
+ GitPerson result = new GitPerson();
+ result.name = ident.getName();
+ result.email = ident.getEmailAddress();
+ result.date = new Timestamp(ident.getWhen().getTime());
+ result.tz = ident.getTimeZoneOffset();
+ return result;
+ }
+
+ private CommonConverters() {
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
index 0ccb15c..fd727c4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetRelated.java
@@ -21,12 +21,12 @@
import com.google.common.collect.Sets;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.extensions.common.CommitInfo;
-import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetAncestor;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.ProjectControl;
@@ -39,7 +39,6 @@
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
@@ -50,7 +49,6 @@
import org.slf4j.LoggerFactory;
import java.io.IOException;
-import java.sql.Timestamp;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
@@ -272,15 +270,6 @@
return r;
}
- private static GitPerson toGitPerson(PersonIdent id) {
- GitPerson p = new GitPerson();
- p.name = id.getName();
- p.email = id.getEmailAddress();
- p.date = new Timestamp(id.getWhen().getTime());
- p.tz = id.getTimeZoneOffset();
- return p;
- }
-
public static class RelatedInfo {
public List<ChangeAndCommit> changes;
}
@@ -309,7 +298,7 @@
p.commit = c.getParent(i).name();
commit.parents.add(p);
}
- commit.author = toGitPerson(c.getAuthorIdent());
+ commit.author = CommonConverters.toGitPerson(c.getAuthorIdent());
commit.subject = c.getShortMessage();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
index 0d07ce8..a857914 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetCommit.java
@@ -17,6 +17,7 @@
import com.google.gerrit.extensions.common.CommitInfo;
import com.google.gerrit.extensions.common.GitPerson;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.PersonIdent;
@@ -36,8 +37,8 @@
private static CommitInfo toCommitInfo(RevCommit commit) {
CommitInfo info = new CommitInfo();
info.commit = commit.getName();
- info.author = toGitPerson(commit.getAuthorIdent());
- info.committer = toGitPerson(commit.getCommitterIdent());
+ info.author = CommonConverters.toGitPerson(commit.getAuthorIdent());
+ info.committer = CommonConverters.toGitPerson(commit.getCommitterIdent());
info.subject = commit.getShortMessage();
info.message = commit.getFullMessage();
info.parents = new ArrayList<>(commit.getParentCount());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
index 1440200..7e52381 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
@@ -20,12 +20,12 @@
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.CommonConverters;
import com.google.gerrit.server.args4j.TimestampHandler;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.inject.Inject;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.lib.ReflogReader;
import org.eclipse.jgit.lib.Repository;
@@ -122,14 +122,7 @@
public ReflogEntryInfo(ReflogEntry e) {
oldId = e.getOldId().getName();
newId = e.getNewId().getName();
-
- PersonIdent ident = e.getWho();
- who = new GitPerson();
- who.name = ident.getName();
- who.email = ident.getEmailAddress();
- who.date = new Timestamp(ident.getWhen().getTime());
- who.tz = ident.getTimeZoneOffset();
-
+ who = CommonConverters.toGitPerson(e.getWho());
comment = e.getComment();
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index a28ee40..8b895f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -16,6 +16,7 @@
import com.google.common.base.Predicate;
import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -38,14 +39,12 @@
import com.google.gerrit.server.account.GroupCache;
import com.google.gerrit.server.account.GroupControl;
import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gerrit.server.util.TreeFormatter;
import com.google.gson.reflect.TypeToken;
import com.google.inject.Inject;
import com.google.inject.Provider;
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
@@ -449,8 +448,10 @@
private Iterable<Project.NameKey> scan() throws BadRequestException {
if (matchPrefix != null) {
+ checkMatchOptions(matchSubstring == null && matchRegex == null);
return projectCache.byName(matchPrefix);
} else if (matchSubstring != null) {
+ checkMatchOptions(matchPrefix == null && matchRegex == null);
return Iterables.filter(projectCache.all(),
new Predicate<Project.NameKey>() {
public boolean apply(Project.NameKey in) {
@@ -459,32 +460,31 @@
}
});
} else if (matchRegex != null) {
- if (matchRegex.startsWith("^")) {
- matchRegex = matchRegex.substring(1);
- if (matchRegex.endsWith("$") && !matchRegex.endsWith("\\$")) {
- matchRegex = matchRegex.substring(0, matchRegex.length() - 1);
- }
- }
- if (matchRegex.equals(".*")) {
- return projectCache.all();
- }
+ checkMatchOptions(matchPrefix == null && matchSubstring == null);
+ RegexListSearcher<Project.NameKey> searcher;
try {
- final RunAutomaton a =
- new RunAutomaton(new RegExp(matchRegex).toAutomaton());
- return Iterables.filter(projectCache.all(),
- new Predicate<Project.NameKey>() {
- public boolean apply(Project.NameKey in) {
- return a.run(in.get());
- }
- });
+ searcher = new RegexListSearcher<Project.NameKey>(matchRegex) {
+ @Override
+ public String apply(Project.NameKey in) {
+ return in.get();
+ }
+ };
} catch (IllegalArgumentException e) {
throw new BadRequestException(e.getMessage());
}
+ return searcher.search(ImmutableList.copyOf(projectCache.all()));
} else {
return projectCache.all();
}
}
+ private static void checkMatchOptions(boolean cond)
+ throws BadRequestException {
+ if (!cond) {
+ throw new BadRequestException("specify exactly one of p/m/r");
+ }
+ }
+
private void printProjectTree(final PrintWriter stdout,
final TreeMap<Project.NameKey, ProjectNode> treeMap) {
final SortedSet<ProjectNode> sortedNodes = new TreeSet<>();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
index d073002..3d6f8b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/RegexPathPredicate.java
@@ -16,76 +16,21 @@
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.RegexPredicate;
+import com.google.gerrit.server.util.RegexListSearcher;
import com.google.gwtorm.server.OrmException;
-import dk.brics.automaton.Automaton;
-import dk.brics.automaton.RegExp;
-import dk.brics.automaton.RunAutomaton;
-
-import java.util.Collections;
import java.util.List;
class RegexPathPredicate extends RegexPredicate<ChangeData> {
- private final RunAutomaton pattern;
-
- private final String prefixBegin;
- private final String prefixEnd;
- private final int prefixLen;
- private final boolean prefixOnly;
-
RegexPathPredicate(String fieldName, String re) {
super(ChangeField.PATH, re);
-
- if (re.startsWith("^")) {
- re = re.substring(1);
- }
-
- if (re.endsWith("$") && !re.endsWith("\\$")) {
- re = re.substring(0, re.length() - 1);
- }
-
- Automaton automaton = new RegExp(re).toAutomaton();
- prefixBegin = automaton.getCommonPrefix();
- prefixLen = prefixBegin.length();
-
- if (0 < prefixLen) {
- char max = (char) (prefixBegin.charAt(prefixLen - 1) + 1);
- prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
- prefixOnly = re.equals(prefixBegin + ".*");
- } else {
- prefixEnd = "";
- prefixOnly = false;
- }
-
- pattern = prefixOnly ? null : new RunAutomaton(automaton);
}
@Override
public boolean match(ChangeData object) throws OrmException {
List<String> files = object.currentFilePaths();
if (files != null) {
- int begin, end;
-
- if (0 < prefixLen) {
- begin = find(files, prefixBegin);
- end = find(files, prefixEnd);
- } else {
- begin = 0;
- end = files.size();
- }
-
- if (prefixOnly) {
- return begin < end;
- }
-
- while (begin < end) {
- if (pattern.run(files.get(begin++))) {
- return true;
- }
- }
-
- return false;
-
+ return RegexListSearcher.ofStrings(getValue()).hasMatch(files);
} else {
// The ChangeData can't do expensive lookups right now. Bypass
// them and include the result anyway. We might be able to do
@@ -95,11 +40,6 @@
}
}
- private static int find(List<String> files, String p) {
- int r = Collections.binarySearch(files, p);
- return r < 0 ? -(r + 1) : r;
- }
-
@Override
public int getCost() {
return 1;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
new file mode 100644
index 0000000..4b0fd35
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -0,0 +1,112 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.gerrit.server.util;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.primitives.Chars;
+
+import dk.brics.automaton.Automaton;
+import dk.brics.automaton.RegExp;
+import dk.brics.automaton.RunAutomaton;
+
+import java.util.Collections;
+import java.util.List;
+
+/** Helper to search sorted lists for elements matching a regex. */
+public abstract class RegexListSearcher<T> implements Function<T, String> {
+ public static RegexListSearcher<String> ofStrings(String re) {
+ return new RegexListSearcher<String>(re) {
+ @Override
+ public String apply(String in) {
+ return in;
+ }
+ };
+ }
+
+ private final RunAutomaton pattern;
+
+ private final String prefixBegin;
+ private final String prefixEnd;
+ private final int prefixLen;
+ private final boolean prefixOnly;
+
+ public RegexListSearcher(String re) {
+ if (re.startsWith("^")) {
+ re = re.substring(1);
+ }
+
+ if (re.endsWith("$") && !re.endsWith("\\$")) {
+ re = re.substring(0, re.length() - 1);
+ }
+
+ Automaton automaton = new RegExp(re).toAutomaton();
+ prefixBegin = automaton.getCommonPrefix();
+ prefixLen = prefixBegin.length();
+
+ if (0 < prefixLen) {
+ char max = Chars.checkedCast(prefixBegin.charAt(prefixLen - 1) + 1);
+ prefixEnd = prefixBegin.substring(0, prefixLen - 1) + max;
+ prefixOnly = re.equals(prefixBegin + ".*");
+ } else {
+ prefixEnd = "";
+ prefixOnly = false;
+ }
+
+ pattern = prefixOnly ? null : new RunAutomaton(automaton);
+ }
+
+ public Iterable<T> search(List<T> list) {
+ checkNotNull(list);
+ int begin, end;
+
+ if (0 < prefixLen) {
+ // Assumes many consecutive elements may have the same prefix, so the cost
+ // of two binary searches is less than iterating to find the endpoints.
+ begin = find(list, prefixBegin);
+ end = find(list, prefixEnd);
+ } else {
+ begin = 0;
+ end = list.size();
+ }
+
+ if (prefixOnly) {
+ return begin < end ? list.subList(begin, end) : ImmutableList.<T> of();
+ }
+
+ return Iterables.filter(
+ list.subList(begin, end),
+ new Predicate<T>() {
+ @Override
+ public boolean apply(T in) {
+ return pattern.run(RegexListSearcher.this.apply(in));
+ }
+ });
+ }
+
+ public boolean hasMatch(List<T> list) {
+ return !Iterables.isEmpty(search(list));
+ }
+
+ private int find(List<T> list, String p) {
+ int r = Collections.binarySearch(Lists.transform(list, this), p);
+ return r < 0 ? -(r + 1) : r;
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
new file mode 100644
index 0000000..8f73005
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/RegexListSearcherTest.java
@@ -0,0 +1,84 @@
+// Copyright (C) 2014 The Android Open Source Project
+//
+// 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.gerrit.server.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Ordering;
+
+import org.junit.Test;
+
+import java.util.List;
+
+public class RegexListSearcherTest {
+ private static final List<String> EMPTY = ImmutableList.of();
+
+ @Test
+ public void emptyList() {
+ assertSearchReturns(EMPTY, "pat", EMPTY);
+ }
+
+ @Test
+ public void hasMatch() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertTrue(RegexListSearcher.ofStrings("foo").hasMatch(list));
+ assertFalse(RegexListSearcher.ofStrings("xyz").hasMatch(list));
+ }
+
+ @Test
+ public void anchors() {
+ List<String> list = ImmutableList.of("foo");
+ assertSearchReturns(list, "^f.*", list);
+ assertSearchReturns(list, "^f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(list, "f.*o$", list);
+ assertSearchReturns(EMPTY, "^.*\\$", list);
+ }
+
+ @Test
+ public void noCommonPrefix() {
+ List<String> list = ImmutableList.of("bar", "foo", "quux");
+ assertSearchReturns(ImmutableList.of("foo"), "f.*", list);
+ assertSearchReturns(ImmutableList.of("foo"), ".*o.*", list);
+ assertSearchReturns(ImmutableList.of("bar", "foo", "quux"), ".*[aou].*",
+ list);
+ }
+
+ @Test
+ public void commonPrefix() {
+ List<String> list = ImmutableList.of(
+ "bar",
+ "baz",
+ "foo1",
+ "foo2",
+ "foo3",
+ "quux");
+ assertSearchReturns(ImmutableList.of("bar", "baz"), "b.*", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2"), "foo[12]", list);
+ assertSearchReturns(ImmutableList.of("foo1", "foo2", "foo3"), "foo.*",
+ list);
+ assertSearchReturns(ImmutableList.of("quux"), "q.*", list);
+ }
+
+ private void assertSearchReturns(List<?> expected, String re,
+ List<String> inputs) {
+ assertTrue(Ordering.natural().isOrdered(inputs));
+ assertEquals(expected,
+ ImmutableList.copyOf(RegexListSearcher.ofStrings(re).search(inputs)));
+ }
+}