Line data Source code
1 : // Copyright (C) 2021 The Android Open Source Project
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 :
15 : package com.google.gerrit.server.query.change;
16 :
17 : import static com.google.common.collect.ImmutableSet.toImmutableSet;
18 :
19 : import com.google.common.base.CharMatcher;
20 : import com.google.gerrit.entities.Account;
21 : import com.google.gerrit.entities.Change;
22 : import com.google.gerrit.entities.PatchSet;
23 : import com.google.gerrit.entities.Project;
24 : import com.google.gerrit.git.ObjectIds;
25 : import com.google.gerrit.index.query.Predicate;
26 : import com.google.gerrit.server.CommentsUtil;
27 : import com.google.gerrit.server.StarredChangesUtil;
28 : import com.google.gerrit.server.change.HashtagsUtil;
29 : import com.google.gerrit.server.index.change.ChangeField;
30 : import java.util.ArrayList;
31 : import java.util.Collection;
32 : import java.util.List;
33 : import java.util.Locale;
34 : import java.util.Set;
35 :
36 : /** Predicates that match against {@link ChangeData}. */
37 : public class ChangePredicates {
38 : private ChangePredicates() {}
39 :
40 : /**
41 : * Returns a predicate that matches changes where the provided {@link
42 : * com.google.gerrit.entities.Account.Id} is in the attention set.
43 : */
44 : public static Predicate<ChangeData> attentionSet(Account.Id id) {
45 4 : return new ChangeIndexPredicate(ChangeField.ATTENTION_SET_USERS, id.toString());
46 : }
47 :
48 : /**
49 : * Returns a predicate that matches changes that are assigned to the provided {@link
50 : * com.google.gerrit.entities.Account.Id}.
51 : */
52 : public static Predicate<ChangeData> assignee(Account.Id id) {
53 4 : return new ChangeIndexPredicate(ChangeField.ASSIGNEE_SPEC, id.toString());
54 : }
55 :
56 : /**
57 : * Returns a predicate that matches changes that are a revert of the provided {@link
58 : * com.google.gerrit.entities.Change.Id}.
59 : */
60 : public static Predicate<ChangeData> revertOf(Change.Id revertOf) {
61 4 : return new ChangeIndexCardinalPredicate(ChangeField.REVERT_OF, revertOf.toString(), 1);
62 : }
63 :
64 : /**
65 : * Returns a predicate that matches changes that have a comment authored by the provided {@link
66 : * com.google.gerrit.entities.Account.Id}.
67 : */
68 : public static Predicate<ChangeData> commentBy(Account.Id id) {
69 4 : return new ChangeIndexPredicate(ChangeField.COMMENTBY, id.toString());
70 : }
71 :
72 : /**
73 : * Returns a predicate that matches changes where the provided {@link
74 : * com.google.gerrit.entities.Account.Id} has a pending change edit.
75 : */
76 : public static Predicate<ChangeData> editBy(Account.Id id) {
77 8 : return new ChangeIndexPredicate(ChangeField.EDITBY, id.toString());
78 : }
79 :
80 : /**
81 : * Returns a predicate that matches changes where the provided {@link
82 : * com.google.gerrit.entities.Account.Id} has a pending draft comment.
83 : */
84 : public static Predicate<ChangeData> draftBy(CommentsUtil commentsUtil, Account.Id id) {
85 7 : Set<Predicate<ChangeData>> changeIdPredicates =
86 7 : commentsUtil.getChangesWithDrafts(id).stream()
87 7 : .map(ChangePredicates::idStr)
88 7 : .collect(toImmutableSet());
89 7 : return changeIdPredicates.isEmpty()
90 5 : ? ChangeIndexPredicate.none()
91 7 : : Predicate.or(changeIdPredicates);
92 : }
93 :
94 : /**
95 : * Returns a predicate that matches changes where the provided {@link
96 : * com.google.gerrit.entities.Account.Id} has starred changes with {@code label}.
97 : */
98 : public static Predicate<ChangeData> starBy(
99 : StarredChangesUtil starredChangesUtil, Account.Id id, String label) {
100 4 : Set<Predicate<ChangeData>> starredChanges =
101 4 : starredChangesUtil.byAccountId(id, label).stream()
102 4 : .map(ChangePredicates::idStr)
103 4 : .collect(toImmutableSet());
104 4 : return starredChanges.isEmpty() ? ChangeIndexPredicate.none() : Predicate.or(starredChanges);
105 : }
106 :
107 : /**
108 : * Returns a predicate that matches changes that were reviewed by any of the provided {@link
109 : * com.google.gerrit.entities.Account.Id}.
110 : */
111 : public static Predicate<ChangeData> reviewedBy(Collection<Account.Id> ids) {
112 4 : List<Predicate<ChangeData>> predicates = new ArrayList<>(ids.size());
113 4 : for (Account.Id id : ids) {
114 4 : predicates.add(new ChangeIndexPredicate(ChangeField.REVIEWEDBY, id.toString()));
115 4 : }
116 4 : return Predicate.or(predicates);
117 : }
118 :
119 : /** Returns a predicate that matches changes that were not yet reviewed. */
120 : public static Predicate<ChangeData> unreviewed() {
121 4 : return Predicate.not(
122 4 : new ChangeIndexPredicate(ChangeField.REVIEWEDBY, ChangeField.NOT_REVIEWED.toString()));
123 : }
124 :
125 : /**
126 : * Returns a predicate that matches the change with the provided {@link
127 : * com.google.gerrit.entities.Change.Id}.
128 : */
129 : public static Predicate<ChangeData> idStr(Change.Id id) {
130 68 : return new ChangeIndexCardinalPredicate(
131 68 : ChangeField.LEGACY_ID_STR, ChangeQueryBuilder.FIELD_CHANGE, id.toString(), 1);
132 : }
133 :
134 : /**
135 : * Returns a predicate that matches changes owned by the provided {@link
136 : * com.google.gerrit.entities.Account.Id}.
137 : */
138 : public static Predicate<ChangeData> owner(Account.Id id) {
139 102 : return new ChangeIndexCardinalPredicate(ChangeField.OWNER_SPEC, id.toString(), 5000);
140 : }
141 :
142 : /**
143 : * Returns a predicate that matches changes where the latest patch set was uploaded by the
144 : * provided {@link com.google.gerrit.entities.Account.Id}.
145 : */
146 : public static Predicate<ChangeData> uploader(Account.Id id) {
147 4 : return new ChangeIndexPredicate(ChangeField.UPLOADER_SPEC, id.toString());
148 : }
149 :
150 : /**
151 : * Returns a predicate that matches changes that are a cherry pick of the provided {@link
152 : * com.google.gerrit.entities.Change.Id}.
153 : */
154 : public static Predicate<ChangeData> cherryPickOf(Change.Id id) {
155 0 : return new ChangeIndexPredicate(ChangeField.CHERRY_PICK_OF_CHANGE, id.toString());
156 : }
157 :
158 : /**
159 : * Returns a predicate that matches changes that are a cherry pick of the provided {@link
160 : * com.google.gerrit.entities.PatchSet.Id}.
161 : */
162 : public static Predicate<ChangeData> cherryPickOf(PatchSet.Id psId) {
163 0 : return Predicate.and(
164 0 : cherryPickOf(psId.changeId()),
165 0 : new ChangeIndexPredicate(ChangeField.CHERRY_PICK_OF_PATCHSET, String.valueOf(psId.get())));
166 : }
167 :
168 : /**
169 : * Returns a predicate that matches changes in the provided {@link
170 : * com.google.gerrit.entities.Project.NameKey}.
171 : */
172 : public static Predicate<ChangeData> project(Project.NameKey id) {
173 110 : return new ChangeIndexCardinalPredicate(ChangeField.PROJECT_SPEC, id.get(), 1_000_000);
174 : }
175 :
176 : /** Returns a predicate that matches changes targeted at the provided {@code refName}. */
177 : public static Predicate<ChangeData> ref(String refName) {
178 104 : return new ChangeIndexCardinalPredicate(ChangeField.REF_SPEC, refName, 10_000);
179 : }
180 :
181 : /** Returns a predicate that matches changes in the provided {@code topic}. */
182 : public static Predicate<ChangeData> exactTopic(String topic) {
183 27 : return new ChangeIndexPredicate(ChangeField.EXACT_TOPIC, topic);
184 : }
185 :
186 : /** Returns a predicate that matches changes in the provided {@code topic}. */
187 : public static Predicate<ChangeData> fuzzyTopic(String topic) {
188 4 : return new ChangeIndexPredicate(ChangeField.FUZZY_TOPIC, topic);
189 : }
190 :
191 : /** Returns a predicate that matches changes in the provided {@code topic}. Used with prefixes */
192 : public static Predicate<ChangeData> prefixTopic(String topic) {
193 4 : return new ChangeIndexPredicate(ChangeField.PREFIX_TOPIC, topic);
194 : }
195 :
196 : /** Returns a predicate that matches changes submitted in the provided {@code changeSet}. */
197 : public static Predicate<ChangeData> submissionId(String changeSet) {
198 26 : return new ChangeIndexPredicate(ChangeField.SUBMISSIONID_SPEC, changeSet);
199 : }
200 :
201 : /** Returns a predicate that matches changes that modified the provided {@code path}. */
202 : public static Predicate<ChangeData> path(String path) {
203 11 : return new ChangeIndexPredicate(ChangeField.PATH_SPEC, path);
204 : }
205 :
206 : /** Returns a predicate that matches changes tagged with the provided {@code hashtag}. */
207 : public static Predicate<ChangeData> hashtag(String hashtag) {
208 : // Use toLowerCase without locale to match behavior in ChangeField.
209 4 : return new ChangeIndexPredicate(
210 4 : ChangeField.HASHTAG_SPEC, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
211 : }
212 :
213 : /** Returns a predicate that matches changes tagged with the provided {@code hashtag}. */
214 : public static Predicate<ChangeData> fuzzyHashtag(String hashtag) {
215 : // Use toLowerCase without locale to match behavior in ChangeField.
216 4 : return new ChangeIndexPredicate(
217 4 : ChangeField.FUZZY_HASHTAG, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
218 : }
219 :
220 : /**
221 : * Returns a predicate that matches changes in the provided {@code hashtag}. Used with prefixes
222 : */
223 : public static Predicate<ChangeData> prefixHashtag(String hashtag) {
224 : // Use toLowerCase without locale to match behavior in ChangeField.
225 4 : return new ChangeIndexPredicate(
226 4 : ChangeField.PREFIX_HASHTAG, HashtagsUtil.cleanupHashtag(hashtag).toLowerCase());
227 : }
228 :
229 : /** Returns a predicate that matches changes that modified the provided {@code file}. */
230 : public static Predicate<ChangeData> file(ChangeQueryBuilder.Arguments args, String file) {
231 11 : Predicate<ChangeData> eqPath = path(file);
232 11 : if (!args.getSchema().hasField(ChangeField.FILE_PART_SPEC)) {
233 1 : return eqPath;
234 : }
235 10 : return Predicate.or(eqPath, new ChangeIndexPredicate(ChangeField.FILE_PART_SPEC, file));
236 : }
237 :
238 : /**
239 : * Returns a predicate that matches changes with the provided {@code footer} in their commit
240 : * message.
241 : */
242 : public static Predicate<ChangeData> footer(String footer) {
243 4 : int indexEquals = footer.indexOf('=');
244 4 : int indexColon = footer.indexOf(':');
245 :
246 : // footer key cannot contain '='
247 4 : if (indexEquals > 0 && (indexEquals < indexColon || indexColon < 0)) {
248 4 : footer = footer.substring(0, indexEquals) + ": " + footer.substring(indexEquals + 1);
249 : }
250 4 : return new ChangeIndexPredicate(ChangeField.FOOTER_SPEC, footer.toLowerCase(Locale.US));
251 : }
252 :
253 : /**
254 : * Returns a predicate that matches changes with the provided {@code footer} name in their commit
255 : * message.
256 : */
257 : public static Predicate<ChangeData> hasFooter(String footerName) {
258 4 : return new ChangeIndexPredicate(ChangeField.FOOTER_NAME, footerName);
259 : }
260 :
261 : /**
262 : * Returns a predicate that matches changes that modified files in the provided {@code directory}.
263 : */
264 : public static Predicate<ChangeData> directory(String directory) {
265 5 : return new ChangeIndexPredicate(
266 5 : ChangeField.DIRECTORY_SPEC, CharMatcher.is('/').trimFrom(directory).toLowerCase(Locale.US));
267 : }
268 :
269 : /** Returns a predicate that matches changes with the provided {@code trackingId}. */
270 : public static Predicate<ChangeData> trackingId(String trackingId) {
271 4 : return new ChangeIndexCardinalPredicate(ChangeField.TR, trackingId, 5);
272 : }
273 :
274 : /** Returns a predicate that matches changes authored by the provided {@code exactAuthor}. */
275 : public static Predicate<ChangeData> exactAuthor(String exactAuthor) {
276 4 : return new ChangeIndexPredicate(
277 4 : ChangeField.EXACT_AUTHOR_SPEC, exactAuthor.toLowerCase(Locale.US));
278 : }
279 :
280 : /** Returns a predicate that matches changes authored by the provided {@code author}. */
281 : public static Predicate<ChangeData> author(String author) {
282 4 : return new ChangeIndexPredicate(ChangeField.AUTHOR_PARTS_SPEC, author);
283 : }
284 :
285 : /**
286 : * Returns a predicate that matches changes where the patch set was committed by {@code
287 : * exactCommitter}.
288 : */
289 : public static Predicate<ChangeData> exactCommitter(String exactCommitter) {
290 4 : return new ChangeIndexPredicate(
291 4 : ChangeField.EXACT_COMMITTER_SPEC, exactCommitter.toLowerCase(Locale.US));
292 : }
293 :
294 : /**
295 : * Returns a predicate that matches changes where the patch set was committed by {@code
296 : * committer}.
297 : */
298 : public static Predicate<ChangeData> committer(String comitter) {
299 4 : return new ChangeIndexPredicate(
300 4 : ChangeField.COMMITTER_PARTS_SPEC, comitter.toLowerCase(Locale.US));
301 : }
302 :
303 : /** Returns a predicate that matches changes whose ID starts with the provided {@code id}. */
304 : public static Predicate<ChangeData> idPrefix(String id) {
305 99 : return new ChangeIndexCardinalPredicate(ChangeField.ID, id, 5);
306 : }
307 :
308 : /**
309 : * Returns a predicate that matches changes in a project that has the provided {@code prefix} in
310 : * its name.
311 : */
312 : public static Predicate<ChangeData> projectPrefix(String prefix) {
313 10 : return new ChangeIndexPredicate(ChangeField.PROJECTS_SPEC, prefix);
314 : }
315 :
316 : /**
317 : * Returns a predicate that matches changes where a patch set has the provided {@code commitId}
318 : * either as prefix or as full {@link org.eclipse.jgit.lib.ObjectId}.
319 : */
320 : public static Predicate<ChangeData> commitPrefix(String commitId) {
321 82 : if (commitId.length() == ObjectIds.STR_LEN) {
322 82 : return new ChangeIndexCardinalPredicate(ChangeField.EXACT_COMMIT, commitId, 5);
323 : }
324 8 : return new ChangeIndexCardinalPredicate(ChangeField.COMMIT, commitId, 5);
325 : }
326 :
327 : /**
328 : * Returns a predicate that matches changes where the provided {@code message} appears in the
329 : * commit message. Uses full-text search semantics.
330 : */
331 : public static Predicate<ChangeData> message(String message) {
332 13 : return new ChangeIndexPredicate(ChangeField.COMMIT_MESSAGE, message);
333 : }
334 :
335 : /**
336 : * Returns a predicate that matches changes where the provided {@code comment} appears in any
337 : * comment on any patch set of the change. Uses full-text search semantics.
338 : */
339 : public static Predicate<ChangeData> comment(String comment) {
340 10 : return new ChangeIndexPredicate(ChangeField.COMMENT, comment);
341 : }
342 :
343 : /**
344 : * Returns a predicate that matches with changes having a specific submit rule evaluating to a
345 : * certain result. Value should be in the form of "$ruleName=$status" with $ruleName equals to
346 : * '$plugin_name~$rule_name' and $rule_name equals to the name of the class that implements the
347 : * {@link com.google.gerrit.server.rules.SubmitRule}. For gerrit core rules, $ruleName should be
348 : * in the form of 'gerrit~$rule_name'.
349 : */
350 : public static Predicate<ChangeData> submitRuleStatus(String value) {
351 4 : return new ChangeIndexPredicate(ChangeField.SUBMIT_RULE_RESULT, value);
352 : }
353 :
354 : /**
355 : * Returns a predicate that matches with changes that are pure reverts if {@code value} is equal
356 : * to "1", or non-pure reverts if {@code value} is "0".
357 : */
358 : public static Predicate<ChangeData> pureRevert(String value) {
359 5 : return new ChangeIndexPredicate(ChangeField.IS_PURE_REVERT_SPEC, value);
360 : }
361 :
362 : /**
363 : * Returns a predicate that matches with changes that are submittable if {@code value} is equal to
364 : * "1", or non-submittable if {@code value} is "0".
365 : *
366 : * <p>The computation of this field is based on the evaluation of {@link
367 : * com.google.gerrit.entities.SubmitRequirement}s.
368 : */
369 : public static Predicate<ChangeData> isSubmittable(String value) {
370 0 : return new ChangeIndexPredicate(ChangeField.IS_SUBMITTABLE_SPEC, value);
371 : }
372 : }
|