Merge "A11y - add headings for comments"
diff --git a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
index 780a24f..fca1b36 100644
--- a/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
+++ b/java/com/google/gerrit/server/project/ProjectsConsistencyChecker.java
@@ -48,9 +48,7 @@
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.index.change.ChangeField;
import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gerrit.server.query.change.ChangeIdPredicate;
import com.google.gerrit.server.query.change.ChangePredicates;
-import com.google.gerrit.server.query.change.CommitPredicate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -230,11 +228,11 @@
}
// Find changes that have a matching Change-Id.
- predicates.add(new ChangeIdPredicate(changeId));
+ predicates.add(ChangePredicates.idPrefix(changeId));
});
// Find changes that have a matching commit.
- predicates.add(new CommitPredicate(commit.name()));
+ predicates.add(ChangePredicates.commitPrefix(commit.name()));
}
if (!predicates.isEmpty()) {
diff --git a/java/com/google/gerrit/server/query/change/AuthorPredicate.java b/java/com/google/gerrit/server/query/change/AuthorPredicate.java
deleted file mode 100644
index 6e362ad..0000000
--- a/java/com/google/gerrit/server/query/change/AuthorPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.change.ChangeField.AUTHOR;
-import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_AUTHOR;
-
-import com.google.gerrit.server.index.change.ChangeField;
-
-public class AuthorPredicate extends ChangeIndexPredicate {
- public AuthorPredicate(String value) {
- super(AUTHOR, FIELD_AUTHOR, value.toLowerCase());
- }
-
- @Override
- public boolean match(ChangeData object) {
- return ChangeField.getAuthorParts(object).contains(getValue().toLowerCase());
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
deleted file mode 100644
index f06d1f2..0000000
--- a/java/com/google/gerrit/server/query/change/ChangeIdPredicate.java
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright (C) 2010 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.entities.Change;
-import com.google.gerrit.server.index.change.ChangeField;
-
-/** Predicate over Change-Id strings (aka Change.Key). */
-public class ChangeIdPredicate extends ChangeIndexPredicate {
- public ChangeIdPredicate(String id) {
- super(ChangeField.ID, ChangeQueryBuilder.FIELD_CHANGE, id);
- }
-
- @Override
- public boolean match(ChangeData cd) {
- Change change = cd.change();
- if (change == null) {
- return false;
- }
-
- String key = change.getKey().get();
- if (key.equals(getValue()) || key.startsWith(getValue())) {
- return true;
- }
- return false;
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
index cce82f4..c4ca460 100644
--- a/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
+++ b/java/com/google/gerrit/server/query/change/ChangeIndexPredicate.java
@@ -14,6 +14,12 @@
package com.google.gerrit.server.query.change;
+import static com.google.common.collect.ImmutableSet.toImmutableSet;
+
+import com.google.common.base.CharMatcher;
+import com.google.common.base.Splitter;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.google.common.primitives.Longs;
import com.google.gerrit.index.FieldDef;
@@ -22,11 +28,21 @@
import com.google.gerrit.index.query.Matchable;
import com.google.gerrit.index.query.Predicate;
import java.util.Objects;
+import java.util.Set;
+import java.util.stream.StreamSupport;
/** Predicate that is mapped to a field in the change index. */
public class ChangeIndexPredicate extends IndexPredicate<ChangeData>
implements Matchable<ChangeData> {
/**
+ * Text segmentation to be applied to both the query string and the indexed field for full-text
+ * queries. This is inspired by http://unicode.org/reports/tr29/ which is what Lucene uses, but
+ * complexity was reduced to the bare minimum at the cost of small discrepancies to the Unicode
+ * spec.
+ */
+ private static final Splitter FULL_TEXT_SPLITTER = Splitter.on(CharMatcher.anyOf(" ,.-\\/_\n"));
+
+ /**
* Returns an index predicate that matches no changes in the index.
*
* <p>This predicate should be used in preference to a non-index predicate (such as {@code
@@ -46,6 +62,21 @@
super(def, name, value);
}
+ /**
+ * This method matches documents without calling an index subsystem. For primitive fields (e.g.
+ * integer, long) , the matching logic is consistent across this method and all known index
+ * implementations. For text fields (i.e. prefix and full-text) the semantics vary between this
+ * implementation and known index implementations:
+ * <li>Prefix: Lucene as well as {@link #match(ChangeData)} matches terms as true prefixes
+ * (prefix:foo -> `foo bar` matches, but `baz foo bar` does not match). The index
+ * implementation at Google tokenizes both the query and the indexed text and matches tokens
+ * individually (prefix:fo ba -> `baz foo bar` matches).
+ * <li>Full text: Lucene uses a {@code PhraseQuery} to search for terms in full text fields
+ * in-order. The index implementation at Google as well as {@link #match(ChangeData)}
+ * tokenizes both the query and the indexed text and matches tokens individually.
+ *
+ * @return true if the predicate matches the provided {@link ChangeData}.
+ */
@Override
public boolean match(ChangeData cd) {
if (getField().isRepeatable()) {
@@ -74,7 +105,26 @@
return Objects.equals(fieldValueFromObject, value);
} else if (fieldTypeName.equals(FieldType.LONG.getName())) {
return Objects.equals(fieldValueFromObject, Longs.tryParse(value));
+ } else if (fieldTypeName.equals(FieldType.PREFIX.getName())) {
+ return String.valueOf(fieldValueFromObject).startsWith(value);
+ } else if (fieldTypeName.equals(FieldType.FULL_TEXT.getName())) {
+ Set<String> tokenizedField = tokenizeString(String.valueOf(fieldValueFromObject));
+ Set<String> tokenizedValue = tokenizeString(value);
+ return Sets.intersection(tokenizedField, tokenizedValue).size() == tokenizedValue.size();
+ } else if (fieldTypeName.equals(FieldType.STORED_ONLY.getName())) {
+ throw new IllegalStateException("can't filter for storedOnly field " + getField().getName());
+ } else if (fieldTypeName.equals(FieldType.TIMESTAMP.getName())) {
+ throw new IllegalStateException("timestamp queries must be handled in subclasses");
+ } else if (fieldTypeName.equals(FieldType.INTEGER_RANGE.getName())) {
+ throw new IllegalStateException("integer range queries must be handled in subclasses");
+ } else {
+ throw new IllegalStateException("unrecognized field " + fieldTypeName);
}
- throw new UnsupportedOperationException("match function must be provided in subclass");
+ }
+
+ private static ImmutableSet<String> tokenizeString(String value) {
+ return StreamSupport.stream(FULL_TEXT_SPLITTER.split(value.toLowerCase()).spliterator(), false)
+ .filter(s -> !s.trim().isEmpty())
+ .collect(toImmutableSet());
}
}
diff --git a/java/com/google/gerrit/server/query/change/ChangePredicates.java b/java/com/google/gerrit/server/query/change/ChangePredicates.java
index 7ccc7b6..617002d 100644
--- a/java/com/google/gerrit/server/query/change/ChangePredicates.java
+++ b/java/com/google/gerrit/server/query/change/ChangePredicates.java
@@ -19,6 +19,7 @@
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.Project;
+import com.google.gerrit.git.ObjectIds;
import com.google.gerrit.index.query.Predicate;
import com.google.gerrit.server.change.HashtagsUtil;
import com.google.gerrit.server.index.change.ChangeField;
@@ -145,6 +146,11 @@
return new ChangeIndexPredicate(ChangeField.EXACT_TOPIC, topic);
}
+ /** Returns a predicate that matches changes in the provided {@code topic}. */
+ public static Predicate<ChangeData> fuzzyTopic(String topic) {
+ return new ChangeIndexPredicate(ChangeField.FUZZY_TOPIC, topic);
+ }
+
/** Returns a predicate that matches changes submitted in the provided {@code changeSet}. */
public static Predicate<ChangeData> submissionId(String changeSet) {
return new ChangeIndexPredicate(ChangeField.SUBMISSIONID, changeSet);
@@ -162,6 +168,13 @@
ChangeField.HASHTAG, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
}
+ /** Returns a predicate that matches changes tagged with the provided {@code hashtag}. */
+ public static Predicate<ChangeData> fuzzyHashtag(String hashtag) {
+ // Use toLowerCase without locale to match behavior in ChangeField.
+ return new ChangeIndexPredicate(
+ ChangeField.FUZZY_HASHTAG, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
+ }
+
/** Returns a predicate that matches changes that modified the provided {@code file}. */
public static Predicate<ChangeData> file(ChangeQueryBuilder.Arguments args, String file) {
Predicate<ChangeData> eqPath = path(file);
@@ -204,6 +217,11 @@
return new ChangeIndexPredicate(ChangeField.EXACT_AUTHOR, exactAuthor.toLowerCase(Locale.US));
}
+ /** Returns a predicate that matches changes authored by the provided {@code author}. */
+ public static Predicate<ChangeData> author(String author) {
+ return new ChangeIndexPredicate(ChangeField.AUTHOR, author);
+ }
+
/**
* Returns a predicate that matches changes where the patch set was committed by {@code
* exactCommitter}.
@@ -212,4 +230,52 @@
return new ChangeIndexPredicate(
ChangeField.EXACT_COMMITTER, exactCommitter.toLowerCase(Locale.US));
}
+
+ /**
+ * Returns a predicate that matches changes where the patch set was committed by {@code
+ * committer}.
+ */
+ public static Predicate<ChangeData> committer(String comitter) {
+ return new ChangeIndexPredicate(ChangeField.COMMITTER, comitter.toLowerCase(Locale.US));
+ }
+
+ /** Returns a predicate that matches changes whose ID starts with the provided {@code id}. */
+ public static Predicate<ChangeData> idPrefix(String id) {
+ return new ChangeIndexPredicate(ChangeField.ID, id);
+ }
+
+ /**
+ * Returns a predicate that matches changes in a project that has the provided {@code prefix} in
+ * its name.
+ */
+ public static Predicate<ChangeData> projectPrefix(String prefix) {
+ return new ChangeIndexPredicate(ChangeField.PROJECTS, prefix);
+ }
+
+ /**
+ * Returns a predicate that matches changes where a patch set has the provided {@code commitId}
+ * either as prefix or as full {@link org.eclipse.jgit.lib.ObjectId}.
+ */
+ public static Predicate<ChangeData> commitPrefix(String commitId) {
+ if (commitId.length() == ObjectIds.STR_LEN) {
+ return new ChangeIndexPredicate(ChangeField.EXACT_COMMIT, commitId);
+ }
+ return new ChangeIndexPredicate(ChangeField.COMMIT, commitId);
+ }
+
+ /**
+ * Returns a predicate that matches changes where the provided {@code message} appears in the
+ * commit message. Uses full-text search semantics.
+ */
+ public static Predicate<ChangeData> message(String message) {
+ return new ChangeIndexPredicate(ChangeField.COMMIT_MESSAGE, message);
+ }
+
+ /**
+ * Returns a predicate that matches changes where the provided {@code comment} appears in any
+ * comment on any patch set of the change. Uses full-text search semantics.
+ */
+ public static Predicate<ChangeData> comment(String comment) {
+ return new ChangeIndexPredicate(ChangeField.COMMENT, comment);
+ }
}
diff --git a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 1963ca9..94b5442 100644
--- a/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -528,7 +528,7 @@
return Predicate.and(
project(triplet.get().project().get()),
branch(triplet.get().branch().branch()),
- new ChangeIdPredicate(parseChangeId(triplet.get().id().get())));
+ ChangePredicates.idPrefix(parseChangeId(triplet.get().id().get())));
}
if (PAT_LEGACY_ID.matcher(query).matches()) {
Integer id = Ints.tryParse(query);
@@ -538,7 +538,7 @@
: ChangePredicates.idStr(Change.id(id));
}
} else if (PAT_CHANGE_ID.matcher(query).matches()) {
- return new ChangeIdPredicate(parseChangeId(query));
+ return ChangePredicates.idPrefix(parseChangeId(query));
}
throw new QueryParseException("Invalid change format");
@@ -546,7 +546,7 @@
@Operator
public Predicate<ChangeData> comment(String value) {
- return new CommentPredicate(args.index, value);
+ return ChangePredicates.comment(value);
}
@Operator
@@ -705,7 +705,7 @@
@Operator
public Predicate<ChangeData> commit(String id) {
- return new CommitPredicate(id);
+ return ChangePredicates.commitPrefix(id);
}
@Operator
@@ -733,7 +733,7 @@
@Operator
public Predicate<ChangeData> projects(String name) {
- return new ProjectPrefixPredicate(name);
+ return ChangePredicates.projectPrefix(name);
}
@Operator
@@ -807,7 +807,7 @@
throw new QueryParseException(
"'inhashtag' operator is not supported by change index version");
}
- return new FuzzyHashtagPredicate(hashtag, args.index);
+ return ChangePredicates.fuzzyHashtag(hashtag);
}
@Operator
@@ -823,7 +823,7 @@
if (name.isEmpty()) {
return ChangePredicates.exactTopic(name);
}
- return new FuzzyTopicPredicate(name, args.index);
+ return ChangePredicates.fuzzyTopic(name);
}
@Operator
@@ -998,7 +998,7 @@
@Operator
public Predicate<ChangeData> message(String text) {
- return new MessagePredicate(args.index, text);
+ return ChangePredicates.message(text);
}
@Operator
@@ -1390,18 +1390,18 @@
public Predicate<ChangeData> author(String who) throws QueryParseException {
if (args.getSchema().hasField(ChangeField.EXACT_AUTHOR)) {
return getAuthorOrCommitterPredicate(
- who.trim(), ChangePredicates::exactAuthor, AuthorPredicate::new);
+ who.trim(), ChangePredicates::exactAuthor, ChangePredicates::author);
}
- return getAuthorOrCommitterFullTextPredicate(who.trim(), AuthorPredicate::new);
+ return getAuthorOrCommitterFullTextPredicate(who.trim(), ChangePredicates::author);
}
@Operator
public Predicate<ChangeData> committer(String who) throws QueryParseException {
if (args.getSchema().hasField(ChangeField.EXACT_COMMITTER)) {
return getAuthorOrCommitterPredicate(
- who.trim(), ChangePredicates::exactCommitter, CommitterPredicate::new);
+ who.trim(), ChangePredicates::exactCommitter, ChangePredicates::committer);
}
- return getAuthorOrCommitterFullTextPredicate(who.trim(), CommitterPredicate::new);
+ return getAuthorOrCommitterFullTextPredicate(who.trim(), ChangePredicates::committer);
}
@Operator
diff --git a/java/com/google/gerrit/server/query/change/CommentPredicate.java b/java/com/google/gerrit/server/query/change/CommentPredicate.java
deleted file mode 100644
index 7463324..0000000
--- a/java/com/google/gerrit/server/query/change/CommentPredicate.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2013 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.entities.Change;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.index.change.IndexedChangeQuery;
-
-public class CommentPredicate extends ChangeIndexPredicate {
- protected final ChangeIndex index;
-
- public CommentPredicate(ChangeIndex index, String value) {
- super(ChangeField.COMMENT, value);
- this.index = index;
- }
-
- @Override
- public boolean match(ChangeData object) {
- try {
- Change.Id id = object.getId();
- Predicate<ChangeData> p =
- Predicate.and(
- index.getSchema().useLegacyNumericFields()
- ? ChangePredicates.id(id)
- : ChangePredicates.idStr(id),
- this);
- for (ChangeData cData : index.getSource(p, IndexedChangeQuery.oneResult()).read()) {
- if (cData.getId().equals(id)) {
- return true;
- }
- }
- } catch (QueryParseException e) {
- throw new StorageException(e);
- }
-
- return false;
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/CommitPredicate.java b/java/com/google/gerrit/server/query/change/CommitPredicate.java
deleted file mode 100644
index 2b3b345..0000000
--- a/java/com/google/gerrit/server/query/change/CommitPredicate.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// 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.git.ObjectIds.matchesAbbreviation;
-import static com.google.gerrit.server.index.change.ChangeField.COMMIT;
-import static com.google.gerrit.server.index.change.ChangeField.EXACT_COMMIT;
-
-import com.google.gerrit.entities.PatchSet;
-import com.google.gerrit.git.ObjectIds;
-import com.google.gerrit.index.FieldDef;
-
-public class CommitPredicate extends ChangeIndexPredicate {
- static FieldDef<ChangeData, ?> commitField(String id) {
- if (id.length() == ObjectIds.STR_LEN) {
- return EXACT_COMMIT;
- }
- return COMMIT;
- }
-
- public CommitPredicate(String id) {
- super(commitField(id), id);
- }
-
- @Override
- public boolean match(ChangeData object) {
- String id = getValue().toLowerCase();
- for (PatchSet p : object.patchSets()) {
- if (equals(p, id)) {
- return true;
- }
- }
- return false;
- }
-
- protected boolean equals(PatchSet p, String id) {
- if (getField() == EXACT_COMMIT) {
- return p.commitId().name().equals(id);
- }
- return matchesAbbreviation(p.commitId(), id);
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/CommitterPredicate.java b/java/com/google/gerrit/server/query/change/CommitterPredicate.java
deleted file mode 100644
index 65034a2..0000000
--- a/java/com/google/gerrit/server/query/change/CommitterPredicate.java
+++ /dev/null
@@ -1,31 +0,0 @@
-// 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.change.ChangeField.COMMITTER;
-import static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_COMMITTER;
-
-import com.google.gerrit.server.index.change.ChangeField;
-
-public class CommitterPredicate extends ChangeIndexPredicate {
- public CommitterPredicate(String value) {
- super(COMMITTER, FIELD_COMMITTER, value.toLowerCase());
- }
-
- @Override
- public boolean match(ChangeData object) {
- return ChangeField.getCommitterParts(object).contains(getValue().toLowerCase());
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/FuzzyHashtagPredicate.java b/java/com/google/gerrit/server/query/change/FuzzyHashtagPredicate.java
deleted file mode 100644
index b4d6b5f..0000000
--- a/java/com/google/gerrit/server/query/change/FuzzyHashtagPredicate.java
+++ /dev/null
@@ -1,33 +0,0 @@
-// 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 static com.google.gerrit.server.index.change.ChangeField.FUZZY_HASHTAG;
-
-import com.google.gerrit.server.index.change.ChangeIndex;
-
-public class FuzzyHashtagPredicate extends ChangeIndexPredicate {
- protected final ChangeIndex index;
-
- public FuzzyHashtagPredicate(String hashtag, ChangeIndex index) {
- super(FUZZY_HASHTAG, hashtag.toLowerCase());
- this.index = index;
- }
-
- @Override
- public boolean match(ChangeData cd) {
- return cd.hashtags().stream().anyMatch(ht -> ht.toLowerCase().contains(getValue()));
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java b/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
deleted file mode 100644
index 414f85b..0000000
--- a/java/com/google/gerrit/server/query/change/FuzzyTopicPredicate.java
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright (C) 2010 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.change.ChangeField.FUZZY_TOPIC;
-
-import com.google.common.collect.Iterables;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.index.change.IndexedChangeQuery;
-
-public class FuzzyTopicPredicate extends ChangeIndexPredicate {
- protected final ChangeIndex index;
-
- public FuzzyTopicPredicate(String topic, ChangeIndex index) {
- super(FUZZY_TOPIC, topic);
- this.index = index;
- }
-
- @Override
- public boolean match(ChangeData cd) {
- Change change = cd.change();
- if (change == null) {
- return false;
- }
- String t = change.getTopic();
- if (t == null) {
- return false;
- }
- try {
- Predicate<ChangeData> thisId =
- index.getSchema().useLegacyNumericFields()
- ? ChangePredicates.id(cd.getId())
- : ChangePredicates.idStr(cd.getId());
- Iterable<ChangeData> results =
- index.getSource(and(thisId, this), IndexedChangeQuery.oneResult()).read();
- return !Iterables.isEmpty(results);
- } catch (QueryParseException e) {
- throw new StorageException(e);
- }
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 340b327..76ebd81 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -65,7 +65,7 @@
}
private static Predicate<ChangeData> change(Change.Key key) {
- return new ChangeIdPredicate(key.get());
+ return ChangePredicates.idPrefix(key.get());
}
private static Predicate<ChangeData> project(Project.NameKey project) {
@@ -77,7 +77,7 @@
}
private static Predicate<ChangeData> commit(String id) {
- return new CommitPredicate(id);
+ return ChangePredicates.commitPrefix(id);
}
private final ChangeData.Factory changeDataFactory;
@@ -108,7 +108,7 @@
}
public List<ChangeData> byKeyPrefix(String prefix) {
- return query(new ChangeIdPredicate(prefix));
+ return query(ChangePredicates.idPrefix(prefix));
}
public List<ChangeData> byLegacyChangeId(Change.Id id) {
diff --git a/java/com/google/gerrit/server/query/change/MessagePredicate.java b/java/com/google/gerrit/server/query/change/MessagePredicate.java
deleted file mode 100644
index 3228f96..0000000
--- a/java/com/google/gerrit/server/query/change/MessagePredicate.java
+++ /dev/null
@@ -1,53 +0,0 @@
-// Copyright (C) 2010 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.exceptions.StorageException;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.index.change.IndexedChangeQuery;
-
-/** Predicate to match changes that contains specified text in commit messages body. */
-public class MessagePredicate extends ChangeIndexPredicate {
- protected final ChangeIndex index;
-
- public MessagePredicate(ChangeIndex index, String value) {
- super(ChangeField.COMMIT_MESSAGE, value);
- this.index = index;
- }
-
- @Override
- public boolean match(ChangeData object) {
- try {
- Predicate<ChangeData> p =
- Predicate.and(
- index.getSchema().useLegacyNumericFields()
- ? ChangePredicates.id(object.getId())
- : ChangePredicates.idStr(object.getId()),
- this);
- for (ChangeData cData : index.getSource(p, IndexedChangeQuery.oneResult()).read()) {
- if (cData.getId().equals(object.getId())) {
- return true;
- }
- }
- } catch (QueryParseException e) {
- throw new StorageException(e);
- }
-
- return false;
- }
-}
diff --git a/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java b/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
deleted file mode 100644
index b89fffe..0000000
--- a/java/com/google/gerrit/server/query/change/ProjectPrefixPredicate.java
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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.query.change;
-
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.server.index.change.ChangeField;
-
-public class ProjectPrefixPredicate extends ChangeIndexPredicate {
- public ProjectPrefixPredicate(String prefix) {
- super(ChangeField.PROJECTS, prefix);
- }
-
- @Override
- public boolean match(ChangeData object) {
- Change c = object.change();
- return c != null && c.getDest().project().get().startsWith(getValue());
- }
-}
diff --git a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
index 9ba7fa3..ae7f540 100644
--- a/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
+++ b/java/com/google/gerrit/server/restapi/project/CommitsCollection.java
@@ -35,7 +35,6 @@
import com.google.gerrit.server.project.Reachable;
import com.google.gerrit.server.query.change.ChangeData;
import com.google.gerrit.server.query.change.ChangePredicates;
-import com.google.gerrit.server.query.change.CommitPredicate;
import com.google.gerrit.server.update.RetryHelper;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -152,7 +151,7 @@
ChangePredicates.project(project),
Predicate.or(
Arrays.stream(commit.getParents())
- .map(parent -> new CommitPredicate(parent.getId().getName()))
+ .map(parent -> ChangePredicates.commitPrefix(parent.getId().getName()))
.collect(toImmutableList())));
changes =
retryHelper
diff --git a/java/com/google/gerrit/server/submit/CommitMergeStatus.java b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
index bf8b840..4638bfa 100644
--- a/java/com/google/gerrit/server/submit/CommitMergeStatus.java
+++ b/java/com/google/gerrit/server/submit/CommitMergeStatus.java
@@ -77,7 +77,14 @@
EMPTY_COMMIT(
"Change could not be merged because the commit is empty.\n"
+ "\n"
- + "Project policy requires all commits to contain modifications to at least one file.");
+ + "Project policy requires all commits to contain modifications to at least one file."),
+
+ FAST_FORWARD_INDEPENDENT_CHANGES(
+ "Change could not be merged because the submission has two independent changes "
+ + "with the same destination branch.\n"
+ + "\n"
+ + "Independent changes can't be submitted to the same destination branch with "
+ + "FAST_FORWARD_ONLY submit strategy");
private final String description;
diff --git a/java/com/google/gerrit/server/submit/FastForwardOnly.java b/java/com/google/gerrit/server/submit/FastForwardOnly.java
index 176b063..8a30898 100644
--- a/java/com/google/gerrit/server/submit/FastForwardOnly.java
+++ b/java/com/google/gerrit/server/submit/FastForwardOnly.java
@@ -14,11 +14,15 @@
package com.google.gerrit.server.submit;
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.entities.BranchNameKey;
import com.google.gerrit.server.git.CodeReviewCommit;
import com.google.gerrit.server.update.RepoContext;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
public class FastForwardOnly extends SubmitStrategy {
FastForwardOnly(SubmitStrategy.Arguments args) {
@@ -28,6 +32,21 @@
@Override
public List<SubmitStrategyOp> buildOps(Collection<CodeReviewCommit> toMerge) {
List<CodeReviewCommit> sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
+
+ Map<BranchNameKey, CodeReviewCommit> branchToCommit = new HashMap<>();
+ for (CodeReviewCommit codeReviewCommit : sorted) {
+ BranchNameKey branchNameKey = codeReviewCommit.change().getDest();
+ CodeReviewCommit otherCommitInBranch = branchToCommit.get(branchNameKey);
+ if (otherCommitInBranch == null) {
+ branchToCommit.put(branchNameKey, codeReviewCommit);
+ } else {
+ // we found another change with the same destination branch.
+ codeReviewCommit.setStatusCode(CommitMergeStatus.FAST_FORWARD_INDEPENDENT_CHANGES);
+ otherCommitInBranch.setStatusCode(CommitMergeStatus.FAST_FORWARD_INDEPENDENT_CHANGES);
+ return ImmutableList.of();
+ }
+ }
+
List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
CodeReviewCommit newTipCommit =
args.mergeUtil.getFirstFastForward(args.mergeTip.getInitialTip(), args.rw, sorted);
diff --git a/java/com/google/gerrit/server/submit/SubmitStrategyListener.java b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
index b533bebc..59c6b81 100644
--- a/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
+++ b/java/com/google/gerrit/server/submit/SubmitStrategyListener.java
@@ -134,6 +134,7 @@
case NOT_FAST_FORWARD:
case EMPTY_COMMIT:
case MISSING_DEPENDENCY:
+ case FAST_FORWARD_INDEPENDENT_CHANGES:
// TODO(dborowitz): Reformat these messages to be more appropriate for
// short problem descriptions.
String message = s.getDescription();
diff --git a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
index 66eb48c..58e48e9 100644
--- a/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
+++ b/javatests/com/google/gerrit/acceptance/rest/change/SubmitByFastForwardIT.java
@@ -20,6 +20,7 @@
import com.google.gerrit.acceptance.GitUtil;
import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.config.GerritConfig;
import com.google.gerrit.acceptance.testsuite.project.ProjectOperations;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
@@ -107,6 +108,39 @@
}
@Test
+ @GerritConfig(name = "change.submitWholeTopic", value = "true")
+ public void submitTwoIndependentChangesWithFastForwardFail() throws Throwable {
+ RevCommit initialHead = projectOperations.project(project).getHead("master");
+ PushOneCommit.Result change1 = createChange("subject1", "file1.txt", "content", "topic");
+
+ testRepo.reset(initialHead);
+ PushOneCommit.Result change2 = createChange("subject2", "file2.txt", "content", "topic");
+
+ approve(change1.getChangeId());
+ approve(change2.getChangeId());
+
+ String fastForwardIndependentChangesError =
+ "Change could not be merged because the submission"
+ + " has two independent changes with the same destination branch. Independent changes can't "
+ + "be submitted to the same destination branch with FAST_FORWARD_ONLY submit strategy";
+
+ submitWithConflict(
+ change2.getChangeId(),
+ String.format(
+ "Failed to submit 2 changes due to the following problems:\n"
+ + "Change %d: %s\nChange %d: %s",
+ change1.getChange().getId().get(),
+ fastForwardIndependentChangesError,
+ change2.getChange().getId().get(),
+ fastForwardIndependentChangesError));
+
+ RevCommit updatedHead = projectOperations.project(project).getHead("master");
+ assertThat(updatedHead.getId()).isEqualTo(initialHead.getId());
+ assertRefUpdatedEvents();
+ assertChangeMergedEvents();
+ }
+
+ @Test
public void submitFastForwardNotPossible_Conflict() throws Throwable {
RevCommit initialHead = projectOperations.project(project).getHead("master");
PushOneCommit.Result change = createChange("Change 1", "a.txt", "content");
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
index d351af6..693406c 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_html.ts
@@ -429,6 +429,7 @@
></gr-linked-text>
</gr-editable-content>
</div>
+ <h3 class="assistive-tech-only">Comments and Checks Summary</h3>
<gr-change-summary
change-comments="[[_changeComments]]"
comment-threads="[[_commentThreads]]"
@@ -464,7 +465,7 @@
<paper-tab
on-click="_onPaperTabClick"
data-name$="[[_constants.PrimaryTab.FILES]]"
- >Files</paper-tab
+ ><span>Files</span></paper-tab
>
<paper-tab
on-click="_onPaperTabClick"
@@ -482,7 +483,7 @@
<paper-tab
data-name$="[[_constants.PrimaryTab.CHECKS]]"
on-click="_onPaperTabClick"
- >Checks</paper-tab
+ ><span>Checks</span></paper-tab
>
</template>
<template
@@ -503,7 +504,7 @@
data-name$="[[_constants.PrimaryTab.FINDINGS]]"
on-click="_onPaperTabClick"
>
- Findings
+ <span>Findings</span>
</paper-tab>
</paper-tabs>
@@ -563,6 +564,7 @@
is="dom-if"
if="[[_isTabActive(_constants.PrimaryTab.COMMENT_THREADS, _activeTabs)]]"
>
+ <h3 class="assistive-tech-only">Comments</h3>
<gr-thread-list
threads="[[_commentThreads]]"
change="[[_change]]"
@@ -578,6 +580,7 @@
is="dom-if"
if="[[_isTabActive(_constants.PrimaryTab.CHECKS, _activeTabs)]]"
>
+ <h3 class="assistive-tech-only">Checks</h3>
<gr-checks-tab
id="checksTab"
tab-state="[[_tabState.checksTab]]"
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
index 2408a26..b4b4c19 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread.ts
@@ -412,16 +412,16 @@
return displayPath;
}
- _computeDisplayLine() {
- if (this.lineNum === FILE) {
+ _computeDisplayLine(lineNum?: LineNumber, range?: CommentRange) {
+ if (lineNum === FILE) {
if (this.path === SpecialFilePath.PATCHSET_LEVEL_COMMENTS) {
return '';
}
return FILE;
}
- if (this.lineNum) return `#${this.lineNum}`;
+ if (lineNum) return `#${lineNum}`;
// If range is set, then lineNum equals the end line of the range.
- if (this.range) return `#${this.range.end_line}`;
+ if (range) return `#${range.end_line}`;
return '';
}
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
index b6e44f6..6ab82b7 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_html.ts
@@ -123,7 +123,7 @@
<template is="dom-if" if="[[!_isPatchsetLevelComment(path)]]">
<a
href$="[[_getDiffUrlForComment(projectName, changeNum, path, patchNum)]]"
- >[[_computeDisplayLine()]]</a
+ >[[_computeDisplayLine(lineNum, range)]]</a
>
</template>
</div>
diff --git a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
index 64f5ad8..2eeb12d 100644
--- a/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
+++ b/polygerrit-ui/app/elements/shared/gr-comment-thread/gr-comment-thread_test.ts
@@ -285,15 +285,24 @@
test('_computeDisplayLine', () => {
element.lineNum = 5;
- assert.equal(element._computeDisplayLine(), '#5');
+ assert.equal(
+ element._computeDisplayLine(element.lineNum, element.range),
+ '#5'
+ );
element.path = SpecialFilePath.COMMIT_MESSAGE;
element.lineNum = 5;
- assert.equal(element._computeDisplayLine(), '#5');
+ assert.equal(
+ element._computeDisplayLine(element.lineNum, element.range),
+ '#5'
+ );
element.lineNum = undefined;
element.path = SpecialFilePath.PATCHSET_LEVEL_COMMENTS;
- assert.equal(element._computeDisplayLine(), '');
+ assert.equal(
+ element._computeDisplayLine(element.lineNum, element.range),
+ ''
+ );
});
});
});