Support alias "self" in queries

Writing an expression like "owner:self status:open" will now identify
changes that the caller owns and are still open. This self alias
is valid in contexts where a user is expected as an argument to a
query operator.

Change-Id: I87e58dda43d8da560b31400f1257857b0ea96175
This commit is contained in:
Shawn O. Pearce
2012-04-07 14:59:11 -07:00
parent 8010d4b9a6
commit 068a0d8dde
3 changed files with 111 additions and 52 deletions

View File

@@ -75,7 +75,8 @@ Change-Id that was scraped out of the commit message.
[[owner]] [[owner]]
owner:'USER':: owner:'USER'::
+ +
Changes originally submitted by 'USER'. Changes originally submitted by 'USER'. The special case of
`owner:self` will find changes owned by the caller.
[[ownerin]] [[ownerin]]
ownerin:'GROUP':: ownerin:'GROUP'::
@@ -85,7 +86,9 @@ Changes originally submitted by a user in 'GROUP'.
[[reviewer]] [[reviewer]]
reviewer:'USER':: reviewer:'USER'::
+ +
Changes that have been, or need to be, reviewed by 'USER'. Changes that have been, or need to be, reviewed by 'USER'. The
special case of `reviewer:self` will find changes where the caller
has been added as a reviewer.
[[reviewerin]] [[reviewerin]]
reviewerin:'GROUP':: reviewerin:'GROUP'::
@@ -213,6 +216,16 @@ is:reviewed::
True if there is at least one non-zero score on the change, in any True if there is at least one non-zero score on the change, in any
approval category, by any user. approval category, by any user.
is:owner::
+
True on any change where the current user is the change owner.
Same as `owner:self`.
is:reviewer::
+
True on any change where the current user is a reviewer.
Same as `reviewer:self`.
is:open:: is:open::
+ +
True if the change is other open or submitted, merge pending. True if the change is other open or submitted, merge pending.
@@ -373,16 +386,20 @@ the change. This flag is always added to any query.
starredby:'USER':: starredby:'USER'::
+ +
Matches changes that have been starred by 'USER'. Matches changes that have been starred by 'USER'.
The special case `starredby:self` applies to the caller.
watchedby:'USER':: watchedby:'USER'::
+ +
Matches changes that 'USER' has configured watch filters for. Matches changes that 'USER' has configured watch filters for.
The special case `watchedby:self` applies to the caller.
draftby:'USER':: draftby:'USER'::
+ +
Matches changes that 'USER' has left unpublished drafts on. Matches changes that 'USER' has left unpublished drafts on.
Since the drafts are unpublished, it is not possible to see the Since the drafts are unpublished, it is not possible to see the
draft text, or even how many drafts there are. draft text, or even how many drafts there are. The special case
of `draftby:self` will find changes where the caller has created
a draft comment.
limit:'CNT':: limit:'CNT'::
+ +

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.query; package com.google.gerrit.server.query;
import com.google.common.collect.Iterables;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import java.util.Collection; import java.util.Collection;
@@ -44,23 +45,35 @@ import java.util.List;
public abstract class Predicate<T> { public abstract class Predicate<T> {
/** Combine the passed predicates into a single AND node. */ /** Combine the passed predicates into a single AND node. */
public static <T> Predicate<T> and(final Predicate<T>... that) { public static <T> Predicate<T> and(final Predicate<T>... that) {
if (that.length == 1) {
return that[0];
}
return new AndPredicate<T>(that); return new AndPredicate<T>(that);
} }
/** Combine the passed predicates into a single AND node. */ /** Combine the passed predicates into a single AND node. */
public static <T> Predicate<T> and( public static <T> Predicate<T> and(
final Collection<? extends Predicate<T>> that) { final Collection<? extends Predicate<T>> that) {
if (that.size() == 1) {
return Iterables.getOnlyElement(that);
}
return new AndPredicate<T>(that); return new AndPredicate<T>(that);
} }
/** Combine the passed predicates into a single OR node. */ /** Combine the passed predicates into a single OR node. */
public static <T> Predicate<T> or(final Predicate<T>... that) { public static <T> Predicate<T> or(final Predicate<T>... that) {
if (that.length == 1) {
return that[0];
}
return new OrPredicate<T>(that); return new OrPredicate<T>(that);
} }
/** Combine the passed predicates into a single OR node. */ /** Combine the passed predicates into a single OR node. */
public static <T> Predicate<T> or( public static <T> Predicate<T> or(
final Collection<? extends Predicate<T>> that) { final Collection<? extends Predicate<T>> that) {
if (that.size() == 1) {
return Iterables.getOnlyElement(that);
}
return new OrPredicate<T>(that); return new OrPredicate<T>(that);
} }

View File

@@ -14,6 +14,7 @@
package com.google.gerrit.server.query.change; package com.google.gerrit.server.query.change;
import com.google.common.collect.Lists;
import com.google.gerrit.common.data.ApprovalTypes; import com.google.gerrit.common.data.ApprovalTypes;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -44,6 +45,7 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@@ -206,10 +208,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
} }
if ("draft".equalsIgnoreCase(value)) { if ("draft".equalsIgnoreCase(value)) {
if (currentUser instanceof IdentifiedUser) { return new HasDraftByPredicate(args.dbProvider, self());
return new HasDraftByPredicate(args.dbProvider,
((IdentifiedUser) currentUser).getAccountId());
}
} }
throw new IllegalArgumentException(); throw new IllegalArgumentException();
@@ -233,6 +232,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
return new IsReviewedPredicate(args.dbProvider); return new IsReviewedPredicate(args.dbProvider);
} }
if ("owner".equalsIgnoreCase(value)) {
return new OwnerPredicate(args.dbProvider, self());
}
if ("reviewer".equalsIgnoreCase(value)) {
return new ReviewerPredicate(args.dbProvider, self());
}
try { try {
return status(value); return status(value);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
@@ -303,42 +310,59 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> starredby(String who) public Predicate<ChangeData> starredby(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = args.accountResolver.find(who); if ("self".equals(who)) {
if (account == null) { return new IsStarredByPredicate(args.dbProvider, currentUser);
throw error("User " + who + " not found");
} }
return new IsStarredByPredicate(args.dbProvider, // Set<Account.Id> m = parseAccount(who);
args.userFactory.create(args.dbProvider, account.getId())); List<IsStarredByPredicate> p = Lists.newArrayListWithCapacity(m.size());
for (Account.Id id : m) {
p.add(new IsStarredByPredicate(args.dbProvider,
args.userFactory.create(args.dbProvider, id)));
}
return Predicate.or(p);
} }
@Operator @Operator
public Predicate<ChangeData> watchedby(String who) public Predicate<ChangeData> watchedby(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = args.accountResolver.find(who); Set<Account.Id> m = parseAccount(who);
if (account == null) { List<IsWatchedByPredicate> p = Lists.newArrayListWithCapacity(m.size());
throw error("User " + who + " not found"); for (Account.Id id : m) {
if (currentUser instanceof IdentifiedUser
&& id.equals(((IdentifiedUser) currentUser).getAccountId())) {
p.add(new IsWatchedByPredicate(args, currentUser));
} else {
p.add(new IsWatchedByPredicate(args,
args.userFactory.create(args.dbProvider, id)));
} }
return new IsWatchedByPredicate(args, args.userFactory.create( }
args.dbProvider, account.getId())); return Predicate.or(p);
} }
@Operator @Operator
public Predicate<ChangeData> draftby(String who) throws QueryParseException, public Predicate<ChangeData> draftby(String who) throws QueryParseException,
OrmException { OrmException {
Account account = args.accountResolver.find(who); Set<Account.Id> m = parseAccount(who);
if (account == null) { List<HasDraftByPredicate> p = Lists.newArrayListWithCapacity(m.size());
throw error("User " + who + " not found"); for (Account.Id id : m) {
p.add(new HasDraftByPredicate(args.dbProvider, id));
} }
return new HasDraftByPredicate(args.dbProvider, account.getId()); return Predicate.or(p);
} }
@Operator @Operator
public Predicate<ChangeData> visibleto(String who) public Predicate<ChangeData> visibleto(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Account account = args.accountResolver.find(who); if ("self".equals(who)) {
if (account != null) { return is_visible();
return visibleto(args.userFactory }
.create(args.dbProvider, account.getId())); Set<Account.Id> m = args.accountResolver.findAll(who);
if (!m.isEmpty()) {
List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
for (Account.Id id : m) {
return visibleto(args.userFactory.create(args.dbProvider, id));
}
return Predicate.or(p);
} }
// If its not an account, maybe its a group? // If its not an account, maybe its a group?
@@ -375,24 +399,17 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> owner(String who) throws QueryParseException, public Predicate<ChangeData> owner(String who) throws QueryParseException,
OrmException { OrmException {
Set<Account.Id> m = args.accountResolver.findAll(who); Set<Account.Id> m = parseAccount(who);
if (m.isEmpty()) { List<OwnerPredicate> p = Lists.newArrayListWithCapacity(m.size());
throw error("User " + who + " not found");
} else if (m.size() == 1) {
Account.Id id = m.iterator().next();
return new OwnerPredicate(args.dbProvider, id);
} else {
List<OwnerPredicate> p = new ArrayList<OwnerPredicate>(m.size());
for (Account.Id id : m) { for (Account.Id id : m) {
p.add(new OwnerPredicate(args.dbProvider, id)); p.add(new OwnerPredicate(args.dbProvider, id));
} }
return Predicate.or(p); return Predicate.or(p);
} }
}
@Operator @Operator
public Predicate<ChangeData> ownerin(String group) throws QueryParseException, public Predicate<ChangeData> ownerin(String group)
OrmException { throws QueryParseException {
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group)); AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
if (g == null) { if (g == null) {
throw error("Group " + group + " not found"); throw error("Group " + group + " not found");
@@ -403,24 +420,17 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
@Operator @Operator
public Predicate<ChangeData> reviewer(String who) public Predicate<ChangeData> reviewer(String who)
throws QueryParseException, OrmException { throws QueryParseException, OrmException {
Set<Account.Id> m = args.accountResolver.findAll(who); Set<Account.Id> m = parseAccount(who);
if (m.isEmpty()) { List<ReviewerPredicate> p = Lists.newArrayListWithCapacity(m.size());
throw error("User " + who + " not found");
} else if (m.size() == 1) {
Account.Id id = m.iterator().next();
return new ReviewerPredicate(args.dbProvider, id);
} else {
List<ReviewerPredicate> p = new ArrayList<ReviewerPredicate>(m.size());
for (Account.Id id : m) { for (Account.Id id : m) {
p.add(new ReviewerPredicate(args.dbProvider, id)); p.add(new ReviewerPredicate(args.dbProvider, id));
} }
return Predicate.or(p); return Predicate.or(p);
} }
}
@Operator @Operator
public Predicate<ChangeData> reviewerin(String group) public Predicate<ChangeData> reviewerin(String group)
throws QueryParseException, OrmException { throws QueryParseException {
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group)); AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
if (g == null) { if (g == null) {
throw error("Group " + group + " not found"); throw error("Group " + group + " not found");
@@ -532,4 +542,23 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
throw error("Unsupported query:" + query); throw error("Unsupported query:" + query);
} }
} }
private Set<Account.Id> parseAccount(String who)
throws QueryParseException, OrmException {
if ("self".equals(who)) {
return Collections.singleton(self());
}
Set<Account.Id> matches = args.accountResolver.findAll(who);
if (matches.isEmpty()) {
throw error("User " + who + " not found");
}
return matches;
}
private Account.Id self() {
if (currentUser instanceof IdentifiedUser) {
return ((IdentifiedUser) currentUser).getAccountId();
}
throw new IllegalArgumentException();
}
} }