Merge "Add acceptance tests for listing group includes"
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index 70e9cfd..e0b056b 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -1109,8 +1109,9 @@
~~~~~~~~~~~~~~~~~~~
The 'BLOCK' rule blocks a permission globally. An inherited 'BLOCK' rule cannot
-be overridden in the inheriting project. Any 'ALLOW' rule which conflicts with
-an inherited 'BLOCK' rule will not be honored. Searching for 'BLOCK' rules, in
+be overridden in the inheriting project. Any 'ALLOW' rule, from a different
+access section or from an inheriting project, which conflicts with an
+inherited 'BLOCK' rule will not be honored. Searching for 'BLOCK' rules, in
the chain of parent projects, ignores the Exclusive flag that is normally
applied to access sections.
@@ -1131,6 +1132,26 @@
every vote from '-INFINITE..min' and 'max..INFINITE'. For the example above it
means that the range '-1..+1' is not affected by this block.
+'BLOCK' and 'ALLOW' rules in the same access section
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+When an access section of a project contains a 'BLOCK' and an 'ALLOW' rule for
+the same permission then this 'ALLOW' rule overrides the 'BLOCK' rule:
+
+====
+ [access "refs/heads/*"]
+ push = block group X
+ push = group Y
+====
+
+In this case a user which is a member of the group 'Y' will still be allowed to
+push to 'refs/heads/*' even if it is a member of the group 'X'.
+
+NOTE: An 'ALLOW' rule overrides a 'BLOCK' rule only when both of them are
+inside the same access section of the same project. An 'ALLOW' rule in a
+different access section of the same project or in any access section in an
+inheriting project cannot override a 'BLOCK' rule.
+
Examples
~~~~~~~~
@@ -1160,6 +1181,23 @@
pushTag = group Project Owners
====
+Let only a dedicated group vote in a special category
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+Assume there is a more restrictive process for submitting changes in stable
+release branches which is manifested as a new voting category
+'Release-Process'. Assume we want to make sure that only a 'Release Engineers'
+group can vote in this category and that even project owners cannot approve
+this category. We have to block everyone except the 'Release Engineers' to vote
+in this category and, of course, allow 'Release Engineers' to vote in that
+category. In the "`All-Projects`" we define the following rules:
+
+====
+ [access "refs/heads/stable*"]
+ label-Release-Proces = block -1..+1 group Anonymous Users
+ label-Release-Proces = -1..+1 group Release Engineers
+====
+
[[conversion_table]]
Conversion table from 2.1.x series to 2.2.x series
--------------------------------------------------
@@ -1328,6 +1366,13 @@
command, but also to the web UI results pagination size.
+[[capability_accessDatabase]]
+Access Database
+~~~~~~~~~~~~~~~
+
+Allow users to access the database using the `gsql` command.
+
+
[[capability_startReplication]]
Start Replication
~~~~~~~~~~~~~~~~~
diff --git a/Documentation/rest-api-groups.txt b/Documentation/rest-api-groups.txt
index 1da538d..b24616f 100644
--- a/Documentation/rest-api-groups.txt
+++ b/Documentation/rest-api-groups.txt
@@ -153,7 +153,6 @@
"MyProject-Committers": {
"kind": "gerritcodereview#group",
"id": "9999c971bb4ab872aab759d8c49833ee6b9ff320",
- "name": "MyProject-Committers",
"url": "#/admin/groups/uuid-9999c971bb4ab872aab759d8c49833ee6b9ff320",
"options": {
"visible_to_all": true
@@ -954,8 +953,8 @@
The request also succeeds if the group is already included in this
group, but then the HTTP response code is `200 OK`.
-POST /groups/\{group-id\}/groups (Include Groups)
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+Include Groups
+~~~~~~~~~~~~~~
[verse]
'POST /groups/link:#group-id[\{group-id\}]/groups'
@@ -1112,7 +1111,9 @@
|Field Name ||Description
|`kind` ||`gerritcodereview#group`
|`id` ||The URL encoded UUID of the group.
-|`name` ||The name of the group.
+|`name` |
+not set if returned in a map where the group name is used as map key|
+The name of the group.
|`url` |optional|
URL to information about the group. Typically a URL to a web page that
permits users to apply to join the group, or manage their membership.
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 3c77568..55eff49 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -129,6 +129,50 @@
}
----
+[[create-project]]
+Create Project
+~~~~~~~~~~~~~~
+[verse]
+'PUT /projects/link:#project-name[\{project-name\}]'
+
+Creates a new project.
+
+In the request body additional data for the project can be provided as
+link:#project-input[ProjectInput].
+
+.Request
+----
+ PUT /projects/MyProject HTTP/1.0
+ Content-Type: application/json;charset=UTF-8
+
+ {
+ "description": "This is a demo project.",
+ "submit_type": "CHERRY_PICK",
+ "owners": [
+ "MyProject-Owners"
+ ]
+ }
+----
+
+As response the link:#project-info[ProjectInfo] entity is returned that
+describes the created project.
+
+.Response
+----
+ HTTP/1.1 201 Created
+ Content-Disposition: attachment
+ Content-Type: application/json;charset=UTF-8
+
+ )]}'
+ {
+ "kind": "gerritcodereview#project",
+ "id": "MyProject",
+ "name": "MyProject",
+ "parent": "All-Projects",
+ "description": "This is a demo project."
+ }
+----
+
[[get-project-description]]
Get Project Description
~~~~~~~~~~~~~~~~~~~~~~~
@@ -627,6 +671,56 @@
|`branches` |optional|Map of branch names to HEAD revisions.
|===========================
+[[project-input]]
+ProjectInput
+~~~~~~~~~~~~
+The `ProjectInput` entity contains information for the creation of
+a new project.
+
+[options="header",width="50%",cols="1,^2,4"]
+|=========================================
+|Field Name ||Description
+|`name` |optional|
+The name of the project (not encoded). +
+If set, must match the project name in the URL.
+|`parent` |optional|
+The name of the parent project. +
+If not set, the `All-Projects` project will be the parent project.
+|`description` |optional|The description of the project.
+|`permissions_only` |`false` if not set|
+Whether a permission-only project should be created.
+|`create_empty_commit` |`false` if not set|
+Whether an empty initial commit should be created.
+|`submit_type` |optional|
+The submit type that should be set for the project
+(`MERGE_IF_NECESSARY`, `REBASE_IF_NECESSARY`, `FAST_FORWARD_ONLY`,
+`MERGE_ALWAYS`, `CHERRY_PICK`). +
+If not set, `MERGE_IF_NECESSARY` is set as submit type.
+|`branches` |optional|
+A list of branches that should be initially created. +
+For the branch names the `refs/heads/` prefix can be omitted.
+|`owners` |optional|
+A list of groups that should be assigned as project owner. +
+Each group in the list must be specified as
+link:rest-api-groups.html#group-id[group-id]. +
+If not set, the link:config-gerrit.html#repository.name.ownerGroup[
+groups that are configured as default owners] are set as project
+owners.
+|`use_contributor_agreements`|`INHERIT` if not set|
+Whether contributor agreements should be used for the project (`TRUE`,
+`FALSE`, `INHERIT`).
+|`use_signed_off_by` |`INHERIT` if not set|
+Whether the usage of 'Signed-Off-By' footers is required for the
+project (`TRUE`, `FALSE`, `INHERIT`).
+|`use_content_merge` |`INHERIT` if not set|
+Whether content merge should be enabled for the project (`TRUE`,
+`FALSE`, `INHERIT`). +
+`FALSE`, if the `submit_type` is `FAST_FORWARD_ONLY`.
+|`require_change_id` |`INHERIT` if not set|
+Whether the usage of Change-Ids is required for the project (`TRUE`,
+`FALSE`, `INHERIT`).
+|=========================================
+
[[project-parent-input]]
ProjectParentInput
~~~~~~~~~~~~~~~~~~
diff --git a/ReleaseNotes/ReleaseNotes-2.6.txt b/ReleaseNotes/ReleaseNotes-2.6.txt
index ce8831f..70526e1 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.txt
@@ -52,6 +52,11 @@
* Remove Reviewer is a new permission.
* Pushing a signed tag is a new permission.
* Editing the topic name is a new permission.
+* Raw database access with the `gsql` command is a new global capability.
++
+Previously site administrators had this capability by default. Now it has
+to be explicitly assigned, even for site administrators.
+
* link:https://code.google.com/p/gerrit/issues/detail?id=1585[Issue 1585]:
Viewing other users' draft changes is a new permission.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
new file mode 100644
index 0000000..3b249f3
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/CreateProjectIT.java
@@ -0,0 +1,275 @@
+// 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.acceptance.rest.project;
+
+import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectInfo;
+import static com.google.gerrit.acceptance.rest.project.ProjectAssert.assertProjectOwners;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import com.google.common.reflect.TypeToken;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gson.Gson;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+
+import com.jcraft.jsch.JSchException;
+
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+
+public class CreateProjectIT extends AbstractDaemonTest {
+
+ @Inject
+ private AccountCreator accounts;
+
+ @Inject
+ private ProjectCache projectCache;
+
+ @Inject
+ private GroupCache groupCache;
+
+ @Inject
+ private GitRepositoryManager git;
+
+ private TestAccount admin;
+ private RestSession session;
+
+ @Before
+ public void setUp() throws Exception {
+ admin = accounts.create("admin", "admin@example.com", "Administrator",
+ "Administrators");
+ session = new RestSession(admin);
+ }
+
+ @Test
+ public void testCreateProject() throws IOException {
+ final String newProjectName = "newProject";
+ RestResponse r = session.put("/projects/" + newProjectName);
+ assertEquals(HttpStatus.SC_CREATED, r.getStatusCode());
+ @SuppressWarnings("serial")
+ ProjectInfo p = (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+ assertEquals(newProjectName, p.name);
+ ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ assertNotNull(projectState);
+ assertProjectInfo(projectState.getProject(), p);
+ assertHead(newProjectName, "refs/heads/master");
+ }
+
+ @Test
+ public void testCreateProjectWithNameMismatch_BadRequest() throws IOException {
+ ProjectInput in = new ProjectInput();
+ in.name = "otherName";
+ RestResponse r = session.put("/projects/someName", in);
+ assertEquals(HttpStatus.SC_BAD_REQUEST, r.getStatusCode());
+ }
+
+ @Test
+ public void testCreateProjectWithProperties() throws IOException {
+ final String newProjectName = "newProject";
+ ProjectInput in = new ProjectInput();
+ in.description = "Test description";
+ in.submit_type = SubmitType.CHERRY_PICK;
+ in.use_contributor_agreements = InheritableBoolean.TRUE;
+ in.use_signed_off_by = InheritableBoolean.TRUE;
+ in.use_content_merge = InheritableBoolean.TRUE;
+ in.require_change_id = InheritableBoolean.TRUE;
+ RestResponse r = session.put("/projects/" + newProjectName, in);
+ @SuppressWarnings("serial")
+ ProjectInfo p = (new Gson()).fromJson(r.getReader(), new TypeToken<ProjectInfo>() {}.getType());
+ assertEquals(newProjectName, p.name);
+ Project project = projectCache.get(new Project.NameKey(newProjectName)).getProject();
+ assertProjectInfo(project, p);
+ assertEquals(in.description, project.getDescription());
+ assertEquals(in.submit_type, project.getSubmitType());
+ assertEquals(in.use_contributor_agreements, project.getUseContributorAgreements());
+ assertEquals(in.use_signed_off_by, project.getUseSignedOffBy());
+ assertEquals(in.use_content_merge, project.getUseContentMerge());
+ assertEquals(in.require_change_id, project.getRequireChangeID());
+ }
+
+ @Test
+ public void testCreateChildProject() throws IOException {
+ final String parentName = "parent";
+ RestResponse r = session.put("/projects/" + parentName);
+ r.consume();
+ final String childName = "child";
+ ProjectInput in = new ProjectInput();
+ in.parent = parentName;
+ r = session.put("/projects/" + childName, in);
+ Project project = projectCache.get(new Project.NameKey(childName)).getProject();
+ assertEquals(in.parent, project.getParentName());
+ }
+
+ public void testCreateChildProjectUnderNonExistingParent_UnprocessableEntity()
+ throws IOException {
+ ProjectInput in = new ProjectInput();
+ in.parent = "non-existing-project";
+ RestResponse r = session.put("/projects/child", in);
+ assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ }
+
+ @Test
+ public void testCreateProjectWithOwner() throws IOException {
+ final String newProjectName = "newProject";
+ ProjectInput in = new ProjectInput();
+ in.owners = Lists.newArrayListWithCapacity(3);
+ in.owners.add("Administrators"); // by name
+ in.owners.add(groupUuid("Registered Users").get()); // by group UUID
+ in.owners.add(Integer.toString(groupCache.get(new AccountGroup.NameKey("Anonymous Users"))
+ .getId().get())); // by legacy group ID
+ session.put("/projects/" + newProjectName, in);
+ ProjectState projectState = projectCache.get(new Project.NameKey(newProjectName));
+ Set<AccountGroup.UUID> expectedOwnerIds = Sets.newHashSetWithExpectedSize(3);
+ expectedOwnerIds.add(groupUuid("Administrators"));
+ expectedOwnerIds.add(groupUuid("Registered Users"));
+ expectedOwnerIds.add(groupUuid("Anonymous Users"));
+ assertProjectOwners(expectedOwnerIds, projectState);
+ }
+
+ public void testCreateProjectWithNonExistingOwner_UnprocessableEntity()
+ throws IOException {
+ ProjectInput in = new ProjectInput();
+ in.owners = Collections.singletonList("non-existing-group");
+ RestResponse r = session.put("/projects/newProject", in);
+ assertEquals(HttpStatus.SC_UNPROCESSABLE_ENTITY, r.getStatusCode());
+ }
+
+ @Test
+ public void testCreatePermissionOnlyProject() throws IOException {
+ final String newProjectName = "newProject";
+ ProjectInput in = new ProjectInput();
+ in.permissions_only = true;
+ session.put("/projects/" + newProjectName, in);
+ assertHead(newProjectName, GitRepositoryManager.REF_CONFIG);
+ }
+
+ @Test
+ public void testCreateProjectWithEmptyCommit() throws IOException {
+ final String newProjectName = "newProject";
+ ProjectInput in = new ProjectInput();
+ in.create_empty_commit = true;
+ session.put("/projects/" + newProjectName, in);
+ assertEmptyCommit(newProjectName, "refs/heads/master");
+ }
+
+ @Test
+ public void testCreateProjectWithBranches() throws IOException {
+ final String newProjectName = "newProject";
+ ProjectInput in = new ProjectInput();
+ in.create_empty_commit = true;
+ in.branches = Lists.newArrayListWithCapacity(3);
+ in.branches.add("refs/heads/test");
+ in.branches.add("refs/heads/master");
+ in.branches.add("release"); // without 'refs/heads' prefix
+ session.put("/projects/" + newProjectName, in);
+ assertHead(newProjectName, "refs/heads/test");
+ assertEmptyCommit(newProjectName, "refs/heads/test", "refs/heads/master",
+ "refs/heads/release");
+ }
+
+ @Test
+ public void testCreateProjectWithoutCapability_Forbidden() throws OrmException,
+ JSchException, IOException {
+ TestAccount user = accounts.create("user", "user@example.com", "User");
+ RestResponse r = (new RestSession(user)).put("/projects/newProject");
+ assertEquals(HttpStatus.SC_FORBIDDEN, r.getStatusCode());
+ }
+
+ @Test
+ public void testCreateProjectWhenProjectAlreadyExists_Conflict()
+ throws OrmException, JSchException, IOException {
+ RestResponse r = session.put("/projects/All-Projects");
+ assertEquals(HttpStatus.SC_CONFLICT, r.getStatusCode());
+ }
+
+ private AccountGroup.UUID groupUuid(String groupName) {
+ return groupCache.get(new AccountGroup.NameKey(groupName)).getGroupUUID();
+ }
+
+ private void assertHead(String projectName, String expectedRef)
+ throws RepositoryNotFoundException, IOException {
+ Repository repo = git.openRepository(new Project.NameKey(projectName));
+ try {
+ assertEquals(expectedRef, repo.getRef(Constants.HEAD).getTarget()
+ .getName());
+ } finally {
+ repo.close();
+ }
+ }
+
+ private void assertEmptyCommit(String projectName, String... refs)
+ throws RepositoryNotFoundException, IOException {
+ Repository repo = git.openRepository(new Project.NameKey(projectName));
+ RevWalk rw = new RevWalk(repo);
+ TreeWalk tw = new TreeWalk(repo);
+ try {
+ for (String ref : refs) {
+ RevCommit commit = rw.lookupCommit(repo.getRef(ref).getObjectId());
+ rw.parseBody(commit);
+ tw.addTree(commit.getTree());
+ assertFalse("ref " + ref + " has non empty commit", tw.next());
+ tw.reset();
+ }
+ } finally {
+ tw.release();
+ rw.release();
+ repo.close();
+ }
+ }
+
+ @SuppressWarnings("unused")
+ private static class ProjectInput {
+ String name;
+ String parent;
+ String description;
+ boolean permissions_only;
+ boolean create_empty_commit;
+ SubmitType submit_type;
+ List<String> branches;
+ List<String> owners;
+ InheritableBoolean use_contributor_agreements;
+ InheritableBoolean use_signed_off_by;
+ InheritableBoolean use_content_merge;
+ InheritableBoolean require_change_id;
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
new file mode 100644
index 0000000..25ccbee
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectAssert.java
@@ -0,0 +1,49 @@
+// 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.acceptance.rest.project;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.project.ProjectState;
+
+import java.util.Set;
+
+public class ProjectAssert {
+
+ public static void assertProjectInfo(Project project, ProjectInfo info) {
+ if (info.name != null) {
+ // 'name' is not set if returned in a map
+ assertEquals(project.getName(), info.name);
+ }
+ assertEquals(project.getName(), Url.decode(info.id));
+ Project.NameKey parentName = project.getParent(new Project.NameKey("All-Projects"));
+ assertEquals(parentName != null ? parentName.get() : null, info.parent);
+ assertEquals(project.getDescription(), Strings.nullToEmpty(info.description));
+ }
+
+ public static void assertProjectOwners(Set<AccountGroup.UUID> expectedOwners,
+ ProjectState state) {
+ for (AccountGroup.UUID g : state.getOwners()) {
+ assertTrue("unexpected owner group " + g, expectedOwners.remove(g));
+ }
+ assertTrue("missing owner groups: " + expectedOwners,
+ expectedOwners.isEmpty());
+ }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
new file mode 100644
index 0000000..72dd2d6
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/ProjectInfo.java
@@ -0,0 +1,22 @@
+// 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.acceptance.rest.project;
+
+public class ProjectInfo {
+ public String id;
+ public String name;
+ public String parent;
+ public String description;
+}
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 67e9de6..973cc6a 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
@@ -21,6 +21,9 @@
/** Server wide capabilities. Represented as {@link Permission} objects. */
public class GlobalCapability {
+ /** Ability to access the database (with gsql). */
+ public static final String ACCESS_DATABASE = "accessDatabase";
+
/**
* Denotes the server's administrators.
* <p>
@@ -81,6 +84,7 @@
static {
NAMES_ALL = new ArrayList<String>();
+ NAMES_ALL.add(ACCESS_DATABASE);
NAMES_ALL.add(ADMINISTRATE_SERVER);
NAMES_ALL.add(CREATE_ACCOUNT);
NAMES_ALL.add(CREATE_GROUP);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
index a8aa045..0638906 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ProjectAdminService.java
@@ -22,7 +22,6 @@
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
import com.google.gwtjsonrpc.common.RpcImpl;
-import com.google.gwtjsonrpc.common.VoidResult;
import com.google.gwtjsonrpc.common.RpcImpl.Version;
import java.util.List;
@@ -35,12 +34,6 @@
void projectDetail(Project.NameKey projectName,
AsyncCallback<ProjectDetail> callback);
- @Audit
- @SignInRequired
- void createNewProject(String projectName, String parentName,
- boolean emptyCommit, boolean permissionsOnly,
- AsyncCallback<VoidResult> callback);
-
void projectAccess(Project.NameKey projectName,
AsyncCallback<ProjectAccess> callback);
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.java
new file mode 100644
index 0000000..b63697f
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/restapi/UnprocessableEntityException.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.extensions.restapi;
+
+/** Resource referenced in the request body is not found (HTTP 422 Unprocessable Entity). */
+public class UnprocessableEntityException extends RestApiException {
+ private static final long serialVersionUID = 1L;
+
+ public UnprocessableEntityException(String msg) {
+ super(msg);
+ }
+}
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 9d65222..c8a7c6e 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
@@ -146,6 +146,7 @@
# Capability Names
capabilityNames = \
+ accessDatabase, \
administrateServer, \
createAccount, \
createGroup, \
@@ -159,7 +160,8 @@
viewCaches, \
viewConnections, \
viewQueue
-administrateServer = Administrate Server
+accessDatabase = Access Database
+administrateServer = Administrate Server
createAccount = Create Account
createGroup = Create Group
createProject = Create Project
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
index a6b0f17..f7d50f2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/CreateProjectScreen.java
@@ -20,7 +20,9 @@
import com.google.gerrit.client.ErrorDialog;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.NotFoundScreen;
+import com.google.gerrit.client.VoidResult;
import com.google.gerrit.client.account.AccountCapabilities;
+import com.google.gerrit.client.projects.ProjectApi;
import com.google.gerrit.client.projects.ProjectInfo;
import com.google.gerrit.client.projects.ProjectMap;
import com.google.gerrit.client.rpc.GerritCallback;
@@ -38,6 +40,7 @@
import com.google.gwt.event.dom.client.KeyPressEvent;
import com.google.gwt.event.dom.client.KeyPressHandler;
import com.google.gwt.user.client.History;
+import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
@@ -45,7 +48,6 @@
import com.google.gwt.user.client.ui.SuggestBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwtexpui.globalkey.client.NpTextBox;
-import com.google.gwtjsonrpc.common.VoidResult;
public class CreateProjectScreen extends Screen {
private Grid grid;
@@ -231,9 +233,8 @@
}
enableForm(false);
- Util.PROJECT_SVC.createNewProject(projectName, parentName,
- emptyCommit.getValue(), permissionsOnly.getValue(),
- new GerritCallback<VoidResult>() {
+ ProjectApi.createProject(projectName, parentName, emptyCommit.getValue(),
+ permissionsOnly.getValue(), new AsyncCallback<VoidResult>() {
@Override
public void onSuccess(VoidResult result) {
String nameWithoutSuffix = ProjectUtil.stripGitSuffix(projectName);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
new file mode 100644
index 0000000..a6676dc
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -0,0 +1,51 @@
+// 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.projects;
+
+import com.google.gerrit.client.VoidResult;
+import com.google.gerrit.client.rpc.RestApi;
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+public class ProjectApi {
+ /** Create a new project */
+ public static void createProject(String projectName, String parent,
+ Boolean createEmptyCcommit, Boolean permissionsOnly,
+ AsyncCallback<VoidResult> asyncCallback) {
+ ProjectInput input = ProjectInput.create();
+ input.setName(projectName);
+ input.setParent(parent);
+ input.setPermissionsOnly(permissionsOnly);
+ input.setCreateEmptyCommit(createEmptyCcommit);
+ new RestApi("/projects/").id(projectName).ifNoneMatch()
+ .put(input, asyncCallback);
+ }
+
+ private static class ProjectInput extends JavaScriptObject {
+ static ProjectInput create() {
+ return (ProjectInput) createObject();
+ }
+
+ protected ProjectInput() {
+ }
+
+ final native void setName(String n) /*-{ if(n)this.name=n; }-*/;
+
+ final native void setParent(String p) /*-{ if(p)this.parent=p; }-*/;
+
+ final native void setPermissionsOnly(boolean po) /*-{ if(po)this.permissions_only=po; }-*/;
+
+ final native void setCreateEmptyCommit(boolean cc) /*-{ if(cc)this.create_empty_commit=cc; }-*/;
+ }
+}
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 edfdc23..0061cbf 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
@@ -62,6 +62,7 @@
import com.google.gerrit.extensions.restapi.RestResource;
import com.google.gerrit.extensions.restapi.RestView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
import com.google.gerrit.httpd.WebSession;
import com.google.gerrit.server.AccessPath;
import com.google.gerrit.server.AnonymousUser;
@@ -319,6 +320,9 @@
Objects.firstNonNull(e.getMessage(), "Precondition failed"));
} catch (ResourceNotFoundException e) {
replyError(res, status = SC_NOT_FOUND, "Not found");
+ } catch (UnprocessableEntityException e) {
+ replyError(res, status = 422,
+ Objects.firstNonNull(e.getMessage(), "Unprocessable Entity"));
} catch (AmbiguousViewException e) {
replyError(res, status = SC_NOT_FOUND, e.getMessage());
} catch (MalformedJsonException e) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java
deleted file mode 100644
index 149c866..0000000
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/CreateProjectHandler.java
+++ /dev/null
@@ -1,85 +0,0 @@
-// Copyright (C) 2011 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License
-
-package com.google.gerrit.httpd.rpc.project;
-
-import com.google.gerrit.common.errors.ProjectCreationFailedException;
-import com.google.gerrit.httpd.rpc.Handler;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.Project.SubmitType;
-import com.google.gerrit.server.project.PerformCreateProject;
-import com.google.gerrit.server.project.CreateProjectArgs;
-import com.google.gerrit.server.project.NoSuchProjectException;
-import com.google.gerrit.server.project.ProjectControl;
-import com.google.gwtjsonrpc.common.VoidResult;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-
-import java.util.Collections;
-
-public class CreateProjectHandler extends Handler<VoidResult> {
-
- interface Factory {
- CreateProjectHandler create(@Assisted("projectName") String projectName,
- @Assisted("parentName") String parentName,
- @Assisted("emptyCommit") boolean emptyCommit,
- @Assisted("permissionsOnly") boolean permissionsOnly);
- }
-
- private final PerformCreateProject.Factory createProjectFactory;
- private final ProjectControl.Factory projectControlFactory;
- private final String projectName;
- private final String parentName;
- private final boolean emptyCommit;
- private final boolean permissionsOnly;
-
- @Inject
- public CreateProjectHandler(final PerformCreateProject.Factory createProjectFactory,
- final ProjectControl.Factory projectControlFactory,
- @Assisted("projectName") final String projectName,
- @Assisted("parentName") final String parentName,
- @Assisted("emptyCommit") final boolean emptyCommit,
- @Assisted("permissionsOnly") final boolean permissionsOnly) {
- this.createProjectFactory = createProjectFactory;
- this.projectControlFactory = projectControlFactory;
- this.projectName = projectName;
- this.parentName = parentName;
- this.emptyCommit = emptyCommit;
- this.permissionsOnly = permissionsOnly;
- }
-
- @Override
- public VoidResult call() throws ProjectCreationFailedException {
- final CreateProjectArgs args = new CreateProjectArgs();
- args.setProjectName(projectName);
- if (!parentName.equals("")) {
- final Project.NameKey nameKey = new Project.NameKey(parentName);
- try {
- args.newParent = projectControlFactory.validateFor(nameKey);
- } catch (NoSuchProjectException e) {
- throw new ProjectCreationFailedException("Parent project \""
- + parentName + "\" does not exist.", e);
- }
- }
- args.projectDescription = "";
- args.submitType = SubmitType.MERGE_IF_NECESSARY;
- args.branch = Collections.emptyList();
- args.createEmptyCommit = emptyCommit;
- args.permissionsOnly = permissionsOnly;
-
- final PerformCreateProject createProject = createProjectFactory.create(args);
- createProject.createProject();
- return VoidResult.INSTANCE;
- }
-}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
index 35014d2..05510ff 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAdminServiceImpl.java
@@ -41,7 +41,6 @@
private final ListBranches.Factory listBranchesFactory;
private final VisibleProjectDetails.Factory visibleProjectDetailsFactory;
private final ProjectAccessFactory.Factory projectAccessFactory;
- private final CreateProjectHandler.Factory createProjectHandlerFactory;
private final ProjectDetailFactory.Factory projectDetailFactory;
@Inject
@@ -53,8 +52,7 @@
final ListBranches.Factory listBranchesFactory,
final VisibleProjectDetails.Factory visibleProjectDetailsFactory,
final ProjectAccessFactory.Factory projectAccessFactory,
- final ProjectDetailFactory.Factory projectDetailFactory,
- final CreateProjectHandler.Factory createNewProjectFactory) {
+ final ProjectDetailFactory.Factory projectDetailFactory) {
this.addBranchFactory = addBranchFactory;
this.changeProjectAccessFactory = changeProjectAccessFactory;
this.reviewProjectAccessFactory = reviewProjectAccessFactory;
@@ -64,7 +62,6 @@
this.visibleProjectDetailsFactory = visibleProjectDetailsFactory;
this.projectAccessFactory = projectAccessFactory;
this.projectDetailFactory = projectDetailFactory;
- this.createProjectHandlerFactory = createNewProjectFactory;
}
@Override
@@ -131,12 +128,4 @@
addBranchFactory.create(projectName, branchName, startingRevision).to(
callback);
}
-
- @Override
- public void createNewProject(String projectName, String parentName,
- boolean emptyCommit, boolean permissionsOnly,
- AsyncCallback<VoidResult> callback) {
- createProjectHandlerFactory.create(projectName, parentName, emptyCommit,
- permissionsOnly).to(callback);
- }
}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
index e943e3fc..2d4f210 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectModule.java
@@ -31,7 +31,6 @@
factory(AddBranch.Factory.class);
factory(ChangeProjectAccess.Factory.class);
factory(ReviewProjectAccess.Factory.class);
- factory(CreateProjectHandler.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);
factory(ListBranches.Factory.class);
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 d9c42d5..706ae13 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
@@ -130,6 +130,12 @@
|| canAdministrateServer();
}
+
+ /** @return true if the user can access the database (with gsql). */
+ public boolean canAccessDatabase() {
+ return canPerform(GlobalCapability.ACCESS_DATABASE);
+ }
+
/** @return true if the user can force replication to any configured destination. */
public boolean canStartReplication() {
return canPerform(GlobalCapability.START_REPLICATION)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index c0052b9..a432c6d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.account;
+import static com.google.gerrit.common.data.GlobalCapability.ACCESS_DATABASE;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_ACCOUNT;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
@@ -109,6 +110,7 @@
have.put(VIEW_CONNECTIONS, cc.canViewConnections());
have.put(VIEW_QUEUE, cc.canViewQueue());
have.put(START_REPLICATION, cc.canStartReplication());
+ have.put(ACCESS_DATABASE, cc.canAccessDatabase());
QueueProvider.QueueType queue = cc.getQueueType();
if (queue != QueueProvider.QueueType.INTERACTIVE
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
index d270656..2a43841 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
@@ -16,6 +16,7 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
@@ -40,7 +41,7 @@
@Override
public List<GroupInfo> apply(AccountResource resource)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
IdentifiedUser user = resource.getUser();
Account.Id userId = user.getAccountId();
List<GroupInfo> groups = Lists.newArrayList();
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 49fba6b..ab3db84 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
@@ -91,6 +91,7 @@
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.AccessControlModule;
import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.PerformCreateProject;
import com.google.gerrit.server.project.PermissionCollection;
import com.google.gerrit.server.project.ProjectCacheImpl;
import com.google.gerrit.server.project.ProjectControl;
@@ -185,6 +186,7 @@
factory(ProjectState.Factory.class);
factory(RebasedPatchSetSender.Factory.class);
factory(ReplacePatchSetSender.Factory.class);
+ factory(PerformCreateProject.Factory.class);
bind(PermissionCollection.Factory.class);
bind(AccountVisibility.class)
.toProvider(AccountVisibilityProvider.class)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 38489ed..4a36ba4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -23,11 +23,9 @@
import com.google.gerrit.server.changedetail.PublishDraft;
import com.google.gerrit.server.git.BanCommit;
import com.google.gerrit.server.git.MergeOp;
-import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.SubmoduleOp;
import com.google.gerrit.server.patch.RemoveReviewer;
import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.PerformCreateProject;
import com.google.gerrit.server.project.PerRequestProjectControlCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.SuggestParentCandidates;
@@ -40,7 +38,6 @@
bind(RequestCleanup.class).in(RequestScoped.class);
bind(RequestScopedReviewDbProvider.class);
bind(IdentifiedUser.RequestFactory.class).in(SINGLETON);
- bind(MetaDataUpdate.User.class).in(RequestScoped.class);
bind(ApprovalsUtil.class);
bind(PerRequestProjectControlCache.class).in(RequestScoped.class);
@@ -56,7 +53,6 @@
factory(DeleteDraftPatchSet.Factory.class);
factory(PublishDraft.Factory.class);
factory(RemoveReviewer.Factory.class);
- factory(PerformCreateProject.Factory.class);
factory(SuggestParentCandidates.Factory.class);
factory(BanCommit.Factory.class);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index 94791cb..79b80e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -179,7 +179,7 @@
@Override
public Object apply(IncludedGroupResource resource,
PutIncludedGroup.Input input) throws ResourceNotFoundException,
- OrmException {
+ MethodNotAllowedException, OrmException {
// Do nothing, the group is already included.
return get.get().apply(resource);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
index f05694d..af336c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/CreateGroup.java
@@ -24,6 +24,7 @@
import com.google.gerrit.extensions.annotations.RequiresCapability;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestModifyView;
import com.google.gerrit.extensions.restapi.TopLevelResource;
@@ -79,7 +80,7 @@
@Override
public GroupInfo apply(TopLevelResource resource, Input input)
throws ResourceNotFoundException, AuthException, BadRequestException,
- OrmException, NameAlreadyUsedException {
+ NameAlreadyUsedException, MethodNotAllowedException, OrmException {
if (input == null) {
input = new Input();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetDetail.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetDetail.java
index adb04cd..9d17b62 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetDetail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetDetail.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.group;
import com.google.gerrit.common.groups.ListGroupsOption;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
@@ -32,7 +33,7 @@
@Override
public GroupInfo apply(GroupResource rsrc) throws ResourceNotFoundException,
- OrmException {
+ MethodNotAllowedException, OrmException {
return json.format(rsrc);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java
index 32cf9be..7bc5ee0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetGroup.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.group;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
@@ -30,7 +31,7 @@
@Override
public GroupInfo apply(GroupResource resource)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
return json.format(resource.getGroup());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetIncludedGroup.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetIncludedGroup.java
index 7e63816..d6ae1c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetIncludedGroup.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetIncludedGroup.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.group;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.server.group.GroupJson.GroupInfo;
@@ -30,7 +31,7 @@
@Override
public GroupInfo apply(IncludedGroupResource rsrc)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
return json.format(rsrc.getMemberDescription());
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetOwner.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetOwner.java
index 797544d..169fbdd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GetOwner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GetOwner.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.group;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -36,7 +37,7 @@
@Override
public GroupInfo apply(GroupResource resource)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
AccountGroup group = resource.toAccountGroup();
if (group == null) {
throw new ResourceNotFoundException();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
index 6a729fc..dcf3667 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupJson.java
@@ -21,6 +21,7 @@
import com.google.gerrit.common.data.GroupDescription;
import com.google.gerrit.common.data.GroupDescriptions;
import com.google.gerrit.common.groups.ListGroupsOption;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -64,15 +65,15 @@
return this;
}
- public GroupInfo format(GroupResource rsrc) throws ResourceNotFoundException,
- OrmException {
+ public GroupInfo format(GroupResource rsrc)
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
GroupInfo info = init(rsrc.getGroup());
initMembersAndIncludes(rsrc, info);
return info;
}
public GroupInfo format(GroupDescription.Basic group)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
GroupInfo info = init(group);
if (options.contains(MEMBERS) || options.contains(INCLUDES)) {
GroupResource rsrc =
@@ -106,7 +107,7 @@
}
private GroupInfo initMembersAndIncludes(GroupResource rsrc, GroupInfo info)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
if (options.contains(MEMBERS)) {
info.members = listMembers.get().apply(rsrc);
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 88a7ca8..97614ef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -25,6 +25,7 @@
import com.google.gerrit.common.groups.ListGroupsOption;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
@@ -146,7 +147,8 @@
new TypeToken<Map<String, GroupInfo>>() {}.getType());
}
- public List<GroupInfo> get() throws ResourceNotFoundException, OrmException {
+ public List<GroupInfo> get() throws ResourceNotFoundException,
+ MethodNotAllowedException, OrmException {
List<GroupInfo> groupInfos;
if (user != null) {
if (owned) {
@@ -187,7 +189,7 @@
}
private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user)
- throws ResourceNotFoundException, OrmException {
+ throws ResourceNotFoundException, MethodNotAllowedException, OrmException {
List<GroupInfo> groups = Lists.newArrayList();
for (AccountGroup g : filterGroups(groupCache.all())) {
GroupControl ctl = groupControlFactory.controlFor(g);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
index 3536186..31332d3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
@@ -18,6 +18,7 @@
import com.google.common.collect.Lists;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.AccountGroupIncludeByUuid;
@@ -51,9 +52,9 @@
@Override
public List<GroupInfo> apply(GroupResource rsrc)
- throws ResourceNotFoundException, OrmException {
+ throws MethodNotAllowedException, ResourceNotFoundException, OrmException {
if (rsrc.toAccountGroup() == null) {
- throw new ResourceNotFoundException(rsrc.getGroupUUID().get());
+ throw new MethodNotAllowedException();
}
boolean ownerOfParent = rsrc.getControl().isOwner();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index f586d55..a9aae17 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -20,6 +20,7 @@
import com.google.common.collect.Ordering;
import com.google.gerrit.common.data.GroupDetail;
import com.google.gerrit.common.errors.NoSuchGroupException;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestReadView;
import com.google.gerrit.reviewdb.client.Account;
@@ -59,9 +60,9 @@
@Override
public List<AccountInfo> apply(final GroupResource resource)
- throws ResourceNotFoundException, OrmException {
+ throws MethodNotAllowedException, ResourceNotFoundException, OrmException {
if (resource.toAccountGroup() == null) {
- throw new ResourceNotFoundException(resource.getGroupUUID().get());
+ throw new MethodNotAllowedException();
}
try {
final Map<Account.Id, AccountInfo> members =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
index 50efb68..b04f337 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/CleanupHandle.java
@@ -16,19 +16,14 @@
import java.io.File;
import java.io.IOException;
-import java.lang.ref.ReferenceQueue;
-import java.lang.ref.WeakReference;
import java.util.jar.JarFile;
-class CleanupHandle extends WeakReference<ClassLoader> {
+class CleanupHandle {
private final File tmpFile;
private final JarFile jarFile;
CleanupHandle(File tmpFile,
- JarFile jarFile,
- ClassLoader ref,
- ReferenceQueue<ClassLoader> queue) {
- super(ref, queue);
+ JarFile jarFile) {
this.tmpFile = tmpFile;
this.jarFile = jarFile;
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index 4e565d3..b3f3183 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -18,6 +18,7 @@
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
+import com.google.common.collect.Queues;
import com.google.common.collect.Sets;
import com.google.gerrit.extensions.annotations.PluginName;
import com.google.gerrit.extensions.events.LifecycleListener;
@@ -42,7 +43,6 @@
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.lang.ref.ReferenceQueue;
import java.net.URL;
import java.net.URLClassLoader;
import java.text.SimpleDateFormat;
@@ -50,8 +50,11 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
+import java.util.Hashtable;
+import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
@@ -72,8 +75,8 @@
private final ConcurrentMap<String, Plugin> running;
private final ConcurrentMap<String, Plugin> disabled;
private final Map<String, FileSnapshot> broken;
- private final ReferenceQueue<ClassLoader> cleanupQueue;
- private final ConcurrentMap<CleanupHandle, Boolean> cleanupHandles;
+ private final Map<Plugin, CleanupHandle> cleanupHandles;
+ private final Queue<Plugin> toCleanup;
private final Provider<PluginCleanerTask> cleaner;
private final PluginScannerThread scanner;
@@ -91,8 +94,8 @@
running = Maps.newConcurrentMap();
disabled = Maps.newConcurrentMap();
broken = Maps.newHashMap();
- cleanupQueue = new ReferenceQueue<ClassLoader>();
- cleanupHandles = Maps.newConcurrentMap();
+ toCleanup = Queues.newArrayDeque();
+ cleanupHandles = new Hashtable<Plugin,CleanupHandle>();
cleaner = pct;
long checkFrequency = ConfigUtil.getTimeUnit(cfg,
@@ -188,6 +191,15 @@
}
}
+ synchronized private void unloadPlugin(Plugin plugin) {
+ String name = plugin.getName();
+ log.info(String.format("Unloading plugin %s", name));
+ plugin.stop();
+ running.remove(name);
+ disabled.remove(name);
+ toCleanup.add(plugin);
+ }
+
public void disablePlugins(Set<String> names) {
synchronized (this) {
for (String name : names) {
@@ -200,8 +212,7 @@
File off = new File(pluginsDir, active.getName() + ".jar.disabled");
active.getSrcJar().renameTo(off);
- active.stop();
- running.remove(name);
+ unloadPlugin(active);
try {
FileSnapshot snapshot = FileSnapshot.save(off);
Plugin offPlugin = loadPlugin(name, off, snapshot);
@@ -254,12 +265,12 @@
srvInfoImpl.state = ServerInformation.State.SHUTDOWN;
synchronized (this) {
for (Plugin p : running.values()) {
- p.stop();
+ unloadPlugin(p);
}
running.clear();
disabled.clear();
broken.clear();
- if (cleanupHandles.size() > running.size()) {
+ if (!toCleanup.isEmpty()) {
System.gc();
processPendingCleanups();
}
@@ -347,15 +358,14 @@
&& oldPlugin.canReload()
&& newPlugin.canReload();
if (!reload && oldPlugin != null) {
- oldPlugin.stop();
- running.remove(name);
+ unloadPlugin(oldPlugin);
}
if (!newPlugin.isDisabled()) {
newPlugin.start(env);
}
if (reload) {
env.onReloadPlugin(oldPlugin, newPlugin);
- oldPlugin.stop();
+ unloadPlugin(oldPlugin);
} else if (!newPlugin.isDisabled()) {
env.onStartPlugin(newPlugin);
}
@@ -380,8 +390,7 @@
}
}
for (String name : unload){
- log.info(String.format("Unloading plugin %s", name));
- running.remove(name).stop();
+ unloadPlugin(running.get(name));
}
}
@@ -398,16 +407,19 @@
}
synchronized int processPendingCleanups() {
- CleanupHandle h;
- while ((h = (CleanupHandle) cleanupQueue.poll()) != null) {
- h.cleanup();
- cleanupHandles.remove(h);
+ Iterator<Plugin> iterator = toCleanup.iterator();
+ while (iterator.hasNext()) {
+ Plugin plugin = iterator.next();
+ iterator.remove();
+
+ CleanupHandle cleanupHandle = cleanupHandles.remove(plugin);
+ cleanupHandle.cleanup();
}
- return Math.max(0, cleanupHandles.size() - running.size());
+ return toCleanup.size();
}
private void cleanInBackground() {
- int cnt = Math.max(0, cleanupHandles.size() - running.size());
+ int cnt = toCleanup.size();
if (0 < cnt) {
cleaner.get().clean(cnt);
}
@@ -451,20 +463,17 @@
URL[] urls = {tmp.toURI().toURL()};
ClassLoader parentLoader = parentFor(type);
ClassLoader pluginLoader = new URLClassLoader(urls, parentLoader);
- final CleanupHandle cleanupHandle =
- new CleanupHandle(tmp, jarFile, pluginLoader, cleanupQueue);
- cleanupHandle.enqueue();
- cleanupHandles.put(cleanupHandle, Boolean.TRUE);
-
Class<? extends Module> sysModule = load(sysName, pluginLoader);
Class<? extends Module> sshModule = load(sshName, pluginLoader);
Class<? extends Module> httpModule = load(httpName, pluginLoader);
- keep = true;
- return new Plugin(name,
+ Plugin plugin = new Plugin(name,
srcJar, snapshot,
jarFile, manifest,
new File(dataDir, name), type, pluginLoader,
sysModule, sshModule, httpModule);
+ cleanupHandles.put(plugin, new CleanupHandle(tmp, jarFile));
+ keep = true;
+ return plugin;
} finally {
if (!keep) {
jarFile.close();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
new file mode 100644
index 0000000..51e6961
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -0,0 +1,136 @@
+// 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.project;
+
+import com.google.common.base.Objects;
+import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.errors.ProjectCreationFailedException;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
+import com.google.gerrit.reviewdb.client.Project.SubmitType;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.server.project.CreateProject.Input;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.List;
+
+@RequiresCapability(GlobalCapability.CREATE_PROJECT)
+class CreateProject implements RestModifyView<TopLevelResource, Input> {
+ static class Input {
+ String name;
+ String parent;
+ String description;
+ boolean permissionsOnly;
+ boolean createEmptyCommit;
+ SubmitType submitType;
+ List<String> branches;
+ List<String> owners;
+ InheritableBoolean useContributorAgreements;
+ InheritableBoolean useSignedOffBy;
+ InheritableBoolean useContentMerge;
+ InheritableBoolean requireChangeId;
+ }
+
+ static interface Factory {
+ CreateProject create(String name);
+ }
+
+ private final PerformCreateProject.Factory createProjectFactory;
+ private final Provider<ProjectsCollection> projectsCollection;
+ private final Provider<GroupsCollection> groupsCollection;
+ private final ProjectJson json;
+ private final String name;
+
+ @Inject
+ CreateProject(PerformCreateProject.Factory performCreateProjectFactory,
+ Provider<ProjectsCollection> projectsCollection,
+ Provider<GroupsCollection> groupsCollection, ProjectJson json,
+ @Assisted String name) {
+ this.createProjectFactory = performCreateProjectFactory;
+ this.projectsCollection = projectsCollection;
+ this.groupsCollection = groupsCollection;
+ this.json = json;
+ this.name = name;
+ }
+
+ @Override
+ public Object apply(TopLevelResource resource, Input input)
+ throws BadRequestException, UnprocessableEntityException,
+ ProjectCreationFailedException {
+ if (input == null) {
+ input = new Input();
+ }
+ if (input.name != null && !name.equals(input.name)) {
+ throw new BadRequestException("name must match URL");
+ }
+
+ final CreateProjectArgs args = new CreateProjectArgs();
+ args.setProjectName(name);
+ if (!Strings.isNullOrEmpty(input.parent)) {
+ try {
+ args.newParent =
+ projectsCollection.get().parse(input.parent).getControl();
+ } catch (ResourceNotFoundException e) {
+ throw new UnprocessableEntityException(String.format(
+ "parent project \"%s\" not found", input.parent));
+ }
+ }
+ args.createEmptyCommit = input.createEmptyCommit;
+ args.permissionsOnly = input.permissionsOnly;
+ args.projectDescription = Strings.emptyToNull(input.description);
+ args.submitType =
+ Objects.firstNonNull(input.submitType, SubmitType.MERGE_IF_NECESSARY);
+ args.branch = input.branches;
+ if (input.owners != null) {
+ List<AccountGroup.UUID> ownerIds =
+ Lists.newArrayListWithCapacity(input.owners.size());
+ for (String owner : input.owners) {
+ try {
+ ownerIds.add(groupsCollection.get().parse(owner).getGroupUUID());
+ } catch (ResourceNotFoundException e) {
+ throw new UnprocessableEntityException(String.format(
+ "group \"%s\" not found", owner));
+ }
+ }
+ args.ownerIds = ownerIds;
+ }
+ args.contributorAgreements =
+ Objects.firstNonNull(input.useContributorAgreements,
+ InheritableBoolean.INHERIT);
+ args.signedOffBy =
+ Objects.firstNonNull(input.useSignedOffBy, InheritableBoolean.INHERIT);
+ args.contentMerge =
+ input.submitType == SubmitType.FAST_FORWARD_ONLY
+ ? InheritableBoolean.FALSE : Objects.firstNonNull(
+ input.useContentMerge, InheritableBoolean.INHERIT);
+ args.changeIdRequired =
+ Objects.firstNonNull(input.requireChangeId, InheritableBoolean.INHERIT);
+
+ Project p = createProjectFactory.create(args).createProject();
+ return Response.created(json.format(p));
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
index 24c0f6f..7bbd2e7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProjectArgs.java
@@ -41,6 +41,7 @@
signedOffBy = InheritableBoolean.INHERIT;
contentMerge = InheritableBoolean.INHERIT;
changeIdRequired = InheritableBoolean.INHERIT;
+ submitType = SubmitType.MERGE_IF_NECESSARY;
}
public Project.NameKey getProject() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
index 09f2163..a482278 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetProject.java
@@ -14,32 +14,20 @@
package com.google.gerrit.server.project;
-import com.google.common.base.Strings;
import com.google.gerrit.extensions.restapi.RestReadView;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.Project;
+import com.google.inject.Inject;
class GetProject implements RestReadView<ProjectResource> {
- @Override
- public Object apply(ProjectResource resource) {
- Project project = resource.getControl().getProject();
- ProjectInfo info = new ProjectInfo();
- info.name = resource.getName();
- info.parent = Strings.emptyToNull(project.getParentName());
- info.description = Strings.emptyToNull(project.getDescription());
- info.finish();
- return info;
+
+ private final ProjectJson json;
+
+ @Inject
+ GetProject(ProjectJson json) {
+ this.json = json;
}
- static class ProjectInfo {
- final String kind = "gerritcodereview#project";
- String id;
- String name;
- String parent;
- String description;
-
- void finish() {
- id = Url.encode(name);
- }
+ @Override
+ public Object apply(ProjectResource rsrc) {
+ return json.format(rsrc);
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 8ed0138..6b63db3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -19,6 +19,8 @@
import com.google.gerrit.extensions.registration.DynamicMap;
import com.google.gerrit.extensions.restapi.RestApiModule;
+import com.google.gerrit.server.project.CreateProject;
+import com.google.inject.assistedinject.FactoryModuleBuilder;
public class Module extends RestApiModule {
@Override
@@ -29,6 +31,7 @@
DynamicMap.mapOf(binder(), PROJECT_KIND);
DynamicMap.mapOf(binder(), DASHBOARD_KIND);
+ put(PROJECT_KIND).to(PutProject.class);
get(PROJECT_KIND).to(GetProject.class);
get(PROJECT_KIND, "description").to(GetDescription.class);
put(PROJECT_KIND, "description").to(PutDescription.class);
@@ -41,5 +44,6 @@
get(DASHBOARD_KIND).to(GetDashboard.class);
put(DASHBOARD_KIND).to(SetDashboard.class);
delete(DASHBOARD_KIND).to(DeleteDashboard.class);
+ install(new FactoryModuleBuilder().build(CreateProject.Factory.class));
}
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
index 6f2a834..dd8722f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PerformCreateProject.java
@@ -99,7 +99,7 @@
this.metaDataUpdateFactory = metaDataUpdateFactory;
}
- public void createProject() throws ProjectCreationFailedException {
+ public Project createProject() throws ProjectCreationFailedException {
validateParameters();
final Project.NameKey nameKey = createProjectArgs.getProject();
try {
@@ -133,6 +133,8 @@
&& createProjectArgs.createEmptyCommit) {
createEmptyCommits(repo, nameKey, createProjectArgs.branch);
}
+
+ return projectCache.get(nameKey).getProject();
} finally {
repo.close();
}
@@ -150,6 +152,7 @@
if (repo.getObjectDatabase().exists()) {
throw new ProjectCreationFailedException("project \"" + nameKey + "\" exists");
}
+ throw err;
} finally {
repo.close();
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index e416ed7..483ecaf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -16,10 +16,13 @@
import static com.google.gerrit.server.project.RefControl.isRE;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
import com.google.inject.Inject;
import com.google.inject.Singleton;
@@ -73,7 +76,7 @@
}
boolean perUser = false;
- List<AccessSection> sections = new ArrayList<AccessSection>();
+ Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
for (SectionMatcher matcher : matcherList) {
// If the matcher has to expand parameters and its prefix matches the
// reference there is a very good chance the reference is actually user
@@ -93,9 +96,10 @@
}
if (matcher.match(ref, username)) {
- sections.add(matcher.section);
+ sectionToProject.put(matcher.section, matcher.project);
}
}
+ List<AccessSection> sections = Lists.newArrayList(sectionToProject.keySet());
sorter.sort(ref, sections);
Set<SeenRule> seen = new HashSet<SeenRule>();
@@ -104,7 +108,9 @@
HashMap<String, List<PermissionRule>> permissions =
new HashMap<String, List<PermissionRule>>();
+ Map<PermissionRule, ProjectRef> ruleProps = Maps.newIdentityHashMap();
for (AccessSection section : sections) {
+ Project.NameKey project = sectionToProject.get(section);
for (Permission permission : section.getPermissions()) {
boolean exclusivePermissionExists =
exclusiveGroupPermissions.contains(permission.getName());
@@ -124,6 +130,7 @@
permissions.put(permission.getName(), r);
}
r.add(rule);
+ ruleProps.put(rule, new ProjectRef(project, section.getName()));
}
}
@@ -133,16 +140,20 @@
}
}
- return new PermissionCollection(permissions, perUser ? username : null);
+ return new PermissionCollection(permissions, ruleProps,
+ perUser ? username : null);
}
}
private final Map<String, List<PermissionRule>> rules;
+ private final Map<PermissionRule, ProjectRef> ruleProps;
private final String username;
private PermissionCollection(Map<String, List<PermissionRule>> rules,
+ Map<PermissionRule, ProjectRef> ruleProps,
String username) {
this.rules = rules;
+ this.ruleProps = ruleProps;
this.username = username;
}
@@ -167,6 +178,10 @@
return r != null ? r : Collections.<PermissionRule> emptyList();
}
+ ProjectRef getRuleProps(PermissionRule rule) {
+ return ruleProps.get(rule);
+ }
+
/**
* Obtain all declared permission rules that match the reference.
*
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
new file mode 100644
index 0000000..4b42528
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectJson.java
@@ -0,0 +1,57 @@
+// 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.project;
+
+import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.inject.Inject;
+
+public class ProjectJson {
+
+ private final AllProjectsName allProjects;
+
+ @Inject
+ ProjectJson(AllProjectsName allProjects) {
+ this.allProjects = allProjects;
+ }
+
+ public ProjectInfo format(ProjectResource rsrc) {
+ return format(rsrc.getControl().getProject());
+ }
+
+ public ProjectInfo format(Project p) {
+ ProjectInfo info = new ProjectInfo();
+ info.name = p.getName();
+ Project.NameKey parentName = p.getParent(allProjects);
+ info.parent = parentName != null ? parentName.get() : null;
+ info.description = Strings.emptyToNull(p.getDescription());
+ info.finish();
+ return info;
+ }
+
+ static class ProjectInfo {
+ final String kind = "gerritcodereview#project";
+ String id;
+ String name;
+ String parent;
+ String description;
+
+ void finish() {
+ id = Url.encode(name);
+ }
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
new file mode 100644
index 0000000..0315fad
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectRef.java
@@ -0,0 +1,45 @@
+// 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.project;
+
+import com.google.gerrit.reviewdb.client.Project;
+
+class ProjectRef {
+
+ final Project.NameKey project;
+ final String ref;
+
+ ProjectRef(Project.NameKey project, String ref) {
+ this.project = project;
+ this.ref = ref;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ return other instanceof ProjectRef
+ && project.equals(((ProjectRef) other).project)
+ && ref.equals(((ProjectRef) other).ref);
+ }
+
+ @Override
+ public int hashCode() {
+ return project.hashCode() * 31 + ref.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return project + ", " + ref;
+ }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 413cbeb..e42be92f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -212,7 +212,8 @@
section.setPermissions(copy);
}
- SectionMatcher matcher = SectionMatcher.wrap(section);
+ SectionMatcher matcher = SectionMatcher.wrap(getProject().getNameKey(),
+ section);
if (matcher != null) {
sm.add(matcher);
}
@@ -350,4 +351,4 @@
}
return false;
}
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
index 68e6941..c833726 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectsCollection.java
@@ -15,6 +15,7 @@
package com.google.gerrit.server.project;
import com.google.gerrit.extensions.registration.DynamicMap;
+import com.google.gerrit.extensions.restapi.AcceptsCreate;
import com.google.gerrit.extensions.restapi.IdString;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.RestCollection;
@@ -27,21 +28,24 @@
import com.google.inject.Provider;
public class ProjectsCollection implements
- RestCollection<TopLevelResource, ProjectResource> {
+ RestCollection<TopLevelResource, ProjectResource>,
+ AcceptsCreate<TopLevelResource> {
private final DynamicMap<RestView<ProjectResource>> views;
private final Provider<ListProjects> list;
private final ProjectControl.GenericFactory controlFactory;
private final Provider<CurrentUser> user;
+ private final CreateProject.Factory createProjectFactory;
@Inject
ProjectsCollection(DynamicMap<RestView<ProjectResource>> views,
Provider<ListProjects> list,
ProjectControl.GenericFactory controlFactory,
- Provider<CurrentUser> user) {
+ CreateProject.Factory factory, Provider<CurrentUser> user) {
this.views = views;
this.list = list;
this.controlFactory = controlFactory;
this.user = user;
+ this.createProjectFactory = factory;
}
@Override
@@ -52,10 +56,15 @@
@Override
public ProjectResource parse(TopLevelResource parent, IdString id)
throws ResourceNotFoundException {
+ return parse(id.get());
+ }
+
+ public ProjectResource parse(String id)
+ throws ResourceNotFoundException {
ProjectControl ctl;
try {
ctl = controlFactory.controlFor(
- new Project.NameKey(id.get()),
+ new Project.NameKey(id),
user.get());
} catch (NoSuchProjectException e) {
throw new ResourceNotFoundException(id);
@@ -70,4 +79,10 @@
public DynamicMap<RestView<ProjectResource>> views() {
return views;
}
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public CreateProject create(TopLevelResource parent, IdString name) {
+ return createProjectFactory.create(name.get());
+ }
}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
new file mode 100644
index 0000000..0a96cb5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutProject.java
@@ -0,0 +1,28 @@
+// 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.project;
+
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.project.CreateProject.Input;
+
+public class PutProject implements RestModifyView<ProjectResource, Input> {
+ @Override
+ public Object apply(ProjectResource resource, Input input)
+ throws ResourceConflictException {
+ throw new ResourceConflictException("Project \"" + resource.getName()
+ + "\" already exists");
+ }
+}
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 229b062..59b7670 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
@@ -14,6 +14,8 @@
package com.google.gerrit.server.project;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRange;
@@ -42,6 +44,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
/** Manages access control for Git references (aka branches, tags). */
@@ -117,18 +120,18 @@
*/
public boolean isVisibleByRegisteredUsers() {
List<PermissionRule> access = relevant.getPermission(Permission.READ);
+ Set<ProjectRef> allows = Sets.newHashSet();
+ Set<ProjectRef> blocks = Sets.newHashSet();
for (PermissionRule rule : access) {
if (rule.isBlock()) {
- return false;
- }
- }
- for (PermissionRule rule : access) {
- if (rule.getGroup().getUUID().equals(AccountGroup.ANONYMOUS_USERS)
+ blocks.add(relevant.getRuleProps(rule));
+ } else if (rule.getGroup().getUUID().equals(AccountGroup.ANONYMOUS_USERS)
|| rule.getGroup().getUUID().equals(AccountGroup.REGISTERED_USERS)) {
- return true;
+ allows.add(relevant.getRuleProps(rule));
}
}
- return false;
+ blocks.removeAll(allows);
+ return blocks.isEmpty() && !allows.isEmpty();
}
/**
@@ -218,16 +221,7 @@
// granting of powers beyond pushing to the configuration.
return false;
}
- boolean result = false;
- for (PermissionRule rule : access(Permission.PUSH)) {
- if (rule.isBlock()) {
- return false;
- }
- if (rule.getForce()) {
- result = true;
- }
- }
- return result;
+ return canForcePerform(Permission.PUSH);
}
/**
@@ -375,16 +369,7 @@
/** @return true if this user can force edit topic names. */
public boolean canForceEditTopicName() {
- boolean result = false;
- for (PermissionRule rule : access(Permission.EDIT_TOPIC_NAME)) {
- if (rule.isBlock()) {
- return false;
- }
- if (rule.getForce()) {
- result = true;
- }
- }
- return result;
+ return canForcePerform(Permission.EDIT_TOPIC_NAME);
}
/** All value ranges of any allowed label permission. */
@@ -416,39 +401,97 @@
return null;
}
- private static PermissionRange toRange(String permissionName,
- List<PermissionRule> ruleList) {
- int min = 0;
- int max = 0;
- int blockMin = Integer.MIN_VALUE;
- int blockMax = Integer.MAX_VALUE;
- for (PermissionRule rule : ruleList) {
+ private static class AllowedRange {
+ private int allowMin = 0;
+ private int allowMax = 0;
+ private int blockMin = Integer.MIN_VALUE;
+ private int blockMax = Integer.MAX_VALUE;
+
+ void update(PermissionRule rule) {
if (rule.isBlock()) {
blockMin = Math.max(blockMin, rule.getMin());
blockMax = Math.min(blockMax, rule.getMax());
} else {
- min = Math.min(min, rule.getMin());
- max = Math.max(max, rule.getMax());
+ allowMin = Math.min(allowMin, rule.getMin());
+ allowMax = Math.max(allowMax, rule.getMax());
}
}
- if (blockMin > Integer.MIN_VALUE) {
- min = Math.max(min, blockMin + 1);
+
+ int getAllowMin() {
+ return allowMin;
}
- if (blockMax < Integer.MAX_VALUE) {
- max = Math.min(max, blockMax - 1);
+ int getAllowMax() {
+ return allowMax;
}
+ int getBlockMin() {
+ // ALLOW wins over BLOCK on the same project
+ return Math.min(blockMin, allowMin - 1);
+ }
+ int getBlockMax() {
+ // ALLOW wins over BLOCK on the same project
+ return Math.max(blockMax, allowMax + 1);
+ }
+ }
+
+ private PermissionRange toRange(String permissionName,
+ List<PermissionRule> ruleList) {
+ Map<ProjectRef, AllowedRange> ranges = Maps.newHashMap();
+ for (PermissionRule rule : ruleList) {
+ ProjectRef p = relevant.getRuleProps(rule);
+ AllowedRange r = ranges.get(p);
+ if (r == null) {
+ r = new AllowedRange();
+ ranges.put(p, r);
+ }
+ r.update(rule);
+ }
+ int allowMin = 0;
+ int allowMax = 0;
+ int blockMin = Integer.MIN_VALUE;
+ int blockMax = Integer.MAX_VALUE;
+ for (AllowedRange r : ranges.values()) {
+ allowMin = Math.min(allowMin, r.getAllowMin());
+ allowMax = Math.max(allowMax, r.getAllowMax());
+ blockMin = Math.max(blockMin, r.getBlockMin());
+ blockMax = Math.min(blockMax, r.getBlockMax());
+ }
+
+ // BLOCK wins over ALLOW across projects
+ int min = Math.max(allowMin, blockMin + 1);
+ int max = Math.min(allowMax, blockMax - 1);
return new PermissionRange(permissionName, min, max);
}
/** True if the user has this permission. Works only for non labels. */
boolean canPerform(String permissionName) {
List<PermissionRule> access = access(permissionName);
+ Set<ProjectRef> allows = Sets.newHashSet();
+ Set<ProjectRef> blocks = Sets.newHashSet();
for (PermissionRule rule : access) {
if (rule.isBlock() && !rule.getForce()) {
- return false;
+ blocks.add(relevant.getRuleProps(rule));
+ } else {
+ allows.add(relevant.getRuleProps(rule));
}
}
- return !access.isEmpty();
+ blocks.removeAll(allows);
+ return blocks.isEmpty() && !allows.isEmpty();
+ }
+
+ /** True if the user has force this permission. Works only for non labels. */
+ private boolean canForcePerform(String permissionName) {
+ List<PermissionRule> access = access(permissionName);
+ Set<ProjectRef> allows = Sets.newHashSet();
+ Set<ProjectRef> blocks = Sets.newHashSet();
+ for (PermissionRule rule : access) {
+ if (rule.isBlock()) {
+ blocks.add(relevant.getRuleProps(rule));
+ } else if (rule.getForce()) {
+ allows.add(relevant.getRuleProps(rule));
+ }
+ }
+ blocks.removeAll(allows);
+ return blocks.isEmpty() && !allows.isEmpty();
}
/** Rules for the given permission, or the empty list. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
index 1c70b04..6f8af80 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SectionMatcher.java
@@ -18,6 +18,7 @@
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ParameterizedString;
+import com.google.gerrit.reviewdb.client.Project;
import dk.brics.automaton.Automaton;
@@ -31,33 +32,37 @@
* faster selection of which sections are relevant to any given input reference.
*/
abstract class SectionMatcher {
- static SectionMatcher wrap(AccessSection section) {
+ static SectionMatcher wrap(Project.NameKey project, AccessSection section) {
String ref = section.getName();
if (AccessSection.isValid(ref)) {
- return wrap(ref, section);
+ return wrap(project, ref, section);
} else {
return null;
}
}
- static SectionMatcher wrap(String pattern, AccessSection section) {
+ static SectionMatcher wrap(Project.NameKey project, String pattern,
+ AccessSection section) {
if (pattern.contains("${")) {
- return new ExpandParameters(pattern, section);
+ return new ExpandParameters(project, pattern, section);
} else if (isRE(pattern)) {
- return new Regexp(pattern, section);
+ return new Regexp(project, pattern, section);
} else if (pattern.endsWith("/*")) {
- return new Prefix(pattern.substring(0, pattern.length() - 1), section);
+ return new Prefix(project, pattern.substring(0, pattern.length() - 1),
+ section);
} else {
- return new Exact(pattern, section);
+ return new Exact(project, pattern, section);
}
}
+ final Project.NameKey project;
final AccessSection section;
- SectionMatcher(AccessSection section) {
+ SectionMatcher(Project.NameKey project, AccessSection section) {
+ this.project = project;
this.section = section;
}
@@ -66,8 +71,8 @@
private static class Exact extends SectionMatcher {
private final String expect;
- Exact(String name, AccessSection section) {
- super(section);
+ Exact(Project.NameKey project, String name, AccessSection section) {
+ super(project, section);
expect = name;
}
@@ -80,8 +85,8 @@
private static class Prefix extends SectionMatcher {
private final String prefix;
- Prefix(String pfx, AccessSection section) {
- super(section);
+ Prefix(Project.NameKey project, String pfx, AccessSection section) {
+ super(project, section);
prefix = pfx;
}
@@ -94,8 +99,8 @@
private static class Regexp extends SectionMatcher {
private final Pattern pattern;
- Regexp(String re, AccessSection section) {
- super(section);
+ Regexp(Project.NameKey project, String re, AccessSection section) {
+ super(project, section);
pattern = Pattern.compile(re);
}
@@ -109,8 +114,9 @@
private final ParameterizedString template;
private final String prefix;
- ExpandParameters(String pattern, AccessSection section) {
- super(section);
+ ExpandParameters(Project.NameKey project, String pattern,
+ AccessSection section) {
+ super(project, section);
template = new ParameterizedString(pattern);
if (isRE(pattern)) {
@@ -141,7 +147,7 @@
u = username;
}
- SectionMatcher next = wrap(
+ SectionMatcher next = wrap(project,
template.replace(Collections.singletonMap("username", u)),
section);
return next != null ? next.match(ref, username) : false;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index e84cced..c1fb5de 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -54,7 +54,7 @@
}
@Override
- public Object apply(ProjectResource resource, Input input)
+ public String apply(ProjectResource resource, Input input)
throws AuthException, BadRequestException, ResourceConflictException,
Exception {
ProjectControl ctl = resource.getControl();
@@ -83,11 +83,8 @@
config.commit(md);
cache.evict(ctl.getProject());
- ListProjects.ProjectInfo info = new ListProjects.ProjectInfo();
- info.setName(resource.getName());
- info.parent = project.getParentName();
- info.description = project.getDescription();
- return info;
+ Project.NameKey parentName = project.getParent(allProjects);
+ return parentName != null ? parentName.get() : "";
} finally {
md.close();
}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
index ab5ad59..51ea5a2 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/project/RefControlTest.java
@@ -14,6 +14,7 @@
package com.google.gerrit.server.project;
+import static com.google.gerrit.common.data.Permission.EDIT_TOPIC_NAME;
import static com.google.gerrit.common.data.Permission.LABEL;
import static com.google.gerrit.common.data.Permission.OWNER;
import static com.google.gerrit.common.data.Permission.PUSH;
@@ -253,6 +254,139 @@
assertFalse("u can't vote -2", range.contains(-2));
assertFalse("u can't vote 2", range.contains(2));
}
+
+ public void testUnblockNoForce() {
+ grant(local, PUSH, anonymous, "refs/heads/*").setBlock();
+ grant(local, PUSH, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ assertTrue("u can push", u.controlForRef("refs/heads/master").canUpdate());
+ }
+
+ public void testUnblockForce() {
+ PermissionRule r = grant(local, PUSH, anonymous, "refs/heads/*");
+ r.setBlock();
+ r.setForce(true);
+ grant(local, PUSH, devs, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(devs);
+ assertTrue("u can force push", u.controlForRef("refs/heads/master").canForceUpdate());
+ }
+
+ public void testUnblockForceWithAllowNoForce_NotPossible() {
+ PermissionRule r = grant(local, PUSH, anonymous, "refs/heads/*");
+ r.setBlock();
+ r.setForce(true);
+ grant(local, PUSH, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ assertFalse("u can't force push", u.controlForRef("refs/heads/master").canForceUpdate());
+ }
+
+ public void testUnblockMoreSpecificRef_Fails() {
+ grant(local, PUSH, anonymous, "refs/heads/*").setBlock();
+ grant(local, PUSH, devs, "refs/heads/master");
+
+ ProjectControl u = user(devs);
+ assertFalse("u can't push", u.controlForRef("refs/heads/master").canUpdate());
+ }
+
+ public void testUnblockLargerScope_Fails() {
+ grant(local, PUSH, anonymous, "refs/heads/master").setBlock();
+ grant(local, PUSH, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ assertFalse("u can't push", u.controlForRef("refs/heads/master").canUpdate());
+ }
+
+ public void testUnblockInLocal_Fails() {
+ grant(parent, PUSH, anonymous, "refs/heads/*").setBlock();
+ grant(local, PUSH, fixers, "refs/heads/*");
+
+ ProjectControl f = user(fixers);
+ assertFalse("u can't push", f.controlForRef("refs/heads/master").canUpdate());
+ }
+
+ public void testUnblockInParentBlockInLocal() {
+ grant(parent, PUSH, anonymous, "refs/heads/*").setBlock();
+ grant(parent, PUSH, devs, "refs/heads/*");
+ grant(local, PUSH, devs, "refs/heads/*").setBlock();
+
+ ProjectControl d = user(devs);
+ assertFalse("u can't push", d.controlForRef("refs/heads/master").canUpdate());
+ }
+
+ public void testUnblockVisibilityByRegisteredUsers() {
+ grant(local, READ, anonymous, "refs/heads/*").setBlock();
+ grant(local, READ, registered, "refs/heads/*");
+
+ ProjectControl u = user(registered);
+ assertTrue("u can read", u.controlForRef("refs/heads/master").isVisibleByRegisteredUsers());
+ }
+
+ public void testUnblockInLocalVisibilityByRegisteredUsers_Fails() {
+ grant(parent, READ, anonymous, "refs/heads/*").setBlock();
+ grant(local, READ, registered, "refs/heads/*");
+
+ ProjectControl u = user(registered);
+ assertFalse("u can't read", u.controlForRef("refs/heads/master").isVisibleByRegisteredUsers());
+ }
+
+ public void testUnblockForceEditTopicName() {
+ grant(local, EDIT_TOPIC_NAME, anonymous, "refs/heads/*").setBlock();
+ grant(local, EDIT_TOPIC_NAME, devs, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(devs);
+ assertTrue("u can edit topic name", u.controlForRef("refs/heads/master").canForceEditTopicName());
+ }
+
+ public void testUnblockInLocalForceEditTopicName_Fails() {
+ grant(parent, EDIT_TOPIC_NAME, anonymous, "refs/heads/*").setBlock();
+ grant(local, EDIT_TOPIC_NAME, devs, "refs/heads/*").setForce(true);
+
+ ProjectControl u = user(registered);
+ assertFalse("u can't edit topic name", u.controlForRef("refs/heads/master").canForceEditTopicName());
+ }
+
+ public void testUnblockRange() {
+ grant(local, LABEL + "Code-Review", -1, +1, anonymous, "refs/heads/*").setBlock();
+ grant(local, LABEL + "Code-Review", -2, +2, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertTrue("u can vote -2", range.contains(-2));
+ assertTrue("u can vote +2", range.contains(2));
+ }
+
+ public void testUnblockRangeOnMoreSpecificRef_Fails() {
+ grant(local, LABEL + "Code-Review", -1, +1, anonymous, "refs/heads/*").setBlock();
+ grant(local, LABEL + "Code-Review", -2, +2, devs, "refs/heads/master");
+
+ ProjectControl u = user(devs);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertFalse("u can't vote -2", range.contains(-2));
+ assertFalse("u can't vote +2", range.contains(-2));
+ }
+
+ public void testUnblockRangeOnLargerScope_Fails() {
+ grant(local, LABEL + "Code-Review", -1, +1, anonymous, "refs/heads/master").setBlock();
+ grant(local, LABEL + "Code-Review", -2, +2, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertFalse("u can't vote -2", range.contains(-2));
+ assertFalse("u can't vote +2", range.contains(-2));
+ }
+
+ public void testUnblockInLocalRange_Fails() {
+ grant(parent, LABEL + "Code-Review", -1, 1, anonymous, "refs/heads/*").setBlock();
+ grant(local, LABEL + "Code-Review", -2, +2, devs, "refs/heads/*");
+
+ ProjectControl u = user(devs);
+ PermissionRange range = u.controlForRef("refs/heads/master").getRange(LABEL + "Code-Review");
+ assertFalse("u can't vote -2", range.contains(-2));
+ assertFalse("u can't vote 2", range.contains(2));
+ }
// -----------------------------------------------------------------------
private final Map<Project.NameKey, ProjectState> all;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index 0ca8038..ecf370d 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -25,7 +25,7 @@
/** Opens a query processor. */
@AdminHighPriorityCommand
-@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@RequiresCapability(GlobalCapability.ACCESS_DATABASE)
@CommandMetaData(name = "gsql", descr = "Administrative interface to active database")
final class AdminQueryShell extends SshCommand {
@Inject
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
index c04636c..98dd333 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ListGroupsCommand.java
@@ -16,6 +16,7 @@
import com.google.common.base.Objects;
import com.google.common.base.Strings;
+import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -81,7 +82,8 @@
identifiedUser, userFactory, accountGetGroups, json);
}
- void display(final PrintWriter out) throws ResourceNotFoundException, OrmException {
+ void display(final PrintWriter out) throws ResourceNotFoundException,
+ MethodNotAllowedException, OrmException {
final ColumnFormatter formatter = new ColumnFormatter(out, '\t');
for (final GroupInfo info : get()) {
formatter.addColumn(Objects.firstNonNull(info.name, "n/a"));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
index 18ce77f..5226962 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/QueryShell.java
@@ -15,7 +15,9 @@
package com.google.gerrit.sshd.commands;
import com.google.gerrit.common.Version;
+import com.google.gerrit.common.errors.PermissionDeniedException;
import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
import com.google.gson.JsonObject;
import com.google.gwtorm.jdbc.JdbcSchema;
import com.google.gwtorm.server.OrmException;
@@ -55,6 +57,7 @@
private final BufferedReader in;
private final PrintWriter out;
private final SchemaFactory<ReviewDb> dbFactory;
+ private final IdentifiedUser currentUser;
private OutputFormat outputFormat = OutputFormat.PRETTY;
private ReviewDb db;
@@ -63,12 +66,14 @@
@Inject
QueryShell(final SchemaFactory<ReviewDb> dbFactory,
+ final IdentifiedUser currentUser,
@Assisted final InputStream in, @Assisted final OutputStream out)
throws UnsupportedEncodingException {
this.dbFactory = dbFactory;
this.in = new BufferedReader(new InputStreamReader(in, "UTF-8"));
this.out = new PrintWriter(new OutputStreamWriter(out, "UTF-8"));
+ this.currentUser = currentUser;
}
public void setOutputFormat(OutputFormat fmt) {
@@ -77,6 +82,7 @@
public void run() {
try {
+ checkPermission();
db = dbFactory.open();
try {
connection = ((JdbcSchema) db).getConnection();
@@ -99,6 +105,8 @@
} catch (SQLException err) {
out.println("fatal: Cannot open connection: " + err.getMessage());
+ } catch (PermissionDeniedException err) {
+ out.println("fatal: " + err.getMessage());
} finally {
out.flush();
}
@@ -106,6 +114,7 @@
public void execute(String query) {
try {
+ checkPermission();
db = dbFactory.open();
try {
connection = ((JdbcSchema) db).getConnection();
@@ -127,11 +136,31 @@
} catch (SQLException err) {
out.println("fatal: Cannot open connection: " + err.getMessage());
+ } catch (PermissionDeniedException err) {
+ out.println("fatal: " + err.getMessage());
} finally {
out.flush();
}
}
+ /**
+ * Assert that the current user is permitted to perform raw queries.
+ * <p>
+ * As the @RequireCapability guards at various entry points of internal
+ * commands implicitly add administrators (which we want to avoid), we also
+ * check permissions within QueryShell and grant access only to those who
+ * canPerformRawQuery, regardless of whether they are administrators or not.
+ *
+ * @throws PermissionDeniedException
+ */
+ private void checkPermission() throws PermissionDeniedException {
+ if (!currentUser.getCapabilities().canAccessDatabase()) {
+ throw new PermissionDeniedException(String.format(
+ "%s does not have \"Perform Raw Query\" capability.",
+ currentUser.getUserName()));
+ }
+ }
+
private void readEvalPrintLoop() {
final StringBuilder buffer = new StringBuilder();
boolean executed = false;