Merge changes from topics 'query-refactor', 'kill-sortkey'
* changes: QueryProcessor: Don't double-add visibleto predicate Add query tests for visibleto predicate Terminate sortkey with prejudice Use secondary index for advertising extra haves during push OutputStreamQuery: Optimize formatter allocation Move stream-based QueryProcessor output to its own class Add QueryProcessor methods to search by Predicate Fix limit handling in QueryProcessor QueryChanges: Remove unused reverse field
This commit is contained in:
@@ -116,7 +116,7 @@ Find the 2 most recent open changes in the tools/gerrit project:
|
||||
====
|
||||
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2
|
||||
{"project":"tools/gerrit", ...}
|
||||
{"project":"tools/gerrit", ..., sortKey:"000e6aee00003e26", ...}
|
||||
{"project":"tools/gerrit", ...}
|
||||
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
|
||||
====
|
||||
|
||||
|
||||
@@ -35,8 +35,6 @@ was created.
|
||||
lastUpdated:: Time in seconds since the UNIX epoch when this change
|
||||
was last updated.
|
||||
|
||||
sortKey:: Internal key used to sort changes, based on lastUpdated.
|
||||
|
||||
open:: Boolean indicating if the change is still open for review.
|
||||
|
||||
status:: Current state of this change.
|
||||
|
||||
@@ -1123,7 +1123,6 @@ link:rest-api-changes.html#change-info[ChangeInfo] entities.
|
||||
"created": "2013-02-01 09:59:32.126000000",
|
||||
"updated": "2013-02-21 11:16:36.775000000",
|
||||
"mergeable": true,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
|
||||
@@ -55,7 +55,6 @@ the resulting change.
|
||||
"mergeable": true,
|
||||
"insertions": 0,
|
||||
"deletions": 0,
|
||||
"_sortkey": "002cbc25000004e5",
|
||||
"_number": 4711,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -105,7 +104,6 @@ Query for open changes of watched projects:
|
||||
"mergeable": true,
|
||||
"insertions": 26,
|
||||
"deletions": 10,
|
||||
"_sortkey": "001e7057000006dc",
|
||||
"_number": 1756,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -123,7 +121,6 @@ Query for open changes of watched projects:
|
||||
"mergeable": true,
|
||||
"insertions": 12,
|
||||
"deletions": 18,
|
||||
"_sortkey": "001e7056000006dd",
|
||||
"_number": 1757,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -177,7 +174,6 @@ Query that retrieves changes for a user's dashboard:
|
||||
"mergeable": true,
|
||||
"insertions": 4,
|
||||
"deletions": 7,
|
||||
"_sortkey": "001e7057000006dc",
|
||||
"_number": 1756,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -330,7 +326,6 @@ default. Optional fields are:
|
||||
"mergeable": true,
|
||||
"insertions": 16,
|
||||
"deletions": 7,
|
||||
"_sortkey": "001c9bf400000061",
|
||||
"_number": 97,
|
||||
"owner": {
|
||||
"name": "Shawn Pearce"
|
||||
@@ -467,7 +462,6 @@ describes the change.
|
||||
"mergeable": true,
|
||||
"insertions": 34,
|
||||
"deletions": 101,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -520,7 +514,6 @@ REJECTED > APPROVED > DISLIKED > RECOMMENDED.
|
||||
"mergeable": true,
|
||||
"insertions": 126,
|
||||
"deletions": 11,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"_account_id": 1000096,
|
||||
@@ -764,7 +757,6 @@ describes the abandoned change.
|
||||
"mergeable": true,
|
||||
"insertions": 3,
|
||||
"deletions": 310,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -823,7 +815,6 @@ describes the restored change.
|
||||
"mergeable": true,
|
||||
"insertions": 2,
|
||||
"deletions": 13,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -880,7 +871,6 @@ is included.
|
||||
"mergeable": false,
|
||||
"insertions": 33,
|
||||
"deletions": 9,
|
||||
"_sortkey": "0024cf9a000012bf",
|
||||
"_number": 4799,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -973,7 +963,6 @@ describes the reverting change.
|
||||
"mergeable": true,
|
||||
"insertions": 6,
|
||||
"deletions": 4,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -1035,7 +1024,6 @@ describes the submitted/merged change.
|
||||
"status": "MERGED",
|
||||
"created": "2013-02-01 09:59:32.126000000",
|
||||
"updated": "2013-02-21 11:16:36.775000000",
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -1179,7 +1167,6 @@ missing from the result. At least `id`, `project`, `branch`, and
|
||||
"mergeable": true,
|
||||
"insertions": 34,
|
||||
"deletions": 101,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -1228,7 +1215,6 @@ Only the change owner, a project owner, or an administrator may fix changes.
|
||||
"mergeable": true,
|
||||
"insertions": 34,
|
||||
"deletions": 101,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -1863,7 +1849,6 @@ for the current patch set.
|
||||
"mergeable": true,
|
||||
"insertions": 34,
|
||||
"deletions": 45,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"_account_id": 1000096,
|
||||
@@ -2128,7 +2113,6 @@ is included.
|
||||
"mergeable": false,
|
||||
"insertions": 21,
|
||||
"deletions": 21,
|
||||
"_sortkey": "0024cf9a000012bf",
|
||||
"_number": 4799,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -3096,7 +3080,6 @@ describes the resulting cherry picked change.
|
||||
"mergeable": true,
|
||||
"insertions": 12,
|
||||
"deletions": 11,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -3150,7 +3133,6 @@ describes the change.
|
||||
"mergeable": true,
|
||||
"insertions": 261,
|
||||
"deletions": 101,
|
||||
"_sortkey": "0023412400000f7d",
|
||||
"_number": 3965,
|
||||
"owner": {
|
||||
"name": "John Doe"
|
||||
@@ -3335,7 +3317,6 @@ Not set for merged changes, or if the change has not yet been tested.
|
||||
Number of inserted lines.
|
||||
|`deletions` ||
|
||||
Number of deleted lines.
|
||||
|`_sortkey` ||The sortkey of the change.
|
||||
|`_number` ||The legacy numeric ID of the change.
|
||||
|`owner` ||
|
||||
The owner of the change as an link:rest-api-accounts.html#account-info[
|
||||
@@ -3373,7 +3354,7 @@ Only set if link:#current-revision[the current revision] is requested
|
||||
if link:#all-revisions[all revisions] are requested.
|
||||
|`_more_changes` |optional, not set if `false`|
|
||||
Whether the query would deliver more results if not limited. +
|
||||
Only set on either the last or the first change that is returned.
|
||||
Only set on the last change that is returned.
|
||||
|`problems` |optional|
|
||||
A list of link:#problem-info[ProblemInfo] entities describing potential
|
||||
problems with this change. Only set if link:#check[CHECK] is set.
|
||||
|
||||
@@ -31,7 +31,6 @@ public class ChangeInfo {
|
||||
protected String topic;
|
||||
protected boolean starred;
|
||||
protected Timestamp lastUpdatedOn;
|
||||
protected String sortKey;
|
||||
protected PatchSet.Id patchSetId;
|
||||
protected boolean latest;
|
||||
|
||||
@@ -52,7 +51,6 @@ public class ChangeInfo {
|
||||
branch = c.getDest().getShortName();
|
||||
topic = c.getTopic();
|
||||
lastUpdatedOn = c.getLastUpdatedOn();
|
||||
sortKey = c.getSortKey();
|
||||
patchSetId = patchId;
|
||||
latest = patchSetId == null || patchSetId.equals(c.currentPatchSetId());
|
||||
}
|
||||
@@ -112,8 +110,4 @@ public class ChangeInfo {
|
||||
public java.sql.Timestamp getLastUpdatedOn() {
|
||||
return lastUpdatedOn;
|
||||
}
|
||||
|
||||
public String getSortKey() {
|
||||
return sortKey;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,6 @@ public class ChangeInfo {
|
||||
public Integer insertions;
|
||||
public Integer deletions;
|
||||
|
||||
public String _sortkey;
|
||||
public String baseChange;
|
||||
public int _number;
|
||||
|
||||
|
||||
@@ -96,7 +96,6 @@ public class ChangeInfo extends JavaScriptObject {
|
||||
private final native String updatedRaw() /*-{ return this.updated; }-*/;
|
||||
public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
|
||||
public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
|
||||
public final native String _sortkey() /*-{ return this._sortkey; }-*/;
|
||||
public final native NativeMap<LabelInfo> all_labels() /*-{ return this.labels; }-*/;
|
||||
public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
|
||||
public final native String current_revision() /*-{ return this.current_revision; }-*/;
|
||||
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
package com.google.gerrit.httpd.rpc.change;
|
||||
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
|
||||
import com.google.gerrit.server.query.change.OutputStreamQuery;
|
||||
import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@@ -31,11 +31,11 @@ import javax.servlet.http.HttpServletResponse;
|
||||
@Singleton
|
||||
public class DeprecatedChangeQueryServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final Provider<QueryProcessor> processor;
|
||||
private final Provider<OutputStreamQuery> queryProvider;
|
||||
|
||||
@Inject
|
||||
DeprecatedChangeQueryServlet(Provider<QueryProcessor> processor) {
|
||||
this.processor = processor;
|
||||
DeprecatedChangeQueryServlet(Provider<OutputStreamQuery> queryProvider) {
|
||||
this.queryProvider = queryProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -44,7 +44,7 @@ public class DeprecatedChangeQueryServlet extends HttpServlet {
|
||||
rsp.setContentType("text/json");
|
||||
rsp.setCharacterEncoding("UTF-8");
|
||||
|
||||
QueryProcessor p = processor.get();
|
||||
OutputStreamQuery p = queryProvider.get();
|
||||
OutputFormat format = OutputFormat.JSON;
|
||||
try {
|
||||
format = OutputFormat.valueOf(get(req, "format", format.toString()));
|
||||
|
||||
@@ -433,9 +433,7 @@ public final class Change {
|
||||
@Column(id = 5)
|
||||
protected Timestamp lastUpdatedOn;
|
||||
|
||||
/** A {@link #lastUpdatedOn} ASC,{@link #changeId} ASC for sorting. */
|
||||
@Column(id = 6, length = 16)
|
||||
protected String sortKey;
|
||||
// DELETED: id = 6 (sortkey)
|
||||
|
||||
@Column(id = 7, name = "owner_account_id")
|
||||
protected Account.Id owner;
|
||||
@@ -489,7 +487,6 @@ public final class Change {
|
||||
rowVersion = other.rowVersion;
|
||||
createdOn = other.createdOn;
|
||||
lastUpdatedOn = other.lastUpdatedOn;
|
||||
sortKey = other.sortKey;
|
||||
owner = other.owner;
|
||||
dest = other.dest;
|
||||
open = other.open;
|
||||
@@ -534,14 +531,6 @@ public final class Change {
|
||||
return rowVersion;
|
||||
}
|
||||
|
||||
public String getSortKey() {
|
||||
return sortKey;
|
||||
}
|
||||
|
||||
public void setSortKey(final String newSortKey) {
|
||||
sortKey = newSortKey;
|
||||
}
|
||||
|
||||
public Account.Id getOwner() {
|
||||
return owner;
|
||||
}
|
||||
|
||||
@@ -55,11 +55,6 @@ public interface ChangeAccess extends Access<Change, Change.Id> {
|
||||
@Query("WHERE open = true AND dest = ?")
|
||||
ResultSet<Change> byBranchOpenAll(Branch.NameKey p) throws OrmException;
|
||||
|
||||
@Query("WHERE open = true AND dest.projectName = ? AND sortKey < ?"
|
||||
+ " ORDER BY sortKey DESC LIMIT ?")
|
||||
ResultSet<Change> byProjectOpenNext(Project.NameKey p, String sortKey,
|
||||
int limit) throws OrmException;
|
||||
|
||||
@Query
|
||||
ResultSet<Change> all() throws OrmException;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on);
|
||||
|
||||
-- covers: byProjectOpenAll
|
||||
CREATE INDEX changes_byProjectOpen
|
||||
ON changes (open, dest_project_name, sort_key);
|
||||
ON changes (open, dest_project_name, last_updated_on);
|
||||
|
||||
-- covers: byProject
|
||||
CREATE INDEX changes_byProject
|
||||
|
||||
@@ -83,7 +83,7 @@ ON changes (status, dest_project_name, dest_branch_name, last_updated_on)
|
||||
|
||||
-- covers: byProjectOpenPrev, byProjectOpenNext
|
||||
CREATE INDEX changes_byProjectOpen
|
||||
ON changes (open, dest_project_name, sort_key)
|
||||
ON changes (open, dest_project_name, last_updated_on);
|
||||
#
|
||||
|
||||
-- covers: byProject
|
||||
|
||||
@@ -124,7 +124,7 @@ WHERE status = 's';
|
||||
|
||||
-- covers: byProjectOpenAll
|
||||
CREATE INDEX changes_byProjectOpen
|
||||
ON changes (dest_project_name, sort_key)
|
||||
ON changes (dest_project_name, last_updated_on)
|
||||
WHERE open = 'Y';
|
||||
|
||||
-- covers: byProject
|
||||
|
||||
@@ -15,15 +15,10 @@
|
||||
package com.google.gerrit.server;
|
||||
|
||||
import static com.google.gerrit.server.change.PatchSetInserter.ValidatePolicy.RECEIVE_COMMITS;
|
||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.concurrent.TimeUnit.MINUTES;
|
||||
import static java.util.concurrent.TimeUnit.SECONDS;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.primitives.Ints;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
@@ -85,15 +80,6 @@ import java.util.Map;
|
||||
|
||||
@Singleton
|
||||
public class ChangeUtil {
|
||||
/**
|
||||
* Epoch for sort key calculations, Tue Sep 30 2008 17:00:00.
|
||||
* <p>
|
||||
* We overrun approximately 4,083 years later, so ~6092.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
private static final long SORT_KEY_EPOCH_MINS =
|
||||
MINUTES.convert(1222819200L, SECONDS);
|
||||
|
||||
private static final Object uuidLock = new Object();
|
||||
private static final int SEED = 0x2418e6f9;
|
||||
private static int uuidPrefix;
|
||||
@@ -150,7 +136,6 @@ public class ChangeUtil {
|
||||
|
||||
public static void updated(Change c) {
|
||||
c.setLastUpdatedOn(TimeUtil.nowTs());
|
||||
computeSortKey(c);
|
||||
}
|
||||
|
||||
public static void insertAncestors(ReviewDb db, PatchSet.Id id, RevCommit src)
|
||||
@@ -166,29 +151,6 @@ public class ChangeUtil {
|
||||
db.patchSetAncestors().insert(toInsert);
|
||||
}
|
||||
|
||||
public static String sortKey(long lastUpdatedMs, int id) {
|
||||
long lastUpdatedMins = MINUTES.convert(lastUpdatedMs, MILLISECONDS);
|
||||
long minsSinceEpoch = lastUpdatedMins - SORT_KEY_EPOCH_MINS;
|
||||
StringBuilder r = new StringBuilder(16);
|
||||
r.setLength(16);
|
||||
formatHexInt(r, 0, Ints.checkedCast(minsSinceEpoch));
|
||||
formatHexInt(r, 8, id);
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
public static long parseSortKey(String sortKey) {
|
||||
if ("z".equals(sortKey)) {
|
||||
return Long.MAX_VALUE;
|
||||
}
|
||||
return Long.parseLong(sortKey, 16);
|
||||
}
|
||||
|
||||
public static void computeSortKey(Change c) {
|
||||
long lastUpdatedMs = c.getLastUpdatedOn().getTime();
|
||||
int id = c.getId().get();
|
||||
c.setSortKey(sortKey(lastUpdatedMs, id));
|
||||
}
|
||||
|
||||
public static PatchSet.Id nextPatchSetId(Map<String, Ref> allRefs,
|
||||
PatchSet.Id id) {
|
||||
PatchSet.Id next = nextPatchSetId(id);
|
||||
@@ -585,19 +547,4 @@ public class ChangeUtil {
|
||||
public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
|
||||
return new PatchSet.Id(id.getParentKey(), id.get() + 1);
|
||||
}
|
||||
|
||||
private static final char[] hexchar =
|
||||
{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', //
|
||||
'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
private static void formatHexInt(final StringBuilder dst, final int p, int w) {
|
||||
int o = p + 7;
|
||||
while (o >= p && w != 0) {
|
||||
dst.setCharAt(o--, hexchar[w & 0xf]);
|
||||
w >>>= 4;
|
||||
}
|
||||
while (o >= p) {
|
||||
dst.setCharAt(o--, '0');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,6 @@ public class ChangeInserter {
|
||||
patchSet.setRevision(new RevId(commit.name()));
|
||||
patchSetInfo = patchSetInfoFactory.get(commit, patchSet.getId());
|
||||
change.setCurrentPatchSet(patchSetInfo);
|
||||
ChangeUtil.computeSortKey(change);
|
||||
}
|
||||
|
||||
public Change getChange() {
|
||||
|
||||
@@ -104,6 +104,7 @@ import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
|
||||
import com.google.gerrit.server.query.change.QueryResult;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
@@ -252,10 +253,16 @@ public class ChangeJson {
|
||||
return format(cd, Optional.of(rsrc.getPatchSet().getId()));
|
||||
}
|
||||
|
||||
public List<List<ChangeInfo>> formatList2(List<List<ChangeData>> in)
|
||||
public List<List<ChangeInfo>> formatQueryResults(List<QueryResult> in)
|
||||
throws OrmException {
|
||||
accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
|
||||
Iterable<ChangeData> all = Iterables.concat(in);
|
||||
Iterable<ChangeData> all = FluentIterable.from(in)
|
||||
.transformAndConcat(new Function<QueryResult, List<ChangeData>>() {
|
||||
@Override
|
||||
public List<ChangeData> apply(QueryResult in) {
|
||||
return in.changes();
|
||||
}
|
||||
});
|
||||
ChangeData.ensureChangeLoaded(all);
|
||||
if (has(ALL_REVISIONS)) {
|
||||
ChangeData.ensureAllPatchSetsLoaded(all);
|
||||
@@ -270,8 +277,12 @@ public class ChangeJson {
|
||||
|
||||
List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
|
||||
Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
|
||||
for (List<ChangeData> changes : in) {
|
||||
res.add(toChangeInfo(out, changes, reviewed));
|
||||
for (QueryResult r : in) {
|
||||
List<ChangeInfo> infos = toChangeInfo(out, r.changes(), reviewed);
|
||||
if (r.moreChanges()) {
|
||||
infos.get(infos.size() - 1)._moreChanges = true;
|
||||
}
|
||||
res.add(infos);
|
||||
}
|
||||
accountLoader.fill();
|
||||
return res;
|
||||
@@ -368,7 +379,6 @@ public class ChangeJson {
|
||||
out.created = in.getCreatedOn();
|
||||
out.updated = in.getLastUpdatedOn();
|
||||
out._number = in.getId().get();
|
||||
out._sortkey = in.getSortKey();
|
||||
out.starred = userProvider.get().getStarredChanges().contains(in.getId())
|
||||
? true
|
||||
: null;
|
||||
|
||||
@@ -47,7 +47,6 @@ import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ApprovalsUtil;
|
||||
import com.google.gerrit.server.ChangeMessagesUtil;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.ProjectUtil;
|
||||
@@ -271,7 +270,6 @@ public class Submit implements RestModifyView<RevisionResource, SubmitInput>,
|
||||
if (change.getStatus().isOpen()) {
|
||||
change.setStatus(Change.Status.SUBMITTED);
|
||||
change.setLastUpdatedOn(timestamp);
|
||||
ChangeUtil.computeSortKey(change);
|
||||
return change;
|
||||
}
|
||||
return null;
|
||||
|
||||
@@ -31,7 +31,6 @@ public class ChangeAttribute {
|
||||
|
||||
public Long createdOn;
|
||||
public Long lastUpdated;
|
||||
public String sortKey;
|
||||
public Boolean open;
|
||||
public Change.Status status;
|
||||
public List<MessageAttribute> comments;
|
||||
|
||||
@@ -18,5 +18,5 @@ public class QueryStatsAttribute {
|
||||
public final String type = "stats";
|
||||
public int rowCount;
|
||||
public long runTimeMilliseconds;
|
||||
public String resumeSortKey;
|
||||
public boolean moreChanges;
|
||||
}
|
||||
|
||||
@@ -164,7 +164,6 @@ public class EventFactory {
|
||||
public void extend(ChangeAttribute a, Change change) {
|
||||
a.createdOn = change.getCreatedOn().getTime() / 1000L;
|
||||
a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
|
||||
a.sortKey = change.getSortKey();
|
||||
a.open = change.getStatus().isOpen();
|
||||
}
|
||||
|
||||
|
||||
@@ -111,6 +111,7 @@ import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.server.project.ProjectState;
|
||||
import com.google.gerrit.server.project.RefControl;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.server.ssh.SshInfo;
|
||||
import com.google.gerrit.server.util.LabelVote;
|
||||
import com.google.gerrit.server.util.MagicBranch;
|
||||
@@ -336,6 +337,7 @@ public class ReceiveCommits {
|
||||
|
||||
@Inject
|
||||
ReceiveCommits(final ReviewDb db,
|
||||
final Provider<QueryProcessor> queryProcessor,
|
||||
final SchemaFactory<ReviewDb> schemaFactory,
|
||||
final ChangeData.Factory changeDataFactory,
|
||||
final ChangeUpdate.Factory updateFactory,
|
||||
@@ -472,7 +474,7 @@ public class ReceiveCommits {
|
||||
});
|
||||
advHooks.add(rp.getAdvertiseRefsHook());
|
||||
advHooks.add(new ReceiveCommitsAdvertiseRefsHook(
|
||||
db, projectControl.getProject().getNameKey()));
|
||||
db, queryProcessor, projectControl.getProject().getNameKey()));
|
||||
rp.setAdvertiseRefsHook(AdvertiseRefsHookChain.newChain(advHooks));
|
||||
}
|
||||
|
||||
|
||||
@@ -18,13 +18,18 @@ import static org.eclipse.jgit.lib.RefDatabase.ALL;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.server.util.MagicBranch;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
@@ -39,6 +44,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -48,11 +54,14 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
||||
.getLogger(ReceiveCommitsAdvertiseRefsHook.class);
|
||||
|
||||
private final ReviewDb db;
|
||||
private final Provider<QueryProcessor> queryProcessor;
|
||||
private final Project.NameKey projectName;
|
||||
|
||||
public ReceiveCommitsAdvertiseRefsHook(ReviewDb db,
|
||||
Provider<QueryProcessor> queryProcessor,
|
||||
Project.NameKey projectName) {
|
||||
this.db = db;
|
||||
this.queryProcessor = queryProcessor;
|
||||
this.projectName = projectName;
|
||||
}
|
||||
|
||||
@@ -93,10 +102,11 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
||||
Set<ObjectId> toInclude = Sets.newHashSet();
|
||||
|
||||
// Advertise some recent open changes, in case a commit is based one.
|
||||
final int limit = 32;
|
||||
try {
|
||||
Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(32);
|
||||
for (Change c : db.changes().byProjectOpenNext(projectName, "z", 32)) {
|
||||
PatchSet.Id id = c.currentPatchSetId();
|
||||
Set<PatchSet.Id> toGet = Sets.newHashSetWithExpectedSize(limit);
|
||||
for (ChangeData cd : queryRecentChanges(limit)) {
|
||||
PatchSet.Id id = cd.change().currentPatchSetId();
|
||||
if (id != null) {
|
||||
toGet.add(id);
|
||||
}
|
||||
@@ -164,6 +174,20 @@ public class ReceiveCommitsAdvertiseRefsHook implements AdvertiseRefsHook {
|
||||
return toInclude;
|
||||
}
|
||||
|
||||
private List<ChangeData> queryRecentChanges(int limit)
|
||||
throws OrmException {
|
||||
QueryProcessor qp = queryProcessor.get();
|
||||
qp.setLimit(limit);
|
||||
ChangeQueryBuilder qb = qp.getQueryBuilder();
|
||||
Predicate<ChangeData> p =
|
||||
Predicate.and(qb.project(projectName.get()), qb.status_open());
|
||||
try {
|
||||
return qp.queryChanges(p).changes();
|
||||
} catch (QueryParseException e) {
|
||||
throw new OrmException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean skip(String name) {
|
||||
return name.startsWith(RefNames.REFS_CHANGES)
|
||||
|| name.startsWith(RefNames.REFS_CACHE_AUTOMERGE)
|
||||
|
||||
@@ -14,9 +14,8 @@
|
||||
|
||||
package com.google.gerrit.server.index;
|
||||
|
||||
import static com.google.gerrit.common.data.GlobalCapability.DEFAULT_MAX_QUERY_LIMIT;
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
@@ -29,9 +28,9 @@ import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gerrit.server.query.change.AndSource;
|
||||
import com.google.gerrit.server.query.change.BasicChangeRewrites;
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryBuilder;
|
||||
import com.google.gerrit.server.query.change.ChangeQueryRewriter;
|
||||
import com.google.gerrit.server.query.change.ChangeStatusPredicate;
|
||||
import com.google.gerrit.server.query.change.LimitPredicate;
|
||||
import com.google.gerrit.server.query.change.OrSource;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
@@ -129,16 +128,14 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
|
||||
throws QueryParseException {
|
||||
public Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start,
|
||||
int limit) throws QueryParseException {
|
||||
checkArgument(limit > 0, "limit must be positive: %s", limit);
|
||||
ChangeIndex index = indexes.getSearchIndex();
|
||||
in = basicRewrites.rewrite(in);
|
||||
int limit = MoreObjects.firstNonNull(
|
||||
ChangeQueryBuilder.getLimit(in), DEFAULT_MAX_QUERY_LIMIT);
|
||||
// Increase the limit rather than skipping, since we don't know how many
|
||||
// skipped results would have been filtered out by the enclosing AndSource.
|
||||
limit += start;
|
||||
limit = Math.max(limit, 1);
|
||||
|
||||
Predicate<ChangeData> out = rewriteImpl(in, index, limit);
|
||||
if (in == out || out instanceof IndexPredicate) {
|
||||
@@ -168,6 +165,9 @@ public class IndexRewriteImpl implements ChangeQueryRewriter {
|
||||
ChangeIndex index, int limit) throws QueryParseException {
|
||||
if (isIndexPredicate(in, index)) {
|
||||
return in;
|
||||
} else if (in instanceof LimitPredicate) {
|
||||
// Replace any limits with the limit provided by the caller.
|
||||
return new LimitPredicate(limit);
|
||||
} else if (!isRewritePossible(in)) {
|
||||
return null; // magic to indicate "in" cannot be rewritten
|
||||
}
|
||||
|
||||
@@ -138,7 +138,8 @@ public abstract class QueryBuilder<T> {
|
||||
* @param p the predicate to find.
|
||||
* @param clazz type of the predicate instance.
|
||||
* @param name name of the operator.
|
||||
* @return the predicate, null if not found.
|
||||
* @return the first instance of a predicate having the given type, as found
|
||||
* by a depth-first search.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T, P extends OperatorPredicate<T>> P find(Predicate<T> p,
|
||||
|
||||
@@ -16,13 +16,11 @@ package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.query.IntPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryRewriter;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.OutOfScopeException;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||
private static final ChangeQueryBuilder BUILDER = new ChangeQueryBuilder(
|
||||
@@ -69,14 +67,6 @@ public class BasicChangeRewrites extends QueryRewriter<ChangeData> {
|
||||
ChangeStatusPredicate.forStatus(Change.Status.MERGED));
|
||||
}
|
||||
|
||||
@NoCostComputation
|
||||
@Rewrite("A=(limit:*) B=(limit:*)")
|
||||
public Predicate<ChangeData> r00_smallestLimit(
|
||||
@Named("A") IntPredicate<ChangeData> a,
|
||||
@Named("B") IntPredicate<ChangeData> b) {
|
||||
return a.intValue() <= b.intValue() ? a : b;
|
||||
}
|
||||
|
||||
private static final class InvalidProvider<T> implements Provider<T> {
|
||||
@Override
|
||||
public T get() {
|
||||
|
||||
@@ -45,7 +45,6 @@ import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.ListChildProjects;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.query.IntPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryBuilder;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
@@ -121,12 +120,6 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
private static final QueryBuilder.Definition<ChangeData, ChangeQueryBuilder> mydef =
|
||||
new QueryBuilder.Definition<>(ChangeQueryBuilder.class);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Integer getLimit(Predicate<ChangeData> p) {
|
||||
IntPredicate<?> ip = find(p, IntPredicate.class, FIELD_LIMIT);
|
||||
return ip != null ? ip.intValue() : null;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static class Arguments {
|
||||
final Provider<ReviewDb> db;
|
||||
@@ -633,28 +626,8 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> limit(String limit) {
|
||||
return limit(Integer.parseInt(limit));
|
||||
}
|
||||
|
||||
static class LimitPredicate extends IntPredicate<ChangeData> {
|
||||
LimitPredicate(int limit) {
|
||||
super(FIELD_LIMIT, limit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public Predicate<ChangeData> limit(int limit) {
|
||||
return new LimitPredicate(limit);
|
||||
public Predicate<ChangeData> limit(String limit) throws QueryParseException {
|
||||
return new LimitPredicate(Integer.parseInt(limit));
|
||||
}
|
||||
|
||||
@Operator
|
||||
|
||||
@@ -18,6 +18,6 @@ import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
|
||||
public interface ChangeQueryRewriter {
|
||||
Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start)
|
||||
Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int start, int limit)
|
||||
throws QueryParseException;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// 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 static com.google.gerrit.server.query.change.ChangeQueryBuilder.FIELD_LIMIT;
|
||||
|
||||
import com.google.gerrit.server.query.IntPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryBuilder;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
|
||||
public class LimitPredicate extends IntPredicate<ChangeData> {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Integer getLimit(Predicate<ChangeData> p) {
|
||||
IntPredicate<?> ip = QueryBuilder.find(p, IntPredicate.class, FIELD_LIMIT);
|
||||
return ip != null ? ip.intValue() : null;
|
||||
}
|
||||
|
||||
public LimitPredicate(int limit) throws QueryParseException {
|
||||
super(ChangeQueryBuilder.FIELD_LIMIT, limit);
|
||||
if (limit <= 0) {
|
||||
throw new QueryParseException("limit must be positive: " + limit);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean match(ChangeData object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCost() {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,405 @@
|
||||
// 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.common.TimeUtil;
|
||||
import com.google.gerrit.common.data.LabelTypes;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gerrit.server.data.ChangeAttribute;
|
||||
import com.google.gerrit.server.data.PatchSetAttribute;
|
||||
import com.google.gerrit.server.data.QueryStatsAttribute;
|
||||
import com.google.gerrit.server.events.EventFactory;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.util.io.DisabledOutputStream;
|
||||
import org.joda.time.format.DateTimeFormat;
|
||||
import org.joda.time.format.DateTimeFormatter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Change query implementation that outputs to a stream in the style of an SSH
|
||||
* command.
|
||||
*/
|
||||
public class OutputStreamQuery {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(OutputStreamQuery.class);
|
||||
|
||||
private static final DateTimeFormatter dtf =
|
||||
DateTimeFormat.forPattern("yyyy-MM-dd HH:mm:ss zzz");
|
||||
|
||||
public static enum OutputFormat {
|
||||
TEXT, JSON
|
||||
}
|
||||
|
||||
private final QueryProcessor queryProcessor;
|
||||
private final EventFactory eventFactory;
|
||||
private final TrackingFooters trackingFooters;
|
||||
private final CurrentUser user;
|
||||
|
||||
private OutputFormat outputFormat = OutputFormat.TEXT;
|
||||
private boolean includePatchSets;
|
||||
private boolean includeCurrentPatchSet;
|
||||
private boolean includeApprovals;
|
||||
private boolean includeComments;
|
||||
private boolean includeFiles;
|
||||
private boolean includeCommitMessage;
|
||||
private boolean includeDependencies;
|
||||
private boolean includeSubmitRecords;
|
||||
private boolean includeAllReviewers;
|
||||
|
||||
private OutputStream outputStream = DisabledOutputStream.INSTANCE;
|
||||
private PrintWriter out;
|
||||
|
||||
@Inject
|
||||
OutputStreamQuery(QueryProcessor queryProcessor,
|
||||
EventFactory eventFactory,
|
||||
TrackingFooters trackingFooters,
|
||||
CurrentUser user) {
|
||||
this.queryProcessor = queryProcessor;
|
||||
this.eventFactory = eventFactory;
|
||||
this.trackingFooters = trackingFooters;
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
void setLimit(int n) {
|
||||
queryProcessor.setLimit(n);
|
||||
}
|
||||
|
||||
public void setStart(int n) {
|
||||
queryProcessor.setStart(n);
|
||||
}
|
||||
|
||||
public void setIncludePatchSets(boolean on) {
|
||||
includePatchSets = on;
|
||||
}
|
||||
|
||||
public boolean getIncludePatchSets() {
|
||||
return includePatchSets;
|
||||
}
|
||||
|
||||
public void setIncludeCurrentPatchSet(boolean on) {
|
||||
includeCurrentPatchSet = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeCurrentPatchSet() {
|
||||
return includeCurrentPatchSet;
|
||||
}
|
||||
|
||||
public void setIncludeApprovals(boolean on) {
|
||||
includeApprovals = on;
|
||||
}
|
||||
|
||||
public void setIncludeComments(boolean on) {
|
||||
includeComments = on;
|
||||
}
|
||||
|
||||
public void setIncludeFiles(boolean on) {
|
||||
includeFiles = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeFiles() {
|
||||
return includeFiles;
|
||||
}
|
||||
|
||||
public void setIncludeDependencies(boolean on) {
|
||||
includeDependencies = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeDependencies() {
|
||||
return includeDependencies;
|
||||
}
|
||||
|
||||
public void setIncludeCommitMessage(boolean on) {
|
||||
includeCommitMessage = on;
|
||||
}
|
||||
|
||||
public void setIncludeSubmitRecords(boolean on) {
|
||||
includeSubmitRecords = on;
|
||||
}
|
||||
|
||||
public void setIncludeAllReviewers(boolean on) {
|
||||
includeAllReviewers = on;
|
||||
}
|
||||
|
||||
public void setOutput(OutputStream out, OutputFormat fmt) {
|
||||
this.outputStream = out;
|
||||
this.outputFormat = fmt;
|
||||
}
|
||||
|
||||
public void query(String queryString) throws IOException {
|
||||
out = new PrintWriter( //
|
||||
new BufferedWriter( //
|
||||
new OutputStreamWriter(outputStream, "UTF-8")));
|
||||
try {
|
||||
if (queryProcessor.isDisabled()) {
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "query disabled";
|
||||
show(m);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final QueryStatsAttribute stats = new QueryStatsAttribute();
|
||||
stats.runTimeMilliseconds = TimeUtil.nowMs();
|
||||
|
||||
QueryResult results = queryProcessor.queryByString(queryString);
|
||||
ChangeAttribute c = null;
|
||||
for (ChangeData d : results.changes()) {
|
||||
ChangeControl cc = d.changeControl().forUser(user);
|
||||
|
||||
LabelTypes labelTypes = cc.getLabelTypes();
|
||||
c = eventFactory.asChangeAttribute(d.change());
|
||||
eventFactory.extend(c, d.change());
|
||||
|
||||
if (!trackingFooters.isEmpty()) {
|
||||
eventFactory.addTrackingIds(c,
|
||||
trackingFooters.extract(d.commitFooters()));
|
||||
}
|
||||
|
||||
if (includeAllReviewers) {
|
||||
eventFactory.addAllReviewers(c, d.notes());
|
||||
}
|
||||
|
||||
if (includeSubmitRecords) {
|
||||
eventFactory.addSubmitRecords(c, new SubmitRuleEvaluator(d)
|
||||
.setAllowClosed(true)
|
||||
.setAllowDraft(true)
|
||||
.canSubmit());
|
||||
}
|
||||
|
||||
if (includeCommitMessage) {
|
||||
eventFactory.addCommitMessage(c, d.commitMessage());
|
||||
}
|
||||
|
||||
if (includePatchSets) {
|
||||
if (includeFiles) {
|
||||
eventFactory.addPatchSets(c, d.patches(),
|
||||
includeApprovals ? d.approvals().asMap() : null,
|
||||
includeFiles, d.change(), labelTypes);
|
||||
} else {
|
||||
eventFactory.addPatchSets(c, d.patches(),
|
||||
includeApprovals ? d.approvals().asMap() : null,
|
||||
labelTypes);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeCurrentPatchSet) {
|
||||
PatchSet current = d.currentPatchSet();
|
||||
if (current != null) {
|
||||
c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
|
||||
eventFactory.addApprovals(c.currentPatchSet,
|
||||
d.currentApprovals(), labelTypes);
|
||||
|
||||
if (includeFiles) {
|
||||
eventFactory.addPatchSetFileNames(c.currentPatchSet,
|
||||
d.change(), d.currentPatchSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeComments) {
|
||||
eventFactory.addComments(c, d.messages());
|
||||
if (includePatchSets) {
|
||||
for (PatchSetAttribute attribute : c.patchSets) {
|
||||
eventFactory.addPatchSetComments(attribute, d.publishedComments());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeDependencies) {
|
||||
eventFactory.addDependencies(c, d.change());
|
||||
}
|
||||
|
||||
show(c);
|
||||
}
|
||||
|
||||
stats.rowCount = results.changes().size();
|
||||
stats.moreChanges = results.moreChanges();
|
||||
stats.runTimeMilliseconds =
|
||||
TimeUtil.nowMs() - stats.runTimeMilliseconds;
|
||||
show(stats);
|
||||
} catch (OrmException err) {
|
||||
log.error("Cannot execute query: " + queryString, err);
|
||||
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "cannot query database";
|
||||
show(m);
|
||||
|
||||
} catch (QueryParseException e) {
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = e.getMessage();
|
||||
show(m);
|
||||
} catch (NoSuchChangeException e) {
|
||||
log.error("Missing change: " + e.getMessage(), e);
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "missing change " + e.getMessage();
|
||||
show(m);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void show(Object data) {
|
||||
switch (outputFormat) {
|
||||
default:
|
||||
case TEXT:
|
||||
if (data instanceof ChangeAttribute) {
|
||||
out.print("change ");
|
||||
out.print(((ChangeAttribute) data).id);
|
||||
out.print("\n");
|
||||
showText(data, 1);
|
||||
} else {
|
||||
showText(data, 0);
|
||||
}
|
||||
out.print('\n');
|
||||
break;
|
||||
|
||||
case JSON:
|
||||
out.print(new Gson().toJson(data));
|
||||
out.print('\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void showText(Object data, int depth) {
|
||||
for (Field f : fieldsOf(data.getClass())) {
|
||||
Object val;
|
||||
try {
|
||||
val = f.get(data);
|
||||
} catch (IllegalArgumentException err) {
|
||||
continue;
|
||||
} catch (IllegalAccessException err) {
|
||||
continue;
|
||||
}
|
||||
if (val == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
showField(f.getName(), val, depth);
|
||||
}
|
||||
}
|
||||
|
||||
private String indent(int spaces) {
|
||||
if (spaces == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format("%" + spaces + "s", " ");
|
||||
}
|
||||
}
|
||||
|
||||
private void showField(String field, Object value, int depth) {
|
||||
final int spacesDepthRatio = 2;
|
||||
String indent = indent(depth * spacesDepthRatio);
|
||||
out.print(indent);
|
||||
out.print(field);
|
||||
out.print(':');
|
||||
if (value instanceof String && ((String) value).contains("\n")) {
|
||||
out.print(' ');
|
||||
// Idention for multi-line text is
|
||||
// current depth indetion + length of field + length of ": "
|
||||
indent = indent(indent.length() + field.length() + spacesDepthRatio);
|
||||
out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
|
||||
out.print('\n');
|
||||
} else if (value instanceof Long && isDateField(field)) {
|
||||
out.print(' ');
|
||||
out.print(dtf.print(((Long) value) * 1000L));
|
||||
out.print('\n');
|
||||
} else if (isPrimitive(value)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
} else if (value instanceof Collection) {
|
||||
out.print('\n');
|
||||
boolean firstElement = true;
|
||||
for (Object thing : ((Collection<?>) value)) {
|
||||
// The name of the collection was initially printed at the beginning
|
||||
// of this routine. Beginning at the second sub-element, reprint
|
||||
// the collection name so humans can separate individual elements
|
||||
// with less strain and error.
|
||||
//
|
||||
if (firstElement) {
|
||||
firstElement = false;
|
||||
} else {
|
||||
out.print(indent);
|
||||
out.print(field);
|
||||
out.print(":\n");
|
||||
}
|
||||
if (isPrimitive(thing)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
} else {
|
||||
showText(thing, depth + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.print('\n');
|
||||
showText(value, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(Object value) {
|
||||
return value instanceof String //
|
||||
|| value instanceof Number //
|
||||
|| value instanceof Boolean //
|
||||
|| value instanceof Enum;
|
||||
}
|
||||
|
||||
private static boolean isDateField(String name) {
|
||||
return "lastUpdated".equals(name) //
|
||||
|| "grantedOn".equals(name) //
|
||||
|| "timestamp".equals(name) //
|
||||
|| "createdOn".equals(name);
|
||||
}
|
||||
|
||||
private List<Field> fieldsOf(Class<?> type) {
|
||||
List<Field> r = new ArrayList<>();
|
||||
if (type.getSuperclass() != null) {
|
||||
r.addAll(fieldsOf(type.getSuperclass()));
|
||||
}
|
||||
r.addAll(Arrays.asList(type.getDeclaredFields()));
|
||||
return r;
|
||||
}
|
||||
|
||||
static class ErrorMessage {
|
||||
public final String type = "error";
|
||||
public String message;
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,6 @@ import com.google.inject.Provider;
|
||||
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.BitSet;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
@@ -42,7 +41,6 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
|
||||
private final ChangeJson json;
|
||||
private final QueryProcessor imp;
|
||||
private final Provider<CurrentUser> user;
|
||||
private boolean reverse;
|
||||
private EnumSet<ListChangesOption> options;
|
||||
|
||||
@Option(name = "--query", aliases = {"-q"}, metaVar = "QUERY", usage = "Query string")
|
||||
@@ -137,32 +135,15 @@ public class QueryChanges implements RestReadView<TopLevelResource> {
|
||||
private List<List<ChangeInfo>> query0() throws OrmException,
|
||||
QueryParseException {
|
||||
int cnt = queries.size();
|
||||
BitSet more = new BitSet(cnt);
|
||||
List<List<ChangeData>> data = imp.queryChanges(queries);
|
||||
for (int n = 0; n < cnt; n++) {
|
||||
List<ChangeData> changes = data.get(n);
|
||||
if (imp.getLimit() > 0 && changes.size() > imp.getLimit()) {
|
||||
if (reverse) {
|
||||
changes = changes.subList(1, changes.size());
|
||||
} else {
|
||||
changes = changes.subList(0, imp.getLimit());
|
||||
}
|
||||
data.set(n, changes);
|
||||
more.set(n, true);
|
||||
}
|
||||
}
|
||||
|
||||
List<List<ChangeInfo>> res = json.addOptions(options).formatList2(data);
|
||||
List<QueryResult> results = imp.queryByStrings(queries);
|
||||
List<List<ChangeInfo>> res = json.addOptions(options)
|
||||
.formatQueryResults(results);
|
||||
for (int n = 0; n < cnt; n++) {
|
||||
List<ChangeInfo> info = res.get(n);
|
||||
if (more.get(n) && !info.isEmpty()) {
|
||||
if (reverse) {
|
||||
info.get(0)._moreChanges = true;
|
||||
} else {
|
||||
if (results.get(n).moreChanges()) {
|
||||
info.get(info.size() - 1)._moreChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,494 +14,178 @@
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.common.base.MoreObjects;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.common.TimeUtil;
|
||||
import com.google.common.collect.Ordering;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.LabelTypes;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.config.TrackingFooters;
|
||||
import com.google.gerrit.server.data.ChangeAttribute;
|
||||
import com.google.gerrit.server.data.PatchSetAttribute;
|
||||
import com.google.gerrit.server.data.QueryStatsAttribute;
|
||||
import com.google.gerrit.server.events.EventFactory;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.SubmitRuleEvaluator;
|
||||
import com.google.gerrit.server.index.IndexPredicate;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.gwtorm.server.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.util.io.DisabledOutputStream;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.io.PrintWriter;
|
||||
import java.lang.reflect.Field;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
public class QueryProcessor {
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(QueryProcessor.class);
|
||||
|
||||
public static enum OutputFormat {
|
||||
TEXT, JSON
|
||||
}
|
||||
|
||||
private final Gson gson = new Gson();
|
||||
private final SimpleDateFormat sdf =
|
||||
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
|
||||
|
||||
private final EventFactory eventFactory;
|
||||
private final ChangeQueryBuilder queryBuilder;
|
||||
private final ChangeQueryRewriter queryRewriter;
|
||||
private final TrackingFooters trackingFooters;
|
||||
private final CurrentUser user;
|
||||
private final int maxLimit;
|
||||
private final int permittedLimit;
|
||||
|
||||
private OutputFormat outputFormat = OutputFormat.TEXT;
|
||||
private int limit;
|
||||
private int limitFromCaller;
|
||||
private int start;
|
||||
private boolean includePatchSets;
|
||||
private boolean includeCurrentPatchSet;
|
||||
private boolean includeApprovals;
|
||||
private boolean includeComments;
|
||||
private boolean includeFiles;
|
||||
private boolean includeCommitMessage;
|
||||
private boolean includeDependencies;
|
||||
private boolean includeSubmitRecords;
|
||||
private boolean includeAllReviewers;
|
||||
|
||||
private OutputStream outputStream = DisabledOutputStream.INSTANCE;
|
||||
private PrintWriter out;
|
||||
private boolean moreResults;
|
||||
|
||||
@Inject
|
||||
QueryProcessor(EventFactory eventFactory,
|
||||
ChangeQueryBuilder.Factory queryBuilder, CurrentUser currentUser,
|
||||
ChangeQueryRewriter queryRewriter,
|
||||
TrackingFooters trackingFooters) {
|
||||
this.eventFactory = eventFactory;
|
||||
QueryProcessor(ChangeQueryBuilder.Factory queryBuilder,
|
||||
CurrentUser currentUser,
|
||||
ChangeQueryRewriter queryRewriter) {
|
||||
this.queryBuilder = queryBuilder.create(currentUser);
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.trackingFooters = trackingFooters;
|
||||
this.user = currentUser;
|
||||
this.maxLimit = currentUser.getCapabilities()
|
||||
this.permittedLimit = currentUser.getCapabilities()
|
||||
.getRange(GlobalCapability.QUERY_LIMIT)
|
||||
.getMax();
|
||||
this.moreResults = false;
|
||||
}
|
||||
|
||||
int getLimit() {
|
||||
return limit;
|
||||
public ChangeQueryBuilder getQueryBuilder() {
|
||||
return queryBuilder;
|
||||
}
|
||||
|
||||
void setLimit(int n) {
|
||||
limit = n;
|
||||
public void setLimit(int n) {
|
||||
limitFromCaller = n;
|
||||
}
|
||||
|
||||
public void setStart(int n) {
|
||||
start = n;
|
||||
}
|
||||
|
||||
public void setIncludePatchSets(boolean on) {
|
||||
includePatchSets = on;
|
||||
}
|
||||
|
||||
public boolean getIncludePatchSets() {
|
||||
return includePatchSets;
|
||||
}
|
||||
|
||||
public void setIncludeCurrentPatchSet(boolean on) {
|
||||
includeCurrentPatchSet = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeCurrentPatchSet() {
|
||||
return includeCurrentPatchSet;
|
||||
}
|
||||
|
||||
public void setIncludeApprovals(boolean on) {
|
||||
includeApprovals = on;
|
||||
}
|
||||
|
||||
public void setIncludeComments(boolean on) {
|
||||
includeComments = on;
|
||||
}
|
||||
|
||||
public void setIncludeFiles(boolean on) {
|
||||
includeFiles = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeFiles() {
|
||||
return includeFiles;
|
||||
}
|
||||
|
||||
public void setIncludeDependencies(boolean on) {
|
||||
includeDependencies = on;
|
||||
}
|
||||
|
||||
public boolean getIncludeDependencies() {
|
||||
return includeDependencies;
|
||||
}
|
||||
|
||||
public void setIncludeCommitMessage(boolean on) {
|
||||
includeCommitMessage = on;
|
||||
}
|
||||
|
||||
public void setIncludeSubmitRecords(boolean on) {
|
||||
includeSubmitRecords = on;
|
||||
}
|
||||
|
||||
public void setIncludeAllReviewers(boolean on) {
|
||||
includeAllReviewers = on;
|
||||
}
|
||||
|
||||
public void setOutput(OutputStream out, OutputFormat fmt) {
|
||||
this.outputStream = out;
|
||||
this.outputFormat = fmt;
|
||||
/**
|
||||
* Query for changes that match the query string.
|
||||
*
|
||||
* @see #queryChanges(List)
|
||||
* @param queryString the query string to parse.
|
||||
* @return results of the query.
|
||||
*/
|
||||
public QueryResult queryByString(String queryString)
|
||||
throws OrmException, QueryParseException {
|
||||
return queryByStrings(ImmutableList.of(queryString)).get(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for changes that match the query string.
|
||||
* Perform multiple queries over a list of query strings.
|
||||
*
|
||||
* @see #queryChanges(List)
|
||||
* @param queryStrings the query strings to parse.
|
||||
* @return results of the queries, one list per input query.
|
||||
*/
|
||||
public List<QueryResult> queryByStrings(List<String> queryStrings)
|
||||
throws OrmException, QueryParseException {
|
||||
List<Predicate<ChangeData>> queries = new ArrayList<>(queryStrings.size());
|
||||
for (String qs : queryStrings) {
|
||||
queries.add(queryBuilder.parse(qs));
|
||||
}
|
||||
return queryChanges(queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for changes that match a structured query.
|
||||
*
|
||||
* @see #queryChanges(List)
|
||||
* @param query the query.
|
||||
* @return results of the query.
|
||||
*/
|
||||
public QueryResult queryChanges(Predicate<ChangeData> query)
|
||||
throws OrmException, QueryParseException {
|
||||
return queryChanges(ImmutableList.of(query)).get(0);
|
||||
}
|
||||
|
||||
/*
|
||||
* Perform multiple queries over a list of query strings.
|
||||
* <p>
|
||||
* If a limit was specified using {@link #setLimit(int)} this method may
|
||||
* return up to {@code limit + 1} results, allowing the caller to determine if
|
||||
* there are more than {@code limit} matches and suggest to its own caller
|
||||
* that the query could be retried with {@link #setStart(int)}.
|
||||
*
|
||||
* @param queries the queries.
|
||||
* @return results of the queries, one list per input query.
|
||||
*/
|
||||
public List<ChangeData> queryChanges(String queryString)
|
||||
public List<QueryResult> queryChanges(List<Predicate<ChangeData>> queries)
|
||||
throws OrmException, QueryParseException {
|
||||
return queryChanges(ImmutableList.of(queryString)).get(0);
|
||||
return queryChanges(null, queries);
|
||||
}
|
||||
|
||||
/**
|
||||
* Query for changes that match the query string.
|
||||
* <p>
|
||||
* If a limit was specified using {@link #setLimit(int)} this method may
|
||||
* return up to {@code limit + 1} results, allowing the caller to determine if
|
||||
* there are more than {@code limit} matches and suggest to its own caller
|
||||
* that the query could be retried with {@link #setStart(int)}.
|
||||
*/
|
||||
public List<List<ChangeData>> queryChanges(List<String> queries)
|
||||
static {
|
||||
// In addition to this assumption, this queryChanges assumes the basic
|
||||
// rewrites do not touch visibleto predicates either.
|
||||
checkState(
|
||||
!IsVisibleToPredicate.class.isAssignableFrom(IndexPredicate.class),
|
||||
"QueryProcessor assumes visibleto is not used by the index rewriter.");
|
||||
}
|
||||
|
||||
private List<QueryResult> queryChanges(List<String> queryStrings,
|
||||
List<Predicate<ChangeData>> queries)
|
||||
throws OrmException, QueryParseException {
|
||||
final Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
|
||||
Predicate<ChangeData> visibleToMe = queryBuilder.is_visible();
|
||||
int cnt = queries.size();
|
||||
|
||||
// Parse and rewrite all queries.
|
||||
List<Integer> limits = Lists.newArrayListWithCapacity(cnt);
|
||||
List<ChangeDataSource> sources = Lists.newArrayListWithCapacity(cnt);
|
||||
for (String query : queries) {
|
||||
Predicate<ChangeData> q = parseQuery(query, visibleToMe);
|
||||
Predicate<ChangeData> s = queryRewriter.rewrite(q, start);
|
||||
List<Integer> limits = new ArrayList<>(cnt);
|
||||
List<Predicate<ChangeData>> predicates = new ArrayList<>(cnt);
|
||||
List<ChangeDataSource> sources = new ArrayList<>(cnt);
|
||||
for (Predicate<ChangeData> q : queries) {
|
||||
int limit = getEffectiveLimit(q);
|
||||
limits.add(limit);
|
||||
|
||||
// Always bump limit by 1, even if this results in exceeding the permitted
|
||||
// max for this user. The only way to see if there are more changes is to
|
||||
// ask for one more result from the query.
|
||||
Predicate<ChangeData> s = queryRewriter.rewrite(q, start, limit + 1);
|
||||
if (!(s instanceof ChangeDataSource)) {
|
||||
q = Predicate.and(queryBuilder.status_open(), q);
|
||||
s = queryRewriter.rewrite(q, start);
|
||||
s = queryRewriter.rewrite(q, start, limit);
|
||||
}
|
||||
if (!(s instanceof ChangeDataSource)) {
|
||||
throw new QueryParseException("invalid query: " + s);
|
||||
}
|
||||
|
||||
// Don't trust QueryRewriter to have left the visible predicate.
|
||||
AndSource a = new AndSource(ImmutableList.of(s, visibleToMe), start);
|
||||
limits.add(limit(q));
|
||||
predicates.add(a);
|
||||
sources.add(a);
|
||||
}
|
||||
|
||||
// Run each query asynchronously, if supported.
|
||||
List<ResultSet<ChangeData>> matches = Lists.newArrayListWithCapacity(cnt);
|
||||
List<ResultSet<ChangeData>> matches = new ArrayList<>(cnt);
|
||||
for (ChangeDataSource s : sources) {
|
||||
matches.add(s.read());
|
||||
}
|
||||
|
||||
List<List<ChangeData>> out = Lists.newArrayListWithCapacity(cnt);
|
||||
List<QueryResult> out = new ArrayList<>(cnt);
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
List<ChangeData> results = matches.get(i).toList();
|
||||
if (results.size() > maxLimit) {
|
||||
moreResults = true;
|
||||
}
|
||||
int limit = limits.get(i);
|
||||
if (limit < results.size()) {
|
||||
results = results.subList(0, limit);
|
||||
}
|
||||
out.add(results);
|
||||
out.add(QueryResult.create(
|
||||
queryStrings != null ? queryStrings.get(i) : null,
|
||||
predicates.get(i),
|
||||
limits.get(i),
|
||||
matches.get(i).toList()));
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
public void query(String queryString) throws IOException {
|
||||
out = new PrintWriter( //
|
||||
new BufferedWriter( //
|
||||
new OutputStreamWriter(outputStream, "UTF-8")));
|
||||
try {
|
||||
if (isDisabled()) {
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "query disabled";
|
||||
show(m);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
final QueryStatsAttribute stats = new QueryStatsAttribute();
|
||||
stats.runTimeMilliseconds = TimeUtil.nowMs();
|
||||
|
||||
List<ChangeData> results = queryChanges(queryString);
|
||||
ChangeAttribute c = null;
|
||||
for (ChangeData d : results) {
|
||||
ChangeControl cc = d.changeControl().forUser(user);
|
||||
|
||||
LabelTypes labelTypes = cc.getLabelTypes();
|
||||
c = eventFactory.asChangeAttribute(d.change());
|
||||
eventFactory.extend(c, d.change());
|
||||
|
||||
if (!trackingFooters.isEmpty()) {
|
||||
eventFactory.addTrackingIds(c,
|
||||
trackingFooters.extract(d.commitFooters()));
|
||||
}
|
||||
|
||||
if (includeAllReviewers) {
|
||||
eventFactory.addAllReviewers(c, d.notes());
|
||||
}
|
||||
|
||||
if (includeSubmitRecords) {
|
||||
eventFactory.addSubmitRecords(c, new SubmitRuleEvaluator(d)
|
||||
.setAllowClosed(true)
|
||||
.setAllowDraft(true)
|
||||
.canSubmit());
|
||||
}
|
||||
|
||||
if (includeCommitMessage) {
|
||||
eventFactory.addCommitMessage(c, d.commitMessage());
|
||||
}
|
||||
|
||||
if (includePatchSets) {
|
||||
if (includeFiles) {
|
||||
eventFactory.addPatchSets(c, d.patches(),
|
||||
includeApprovals ? d.approvals().asMap() : null,
|
||||
includeFiles, d.change(), labelTypes);
|
||||
} else {
|
||||
eventFactory.addPatchSets(c, d.patches(),
|
||||
includeApprovals ? d.approvals().asMap() : null,
|
||||
labelTypes);
|
||||
}
|
||||
}
|
||||
|
||||
if (includeCurrentPatchSet) {
|
||||
PatchSet current = d.currentPatchSet();
|
||||
if (current != null) {
|
||||
c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
|
||||
eventFactory.addApprovals(c.currentPatchSet,
|
||||
d.currentApprovals(), labelTypes);
|
||||
|
||||
if (includeFiles) {
|
||||
eventFactory.addPatchSetFileNames(c.currentPatchSet,
|
||||
d.change(), d.currentPatchSet());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeComments) {
|
||||
eventFactory.addComments(c, d.messages());
|
||||
if (includePatchSets) {
|
||||
for (PatchSetAttribute attribute : c.patchSets) {
|
||||
eventFactory.addPatchSetComments(attribute, d.publishedComments());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (includeDependencies) {
|
||||
eventFactory.addDependencies(c, d.change());
|
||||
}
|
||||
|
||||
show(c);
|
||||
}
|
||||
|
||||
stats.rowCount = results.size();
|
||||
if (moreResults) {
|
||||
stats.resumeSortKey = c.sortKey;
|
||||
}
|
||||
stats.runTimeMilliseconds =
|
||||
TimeUtil.nowMs() - stats.runTimeMilliseconds;
|
||||
show(stats);
|
||||
} catch (OrmException err) {
|
||||
log.error("Cannot execute query: " + queryString, err);
|
||||
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "cannot query database";
|
||||
show(m);
|
||||
|
||||
} catch (QueryParseException e) {
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = e.getMessage();
|
||||
show(m);
|
||||
} catch (NoSuchChangeException e) {
|
||||
log.error("Missing change: " + e.getMessage(), e);
|
||||
ErrorMessage m = new ErrorMessage();
|
||||
m.message = "missing change " + e.getMessage();
|
||||
show(m);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
boolean isDisabled() {
|
||||
return maxLimit <= 0;
|
||||
return permittedLimit <= 0;
|
||||
}
|
||||
|
||||
private int limit(Predicate<ChangeData> s) {
|
||||
int n = MoreObjects.firstNonNull(ChangeQueryBuilder.getLimit(s), maxLimit);
|
||||
return limit > 0 ? Math.min(n, limit) + 1 : n + 1;
|
||||
private int getEffectiveLimit(Predicate<ChangeData> p) {
|
||||
List<Integer> possibleLimits = new ArrayList<>(3);
|
||||
possibleLimits.add(permittedLimit);
|
||||
if (limitFromCaller > 0) {
|
||||
possibleLimits.add(limitFromCaller);
|
||||
}
|
||||
|
||||
private Predicate<ChangeData> parseQuery(String queryString,
|
||||
final Predicate<ChangeData> visibleToMe) throws QueryParseException {
|
||||
return Predicate.and(queryBuilder.parse(queryString),
|
||||
queryBuilder.limit(limit > 0 ? Math.min(limit, maxLimit) + 1 : maxLimit),
|
||||
visibleToMe);
|
||||
Integer limitFromPredicate = LimitPredicate.getLimit(p);
|
||||
if (limitFromPredicate != null) {
|
||||
possibleLimits.add(limitFromPredicate);
|
||||
}
|
||||
|
||||
private void show(Object data) {
|
||||
switch (outputFormat) {
|
||||
default:
|
||||
case TEXT:
|
||||
if (data instanceof ChangeAttribute) {
|
||||
out.print("change ");
|
||||
out.print(((ChangeAttribute) data).id);
|
||||
out.print("\n");
|
||||
showText(data, 1);
|
||||
} else {
|
||||
showText(data, 0);
|
||||
}
|
||||
out.print('\n');
|
||||
break;
|
||||
|
||||
case JSON:
|
||||
out.print(gson.toJson(data));
|
||||
out.print('\n');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void showText(Object data, int depth) {
|
||||
for (Field f : fieldsOf(data.getClass())) {
|
||||
Object val;
|
||||
try {
|
||||
val = f.get(data);
|
||||
} catch (IllegalArgumentException err) {
|
||||
continue;
|
||||
} catch (IllegalAccessException err) {
|
||||
continue;
|
||||
}
|
||||
if (val == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
showField(f.getName(), val, depth);
|
||||
}
|
||||
}
|
||||
|
||||
private String indent(int spaces) {
|
||||
if (spaces == 0) {
|
||||
return "";
|
||||
} else {
|
||||
return String.format("%" + spaces + "s", " ");
|
||||
}
|
||||
}
|
||||
|
||||
private void showField(String field, Object value, int depth) {
|
||||
final int spacesDepthRatio = 2;
|
||||
String indent = indent(depth * spacesDepthRatio);
|
||||
out.print(indent);
|
||||
out.print(field);
|
||||
out.print(':');
|
||||
if (value instanceof String && ((String) value).contains("\n")) {
|
||||
out.print(' ');
|
||||
// Idention for multi-line text is
|
||||
// current depth indetion + length of field + length of ": "
|
||||
indent = indent(indent.length() + field.length() + spacesDepthRatio);
|
||||
out.print(((String) value).replaceAll("\n", "\n" + indent).trim());
|
||||
out.print('\n');
|
||||
} else if (value instanceof Long && isDateField(field)) {
|
||||
out.print(' ');
|
||||
out.print(sdf.format(new Date(((Long) value) * 1000L)));
|
||||
out.print('\n');
|
||||
} else if (isPrimitive(value)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
} else if (value instanceof Collection) {
|
||||
out.print('\n');
|
||||
boolean firstElement = true;
|
||||
for (Object thing : ((Collection<?>) value)) {
|
||||
// The name of the collection was initially printed at the beginning
|
||||
// of this routine. Beginning at the second sub-element, reprint
|
||||
// the collection name so humans can separate individual elements
|
||||
// with less strain and error.
|
||||
//
|
||||
if (firstElement) {
|
||||
firstElement = false;
|
||||
} else {
|
||||
out.print(indent);
|
||||
out.print(field);
|
||||
out.print(":\n");
|
||||
}
|
||||
if (isPrimitive(thing)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
} else {
|
||||
showText(thing, depth + 1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.print('\n');
|
||||
showText(value, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(Object value) {
|
||||
return value instanceof String //
|
||||
|| value instanceof Number //
|
||||
|| value instanceof Boolean //
|
||||
|| value instanceof Enum;
|
||||
}
|
||||
|
||||
private static boolean isDateField(String name) {
|
||||
return "lastUpdated".equals(name) //
|
||||
|| "grantedOn".equals(name) //
|
||||
|| "timestamp".equals(name) //
|
||||
|| "createdOn".equals(name);
|
||||
}
|
||||
|
||||
private List<Field> fieldsOf(Class<?> type) {
|
||||
List<Field> r = new ArrayList<>();
|
||||
if (type.getSuperclass() != null) {
|
||||
r.addAll(fieldsOf(type.getSuperclass()));
|
||||
}
|
||||
r.addAll(Arrays.asList(type.getDeclaredFields()));
|
||||
return r;
|
||||
}
|
||||
|
||||
static class ErrorMessage {
|
||||
public final String type = "error";
|
||||
public String message;
|
||||
return Ordering.natural().min(possibleLimits);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// 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.auto.value.AutoValue;
|
||||
import com.google.gerrit.common.Nullable;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Results of a query over changes. */
|
||||
@AutoValue
|
||||
public abstract class QueryResult {
|
||||
static QueryResult create(@Nullable String query,
|
||||
Predicate<ChangeData> predicate, int limit, List<ChangeData> changes) {
|
||||
boolean moreChanges;
|
||||
if (changes.size() > limit) {
|
||||
moreChanges = true;
|
||||
changes = changes.subList(0, limit);
|
||||
} else {
|
||||
moreChanges = false;
|
||||
}
|
||||
return new AutoValue_QueryResult(query, predicate, changes, moreChanges);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return the original query string, or null if the query was created
|
||||
* programmatically.
|
||||
*/
|
||||
@Nullable public abstract String query();
|
||||
|
||||
/**
|
||||
* @return the predicate after all rewriting and other modification by the
|
||||
* query subsystem.
|
||||
*/
|
||||
public abstract Predicate<ChangeData> predicate();
|
||||
|
||||
/** @return the query results. */
|
||||
public abstract List<ChangeData> changes();
|
||||
|
||||
/**
|
||||
* @return whether the query could be retried with
|
||||
* {@link QueryProcessor#setStart(int)} to produce more results. Never
|
||||
* true if {@link #changes()} is empty.
|
||||
*/
|
||||
public abstract boolean moreChanges();
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import java.util.List;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
public static final Class<Schema_101> C = Schema_101.class;
|
||||
public static final Class<Schema_102> C = Schema_102.class;
|
||||
|
||||
public static class Module extends AbstractModule {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
// 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.schema;
|
||||
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gwtorm.jdbc.JdbcSchema;
|
||||
import com.google.gwtorm.schema.sql.DialectPostgreSQL;
|
||||
import com.google.gwtorm.schema.sql.SqlDialect;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
public class Schema_102 extends SchemaVersion {
|
||||
@Inject
|
||||
Schema_102(Provider<Schema_100> prior) {
|
||||
super(prior);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void migrateData(ReviewDb db, UpdateUI ui)
|
||||
throws OrmException, SQLException {
|
||||
JdbcSchema schema = (JdbcSchema) db;
|
||||
SqlDialect dialect = schema.getDialect();
|
||||
try (Statement stmt = schema.getConnection().createStatement()) {
|
||||
stmt.executeUpdate("DROP INDEX changes_byProjectOpen");
|
||||
if (dialect instanceof DialectPostgreSQL) {
|
||||
stmt.executeUpdate("CREATE INDEX changes_byProjectOpen"
|
||||
+ " ON changes (dest_project_name, last_updated_on)"
|
||||
+ " WHERE open = 'Y'");
|
||||
} else {
|
||||
stmt.executeUpdate("CREATE INDEX changes_byProjectOpen"
|
||||
+ " ON changes (open, dest_project_name, last_updated_on)");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,6 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetApproval.LabelId;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountManager;
|
||||
import com.google.gerrit.server.account.AuthRequest;
|
||||
@@ -112,7 +111,6 @@ public class LabelNormalizerTest {
|
||||
new Change.Id(1), userId,
|
||||
new Branch.NameKey(allProjects, "refs/heads/master"),
|
||||
TimeUtil.nowTs());
|
||||
ChangeUtil.computeSortKey(change);
|
||||
PatchSetInfo ps = new PatchSetInfo(new PatchSet.Id(change.getId(), 1));
|
||||
ps.setSubject("Test change");
|
||||
change.setCurrentPatchSet(ps);
|
||||
|
||||
@@ -97,7 +97,7 @@ public class IndexRewriteTest {
|
||||
parse("-status:abandoned (status:open OR status:merged)");
|
||||
assertEquals(
|
||||
query(parse("status:new OR status:submitted OR status:draft OR status:merged")),
|
||||
rewrite.rewrite(in, 0));
|
||||
rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -158,24 +158,26 @@ public class IndexRewriteTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLimit() throws Exception {
|
||||
Predicate<ChangeData> in = parse("file:a limit:3");
|
||||
Predicate<ChangeData> out = rewrite(in);
|
||||
public void testLimitArgumentOverridesAllLimitPredicates() throws Exception {
|
||||
Predicate<ChangeData> in = parse("limit:1 file:a limit:3");
|
||||
Predicate<ChangeData> out = rewrite(in, 5);
|
||||
assertSame(AndSource.class, out.getClass());
|
||||
assertEquals(ImmutableList.of(
|
||||
query(in.getChild(0), 3),
|
||||
in.getChild(1)),
|
||||
query(in.getChild(1), 5),
|
||||
parse("limit:5"),
|
||||
parse("limit:5")),
|
||||
out.getChildren());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testStartIncreasesLimit() throws Exception {
|
||||
int n = 3;
|
||||
Predicate<ChangeData> f = parse("file:a");
|
||||
Predicate<ChangeData> l = parse("limit:3");
|
||||
Predicate<ChangeData> l = parse("limit:" + n);
|
||||
Predicate<ChangeData> in = and(f, l);
|
||||
assertEquals(and(query(f, 3), l), rewrite.rewrite(in, 0));
|
||||
assertEquals(and(query(f, 4), l), rewrite.rewrite(in, 1));
|
||||
assertEquals(and(query(f, 5), l), rewrite.rewrite(in, 2));
|
||||
assertEquals(and(query(f, 3), parse("limit:3")), rewrite.rewrite(in, 0, n));
|
||||
assertEquals(and(query(f, 4), parse("limit:4")), rewrite.rewrite(in, 1, n));
|
||||
assertEquals(and(query(f, 5), parse("limit:5")), rewrite.rewrite(in, 2, n));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -220,7 +222,12 @@ public class IndexRewriteTest {
|
||||
|
||||
private Predicate<ChangeData> rewrite(Predicate<ChangeData> in)
|
||||
throws QueryParseException {
|
||||
return rewrite.rewrite(in, 0);
|
||||
return rewrite.rewrite(in, 0, DEFAULT_MAX_QUERY_LIMIT);
|
||||
}
|
||||
|
||||
private Predicate<ChangeData> rewrite(Predicate<ChangeData> in, int limit)
|
||||
throws QueryParseException {
|
||||
return rewrite.rewrite(in, 0, limit);
|
||||
}
|
||||
|
||||
private IndexedChangeQuery query(Predicate<ChangeData> p)
|
||||
|
||||
@@ -136,18 +136,22 @@ public abstract class AbstractQueryChangesTest {
|
||||
userAccount.setPreferredEmail("user@example.com");
|
||||
db.accounts().update(ImmutableList.of(userAccount));
|
||||
user = userFactory.create(userId);
|
||||
requestContext.setContext(newRequestContext(userAccount.getId()));
|
||||
}
|
||||
|
||||
requestContext.setContext(new RequestContext() {
|
||||
private RequestContext newRequestContext(Account.Id requestUserId) {
|
||||
final CurrentUser requestUser = userFactory.create(requestUserId);
|
||||
return new RequestContext() {
|
||||
@Override
|
||||
public CurrentUser getCurrentUser() {
|
||||
return user;
|
||||
return requestUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Provider<ReviewDb> getReviewDbProvider() {
|
||||
return Providers.of(db);
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -543,9 +547,22 @@ public abstract class AbstractQueryChangesTest {
|
||||
|
||||
List<ChangeInfo> results;
|
||||
for (int i = 1; i <= n + 2; i++) {
|
||||
int expectedSize;
|
||||
Boolean expectedMoreChanges;
|
||||
if (i < n) {
|
||||
expectedSize = i;
|
||||
expectedMoreChanges = true;
|
||||
} else {
|
||||
expectedSize = n;
|
||||
expectedMoreChanges = null;
|
||||
}
|
||||
results = query("status:new limit:" + i);
|
||||
assertThat(results).hasSize(Math.min(i, n));
|
||||
String msg = "i=" + i;
|
||||
assert_().withFailureMessage(msg).that(results).hasSize(expectedSize);
|
||||
assertResultEquals(last, results.get(0));
|
||||
assert_().withFailureMessage(msg)
|
||||
.that(results.get(results.size() - 1)._moreChanges)
|
||||
.isEqualTo(expectedMoreChanges);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1031,6 +1048,49 @@ public abstract class AbstractQueryChangesTest {
|
||||
assertThat(query("repo")).hasSize(6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void implicitVisibleTo() throws Exception {
|
||||
TestRepository<InMemoryRepository> repo = createProject("repo");
|
||||
Change change1 = newChange(repo, null, null, userId.get(), null).insert();
|
||||
ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
|
||||
Change change2 = ins2.getChange();
|
||||
change2.setStatus(Change.Status.DRAFT);
|
||||
ins2.insert();
|
||||
|
||||
String q = "project:repo";
|
||||
List<ChangeInfo> results = query(q);
|
||||
assertThat(results).hasSize(2);
|
||||
assertResultEquals(change2, results.get(0));
|
||||
assertResultEquals(change1, results.get(1));
|
||||
|
||||
// Second user cannot see first user's drafts.
|
||||
requestContext.setContext(newRequestContext(accountManager
|
||||
.authenticate(AuthRequest.forUser("anotheruser")).getAccountId()));
|
||||
assertResultEquals(change1, queryOne(q));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void explicitVisibleTo() throws Exception {
|
||||
TestRepository<InMemoryRepository> repo = createProject("repo");
|
||||
Change change1 = newChange(repo, null, null, userId.get(), null).insert();
|
||||
ChangeInserter ins2 = newChange(repo, null, null, userId.get(), null);
|
||||
Change change2 = ins2.getChange();
|
||||
change2.setStatus(Change.Status.DRAFT);
|
||||
ins2.insert();
|
||||
|
||||
String q = "project:repo";
|
||||
List<ChangeInfo> results = query(q);
|
||||
assertThat(results).hasSize(2);
|
||||
assertResultEquals(change2, results.get(0));
|
||||
assertResultEquals(change1, results.get(1));
|
||||
|
||||
// Second user cannot see first user's drafts.
|
||||
Account.Id user2 = accountManager
|
||||
.authenticate(AuthRequest.forUser("anotheruser"))
|
||||
.getAccountId();
|
||||
assertResultEquals(change1, queryOne(q + " visibleto:" + user2.get()));
|
||||
}
|
||||
|
||||
protected ChangeInserter newChange(
|
||||
TestRepository<InMemoryRepository> repo,
|
||||
@Nullable RevCommit commit, @Nullable String key, @Nullable Integer owner,
|
||||
|
||||
@@ -25,7 +25,6 @@ import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.PatchSetInfo;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.RevId;
|
||||
import com.google.gerrit.server.ChangeUtil;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.config.AllUsersNameProvider;
|
||||
@@ -114,6 +113,5 @@ public class TestChanges {
|
||||
change.getId(), curr != null ? curr.get() + 1 : 1));
|
||||
ps.setSubject("Change subject");
|
||||
change.setCurrentPatchSet(ps);
|
||||
ChangeUtil.computeSortKey(change);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.server.query.change.OutputStreamQuery;
|
||||
import com.google.gerrit.server.query.change.OutputStreamQuery.OutputFormat;
|
||||
import com.google.gerrit.sshd.CommandMetaData;
|
||||
import com.google.gerrit.sshd.SshCommand;
|
||||
import com.google.inject.Inject;
|
||||
@@ -27,10 +28,10 @@ import java.util.List;
|
||||
@CommandMetaData(name = "query", description = "Query the change database")
|
||||
class Query extends SshCommand {
|
||||
@Inject
|
||||
private QueryProcessor processor;
|
||||
private OutputStreamQuery processor;
|
||||
|
||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||
void setFormat(QueryProcessor.OutputFormat format) {
|
||||
void setFormat(OutputFormat format) {
|
||||
processor.setOutput(out, format);
|
||||
}
|
||||
|
||||
@@ -97,7 +98,7 @@ class Query extends SshCommand {
|
||||
|
||||
@Override
|
||||
protected void parseCommandLine() throws UnloggedFailure {
|
||||
processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
|
||||
processor.setOutput(out, OutputFormat.TEXT);
|
||||
super.parseCommandLine();
|
||||
if (processor.getIncludeFiles() &&
|
||||
!(processor.getIncludePatchSets() || processor.getIncludeCurrentPatchSet())) {
|
||||
|
||||
Reference in New Issue
Block a user