Merge "Add 'parentof' operator that matches parent changes for a change"
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index e02dc21..52c282e 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -190,6 +190,13 @@
+
Changes occurring in projects starting with 'PREFIX'.
+[[parentof]]
+parentof:'ID'::
+Changes which are parent to the change specified by 'ID'. Change 'ID' can be
+specified as a legacy numerical 'ID' such as 15183, or a Change-Id that can be
+picked from the commit message. This operator will return immediate parents
+and will not return grand parents or higher level ancestors of the given change.
+
[[parentproject]]
parentproject:'PROJECT'::
+
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 68a90d2..4e3edcd 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -173,6 +173,7 @@
public static final String FIELD_MESSAGE = "message";
public static final String FIELD_OWNER = "owner";
public static final String FIELD_OWNERIN = "ownerin";
+ public static final String FIELD_PARENTOF = "parentof";
public static final String FIELD_PARENTPROJECT = "parentproject";
public static final String FIELD_PATH = "path";
public static final String FIELD_PENDING_REVIEWER = "pendingreviewer";
@@ -735,6 +736,16 @@
}
@Operator
+ public Predicate<ChangeData> parentof(String value) throws QueryParseException {
+ List<ChangeData> changes = parseChangeData(value);
+ List<Predicate<ChangeData>> or = new ArrayList<>(changes.size());
+ for (ChangeData c : changes) {
+ or.add(new ParentOfPredicate(value, c, args.repoManager));
+ }
+ return Predicate.or(or);
+ }
+
+ @Operator
public Predicate<ChangeData> parentproject(String name) {
return new ParentProjectPredicate(args.projectCache, args.childProjects, name);
}
@@ -1560,14 +1571,18 @@
}
private List<Change> parseChange(String value) throws QueryParseException {
+ return asChanges(parseChangeData(value));
+ }
+
+ private List<ChangeData> parseChangeData(String value) throws QueryParseException {
if (PAT_LEGACY_ID.matcher(value).matches()) {
Optional<Change.Id> id = Change.Id.tryParse(value);
if (!id.isPresent()) {
throw error("Invalid change id " + value);
}
- return asChanges(args.queryProvider.get().byLegacyChangeId(id.get()));
+ return args.queryProvider.get().byLegacyChangeId(id.get());
} else if (PAT_CHANGE_ID.matcher(value).matches()) {
- List<Change> changes = asChanges(args.queryProvider.get().byKeyPrefix(parseChangeId(value)));
+ List<ChangeData> changes = args.queryProvider.get().byKeyPrefix(parseChangeId(value));
if (changes.isEmpty()) {
throw error("Change " + value + " not found");
}
diff --git a/java/com/google/gerrit/server/query/change/ParentOfPredicate.java b/java/com/google/gerrit/server/query/change/ParentOfPredicate.java
new file mode 100644
index 0000000..e48d586
--- /dev/null
+++ b/java/com/google/gerrit/server/query/change/ParentOfPredicate.java
@@ -0,0 +1,62 @@
+// Copyright (C) 2021 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.collect.Sets;
+import com.google.gerrit.entities.PatchSet;
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.query.Matchable;
+import com.google.gerrit.index.query.OperatorPredicate;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import java.io.IOException;
+import java.util.Set;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+public class ParentOfPredicate extends OperatorPredicate<ChangeData>
+ implements Matchable<ChangeData> {
+ protected final Set<RevCommit> parents;
+
+ public ParentOfPredicate(String value, ChangeData change, GitRepositoryManager repoManager) {
+ super(ChangeQueryBuilder.FIELD_PARENTOF, value);
+ this.parents = getParents(change, repoManager);
+ }
+
+ @Override
+ public boolean match(ChangeData changeData) {
+ return changeData.patchSets().stream().anyMatch(ps -> parents.contains(ps.commitId()));
+ }
+
+ @Override
+ public int getCost() {
+ return 1;
+ }
+
+ protected Set<RevCommit> getParents(ChangeData change, GitRepositoryManager repoManager) {
+ PatchSet ps = change.currentPatchSet();
+ try (Repository repo = repoManager.openRepository(change.project());
+ RevWalk walk = new RevWalk(repo)) {
+ RevCommit c = walk.parseCommit(ps.commitId());
+ return Sets.newHashSet(c.getParents());
+ } catch (IOException e) {
+ throw new StorageException(
+ String.format(
+ "Loading commit %s for ps %d of change %d failed.",
+ ps.commitId(), ps.id().get(), ps.id().changeId().get()),
+ e);
+ }
+ }
+}
diff --git a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 7efcb4b..48bd321 100644
--- a/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/javatests/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -705,6 +705,24 @@
}
@Test
+ public void byParentOf() throws Exception {
+ TestRepository<Repo> repo1 = createProject("repo1");
+ RevCommit commit1 = repo1.parseBody(repo1.commit().message("message").create());
+ Change change1 = insert(repo1, newChangeForCommit(repo1, commit1));
+ RevCommit commit2 = repo1.parseBody(repo1.commit(commit1));
+ Change change2 = insert(repo1, newChangeForCommit(repo1, commit2));
+ RevCommit commit3 = repo1.parseBody(repo1.commit(commit1, commit2));
+ Change change3 = insert(repo1, newChangeForCommit(repo1, commit3));
+
+ assertQuery("parentof:" + change1.getId().get());
+ assertQuery("parentof:" + change1.getKey().get());
+ assertQuery("parentof:" + change2.getId().get(), change1);
+ assertQuery("parentof:" + change2.getKey().get(), change1);
+ assertQuery("parentof:" + change3.getId().get(), change2, change1);
+ assertQuery("parentof:" + change3.getKey().get(), change2, change1);
+ }
+
+ @Test
public void byParentProject() throws Exception {
TestRepository<Repo> repo1 = createProject("repo1");
TestRepository<Repo> repo2 = createProject("repo2", "repo1");
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
index 43f8a2b..97d5271 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.ts
@@ -93,6 +93,7 @@
'onlyextensions:',
'owner:',
'ownerin:',
+ 'parentof:',
'parentproject:',
'project:',
'projects:',