Support searching changes by directories
Signed-off-by: Edwin Kempin <ekempin@google.com> Change-Id: I4192646dc50ace09e1f7b697f5b352b6ceca12fa
This commit is contained in:
@@ -306,6 +306,18 @@ An extension is defined as the portion of the filename following the final `.`.
|
|||||||
Files with no `.` in their name have no extension and can be matched by an
|
Files with no `.` in their name have no extension and can be matched by an
|
||||||
empty string.
|
empty string.
|
||||||
|
|
||||||
|
[[directory]]
|
||||||
|
directory:'DIR', dir:'DIR'::
|
||||||
|
+
|
||||||
|
Matches any change where the current patch set touches a file in the directory
|
||||||
|
'DIR'. The matching is done case-insensitive. 'DIR' can be a full directory
|
||||||
|
name, a directory prefix or any combination of intermediate directory segments.
|
||||||
|
E.g. a change that touches a file in the directory 'a/b/c' matches for 'a/b/c',
|
||||||
|
'a', 'a/b', 'b', 'b/c' and 'c'.
|
||||||
|
+
|
||||||
|
Slash ('/') is used path separator. Leading and trailing slashes are allowed
|
||||||
|
but are not mandatory.
|
||||||
|
|
||||||
[[footer]]
|
[[footer]]
|
||||||
footer:'FOOTER'::
|
footer:'FOOTER'::
|
||||||
+
|
+
|
||||||
|
|||||||
@@ -243,6 +243,48 @@ public class ChangeField {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Folders that are touched by the current patch set. */
|
||||||
|
public static final FieldDef<ChangeData, Iterable<String>> DIRECTORY =
|
||||||
|
exact(ChangeQueryBuilder.FIELD_DIRECTORY).buildRepeatable(ChangeField::getDirectories);
|
||||||
|
|
||||||
|
public static Set<String> getDirectories(ChangeData cd) throws OrmException {
|
||||||
|
List<String> paths;
|
||||||
|
try {
|
||||||
|
paths = cd.currentFilePaths();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
Splitter s = Splitter.on('/').omitEmptyStrings();
|
||||||
|
Set<String> r = new HashSet<>();
|
||||||
|
for (String path : paths) {
|
||||||
|
StringBuilder directory = new StringBuilder();
|
||||||
|
directory.append("");
|
||||||
|
r.add(directory.toString());
|
||||||
|
String nextPart = null;
|
||||||
|
for (String part : s.split(path)) {
|
||||||
|
if (nextPart != null) {
|
||||||
|
r.add(nextPart);
|
||||||
|
|
||||||
|
if (directory.length() > 0) {
|
||||||
|
directory.append("/");
|
||||||
|
}
|
||||||
|
directory.append(nextPart);
|
||||||
|
|
||||||
|
String intermediateDir = directory.toString();
|
||||||
|
int i = intermediateDir.indexOf('/');
|
||||||
|
while (i >= 0) {
|
||||||
|
r.add(intermediateDir);
|
||||||
|
intermediateDir = intermediateDir.substring(i + 1);
|
||||||
|
i = intermediateDir.indexOf('/');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
nextPart = part;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
/** Owner/creator of the change. */
|
/** Owner/creator of the change. */
|
||||||
public static final FieldDef<ChangeData, Integer> OWNER =
|
public static final FieldDef<ChangeData, Integer> OWNER =
|
||||||
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
|
integer(ChangeQueryBuilder.FIELD_OWNER).build(changeGetter(c -> c.getOwner().get()));
|
||||||
|
|||||||
@@ -107,7 +107,9 @@ public class ChangeSchemaDefinitions extends SchemaDefinitions<ChangeData> {
|
|||||||
|
|
||||||
@Deprecated static final Schema<ChangeData> V53 = schema(V52, ChangeField.ONLY_EXTENSIONS);
|
@Deprecated static final Schema<ChangeData> V53 = schema(V52, ChangeField.ONLY_EXTENSIONS);
|
||||||
|
|
||||||
static final Schema<ChangeData> V54 = schema(V53, ChangeField.FOOTER);
|
@Deprecated static final Schema<ChangeData> V54 = schema(V53, ChangeField.FOOTER);
|
||||||
|
|
||||||
|
static final Schema<ChangeData> V55 = schema(V54, ChangeField.DIRECTORY);
|
||||||
|
|
||||||
public static final String NAME = "changes";
|
public static final String NAME = "changes";
|
||||||
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
|
public static final ChangeSchemaDefinitions INSTANCE = new ChangeSchemaDefinitions();
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
|||||||
public static final String FIELD_COMMENTBY = "commentby";
|
public static final String FIELD_COMMENTBY = "commentby";
|
||||||
public static final String FIELD_COMMIT = "commit";
|
public static final String FIELD_COMMIT = "commit";
|
||||||
public static final String FIELD_COMMITTER = "committer";
|
public static final String FIELD_COMMITTER = "committer";
|
||||||
|
public static final String FIELD_DIRECTORY = "directory";
|
||||||
public static final String FIELD_EXACTCOMMITTER = "exactcommitter";
|
public static final String FIELD_EXACTCOMMITTER = "exactcommitter";
|
||||||
public static final String FIELD_EXTENSION = "extension";
|
public static final String FIELD_EXTENSION = "extension";
|
||||||
public static final String FIELD_ONLY_EXTENSIONS = "onlyextensions";
|
public static final String FIELD_ONLY_EXTENSIONS = "onlyextensions";
|
||||||
@@ -772,6 +773,19 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
|||||||
throw new QueryParseException("'footer' operator is not supported by change index version");
|
throw new QueryParseException("'footer' operator is not supported by change index version");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Operator
|
||||||
|
public Predicate<ChangeData> dir(String directory) throws QueryParseException {
|
||||||
|
return directory(directory);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Operator
|
||||||
|
public Predicate<ChangeData> directory(String directory) throws QueryParseException {
|
||||||
|
if (args.getSchema().hasField(ChangeField.DIRECTORY)) {
|
||||||
|
return new DirectoryPredicate(directory);
|
||||||
|
}
|
||||||
|
throw new QueryParseException("'directory' operator is not supported by change index version");
|
||||||
|
}
|
||||||
|
|
||||||
@Operator
|
@Operator
|
||||||
public Predicate<ChangeData> label(String name)
|
public Predicate<ChangeData> label(String name)
|
||||||
throws QueryParseException, OrmException, IOException, ConfigInvalidException {
|
throws QueryParseException, OrmException, IOException, ConfigInvalidException {
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// 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.common.base.CharMatcher;
|
||||||
|
import com.google.gerrit.server.index.change.ChangeField;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
public class DirectoryPredicate extends ChangeIndexPredicate {
|
||||||
|
private static String clean(String directory) {
|
||||||
|
return CharMatcher.is('/').trimFrom(directory).toLowerCase(Locale.US);
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectoryPredicate(String value) {
|
||||||
|
super(ChangeField.DIRECTORY, clean(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean match(ChangeData cd) throws OrmException {
|
||||||
|
return ChangeField.getDirectories(cd).contains(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCost() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1502,6 +1502,81 @@ public abstract class AbstractQueryChangesTest extends GerritServerTests {
|
|||||||
assertQuery("footer:=");
|
assertQuery("footer:=");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void byDirectory() throws Exception {
|
||||||
|
if (getSchemaVersion() < 55) {
|
||||||
|
assertMissingField(ChangeField.DIRECTORY);
|
||||||
|
String unsupportedOperatorMessage =
|
||||||
|
"'directory' operator is not supported by change index version";
|
||||||
|
assertFailingQuery("directory:src/java", unsupportedOperatorMessage);
|
||||||
|
assertFailingQuery("dir:src/java", unsupportedOperatorMessage);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
Change change1 = insert(repo, newChangeWithFiles(repo, "src/foo.h", "src/foo.cc"));
|
||||||
|
Change change2 = insert(repo, newChangeWithFiles(repo, "src/java/foo.java", "src/js/bar.js"));
|
||||||
|
Change change3 =
|
||||||
|
insert(repo, newChangeWithFiles(repo, "documentation/training/slides/README.txt"));
|
||||||
|
Change change4 = insert(repo, newChangeWithFiles(repo, "a.txt"));
|
||||||
|
Change change5 = insert(repo, newChangeWithFiles(repo, "a/b/c/d/e/foo.txt"));
|
||||||
|
|
||||||
|
// matching by directory prefix works
|
||||||
|
assertQuery("directory:src", change2, change1);
|
||||||
|
assertQuery("directory:src/java", change2);
|
||||||
|
assertQuery("directory:src/js", change2);
|
||||||
|
assertQuery("directory:documentation/", change3);
|
||||||
|
assertQuery("directory:documentation/training", change3);
|
||||||
|
assertQuery("directory:documentation/training/slides", change3);
|
||||||
|
|
||||||
|
// 'dir' alias works
|
||||||
|
assertQuery("dir:src", change2, change1);
|
||||||
|
assertQuery("dir:src/java", change2);
|
||||||
|
|
||||||
|
// case doesn't matter
|
||||||
|
assertQuery("directory:Documentation/TrAiNiNg/SLIDES", change3);
|
||||||
|
|
||||||
|
// leading and trailing '/' doesn't matter
|
||||||
|
assertQuery("directory:/documentation/training/slides", change3);
|
||||||
|
assertQuery("directory:documentation/training/slides/", change3);
|
||||||
|
assertQuery("directory:/documentation/training/slides/", change3);
|
||||||
|
|
||||||
|
// files do not match as directory
|
||||||
|
assertQuery("directory:src/foo.h");
|
||||||
|
assertQuery("directory:documentation/training/slides/README.txt");
|
||||||
|
|
||||||
|
// root directory matches all changes
|
||||||
|
assertQuery("directory:/", change5, change4, change3, change2, change1);
|
||||||
|
assertQuery("directory:\"\"", change5, change4, change3, change2, change1);
|
||||||
|
assertFailingQuery("directory:");
|
||||||
|
|
||||||
|
// matching single directory segments works
|
||||||
|
assertQuery("directory:java", change2);
|
||||||
|
assertQuery("directory:slides", change3);
|
||||||
|
|
||||||
|
// files do not match as directory segment
|
||||||
|
assertQuery("directory:foo.h");
|
||||||
|
|
||||||
|
// matching any combination of intermediate directory segments works
|
||||||
|
assertQuery("directory:training/slides", change3);
|
||||||
|
assertQuery("directory:b/c", change5);
|
||||||
|
assertQuery("directory:b/c/d", change5);
|
||||||
|
assertQuery("directory:b/c/d/e", change5);
|
||||||
|
assertQuery("directory:c/d", change5);
|
||||||
|
assertQuery("directory:c/d/e", change5);
|
||||||
|
assertQuery("directory:d/e", change5);
|
||||||
|
|
||||||
|
// files do not match as directory segments
|
||||||
|
assertQuery("directory:d/e/foo.txt");
|
||||||
|
assertQuery("directory:e/foo.txt");
|
||||||
|
|
||||||
|
// matching any combination of intermediate directory segments works with leading and trailing
|
||||||
|
// '/'
|
||||||
|
assertQuery("directory:/b/c", change5);
|
||||||
|
assertQuery("directory:/b/c/", change5);
|
||||||
|
assertQuery("directory:b/c/", change5);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void byComment() throws Exception {
|
public void byComment() throws Exception {
|
||||||
TestRepository<Repo> repo = createProject("repo");
|
TestRepository<Repo> repo = createProject("repo");
|
||||||
|
|||||||
@@ -36,6 +36,8 @@
|
|||||||
'conflicts:',
|
'conflicts:',
|
||||||
'deleted:',
|
'deleted:',
|
||||||
'delta:',
|
'delta:',
|
||||||
|
'dir:',
|
||||||
|
'directory:',
|
||||||
'ext:',
|
'ext:',
|
||||||
'extension:',
|
'extension:',
|
||||||
'file:',
|
'file:',
|
||||||
|
|||||||
Reference in New Issue
Block a user