Merge "Add SSH command to set group members"
diff --git a/.buckversion b/.buckversion
index 2c4c008..8c4a95b 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-410fcf3420cb06e62cbe9ee93eff931fa9a9b1a2
+a3aadacd7c1ccd819420a73975a08ba67110decb
diff --git a/.gitignore b/.gitignore
index 78e784d5..b979660 100644
--- a/.gitignore
+++ b/.gitignore
@@ -13,3 +13,4 @@
/buck-out
/local.properties
*.pyc
+/gwt-unitCache
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index a3bfe27..fab71dd 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -747,7 +747,8 @@
For every configured label `My-Name` in the project, there is a
corresponding permission `label-My-Name` with a range corresponding to
-the defined values.
+the defined values. There is also a corresponding `labelAs-My-Name`
+permission that enables editing another user's label.
Gerrit comes pre-configured with a default 'Code-Review' label that can
be granted to groups within projects, enabling functionality for that
@@ -1266,6 +1267,27 @@
command, but also to the web UI results pagination size.
+[[capability_runAs]]
+Run As
+~~~~~~
+
+Allow users to impersonate any other user with the `X-Gerrit-RunAs`
+HTTP header on REST API calls, or the link:cmd-suexec.html[suexec]
+SSH command.
+
+When impersonating an administrator the Administrate Server capability
+is not honored. This security feature tries to prevent a role with
+Run As capability from modifying the access controls in All-Projects,
+however modification may still be possible if the impersonated user
+has permission to push or submit changes on `refs/meta/config`. Run
+As also blocks using most capabilities including Create User, Run
+Garbage Collection, etc., unless the capability is also explicitly
+granted to a group the administrator is a member of.
+
+Administrators do not automatically inherit this capability; it must
+be explicitly granted.
+
+
[[capability_runGC]]
Run Garbage Collection
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/cmd-show-caches.txt b/Documentation/cmd-show-caches.txt
index 126b2a0..d426508 100644
--- a/Documentation/cmd-show-caches.txt
+++ b/Documentation/cmd-show-caches.txt
@@ -25,6 +25,10 @@
operating system, and other details about the environment
that Gerrit Code Review is running in.
+--width::
+-w::
+ Width of the output table.
+
ACCESS
------
Caller must be a member of the privileged 'Administrators' group,
diff --git a/Documentation/cmd-show-connections.txt b/Documentation/cmd-show-connections.txt
index 8404a97..ab9fadf 100644
--- a/Documentation/cmd-show-connections.txt
+++ b/Documentation/cmd-show-connections.txt
@@ -32,6 +32,11 @@
-n::
Show client hostnames as IP addresses instead of DNS hostname.
+--wide::
+-w::
+ Do not format the output to the terminal width (default of
+ 80 columns).
+
DISPLAY
-------
diff --git a/Documentation/cmd-show-queue.txt b/Documentation/cmd-show-queue.txt
index f99e342..b75aeeb 100644
--- a/Documentation/cmd-show-queue.txt
+++ b/Documentation/cmd-show-queue.txt
@@ -37,6 +37,13 @@
---------
Intended for interactive use only.
+OPTIONS
+-------
+--wide::
+-w::
+ Do not format the output to the terminal width (default of
+ 80 columns).
+
DISPLAY
-------
diff --git a/Documentation/cmd-suexec.txt b/Documentation/cmd-suexec.txt
index baffd53..78fc361 100644
--- a/Documentation/cmd-suexec.txt
+++ b/Documentation/cmd-suexec.txt
@@ -19,10 +19,14 @@
DESCRIPTION
-----------
-The suexec command can only be invoked by the magic user `Gerrit
-Code Review` and permits executing any other command as any other
+The suexec command permits executing any other command as any other
registered user account.
+suexec can only be invoked by the magic user `Gerrit Code Review`,
+or any user granted granted the link:access-control.html#capability_runAs[Run As]
+capability. The run as capability is permitted to be used only if
+link:config-gerrit.html[auth.enableRunAs] is true.
+
OPTIONS
-------
@@ -39,7 +43,8 @@
ACCESS
------
Caller must be the magic user Gerrit Code Review using the SSH
-daemon's host key or a key on this daemon's peer host key ring.
+daemon's host key, or a key on this daemon's peer host key ring,
+or a user granted the Run As capability.
SCRIPTING
---------
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 865dd05..8d134ff 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -390,6 +390,18 @@
+
By default this is set to false.
+[[auth.enableRunAs]]auth.enableRunAs::
++
+If true HTTP REST APIs will accept the `X-Gerrit-RunAs` HTTP request
+header from any users granted the link:access-control.html#capability_runAs[Run As]
+capability. The header and capability permit the authenticated user
+to impersonate another account.
++
+If false the feature is disabled and cannot be re-enabled without
+editing gerrit.config and restarting the server.
++
+Default is true.
+
[[cache]]Section cache
~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index dbb9006..5aca9a4 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -53,8 +53,7 @@
----
In Eclipse, choose 'Import existing project' and select the `gerrit` project
-from the current working directory. Do not import any of the other Maven
-based projects.
+from the current working directory.
Expand the `gerrit` project, right-click on the `buck-out` folder, select
'Properties', and then under 'Attributes' check 'Derived'.
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 84cb1e0..daf83cf 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -271,6 +271,10 @@
Developers concerned with stable branches are encouraged to backport or push
patchsets to these branches, even if no new release is planned.
+Fixes that are known to be needed for a particular release should be pushed
+for review on that release's stable branch. It will then be included in
+the master branch when the stable branch is merged back.
+
GERRIT
------
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 0594d93..cf2ac03 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -2597,40 +2597,62 @@
[[label-info]]
LabelInfo
~~~~~~~~~
-The `LabelInfo` entity contains information about a label on a change.
+The `LabelInfo` entity contains information about a label on a change, always
+corresponding to the current patch set.
+There are two options that control the contents of `LabelInfo`:
+link:#labels[`LABELS`] and link:#detailed-labels[`DETAILED_LABELS`].
+
+* For a quick summary of the state of labels, use `LABELS`.
+* For detailed information about labels, including exact numeric votes for all
+ users and the allowed range of votes for the current user, use `DETAILED_LABELS`.
+
+Common fields
+^^^^^^^^^^^^^
[options="header",width="50%",cols="1,^1,5"]
|===========================
|Field Name ||Description
-|`approved` |optional|The user who approved this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`rejected` |optional|The user who rejected this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`recommended` |optional|The user who recommended this label on the
-change as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`disliked` |optional|The user who disliked this label on the change
-as a link:rest-api-accounts.html#account-info[AccountInfo] entity. +
-Only set if link:#labels[labels] are requested.
-|`value` |optional|The voting value of the user who
-recommended/disliked this label on the change if it is not
-"`+1`"/"`-1`". +
-Only set if link:#labels[labels] are requested.
|`optional` |not set if `false`|
Whether the label is optional. Optional means the label may be set, but
it's neither necessary for submission nor does it block submission if
set.
+|===========================
+
+Fields set by `LABELS`
+^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
+|`approved` |optional|One user who approved this label on the change
+(voted the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`rejected` |optional|One user who rejected this label on the change
+(voted the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`recommended` |optional|One user who recommended this label on the
+change (voted positively, but not the maximum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`disliked` |optional|One user who disliked this label on the change
+(voted negatively, but not the minimum value) as an
+link:rest-api-accounts.html#account-info[AccountInfo] entity.
+|`value` |optional|The voting value of the user who
+recommended/disliked this label on the change if it is not
+"`+1`"/"`-1`".
+|===========================
+
+Fields set by `DETAILED_LABELS`
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+[options="header",width="50%",cols="1,^1,5"]
+|===========================
+|Field Name ||Description
|`all` |optional|List of all approvals for this label as a list
-of link:#approval-info[ApprovalInfo] entities. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+of link:#approval-info[ApprovalInfo] entities.
|`values` |optional|A map of all values that are allowed for this
label. The map maps the values ("`-2`", "`-1`", " `0`", "`+1`", "`+2`")
-to the value descriptions. +
-Only set if link:#detailed-labels[detailed labels] are requested.
+to the value descriptions.
|===========================
+
[[restore-input]]
RestoreInput
~~~~~~~~~~~~
@@ -2705,6 +2727,10 @@
after the review is stored. +
Allowed values are `NONE`, `OWNER`, `OWNER_REVIEWERS` and `ALL`. +
If not set, the default is `ALL`.
+|`on_behalf_of`|optional|
+link:rest-api-accounts.html#account-id[\{account-id\}] the review
+should be posted on behalf of. To use this option the caller must
+have been granted `labelAs-NAME` permission for all keys of labels.
|============================
[[reviewer-info]]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
new file mode 100644
index 0000000..70f564c
--- /dev/null
+++ b/Documentation/rest-api-config.txt
@@ -0,0 +1,137 @@
+Gerrit Code Review - /config/ REST API
+======================================
+
+This page describes the config related REST endpoints.
+Please also take note of the general information on the
+link:rest-api.html[REST API].
+
+[[config-endpoints]]
+Config Endpoints
+---------------
+
+[[list-capabilities]]
+List Capabilities
+~~~~~~~~~~~~~~~~~
+[verse]
+'GET /config/server/capabilities'
+
+Lists the capabilities that are available in the system. There are two
+kinds of capabilities: core and plugin-owned capabilities.
+
+As result a map of link:#capability-info[CapabilityInfo] entities is
+returned.
+
+The entries in the map are sorted by capability ID.
+
+.Request
+----
+ GET /config/server/capabilities/ HTTP/1.0
+----
+
+.Response
+----
+ HTTP/1.1 200 OK
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "accessDatabase": {
+ "kind": "gerritcodereview#capability",
+ "id": "accessDatabase",
+ "name": "Access Database"
+ },
+ "administrateServer": {
+ "kind": "gerritcodereview#capability",
+ "id": "administrateServer",
+ "name": "Administrate Server"
+ },
+ "createAccount": {
+ "kind": "gerritcodereview#capability",
+ "id": "createAccount",
+ "name": "Create Account"
+ },
+ "createGroup": {
+ "kind": "gerritcodereview#capability",
+ "id": "createGroup",
+ "name": "Create Group"
+ },
+ "createProject": {
+ "kind": "gerritcodereview#capability",
+ "id": "createProject",
+ "name": "Create Project"
+ },
+ "emailReviewers": {
+ "kind": "gerritcodereview#capability",
+ "id": "emailReviewers",
+ "name": "Email Reviewers"
+ },
+ "flushCaches": {
+ "kind": "gerritcodereview#capability",
+ "id": "flushCaches",
+ "name": "Flush Caches"
+ },
+ "killTask": {
+ "kind": "gerritcodereview#capability",
+ "id": "killTask",
+ "name": "Kill Task"
+ },
+ "priority": {
+ "kind": "gerritcodereview#capability",
+ "id": "priority",
+ "name": "Priority"
+ },
+ "queryLimit": {
+ "kind": "gerritcodereview#capability",
+ "id": "queryLimit",
+ "name": "Query Limit"
+ },
+ "runGC": {
+ "kind": "gerritcodereview#capability",
+ "id": "runGC",
+ "name": "Run Garbage Collection"
+ },
+ "startReplication": {
+ "kind": "gerritcodereview#capability",
+ "id": "startReplication",
+ "name": "Start Replication"
+ },
+ "streamEvents": {
+ "kind": "gerritcodereview#capability",
+ "id": "streamEvents",
+ "name": "Stream Events"
+ },
+ "viewCaches": {
+ "kind": "gerritcodereview#capability",
+ "id": "viewCaches",
+ "name": "View Caches"
+ },
+ "viewConnections": {
+ "kind": "gerritcodereview#capability",
+ "id": "viewConnections",
+ "name": "View Connections"
+ },
+ "viewQueue": {
+ "kind": "gerritcodereview#capability",
+ "id": "viewQueue",
+ "name": "View Queue"
+ }
+ }
+----
+
+[[capability-info]]
+CapabilityInfo
+~~~~~~~~~~~~~~
+The `CapabilityInfo` entity contains information about a capability.
+
+[options="header",width="50%",cols="1,5"]
+|=================================
+|Field Name |Description
+|`kind` |`gerritcodereview#capability`
+|`id` |capability ID
+|`name` |capability name
+|=================================
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 4785350..d8f021b 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -934,7 +934,8 @@
[verse]
'PUT /groups/link:#group-id[\{group-id\}]/groups/link:#group-id[\{group-id\}]'
-Includes a group into a Gerrit internal group.
+Includes an internal or external group into a Gerrit internal group.
+External groups must be specified using the UUID.
.Request
----
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index ccc8604..c0dde1c 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -13,6 +13,8 @@
Account related REST endpoints
link:rest-api-changes.html[/changes/]::
Change related REST endpoints
+link:rest-api-config.html[/config/]::
+ Config related REST endpoints
link:rest-api-groups.html[/groups/]::
Group related REST endpoints
link:rest-api-projects.html[/projects/]::
diff --git a/ReleaseNotes/ReleaseNotes-2.6.txt b/ReleaseNotes/ReleaseNotes-2.6.txt
index 12986f6..0c21182 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.txt
@@ -95,11 +95,6 @@
Search
^^^^^^
-* Animate search bar by expanding & unexpanding
-+
-When the search bar is used, expand it to allow for more text to be
-visible. When it is blurred, shrink it back to the original size.
-
* Suggest projects, groups and users in search panel
+
Suggest projects, groups and users in the search panel as parameter for
@@ -1192,7 +1187,7 @@
ordering issue between the SearchPanel and the HintTextBox.
* link:https://code.google.com/p/gerrit/issues/detail?id=1661[Issue 1661]:
- Update links to Change-Id and Signed-off-by docu on project info
+ Update links to Change-Id and Signed-off-by documentation on project info
screen
* Use href="javascript;" for All {Side-by-Side,Unified} links
@@ -1232,6 +1227,12 @@
URL-unescape the path portion of a change history token to correctly
handle paths with URL-escapable characters, i.e. '+', ' ', etc.
+* link:https://code.google.com/p/gerrit/issues/detail?id=1915[Issue 1915]:
+Don't show non-visible drafts in the diff screens.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=1801[Issue 1801]:
+Correctly keep patch set ordering after a new patch set is added via
+the Web UI.
REST API
~~~~~~~~
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index d14449d..94f4646 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -141,6 +141,13 @@
Get diff of a file in a revision]
+Config
+^^^^^^
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/rest-api-config.html#get-capabilities[
+Get capabilities]
+
+
Projects
^^^^^^^^
@@ -164,6 +171,28 @@
Get child project]
+Capabilities
+~~~~~~~~~~~~
+
+
+New global capabilities are added.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_generateHttpPassword[
+Generate Http Password] Allows non-administrator users to generate HTTP
+passwords for users other than themself.
++
+This capability would typically be assigned to a non-interactive group
+to be able to generate HTTP passwords for users from a tool or web service
+that uses the Gerrit REST API.
+
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/access_control.html#capability_runAs[
+Run As] Allows users to impersonate other users by setting the `X-Gerrit-RunAs`
+HTTP header on REST API calls.
++
+Site administrators do not inherit this capability; it must be granted
+explicitly.
+
+
Plugins
~~~~~~~
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
index b8de9ae..e18aee2 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/GlobalCapability.java
@@ -70,6 +70,9 @@
/** Maximum result limit per executed query. */
public static final String QUERY_LIMIT = "queryLimit";
+ /** Ability to impersonate another user. */
+ public static final String RUN_AS = "runAs";
+
/** Can run the Git garbage collection. */
public static final String RUN_GC = "runGC";
@@ -103,6 +106,7 @@
NAMES_ALL.add(KILL_TASK);
NAMES_ALL.add(PRIORITY);
NAMES_ALL.add(QUERY_LIMIT);
+ NAMES_ALL.add(RUN_AS);
NAMES_ALL.add(RUN_GC);
NAMES_ALL.add(START_REPLICATION);
NAMES_ALL.add(STREAM_EVENTS);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 0585651..10d0b13 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -29,6 +29,7 @@
public static final String FORGE_COMMITTER = "forgeCommitter";
public static final String FORGE_SERVER = "forgeServerAsCommitter";
public static final String LABEL = "label-";
+ public static final String LABEL_AS = "labelAs-";
public static final String OWNER = "owner";
public static final String PUBLISH_DRAFTS = "publishDrafts";
public static final String PUSH = "push";
@@ -43,6 +44,7 @@
private static final List<String> NAMES_LC;
private static final int labelIndex;
+ private static final int labelAsIndex;
static {
NAMES_LC = new ArrayList<String>();
@@ -58,6 +60,7 @@
NAMES_LC.add(PUSH_TAG.toLowerCase());
NAMES_LC.add(PUSH_SIGNED_TAG.toLowerCase());
NAMES_LC.add(LABEL.toLowerCase());
+ NAMES_LC.add(LABEL_AS.toLowerCase());
NAMES_LC.add(REBASE.toLowerCase());
NAMES_LC.add(REMOVE_REVIEWER.toLowerCase());
NAMES_LC.add(SUBMIT.toLowerCase());
@@ -67,15 +70,18 @@
NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
labelIndex = NAMES_LC.indexOf(Permission.LABEL);
+ labelAsIndex = NAMES_LC.indexOf(Permission.LABEL_AS.toLowerCase());
}
/** @return true if the name is recognized as a permission name. */
public static boolean isPermission(String varName) {
- String lc = varName.toLowerCase();
- if (lc.startsWith(LABEL)) {
- return LABEL.length() < lc.length();
- }
- return NAMES_LC.contains(lc);
+ return isLabel(varName)
+ || isLabelAs(varName)
+ || NAMES_LC.contains(varName.toLowerCase());
+ }
+
+ public static boolean hasRange(String varName) {
+ return isLabel(varName) || isLabelAs(varName);
}
/** @return true if the permission name is actually for a review label. */
@@ -83,11 +89,30 @@
return varName.startsWith(LABEL) && LABEL.length() < varName.length();
}
+ /** @return true if the permission is for impersonated review labels. */
+ public static boolean isLabelAs(String var) {
+ return var.startsWith(LABEL_AS) && LABEL_AS.length() < var.length();
+ }
+
/** @return permission name for the given review label. */
public static String forLabel(String labelName) {
return LABEL + labelName;
}
+ /** @return permission name to apply a label for another user. */
+ public static String forLabelAs(String labelName) {
+ return LABEL_AS + labelName;
+ }
+
+ public static String extractLabel(String varName) {
+ if (isLabel(varName)) {
+ return varName.substring(LABEL.length());
+ } else if (isLabelAs(varName)) {
+ return varName.substring(LABEL_AS.length());
+ }
+ return null;
+ }
+
public static boolean canBeOnAllProjects(String ref, String permissionName) {
if (AccessSection.ALL.equals(ref)) {
return !OWNER.equals(permissionName);
@@ -110,15 +135,8 @@
return name;
}
- public boolean isLabel() {
- return isLabel(getName());
- }
-
public String getLabel() {
- if (isLabel()) {
- return getName().substring(LABEL.length());
- }
- return null;
+ return extractLabel(getName());
}
public Boolean getExclusiveGroup() {
@@ -223,8 +241,10 @@
}
private static int index(Permission a) {
- if (a.isLabel()) {
+ if (isLabel(a.getName())) {
return labelIndex;
+ } else if (isLabelAs(a.getName())) {
+ return labelAsIndex;
}
int index = NAMES_LC.indexOf(a.getName().toLowerCase());
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
index 3490dd7..0363fd6 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PermissionRange.java
@@ -86,7 +86,7 @@
}
public String getLabel() {
- return isLabel() ? getName().substring(Permission.LABEL.length()) : null;
+ return Permission.extractLabel(getName());
}
public int getMin() {
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
index 904c5c7..ee6cc95 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAccess.java
@@ -17,6 +17,7 @@
import com.google.gerrit.reviewdb.client.Project;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class ProjectAccess {
@@ -28,6 +29,7 @@
protected boolean isConfigVisible;
protected boolean canUpload;
protected LabelTypes labelTypes;
+ protected Map<String, String> capabilities;
public ProjectAccess() {
}
@@ -112,4 +114,12 @@
public void setLabelTypes(LabelTypes labelTypes) {
this.labelTypes = labelTypes;
}
+
+ public Map<String, String> getCapabilities() {
+ return capabilities;
+ }
+
+ public void setCapabilities(Map<String, String> capabilities) {
+ this.capabilities = capabilities;
+ }
}
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index cf38248..fbd493f 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -87,11 +87,17 @@
java_test(
name = 'ui_tests',
srcs = glob(['src/test/java/**/*.java']),
+ resources = glob(['src/test/resources/**/*']) + [
+ 'src/main/java/com/google/gerrit/GerritGwtUI.gwt.xml',
+ ],
deps = [
':ui_module',
'//lib:junit',
'//lib/gwt:dev',
+ '//lib/gwt:user',
+ '//lib/gwt:gwt-test-utils',
'//lib/jgit:jgit',
],
source_under_test = [':ui_module'],
+ visibility = ['//tools/eclipse:classpath'],
)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
index 344104d..b734b41 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccessSectionEditor.java
@@ -93,9 +93,8 @@
public AccessSectionEditor(ProjectAccess access) {
projectAccess = access;
-
- permissionSelector =
- new ValueListBox<String>(PermissionNameRenderer.INSTANCE);
+ permissionSelector = new ValueListBox<String>(
+ new PermissionNameRenderer(access.getCapabilities()));
permissionSelector.addValueChangeHandler(new ValueChangeHandler<String>() {
@Override
public void onValueChange(ValueChangeEvent<String> event) {
@@ -222,12 +221,15 @@
List<String> perms = new ArrayList<String>();
if (AccessSection.GLOBAL_CAPABILITIES.equals(value.getName())) {
- for (String varName : Util.C.capabilityNames().keySet()) {
+ for (String varName : projectAccess.getCapabilities().keySet()) {
addPermission(varName, perms);
}
} else if (RefConfigSection.isValid(value.getName())) {
for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
- addPermission(Permission.LABEL + t.getName(), perms);
+ addPermission(Permission.forLabel(t.getName()), perms);
+ }
+ for (LabelType t : projectAccess.getLabelTypes().getLabelTypes()) {
+ addPermission(Permission.forLabelAs(t.getName()), perms);
}
for (String varName : Util.C.permissionNames().keySet()) {
addPermission(varName, perms);
@@ -282,7 +284,7 @@
@Override
public PermissionEditor create(int index) {
PermissionEditor subEditor =
- new PermissionEditor(projectAccess.getProjectName(), readOnly, value,
+ new PermissionEditor(projectAccess, readOnly, value,
projectAccess.getLabelTypes());
permissionContainer.insert(subEditor, index);
return subEditor;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
index 51ff978..7aaf04d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AccountGroupMembersScreen.java
@@ -58,6 +58,7 @@
private Button delInclude;
private FlowPanel noMembersInfo;
+ private AccountGroupSuggestOracle accountGroupSuggestOracle;
public AccountGroupMembersScreen(final GroupInfo toShow, final String token) {
super(toShow, token);
@@ -109,9 +110,10 @@
}
private void initIncludeList() {
+ accountGroupSuggestOracle = new AccountGroupSuggestOracle();
addIncludeBox =
new AddMemberBox(Util.C.buttonAddIncludedGroup(),
- Util.C.defaultAccountGroupName(), new AccountGroupSuggestOracle());
+ Util.C.defaultAccountGroupName(), accountGroupSuggestOracle);
addIncludeBox.addClickHandler(new ClickHandler() {
@Override
@@ -187,13 +189,18 @@
}
void doAddNewInclude() {
- final String groupName = addIncludeBox.getText();
+ String groupName = addIncludeBox.getText();
if (groupName.length() == 0) {
return;
}
+ AccountGroup.UUID uuid = accountGroupSuggestOracle.getUUID(groupName);
+ if (uuid == null) {
+ return;
+ }
+
addIncludeBox.setEnabled(false);
- GroupApi.addIncludedGroup(getGroupUUID(), groupName,
+ GroupApi.addIncludedGroup(getGroupUUID(), uuid.get(),
new GerritCallback<GroupInfo>() {
public void onSuccess(final GroupInfo result) {
addIncludeBox.setEnabled(true);
@@ -290,28 +297,12 @@
return str == null ? "" : str;
}
};
- int insertPosition = table.getRowCount();
- int left = 1;
- int right = table.getRowCount() - 1;
- while (left <= right) {
- int middle = (left + right) >>> 1; // (left+right)/2
- AccountInfo i = getRowItem(middle);
- int cmp = c.compare(i, info);
-
- if (cmp < 0) {
- left = middle + 1;
- } else if (cmp > 0) {
- right = middle - 1;
- } else {
- // group is already contained in the table
- return;
- }
+ int insertPos = getInsertRow(c, info);
+ if (insertPos >= 0) {
+ table.insertRow(insertPos);
+ applyDataRowStyle(insertPos);
+ populate(insertPos, info);
}
- insertPosition = left;
-
- table.insertRow(insertPosition);
- applyDataRowStyle(insertPosition);
- populate(insertPosition, info);
}
void populate(final int row, final AccountInfo i) {
@@ -405,29 +396,12 @@
return (str == null) ? "" : str;
}
};
-
- int insertPosition = table.getRowCount();
- int left = 1;
- int right = table.getRowCount() - 1;
- while (left <= right) {
- int middle = (left + right) >>> 1; // (left+right)/2
- GroupInfo i = getRowItem(middle);
- int cmp = c.compare(i, info);
-
- if (cmp < 0) {
- left = middle + 1;
- } else if (cmp > 0) {
- right = middle - 1;
- } else {
- // group is already contained in the table
- return;
- }
+ int insertPos = getInsertRow(c, info);
+ if (insertPos >= 0) {
+ table.insertRow(insertPos);
+ applyDataRowStyle(insertPos);
+ populate(insertPos, info);
}
- insertPosition = left;
-
- table.insertRow(insertPosition);
- applyDataRowStyle(insertPosition);
- populate(insertPosition, info);
}
void populate(final int row, final GroupInfo i) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index f4c0b55..e505860 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -125,8 +125,6 @@
String refErrorPrintable();
String errorsMustBeFixed();
- Map<String, String> capabilityNames();
-
String sectionTypeReference();
String sectionTypeSection();
Map<String, String> sectionNames();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index ce27780..2f2345d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -144,41 +144,6 @@
refErrorPrintable = References may contain only printable characters
errorsMustBeFixed = Errors must be fixed before committing changes.
-# Capability Names
-capabilityNames = \
- accessDatabase, \
- administrateServer, \
- createAccount, \
- createGroup, \
- createProject, \
- emailReviewers, \
- flushCaches, \
- killTask, \
- priority, \
- queryLimit, \
- runGC, \
- startReplication, \
- streamEvents, \
- viewCaches, \
- viewConnections, \
- viewQueue
-accessDatabase = Access Database
-administrateServer = Administrate Server
-createAccount = Create Account
-createGroup = Create Group
-createProject = Create Project
-emailReviewers = Email Reviewers
-flushCaches = Flush Caches
-killTask = Kill Task
-priority = Priority
-queryLimit = Query Limit
-runGC = Run Garbage Collection
-startReplication = Start Replication
-streamEvents = Stream Events
-viewCaches = View Caches
-viewConnections = View Connections
-viewQueue = View Queue
-
# Section Names
sectionTypeReference = Reference:
sectionTypeSection = Section:
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
index 3b4d7d4..d0c52c6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.java
@@ -19,6 +19,7 @@
public interface AdminMessages extends Messages {
String group(String name);
String label(String name);
+ String labelAs(String name);
String project(String name);
String deletedGroup(int id);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
index 7f8cd56..28f3fcc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminMessages.properties
@@ -1,5 +1,6 @@
group = Group {0}
label = Label {0}
+labelAs = Label {0} (On Behalf Of)
project = Project {0}
deletedGroup = Deleted Group {0}
deletedReference = Reference {0} was deleted
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index dac0b6a..bed6b4a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -23,6 +23,7 @@
import com.google.gerrit.client.ui.FilteredUserInterface;
import com.google.gerrit.client.ui.IgnoreOutdatedFilterResultsCallbackWrapper;
import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.http.client.URL;
@@ -55,10 +56,10 @@
protected void onLoad() {
super.onLoad();
display();
- refresh();
+ refresh(false);
}
- private void refresh() {
+ private void refresh(final boolean open) {
setToken(subname == null || "".equals(subname) ? ADMIN_GROUPS
: ADMIN_GROUPS + "?filter=" + URL.encodeQueryString(subname));
GroupMap.match(subname,
@@ -66,8 +67,13 @@
new GerritCallback<GroupMap>() {
@Override
public void onSuccess(GroupMap result) {
- groups.display(result, subname);
- groups.finishDisplay();
+ if (open && result.values().length() > 0) {
+ Gerrit.display(PageLinks.toGroup(
+ result.values().get(0).getGroupUUID()));
+ } else {
+ groups.display(result, subname);
+ groups.finishDisplay();
+ }
}
}));
}
@@ -99,7 +105,7 @@
@Override
public void onKeyUp(KeyUpEvent event) {
subname = filterTxt.getValue();
- refresh();
+ refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
}
});
hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
index 9848c18..028fb87 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionEditor.java
@@ -24,6 +24,7 @@
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.RefConfigSection;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
@@ -107,17 +108,19 @@
private PermissionRange.WithDefaults validRange;
private boolean isDeleted;
- public PermissionEditor(Project.NameKey projectName,
+ public PermissionEditor(ProjectAccess projectAccess,
boolean readOnly,
AccessSection section,
LabelTypes labelTypes) {
this.readOnly = readOnly;
this.section = section;
- this.projectName = projectName;
+ this.projectName = projectAccess.getProjectName();
this.labelTypes = labelTypes;
- normalName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
- deletedName = new ValueLabel<String>(PermissionNameRenderer.INSTANCE);
+ PermissionNameRenderer nameRenderer =
+ new PermissionNameRenderer(projectAccess.getCapabilities());
+ normalName = new ValueLabel<String>(nameRenderer);
+ deletedName = new ValueLabel<String>(nameRenderer);
initWidget(uiBinder.createAndBindUi(this));
groupToAdd.setProject(projectName);
@@ -262,7 +265,7 @@
public void setValue(Permission value) {
this.value = value;
- if (value.isLabel()) {
+ if (Permission.hasRange(value.getName())) {
LabelType lt = labelTypes.byLabel(value.getLabel());
if (lt != null) {
validRange = new PermissionRange.WithDefaults(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
index ad3473c..d8ee195 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PermissionNameRenderer.java
@@ -22,37 +22,54 @@
import java.util.Map;
class PermissionNameRenderer implements Renderer<String> {
- static final PermissionNameRenderer INSTANCE = new PermissionNameRenderer();
-
- private static final Map<String, String> all;
+ private static final Map<String, String> permissions;
static {
- all = new HashMap<String, String>();
- for (Map.Entry<String, String> e : Util.C.capabilityNames().entrySet()) {
- all.put(e.getKey(), e.getValue());
- all.put(e.getKey().toLowerCase(), e.getValue());
- }
+ permissions = new HashMap<String, String>();
for (Map.Entry<String, String> e : Util.C.permissionNames().entrySet()) {
- all.put(e.getKey(), e.getValue());
- all.put(e.getKey().toLowerCase(), e.getValue());
+ permissions.put(e.getKey(), e.getValue());
+ permissions.put(e.getKey().toLowerCase(), e.getValue());
}
}
+ private final Map<String, String> fromServer;
+
+ PermissionNameRenderer(Map<String, String> allFromOutside) {
+ fromServer = allFromOutside;
+ }
+
@Override
public String render(String varName) {
- if (Permission.isLabel(varName)) {
- return Util.M.label(new Permission(varName).getLabel());
+ if (Permission.isLabelAs(varName)) {
+ return Util.M.labelAs(Permission.extractLabel(varName));
+ } else if (Permission.isLabel(varName)) {
+ return Util.M.label(Permission.extractLabel(varName));
}
- String desc = all.get(varName);
- if (desc == null) {
- desc = all.get(varName.toLowerCase());
+ String desc = permissions.get(varName);
+ if (desc != null) {
+ return desc;
}
- return desc != null ? desc : varName;
+
+ desc = fromServer.get(varName);
+ if (desc != null) {
+ return desc;
+ }
+
+ desc = permissions.get(varName.toLowerCase());
+ if (desc != null) {
+ return desc;
+ }
+
+ desc = fromServer.get(varName.toLowerCase());
+ if (desc != null) {
+ return desc;
+ }
+ return varName;
}
@Override
public void render(String object, Appendable appendable) throws IOException {
appendable.append(render(object));
}
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
index 96824f3..28f5d85 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectAccessScreen.java
@@ -18,7 +18,12 @@
import static com.google.gerrit.common.ProjectAccessUtil.removeEmptyPermissionsAndSections;
import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.config.CapabilityInfo;
+import com.google.gerrit.client.config.ConfigServerApi;
+import com.google.gerrit.client.rpc.CallbackGroup;
import com.google.gerrit.client.rpc.GerritCallback;
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
@@ -33,6 +38,7 @@
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.HTMLPanel;
import com.google.gwt.user.client.ui.Label;
@@ -41,8 +47,10 @@
import com.google.gwtexpui.globalkey.client.NpTextArea;
import java.util.Collections;
+import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
public class ProjectAccessScreen extends ProjectScreen {
@@ -90,6 +98,8 @@
private ProjectAccess access;
+ private NativeMap<CapabilityInfo> capabilityMap;
+
public ProjectAccessScreen(final Project.NameKey toShow) {
super(toShow);
}
@@ -107,18 +117,36 @@
@Override
protected void onLoad() {
super.onLoad();
+ CallbackGroup cbs = new CallbackGroup();
+ ConfigServerApi.capabilities(
+ cbs.add(new AsyncCallback<NativeMap<CapabilityInfo>>() {
+ @Override
+ public void onSuccess(NativeMap<CapabilityInfo> result) {
+ capabilityMap = result;
+ }
+
+ @Override
+ public void onFailure(Throwable caught) {
+ // Handled by ScreenLoadCallback.onFailure().
+ }
+ }));
Util.PROJECT_SVC.projectAccess(getProjectKey(),
- new ScreenLoadCallback<ProjectAccess>(this) {
+ cbs.addGwtjsonrpc(new ScreenLoadCallback<ProjectAccess>(this) {
@Override
public void preDisplay(ProjectAccess access) {
displayReadOnly(access);
}
- });
+ }));
savedPanel = ACCESS;
}
private void displayReadOnly(ProjectAccess access) {
this.access = access;
+ Map<String, String> allCapabilities = new HashMap<String, String>();
+ for (CapabilityInfo c : Natives.asList(capabilityMap.values())) {
+ allCapabilities.put(c.id(), c.name());
+ }
+ this.access.setCapabilities(allCapabilities);
accessEditor.setEditing(false);
UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
index ee58420..331afee 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectListScreen.java
@@ -29,6 +29,7 @@
import com.google.gerrit.client.ui.ProjectsTable;
import com.google.gerrit.client.ui.Screen;
import com.google.gerrit.common.PageLinks;
+import com.google.gwt.event.dom.client.KeyCodes;
import com.google.gwt.event.dom.client.KeyUpEvent;
import com.google.gwt.event.dom.client.KeyUpHandler;
import com.google.gwt.http.client.URL;
@@ -64,10 +65,10 @@
protected void onLoad() {
super.onLoad();
display();
- refresh();
+ refresh(false);
}
- private void refresh() {
+ private void refresh(final boolean open) {
setToken(subname == null || "".equals(subname) ? ADMIN_PROJECTS
: ADMIN_PROJECTS + "?filter=" + URL.encodeQueryString(subname));
ProjectMap.match(subname,
@@ -75,7 +76,12 @@
new GerritCallback<ProjectMap>() {
@Override
public void onSuccess(ProjectMap result) {
- projects.display(result);
+ if (open && result.values().length() > 0) {
+ Gerrit.display(PageLinks.toProject(
+ result.values().get(0).name_key()));
+ } else {
+ projects.display(result);
+ }
}
}));
}
@@ -153,7 +159,7 @@
@Override
public void onKeyUp(KeyUpEvent event) {
subname = filterTxt.getValue();
- refresh();
+ refresh(event.getNativeEvent().getKeyCode() == KeyCodes.KEY_ENTER);
}
});
hp.add(filterTxt);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 6819086..8689465 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -88,6 +88,7 @@
private KeyCommandSet keysAction;
private HandlerRegistration regNavigation;
private HandlerRegistration regAction;
+ private HandlerRegistration regDetailCache;
private Grid patchesGrid;
private ListBox patchesList;
@@ -132,6 +133,10 @@
regAction.removeHandler();
regAction = null;
}
+ if (regDetailCache != null) {
+ regDetailCache.removeHandler();
+ regDetailCache = null;
+ }
super.onUnload();
}
@@ -152,7 +157,7 @@
ChangeCache cache = ChangeCache.get(changeId);
detailCache = cache.getChangeDetailCache();
- detailCache.addValueChangeHandler(this);
+ regDetailCache = detailCache.addValueChangeHandler(this);
addStyleName(Gerrit.RESOURCES.css().changeScreen());
addStyleName(Gerrit.RESOURCES.css().screenNoHeader());
@@ -263,7 +268,7 @@
@Override
public void onValueChange(final ValueChangeEvent<ChangeDetail> event) {
- if (isAttached()) {
+ if (isAttached() && isLastValueChangeHandler()) {
// Until this screen is fully migrated to the new API, these calls must
// happen sequentially after the ChangeDetail lookup, because we can't
// start an async get at the source of every call that might trigger a
@@ -295,6 +300,15 @@
}
}
+ // Find the last attached screen.
+ // When DialogBox is used (i. e. CommentedActionDialog) then the original
+ // ChangeScreen is still in attached state.
+ // Use here the fact, that the handlers (ChangeScreen) are sorted.
+ private boolean isLastValueChangeHandler() {
+ int count = detailCache.getHandlerCount();
+ return count > 0 && detailCache.getHandler(count - 1) == this;
+ }
+
private void display(final ChangeDetail detail) {
displayTitle(detail.getChange().getKey(), detail.getChange().getSubject());
discardDiffBaseIfNotApplicable(detail.getChange().getId());
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
new file mode 100644
index 0000000..45abbd6
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/CapabilityInfo.java
@@ -0,0 +1,25 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+public class CapabilityInfo extends JavaScriptObject {
+ public final native String id() /*-{ return this.id; }-*/;
+ public final native String name() /*-{ return this.name; }-*/;
+
+ protected CapabilityInfo() {
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
new file mode 100644
index 0000000..b283a0d
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/config/ConfigServerApi.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.client.rpc.NativeMap;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+/**
+ * A collection of static methods which work on the Gerrit REST API for server
+ * configuration.
+ */
+public class ConfigServerApi {
+ /** map of the server wide capabilities (core & plugins). */
+ public static void capabilities(AsyncCallback<NativeMap<CapabilityInfo>> cb) {
+ new RestApi("/config/server/capabilities/").get(cb);
+ }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
index 9fd858e..904b535 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
@@ -60,6 +60,7 @@
@Override
protected void onInitUI() {
super.onInitUI();
+ setHeaderVisible(false);
add(diffTable = new DiffTable());
}
@@ -103,6 +104,7 @@
if (cmB != null) {
cmB.refresh();
}
+ Window.enableScrolling(false);
}
@Override
@@ -120,6 +122,7 @@
cmB.getWrapperElement().removeFromParent();
cmB = null;
}
+ Window.enableScrolling(true);
}
private void display(DiffInfo diff) {
@@ -141,7 +144,6 @@
});
cmA.on("scroll", doScroll(cmB));
cmB.on("scroll", doScroll(cmA));
- Window.enableScrolling(false);
}
private CodeMirror displaySide(DiffInfo.FileMeta meta, String contents,
@@ -150,7 +152,7 @@
contents = "";
}
Configuration cfg = Configuration.create()
- .set("readOnly", "nocursor")
+ .set("readOnly", true)
.set("lineNumbers", true)
.set("tabSize", 2)
.set("mode", getContentType(meta))
@@ -259,35 +261,32 @@
: null;
}
- private static class EditIterator {
+ static class EditIterator {
private final JsArrayString lines;
private final int startLine;
private int currLineIndex;
private int currLineOffset;
- private EditIterator(JsArrayString lineArray, int start) {
+ EditIterator(JsArrayString lineArray, int start) {
lines = lineArray;
startLine = start;
}
- private LineCharacter advance(int numOfChar) {
+ LineCharacter advance(int numOfChar) {
while (currLineIndex < lines.length()) {
- String line = lines.get(currLineIndex).substring(currLineOffset);
- int lengthWithNewline = line.length() + 1;
+ int lengthWithNewline =
+ lines.get(currLineIndex).length() - currLineOffset + 1;
if (numOfChar < lengthWithNewline) {
LineCharacter at = LineCharacter.create(
startLine + currLineIndex,
numOfChar + currLineOffset);
currLineOffset += numOfChar;
- if (currLineOffset == line.length()) {
- advanceLine();
- }
return at;
}
numOfChar -= lengthWithNewline;
advanceLine();
}
- throw new IllegalStateException("LineIterator index out of bound");
+ throw new IllegalStateException("EditIterator index out of bound");
}
private void advanceLine() {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
index 1edb8fd..0793527 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/FancyFlexTable.java
@@ -25,6 +25,7 @@
import com.google.gwt.user.client.ui.Widget;
import com.google.gwtexpui.safehtml.client.SafeHtml;
+import java.util.Comparator;
import java.util.Iterator;
public abstract class FancyFlexTable<RowItem> extends Composite {
@@ -58,6 +59,71 @@
setRowItem(table.getCellFormatter().getElement(row, 0), item);
}
+ /**
+ * Finds an item in the table.
+ *
+ * @param comparator comparator by which the items in the table are sorted
+ * @param item the item that should be found
+ * @return if the item is found the number of the row that contains the item;
+ * if the item is not found <code>-1</code>
+ */
+ protected int findRowItem(Comparator<RowItem> comparator, RowItem item) {
+ int row = lookupRowItem(comparator, item);
+ if (row < table.getRowCount()
+ && comparator.compare(item, getRowItem(row)) == 0) {
+ return row;
+ }
+ return -1;
+ }
+
+ /**
+ * Finds the number of the row where a new item should be inserted into the
+ * table.
+ *
+ * @param comparator comparator by which the items in the table are sorted
+ * @param item the new item that should be inserted
+ * @return if the item is not yet contained in the table, the number of the
+ * row where the new item should be inserted; if the item is already
+ * contained in the table <code>-1</code>
+ */
+ protected int getInsertRow(Comparator<RowItem> comparator, RowItem item) {
+ int row = lookupRowItem(comparator, item);
+ if (row >= table.getRowCount()
+ || comparator.compare(item, getRowItem(row)) != 0) {
+ return row;
+ }
+ return -1;
+ }
+
+ /**
+ * Makes a binary search for the given row item over the table.
+ *
+ * @param comparator comparator by which the items in the table are sorted
+ * @param item the item that should be looked up
+ * @return if the item is found the number of the row that contains the item;
+ * if the item is not found the number of the row where the item
+ * should be inserted according to the given comparator.
+ */
+ private int lookupRowItem(Comparator<RowItem> comparator, RowItem item) {
+ int left = 1;
+ int right = table.getRowCount() - 1;
+ while (left <= right) {
+ int middle = (left + right) >>> 1; // (left+right)/2
+ RowItem i = getRowItem(middle);
+ int cmp = comparator.compare(i, item);
+
+ if (cmp < 0) {
+ left = middle + 1;
+ } else if (cmp > 0) {
+ right = middle - 1;
+ } else {
+ // item is already contained in the table
+ return middle;
+ }
+ }
+ return left;
+ }
+
protected void resetHtml(final SafeHtml body) {
for (final Iterator<Widget> i = table.iterator(); i.hasNext();) {
i.next();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
index 6dad875..d834eb2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/ListenableValue.java
@@ -45,4 +45,13 @@
ValueChangeHandler<T> handler) {
return manager.addHandler(ValueChangeEvent.getType(), handler);
}
+
+ public int getHandlerCount() {
+ return manager.getHandlerCount(ValueChangeEvent.getType());
+ }
+
+ public ValueChangeHandler<?> getHandler(int index) {
+ return manager.getHandler(ValueChangeEvent.getType(), index);
+ }
+
}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
index 63c2636..afbd740 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ui/NavigationTable.java
@@ -261,6 +261,9 @@
}
public void finishDisplay() {
+ if (currentRow >= table.getRowCount()) {
+ currentRow = -1;
+ }
if (saveId != null) {
movePointerTo(savedPositions.get(saveId));
}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
index 8a8e9d5..9a7b64a 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/LineCharacter.java
@@ -20,16 +20,13 @@
public class LineCharacter extends JavaScriptObject {
public static LineCharacter create(int line, int ch) {
LineCharacter lineCh = createObject().cast();
- return lineCh.setLine(line).setCh(ch);
+ lineCh.setLine(line);
+ lineCh.setCh(ch);
+ return lineCh;
}
- private final native LineCharacter setLine(int line) /*-{
- this.line = line; return this;
- }-*/;
-
- private final native LineCharacter setCh(int ch) /*-{
- this.ch = ch; return this;
- }-*/;
+ public final native void setLine(int line) /*-{ this.line = line; }-*/;
+ public final native void setCh(int ch) /*-{ this.ch = ch; }-*/;
public final native int getLine() /*-{ return this.line; }-*/;
public final native int getCh() /*-{ return this.ch; }-*/;
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
new file mode 100644
index 0000000..f0bbd3d
--- /dev/null
+++ b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
@@ -0,0 +1,97 @@
+// Copyright (C) 2013 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.diff;
+
+import static org.junit.Assert.assertEquals;
+
+import com.google.gerrit.client.diff.CodeMirrorDemo.EditIterator;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+import com.googlecode.gwt.test.GwtModule;
+import com.googlecode.gwt.test.GwtTest;
+
+import net.codemirror.lib.LineCharacter;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/** Unit tests for EditIterator */
+@GwtModule("com.google.gerrit.GerritGwtUI")
+public class EditIteratorTest extends GwtTest {
+ private JsArrayString lines;
+
+ private void assertLineChsEqual(LineCharacter a, LineCharacter b) {
+ assertEquals(a.getLine() + "," + a.getCh(), b.getLine() + "," + b.getCh());
+ }
+
+ @Before
+ public void initialize() {
+ lines = (JsArrayString) JavaScriptObject.createArray();
+ lines.push("1st");
+ lines.push("2nd");
+ lines.push("3rd");
+ }
+
+ @Test
+ public void testNoAdvance() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(0, 0), iter.advance(0));
+ }
+
+ @Test
+ public void testSimpleAdvance() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(0, 1), iter.advance(1));
+ }
+
+ @Test
+ public void testEndsBeforeNewline() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(0, 3), iter.advance(3));
+ }
+
+ @Test
+ public void testEndsOnNewline() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(1, 0), iter.advance(4));
+ }
+
+ @Test
+ public void testAcrossNewline() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(1, 1), iter.advance(5));
+ }
+
+ @Test
+ public void testContinueFromBeforeNewline() {
+ EditIterator iter = new EditIterator(lines, 0);
+ iter.advance(3);
+ assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(7));
+ }
+
+ @Test
+ public void testContinueFromAfterNewline() {
+ EditIterator iter = new EditIterator(lines, 0);
+ iter.advance(4);
+ assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(6));
+ }
+
+ @Test
+ public void testAcrossMultipleLines() {
+ EditIterator iter = new EditIterator(lines, 0);
+ assertLineChsEqual(LineCharacter.create(2, 2), iter.advance(10));
+ }
+}
diff --git a/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
new file mode 100644
index 0000000..c0cbb30
--- /dev/null
+++ b/gerrit-gwtui/src/test/resources/META-INF/gwt-test-utils.properties
@@ -0,0 +1 @@
+com.google.gerrit.GerritGwtUI = gwt-module
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index 96792f0..9968e2a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -185,6 +185,7 @@
public void setUserAccountId(Account.Id id) {
key = new Key("id:" + id);
val = new Val(id, 0, false, null, 0, null, null);
+ user = identified.runAs(id, user);
}
@Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
new file mode 100644
index 0000000..ff65674
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/RunAsFilter.java
@@ -0,0 +1,123 @@
+// Copyright (C) 2013 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 static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN;
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.account.AccountResolver;
+import com.google.gerrit.server.config.AuthConfig;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+import com.google.inject.servlet.ServletModule;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+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.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/** Allows running a request as another user account. */
+@Singleton
+class RunAsFilter implements Filter {
+ private static final Logger log = LoggerFactory.getLogger(RunAsFilter.class);
+ private static final String RUN_AS = "X-Gerrit-RunAs";
+
+ static class Module extends ServletModule {
+ @Override
+ protected void configureServlets() {
+ filter("/*").through(RunAsFilter.class);
+ }
+ }
+
+ private final boolean enabled;
+ private final Provider<WebSession> session;
+ private final AccountResolver accountResolver;
+
+ @Inject
+ RunAsFilter(AuthConfig config,
+ Provider<WebSession> session,
+ AccountResolver accountResolver) {
+ this.enabled = config.isRunAsEnabled();
+ this.session = session;
+ this.accountResolver = accountResolver;
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpServletRequest req = (HttpServletRequest) request;
+ HttpServletResponse res = (HttpServletResponse) response;
+
+ String runas = req.getHeader(RUN_AS);
+ if (runas != null) {
+ if (!enabled) {
+ RestApiServlet.replyError(res,
+ SC_FORBIDDEN,
+ RUN_AS + " disabled by auth.enableRunAs = false");
+ return;
+ }
+
+ CurrentUser self = session.get().getCurrentUser();
+ if (!self.getCapabilities().canRunAs()) {
+ RestApiServlet.replyError(res,
+ SC_FORBIDDEN,
+ "not permitted to use " + RUN_AS);
+ return;
+ }
+
+ Account target;
+ try {
+ target = accountResolver.find(runas);
+ } catch (OrmException e) {
+ log.warn("cannot resolve account for " + RUN_AS, e);
+ RestApiServlet.replyError(res,
+ SC_INTERNAL_SERVER_ERROR,
+ "cannot resolve " + RUN_AS);
+ return;
+ }
+ if (target == null) {
+ RestApiServlet.replyError(res,
+ SC_FORBIDDEN,
+ "no account matches " + RUN_AS);
+ return;
+ }
+ session.get().setUserAccountId(target.getId());
+ }
+
+ chain.doFilter(req, res);
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) {
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index bd25faf..47219ca 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -27,6 +27,7 @@
import com.google.gerrit.httpd.rpc.account.AccountsRestApiServlet;
import com.google.gerrit.httpd.rpc.change.ChangesRestApiServlet;
import com.google.gerrit.httpd.rpc.change.DeprecatedChangeQueryServlet;
+import com.google.gerrit.httpd.rpc.config.ConfigRestApiServlet;
import com.google.gerrit.httpd.rpc.group.GroupsRestApiServlet;
import com.google.gerrit.httpd.rpc.project.ProjectsRestApiServlet;
import com.google.gerrit.reviewdb.client.Change;
@@ -102,6 +103,7 @@
serveRegex("^/(?:a/)?tools/(.*)$").with(ToolServlet.class);
serveRegex("^/(?:a/)?accounts/(.*)$").with(AccountsRestApiServlet.class);
serveRegex("^/(?:a/)?changes/(.*)$").with(ChangesRestApiServlet.class);
+ serveRegex("^/(?:a/)?config/(.*)$").with(ConfigRestApiServlet.class);
serveRegex("^/(?:a/)?groups/(.*)?$").with(GroupsRestApiServlet.class);
serveRegex("^/(?:a/)?projects/(.*)?$").with(ProjectsRestApiServlet.class);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 7878477..efd8e24 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -83,6 +83,7 @@
if (wantSSL) {
install(new RequireSslFilter.Module());
}
+ install(new RunAsFilter.Module());
switch (authConfig.getAuthType()) {
case HTTP:
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index 842fcaf..fac950b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -841,8 +841,8 @@
}
}
- static void replyError(HttpServletResponse res, int statusCode, String msg)
- throws IOException {
+ public static void replyError(HttpServletResponse res, int statusCode,
+ String msg) throws IOException {
res.setStatus(statusCode);
CacheHeaders.setNotCacheable(res);
replyText(null, res, msg);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
new file mode 100644
index 0000000..f951ad3
--- /dev/null
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/config/ConfigRestApiServlet.java
@@ -0,0 +1,32 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.httpd.restapi.RestApiServlet;
+import com.google.gerrit.server.config.ConfigCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+public class ConfigRestApiServlet extends RestApiServlet {
+ private static final long serialVersionUID = 1L;
+
+ @Inject
+ ConfigRestApiServlet(RestApiServlet.Globals globals,
+ Provider<ConfigCollection> configCollection) {
+ super(globals, configCollection);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
index 86a6ef8..ae8c6bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/CurrentUser.java
@@ -51,6 +51,20 @@
}
/**
+ * Identity of the authenticated user.
+ * <p>
+ * In the normal case where a user authenticates as themselves
+ * {@code getRealUser() == this}.
+ * <p>
+ * If {@code X-Gerrit-RunAs} or {@code suexec} was used this method returns
+ * the identity of the account that has permission to act on behalf of this
+ * user.
+ */
+ public CurrentUser getRealUser() {
+ return this;
+ }
+
+ /**
* Get the set of groups the user is currently a member of.
* <p>
* The returned set may be a subset of the user's actual groups; if the user's
@@ -76,11 +90,9 @@
/** Capabilities available to this user account. */
public CapabilityControl getCapabilities() {
- CapabilityControl ctl = capabilities;
- if (ctl == null) {
- ctl = capabilityControlFactory.create(this);
- capabilities = ctl;
+ if (capabilities == null) {
+ capabilities = capabilityControlFactory.create(this);
}
- return ctl;
+ return capabilities;
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index 3826293..8e61c9a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -97,13 +97,20 @@
public IdentifiedUser create(Provider<ReviewDb> db, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupBackend, null, db, id);
+ groupBackend, null, db, id, null);
}
public IdentifiedUser create(SocketAddress remotePeer, Account.Id id) {
return new IdentifiedUser(capabilityControlFactory,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupBackend, Providers.of(remotePeer), null, id);
+ groupBackend, Providers.of(remotePeer), null, id, null);
+ }
+
+ public CurrentUser runAs(SocketAddress remotePeer, Account.Id id,
+ @Nullable CurrentUser caller) {
+ return new IdentifiedUser(capabilityControlFactory,
+ authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+ groupBackend, Providers.of(remotePeer), null, id, caller);
}
}
@@ -152,7 +159,13 @@
public IdentifiedUser create(Account.Id id) {
return new IdentifiedUser(capabilityControlFactory,
authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
- groupBackend, remotePeerProvider, dbProvider, id);
+ groupBackend, remotePeerProvider, dbProvider, id, null);
+ }
+
+ public IdentifiedUser runAs(Account.Id id, CurrentUser caller) {
+ return new IdentifiedUser(capabilityControlFactory,
+ authConfig, anonymousCowardName, canonicalUrl, realm, accountCache,
+ groupBackend, remotePeerProvider, dbProvider, id, caller);
}
}
@@ -183,6 +196,7 @@
private GroupMembership effectiveGroups;
private Set<Change.Id> starredChanges;
private Collection<AccountProjectWatch> notificationFilters;
+ private CurrentUser realUser;
private IdentifiedUser(
CapabilityControl.Factory capabilityControlFactory,
@@ -192,7 +206,9 @@
final Realm realm, final AccountCache accountCache,
final GroupBackend groupBackend,
@Nullable final Provider<SocketAddress> remotePeerProvider,
- @Nullable final Provider<ReviewDb> dbProvider, final Account.Id id) {
+ @Nullable final Provider<ReviewDb> dbProvider,
+ final Account.Id id,
+ @Nullable CurrentUser realUser) {
super(capabilityControlFactory);
this.canonicalUrl = canonicalUrl;
this.accountCache = accountCache;
@@ -202,6 +218,12 @@
this.remotePeerProvider = remotePeerProvider;
this.dbProvider = dbProvider;
this.accountId = id;
+ this.realUser = realUser != null ? realUser : this;
+ }
+
+ @Override
+ public CurrentUser getRealUser() {
+ return realUser;
}
// TODO(cranger): maybe get the state through the accountCache instead.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
index cff3a35..e7a6004 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountsCollection.java
@@ -78,7 +78,7 @@
* "Full Name <email@example.com>", just the email address, a full name
* if it is unique, an account ID, a user name or 'self' for the
* calling user
- * @return the project
+ * @return the user, never null.
* @throws UnprocessableEntityException thrown if the account ID cannot be
* resolved or if the account is not visible to the calling user
*/
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 681bf3c..2cff009 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -15,8 +15,7 @@
package com.google.gerrit.server.account;
import com.google.common.base.Charsets;
-import com.google.common.io.CharStreams;
-import com.google.common.io.InputSupplier;
+import com.google.common.io.ByteSource;
import com.google.gerrit.common.errors.InvalidSshKeyException;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
@@ -77,15 +76,14 @@
max = Math.max(max, k.getKey().get());
}
- final InputStream in = input.raw.getInputStream();
- String sshPublicKey =
- CharStreams.toString(CharStreams.newReaderSupplier(
- new InputSupplier<InputStream>() {
- @Override
- public InputStream getInput() {
- return in;
- }
- }, Charsets.UTF_8));
+ final RawInput rawKey = input.raw;
+ String sshPublicKey = new ByteSource() {
+ @Override
+ public InputStream openStream() throws IOException {
+ return rawKey.getInputStream();
+ }
+ }.asCharSource(Charsets.UTF_8).read();
+
try {
AccountSshKey sshKey =
sshKeyCache.create(new AccountSshKey.Id(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index fad9465..193ea04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -65,8 +65,12 @@
/** @return true if the user can administer this server. */
public boolean canAdministrateServer() {
if (canAdministrateServer == null) {
- canAdministrateServer = user instanceof PeerDaemonUser
- || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+ if (user.getRealUser() != user) {
+ canAdministrateServer = false;
+ } else {
+ canAdministrateServer = user instanceof PeerDaemonUser
+ || matchAny(capabilities.administrateServer, ALLOWED_RULE);
+ }
}
return canAdministrateServer;
}
@@ -130,7 +134,6 @@
|| canAdministrateServer();
}
-
/** @return true if the user can access the database (with gsql). */
public boolean canAccessDatabase() {
return canPerform(GlobalCapability.ACCESS_DATABASE);
@@ -160,6 +163,11 @@
|| canAdministrateServer();
}
+ /** @return true if the user can impersonate another user. */
+ public boolean canRunAs() {
+ return canPerform(GlobalCapability.RUN_AS);
+ }
+
/** @return which priority queue the user's tasks should be submitted to. */
public QueueProvider.QueueType getQueueType() {
// If a non-generic group (that is not Anonymous Users or Registered Users)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
index e796cbb..0601b8d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PutHttpPassword.java
@@ -71,36 +71,41 @@
if (input == null) {
input = new Input();
}
- if (self.get() != rsrc.getUser()
- && !self.get().getCapabilities().canAdministrateServer()) {
- if (input.generate && !self.get().getCapabilities().canGenerateHttpPassword()) {
- throw new AuthException("not allowed to generate HTTP password");
- } else {
- throw new AuthException("not allowed to set HTTP password");
- }
- }
- if (rsrc.getUser().getUserName() == null) {
- throw new ResourceConflictException("username must be set");
- }
- AccountExternalId.Key key =
- new AccountExternalId.Key(SCHEME_USERNAME, rsrc.getUser().getUserName());
- AccountExternalId id = dbProvider.get().accountExternalIds().get(key);
- if (id == null) {
- throw new ResourceNotFoundException();
- }
+ input.httpPassword = Strings.emptyToNull(input.httpPassword);
String newPassword;
if (input.generate) {
+ if (self.get() != rsrc.getUser()
+ && !self.get().getCapabilities().canGenerateHttpPassword()) {
+ throw new AuthException("not allowed to generate HTTP password");
+ }
newPassword = generate();
- } else {
- if (!Strings.isNullOrEmpty(input.httpPassword)
+
+ } else if (input.httpPassword == null) {
+ if (self.get() != rsrc.getUser()
&& !self.get().getCapabilities().canAdministrateServer()) {
+ throw new AuthException("not allowed to clear HTTP password");
+ }
+ newPassword = null;
+ } else {
+ if (!self.get().getCapabilities().canAdministrateServer()) {
throw new AuthException("not allowed to set HTTP password directly, "
+ "need to be Gerrit administrator");
}
- newPassword = Strings.emptyToNull(input.httpPassword);
+ newPassword = input.httpPassword;
}
+ if (rsrc.getUser().getUserName() == null) {
+ throw new ResourceConflictException("username must be set");
+ }
+
+ AccountExternalId id = dbProvider.get().accountExternalIds()
+ .get(new AccountExternalId.Key(
+ SCHEME_USERNAME,
+ rsrc.getUser().getUserName()));
+ if (id == null) {
+ throw new ResourceNotFoundException();
+ }
id.setPassword(newPassword);
dbProvider.get().accountExternalIds().update(Collections.singleton(id));
accountCache.evict(rsrc.getUser().getAccountId());
@@ -110,7 +115,7 @@
: Response.ok(newPassword);
}
- private String generate() {
+ private static String generate() {
byte[] rand = new byte[LEN];
rng.nextBytes(rand);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 549c3c5..fa63da0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -30,6 +30,7 @@
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.ChangeMessage;
@@ -39,6 +40,7 @@
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.AccountsCollection;
import com.google.gerrit.server.change.PostReview.Input;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gwtorm.server.OrmException;
@@ -81,6 +83,18 @@
/** Who to send email notifications to after review is stored. */
public NotifyHandling notify = NotifyHandling.ALL;
+
+ /**
+ * Account ID, name, email address or username of another user. The review
+ * will be posted/updated on behalf of this named user instead of the
+ * caller. Caller must have the labelAs-$NAME permission granted for each
+ * label that appears in {@link #labels}. This is in addition to the named
+ * user also needing to have permission to use the labels.
+ * <p>
+ * {@link #strictLabels} impacts how labels is processed for the named user,
+ * not the caller.
+ */
+ public String onBehalfOf;
}
public static enum DraftHandling {
@@ -104,6 +118,7 @@
}
private final ReviewDb db;
+ private final AccountsCollection accounts;
private final EmailReviewComments.Factory email;
@Deprecated private final ChangeHooks hooks;
@@ -116,16 +131,22 @@
@Inject
PostReview(ReviewDb db,
+ AccountsCollection accounts,
EmailReviewComments.Factory email,
ChangeHooks hooks) {
this.db = db;
+ this.accounts = accounts;
this.email = email;
this.hooks = hooks;
}
@Override
public Object apply(RevisionResource revision, Input input)
- throws AuthException, BadRequestException, OrmException {
+ throws AuthException, BadRequestException, OrmException,
+ UnprocessableEntityException {
+ if (input.onBehalfOf != null) {
+ revision = onBehalfOf(revision, input);
+ }
if (input.labels != null) {
checkLabels(revision, input.strictLabels, input.labels);
}
@@ -171,6 +192,45 @@
return output;
}
+ private RevisionResource onBehalfOf(RevisionResource rev, Input in)
+ throws BadRequestException, AuthException, UnprocessableEntityException,
+ OrmException {
+ if (in.labels == null || in.labels.isEmpty()) {
+ throw new AuthException(String.format(
+ "label required to post review on behalf of \"%s\"",
+ in.onBehalfOf));
+ }
+
+ ChangeControl caller = rev.getControl();
+ Iterator<Map.Entry<String, Short>> itr = in.labels.entrySet().iterator();
+ while (itr.hasNext()) {
+ Map.Entry<String, Short> ent = itr.next();
+ LabelType type = caller.getLabelTypes().byLabel(ent.getKey());
+ if (type == null && in.strictLabels) {
+ throw new BadRequestException(String.format(
+ "label \"%s\" is not a configured label", ent.getKey()));
+ } else if (type == null) {
+ itr.remove();
+ continue;
+ }
+
+ PermissionRange r = caller.getRange(Permission.forLabelAs(type.getName()));
+ if (r == null || r.isEmpty() || !r.contains(ent.getValue())) {
+ throw new AuthException(String.format(
+ "not permitted to modify label \"%s\" on behalf of \"%s\"",
+ ent.getKey(), in.onBehalfOf));
+ }
+ }
+ if (in.labels.isEmpty()) {
+ throw new AuthException(String.format(
+ "label required to post review on behalf of \"%s\"",
+ in.onBehalfOf));
+ }
+
+ ChangeControl target = caller.forUser(accounts.parse(in.onBehalfOf));
+ return new RevisionResource(new ChangeResource(target), rev.getPatchSet());
+ }
+
private void checkLabels(RevisionResource revision, boolean strict,
Map<String, Short> labels) throws BadRequestException, AuthException {
ChangeControl ctl = revision.getControl();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
index 9a804c1..06d2a71 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/AuthConfig.java
@@ -37,6 +37,7 @@
private final AuthType authType;
private final String httpHeader;
private final boolean trustContainerAuth;
+ private final boolean enableRunAs;
private final boolean userNameToLowerCase;
private final boolean gitBasicAuth;
private final String logoutUrl;
@@ -64,6 +65,7 @@
cookiePath = cfg.getString("auth", null, "cookiepath");
cookieSecure = cfg.getBoolean("auth", "cookiesecure", false);
trustContainerAuth = cfg.getBoolean("auth", "trustContainerAuth", false);
+ enableRunAs = cfg.getBoolean("auth", null, "enableRunAs", true);
gitBasicAuth = cfg.getBoolean("auth", "gitBasicAuth", false);
userNameToLowerCase = cfg.getBoolean("auth", "userNameToLowerCase", false);
@@ -164,6 +166,11 @@
return trustContainerAuth;
}
+ /** @return true if users with Run As capability can impersonate others. */
+ public boolean isRunAsEnabled() {
+ return enableRunAs;
+ }
+
/** Whether user name should be converted to lower-case before validation */
public boolean isUserNameToLowerCase() {
return userNameToLowerCase;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
new file mode 100644
index 0000000..3a8bcc5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilitiesCollection.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.ChildCollection;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+public class CapabilitiesCollection implements
+ ChildCollection<ConfigResource, CapabilityResource> {
+ private final DynamicMap<RestView<CapabilityResource>> views;
+ private final Provider<ListCapabilities> list;
+
+ @Inject
+ CapabilitiesCollection(DynamicMap<RestView<CapabilityResource>> views,
+ Provider<ListCapabilities> list) {
+ this.views = views;
+ this.list = list;
+ }
+
+ @Override
+ public RestView<ConfigResource> list() throws ResourceNotFoundException {
+ return list.get();
+ }
+
+ @Override
+ public CapabilityResource parse(ConfigResource parent, IdString id)
+ throws ResourceNotFoundException {
+ throw new ResourceNotFoundException(id);
+ }
+
+ @Override
+ public DynamicMap<RestView<CapabilityResource>> views() {
+ return views;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
new file mode 100644
index 0000000..c0a014c
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityConstants.java
@@ -0,0 +1,42 @@
+// Copyright (C) 2013 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.config;
+
+import org.eclipse.jgit.nls.NLS;
+import org.eclipse.jgit.nls.TranslationBundle;
+
+public class CapabilityConstants extends TranslationBundle {
+ public static CapabilityConstants get() {
+ return NLS.getBundleFor(CapabilityConstants.class);
+ }
+
+ public String accessDatabase;
+ public String administrateServer;
+ public String createAccount;
+ public String createGroup;
+ public String createProject;
+ public String emailReviewers;
+ public String flushCaches;
+ public String killTask;
+ public String priority;
+ public String queryLimit;
+ public String runAs;
+ public String runGC;
+ public String startReplication;
+ public String streamEvents;
+ public String viewCaches;
+ public String viewConnections;
+ public String viewQueue;
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
new file mode 100644
index 0000000..7e3c87e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/CapabilityResource.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class CapabilityResource extends ConfigResource {
+ public static final TypeLiteral<RestView<CapabilityResource>> CAPABILITY_KIND =
+ new TypeLiteral<RestView<CapabilityResource>>() {};
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
new file mode 100644
index 0000000..5ed007a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigCollection.java
@@ -0,0 +1,52 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestCollection;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.inject.Inject;
+
+public class ConfigCollection implements
+ RestCollection<TopLevelResource, ConfigResource> {
+ private final DynamicMap<RestView<ConfigResource>> views;
+
+ @Inject
+ ConfigCollection(DynamicMap<RestView<ConfigResource>> views) {
+ this.views = views;
+ }
+
+ @Override
+ public RestView<TopLevelResource> list() throws ResourceNotFoundException {
+ throw new ResourceNotFoundException();
+ }
+
+ @Override
+ public DynamicMap<RestView<ConfigResource>> views() {
+ return views;
+ }
+
+ @Override
+ public ConfigResource parse(TopLevelResource root, IdString id)
+ throws ResourceNotFoundException {
+ if (id.equals("server")) {
+ return new ConfigResource();
+ }
+ throw new ResourceNotFoundException(id);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
new file mode 100644
index 0000000..ec0e0c2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigResource.java
@@ -0,0 +1,24 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.gerrit.extensions.restapi.RestResource;
+import com.google.gerrit.extensions.restapi.RestView;
+import com.google.inject.TypeLiteral;
+
+public class ConfigResource implements RestResource {
+ public static final TypeLiteral<RestView<ConfigResource>> CONFIG_KIND =
+ new TypeLiteral<RestView<ConfigResource>>() {};
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 9370a89..160b33f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -240,6 +240,7 @@
install(new AuditModule());
install(new com.google.gerrit.server.account.Module());
install(new com.google.gerrit.server.change.Module());
+ install(new com.google.gerrit.server.config.Module());
install(new com.google.gerrit.server.group.Module());
install(new com.google.gerrit.server.project.Module());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
new file mode 100644
index 0000000..d92dfa7
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2013 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.config;
+
+import com.google.common.collect.Maps;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+
+import java.util.Map;
+
+/** List capabilities visible to the calling user. */
+public class ListCapabilities implements RestReadView<ConfigResource> {
+ @Override
+ public Map<String, CapabilityInfo> apply(ConfigResource resource)
+ throws AuthException, BadRequestException, ResourceConflictException,
+ IllegalArgumentException, SecurityException, IllegalAccessException,
+ NoSuchFieldException {
+ Map<String, CapabilityInfo> output = Maps.newTreeMap();
+ Class<? extends CapabilityConstants> bundleClass =
+ CapabilityConstants.get().getClass();
+ CapabilityConstants c = CapabilityConstants.get();
+ for (String id : GlobalCapability.getAllNames()) {
+ String name = (String) bundleClass.getField(id).get(c);
+ output.put(id, new CapabilityInfo(id, name));
+ }
+ return output;
+ }
+
+ public static class CapabilityInfo {
+ final String kind = "gerritcodereview#capability";
+ public String id;
+ public String name;
+
+ public CapabilityInfo(String id, String name) {
+ this.id = id;
+ this.name = name;
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
new file mode 100644
index 0000000..81c2de9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -0,0 +1,30 @@
+// Copyright (C) 2013 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.config;
+
+import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
+import static com.google.gerrit.server.config.CapabilityResource.CAPABILITY_KIND;
+
+import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.RestApiModule;
+
+public class Module extends RestApiModule {
+ @Override
+ protected void configure() {
+ DynamicMap.mapOf(binder(), CONFIG_KIND);
+ DynamicMap.mapOf(binder(), CAPABILITY_KIND);
+ child(CONFIG_KIND, "capabilities").to(CapabilitiesCollection.class);
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 0f72aa0..6be2efc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -512,7 +512,7 @@
if (isPermission(varName)) {
Permission perm = as.getPermission(varName, true);
loadPermissionRules(rc, ACCESS, refName, varName, groupsByName,
- perm, perm.isLabel());
+ perm, Permission.hasRange(varName));
}
}
}
@@ -869,7 +869,7 @@
for (Permission permission : sort(as.getPermissions())) {
have.add(permission.getName().toLowerCase());
- boolean needRange = permission.isLabel();
+ boolean needRange = Permission.hasRange(permission.getName());
List<String> rules = new ArrayList<String>();
for (PermissionRule rule : sort(permission.getRules())) {
GroupReference group = rule.getGroup();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
index 86cebdc..998fc3b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
@@ -98,10 +98,11 @@
@Override
public Address from(final Account.Id fromId) {
if (fromId != null) {
- final Account a = accountCache.get(fromId).getAccount();
- if (a.getPreferredEmail() != null) {
- return new Address(a.getFullName(), a.getPreferredEmail());
- }
+ Account a = accountCache.get(fromId).getAccount();
+ String userEmail = a.getPreferredEmail();
+ return new Address(
+ a.getFullName(),
+ userEmail != null ? userEmail : srvAddr.getEmail());
}
return srvAddr;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index b46f1e7..fc1b808 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -126,6 +126,11 @@
aId = psa != null ? toObjectId(db, psa) : null;
bId = toObjectId(db, psb);
+ if ((psa != null && !control.isPatchVisible(db.patchSets().get(psa), db)) ||
+ (psb != null && !control.isPatchVisible(db.patchSets().get(psb), db))) {
+ throw new NoSuchChangeException(changeId);
+ }
+
final Repository git;
try {
git = repoManager.openRepository(projectKey);
@@ -220,6 +225,9 @@
// proper rename detection between the patch sets.
//
for (final PatchSet ps : db.patchSets().byChange(changeId)) {
+ if (!control.isPatchVisible(ps, db)) {
+ continue;
+ }
String name = fileName;
if (psa != null) {
switch (changeType) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index 5985de0..0c29ec8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -16,6 +16,7 @@
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.errors.InvalidRevisionException;
+import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
import com.google.gerrit.extensions.restapi.DefaultInput;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -80,7 +81,8 @@
@Override
public BranchInfo apply(ProjectResource rsrc, Input input)
- throws BadRequestException, ResourceConflictException, IOException {
+ throws BadRequestException, AuthException, ResourceConflictException,
+ IOException {
if (input == null) {
input = new Input();
}
@@ -124,7 +126,7 @@
}
if (!refControl.canCreate(rw, object)) {
- throw new IllegalStateException("Cannot create \"" + ref + "\"");
+ throw new AuthException("Cannot create \"" + ref + "\"");
}
try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 59b7670..e191436 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -395,7 +395,7 @@
/** The range of permitted values associated with a label permission. */
public PermissionRange getRange(String permission) {
- if (Permission.isLabel(permission)) {
+ if (Permission.hasRange(permission)) {
return toRange(permission, access(permission));
}
return null;
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
new file mode 100644
index 0000000..d19fd22
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/config/CapabilityConstants.properties
@@ -0,0 +1,17 @@
+accessDatabase = Access Database
+administrateServer = Administrate Server
+createAccount = Create Account
+createGroup = Create Group
+createProject = Create Project
+emailReviewers = Email Reviewers
+flushCaches = Flush Caches
+killTask = Kill Task
+priority = Priority
+queryLimit = Query Limit
+runAs = Run As
+runGC = Run Garbage Collection
+startReplication = Start Replication
+streamEvents = Stream Events
+viewCaches = View Caches
+viewConnections = View Connections
+viewQueue = View Queue
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
new file mode 100644
index 0000000..f97ca55
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ListCapabilitiesTest.java
@@ -0,0 +1,39 @@
+// Copyright (C) 2013 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.config;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.server.config.ListCapabilities.CapabilityInfo;
+
+import org.junit.Test;
+
+import java.util.Map;
+
+public class ListCapabilitiesTest {
+ @Test
+ public void testList() throws Exception {
+ Map<String, CapabilityInfo> m =
+ new ListCapabilities().apply(new ConfigResource());
+ for (String id : GlobalCapability.getAllNames()) {
+ assertTrue("contains " + id, m.containsKey(id));
+ assertEquals(id, m.get(id).id);
+ assertNotNull(id + " has name", m.get(id).name);
+ }
+ }
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
index 29e3df0..2db2b67 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
@@ -102,12 +102,13 @@
public void testUSER_NoPreferredEmailUser() {
setFrom("USER");
- final Account.Id user = user("A U. Thor", null);
+ final String name = "A U. Thor";
+ final Account.Id user = user(name, null);
replay(accountCache);
final Address r = create().from(user);
assertNotNull(r);
- assertEquals(ident.getName(), r.name);
+ assertEquals(name, r.name);
assertEquals(ident.getEmailAddress(), r.email);
verify(accountCache);
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
index ccf23e1..298a099 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SuExec.java
@@ -19,6 +19,7 @@
import com.google.gerrit.server.CurrentUser;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PeerDaemonUser;
+import com.google.gerrit.server.config.AuthConfig;
import com.google.gerrit.sshd.SshScope.Context;
import com.google.inject.Inject;
import com.google.inject.Provider;
@@ -45,6 +46,7 @@
private final SshScope sshScope;
private final DispatchCommandProvider dispatcher;
+ private boolean enableRunAs;
private Provider<CurrentUser> caller;
private Provider<SshSession> session;
private IdentifiedUser.GenericFactory userFactory;
@@ -66,36 +68,34 @@
@CommandName(Commands.ROOT) final DispatchCommandProvider dispatcher,
final Provider<CurrentUser> caller, final Provider<SshSession> session,
final IdentifiedUser.GenericFactory userFactory,
- final SshScope.Context callingContext) {
+ final SshScope.Context callingContext,
+ AuthConfig config) {
this.sshScope = sshScope;
this.dispatcher = dispatcher;
this.caller = caller;
this.session = session;
this.userFactory = userFactory;
this.callingContext = callingContext;
+ this.enableRunAs = config.isRunAsEnabled();
atomicCmd = Atomics.newReference();
}
@Override
public void start(Environment env) throws IOException {
try {
- if (caller.get() instanceof PeerDaemonUser) {
- parseCommandLine();
+ checkCanRunAs();
+ parseCommandLine();
- final Context ctx = callingContext.subContext(newSession(), join(args));
- final Context old = sshScope.set(ctx);
- try {
- final BaseCommand cmd = dispatcher.get();
- cmd.setArguments(args.toArray(new String[args.size()]));
- provideStateTo(cmd);
- atomicCmd.set(cmd);
- cmd.start(env);
- } finally {
- sshScope.set(old);
- }
-
- } else {
- throw new UnloggedFailure(1, "fatal: Not a peer daemon");
+ final Context ctx = callingContext.subContext(newSession(), join(args));
+ final Context old = sshScope.set(ctx);
+ try {
+ final BaseCommand cmd = dispatcher.get();
+ cmd.setArguments(args.toArray(new String[args.size()]));
+ provideStateTo(cmd);
+ atomicCmd.set(cmd);
+ cmd.start(env);
+ } finally {
+ sshScope.set(old);
}
} catch (UnloggedFailure e) {
String msg = e.getMessage();
@@ -108,6 +108,17 @@
}
}
+ private void checkCanRunAs() throws UnloggedFailure {
+ if (caller.get() instanceof PeerDaemonUser) {
+ // OK.
+ } else if (!enableRunAs) {
+ throw new UnloggedFailure(1,
+ "fatal: suexec disabled by auth.enableRunAs = false");
+ } else if (!caller.get().getCapabilities().canRunAs()) {
+ throw new UnloggedFailure(1, "fatal: suexec not permitted");
+ }
+ }
+
private SshSession newSession() {
final SocketAddress peer;
if (peerAddress == null) {
@@ -115,8 +126,12 @@
} else {
peer = peerAddress;
}
+ CurrentUser self = caller.get();
+ if (self instanceof PeerDaemonUser) {
+ self = null;
+ }
return new SshSession(session.get(), peer,
- userFactory.create(peer, accountId));
+ userFactory.runAs(peer, accountId, self));
}
private static String join(List<String> args) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
index 1c3d828..f5abf4b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowConnections.java
@@ -27,9 +27,11 @@
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IoSession;
+import org.apache.sshd.server.Environment;
import org.apache.sshd.server.session.ServerSession;
import org.kohsuke.args4j.Option;
+import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
@@ -47,9 +49,28 @@
@Option(name = "--numeric", aliases = {"-n"}, usage = "don't resolve names")
private boolean numeric;
+ @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
+ private boolean wide;
+
@Inject
private SshDaemon daemon;
+ private int hostNameWidth;
+ private int columns = 80;
+
+ @Override
+ public void start(final Environment env) throws IOException {
+ String s = env.getEnv().get(Environment.ENV_COLUMNS);
+ if (s != null && !s.isEmpty()) {
+ try {
+ columns = Integer.parseInt(s);
+ } catch (NumberFormatException err) {
+ columns = 80;
+ }
+ }
+ super.start(env);
+ }
+
@Override
protected void run() throws Failure {
final IoAcceptor acceptor = daemon.getIoAcceptor();
@@ -71,6 +92,8 @@
}
});
+ hostNameWidth = wide ? Integer.MAX_VALUE : columns - 9 - 9 - 10 - 32;
+
final long now = System.currentTimeMillis();
stdout.print(String.format("%-8s %8s %8s %-15s %s\n", //
"Session", "Start", "Idle", "User", "Remote Host"));
@@ -83,7 +106,7 @@
final long start = io.getCreationTime();
final long idle = now - io.getLastIoTime();
- stdout.print(String.format("%8s %8s %8s %-15.15s %.30s\n", //
+ stdout.print(String.format("%8s %8s %8s %-15.15s %s\n", //
id(sd), //
time(now, start), //
age(idle), //
@@ -160,6 +183,11 @@
if (host == null) {
host = remoteAddress.toString();
}
+
+ if (host.length() > hostNameWidth) {
+ return host.substring(0, hostNameWidth);
+ }
+
return host;
}
}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
index fee5275..88973fc 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ShowQueue.java
@@ -42,7 +42,7 @@
@AdminHighPriorityCommand
@CommandMetaData(name = "show-queue", descr = "Display the background work queues, including replication")
final class ShowQueue extends SshCommand {
- @Option(name = "-w", usage = "display without line width truncation")
+ @Option(name = "--wide", aliases = {"-w"}, usage = "display without line width truncation")
private boolean wide;
@Inject
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 2204422..9f37c9d 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -36,3 +36,25 @@
main = 'compiler.py',
visibility = ['PUBLIC'],
)
+
+maven_jar(
+ name = 'gwt-test-utils',
+ id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.45-SNAPSHOT-20130612.054327-7',
+ sha1 = 'ad53b8a05df35fbe394db80d1d7d2913a646bfb3',
+ license = 'Apache2.0',
+ repository = 'http://oss.sonatype.org/content/repositories/snapshots',
+ deps = [
+ ':javassist',
+ '//lib/log:api',
+ ],
+ visibility = ['PUBLIC'],
+)
+
+maven_jar(
+ name = 'javassist',
+ id = 'org.javassist:javassist:3.16.1-GA',
+ sha1 = '315891b371395271977af518d4db5cee1a0bc9bf',
+ license = 'Apache2.0',
+ visibility = [],
+)
+
diff --git a/lib/maven.defs b/lib/maven.defs
index 8cc3a11..551c951 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -44,11 +44,17 @@
raise NameError('expected id="groupId:artifactId:version"')
group, artifact, version = parts
- jar = path.join(name, artifact.lower() + '-' + version)
+ if 'SNAPSHOT' in version:
+ file_version = version.replace('-SNAPSHOT', '')
+ version = version.split('-')[0] + '-SNAPSHOT'
+ else:
+ file_version = version
+
+ jar = path.join(name, artifact.lower() + '-' + file_version)
url = '/'.join([
repository,
group.replace('.', '/'), artifact, version,
- artifact + '-' + version])
+ artifact + '-' + file_version])
binjar = jar + '.jar'
binurl = url + '.jar'
diff --git a/tools/download_file.py b/tools/download_file.py
index d75c984..5fbdb17 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -122,12 +122,17 @@
if not path.exists(cache_ent):
try:
safe_mkdirs(path.dirname(cache_ent))
- print('Download %s' % src_url, file=stderr)
- check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
except OSError as err:
print('error creating directory %s: %s' %
(path.dirname(cache_ent), err), file=stderr)
exit(1)
+
+ print('Download %s' % src_url, file=stderr)
+ try:
+ check_call(['curl', '--proxy-anyauth', '-sfo', cache_ent, src_url])
+ except OSError as err:
+ print('could not invoke curl: %s\nis curl installed?' % err, file=stderr)
+ exit(1)
except CalledProcessError as err:
print('error using curl: %s' % err, file=stderr)
exit(1)
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index f8ed0a0..f2b51a9 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -30,6 +30,7 @@
'//gerrit-acceptance-tests:acceptance_tests',
'//gerrit-gwtdebug:gwtdebug',
'//gerrit-gwtui:ui_module',
+ '//gerrit-gwtui:ui_tests',
'//gerrit-httpd:httpd_tests',
'//gerrit-main:main_lib',
'//gerrit-server:server__compile',