gerrit query SSH command
Define an SSH command that offers the ability to query for change summary information. This provides the same information as a listing on the web interface. The query command is also available over HTTP under the /query URL, with the query string supplied as the q parameter. Bug: issue 504 Change-Id: I72fc11dc8872b781bcd6895b3bcd90d85f22e419 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -66,6 +66,9 @@ link:cmd-review.html[gerrit approve]::
|
||||
link:cmd-ls-projects.html[gerrit ls-projects]::
|
||||
List projects visible to the caller.
|
||||
|
||||
link:cmd-query.html[gerrit query]::
|
||||
Query the change database.
|
||||
|
||||
link:cmd-review.html[gerrit review]::
|
||||
Verify, approve and/or submit a patch set from the command line.
|
||||
|
||||
|
110
Documentation/cmd-query.txt
Normal file
110
Documentation/cmd-query.txt
Normal file
@@ -0,0 +1,110 @@
|
||||
gerrit query
|
||||
============
|
||||
|
||||
NAME
|
||||
----
|
||||
gerrit query - Query the change database
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
[verse]
|
||||
'ssh' -p <port> <host> 'gerrit query' \
|
||||
[\--format {TEXT | JSON}] \
|
||||
[\--current-patch-set] \
|
||||
[\--patch-sets] \
|
||||
[\--] \
|
||||
<query> \
|
||||
[limit:<n>] \
|
||||
[resume\_sortkey:<sortKey>]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
|
||||
Queries the change database and returns results describing changes
|
||||
that match the input query. More recently updated changes appear
|
||||
before older changes, which is the same order presented in the
|
||||
web interface.
|
||||
|
||||
A query may be limited on the number of results it returns with the
|
||||
'limit:' operator. If no limit is supplied an internal default
|
||||
limit is used to prevent explosion of the result set. To obtain
|
||||
results beyond the limit, the 'resume_sortkey:' operator can be used
|
||||
to resume the query at the change that follows the last change of
|
||||
the prior result set.
|
||||
|
||||
Non-option arguments to this command are joined with spaces and then
|
||||
parsed as a query. This simplifies calling conventions over SSH
|
||||
by permitting operators to appear in different arguments without
|
||||
multiple levels of quoting required.
|
||||
|
||||
OPTIONS
|
||||
-------
|
||||
\--current-patch-set::
|
||||
Include information about the current patch set in the results.
|
||||
|
||||
\--patch-sets::
|
||||
Include information about all patch sets. If combined with
|
||||
the \--current-patch-set flag then the current patch set
|
||||
information will be output twice, once in each field.
|
||||
|
||||
limit:<n>::
|
||||
Maximum number of results to return. This is actually a
|
||||
query operator, and not a command line option. If more
|
||||
than one limit: operator is provided, the smallest limit
|
||||
will be used to cut the result set.
|
||||
|
||||
resume\_sortkey:<sortKey>::
|
||||
Resume results from this sort key. Callers should pass
|
||||
the sortKey of the last change of the prior result set to
|
||||
resume a prior query. This is actually a query operator,
|
||||
and not a command line option.
|
||||
|
||||
ACCESS
|
||||
------
|
||||
Any user who has configured an SSH key.
|
||||
|
||||
SCRIPTING
|
||||
---------
|
||||
This command is intended to be used in scripts.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
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", ...}
|
||||
{"type":"stats","rowCount":2,"runningTimeMilliseconds:15}
|
||||
-----
|
||||
|
||||
Resume the same query and obtain the final results:
|
||||
-----
|
||||
$ ssh -p 29418 review.example.com gerrit query --format=JSON status:open project:tools/gerrit limit:2 resume_sortkey:000e6aee00003e26
|
||||
{"project":"tools/gerrit", ...}
|
||||
{"project":"tools/gerrit", ...}
|
||||
{"type":"stats","rowCount":1,"runningTimeMilliseconds:15}
|
||||
-----
|
||||
|
||||
|
||||
SCHEMA
|
||||
------
|
||||
The JSON messages consist of nested objects referencing the
|
||||
link:json.html#change[change],
|
||||
link:json.html#patchset[patchset],
|
||||
link:json.html#[account]
|
||||
involved, and other attributes as appropriate.
|
||||
|
||||
Note that any field may be missing in the JSON messages, so consumers
|
||||
of this JSON stream should deal with that appropriately.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
* link:user-search.html[Query Operators]
|
||||
* link:json.html[JSON Data Formats]
|
||||
* link:access-control.html[Access Controls]
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
@@ -54,93 +54,48 @@ Patchset Added
|
||||
^^^^^^^^^^^^^^
|
||||
type:: "patchset-added"
|
||||
|
||||
change:: <<change,change attribute>>
|
||||
change:: link:json.html#change[change attribute]
|
||||
|
||||
patchset:: <<patchset,patchset attribute>>
|
||||
patchset:: link:json.html#patchset[patchset attribute]
|
||||
|
||||
uploader:: <<account,account attribute>>
|
||||
uploader:: link:json.html#account[account attribute]
|
||||
|
||||
Change Abandoned
|
||||
^^^^^^^^^^^^^^^^
|
||||
type:: "change-abandoned"
|
||||
|
||||
change:: <<change,change attribute>>
|
||||
change:: link:json.html#change[change attribute]
|
||||
|
||||
patchset:: <<patchset,patchset attribute>>
|
||||
patchset:: link:json.html#patchset[patchset attribute]
|
||||
|
||||
abandoner:: <<account,account attribute>>
|
||||
abandoner:: link:json.html#account[account attribute]
|
||||
|
||||
Change Merged
|
||||
^^^^^^^^^^^^^
|
||||
type:: "change-merged"
|
||||
|
||||
change:: <<change,change attribute>>
|
||||
change:: link:json.html#change[change attribute]
|
||||
|
||||
patchset:: <<patchset,patchset attribute>>
|
||||
patchset:: link:json.html#patchset[patchset attribute]
|
||||
|
||||
submitter:: <<account,account attribute>>
|
||||
submitter:: link:json.html#account[account attribute]
|
||||
|
||||
Comment Added
|
||||
^^^^^^^^^^^^^
|
||||
type:: "comment-added"
|
||||
|
||||
change:: <<change,change attribute>>
|
||||
change:: link:json.html#change[change attribute]
|
||||
|
||||
patchset:: <<patchset,patchset attribute>>
|
||||
patchset:: link:json.html#patchset[patchset attribute]
|
||||
|
||||
author:: <<account,account attribute>>
|
||||
author:: link:json.html#account[account attribute]
|
||||
|
||||
comment:: Comment text author had written
|
||||
|
||||
Attributes
|
||||
~~~~~~~~~~
|
||||
Attributes are part events to give context related to the event.
|
||||
|
||||
[[change]]
|
||||
change:: The Gerrit change the event is related to
|
||||
|
||||
project;; Project path in Gerrit
|
||||
|
||||
branch;; Branch name within project
|
||||
|
||||
topic;; Topic name specified by the uploaded for this change series
|
||||
|
||||
id;; Change identifier
|
||||
|
||||
number;; Change number (deprecated)
|
||||
|
||||
subject;; Description of change
|
||||
|
||||
owner;; Owner in account attribute
|
||||
|
||||
url;; Canonical URL to reach this change
|
||||
|
||||
lastUpdated;; Time in seconds since the UNIX epoch when this change
|
||||
was last updated.
|
||||
|
||||
sortKey;; Internal key used to sort changes, based on lastUpdated.
|
||||
|
||||
[[account]]
|
||||
account:: An account that is related to an event or attribute
|
||||
|
||||
name;; Account user's full name
|
||||
|
||||
email;; Account user's preferred email
|
||||
|
||||
[[patchset]]
|
||||
patchset:: Refers to a specific patchset within a change
|
||||
|
||||
number;; The patchset number
|
||||
|
||||
revision;; Git commit-ish for this patchset
|
||||
|
||||
ref;; Git reference pointing at revision
|
||||
|
||||
uploader;; Uploader of patch set in account attribute
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
* link:json.html[JSON Data Formats]
|
||||
* link:access-control.html[Access Controls]
|
||||
|
||||
GERRIT
|
||||
|
118
Documentation/json.txt
Normal file
118
Documentation/json.txt
Normal file
@@ -0,0 +1,118 @@
|
||||
Gerrit Code Review - JSON Data
|
||||
==============================
|
||||
|
||||
Some commands produce JSON data streams intended for other
|
||||
applications to consume. The structures are documented below.
|
||||
Note that any field may be missing in the JSON messages, so consumers
|
||||
of this JSON stream should deal with that appropriately.
|
||||
|
||||
[[change]]
|
||||
change
|
||||
------
|
||||
The Gerrit change being reviewed, or that was already reviewed.
|
||||
|
||||
project:: Project path in Gerrit
|
||||
|
||||
branch:: Branch name within project
|
||||
|
||||
topic:: Topic name specified by the uploader for this change series
|
||||
|
||||
id:: Change identifier, as scraped out of the Change-Id field in
|
||||
the commit message, or as assigned by the server if it was missing.
|
||||
|
||||
number:: Change number (deprecated)
|
||||
|
||||
subject:: Description of change
|
||||
|
||||
owner:: Owner in <<account,account attribute>>
|
||||
|
||||
url:: Canonical URL to reach this change
|
||||
|
||||
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.
|
||||
|
||||
NEW;; Change is still being reviewed.
|
||||
|
||||
SUBMITTED;; Change has been submitted and is in the merge queue.
|
||||
It may be waiting for one or more dependencies.
|
||||
|
||||
MERGED;; Change has been merged to its branch.
|
||||
|
||||
ABANDONED;; Change was abandoned by its owner or administrator.
|
||||
|
||||
trackingIds:: Issue tracking system links in
|
||||
<<trackingid,trackingid attribute>>, scraped out of the commit
|
||||
message based on the server's
|
||||
link:config-gerrit.html#trackingid[trackingid] sections.
|
||||
|
||||
currentPatchSet:: Current <<patchset,patchset attribute>>.
|
||||
|
||||
patchSets:: All <<patchset,patchset attribute>> for this change.
|
||||
|
||||
[[trackingid]]
|
||||
trackingid
|
||||
----------
|
||||
A link to an issue tracking system.
|
||||
|
||||
system:: Name of the system. This comes straight from the
|
||||
gerrit.config file.
|
||||
|
||||
id:: Id number as scraped out of the commit message.
|
||||
|
||||
[[account]]
|
||||
account
|
||||
-------
|
||||
A user account.
|
||||
|
||||
name:: User's full name, if configured.
|
||||
|
||||
email:: User's preferred email address.
|
||||
|
||||
[[patchset]]
|
||||
patchset
|
||||
--------
|
||||
Refers to a specific patchset within a <<change,change>>.
|
||||
|
||||
number:: The patchset number.
|
||||
|
||||
revision:: Git commit for this patchset.
|
||||
|
||||
ref:: Git reference pointing at the revision. This reference is
|
||||
available through the Gerrit Code Review server's Git interface
|
||||
for the containing change.
|
||||
|
||||
uploader:: Uploader of the patch set in <<account,account attribute>>.
|
||||
|
||||
approvals:: The <<approval,approval attribute>> granted.
|
||||
|
||||
[[approval]]
|
||||
approval
|
||||
--------
|
||||
Records the code review approval granted to a patch set.
|
||||
|
||||
type:: Internal name of the approval given.
|
||||
|
||||
description:: Human readable category of the approval.
|
||||
|
||||
value:: Value assigned by the approval, usually a numerical score.
|
||||
|
||||
grantedOn:: Time in seconds since the UNIX epoch when this approval
|
||||
was added or last updated.
|
||||
|
||||
by:: Reviewer of the patch set in <<account,account attribute>>.
|
||||
|
||||
SEE ALSO
|
||||
--------
|
||||
|
||||
* link:cmd-stream-events.html[gerrit stream-events]
|
||||
* link:cmd-query.html[gerrit query]
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
@@ -334,6 +334,13 @@ automatically set to the page size configured in the current user's
|
||||
preferences. Including it in a web query may lead to unpredictable
|
||||
results with regards to pagination.
|
||||
|
||||
resume\_sortkey:'KEY'::
|
||||
+
|
||||
Positions the low level scan routine to start from 'KEY' and
|
||||
continue through changes from this point. This is most often used
|
||||
for paginating result sets. Including this in a web query may lead
|
||||
to unpredictable results.
|
||||
|
||||
sortkey\_after:'KEY', sortkey\_before:'KEY'::
|
||||
+
|
||||
Restart the low level scan routine from 'KEY'. This is automatically
|
||||
|
@@ -0,0 +1,112 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.httpd;
|
||||
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.server.query.change.QueryProcessor.OutputFormat;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.ServletOutputStream;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
public class ChangeQueryServlet extends HttpServlet {
|
||||
private final Provider<QueryProcessor> processor;
|
||||
|
||||
@Inject
|
||||
ChangeQueryServlet(Provider<QueryProcessor> processor) {
|
||||
this.processor = processor;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
rsp.setContentType("text/json");
|
||||
rsp.setCharacterEncoding("UTF-8");
|
||||
|
||||
QueryProcessor p = processor.get();
|
||||
OutputFormat format = OutputFormat.JSON;
|
||||
try {
|
||||
format = OutputFormat.valueOf(get(req, "format", format.toString()));
|
||||
} catch (IllegalArgumentException err) {
|
||||
error(rsp, "invalid format");
|
||||
return;
|
||||
}
|
||||
|
||||
switch (format) {
|
||||
case JSON:
|
||||
rsp.setContentType("text/json");
|
||||
rsp.setCharacterEncoding("UTF-8");
|
||||
break;
|
||||
|
||||
case TEXT:
|
||||
rsp.setContentType("text/plain");
|
||||
rsp.setCharacterEncoding("UTF-8");
|
||||
break;
|
||||
|
||||
default:
|
||||
error(rsp, "invalid format");
|
||||
return;
|
||||
}
|
||||
|
||||
p.setIncludeCurrentPatchSet(get(req, "current-patch-set", false));
|
||||
p.setIncludePatchSets(get(req, "patch-sets", false));
|
||||
p.setOutput(rsp.getOutputStream(), format);
|
||||
p.query(get(req, "q", "status:open"));
|
||||
}
|
||||
|
||||
private static void error(HttpServletResponse rsp, String message)
|
||||
throws IOException {
|
||||
ErrorMessage em = new ErrorMessage();
|
||||
em.message = message;
|
||||
|
||||
ServletOutputStream out = rsp.getOutputStream();
|
||||
try {
|
||||
out.write(new Gson().toJson(em).getBytes("UTF-8"));
|
||||
out.write('\n');
|
||||
out.flush();
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static String get(HttpServletRequest req, String name, String val) {
|
||||
String v = req.getParameter(name);
|
||||
if (v == null || v.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
private static boolean get(HttpServletRequest req, String name, boolean val) {
|
||||
String v = req.getParameter(name);
|
||||
if (v == null || v.isEmpty()) {
|
||||
return val;
|
||||
}
|
||||
return "true".equalsIgnoreCase(v);
|
||||
}
|
||||
|
||||
public static class ErrorMessage {
|
||||
public final String type = "error";
|
||||
public String message;
|
||||
}
|
||||
}
|
@@ -46,6 +46,7 @@ class UrlModule extends ServletModule {
|
||||
serve("/Gerrit/*").with(legacyGerritScreen());
|
||||
serve("/cat/*").with(CatServlet.class);
|
||||
serve("/logout").with(HttpLogoutServlet.class);
|
||||
serve("/query").with(ChangeQueryServlet.class);
|
||||
serve("/signout").with(HttpLogoutServlet.class);
|
||||
serve("/ssh_info").with(SshInfoServlet.class);
|
||||
serve("/static/*").with(StaticServlet.class);
|
||||
|
@@ -18,4 +18,7 @@ public class ApprovalAttribute {
|
||||
public String type;
|
||||
public String description;
|
||||
public String value;
|
||||
|
||||
public Long grantedOn;
|
||||
public AccountAttribute by;
|
||||
}
|
||||
|
@@ -14,6 +14,10 @@
|
||||
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ChangeAttribute {
|
||||
public String project;
|
||||
public String branch;
|
||||
@@ -23,6 +27,13 @@ public class ChangeAttribute {
|
||||
public String subject;
|
||||
public AccountAttribute owner;
|
||||
public String url;
|
||||
public long lastUpdated;
|
||||
|
||||
public Long lastUpdated;
|
||||
public String sortKey;
|
||||
public Boolean open;
|
||||
public Change.Status status;
|
||||
|
||||
public List<TrackingIdAttribute> trackingIds;
|
||||
public PatchSetAttribute currentPatchSet;
|
||||
public List<PatchSetAttribute> patchSets;
|
||||
}
|
||||
|
@@ -14,27 +14,36 @@
|
||||
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
import com.google.gerrit.common.data.ApprovalType;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.reviewdb.Account;
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.reviewdb.TrackingId;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.internal.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
|
||||
@Singleton
|
||||
public class EventFactory {
|
||||
private final AccountCache accountCache;
|
||||
private final Provider<String> urlProvider;
|
||||
private final ApprovalTypes approvalTypes;
|
||||
|
||||
@Inject
|
||||
EventFactory(AccountCache accountCache,
|
||||
@CanonicalWebUrl @Nullable Provider<String> urlProvider) {
|
||||
@CanonicalWebUrl @Nullable Provider<String> urlProvider,
|
||||
ApprovalTypes approvalTypes) {
|
||||
this.accountCache = accountCache;
|
||||
this.urlProvider = urlProvider;
|
||||
this.approvalTypes = approvalTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -53,11 +62,45 @@ public class EventFactory {
|
||||
a.number = change.getId().toString();
|
||||
a.subject = change.getSubject();
|
||||
a.url = getChangeUrl(change);
|
||||
a.owner = asAccountAttribute(change.getOwner());
|
||||
return a;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend the existing ChangeAttribute with additional fields.
|
||||
*
|
||||
* @param a
|
||||
* @param change
|
||||
*/
|
||||
public void extend(ChangeAttribute a, Change change) {
|
||||
a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
|
||||
a.sortKey = change.getSortKey();
|
||||
a.open = change.getStatus().isOpen();
|
||||
a.status = change.getStatus();
|
||||
}
|
||||
|
||||
final AccountState owner = accountCache.get(change.getOwner());
|
||||
a.owner = asAccountAttribute(owner.getAccount());
|
||||
public void addTrackingIds(ChangeAttribute a, Collection<TrackingId> ids) {
|
||||
if (!ids.isEmpty()) {
|
||||
a.trackingIds = new ArrayList<TrackingIdAttribute>(ids.size());
|
||||
for (TrackingId t : ids) {
|
||||
a.trackingIds.add(asTrackingIdAttribute(t));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void addPatchSets(ChangeAttribute a, Collection<PatchSet> ps) {
|
||||
if (!ps.isEmpty()) {
|
||||
a.patchSets = new ArrayList<PatchSetAttribute>(ps.size());
|
||||
for (PatchSet p : ps) {
|
||||
a.patchSets.add(asPatchSetAttribute(p));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public TrackingIdAttribute asTrackingIdAttribute(TrackingId id) {
|
||||
TrackingIdAttribute a = new TrackingIdAttribute();
|
||||
a.system = id.getSystem();
|
||||
a.id = id.getTrackingId();
|
||||
return a;
|
||||
}
|
||||
|
||||
@@ -73,12 +116,36 @@ public class EventFactory {
|
||||
p.revision = patchSet.getRevision().get();
|
||||
p.number = Integer.toString(patchSet.getPatchSetId());
|
||||
p.ref = patchSet.getRefName();
|
||||
|
||||
final AccountState uploader = accountCache.get(patchSet.getUploader());
|
||||
p.uploader = asAccountAttribute(uploader.getAccount());
|
||||
p.uploader = asAccountAttribute(patchSet.getUploader());
|
||||
return p;
|
||||
}
|
||||
|
||||
public void addApprovals(PatchSetAttribute p,
|
||||
Collection<PatchSetApproval> list) {
|
||||
if (!list.isEmpty()) {
|
||||
p.approvals = new ArrayList<ApprovalAttribute>(list.size());
|
||||
for (PatchSetApproval a : list) {
|
||||
if (a.getValue() != 0) {
|
||||
p.approvals.add(asApprovalAttribute(a));
|
||||
}
|
||||
}
|
||||
if (p.approvals.isEmpty()) {
|
||||
p.approvals = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AuthorAttribute for the given account suitable for serialization
|
||||
* to JSON.
|
||||
*
|
||||
* @param id
|
||||
* @return object suitable for serialization to JSON
|
||||
*/
|
||||
public AccountAttribute asAccountAttribute(Account.Id id) {
|
||||
return asAccountAttribute(accountCache.get(id).getAccount());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an AuthorAttribute for the given account suitable for serialization
|
||||
* to JSON.
|
||||
@@ -93,6 +160,27 @@ public class EventFactory {
|
||||
return who;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ApprovalAttribute for the given approval suitable for
|
||||
* serialization to JSON.
|
||||
*
|
||||
* @param approval
|
||||
* @return object suitable for serialization to JSON
|
||||
*/
|
||||
public ApprovalAttribute asApprovalAttribute(PatchSetApproval approval) {
|
||||
ApprovalAttribute a = new ApprovalAttribute();
|
||||
a.type = approval.getCategoryId().get();
|
||||
a.value = Short.toString(approval.getValue());
|
||||
a.by = asAccountAttribute(approval.getAccountId());
|
||||
a.grantedOn = approval.getGranted().getTime() / 1000L;
|
||||
|
||||
ApprovalType at = approvalTypes.getApprovalType(approval.getCategoryId());
|
||||
if (at != null) {
|
||||
a.description = at.getCategory().getName();
|
||||
}
|
||||
return a;
|
||||
}
|
||||
|
||||
/** Get a link to the change; null if the server doesn't know its own address. */
|
||||
private String getChangeUrl(final Change change) {
|
||||
if (change != null && urlProvider.get() != null) {
|
||||
|
@@ -14,9 +14,13 @@
|
||||
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class PatchSetAttribute {
|
||||
public String number;
|
||||
public String revision;
|
||||
public String ref;
|
||||
public AccountAttribute uploader;
|
||||
|
||||
public List<ApprovalAttribute> approvals;
|
||||
}
|
||||
|
@@ -0,0 +1,21 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
public class QueryStats {
|
||||
public final String type = "stats";
|
||||
public int rowCount;
|
||||
public long runTimeMilliseconds;
|
||||
}
|
@@ -0,0 +1,20 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.events;
|
||||
|
||||
public class TrackingIdAttribute {
|
||||
public String system;
|
||||
public String id;
|
||||
}
|
@@ -26,6 +26,8 @@ import static com.google.gerrit.server.query.QueryParser.OR;
|
||||
import static com.google.gerrit.server.query.QueryParser.SINGLE_WORD;
|
||||
import static com.google.gerrit.server.query.QueryParser.VARIABLE_ASSIGN;
|
||||
|
||||
import com.google.gerrit.server.query.change.ChangeData;
|
||||
|
||||
import org.antlr.runtime.tree.Tree;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -237,6 +239,33 @@ public abstract class QueryBuilder<T> {
|
||||
throw error("Unsupported query:" + value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Locate a predicate in the predicate tree.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <P extends OperatorPredicate<T>> P find(Predicate<T> p,
|
||||
Class<P> clazz, String name) {
|
||||
if (p instanceof OperatorPredicate
|
||||
&& ((OperatorPredicate) p).getOperator().equals(name)
|
||||
&& clazz.isAssignableFrom(p.getClass())) {
|
||||
return (P) p;
|
||||
}
|
||||
|
||||
for (Predicate<T> c : p.getChildren()) {
|
||||
P r = find(c, clazz, name);
|
||||
if (r != null) {
|
||||
return r;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate<T>[] children(final Tree r) throws QueryParseException,
|
||||
IllegalArgumentException {
|
||||
|
@@ -24,7 +24,10 @@ import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class ChangeData {
|
||||
private final Change.Id legacyId;
|
||||
@@ -71,6 +74,39 @@ public class ChangeData {
|
||||
return change;
|
||||
}
|
||||
|
||||
public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
|
||||
Change c = change(db);
|
||||
if (c == null) {
|
||||
return null;
|
||||
}
|
||||
for (PatchSet p : patches(db)) {
|
||||
if (p.getId().equals(c.currentPatchSetId())) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public Collection<PatchSetApproval> currentApprovals(Provider<ReviewDb> db)
|
||||
throws OrmException {
|
||||
Change c = change(db);
|
||||
if (c == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
return approvalsFor(db, c.currentPatchSetId());
|
||||
}
|
||||
|
||||
public Collection<PatchSetApproval> approvalsFor(Provider<ReviewDb> db,
|
||||
PatchSet.Id psId) throws OrmException {
|
||||
List<PatchSetApproval> r = new ArrayList<PatchSetApproval>();
|
||||
for (PatchSetApproval p : approvals(db)) {
|
||||
if (p.getPatchSetId().equals(psId)) {
|
||||
r.add(p);
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
public Collection<PatchSet> patches(Provider<ReviewDb> db)
|
||||
throws OrmException {
|
||||
if (patches == null) {
|
||||
|
@@ -70,6 +70,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
public static final String FIELD_IS = "is";
|
||||
public static final String FIELD_HAS = "has";
|
||||
public static final String FIELD_LABEL = "label";
|
||||
public static final String FIELD_LIMIT = "limit";
|
||||
public static final String FIELD_OWNER = "owner";
|
||||
public static final String FIELD_PROJECT = "project";
|
||||
public static final String FIELD_REF = "ref";
|
||||
@@ -334,7 +335,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
}
|
||||
|
||||
public Predicate<ChangeData> limit(int limit) {
|
||||
return new IntPredicate<ChangeData>("limit", limit) {
|
||||
return new IntPredicate<ChangeData>(FIELD_LIMIT, limit) {
|
||||
@Override
|
||||
public boolean match(ChangeData object) {
|
||||
return true;
|
||||
@@ -357,6 +358,22 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
return new SortKeyPredicate.Before(dbProvider, sortKey);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> resume_sortkey(String sortKey) {
|
||||
return sortkey_before(sortKey);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasLimit(Predicate<ChangeData> p) {
|
||||
return find(p, IntPredicate.class, FIELD_LIMIT) != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public boolean hasSortKey(Predicate<ChangeData> p) {
|
||||
return find(p, SortKeyPredicate.class, "sortkey_after") != null
|
||||
|| find(p, SortKeyPredicate.class, "sortkey_before") != null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
protected Predicate<ChangeData> defaultField(String query)
|
||||
|
@@ -105,6 +105,23 @@ public class ChangeQueryRewriter extends QueryRewriter<ChangeData> {
|
||||
return a.intValue() <= b.intValue() ? a : b;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NoCostComputation
|
||||
@Rewrite("A=(sortkey_before:*) B=(sortkey_before:*)")
|
||||
public Predicate<ChangeData> r00_oldestSortKey(
|
||||
@Named("A") SortKeyPredicate.Before a,
|
||||
@Named("B") SortKeyPredicate.Before b) {
|
||||
return a.getValue().compareTo(b.getValue()) <= 0 ? a : b;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@NoCostComputation
|
||||
@Rewrite("A=(sortkey_after:*) B=(sortkey_after:*)")
|
||||
public Predicate<ChangeData> r00_newestSortKey(
|
||||
@Named("A") SortKeyPredicate.After a, @Named("B") SortKeyPredicate.After b) {
|
||||
return a.getValue().compareTo(b.getValue()) >= 0 ? a : b;
|
||||
}
|
||||
|
||||
@Rewrite("status:open P=(project:*) S=(sortkey_after:*) L=(limit:*)")
|
||||
public Predicate<ChangeData> r10_byProjectOpenPrev(
|
||||
@Named("P") final ProjectPredicate p,
|
||||
|
@@ -0,0 +1,329 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.gerrit.reviewdb.Change;
|
||||
import com.google.gerrit.reviewdb.PatchSet;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.events.ChangeAttribute;
|
||||
import com.google.gerrit.server.events.EventFactory;
|
||||
import com.google.gerrit.server.events.QueryStats;
|
||||
import com.google.gerrit.server.query.Predicate;
|
||||
import com.google.gerrit.server.query.QueryParseException;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
|
||||
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.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
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 CurrentUser currentUser;
|
||||
private final EventFactory eventFactory;
|
||||
private final ChangeQueryBuilder queryBuilder;
|
||||
private final ChangeQueryRewriter queryRewriter;
|
||||
private final Provider<ReviewDb> db;
|
||||
|
||||
private int defaultLimit = 500;
|
||||
private OutputFormat outputFormat = OutputFormat.TEXT;
|
||||
private boolean includePatchSets;
|
||||
private boolean includeCurrentPatchSet;
|
||||
|
||||
private OutputStream outputStream = DisabledOutputStream.INSTANCE;
|
||||
private PrintWriter out;
|
||||
|
||||
@Inject
|
||||
QueryProcessor(CurrentUser currentUser, EventFactory eventFactory,
|
||||
ChangeQueryBuilder queryBuilder, ChangeQueryRewriter queryRewriter,
|
||||
Provider<ReviewDb> db) {
|
||||
this.currentUser = currentUser;
|
||||
this.eventFactory = eventFactory;
|
||||
this.queryBuilder = queryBuilder;
|
||||
this.queryRewriter = queryRewriter;
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public void setIncludePatchSets(boolean on) {
|
||||
includePatchSets = on;
|
||||
}
|
||||
|
||||
public void setIncludeCurrentPatchSet(boolean on) {
|
||||
includeCurrentPatchSet = 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 {
|
||||
try {
|
||||
final QueryStats stats = new QueryStats();
|
||||
stats.runTimeMilliseconds = System.currentTimeMillis();
|
||||
|
||||
final Predicate<ChangeData> visibleToMe =
|
||||
queryBuilder.visibleto(currentUser);
|
||||
|
||||
Predicate<ChangeData> s = compileQuery(queryString, visibleToMe);
|
||||
List<ChangeData> results = new ArrayList<ChangeData>();
|
||||
HashSet<Change.Id> want = new HashSet<Change.Id>();
|
||||
for (ChangeData d : ((ChangeDataSource) s).read()) {
|
||||
if (d.hasChange()) {
|
||||
// Checking visibleToMe here should be unnecessary, the
|
||||
// query should have already performed it. But we don't
|
||||
// want to trust the query rewriter that much yet.
|
||||
//
|
||||
if (visibleToMe.match(d)) {
|
||||
results.add(d);
|
||||
}
|
||||
} else {
|
||||
want.add(d.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (!want.isEmpty()) {
|
||||
for (Change c : db.get().changes().get(want)) {
|
||||
ChangeData d = new ChangeData(c);
|
||||
if (visibleToMe.match(d)) {
|
||||
results.add(d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Collections.sort(results, new Comparator<ChangeData>() {
|
||||
@Override
|
||||
public int compare(ChangeData a, ChangeData b) {
|
||||
return b.getChange().getSortKey().compareTo(
|
||||
a.getChange().getSortKey());
|
||||
}
|
||||
});
|
||||
|
||||
if (defaultLimit < results.size()) {
|
||||
results = results.subList(0, defaultLimit);
|
||||
}
|
||||
|
||||
for (ChangeData d : results) {
|
||||
ChangeAttribute c = eventFactory.asChangeAttribute(d.getChange());
|
||||
eventFactory.extend(c, d.getChange());
|
||||
eventFactory.addTrackingIds(c, d.trackingIds(db));
|
||||
|
||||
if (includePatchSets) {
|
||||
eventFactory.addPatchSets(c, d.patches(db));
|
||||
}
|
||||
|
||||
if (includeCurrentPatchSet) {
|
||||
PatchSet current = d.currentPatchSet(db);
|
||||
if (current != null) {
|
||||
c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
|
||||
eventFactory.addApprovals(c.currentPatchSet, //
|
||||
d.approvalsFor(db, current.getId()));
|
||||
}
|
||||
}
|
||||
|
||||
show(c);
|
||||
}
|
||||
|
||||
stats.rowCount = results.size();
|
||||
stats.runTimeMilliseconds =
|
||||
System.currentTimeMillis() - 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);
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
out.flush();
|
||||
} finally {
|
||||
out = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Predicate<ChangeData> compileQuery(String queryString,
|
||||
final Predicate<ChangeData> visibleToMe) throws QueryParseException {
|
||||
|
||||
Predicate<ChangeData> q = queryBuilder.parse(queryString);
|
||||
if (!queryBuilder.hasLimit(q)) {
|
||||
q = Predicate.and(q, queryBuilder.limit(defaultLimit));
|
||||
}
|
||||
if (!queryBuilder.hasSortKey(q)) {
|
||||
q = Predicate.and(q, queryBuilder.sortkey_before("z"));
|
||||
}
|
||||
q = Predicate.and(q, visibleToMe);
|
||||
|
||||
Predicate<ChangeData> s = queryRewriter.rewrite(q);
|
||||
if (!(s instanceof ChangeDataSource)) {
|
||||
s = queryRewriter.rewrite(Predicate.and(queryBuilder.status_open(), q));
|
||||
}
|
||||
|
||||
if (!(s instanceof ChangeDataSource)) {
|
||||
throw new QueryParseException("cannot execute query: " + s);
|
||||
}
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
indent(depth);
|
||||
out.print(f.getName());
|
||||
out.print(":");
|
||||
|
||||
if (val instanceof Long && isDateField(f.getName())) {
|
||||
out.print(' ');
|
||||
out.print(sdf.format(new Date(((Long) val) * 1000L)));
|
||||
out.print('\n');
|
||||
} else {
|
||||
showTextValue(val, depth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void indent(int depth) {
|
||||
for (int i = 0; i < depth; i++) {
|
||||
out.print(" ");
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings( {"cast", "unchecked"})
|
||||
private void showTextValue(Object value, int depth) {
|
||||
if (isPrimitive(value)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
|
||||
} else if (value instanceof Collection) {
|
||||
out.print('\n');
|
||||
for (Object thing : ((Collection) value)) {
|
||||
if (isPrimitive(thing)) {
|
||||
out.print(' ');
|
||||
out.print(value);
|
||||
out.print('\n');
|
||||
} else {
|
||||
showText(thing, depth + 1);
|
||||
out.print('\n');
|
||||
}
|
||||
}
|
||||
} else {
|
||||
out.print('\n');
|
||||
showText(value, depth + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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);
|
||||
}
|
||||
|
||||
private List<Field> fieldsOf(Class<?> type) {
|
||||
List<Field> r = new ArrayList<Field>();
|
||||
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;
|
||||
}
|
||||
}
|
@@ -36,6 +36,7 @@ public class DefaultCommandModule extends CommandModule {
|
||||
command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
|
||||
command(gerrit, "flush-caches").to(AdminFlushCaches.class);
|
||||
command(gerrit, "ls-projects").to(ListProjects.class);
|
||||
command(gerrit, "query").to(Query.class);
|
||||
command(gerrit, "show-caches").to(AdminShowCaches.class);
|
||||
command(gerrit, "show-connections").to(AdminShowConnections.class);
|
||||
command(gerrit, "show-queue").to(ShowQueue.class);
|
||||
|
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2010 The Android Open Source Project
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package com.google.gerrit.sshd.commands;
|
||||
|
||||
import com.google.gerrit.server.query.change.QueryProcessor;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
class Query extends BaseCommand {
|
||||
@Inject
|
||||
private QueryProcessor processor;
|
||||
|
||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||
void setFormat(QueryProcessor.OutputFormat format) {
|
||||
processor.setOutput(out, format);
|
||||
}
|
||||
|
||||
@Option(name = "--current-patch-set", usage = "Include information about current patch set")
|
||||
void setCurrentPatchSet(boolean on) {
|
||||
processor.setIncludeCurrentPatchSet(on);
|
||||
}
|
||||
|
||||
@Option(name = "--patch-sets", usage = "Include information about all patch sets")
|
||||
void setPatchSets(boolean on) {
|
||||
processor.setIncludePatchSets(on);
|
||||
}
|
||||
|
||||
@Argument(index = 0, required = true, multiValued = true, metaVar = "QUERY", usage = "Query to execute")
|
||||
private List<String> query;
|
||||
|
||||
@Override
|
||||
public void start(Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
processor.setOutput(out, QueryProcessor.OutputFormat.TEXT);
|
||||
parseCommandLine();
|
||||
processor.query(join(query, " "));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String join(List<String> list, String sep) {
|
||||
StringBuilder r = new StringBuilder();
|
||||
for (int i = 0; i < list.size(); i++) {
|
||||
if (i > 0) {
|
||||
r.append(sep);
|
||||
}
|
||||
r.append(list.get(i));
|
||||
}
|
||||
return r.toString();
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user