Merge changes I76319392,I72a86254
* changes:
Support searching changes by file extension
AbstractQueryChangesTest: Factor out a method for adding files
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index 7c904f5..abd2531 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -289,6 +289,14 @@
Regular expression matching can be enabled by starting the string
with `^`. In this mode `file:` is an alias of `path:` (see above).
+[[extension]]
+extension:'EXT', ext:'EXT'::
++
+Matches any change touching a file with extension 'EXT', case-insensitive. The
+extension is defined as the portion of the filename following the final `.`.
+Files with no `.` in their name have no extension and cannot be matched with
+this operator; use `file:` instead.
+
[[star]]
star:'LABEL'::
+
diff --git a/java/com/google/gerrit/server/index/change/ChangeField.java b/java/com/google/gerrit/server/index/change/ChangeField.java
index 9e8f111..52dac9d 100644
--- a/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -37,6 +37,7 @@
import com.google.common.collect.Lists;
import com.google.common.collect.Table;
import com.google.common.flogger.FluentLogger;
+import com.google.common.io.Files;
import com.google.common.primitives.Longs;
import com.google.gerrit.common.data.SubmitRecord;
import com.google.gerrit.common.data.SubmitRequirement;
@@ -185,6 +186,26 @@
public static final FieldDef<ChangeData, Iterable<String>> FILE_PART =
exact(ChangeQueryBuilder.FIELD_FILEPART).buildRepeatable(ChangeField::getFileParts);
+ /** File extensions of each file modified in the current patch set. */
+ public static final FieldDef<ChangeData, Iterable<String>> EXTENSION =
+ exact(ChangeQueryBuilder.FIELD_EXTENSION).buildRepeatable(ChangeField::getExtensions);
+
+ public static Set<String> getExtensions(ChangeData cd) throws OrmException {
+ try {
+ return cd.currentFilePaths()
+ .stream()
+ // Use case-insensitive file extensions even though other file fields are case-sensitive.
+ // If we want to find "all Java files", we want to match both .java and .JAVA, even if we
+ // normally care about case sensitivity. (Whether we should change the existing file/path
+ // predicates to be case insensitive is a separate question.)
+ .map(f -> Files.getFileExtension(f).toLowerCase(Locale.US))
+ .filter(e -> !e.isEmpty())
+ .collect(toSet());
+ } catch (IOException e) {
+ throw new OrmException(e);
+ }
+ }
+
/** Owner/creator of the change. */
public static final FieldDef<ChangeData, Integer> OWNER =
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
diff --git a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index 9016fd1..cd24c92 100644
--- a/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -101,7 +101,9 @@
// Bump Lucene version requires reindexing
@Deprecated static final Schema<ChangeData> V50 = schema(V49);
- static final Schema<ChangeData> V51 = schema(V50, ChangeField.TOTAL_COMMENT_COUNT);
+ @Deprecated static final Schema<ChangeData> V51 = schema(V50, ChangeField.TOTAL_COMMENT_COUNT);
+
+ static final Schema<ChangeData> V52 = schema(V51, ChangeField.EXTENSION);
public static final String NAME = "changes";
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index f5df87b..8885f7e 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -137,6 +137,7 @@
public static final String FIELD_COMMIT = "commit";
public static final String FIELD_COMMITTER = "committer";
public static final String FIELD_EXACTCOMMITTER = "exactcommitter";
+ public static final String FIELD_EXTENSION = "extension";
public static final String FIELD_CONFLICTS = "conflicts";
public static final String FIELD_DELETED = "deleted";
public static final String FIELD_DELTA = "delta";
@@ -733,6 +734,16 @@
}
@Operator
+ public Predicate<ChangeData> ext(String ext) {
+ return extension(ext);
+ }
+
+ @Operator
+ public Predicate<ChangeData> extension(String ext) {
+ return new FileExtensionPredicate(ext);
+ }
+
+ @Operator
public Predicate<ChangeData> label(String name)
throws QueryParseException, OrmException, IOException, ConfigInvalidException {
Set<Account.Id> accounts = null;
diff --git a/java/com/google/gerrit/server/query/change/FileExtensionPredicate.java b/java/com/google/gerrit/server/query/change/FileExtensionPredicate.java
new file mode 100644
index 0000000..ee5030a
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/FileExtensionPredicate.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2019 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 com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+import java.util.Locale;
+
+public class FileExtensionPredicate extends ChangeIndexPredicate {
+ private static String clean(String ext) {
+ if (ext.startsWith(".")) {
+ ext = ext.substring(1);
+ }
+ return ext.toLowerCase(Locale.US);
+ }
+
+ FileExtensionPredicate(String value) {
+ super(ChangeField.EXTENSION, clean(value));
+ }
+
+ @Override
+ public boolean match(ChangeData object) throws OrmException {
+ return ChangeField.getExtensions(object).contains(value);
+ }
+
+ @Override
+ public int getCost() {
+ return 0;
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 58ea1d2..6390caa 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -126,6 +126,7 @@
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.junit.TestRepository.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.ObjectReader;
@@ -1323,14 +1324,7 @@
@Test
public void byFileExact() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- RevCommit commit =
- repo.parseBody(
- repo.commit()
- .message("one")
- .add("dir/file1", "contents1")
- .add("dir/file2", "contents2")
- .create());
- Change change = insert(repo, newChangeForCommit(repo, commit));
+ Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("file:file");
assertQuery("file:dir", change);
@@ -1343,14 +1337,7 @@
@Test
public void byFileRegex() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- RevCommit commit =
- repo.parseBody(
- repo.commit()
- .message("one")
- .add("dir/file1", "contents1")
- .add("dir/file2", "contents2")
- .create());
- Change change = insert(repo, newChangeForCommit(repo, commit));
+ Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("file:.*file.*");
assertQuery("file:^file.*"); // Whole path only.
@@ -1360,14 +1347,7 @@
@Test
public void byPathExact() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- RevCommit commit =
- repo.parseBody(
- repo.commit()
- .message("one")
- .add("dir/file1", "contents1")
- .add("dir/file2", "contents2")
- .create());
- Change change = insert(repo, newChangeForCommit(repo, commit));
+ Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("path:file");
assertQuery("path:dir");
@@ -1380,20 +1360,31 @@
@Test
public void byPathRegex() throws Exception {
TestRepository<Repo> repo = createProject("repo");
- RevCommit commit =
- repo.parseBody(
- repo.commit()
- .message("one")
- .add("dir/file1", "contents1")
- .add("dir/file2", "contents2")
- .create());
- Change change = insert(repo, newChangeForCommit(repo, commit));
+ Change change = insert(repo, newChangeWithFiles(repo, "dir/file1", "dir/file2"));
assertQuery("path:.*file.*");
assertQuery("path:^dir.file.*", change);
}
@Test
+ public void byExtension() throws Exception {
+ assume().that(getSchema().hasField(ChangeField.EXTENSION)).isTrue();
+
+ TestRepository<Repo> repo = createProject("repo");
+ Change change1 = insert(repo, newChangeWithFiles(repo, "foo.h", "foo.cc"));
+ Change change2 = insert(repo, newChangeWithFiles(repo, "bar.H", "bar.CC"));
+ Change change3 = insert(repo, newChangeWithFiles(repo, "dir/baz.h", "dir/baz.cc"));
+ Change change4 = insert(repo, newChangeWithFiles(repo, "Quux.java"));
+
+ assertQuery("extension:java", change4);
+ assertQuery("ext:java", change4);
+ assertQuery("ext:.java", change4);
+ assertQuery("ext:jAvA", change4);
+ assertQuery("ext:.jAvA", change4);
+ assertQuery("ext:cc", change3, change2, change1);
+ }
+
+ @Test
public void byComment() throws Exception {
TestRepository<Repo> repo = createProject("repo");
ChangeInserter ins = newChange(repo);
@@ -2879,6 +2870,15 @@
return newChange(repo, commit, null, null, null, false);
}
+ protected ChangeInserter newChangeWithFiles(TestRepository<Repo> repo, String... paths)
+ throws Exception {
+ CommitBuilder b = repo.commit().message("Change with files");
+ for (String path : paths) {
+ b.add(path, "contents of " + path);
+ }
+ return newChangeForCommit(repo, repo.parseBody(b.create()));
+ }
+
protected ChangeInserter newChangeForBranch(TestRepository<Repo> repo, String branch)
throws Exception {
return newChange(repo, null, branch, null, null, false);
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index a81526c..8aff87e 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -36,6 +36,8 @@
'conflicts:',
'deleted:',
'delta:',
+ 'ext:',
+ 'extension:',
'file:',
'from:',
'has:',