Support indexing changes by author and committer

Index changes by the author and committer of the change's current
patch set.

Support searching by exact email address, or by parts of the name
or email address.

Feature: Issue 3333
Change-Id: Id9f963f9453030eb1376d3025dd5cfd5d86e5151
This commit is contained in:
David Pursehouse
2015-08-03 16:18:04 +09:00
parent d3b795a4cb
commit cf43e1788f
9 changed files with 270 additions and 0 deletions

View File

@@ -367,6 +367,19 @@ reviewedby:'USER'::
Changes where 'USER' has commented on the change more recently than the
last update (comment or patch set) from the change owner.
[[author]]
author:'AUTHOR'::
+
Changes where 'AUTHOR' is the author of the current patch set. 'AUTHOR' may be
the author's exact email address, or part of the name or email address.
[[committer]]
committer:'COMMITTER'::
+
Changes where 'COMMITTER' is the committer of the current patch set.
'COMMITTER' may be the committer's exact email address, or part of the name or
email address.
== Argument Quoting

View File

@@ -72,6 +72,8 @@ public class SearchSuggestOracle extends HighlightSuggestOracle {
suggestions.add("owner:");
suggestions.add("owner:self");
suggestions.add("ownerin:");
suggestions.add("author:");
suggestions.add("committer:");
suggestions.add("reviewer:");
suggestions.add("reviewer:self");

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.server.index;
import static com.google.common.base.MoreObjects.firstNonNull;
import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Splitter;
import com.google.common.collect.ImmutableList;
@@ -38,12 +39,14 @@ import com.google.gwtorm.protobuf.ProtobufCodec;
import com.google.gwtorm.server.OrmException;
import com.google.protobuf.CodedOutputStream;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.FooterLine;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -404,6 +407,64 @@ public class ChangeField {
}
};
private static Set<String> getPersonParts(PersonIdent person) {
if (person == null) {
return ImmutableSet.of();
}
HashSet<String> parts = Sets.newHashSet();
String email = person.getEmailAddress().toLowerCase();
parts.add(email);
parts.addAll(Arrays.asList(email.split("@")));
Splitter s = Splitter.on(CharMatcher.anyOf("@.- ")).omitEmptyStrings();
Iterables.addAll(parts, s.split(email));
Iterables.addAll(parts, s.split(person.getName().toLowerCase()));
return parts;
}
public static Set<String> getAuthorParts(ChangeData cd) throws OrmException {
try {
return getPersonParts(cd.getAuthor());
} catch (IOException e) {
throw new OrmException(e);
}
}
public static Set<String> getCommitterParts(ChangeData cd) throws OrmException {
try {
return getPersonParts(cd.getCommitter());
} catch (IOException e) {
throw new OrmException(e);
}
}
/**
* The exact email address, or any part of the author name or email address,
* in the current patch set.
*/
public static final FieldDef<ChangeData, Iterable<String>> AUTHOR =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_AUTHOR, FieldType.FULL_TEXT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
return getAuthorParts(input);
}
};
/**
* The exact email address, or any part of the committer name or email address,
* in the current patch set.
*/
public static final FieldDef<ChangeData, Iterable<String>> COMMITTER =
new FieldDef.Repeatable<ChangeData, String>(
ChangeQueryBuilder.FIELD_COMMITTER, FieldType.FULL_TEXT, false) {
@Override
public Iterable<String> get(ChangeData input, FillArgs args)
throws OrmException {
return getCommitterParts(input);
}
};
public static class ChangeProtoField extends FieldDef.Single<ChangeData, byte[]> {
public static final ProtobufCodec<Change> CODEC =
CodecFactory.encoder(Change.class);

View File

@@ -308,6 +308,7 @@ public class ChangeSchemas {
ChangeField.REVIEWEDBY,
ChangeField.EXACT_COMMIT);
@Deprecated
static final Schema<ChangeData> V23 = schema(
ChangeField.LEGACY_ID2,
ChangeField.ID,
@@ -341,6 +342,40 @@ public class ChangeSchemas {
ChangeField.REVIEWEDBY,
ChangeField.EXACT_COMMIT);
static final Schema<ChangeData> V24 = schema(
ChangeField.LEGACY_ID2,
ChangeField.ID,
ChangeField.STATUS,
ChangeField.PROJECT,
ChangeField.PROJECTS,
ChangeField.REF,
ChangeField.EXACT_TOPIC,
ChangeField.FUZZY_TOPIC,
ChangeField.UPDATED,
ChangeField.FILE_PART,
ChangeField.PATH,
ChangeField.OWNER,
ChangeField.REVIEWER,
ChangeField.COMMIT,
ChangeField.TR,
ChangeField.LABEL,
ChangeField.COMMIT_MESSAGE,
ChangeField.COMMENT,
ChangeField.CHANGE,
ChangeField.APPROVAL,
ChangeField.MERGEABLE,
ChangeField.ADDED,
ChangeField.DELETED,
ChangeField.DELTA,
ChangeField.HASHTAG,
ChangeField.COMMENTBY,
ChangeField.PATCH_SET,
ChangeField.GROUP,
ChangeField.EDITBY,
ChangeField.REVIEWEDBY,
ChangeField.EXACT_COMMIT,
ChangeField.AUTHOR,
ChangeField.COMMITTER);
private static Schema<ChangeData> schema(Collection<FieldDef<ChangeData, ?>> fields) {
return new Schema<>(ImmutableList.copyOf(fields));

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2015 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.query.change;
import static com.google.gerrit.server.index.ChangeField.AUTHOR;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_AUTHOR;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gwtorm.server.OrmException;
public class AuthorPredicate extends IndexPredicate<ChangeData> {
AuthorPredicate(String value) {
super(AUTHOR, FIELD_AUTHOR, value);
}
@Override
public boolean match(ChangeData object) throws OrmException {
return ChangeField.getAuthorParts(object).contains(
getValue().toLowerCase());
}
@Override
public int getCost() {
return 1;
}
}

View File

@@ -62,6 +62,7 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
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.FooterLine;
@@ -317,6 +318,8 @@ public class ChangeData {
private Boolean mergeable;
private Set<Account.Id> editsByUser;
private Set<Account.Id> reviewedBy;
private PersonIdent author;
private PersonIdent committer;
@AssistedInject
private ChangeData(
@@ -620,6 +623,24 @@ public class ChangeData {
return commitFooters;
}
public PersonIdent getAuthor() throws IOException, OrmException {
if (author == null) {
if (!loadCommitData()) {
return null;
}
}
return author;
}
public PersonIdent getCommitter() throws IOException, OrmException {
if (committer == null) {
if (!loadCommitData()) {
return null;
}
}
return committer;
}
private boolean loadCommitData() throws OrmException,
RepositoryNotFoundException, IOException, MissingObjectException,
IncorrectObjectTypeException {
@@ -633,6 +654,8 @@ public class ChangeData {
RevCommit c = walk.parseCommit(ObjectId.fromString(sha1));
commitMessage = c.getFullMessage();
commitFooters = c.getFooterLines();
author = c.getAuthorIdent();
committer = c.getCommitterIdent();
}
return true;
}

View File

@@ -91,12 +91,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
public static final String FIELD_ADDED = "added";
public static final String FIELD_AFTER = "after";
public static final String FIELD_AGE = "age";
public static final String FIELD_AUTHOR = "author";
public static final String FIELD_BEFORE = "before";
public static final String FIELD_BRANCH = "branch";
public static final String FIELD_CHANGE = "change";
public static final String FIELD_COMMENT = "comment";
public static final String FIELD_COMMENTBY = "commentby";
public static final String FIELD_COMMIT = "commit";
public static final String FIELD_COMMITTER = "committer";
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
@@ -843,6 +845,16 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
throw new QueryParseException("Unknown named destination: " + name);
}
@Operator
public Predicate<ChangeData> author(String who) {
return new AuthorPredicate(who);
}
@Operator
public Predicate<ChangeData> committer(String who) {
return new CommitterPredicate(who);
}
@Override
protected Predicate<ChangeData> defaultField(String query) throws QueryParseException {
if (query.startsWith("refs/")) {

View File

@@ -0,0 +1,39 @@
// Copyright (C) 2015 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.query.change;
import static com.google.gerrit.server.index.ChangeField.COMMITTER;
import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_COMMITTER;
import com.google.gerrit.server.index.ChangeField;
import com.google.gerrit.server.index.IndexPredicate;
import com.google.gwtorm.server.OrmException;
public class CommitterPredicate extends IndexPredicate<ChangeData> {
CommitterPredicate(String value) {
super(COMMITTER, FIELD_COMMITTER, value);
}
@Override
public boolean match(ChangeData object) throws OrmException {
return ChangeField.getCommitterParts(object).contains(
getValue().toLowerCase());
}
@Override
public int getCost() {
return 1;
}
}

View File

@@ -379,6 +379,52 @@ public abstract class AbstractQueryChangesTest {
assertQuery("owner:" + user2, change2);
}
@Test
public void byAuthor() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Change change1 = newChange(repo, null, null, userId.get(), null).insert();
// By exact email address
assertQuery("author:jauthor@example.com", change1);
// By email address part
assertQuery("author:jauthor", change1);
assertQuery("author:example", change1);
assertQuery("author:example.com", change1);
// By name part
assertQuery("author:Author", change1);
// By non-existing email address / name / part
assertQuery("author:jcommitter@example.com");
assertQuery("author:somewhere.com");
assertQuery("author:jcommitter");
assertQuery("author:Committer");
}
@Test
public void byCommitter() throws Exception {
TestRepository<Repo> repo = createProject("repo");
Change change1 = newChange(repo, null, null, userId.get(), null).insert();
// By exact email address
assertQuery("committer:jcommitter@example.com", change1);
// By email address part
assertQuery("committer:jcommitter", change1);
assertQuery("committer:example", change1);
assertQuery("committer:example.com", change1);
// By name part
assertQuery("committer:Committer", change1);
// By non-existing email address / name / part
assertQuery("committer:jauthor@example.com");
assertQuery("committer:somewhere.com");
assertQuery("committer:jauthor");
assertQuery("committer:Author");
}
@Test
public void byOwnerIn() throws Exception {
TestRepository<Repo> repo = createProject("repo");