Merge changes I6f8b9302,If538bbc8,I4052ade1,Id07b2b62,I2014a220,I577ae60e,I87e58dda,I9a72cd28,Ic72892ae
* changes: REST API /projects/$SUGGEST REST API /projects/ REST API /accounts/self/capabilities Define a native REST API client /projects/: Support JSON output format Make ls-projects available on HTTP as GET /projects/ Support alias "self" in queries Update GWT to 2.4.0 Update Gson to 2.1
This commit is contained in:
@@ -23,7 +23,7 @@ group, all projects are listed.
|
||||
|
||||
ACCESS
|
||||
------
|
||||
Any user who has configured an SSH key.
|
||||
Any user who has configured an SSH key, or by an user over HTTP.
|
||||
|
||||
SCRIPTING
|
||||
---------
|
||||
@@ -64,6 +64,15 @@ Line-feeds are escaped to allow ls-project to keep the
|
||||
`all`:: Any type of project.
|
||||
--
|
||||
|
||||
--format::
|
||||
What output format to display the results in.
|
||||
+
|
||||
--
|
||||
`text`:: Simple text based format.
|
||||
`json`:: Map of JSON objects describing each project.
|
||||
`json_compact`:: Minimized JSON output.
|
||||
--
|
||||
|
||||
--all::
|
||||
Display all projects that are accessible by the calling user
|
||||
account. Besides the projects that the calling user account has
|
||||
@@ -72,12 +81,43 @@ Line-feeds are escaped to allow ls-project to keep the
|
||||
the 'READ' access right is not assigned to the calling user
|
||||
account).
|
||||
|
||||
--limit::
|
||||
Cap the number of results to the first N matches.
|
||||
|
||||
HTTP
|
||||
----
|
||||
This command is also available over HTTP, as `/projects/` for
|
||||
anonymous access and `/a/projects/` for authenticated access.
|
||||
Named options are available as query parameters. Results can
|
||||
be limited to projects matching a prefix by supplying the prefix
|
||||
as part of the URL, for example `/projects/external/` lists only
|
||||
projects whose name start with the string `external/`.
|
||||
|
||||
Over HTTP the `json_compact` output format is assumed if the client
|
||||
explicitly asks for JSON using HTTP header `Accept: application/json`.
|
||||
When any JSON output format is used on HTTP, readers must skip the
|
||||
first line produced. The first line is a garbage JSON string crafted
|
||||
to prevent a browser from executing the response in a script tag.
|
||||
|
||||
Output will be gzip compressed if `Accept-Encoding: gzip` was used
|
||||
by the client in the request headers.
|
||||
|
||||
EXAMPLES
|
||||
--------
|
||||
|
||||
List visible projects:
|
||||
=====
|
||||
$ ssh -p 29418 review.example.com gerrit ls-projects
|
||||
platform/manifest
|
||||
tools/gerrit
|
||||
tools/gwtorm
|
||||
|
||||
$ curl http://review.example.com/projects/
|
||||
platform/manifest
|
||||
tools/gerrit
|
||||
tools/gwtorm
|
||||
|
||||
$ curl http://review.example.com/projects/tools/
|
||||
tools/gerrit
|
||||
tools/gwtorm
|
||||
=====
|
||||
|
@@ -18,6 +18,7 @@ User Guide
|
||||
* link:user-signedoffby.html[Signed-off-by Lines]
|
||||
* link:access-control.html[Access Controls]
|
||||
* link:error-messages.html[Error Messages]
|
||||
* link:rest-api.html[REST API]
|
||||
* link:user-submodules.html[Subscribing to Git Submodules]
|
||||
|
||||
Installation
|
||||
|
134
Documentation/rest-api.txt
Normal file
134
Documentation/rest-api.txt
Normal file
@@ -0,0 +1,134 @@
|
||||
Gerrit Code Review - REST API
|
||||
=============================
|
||||
|
||||
Gerrit Code Review comes with a REST like API available over HTTP.
|
||||
The API is suitable for automated tools to build upon, as well as
|
||||
supporting some ad-hoc scripting use cases.
|
||||
|
||||
Protocol Details
|
||||
----------------
|
||||
|
||||
Authentication
|
||||
~~~~~~~~~~~~~~
|
||||
By default all REST endpoints assume anonymous access and filter
|
||||
results to correspond to what anonymous users can read (which may
|
||||
be nothing at all).
|
||||
|
||||
Users (and programs) may authenticate using HTTP authentication by
|
||||
supplying the HTTP password from the user's account settings page.
|
||||
Gerrit by default uses HTTP digest authentication. To authenticate,
|
||||
prefix the endpoint URL with `/a/`. For example to authenticate to
|
||||
`/projects/` request URL `/a/projects/`.
|
||||
|
||||
Output Format
|
||||
~~~~~~~~~~~~~
|
||||
Most APIs return text format by default. JSON can be requested
|
||||
by setting the `Accept` HTTP request header to include
|
||||
`application/json`, for example:
|
||||
|
||||
----
|
||||
GET /projects/ HTTP/1.0
|
||||
Accept: application/json
|
||||
----
|
||||
|
||||
JSON responses are encoded using UTF-8 and use content type
|
||||
`application/json`. The JSON response body starts with magic prefix
|
||||
line that must be stripped before feeding the rest of the response
|
||||
body to a JSON parser:
|
||||
|
||||
----
|
||||
)]}'
|
||||
[ ... valid JSON ... ]
|
||||
----
|
||||
|
||||
The default JSON format is `JSON_COMPACT`, which skips unnecessary
|
||||
whitespace. This is not the easiest format for a human to read. Many
|
||||
examples in this documentation use `format=JSON` as a query parameter
|
||||
to obtain pretty formatting in the response. Producing (and parsing)
|
||||
the compact format is more efficient, so most tools should prefer the
|
||||
default compact format.
|
||||
|
||||
Responses will be gzip compressed by the server if the HTTP
|
||||
`Accept-Encoding` request header is set to `gzip`. This may
|
||||
save on network transfer time for larger responses.
|
||||
|
||||
Endpoints
|
||||
---------
|
||||
|
||||
[[accounts_self_capabilities]]
|
||||
/accounts/self/capabilities (Account Capabilities)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Returns the global capabilities (such as createProject or
|
||||
createGroup) that are enabled for the calling user. This can be used
|
||||
by UI tools to discover if administrative features are available
|
||||
to the caller, so they can hide (or show) relevant UI actions.
|
||||
|
||||
----
|
||||
GET /accounts/self/capabilities?format=JSON HTTP/1.0
|
||||
|
||||
)]}'
|
||||
{
|
||||
"queryLimit": {
|
||||
"min": 0,
|
||||
"max": 500
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Administrator that has authenticated with digest authentication:
|
||||
----
|
||||
GET /a/accounts/self/capabilities?format=JSON HTTP/1.0
|
||||
Authorization: Digest username="admin", realm="Gerrit Code Review", nonce="...
|
||||
|
||||
)]}'
|
||||
{
|
||||
"administrateServer": true,
|
||||
"queryLimit": {
|
||||
"min": 0,
|
||||
"max": 500
|
||||
},
|
||||
"createAccount": true,
|
||||
"createGroup": true,
|
||||
"createProject": true,
|
||||
"killTask": true,
|
||||
"viewCaches": true,
|
||||
"flushCaches": true,
|
||||
"viewConnections": true,
|
||||
"viewQueue": true,
|
||||
"startReplication": true
|
||||
}
|
||||
----
|
||||
|
||||
[[projects]]
|
||||
/projects/ (List Projects)
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
Lists the projects accessible by the caller. This is the same as
|
||||
using the link:cmd-ls-projects.html[ls-projects] command over SSH,
|
||||
and accepts the same options as query parameters.
|
||||
|
||||
----
|
||||
GET /projects/?format=JSON&d HTTP/1.0
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Disposition: attachment
|
||||
Content-Type: application/json;charset=UTF-8
|
||||
|
||||
)]}'
|
||||
{
|
||||
"external/bison": {
|
||||
"description": "GNU parser generator"
|
||||
},
|
||||
"external/gcc": {},
|
||||
"external/openssl": {
|
||||
"description": "encryption\ncrypto routines"
|
||||
},
|
||||
"test": {
|
||||
"description": "\u003chtml\u003e is escaped"
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
GERRIT
|
||||
------
|
||||
Part of link:index.html[Gerrit Code Review]
|
@@ -75,7 +75,8 @@ Change-Id that was scraped out of the commit message.
|
||||
[[owner]]
|
||||
owner:'USER'::
|
||||
+
|
||||
Changes originally submitted by 'USER'.
|
||||
Changes originally submitted by 'USER'. The special case of
|
||||
`owner:self` will find changes owned by the caller.
|
||||
|
||||
[[ownerin]]
|
||||
ownerin:'GROUP'::
|
||||
@@ -85,7 +86,9 @@ Changes originally submitted by a user in 'GROUP'.
|
||||
[[reviewer]]
|
||||
reviewer:'USER'::
|
||||
+
|
||||
Changes that have been, or need to be, reviewed by 'USER'.
|
||||
Changes that have been, or need to be, reviewed by 'USER'. The
|
||||
special case of `reviewer:self` will find changes where the caller
|
||||
has been added as a reviewer.
|
||||
|
||||
[[reviewerin]]
|
||||
reviewerin:'GROUP'::
|
||||
@@ -213,6 +216,16 @@ is:reviewed::
|
||||
True if there is at least one non-zero score on the change, in any
|
||||
approval category, by any user.
|
||||
|
||||
is:owner::
|
||||
+
|
||||
True on any change where the current user is the change owner.
|
||||
Same as `owner:self`.
|
||||
|
||||
is:reviewer::
|
||||
+
|
||||
True on any change where the current user is a reviewer.
|
||||
Same as `reviewer:self`.
|
||||
|
||||
is:open::
|
||||
+
|
||||
True if the change is other open or submitted, merge pending.
|
||||
@@ -373,16 +386,20 @@ the change. This flag is always added to any query.
|
||||
starredby:'USER'::
|
||||
+
|
||||
Matches changes that have been starred by 'USER'.
|
||||
The special case `starredby:self` applies to the caller.
|
||||
|
||||
watchedby:'USER'::
|
||||
+
|
||||
Matches changes that 'USER' has configured watch filters for.
|
||||
The special case `watchedby:self` applies to the caller.
|
||||
|
||||
draftby:'USER'::
|
||||
+
|
||||
Matches changes that 'USER' has left unpublished drafts on.
|
||||
Since the drafts are unpublished, it is not possible to see the
|
||||
draft text, or even how many drafts there are.
|
||||
draft text, or even how many drafts there are. The special case
|
||||
of `draftby:self` will find changes where the caller has created
|
||||
a draft comment.
|
||||
|
||||
limit:'CNT'::
|
||||
+
|
||||
|
@@ -15,6 +15,8 @@
|
||||
package com.google.gerrit.common.data;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/** Server wide capabilities. Represented as {@link Permission} objects. */
|
||||
@@ -73,23 +75,34 @@ public class GlobalCapability {
|
||||
/** Can view all pending tasks in the queue (not just the filtered set). */
|
||||
public static final String VIEW_QUEUE = "viewQueue";
|
||||
|
||||
private static final List<String> NAMES_ALL;
|
||||
private static final List<String> NAMES_LC;
|
||||
|
||||
static {
|
||||
NAMES_LC = new ArrayList<String>();
|
||||
NAMES_LC.add(ADMINISTRATE_SERVER.toLowerCase());
|
||||
NAMES_LC.add(CREATE_ACCOUNT.toLowerCase());
|
||||
NAMES_LC.add(CREATE_GROUP.toLowerCase());
|
||||
NAMES_LC.add(CREATE_PROJECT.toLowerCase());
|
||||
NAMES_LC.add(EMAIL_REVIEWERS.toLowerCase());
|
||||
NAMES_LC.add(FLUSH_CACHES.toLowerCase());
|
||||
NAMES_LC.add(KILL_TASK.toLowerCase());
|
||||
NAMES_LC.add(PRIORITY.toLowerCase());
|
||||
NAMES_LC.add(QUERY_LIMIT.toLowerCase());
|
||||
NAMES_LC.add(START_REPLICATION.toLowerCase());
|
||||
NAMES_LC.add(VIEW_CACHES.toLowerCase());
|
||||
NAMES_LC.add(VIEW_CONNECTIONS.toLowerCase());
|
||||
NAMES_LC.add(VIEW_QUEUE.toLowerCase());
|
||||
NAMES_ALL = new ArrayList<String>();
|
||||
NAMES_ALL.add(ADMINISTRATE_SERVER);
|
||||
NAMES_ALL.add(CREATE_ACCOUNT);
|
||||
NAMES_ALL.add(CREATE_GROUP);
|
||||
NAMES_ALL.add(CREATE_PROJECT);
|
||||
NAMES_ALL.add(EMAIL_REVIEWERS);
|
||||
NAMES_ALL.add(FLUSH_CACHES);
|
||||
NAMES_ALL.add(KILL_TASK);
|
||||
NAMES_ALL.add(PRIORITY);
|
||||
NAMES_ALL.add(QUERY_LIMIT);
|
||||
NAMES_ALL.add(START_REPLICATION);
|
||||
NAMES_ALL.add(VIEW_CACHES);
|
||||
NAMES_ALL.add(VIEW_CONNECTIONS);
|
||||
NAMES_ALL.add(VIEW_QUEUE);
|
||||
|
||||
NAMES_LC = new ArrayList<String>(NAMES_ALL.size());
|
||||
for (String name : NAMES_ALL) {
|
||||
NAMES_LC.add(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/** @return all valid capability names. */
|
||||
public static Collection<String> getAllNames() {
|
||||
return Collections.unmodifiableList(NAMES_ALL);
|
||||
}
|
||||
|
||||
/** @return true if the name is recognized as a capability name. */
|
||||
|
@@ -26,9 +26,6 @@ import java.util.List;
|
||||
|
||||
@RpcImpl(version = Version.V2_0)
|
||||
public interface SuggestService extends RemoteJsonService {
|
||||
void suggestProjectNameKey(String query, int limit,
|
||||
AsyncCallback<List<Project.NameKey>> callback);
|
||||
|
||||
void suggestAccount(String query, Boolean enabled, int limit,
|
||||
AsyncCallback<List<AccountInfo>> callback);
|
||||
|
||||
|
@@ -56,6 +56,10 @@ public class RpcStatus implements RpcStartHandler, RpcCompleteHandler {
|
||||
|
||||
@Override
|
||||
public void onRpcStart(final RpcStartEvent event) {
|
||||
onRpcStart();
|
||||
}
|
||||
|
||||
public void onRpcStart() {
|
||||
if (++activeCalls == 1) {
|
||||
if (hideDepth == 0) {
|
||||
loading.setVisible(true);
|
||||
@@ -65,6 +69,10 @@ public class RpcStatus implements RpcStartHandler, RpcCompleteHandler {
|
||||
|
||||
@Override
|
||||
public void onRpcComplete(final RpcCompleteEvent event) {
|
||||
onRpcComplete();
|
||||
}
|
||||
|
||||
public void onRpcComplete() {
|
||||
if (--activeCalls == 0) {
|
||||
loading.setVisible(false);
|
||||
}
|
||||
|
@@ -0,0 +1,36 @@
|
||||
// Copyright (C) 2012 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.client.account;
|
||||
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
|
||||
/** Capabilities the caller has from {@code /accounts/self/capabilities}. */
|
||||
public class AccountCapabilities extends JavaScriptObject {
|
||||
public static void all(AsyncCallback<AccountCapabilities> cb, String... filter) {
|
||||
RestApi api = new RestApi("/accounts/self/capabilities");
|
||||
for (String name : filter) {
|
||||
api.addParameter("q", name);
|
||||
}
|
||||
api.send(cb);
|
||||
}
|
||||
|
||||
protected AccountCapabilities() {
|
||||
}
|
||||
|
||||
public final native boolean canPerform(String name)
|
||||
/*-{ return this[name] ? true : false; }-*/;
|
||||
}
|
@@ -15,6 +15,7 @@
|
||||
package com.google.gerrit.client.account;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.projects.ProjectMap;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.HintTextBox;
|
||||
@@ -22,7 +23,6 @@ import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
|
||||
import com.google.gerrit.client.ui.ProjectsTable;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.data.AccountProjectWatchInfo;
|
||||
import com.google.gerrit.common.data.ProjectList;
|
||||
import com.google.gwt.event.dom.client.ClickEvent;
|
||||
import com.google.gwt.event.dom.client.ClickHandler;
|
||||
import com.google.gwt.event.dom.client.KeyCodes;
|
||||
@@ -226,14 +226,14 @@ public class MyWatchedProjectsScreen extends SettingsScreen implements
|
||||
|
||||
// prevent user input from being overwritten by simply poping up
|
||||
if (! popingUp || "".equals(nameBox.getText()) ) {
|
||||
nameBox.setText(getRowItem(row).getName());
|
||||
nameBox.setText(getRowItem(row).name());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onOpenRow(final int row) {
|
||||
super.onOpenRow(row);
|
||||
nameBox.setText(getRowItem(row).getName());
|
||||
nameBox.setText(getRowItem(row).name());
|
||||
doAddNew();
|
||||
}
|
||||
};
|
||||
@@ -361,11 +361,10 @@ public class MyWatchedProjectsScreen extends SettingsScreen implements
|
||||
}
|
||||
|
||||
protected void populateProjects() {
|
||||
Util.PROJECT_SVC.visibleProjects(
|
||||
new GerritCallback<ProjectList>() {
|
||||
ProjectMap.all(new GerritCallback<ProjectMap>() {
|
||||
@Override
|
||||
public void onSuccess(final ProjectList result) {
|
||||
projectsTab.display(result.getProjects());
|
||||
public void onSuccess(final ProjectMap result) {
|
||||
projectsTab.display(result);
|
||||
if (firstPopupLoad) { // Display was delayed until table was loaded
|
||||
firstPopupLoad = false;
|
||||
displayPopup();
|
||||
|
@@ -17,6 +17,8 @@ package com.google.gerrit.client.admin;
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.ErrorDialog;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.projects.ProjectInfo;
|
||||
import com.google.gerrit.client.projects.ProjectMap;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.ui.HintTextBox;
|
||||
import com.google.gerrit.client.ui.ProjectNameSuggestOracle;
|
||||
@@ -38,8 +40,6 @@ import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
import com.google.gwtexpui.globalkey.client.NpTextBox;
|
||||
import com.google.gwtjsonrpc.common.VoidResult;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class CreateProjectScreen extends Screen {
|
||||
private NpTextBox project;
|
||||
private Button create;
|
||||
@@ -127,31 +127,30 @@ public class CreateProjectScreen extends Screen {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populate(final int row, final Project k) {
|
||||
final Anchor projectLink = new Anchor(k.getName());
|
||||
protected void populate(final int row, final ProjectInfo k) {
|
||||
final Anchor projectLink = new Anchor(k.name());
|
||||
projectLink.addClickHandler(new ClickHandler() {
|
||||
|
||||
@Override
|
||||
public void onClick(ClickEvent event) {
|
||||
sugestParent.setText(getRowItem(row).getName());
|
||||
sugestParent.setText(getRowItem(row).name());
|
||||
}
|
||||
});
|
||||
|
||||
table.setWidget(row, 1, projectLink);
|
||||
table.setText(row, 2, k.getDescription());
|
||||
table.setText(row, 2, k.description());
|
||||
|
||||
setRowItem(row, k);
|
||||
}
|
||||
};
|
||||
suggestedParentsTab.setVisible(false);
|
||||
|
||||
Util.PROJECT_SVC
|
||||
.suggestParentCandidates(new GerritCallback<List<Project>>() {
|
||||
ProjectMap.permissions(new GerritCallback<ProjectMap>() {
|
||||
@Override
|
||||
public void onSuccess(List<Project> result) {
|
||||
if (result != null && !result.isEmpty()) {
|
||||
public void onSuccess(ProjectMap list) {
|
||||
if (!list.isEmpty()) {
|
||||
suggestedParentsTab.setVisible(true);
|
||||
suggestedParentsTab.display(result);
|
||||
suggestedParentsTab.display(list);
|
||||
suggestedParentsTab.finishDisplay();
|
||||
}
|
||||
}
|
||||
|
@@ -14,8 +14,11 @@
|
||||
|
||||
package com.google.gerrit.client.admin;
|
||||
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.account.AccountCapabilities;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.AccountScreen;
|
||||
@@ -48,11 +51,17 @@ public class GroupListScreen extends AccountScreen {
|
||||
@Override
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
addPanel.setVisible(false);
|
||||
AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
|
||||
@Override
|
||||
public void onSuccess(AccountCapabilities ac) {
|
||||
addPanel.setVisible(ac.canPerform(CREATE_GROUP));
|
||||
}
|
||||
}, CREATE_GROUP);
|
||||
Util.GROUP_SVC
|
||||
.visibleGroups(new ScreenLoadCallback<GroupList>(this) {
|
||||
@Override
|
||||
protected void preDisplay(GroupList result) {
|
||||
addPanel.setVisible(result.isCanCreateGroup());
|
||||
groups.display(result.getGroups());
|
||||
groups.finishDisplay();
|
||||
}
|
||||
|
@@ -14,15 +14,19 @@
|
||||
|
||||
package com.google.gerrit.client.admin;
|
||||
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.account.AccountCapabilities;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.projects.ProjectInfo;
|
||||
import com.google.gerrit.client.projects.ProjectMap;
|
||||
import com.google.gerrit.client.rpc.ScreenLoadCallback;
|
||||
import com.google.gerrit.client.ui.Hyperlink;
|
||||
import com.google.gerrit.client.ui.ProjectsTable;
|
||||
import com.google.gerrit.client.ui.Screen;
|
||||
import com.google.gerrit.common.PageLinks;
|
||||
import com.google.gerrit.common.data.ProjectList;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwt.user.client.History;
|
||||
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||
|
||||
@@ -33,11 +37,17 @@ public class ProjectListScreen extends Screen {
|
||||
@Override
|
||||
protected void onLoad() {
|
||||
super.onLoad();
|
||||
Util.PROJECT_SVC.visibleProjects(new ScreenLoadCallback<ProjectList>(this) {
|
||||
createProjectLinkPanel.setVisible(false);
|
||||
AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
|
||||
@Override
|
||||
protected void preDisplay(final ProjectList result) {
|
||||
createProjectLinkPanel.setVisible(result.canCreateProject());
|
||||
projects.display(result.getProjects());
|
||||
public void onSuccess(AccountCapabilities ac) {
|
||||
createProjectLinkPanel.setVisible(ac.canPerform(CREATE_PROJECT));
|
||||
}
|
||||
}, CREATE_PROJECT);
|
||||
ProjectMap.all(new ScreenLoadCallback<ProjectMap>(this) {
|
||||
@Override
|
||||
protected void preDisplay(final ProjectMap result) {
|
||||
projects.display(result);
|
||||
projects.finishDisplay();
|
||||
}
|
||||
});
|
||||
@@ -61,14 +71,14 @@ public class ProjectListScreen extends Screen {
|
||||
History.newItem(link(getRowItem(row)));
|
||||
}
|
||||
|
||||
private String link(final Project item) {
|
||||
return Dispatcher.toProjectAdmin(item.getNameKey(), ProjectScreen.INFO);
|
||||
private String link(final ProjectInfo item) {
|
||||
return Dispatcher.toProjectAdmin(item.name_key(), ProjectScreen.INFO);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void populate(final int row, final Project k) {
|
||||
table.setWidget(row, 1, new Hyperlink(k.getName(), link(k)));
|
||||
table.setText(row, 2, k.getDescription());
|
||||
protected void populate(final int row, final ProjectInfo k) {
|
||||
table.setWidget(row, 1, new Hyperlink(k.name(), link(k)));
|
||||
table.setText(row, 2, k.description());
|
||||
|
||||
setRowItem(row, k);
|
||||
}
|
||||
|
@@ -0,0 +1,46 @@
|
||||
// Copyright (C) 2012 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.client.projects;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle;
|
||||
|
||||
public class ProjectInfo
|
||||
extends JavaScriptObject
|
||||
implements SuggestOracle.Suggestion {
|
||||
public final Project.NameKey name_key() {
|
||||
return new Project.NameKey(name());
|
||||
}
|
||||
|
||||
public final native String name() /*-{ return this.name; }-*/;
|
||||
public final native String description() /*-{ return this.description; }-*/;
|
||||
|
||||
@Override
|
||||
public final String getDisplayString() {
|
||||
if (description() != null) {
|
||||
return name() + " (" + description() + ")";
|
||||
}
|
||||
return name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final String getReplacementString() {
|
||||
return name();
|
||||
}
|
||||
|
||||
protected ProjectInfo() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
// Copyright (C) 2012 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.client.projects;
|
||||
|
||||
import com.google.gerrit.client.rpc.NativeMap;
|
||||
import com.google.gerrit.client.rpc.RestApi;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
import com.google.gwt.http.client.URL;
|
||||
|
||||
/** Projects available from {@code /projects/}. */
|
||||
public class ProjectMap extends NativeMap<ProjectInfo> {
|
||||
public static void all(AsyncCallback<ProjectMap> callback) {
|
||||
new RestApi("/projects/")
|
||||
.addParameterRaw("type", "ALL")
|
||||
.addParameterTrue("all")
|
||||
.addParameterTrue("d") // description
|
||||
.send(NativeMap.copyKeysIntoChildren(callback));
|
||||
}
|
||||
|
||||
public static void permissions(AsyncCallback<ProjectMap> callback) {
|
||||
new RestApi("/projects/")
|
||||
.addParameterRaw("type", "PERMISSIONS")
|
||||
.addParameterTrue("all")
|
||||
.addParameterTrue("d") // description
|
||||
.send(NativeMap.copyKeysIntoChildren(callback));
|
||||
}
|
||||
|
||||
public static void suggest(String prefix, int limit, AsyncCallback<ProjectMap> cb) {
|
||||
new RestApi("/projects/" + URL.encode(prefix).replaceAll("[?]", "%3F"))
|
||||
.addParameterRaw("type", "ALL")
|
||||
.addParameter("n", limit)
|
||||
.addParameterTrue("d") // description
|
||||
.send(NativeMap.copyKeysIntoChildren(cb));
|
||||
}
|
||||
|
||||
protected ProjectMap() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,55 @@
|
||||
// Copyright (C) 2012 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.client.rpc;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
|
||||
import java.util.AbstractList;
|
||||
import java.util.List;
|
||||
|
||||
/** A read-only list of native JavaScript objects stored in a JSON array. */
|
||||
public class NativeList<T extends JavaScriptObject> extends JavaScriptObject {
|
||||
protected NativeList() {
|
||||
}
|
||||
|
||||
public final List<T> asList() {
|
||||
return new AbstractList<T>() {
|
||||
@Override
|
||||
public T set(int index, T element) {
|
||||
T old = NativeList.this.get(index);
|
||||
NativeList.this.set0(index, element);
|
||||
return old;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T get(int index) {
|
||||
return NativeList.this.get(index);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int size() {
|
||||
return NativeList.this.size();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public final boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
public final native int size() /*-{ return this.length; }-*/;
|
||||
public final native T get(int i) /*-{ return this[i]; }-*/;
|
||||
private final native void set0(int i, T v) /*-{ this[i] = v; }-*/;
|
||||
}
|
@@ -0,0 +1,93 @@
|
||||
// Copyright (C) 2012 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.client.rpc;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
/** A map of native JSON objects, keyed by a string. */
|
||||
public class NativeMap<T extends JavaScriptObject> extends JavaScriptObject {
|
||||
/**
|
||||
* Loop through the result map's entries and copy the key strings into the
|
||||
* "name" property of the corresponding child object. This only runs on the
|
||||
* top level map of the result, and requires the children to be JSON objects
|
||||
* and not a JSON primitive (e.g. boolean or string).
|
||||
*/
|
||||
public static <T extends JavaScriptObject,
|
||||
M extends NativeMap<T>> AsyncCallback<M> copyKeysIntoChildren(
|
||||
AsyncCallback<M> callback) {
|
||||
return copyKeysIntoChildren("name", callback);
|
||||
}
|
||||
|
||||
/** Loop through the result map and set asProperty on the children. */
|
||||
public static <T extends JavaScriptObject,
|
||||
M extends NativeMap<T>> AsyncCallback<M> copyKeysIntoChildren(
|
||||
final String asProperty, AsyncCallback<M> callback) {
|
||||
return new TransformCallback<M, M>(callback) {
|
||||
@Override
|
||||
protected M transform(M result) {
|
||||
result.copyKeysIntoChildren(asProperty);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected NativeMap() {
|
||||
}
|
||||
|
||||
public final Set<String> keySet() {
|
||||
return Natives.keys(this);
|
||||
}
|
||||
|
||||
public final native NativeList<T> values()
|
||||
/*-{
|
||||
var s = this;
|
||||
var v = [];
|
||||
var i = 0;
|
||||
for (var k in s) {
|
||||
if (s.hasOwnProperty(k)) {
|
||||
v[i++] = s[k];
|
||||
}
|
||||
}
|
||||
return v;
|
||||
}-*/;
|
||||
|
||||
public final int size() {
|
||||
return keySet().size();
|
||||
}
|
||||
|
||||
public final boolean isEmpty() {
|
||||
return size() == 0;
|
||||
}
|
||||
|
||||
public final boolean containsKey(String n) {
|
||||
return get(n) != null;
|
||||
}
|
||||
|
||||
public final native T get(String n) /*-{ return this[n]; }-*/;
|
||||
|
||||
public final native void copyKeysIntoChildren(String p)
|
||||
/*-{
|
||||
var s = this;
|
||||
for (var k in s) {
|
||||
if (s.hasOwnProperty(k)) {
|
||||
var c = s[k];
|
||||
c[p] = k;
|
||||
}
|
||||
}
|
||||
}-*/;
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
// Copyright (C) 2012 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.client.rpc;
|
||||
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.json.client.JSONObject;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
public class Natives {
|
||||
/**
|
||||
* Get the names of defined properties on the object. The returned set
|
||||
* iterates in the native iteration order, which may match the source order.
|
||||
*/
|
||||
public static Set<String> keys(JavaScriptObject obj) {
|
||||
if (obj != null) {
|
||||
return new JSONObject(obj).keySet();
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
public static <T extends JavaScriptObject> T parseJSON(String json) {
|
||||
if (parser == null) {
|
||||
parser = bestJsonParser();
|
||||
}
|
||||
return parse0(parser, json);
|
||||
}
|
||||
|
||||
private static native <T extends JavaScriptObject>
|
||||
T parse0(JavaScriptObject p, String s)
|
||||
/*-{ return p(s); }-*/;
|
||||
|
||||
private static JavaScriptObject parser;
|
||||
private static native JavaScriptObject bestJsonParser()
|
||||
/*-{
|
||||
if ($wnd.JSON && typeof $wnd.JSON.parse === 'function')
|
||||
return $wnd.JSON.parse;
|
||||
return function(s) { return eval('(' + s + ')'); };
|
||||
}-*/;
|
||||
|
||||
private Natives() {
|
||||
}
|
||||
}
|
@@ -0,0 +1,174 @@
|
||||
// Copyright (C) 2012 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.client.rpc;
|
||||
|
||||
import com.google.gerrit.client.RpcStatus;
|
||||
import com.google.gwt.core.client.GWT;
|
||||
import com.google.gwt.core.client.JavaScriptObject;
|
||||
import com.google.gwt.http.client.Request;
|
||||
import com.google.gwt.http.client.RequestBuilder;
|
||||
import com.google.gwt.http.client.RequestCallback;
|
||||
import com.google.gwt.http.client.RequestException;
|
||||
import com.google.gwt.http.client.Response;
|
||||
import com.google.gwt.http.client.URL;
|
||||
import com.google.gwt.user.client.rpc.StatusCodeException;
|
||||
import com.google.gwtjsonrpc.client.RemoteJsonException;
|
||||
import com.google.gwtjsonrpc.client.ServerUnavailableException;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
import com.google.gwtjsonrpc.common.JsonConstants;
|
||||
|
||||
/** Makes a REST API call to the server. */
|
||||
public class RestApi {
|
||||
/**
|
||||
* Expected JSON content body prefix that prevents XSSI.
|
||||
* <p>
|
||||
* The server always includes this line as the first line of the response
|
||||
* content body when the response body is formatted as JSON. It gets inserted
|
||||
* by the server to prevent the resource from being imported into another
|
||||
* domain's page using a <script> tag. This line must be removed before
|
||||
* the JSON can be parsed.
|
||||
*/
|
||||
private static final String JSON_MAGIC = ")]}'\n";
|
||||
|
||||
private StringBuilder url;
|
||||
private boolean hasQueryParams;
|
||||
|
||||
/**
|
||||
* Initialize a new API call.
|
||||
* <p>
|
||||
* By default the JSON format will be selected by including an HTTP Accept
|
||||
* header in the request.
|
||||
*
|
||||
* @param name URL of the REST resource to access, e.g. {@code "/projects/"}
|
||||
* to list accessible projects from the server.
|
||||
*/
|
||||
public RestApi(String name) {
|
||||
if (name.startsWith("/")) {
|
||||
name = name.substring(1);
|
||||
}
|
||||
|
||||
url = new StringBuilder();
|
||||
url.append(GWT.getHostPageBaseURL());
|
||||
url.append(name);
|
||||
}
|
||||
|
||||
public RestApi addParameter(String name, String value) {
|
||||
return addParameterRaw(name, URL.encodeQueryString(value));
|
||||
}
|
||||
|
||||
public RestApi addParameterTrue(String name) {
|
||||
return addParameterRaw(name, null);
|
||||
}
|
||||
|
||||
public RestApi addParameter(String name, boolean value) {
|
||||
return addParameterRaw(name, value ? "t" : "f");
|
||||
}
|
||||
|
||||
public RestApi addParameter(String name, int value) {
|
||||
return addParameterRaw(name, String.valueOf(value));
|
||||
}
|
||||
|
||||
public RestApi addParameterRaw(String name, String value) {
|
||||
if (hasQueryParams) {
|
||||
url.append("&");
|
||||
} else {
|
||||
url.append("?");
|
||||
hasQueryParams = true;
|
||||
}
|
||||
url.append(name);
|
||||
if (value != null) {
|
||||
url.append("=").append(value);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T extends JavaScriptObject> void send(final AsyncCallback<T> cb) {
|
||||
RequestBuilder req = new RequestBuilder(RequestBuilder.GET, url.toString());
|
||||
req.setHeader("Accept", JsonConstants.JSON_TYPE);
|
||||
req.setCallback(new RequestCallback() {
|
||||
@Override
|
||||
public void onResponseReceived(Request req, Response res) {
|
||||
RpcStatus.INSTANCE.onRpcComplete();
|
||||
int status = res.getStatusCode();
|
||||
if (status != 200) {
|
||||
if ((400 <= status && status < 500) && isTextBody(res)) {
|
||||
cb.onFailure(new RemoteJsonException(res.getText(), status, null));
|
||||
} else {
|
||||
cb.onFailure(new StatusCodeException(status, res.getStatusText()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isJsonBody(res)) {
|
||||
cb.onFailure(new RemoteJsonException("Invalid JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
String json = res.getText();
|
||||
if (!json.startsWith(JSON_MAGIC)) {
|
||||
cb.onFailure(new RemoteJsonException("Invalid JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
T data;
|
||||
try {
|
||||
data = Natives.parseJSON(json.substring(JSON_MAGIC.length()));
|
||||
} catch (RuntimeException e) {
|
||||
cb.onFailure(new RemoteJsonException("Invalid JSON"));
|
||||
return;
|
||||
}
|
||||
|
||||
cb.onSuccess(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Request req, Throwable err) {
|
||||
RpcStatus.INSTANCE.onRpcComplete();
|
||||
if (err.getMessage().contains("XmlHttpRequest.status")) {
|
||||
cb.onFailure(new ServerUnavailableException());
|
||||
} else {
|
||||
cb.onFailure(err);
|
||||
}
|
||||
}
|
||||
});
|
||||
try {
|
||||
RpcStatus.INSTANCE.onRpcStart();
|
||||
req.send();
|
||||
} catch (RequestException e) {
|
||||
RpcStatus.INSTANCE.onRpcComplete();
|
||||
cb.onFailure(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isJsonBody(Response res) {
|
||||
return isContentType(res, JsonConstants.JSON_TYPE);
|
||||
}
|
||||
|
||||
private static boolean isTextBody(Response res) {
|
||||
return isContentType(res, "text/plain");
|
||||
}
|
||||
|
||||
private static boolean isContentType(Response res, String want) {
|
||||
String type = res.getHeader("Content-Type");
|
||||
if (type == null) {
|
||||
return false;
|
||||
}
|
||||
int semi = type.indexOf(';');
|
||||
if (semi >= 0) {
|
||||
type = type.substring(0, semi).trim();
|
||||
}
|
||||
return want.equals(type);
|
||||
}
|
||||
}
|
@@ -0,0 +1,38 @@
|
||||
// Copyright (C) 2012 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.client.rpc;
|
||||
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
|
||||
/** Transforms a value and passes it on to another callback. */
|
||||
public abstract class TransformCallback<I, O> implements AsyncCallback<I>{
|
||||
private final AsyncCallback<O> callback;
|
||||
|
||||
protected TransformCallback(AsyncCallback<O> callback) {
|
||||
this.callback = callback;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuccess(I result) {
|
||||
callback.onSuccess(transform(result));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable caught) {
|
||||
callback.onFailure(caught);
|
||||
}
|
||||
|
||||
protected abstract O transform(I result);
|
||||
}
|
@@ -15,49 +15,25 @@
|
||||
package com.google.gerrit.client.ui;
|
||||
|
||||
import com.google.gerrit.client.RpcStatus;
|
||||
import com.google.gerrit.client.projects.ProjectMap;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwt.user.client.ui.SuggestOracle;
|
||||
import com.google.gwtexpui.safehtml.client.HighlightSuggestOracle;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Suggestion Oracle for Project.NameKey entities. */
|
||||
public class ProjectNameSuggestOracle extends HighlightSuggestOracle {
|
||||
@Override
|
||||
public void onRequestSuggestions(final Request req, final Callback callback) {
|
||||
RpcStatus.hide(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
SuggestUtil.SVC.suggestProjectNameKey(req.getQuery(), req.getLimit(),
|
||||
new GerritCallback<List<Project.NameKey>>() {
|
||||
public void onSuccess(final List<Project.NameKey> result) {
|
||||
final ArrayList<ProjectNameSuggestion> r =
|
||||
new ArrayList<ProjectNameSuggestion>(result.size());
|
||||
for (final Project.NameKey p : result) {
|
||||
r.add(new ProjectNameSuggestion(p));
|
||||
}
|
||||
callback.onSuggestionsReady(req, new Response(r));
|
||||
ProjectMap.suggest(req.getQuery(), req.getLimit(),
|
||||
new GerritCallback<ProjectMap>() {
|
||||
@Override
|
||||
public void onSuccess(ProjectMap map) {
|
||||
callback.onSuggestionsReady(req, new Response(map.values().asList()));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static class ProjectNameSuggestion implements
|
||||
SuggestOracle.Suggestion {
|
||||
private final Project.NameKey key;
|
||||
|
||||
ProjectNameSuggestion(final Project.NameKey k) {
|
||||
key = k;
|
||||
}
|
||||
|
||||
public String getDisplayString() {
|
||||
return key.get();
|
||||
}
|
||||
|
||||
public String getReplacementString() {
|
||||
return key.get();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -15,16 +15,19 @@
|
||||
package com.google.gerrit.client.ui;
|
||||
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.client.projects.ProjectInfo;
|
||||
import com.google.gerrit.client.projects.ProjectMap;
|
||||
import com.google.gwt.event.dom.client.KeyCodes;
|
||||
import com.google.gwt.user.client.DOM;
|
||||
import com.google.gwt.user.client.Element;
|
||||
import com.google.gwt.user.client.Event;
|
||||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
public class ProjectsTable extends NavigationTable<Project> {
|
||||
public class ProjectsTable extends NavigationTable<ProjectInfo> {
|
||||
|
||||
public ProjectsTable() {
|
||||
keysNavigation.add(new PrevKeyCommand(0, 'k', Util.C.projectListPrev()));
|
||||
@@ -41,6 +44,7 @@ public class ProjectsTable extends NavigationTable<Project> {
|
||||
fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MyFlexTable createFlexTable() {
|
||||
MyFlexTable table = new MyFlexTable() {
|
||||
@Override
|
||||
@@ -78,8 +82,8 @@ public class ProjectsTable extends NavigationTable<Project> {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getRowItemKey(final Project item) {
|
||||
return item.getNameKey();
|
||||
protected Object getRowItemKey(final ProjectInfo item) {
|
||||
return item.name();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -89,17 +93,24 @@ public class ProjectsTable extends NavigationTable<Project> {
|
||||
}
|
||||
}
|
||||
|
||||
public void display(final List<Project> projects) {
|
||||
public void display(ProjectMap projects) {
|
||||
while (1 < table.getRowCount())
|
||||
table.removeRow(table.getRowCount() - 1);
|
||||
|
||||
for (final Project k : projects)
|
||||
insert(table.getRowCount(), k);
|
||||
List<ProjectInfo> list = projects.values().asList();
|
||||
Collections.sort(list, new Comparator<ProjectInfo>() {
|
||||
@Override
|
||||
public int compare(ProjectInfo a, ProjectInfo b) {
|
||||
return a.name().compareTo(b.name());
|
||||
}
|
||||
});
|
||||
for(ProjectInfo p : list)
|
||||
insert(table.getRowCount(), p);
|
||||
|
||||
finishDisplay();
|
||||
}
|
||||
|
||||
protected void insert(final int row, final Project k) {
|
||||
protected void insert(final int row, final ProjectInfo k) {
|
||||
table.insertRow(row);
|
||||
|
||||
applyDataRowStyle(row);
|
||||
@@ -112,9 +123,9 @@ public class ProjectsTable extends NavigationTable<Project> {
|
||||
populate(row, k);
|
||||
}
|
||||
|
||||
protected void populate(final int row, final Project k) {
|
||||
table.setText(row, 1, k.getName());
|
||||
table.setText(row, 2, k.getDescription());
|
||||
protected void populate(final int row, final ProjectInfo k) {
|
||||
table.setText(row, 1, k.name());
|
||||
table.setText(row, 2, k.description());
|
||||
|
||||
setRowItem(row, k);
|
||||
}
|
||||
|
@@ -20,12 +20,10 @@ import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.config.GerritServerConfig;
|
||||
import com.google.gwtjsonrpc.server.XsrfException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.http.server.GitSmartHttpTools;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -39,7 +37,6 @@ import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.servlet.http.HttpServletResponseWrapper;
|
||||
|
||||
/**
|
||||
* Trust the authentication which is done by the container.
|
||||
@@ -62,7 +59,7 @@ class ContainerAuthFilter implements Filter {
|
||||
|
||||
@Inject
|
||||
ContainerAuthFilter(Provider<WebSession> session, AccountCache accountCache,
|
||||
@GerritServerConfig Config config) throws XsrfException {
|
||||
@GerritServerConfig Config config) {
|
||||
this.session = session;
|
||||
this.accountCache = accountCache;
|
||||
this.config = config;
|
||||
@@ -80,20 +77,14 @@ class ContainerAuthFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
if (!GitSmartHttpTools.isGitClient(req)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
HttpServletResponseWrapper rsp =
|
||||
new HttpServletResponseWrapper((HttpServletResponse) response);
|
||||
HttpServletResponse rsp = (HttpServletResponse) response;
|
||||
|
||||
if (verify(req, rsp)) {
|
||||
chain.doFilter(req, response);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean verify(HttpServletRequest req, HttpServletResponseWrapper rsp)
|
||||
private boolean verify(HttpServletRequest req, HttpServletResponse rsp)
|
||||
throws IOException {
|
||||
String username = req.getRemoteUser();
|
||||
if (username == null) {
|
||||
|
@@ -41,5 +41,7 @@ public class GitOverHttpModule extends ServletModule {
|
||||
String git = GitOverHttpServlet.URL_REGEX;
|
||||
filterRegex(git).through(authFilter);
|
||||
serveRegex(git).with(GitOverHttpServlet.class);
|
||||
|
||||
filter("/a/*").through(authFilter);
|
||||
}
|
||||
}
|
||||
|
@@ -30,7 +30,6 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.eclipse.jgit.http.server.GitSmartHttpTools;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -100,11 +99,6 @@ class ProjectDigestFilter implements Filter {
|
||||
public void doFilter(ServletRequest request, ServletResponse response,
|
||||
FilterChain chain) throws IOException, ServletException {
|
||||
HttpServletRequest req = (HttpServletRequest) request;
|
||||
if (!GitSmartHttpTools.isGitClient(req)) {
|
||||
chain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
Response rsp = new Response(req, (HttpServletResponse) response);
|
||||
|
||||
if (verify(req, rsp)) {
|
||||
|
@@ -0,0 +1,62 @@
|
||||
// Copyright (C) 2012 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.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.FilterConfig;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
/** Requires the user to be authenticated over HTTP. */
|
||||
@Singleton
|
||||
class RequireIdentifiedUserFilter implements Filter {
|
||||
private final Provider<CurrentUser> user;
|
||||
|
||||
@Inject
|
||||
RequireIdentifiedUserFilter(Provider<CurrentUser> user) {
|
||||
this.user = user;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(FilterConfig filterConfig) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doFilter(ServletRequest request,
|
||||
ServletResponse response, FilterChain chain)
|
||||
throws IOException, ServletException {
|
||||
if (user.get() instanceof IdentifiedUser) {
|
||||
chain.doFilter(request, response);
|
||||
} else {
|
||||
HttpServletResponse res = (HttpServletResponse) response;
|
||||
res.sendError(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,178 @@
|
||||
// Copyright (C) 2012 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.common.base.Strings;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.gwtjsonrpc.server.RPCServletUtils;
|
||||
import com.google.gwtjsonrpc.common.JsonConstants;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.kohsuke.args4j.CmdLineException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.io.StringWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServlet;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public abstract class RestApiServlet extends HttpServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(RestApiServlet.class);
|
||||
|
||||
/** MIME type used for a JSON response body. */
|
||||
protected static final String JSON_TYPE = JsonConstants.JSON_TYPE;
|
||||
|
||||
/**
|
||||
* Garbage prefix inserted before JSON output to prevent XSSI.
|
||||
* <p>
|
||||
* This prefix is ")]}'\n" and is designed to prevent a web browser from
|
||||
* executing the response body if the resource URI were to be referenced using
|
||||
* a <script src="...> HTML tag from another web site. Clients using the
|
||||
* HTTP interface will need to always strip the first line of response data to
|
||||
* remove this magic header.
|
||||
*/
|
||||
protected static final byte[] JSON_MAGIC;
|
||||
|
||||
static {
|
||||
try {
|
||||
JSON_MAGIC = ")]}'\n".getBytes("UTF-8");
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
throw new RuntimeException("UTF-8 not supported", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void service(HttpServletRequest req, HttpServletResponse res)
|
||||
throws ServletException, IOException {
|
||||
noCache(res);
|
||||
try {
|
||||
super.service(req, res);
|
||||
} catch (Error err) {
|
||||
handleError(err, req, res);
|
||||
} catch (RuntimeException err) {
|
||||
handleError(err, req, res);
|
||||
}
|
||||
}
|
||||
|
||||
private static void noCache(HttpServletResponse res) {
|
||||
res.setHeader("Expires", "Fri, 01 Jan 1980 00:00:00 GMT");
|
||||
res.setHeader("Pragma", "no-cache");
|
||||
res.setHeader("Cache-Control", "no-cache, must-revalidate");
|
||||
res.setHeader("Content-Disposition", "attachment");
|
||||
}
|
||||
|
||||
private static void handleError(
|
||||
Throwable err, HttpServletRequest req, HttpServletResponse res)
|
||||
throws IOException {
|
||||
String uri = req.getRequestURI();
|
||||
if (!Strings.isNullOrEmpty(req.getQueryString())) {
|
||||
uri += "?" + req.getQueryString();
|
||||
}
|
||||
log.error(String.format("Error in %s %s", req.getMethod(), uri), err);
|
||||
|
||||
if (!res.isCommitted()) {
|
||||
res.reset();
|
||||
res.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
|
||||
noCache(res);
|
||||
sendText(req, res, "Internal Server Error");
|
||||
}
|
||||
}
|
||||
|
||||
protected static boolean acceptsJson(HttpServletRequest req) {
|
||||
String accept = req.getHeader("Accept");
|
||||
if (accept == null) {
|
||||
return false;
|
||||
} else if (JSON_TYPE.equals(accept)) {
|
||||
return true;
|
||||
} else if (accept.startsWith(JSON_TYPE + ",")) {
|
||||
return true;
|
||||
}
|
||||
for (String p : accept.split("[ ,;][ ,;]*")) {
|
||||
if (JSON_TYPE.equals(p)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
protected static void sendText(HttpServletRequest req,
|
||||
HttpServletResponse res, String data) throws IOException {
|
||||
res.setContentType("text/plain");
|
||||
res.setCharacterEncoding("UTF-8");
|
||||
send(req, res, data.getBytes("UTF-8"));
|
||||
}
|
||||
|
||||
protected static void send(HttpServletRequest req, HttpServletResponse res,
|
||||
byte[] data) throws IOException {
|
||||
if (data.length > 256 && RPCServletUtils.acceptsGzipEncoding(req)) {
|
||||
res.setHeader("Content-Encoding", "gzip");
|
||||
data = HtmlDomUtil.compress(data);
|
||||
}
|
||||
res.setContentLength(data.length);
|
||||
OutputStream out = res.getOutputStream();
|
||||
try {
|
||||
out.write(data);
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ParameterParser {
|
||||
private final CmdLineParser.Factory parserFactory;
|
||||
|
||||
@Inject
|
||||
ParameterParser(CmdLineParser.Factory pf) {
|
||||
this.parserFactory = pf;
|
||||
}
|
||||
|
||||
public <T> boolean parse(T param, HttpServletRequest req,
|
||||
HttpServletResponse res) throws IOException {
|
||||
CmdLineParser clp = parserFactory.create(param);
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
Map<String, String[]> parameterMap = req.getParameterMap();
|
||||
clp.parseOptionMap(parameterMap);
|
||||
} catch (CmdLineException e) {
|
||||
if (!clp.wasHelpRequestedByOption()) {
|
||||
res.setStatus(HttpServletResponse.SC_BAD_REQUEST);
|
||||
sendText(req, res, e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (clp.wasHelpRequestedByOption()) {
|
||||
StringWriter msg = new StringWriter();
|
||||
clp.printQueryStringUsage(req.getRequestURI(), msg);
|
||||
msg.write('\n');
|
||||
msg.write('\n');
|
||||
clp.printUsage(msg, null);
|
||||
msg.write('\n');
|
||||
sendText(req, res, msg.toString());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -23,6 +23,8 @@ import com.google.gerrit.httpd.raw.LegacyGerritServlet;
|
||||
import com.google.gerrit.httpd.raw.SshInfoServlet;
|
||||
import com.google.gerrit.httpd.raw.StaticServlet;
|
||||
import com.google.gerrit.httpd.raw.ToolServlet;
|
||||
import com.google.gerrit.httpd.rpc.account.AccountCapabilitiesServlet;
|
||||
import com.google.gerrit.httpd.rpc.project.ListProjectsServlet;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gwtexpui.server.CacheControlFilter;
|
||||
@@ -69,6 +71,10 @@ class UrlModule extends ServletModule {
|
||||
serveRegex("^/([1-9][0-9]*)/?$").with(directChangeById());
|
||||
serveRegex("^/p/(.*)$").with(queryProjectNew());
|
||||
serveRegex("^/r/(.+)/?$").with(DirectChangeByCommit.class);
|
||||
|
||||
filter("/a/*").through(RequireIdentifiedUserFilter.class);
|
||||
serveRegex("^/(?:a/)?accounts/self/capabilities$").with(AccountCapabilitiesServlet.class);
|
||||
serveRegex("^/(?:a/)?projects/(.*)?$").with(ListProjectsServlet.class);
|
||||
}
|
||||
|
||||
private Key<HttpServlet> notFound() {
|
||||
|
@@ -38,6 +38,7 @@ import com.google.gerrit.server.contact.ContactStore;
|
||||
import com.google.gerrit.server.contact.ContactStoreProvider;
|
||||
import com.google.gerrit.server.util.GuiceRequestScopePropagator;
|
||||
import com.google.gerrit.server.util.RequestScopePropagator;
|
||||
import com.google.gerrit.util.cli.CmdLineParser;
|
||||
import com.google.inject.AbstractModule;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
@@ -135,6 +136,7 @@ public class WebModule extends FactoryModule {
|
||||
bind(ChangeUserName.CurrentUser.class);
|
||||
factory(ChangeUserName.Factory.class);
|
||||
factory(ClearPassword.Factory.class);
|
||||
factory(CmdLineParser.Factory.class);
|
||||
factory(GeneratePassword.Factory.class);
|
||||
|
||||
bind(SocketAddress.class).annotatedWith(RemotePeer.class).toProvider(
|
||||
|
@@ -39,8 +39,6 @@ import com.google.gerrit.server.patch.AddReviewer;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.project.NoSuchProjectException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gwtjsonrpc.common.AsyncCallback;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
@@ -60,8 +58,6 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
|
||||
private static final String MAX_SUFFIX = "\u9fa5";
|
||||
|
||||
private final Provider<ReviewDb> reviewDbProvider;
|
||||
private final ProjectControl.Factory projectControlFactory;
|
||||
private final ProjectCache projectCache;
|
||||
private final AccountCache accountCache;
|
||||
private final GroupControl.Factory groupControlFactory;
|
||||
private final GroupMembers.Factory groupMembersFactory;
|
||||
@@ -74,8 +70,7 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
|
||||
|
||||
@Inject
|
||||
SuggestServiceImpl(final Provider<ReviewDb> schema,
|
||||
final ProjectControl.Factory projectControlFactory,
|
||||
final ProjectCache projectCache, final AccountCache accountCache,
|
||||
final AccountCache accountCache,
|
||||
final GroupControl.Factory groupControlFactory,
|
||||
final GroupMembers.Factory groupMembersFactory,
|
||||
final Provider<CurrentUser> currentUser,
|
||||
@@ -85,8 +80,6 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
|
||||
@GerritServerConfig final Config cfg, final GroupCache groupCache) {
|
||||
super(schema, currentUser);
|
||||
this.reviewDbProvider = schema;
|
||||
this.projectControlFactory = projectControlFactory;
|
||||
this.projectCache = projectCache;
|
||||
this.accountCache = accountCache;
|
||||
this.groupControlFactory = groupControlFactory;
|
||||
this.groupMembersFactory = groupMembersFactory;
|
||||
@@ -111,28 +104,6 @@ class SuggestServiceImpl extends BaseServiceImplementation implements
|
||||
}
|
||||
}
|
||||
|
||||
public void suggestProjectNameKey(final String query, final int limit,
|
||||
final AsyncCallback<List<Project.NameKey>> callback) {
|
||||
final int max = 10;
|
||||
final int n = limit <= 0 ? max : Math.min(limit, max);
|
||||
|
||||
final List<Project.NameKey> r = new ArrayList<Project.NameKey>(n);
|
||||
for (final Project.NameKey nameKey : projectCache.byName(query)) {
|
||||
final ProjectControl ctl;
|
||||
try {
|
||||
ctl = projectControlFactory.validateFor(nameKey);
|
||||
} catch (NoSuchProjectException e) {
|
||||
continue;
|
||||
}
|
||||
|
||||
r.add(ctl.getProject().getNameKey());
|
||||
if (r.size() == n) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
callback.onSuccess(r);
|
||||
}
|
||||
|
||||
private interface VisibilityControl {
|
||||
boolean isVisible(Account account) throws OrmException;
|
||||
}
|
||||
|
@@ -0,0 +1,188 @@
|
||||
// Copyright (C) 2012 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.rpc.account;
|
||||
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_ACCOUNT;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.FLUSH_CACHES;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.KILL_TASK;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.PRIORITY;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.START_REPLICATION;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CACHES;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.VIEW_CONNECTIONS;
|
||||
import static com.google.gerrit.common.data.GlobalCapability.VIEW_QUEUE;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.gerrit.common.data.GlobalCapability;
|
||||
import com.google.gerrit.common.data.PermissionRange;
|
||||
import com.google.gerrit.httpd.RestApiServlet;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.account.CapabilityControl;
|
||||
import com.google.gerrit.server.git.QueueProvider;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStreamWriter;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
public class AccountCapabilitiesServlet extends RestApiServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final ParameterParser paramParser;
|
||||
private final Provider<Impl> factory;
|
||||
|
||||
@Inject
|
||||
AccountCapabilitiesServlet(
|
||||
ParameterParser paramParser, Provider<Impl> factory) {
|
||||
this.paramParser = paramParser;
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
throws IOException {
|
||||
Impl impl = factory.get();
|
||||
if (acceptsJson(req)) {
|
||||
impl.format = OutputFormat.JSON_COMPACT;
|
||||
}
|
||||
if (paramParser.parse(impl, req, res)) {
|
||||
impl.compute();
|
||||
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
OutputStreamWriter out = new OutputStreamWriter(buf, "UTF-8");
|
||||
if (impl.format.isJson()) {
|
||||
res.setContentType(JSON_TYPE);
|
||||
buf.write(JSON_MAGIC);
|
||||
impl.format.newGson().toJson(
|
||||
impl.have,
|
||||
new TypeToken<Map<String, Object>>() {}.getType(),
|
||||
out);
|
||||
out.flush();
|
||||
buf.write('\n');
|
||||
} else {
|
||||
res.setContentType("text/plain");
|
||||
for (Map.Entry<String, Object> e : impl.have.entrySet()) {
|
||||
out.write(e.getKey());
|
||||
if (!(e.getValue() instanceof Boolean)) {
|
||||
out.write(": ");
|
||||
out.write(e.getValue().toString());
|
||||
}
|
||||
out.write('\n');
|
||||
}
|
||||
out.flush();
|
||||
}
|
||||
res.setCharacterEncoding("UTF-8");
|
||||
send(req, res, buf.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
static class Impl {
|
||||
private final CapabilityControl cc;
|
||||
private final Map<String, Object> have;
|
||||
|
||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||
private OutputFormat format = OutputFormat.TEXT;
|
||||
|
||||
@Option(name = "-q", metaVar = "CAP", multiValued = true, usage = "Capability to inspect")
|
||||
void addQuery(String name) {
|
||||
if (query == null) {
|
||||
query = Sets.newHashSet();
|
||||
}
|
||||
query.add(name.toLowerCase());
|
||||
}
|
||||
private Set<String> query;
|
||||
|
||||
@Inject
|
||||
Impl(CurrentUser user) {
|
||||
cc = user.getCapabilities();
|
||||
have = Maps.newLinkedHashMap();
|
||||
}
|
||||
|
||||
void compute() {
|
||||
for (String name : GlobalCapability.getAllNames()) {
|
||||
if (!name.equals(PRIORITY) && want(name) && cc.canPerform(name)) {
|
||||
if (GlobalCapability.hasRange(name)) {
|
||||
have.put(name, new Range(cc.getRange(name)));
|
||||
} else {
|
||||
have.put(name, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
have.put(CREATE_ACCOUNT, cc.canCreateAccount());
|
||||
have.put(CREATE_GROUP, cc.canCreateGroup());
|
||||
have.put(CREATE_PROJECT, cc.canCreateProject());
|
||||
have.put(KILL_TASK, cc.canKillTask());
|
||||
have.put(VIEW_CACHES, cc.canViewCaches());
|
||||
have.put(FLUSH_CACHES, cc.canFlushCaches());
|
||||
have.put(VIEW_CONNECTIONS, cc.canViewConnections());
|
||||
have.put(VIEW_QUEUE, cc.canViewQueue());
|
||||
have.put(START_REPLICATION, cc.canStartReplication());
|
||||
|
||||
QueueProvider.QueueType queue = cc.getQueueType();
|
||||
if (queue != QueueProvider.QueueType.INTERACTIVE
|
||||
|| (query != null && query.contains(PRIORITY))) {
|
||||
have.put(PRIORITY, queue);
|
||||
}
|
||||
|
||||
Iterator<Map.Entry<String, Object>> itr = have.entrySet().iterator();
|
||||
while (itr.hasNext()) {
|
||||
Map.Entry<String, Object> e = itr.next();
|
||||
if (!want(e.getKey())) {
|
||||
itr.remove();
|
||||
} else if (e.getValue() instanceof Boolean && !((Boolean) e.getValue())) {
|
||||
itr.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean want(String name) {
|
||||
return query == null || query.contains(name.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
private static class Range {
|
||||
private transient PermissionRange range;
|
||||
@SuppressWarnings("unused")
|
||||
private int min;
|
||||
@SuppressWarnings("unused")
|
||||
private int max;
|
||||
|
||||
Range(PermissionRange r) {
|
||||
range = r;
|
||||
min = r.getMin();
|
||||
max = r.getMax();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return range.toString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
// Copyright (C) 2012 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.rpc.project;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.gerrit.httpd.RestApiServlet;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.project.ListProjects;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
@Singleton
|
||||
public class ListProjectsServlet extends RestApiServlet {
|
||||
private static final long serialVersionUID = 1L;
|
||||
private final ParameterParser paramParser;
|
||||
private final Provider<ListProjects> factory;
|
||||
|
||||
@Inject
|
||||
ListProjectsServlet(ParameterParser paramParser, Provider<ListProjects> ls) {
|
||||
this.paramParser = paramParser;
|
||||
this.factory = ls;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doGet(HttpServletRequest req, HttpServletResponse res)
|
||||
throws IOException {
|
||||
ListProjects impl = factory.get();
|
||||
if (!Strings.isNullOrEmpty(req.getPathInfo())) {
|
||||
impl.setMatchPrefix(URLDecoder.decode(req.getPathInfo(), "UTF-8"));
|
||||
}
|
||||
if (acceptsJson(req)) {
|
||||
impl.setFormat(OutputFormat.JSON_COMPACT);
|
||||
}
|
||||
if (paramParser.parse(impl, req, res)) {
|
||||
ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
if (impl.getFormat().isJson()) {
|
||||
res.setContentType(JSON_TYPE);
|
||||
buf.write(JSON_MAGIC);
|
||||
} else {
|
||||
res.setContentType("text/plain");
|
||||
}
|
||||
impl.display(buf);
|
||||
res.setCharacterEncoding("UTF-8");
|
||||
send(req, res, buf.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,71 @@
|
||||
// Copyright (C) 2012 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;
|
||||
|
||||
import com.google.gson.FieldNamingPolicy;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.google.gwtjsonrpc.server.SqlTimestampDeserializer;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
/** Standard output format used by an API call. */
|
||||
public enum OutputFormat {
|
||||
/**
|
||||
* The output is a human readable text format. It may also be regular enough
|
||||
* to be machine readable. Whether or not the text format is machine readable
|
||||
* and will be committed to as a long term format that tools can build upon is
|
||||
* specific to each API call.
|
||||
*/
|
||||
TEXT,
|
||||
|
||||
/**
|
||||
* Pretty-printed JSON format. This format uses whitespace to make the output
|
||||
* readable by a human, but is also machine readable with a JSON library. The
|
||||
* structure of the output is a long term format that tools can rely upon.
|
||||
*/
|
||||
JSON,
|
||||
|
||||
/**
|
||||
* Same as {@link #JSON}, but with unnecessary whitespace removed to save
|
||||
* generation time and copy costs. Typically JSON_COMPACT format is used by a
|
||||
* browser based HTML client running over the network.
|
||||
*/
|
||||
JSON_COMPACT;
|
||||
|
||||
/** @return true when the format is either JSON or JSON_COMPACT. */
|
||||
public boolean isJson() {
|
||||
return this == JSON_COMPACT || this == JSON;
|
||||
}
|
||||
|
||||
/** @return a new Gson instance configured according to the format. */
|
||||
public GsonBuilder newGsonBuilder() {
|
||||
if (!isJson()) {
|
||||
throw new IllegalStateException(String.format("%s is not JSON", this));
|
||||
}
|
||||
GsonBuilder gb = new GsonBuilder()
|
||||
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
|
||||
.registerTypeAdapter(Timestamp.class, new SqlTimestampDeserializer());
|
||||
if (this == OutputFormat.JSON) {
|
||||
gb.setPrettyPrinting();
|
||||
}
|
||||
return gb;
|
||||
}
|
||||
|
||||
/** @return a new Gson instance configured according to the format. */
|
||||
public Gson newGson() {
|
||||
return newGsonBuilder().create();
|
||||
}
|
||||
}
|
@@ -14,10 +14,14 @@
|
||||
|
||||
package com.google.gerrit.server.project;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.Project.NameKey;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.OutputFormat;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.util.TreeFormatter;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
@@ -36,6 +40,7 @@ import java.io.PrintWriter;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.SortedSet;
|
||||
import java.util.TreeMap;
|
||||
import java.util.TreeSet;
|
||||
@@ -75,6 +80,9 @@ public class ListProjects {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final ProjectNode.Factory projectNodeFactory;
|
||||
|
||||
@Option(name = "--format", metaVar = "FMT", usage = "Output display format")
|
||||
private OutputFormat format = OutputFormat.TEXT;
|
||||
|
||||
@Option(name = "--show-branch", aliases = {"-b"}, multiValued = true,
|
||||
usage = "displays the sha of each project in the specified branch")
|
||||
private List<String> showBranch;
|
||||
@@ -93,6 +101,11 @@ public class ListProjects {
|
||||
@Option(name = "--all", usage = "display all projects that are accessible by the calling user")
|
||||
private boolean all;
|
||||
|
||||
@Option(name = "--limit", aliases = {"-n"}, metaVar = "CNT", usage = "maximum number of projects to list")
|
||||
private int limit;
|
||||
|
||||
private String matchPrefix;
|
||||
|
||||
@Inject
|
||||
protected ListProjects(CurrentUser currentUser, ProjectCache projectCache,
|
||||
GitRepositoryManager repoManager,
|
||||
@@ -115,6 +128,20 @@ public class ListProjects {
|
||||
return showDescription;
|
||||
}
|
||||
|
||||
public OutputFormat getFormat() {
|
||||
return format;
|
||||
}
|
||||
|
||||
public ListProjects setFormat(OutputFormat fmt) {
|
||||
this.format = fmt;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ListProjects setMatchPrefix(String prefix) {
|
||||
this.matchPrefix = prefix;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void display(OutputStream out) {
|
||||
final PrintWriter stdout;
|
||||
try {
|
||||
@@ -124,10 +151,14 @@ public class ListProjects {
|
||||
throw new RuntimeException("JVM lacks UTF-8 encoding", e);
|
||||
}
|
||||
|
||||
int found = 0;
|
||||
Map<String, ProjectInfo> output = Maps.newTreeMap();
|
||||
Map<String, String> hiddenNames = Maps.newHashMap();
|
||||
|
||||
final TreeMap<Project.NameKey, ProjectNode> treeMap =
|
||||
new TreeMap<Project.NameKey, ProjectNode>();
|
||||
try {
|
||||
for (final Project.NameKey projectName : projectCache.all()) {
|
||||
for (final Project.NameKey projectName : scan()) {
|
||||
final ProjectState e = projectCache.get(projectName);
|
||||
if (e == null) {
|
||||
// If we can't get it from the cache, pretend its not present.
|
||||
@@ -137,18 +168,39 @@ public class ListProjects {
|
||||
|
||||
final ProjectControl pctl = e.controlFor(currentUser);
|
||||
final boolean isVisible = pctl.isVisible() || (all && pctl.isOwner());
|
||||
if (showTree) {
|
||||
if (showTree && !format.isJson()) {
|
||||
treeMap.put(projectName,
|
||||
projectNodeFactory.create(pctl.getProject(), isVisible));
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isVisible) {
|
||||
if (!isVisible && !(showTree && pctl.isOwner())) {
|
||||
// Require the project itself to be visible to the user.
|
||||
//
|
||||
continue;
|
||||
}
|
||||
|
||||
ProjectInfo info = new ProjectInfo();
|
||||
info.name = projectName.get();
|
||||
if (showTree && format.isJson()) {
|
||||
ProjectState parent = e.getParentState();
|
||||
if (parent != null) {
|
||||
ProjectControl parentCtrl = parent.controlFor(currentUser);
|
||||
if (parentCtrl.isVisible() || parentCtrl.isOwner()) {
|
||||
info.parent = parent.getProject().getName();
|
||||
} else {
|
||||
info.parent = hiddenNames.get(parent.getProject().getName());
|
||||
if (info.parent == null) {
|
||||
info.parent = "?-" + (hiddenNames.size() + 1);
|
||||
hiddenNames.put(parent.getProject().getName(), info.parent);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (showDescription && !e.getProject().getDescription().isEmpty()) {
|
||||
info.description = e.getProject().getDescription();
|
||||
}
|
||||
|
||||
try {
|
||||
if (showBranch != null) {
|
||||
Repository git = repoManager.openRepository(projectName);
|
||||
@@ -162,20 +214,19 @@ public class ListProjects {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (Ref ref : refs) {
|
||||
if (ref == null) {
|
||||
// Print stub (forty '-' symbols)
|
||||
stdout.print("----------------------------------------");
|
||||
} else {
|
||||
stdout.print(ref.getObjectId().name());
|
||||
for (int i = 0; i < showBranch.size(); i++) {
|
||||
Ref ref = refs.get(i);
|
||||
if (ref != null && ref.getObjectId() != null) {
|
||||
if (info.branches == null) {
|
||||
info.branches = Maps.newLinkedHashMap();
|
||||
}
|
||||
info.branches.put(showBranch.get(i), ref.getObjectId().name());
|
||||
}
|
||||
stdout.print(' ');
|
||||
}
|
||||
} finally {
|
||||
git.close();
|
||||
}
|
||||
|
||||
} else if (type != FilterType.ALL) {
|
||||
} else if (!showTree && type != FilterType.ALL) {
|
||||
Repository git = repoManager.openRepository(projectName);
|
||||
try {
|
||||
if (!type.matches(git)) {
|
||||
@@ -194,18 +245,40 @@ public class ListProjects {
|
||||
continue;
|
||||
}
|
||||
|
||||
stdout.print(projectName.get());
|
||||
|
||||
String desc;
|
||||
if (showDescription && !(desc = e.getProject().getDescription()).isEmpty()) {
|
||||
// We still want to list every project as one-liners, hence escaping \n.
|
||||
stdout.print(" - " + desc.replace("\n", "\\n"));
|
||||
if (limit > 0 && ++found > limit) {
|
||||
break;
|
||||
}
|
||||
|
||||
stdout.print("\n");
|
||||
if (format.isJson()) {
|
||||
output.put(info.name, info);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (showBranch != null) {
|
||||
for (String name : showBranch) {
|
||||
String ref = info.branches != null ? info.branches.get(name) : null;
|
||||
if (ref == null) {
|
||||
// Print stub (forty '-' symbols)
|
||||
ref = "----------------------------------------";
|
||||
}
|
||||
stdout.print(ref);
|
||||
stdout.print(' ');
|
||||
}
|
||||
}
|
||||
stdout.print(info.name);
|
||||
|
||||
if (info.description != null) {
|
||||
// We still want to list every project as one-liners, hence escaping \n.
|
||||
stdout.print(" - " + info.description.replace("\n", "\\n"));
|
||||
}
|
||||
stdout.print('\n');
|
||||
}
|
||||
|
||||
if (showTree && treeMap.size() > 0) {
|
||||
if (format.isJson()) {
|
||||
format.newGson().toJson(
|
||||
output, new TypeToken<Map<String, ProjectInfo>>() {}.getType(), stdout);
|
||||
stdout.print('\n');
|
||||
} else if (showTree && treeMap.size() > 0) {
|
||||
printProjectTree(stdout, treeMap);
|
||||
}
|
||||
} finally {
|
||||
@@ -213,6 +286,14 @@ public class ListProjects {
|
||||
}
|
||||
}
|
||||
|
||||
private Iterable<NameKey> scan() {
|
||||
if (matchPrefix != null) {
|
||||
return projectCache.byName(matchPrefix);
|
||||
} else {
|
||||
return projectCache.all();
|
||||
}
|
||||
}
|
||||
|
||||
private void printProjectTree(final PrintWriter stdout,
|
||||
final TreeMap<Project.NameKey, ProjectNode> treeMap) {
|
||||
final SortedSet<ProjectNode> sortedNodes = new TreeSet<ProjectNode>();
|
||||
@@ -270,4 +351,11 @@ public class ListProjects {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static class ProjectInfo {
|
||||
transient String name;
|
||||
String parent;
|
||||
String description;
|
||||
Map<String, String> branches;
|
||||
}
|
||||
}
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.query;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
|
||||
import java.util.Collection;
|
||||
@@ -44,23 +45,35 @@ import java.util.List;
|
||||
public abstract class Predicate<T> {
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
public static <T> Predicate<T> and(final Predicate<T>... that) {
|
||||
if (that.length == 1) {
|
||||
return that[0];
|
||||
}
|
||||
return new AndPredicate<T>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single AND node. */
|
||||
public static <T> Predicate<T> and(
|
||||
final Collection<? extends Predicate<T>> that) {
|
||||
if (that.size() == 1) {
|
||||
return Iterables.getOnlyElement(that);
|
||||
}
|
||||
return new AndPredicate<T>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
public static <T> Predicate<T> or(final Predicate<T>... that) {
|
||||
if (that.length == 1) {
|
||||
return that[0];
|
||||
}
|
||||
return new OrPredicate<T>(that);
|
||||
}
|
||||
|
||||
/** Combine the passed predicates into a single OR node. */
|
||||
public static <T> Predicate<T> or(
|
||||
final Collection<? extends Predicate<T>> that) {
|
||||
if (that.size() == 1) {
|
||||
return Iterables.getOnlyElement(that);
|
||||
}
|
||||
return new OrPredicate<T>(that);
|
||||
}
|
||||
|
||||
|
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.server.query.change;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.gerrit.common.data.ApprovalTypes;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||
@@ -44,6 +45,7 @@ import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
@@ -206,10 +208,7 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
}
|
||||
|
||||
if ("draft".equalsIgnoreCase(value)) {
|
||||
if (currentUser instanceof IdentifiedUser) {
|
||||
return new HasDraftByPredicate(args.dbProvider,
|
||||
((IdentifiedUser) currentUser).getAccountId());
|
||||
}
|
||||
return new HasDraftByPredicate(args.dbProvider, self());
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException();
|
||||
@@ -233,6 +232,14 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
return new IsReviewedPredicate(args.dbProvider);
|
||||
}
|
||||
|
||||
if ("owner".equalsIgnoreCase(value)) {
|
||||
return new OwnerPredicate(args.dbProvider, self());
|
||||
}
|
||||
|
||||
if ("reviewer".equalsIgnoreCase(value)) {
|
||||
return new ReviewerPredicate(args.dbProvider, self());
|
||||
}
|
||||
|
||||
try {
|
||||
return status(value);
|
||||
} catch (IllegalArgumentException e) {
|
||||
@@ -303,42 +310,59 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
@Operator
|
||||
public Predicate<ChangeData> starredby(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
if ("self".equals(who)) {
|
||||
return new IsStarredByPredicate(args.dbProvider, currentUser);
|
||||
}
|
||||
return new IsStarredByPredicate(args.dbProvider, //
|
||||
args.userFactory.create(args.dbProvider, account.getId()));
|
||||
Set<Account.Id> m = parseAccount(who);
|
||||
List<IsStarredByPredicate> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new IsStarredByPredicate(args.dbProvider,
|
||||
args.userFactory.create(args.dbProvider, id)));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> watchedby(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
Set<Account.Id> m = parseAccount(who);
|
||||
List<IsWatchedByPredicate> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
if (currentUser instanceof IdentifiedUser
|
||||
&& id.equals(((IdentifiedUser) currentUser).getAccountId())) {
|
||||
p.add(new IsWatchedByPredicate(args, currentUser));
|
||||
} else {
|
||||
p.add(new IsWatchedByPredicate(args,
|
||||
args.userFactory.create(args.dbProvider, id)));
|
||||
}
|
||||
}
|
||||
return new IsWatchedByPredicate(args, args.userFactory.create(
|
||||
args.dbProvider, account.getId()));
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> draftby(String who) throws QueryParseException,
|
||||
OrmException {
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account == null) {
|
||||
throw error("User " + who + " not found");
|
||||
Set<Account.Id> m = parseAccount(who);
|
||||
List<HasDraftByPredicate> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new HasDraftByPredicate(args.dbProvider, id));
|
||||
}
|
||||
return new HasDraftByPredicate(args.dbProvider, account.getId());
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> visibleto(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Account account = args.accountResolver.find(who);
|
||||
if (account != null) {
|
||||
return visibleto(args.userFactory
|
||||
.create(args.dbProvider, account.getId()));
|
||||
if ("self".equals(who)) {
|
||||
return is_visible();
|
||||
}
|
||||
Set<Account.Id> m = args.accountResolver.findAll(who);
|
||||
if (!m.isEmpty()) {
|
||||
List<Predicate<ChangeData>> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
return visibleto(args.userFactory.create(args.dbProvider, id));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
// If its not an account, maybe its a group?
|
||||
@@ -375,24 +399,17 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
@Operator
|
||||
public Predicate<ChangeData> owner(String who) throws QueryParseException,
|
||||
OrmException {
|
||||
Set<Account.Id> m = args.accountResolver.findAll(who);
|
||||
if (m.isEmpty()) {
|
||||
throw error("User " + who + " not found");
|
||||
} else if (m.size() == 1) {
|
||||
Account.Id id = m.iterator().next();
|
||||
return new OwnerPredicate(args.dbProvider, id);
|
||||
} else {
|
||||
List<OwnerPredicate> p = new ArrayList<OwnerPredicate>(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new OwnerPredicate(args.dbProvider, id));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
Set<Account.Id> m = parseAccount(who);
|
||||
List<OwnerPredicate> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new OwnerPredicate(args.dbProvider, id));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> ownerin(String group) throws QueryParseException,
|
||||
OrmException {
|
||||
public Predicate<ChangeData> ownerin(String group)
|
||||
throws QueryParseException {
|
||||
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
|
||||
if (g == null) {
|
||||
throw error("Group " + group + " not found");
|
||||
@@ -403,24 +420,17 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
@Operator
|
||||
public Predicate<ChangeData> reviewer(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
Set<Account.Id> m = args.accountResolver.findAll(who);
|
||||
if (m.isEmpty()) {
|
||||
throw error("User " + who + " not found");
|
||||
} else if (m.size() == 1) {
|
||||
Account.Id id = m.iterator().next();
|
||||
return new ReviewerPredicate(args.dbProvider, id);
|
||||
} else {
|
||||
List<ReviewerPredicate> p = new ArrayList<ReviewerPredicate>(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new ReviewerPredicate(args.dbProvider, id));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
Set<Account.Id> m = parseAccount(who);
|
||||
List<ReviewerPredicate> p = Lists.newArrayListWithCapacity(m.size());
|
||||
for (Account.Id id : m) {
|
||||
p.add(new ReviewerPredicate(args.dbProvider, id));
|
||||
}
|
||||
return Predicate.or(p);
|
||||
}
|
||||
|
||||
@Operator
|
||||
public Predicate<ChangeData> reviewerin(String group)
|
||||
throws QueryParseException, OrmException {
|
||||
throws QueryParseException {
|
||||
AccountGroup g = args.groupCache.get(new AccountGroup.NameKey(group));
|
||||
if (g == null) {
|
||||
throw error("Group " + group + " not found");
|
||||
@@ -532,4 +542,23 @@ public class ChangeQueryBuilder extends QueryBuilder<ChangeData> {
|
||||
throw error("Unsupported query:" + query);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<Account.Id> parseAccount(String who)
|
||||
throws QueryParseException, OrmException {
|
||||
if ("self".equals(who)) {
|
||||
return Collections.singleton(self());
|
||||
}
|
||||
Set<Account.Id> matches = args.accountResolver.findAll(who);
|
||||
if (matches.isEmpty()) {
|
||||
throw error("User " + who + " not found");
|
||||
}
|
||||
return matches;
|
||||
}
|
||||
|
||||
private Account.Id self() {
|
||||
if (currentUser instanceof IdentifiedUser) {
|
||||
return ((IdentifiedUser) currentUser).getAccountId();
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
@@ -30,11 +30,13 @@ final class ListProjectsCommand extends BaseCommand {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
parseCommandLine(impl);
|
||||
if (impl.isShowTree() && (impl.getShowBranch() != null)) {
|
||||
throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
|
||||
}
|
||||
if (impl.isShowTree() && impl.isShowDescription()) {
|
||||
throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
|
||||
if (!impl.getFormat().isJson()) {
|
||||
if (impl.isShowTree() && (impl.getShowBranch() != null)) {
|
||||
throw new UnloggedFailure(1, "fatal: --tree and --show-branch options are not compatible.");
|
||||
}
|
||||
if (impl.isShowTree() && impl.isShowDescription()) {
|
||||
throw new UnloggedFailure(1, "fatal: --tree and --description options are not compatible.");
|
||||
}
|
||||
}
|
||||
impl.display(out);
|
||||
}
|
||||
|
@@ -46,6 +46,7 @@ import org.kohsuke.args4j.NamedOptionDef;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.kohsuke.args4j.OptionDef;
|
||||
import org.kohsuke.args4j.spi.BooleanOptionHandler;
|
||||
import org.kohsuke.args4j.spi.EnumOptionHandler;
|
||||
import org.kohsuke.args4j.spi.OptionHandler;
|
||||
import org.kohsuke.args4j.spi.Setter;
|
||||
|
||||
@@ -66,7 +67,6 @@ import java.util.ResourceBundle;
|
||||
* args4j style format prior to invoking args4j for parsing.
|
||||
*/
|
||||
public class CmdLineParser {
|
||||
|
||||
public interface Factory {
|
||||
CmdLineParser create(Object bean);
|
||||
}
|
||||
@@ -118,6 +118,67 @@ public class CmdLineParser {
|
||||
out.write('\n');
|
||||
}
|
||||
|
||||
public void printQueryStringUsage(String name, StringWriter out) {
|
||||
out.write(name);
|
||||
|
||||
char next = '?';
|
||||
List<NamedOptionDef> booleans = new ArrayList<NamedOptionDef>();
|
||||
for (@SuppressWarnings("rawtypes") OptionHandler handler : parser.options) {
|
||||
if (handler.option instanceof NamedOptionDef) {
|
||||
NamedOptionDef n = (NamedOptionDef) handler.option;
|
||||
|
||||
if (handler instanceof BooleanOptionHandler) {
|
||||
booleans.add(n);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!n.required()) {
|
||||
out.write('[');
|
||||
}
|
||||
out.write(next);
|
||||
next = '&';
|
||||
if (n.name().startsWith("--")) {
|
||||
out.write(n.name().substring(2));
|
||||
} else if (n.name().startsWith("-")) {
|
||||
out.write(n.name().substring(1));
|
||||
} else {
|
||||
out.write(n.name());
|
||||
}
|
||||
out.write('=');
|
||||
|
||||
String var = handler.getDefaultMetaVariable();
|
||||
if (handler instanceof EnumOptionHandler) {
|
||||
var = var.substring(1, var.length() - 1);
|
||||
var = var.replaceAll(" ", "");
|
||||
}
|
||||
out.write(var);
|
||||
if (!n.required()) {
|
||||
out.write(']');
|
||||
}
|
||||
if (n.isMultiValued()) {
|
||||
out.write('*');
|
||||
}
|
||||
}
|
||||
}
|
||||
for (NamedOptionDef n : booleans) {
|
||||
if (!n.required()) {
|
||||
out.write('[');
|
||||
}
|
||||
out.write(next);
|
||||
next = '&';
|
||||
if (n.name().startsWith("--")) {
|
||||
out.write(n.name().substring(2));
|
||||
} else if (n.name().startsWith("-")) {
|
||||
out.write(n.name().substring(1));
|
||||
} else {
|
||||
out.write(n.name());
|
||||
}
|
||||
if (!n.required()) {
|
||||
out.write(']');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean wasHelpRequestedByOption() {
|
||||
return parser.help.value;
|
||||
}
|
||||
|
15
pom.xml
15
pom.xml
@@ -50,7 +50,7 @@ limitations under the License.
|
||||
<gwtormVersion>1.4</gwtormVersion>
|
||||
<gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
|
||||
<gwtexpuiVersion>1.2.5</gwtexpuiVersion>
|
||||
<gwtVersion>2.3.0</gwtVersion>
|
||||
<gwtVersion>2.4.0</gwtVersion>
|
||||
<slf4jVersion>1.6.1</slf4jVersion>
|
||||
<guiceVersion>3.0</guiceVersion>
|
||||
<jettyVersion>7.2.1.v20101111</jettyVersion>
|
||||
@@ -363,7 +363,7 @@ limitations under the License.
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>gwt-maven-plugin</artifactId>
|
||||
<version>2.3.0</version>
|
||||
<version>2.4.0</version>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
@@ -443,6 +443,12 @@ limitations under the License.
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.google.code.gson</groupId>
|
||||
<artifactId>gson</artifactId>
|
||||
<version>2.1</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>gwtorm</groupId>
|
||||
<artifactId>gwtorm</artifactId>
|
||||
@@ -824,11 +830,6 @@ limitations under the License.
|
||||
<url>http://download.java.net/maven/2/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>gson</id>
|
||||
<url>https://google-gson.googlecode.com/svn/mavenrepo/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>objectweb-repository</id>
|
||||
<url>http://maven.objectweb.org/maven2/</url>
|
||||
|
Reference in New Issue
Block a user