Inherit project permissions from more than just All Projects
Add the basic functionality to build a tree of projects for easier administation of access rights. With this change, any project can act as a parent project to another. The way to set a project as a parent of another is done with a new command: `gerrit set-project-parent`. Right now there is no possibility to set or remove the parenthood from the UI. Bug: issue 273 Change-Id: Iac514de89e24b470339ea53065f8b470de68ab75 Uploaded-by: Ulrik Sjölin <ulrik.sjolin@sonyericsson.com> Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
parent
072b470570
commit
fa7bdd39ed
@ -106,9 +106,9 @@ members of `Foo` have submit rights on a project, and the members of
|
|||||||
Project Access Control Lists
|
Project Access Control Lists
|
||||||
----------------------------
|
----------------------------
|
||||||
|
|
||||||
A system wide access control list affecting all projects is stored
|
A system wide access control list affecting all projects is stored in
|
||||||
in project id 0 (named "`\-- All Projects \--`" in a default
|
project "`\-- All Projects \--`". This inheritance can be configured
|
||||||
installation). Only the id is recognized as special by Gerrit.
|
through link:cmd-set-project-parent.html[gerrit set-project-parent].
|
||||||
|
|
||||||
Per-project access control lists are also supported.
|
Per-project access control lists are also supported.
|
||||||
|
|
||||||
|
@ -87,6 +87,9 @@ link:cmd-flush-caches.html[gerrit flush-caches]::
|
|||||||
link:cmd-gsql.html[gerrit gsql]::
|
link:cmd-gsql.html[gerrit gsql]::
|
||||||
Administrative interface to active database.
|
Administrative interface to active database.
|
||||||
|
|
||||||
|
link:cmd-set-project-parent.html[gerrit set-project-parent]::
|
||||||
|
Change the project permissions are inherited from.
|
||||||
|
|
||||||
link:cmd-show-caches.html[gerrit show-caches]::
|
link:cmd-show-caches.html[gerrit show-caches]::
|
||||||
Display current cache statistics.
|
Display current cache statistics.
|
||||||
|
|
||||||
|
51
Documentation/cmd-set-project-parent.txt
Normal file
51
Documentation/cmd-set-project-parent.txt
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
gerrit set-project-parent
|
||||||
|
=========================
|
||||||
|
|
||||||
|
NAME
|
||||||
|
----
|
||||||
|
gerrit set-project-parent - Change the project permissions are inherited from.
|
||||||
|
|
||||||
|
SYNOPSIS
|
||||||
|
--------
|
||||||
|
[verse]
|
||||||
|
'ssh' -p <port> <host> 'gerrit set-project-parent' \
|
||||||
|
[\--parent <NAME>] \
|
||||||
|
<NAME> ...
|
||||||
|
|
||||||
|
DESCRIPTION
|
||||||
|
-----------
|
||||||
|
Changes the project that permissions are inherited through.
|
||||||
|
Every project inherits permissions from another project, by
|
||||||
|
default this is `\-- All Projects \--`. This command sets
|
||||||
|
the project to inherit through another one.
|
||||||
|
|
||||||
|
ACCESS
|
||||||
|
------
|
||||||
|
Caller must be a member of the privileged 'Administrators' group.
|
||||||
|
|
||||||
|
SCRIPTING
|
||||||
|
---------
|
||||||
|
This command is intended to be used in scripts.
|
||||||
|
|
||||||
|
OPTIONS
|
||||||
|
-------
|
||||||
|
\--parent::
|
||||||
|
Name of the parent to inherit through. If not specified,
|
||||||
|
the parent is set back to the default `\-- All Projects \--`.
|
||||||
|
|
||||||
|
EXAMPLES
|
||||||
|
--------
|
||||||
|
Configure `kernel/omap` to inherit permissions from `kernel/common`:
|
||||||
|
|
||||||
|
====
|
||||||
|
$ ssh -p 29418 review.example.com gerrit set-project-parent --parent kernel/common kernel/omap
|
||||||
|
====
|
||||||
|
|
||||||
|
SEE ALSO
|
||||||
|
--------
|
||||||
|
|
||||||
|
* link:access-control.html[Access Controls]
|
||||||
|
|
||||||
|
GERRIT
|
||||||
|
------
|
||||||
|
Part of link:index.html[Gerrit Code Review]
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (C) 2010 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.common.data;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.RefRight;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Additional data about a {@link RefRight} not normally loaded: defines if a
|
||||||
|
* right is inherited from a parent structure (e.g. a parent project).
|
||||||
|
*/
|
||||||
|
public class InheritedRefRight {
|
||||||
|
private RefRight right;
|
||||||
|
private boolean inherited;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a instance of a {@link RefRight} with data about inheritance
|
||||||
|
*/
|
||||||
|
protected InheritedRefRight() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a instance of a {@link RefRight} with data about inheritance
|
||||||
|
*
|
||||||
|
* @param right the right
|
||||||
|
* @param inherited true if the right is inherited, false otherwise
|
||||||
|
*/
|
||||||
|
public InheritedRefRight(RefRight right, boolean inherited) {
|
||||||
|
this.right = right;
|
||||||
|
this.inherited = inherited;
|
||||||
|
}
|
||||||
|
|
||||||
|
public RefRight getRight() {
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isInherited() {
|
||||||
|
return inherited;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof InheritedRefRight) {
|
||||||
|
InheritedRefRight a = this;
|
||||||
|
InheritedRefRight b = (InheritedRefRight) o;
|
||||||
|
return a.getRight().equals(b.getRight())
|
||||||
|
&& a.isInherited() == b.isInherited();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getRight().hashCode();
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@ package com.google.gerrit.common.data;
|
|||||||
|
|
||||||
import com.google.gerrit.reviewdb.AccountGroup;
|
import com.google.gerrit.reviewdb.AccountGroup;
|
||||||
import com.google.gerrit.reviewdb.Project;
|
import com.google.gerrit.reviewdb.Project;
|
||||||
import com.google.gerrit.reviewdb.RefRight;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@ -24,7 +23,7 @@ import java.util.Map;
|
|||||||
public class ProjectDetail {
|
public class ProjectDetail {
|
||||||
public Project project;
|
public Project project;
|
||||||
public Map<AccountGroup.Id, AccountGroup> groups;
|
public Map<AccountGroup.Id, AccountGroup> groups;
|
||||||
public List<RefRight> rights;
|
public List<InheritedRefRight> rights;
|
||||||
|
|
||||||
public ProjectDetail() {
|
public ProjectDetail() {
|
||||||
}
|
}
|
||||||
@ -37,7 +36,7 @@ public class ProjectDetail {
|
|||||||
groups = g;
|
groups = g;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRights(final List<RefRight> r) {
|
public void setRights(final List<InheritedRefRight> r) {
|
||||||
rights = r;
|
rights = r;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -36,6 +36,7 @@ public interface AdminConstants extends Constants {
|
|||||||
String useSignedOffBy();
|
String useSignedOffBy();
|
||||||
|
|
||||||
String headingOwner();
|
String headingOwner();
|
||||||
|
String headingParentProjectName();
|
||||||
String headingDescription();
|
String headingDescription();
|
||||||
String headingSubmitType();
|
String headingSubmitType();
|
||||||
String headingGroupType();
|
String headingGroupType();
|
||||||
|
@ -17,6 +17,7 @@ useContributorAgreements = Require a valid contributor agreement to upload
|
|||||||
useSignedOffBy = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
|
useSignedOffBy = Require <a href="http://gerrit.googlecode.com/svn/documentation/2.0/user-signedoffby.html#Signed-off-by" target="_blank"><code>Signed-off-by</code></a> in commit message
|
||||||
|
|
||||||
headingOwner = Owners
|
headingOwner = Owners
|
||||||
|
headingParentProjectName = Rights Inherit From
|
||||||
headingDescription = Description
|
headingDescription = Description
|
||||||
headingSubmitType = Change Submit Action
|
headingSubmitType = Change Submit Action
|
||||||
headingGroupType = Group Type
|
headingGroupType = Group Type
|
||||||
|
@ -14,13 +14,16 @@
|
|||||||
|
|
||||||
package com.google.gerrit.client.admin;
|
package com.google.gerrit.client.admin;
|
||||||
|
|
||||||
|
import com.google.gerrit.client.Dispatcher;
|
||||||
import com.google.gerrit.client.Gerrit;
|
import com.google.gerrit.client.Gerrit;
|
||||||
import com.google.gerrit.client.rpc.GerritCallback;
|
import com.google.gerrit.client.rpc.GerritCallback;
|
||||||
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
|
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
|
||||||
import com.google.gerrit.client.ui.FancyFlexTable;
|
import com.google.gerrit.client.ui.FancyFlexTable;
|
||||||
|
import com.google.gerrit.client.ui.Hyperlink;
|
||||||
import com.google.gerrit.client.ui.SmallHeading;
|
import com.google.gerrit.client.ui.SmallHeading;
|
||||||
import com.google.gerrit.common.data.ApprovalType;
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
import com.google.gerrit.common.data.GerritConfig;
|
import com.google.gerrit.common.data.GerritConfig;
|
||||||
|
import com.google.gerrit.common.data.InheritedRefRight;
|
||||||
import com.google.gerrit.common.data.ProjectDetail;
|
import com.google.gerrit.common.data.ProjectDetail;
|
||||||
import com.google.gerrit.reviewdb.AccountGroup;
|
import com.google.gerrit.reviewdb.AccountGroup;
|
||||||
import com.google.gerrit.reviewdb.ApprovalCategory;
|
import com.google.gerrit.reviewdb.ApprovalCategory;
|
||||||
@ -46,6 +49,7 @@ import com.google.gwt.user.client.ui.Grid;
|
|||||||
import com.google.gwt.user.client.ui.ListBox;
|
import com.google.gwt.user.client.ui.ListBox;
|
||||||
import com.google.gwt.user.client.ui.Panel;
|
import com.google.gwt.user.client.ui.Panel;
|
||||||
import com.google.gwt.user.client.ui.SuggestBox;
|
import com.google.gwt.user.client.ui.SuggestBox;
|
||||||
|
import com.google.gwt.user.client.ui.VerticalPanel;
|
||||||
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
|
||||||
import com.google.gwtexpui.globalkey.client.NpTextBox;
|
import com.google.gwtexpui.globalkey.client.NpTextBox;
|
||||||
import com.google.gwtexpui.safehtml.client.SafeHtml;
|
import com.google.gwtexpui.safehtml.client.SafeHtml;
|
||||||
@ -59,6 +63,9 @@ import java.util.Map;
|
|||||||
public class ProjectRightsPanel extends Composite {
|
public class ProjectRightsPanel extends Composite {
|
||||||
private Project.NameKey projectName;
|
private Project.NameKey projectName;
|
||||||
|
|
||||||
|
private Panel parentPanel;
|
||||||
|
private Hyperlink parentName;
|
||||||
|
|
||||||
private RightsTable rights;
|
private RightsTable rights;
|
||||||
private Button delRight;
|
private Button delRight;
|
||||||
private Button addRight;
|
private Button addRight;
|
||||||
@ -73,6 +80,7 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
projectName = toShow;
|
projectName = toShow;
|
||||||
|
|
||||||
final FlowPanel body = new FlowPanel();
|
final FlowPanel body = new FlowPanel();
|
||||||
|
initParent(body);
|
||||||
initRights(body);
|
initRights(body);
|
||||||
initWidget(body);
|
initWidget(body);
|
||||||
}
|
}
|
||||||
@ -103,6 +111,15 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
rangeMaxBox.setEnabled(canAdd);
|
rangeMaxBox.setEnabled(canAdd);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initParent(final Panel body) {
|
||||||
|
parentPanel = new VerticalPanel();
|
||||||
|
parentPanel.add(new SmallHeading(Util.C.headingParentProjectName()));
|
||||||
|
|
||||||
|
parentName = new Hyperlink("", "");
|
||||||
|
parentPanel.add(parentName);
|
||||||
|
body.add(parentPanel);
|
||||||
|
}
|
||||||
|
|
||||||
private void initRights(final Panel body) {
|
private void initRights(final Panel body) {
|
||||||
final FlowPanel addPanel = new FlowPanel();
|
final FlowPanel addPanel = new FlowPanel();
|
||||||
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
|
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
|
||||||
@ -230,6 +247,20 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void display(final ProjectDetail result) {
|
void display(final ProjectDetail result) {
|
||||||
|
final Project project = result.project;
|
||||||
|
|
||||||
|
final Project.NameKey wildKey = Gerrit.getConfig().getWildProject();
|
||||||
|
final boolean isWild = wildKey.equals(project.getNameKey());
|
||||||
|
Project.NameKey parent = project.getParent();
|
||||||
|
if (parent == null) {
|
||||||
|
parent = wildKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
parentPanel.setVisible(!isWild);
|
||||||
|
parentName.setTargetHistoryToken(Dispatcher.toProjectAdmin(parent,
|
||||||
|
ProjectAdminScreen.ACCESS_TAB));
|
||||||
|
parentName.setText(parent.get());
|
||||||
|
|
||||||
rights.display(result.groups, result.rights);
|
rights.display(result.groups, result.rights);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,11 +431,11 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void display(final Map<AccountGroup.Id, AccountGroup> groups,
|
void display(final Map<AccountGroup.Id, AccountGroup> groups,
|
||||||
final List<RefRight> refRights) {
|
final List<InheritedRefRight> refRights) {
|
||||||
while (1 < table.getRowCount())
|
while (1 < table.getRowCount())
|
||||||
table.removeRow(table.getRowCount() - 1);
|
table.removeRow(table.getRowCount() - 1);
|
||||||
|
|
||||||
for (final RefRight r : refRights) {
|
for (final InheritedRefRight r : refRights) {
|
||||||
final int row = table.getRowCount();
|
final int row = table.getRowCount();
|
||||||
table.insertRow(row);
|
table.insertRow(row);
|
||||||
applyDataRowStyle(row);
|
applyDataRowStyle(row);
|
||||||
@ -413,14 +444,16 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void populate(final int row,
|
void populate(final int row,
|
||||||
final Map<AccountGroup.Id, AccountGroup> groups, final RefRight r) {
|
final Map<AccountGroup.Id, AccountGroup> groups,
|
||||||
|
final InheritedRefRight r) {
|
||||||
final GerritConfig config = Gerrit.getConfig();
|
final GerritConfig config = Gerrit.getConfig();
|
||||||
|
final RefRight right = r.getRight();
|
||||||
final ApprovalType ar =
|
final ApprovalType ar =
|
||||||
config.getApprovalTypes().getApprovalType(r.getApprovalCategoryId());
|
config.getApprovalTypes().getApprovalType(
|
||||||
final AccountGroup group = groups.get(r.getAccountGroupId());
|
right.getApprovalCategoryId());
|
||||||
|
final AccountGroup group = groups.get(right.getAccountGroupId());
|
||||||
|
|
||||||
if (!projectName.equals(Gerrit.getConfig().getWildProject())
|
if (r.isInherited()) {
|
||||||
&& Gerrit.getConfig().getWildProject().equals(r.getProjectNameKey())) {
|
|
||||||
table.setText(row, 1, "");
|
table.setText(row, 1, "");
|
||||||
} else {
|
} else {
|
||||||
table.setWidget(row, 1, new CheckBox());
|
table.setWidget(row, 1, new CheckBox());
|
||||||
@ -429,27 +462,28 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
if (ar != null) {
|
if (ar != null) {
|
||||||
table.setText(row, 2, ar.getCategory().getName());
|
table.setText(row, 2, ar.getCategory().getName());
|
||||||
} else {
|
} else {
|
||||||
table.setText(row, 2, r.getApprovalCategoryId().get());
|
table.setText(row, 2, right.getApprovalCategoryId().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (group != null) {
|
if (group != null) {
|
||||||
table.setText(row, 3, group.getName());
|
table.setText(row, 3, group.getName());
|
||||||
} else {
|
} else {
|
||||||
table.setText(row, 3, Util.M.deletedGroup(r.getAccountGroupId().get()));
|
table.setText(row, 3, Util.M.deletedGroup(right.getAccountGroupId()
|
||||||
|
.get()));
|
||||||
}
|
}
|
||||||
|
|
||||||
table.setText(row, 4, r.getRefPattern());
|
table.setText(row, 4, right.getRefPattern());
|
||||||
|
|
||||||
{
|
{
|
||||||
final SafeHtmlBuilder m = new SafeHtmlBuilder();
|
final SafeHtmlBuilder m = new SafeHtmlBuilder();
|
||||||
final ApprovalCategoryValue min, max;
|
final ApprovalCategoryValue min, max;
|
||||||
min = ar != null ? ar.getValue(r.getMinValue()) : null;
|
min = ar != null ? ar.getValue(right.getMinValue()) : null;
|
||||||
max = ar != null ? ar.getValue(r.getMaxValue()) : null;
|
max = ar != null ? ar.getValue(right.getMaxValue()) : null;
|
||||||
|
|
||||||
formatValue(m, r.getMinValue(), min);
|
formatValue(m, right.getMinValue(), min);
|
||||||
if (r.getMinValue() != r.getMaxValue()) {
|
if (right.getMinValue() != right.getMaxValue()) {
|
||||||
m.br();
|
m.br();
|
||||||
formatValue(m, r.getMaxValue(), max);
|
formatValue(m, right.getMaxValue(), max);
|
||||||
}
|
}
|
||||||
SafeHtml.set(table, row, 5, m);
|
SafeHtml.set(table, row, 5, m);
|
||||||
}
|
}
|
||||||
@ -463,7 +497,7 @@ public class ProjectRightsPanel extends Composite {
|
|||||||
fmt.addStyleName(row, 5, Gerrit.RESOURCES.css()
|
fmt.addStyleName(row, 5, Gerrit.RESOURCES.css()
|
||||||
.projectAdminApprovalCategoryRangeLine());
|
.projectAdminApprovalCategoryRangeLine());
|
||||||
|
|
||||||
setRowItem(row, r);
|
setRowItem(row, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void formatValue(final SafeHtmlBuilder m, final short v,
|
private void formatValue(final SafeHtmlBuilder m, final short v,
|
||||||
|
@ -174,7 +174,7 @@ class AddRefRight extends Handler<ProjectDetail> {
|
|||||||
rr.setMaxValue(max);
|
rr.setMaxValue(max);
|
||||||
db.refRights().update(Collections.singleton(rr));
|
db.refRights().update(Collections.singleton(rr));
|
||||||
}
|
}
|
||||||
projectCache.evict(projectControl.getProject());
|
projectCache.evictAll();
|
||||||
return projectDetailFactory.create(projectName).call();
|
return projectDetailFactory.create(projectName).call();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ class DeleteRefRights extends Handler<VoidResult> {
|
|||||||
db.refRights().delete(Collections.singleton(m));
|
db.refRights().delete(Collections.singleton(m));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
projectCache.evict(projectControl.getProject());
|
projectCache.evictAll();
|
||||||
return VoidResult.INSTANCE;
|
return VoidResult.INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,6 +16,7 @@ package com.google.gerrit.httpd.rpc.project;
|
|||||||
|
|
||||||
import com.google.gerrit.common.data.ApprovalType;
|
import com.google.gerrit.common.data.ApprovalType;
|
||||||
import com.google.gerrit.common.data.ApprovalTypes;
|
import com.google.gerrit.common.data.ApprovalTypes;
|
||||||
|
import com.google.gerrit.common.data.InheritedRefRight;
|
||||||
import com.google.gerrit.common.data.ProjectDetail;
|
import com.google.gerrit.common.data.ProjectDetail;
|
||||||
import com.google.gerrit.httpd.rpc.Handler;
|
import com.google.gerrit.httpd.rpc.Handler;
|
||||||
import com.google.gerrit.reviewdb.AccountGroup;
|
import com.google.gerrit.reviewdb.AccountGroup;
|
||||||
@ -71,26 +72,34 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
|
|||||||
detail.setProject(projectState.getProject());
|
detail.setProject(projectState.getProject());
|
||||||
|
|
||||||
groups = new HashMap<AccountGroup.Id, AccountGroup>();
|
groups = new HashMap<AccountGroup.Id, AccountGroup>();
|
||||||
final List<RefRight> refRights = new ArrayList<RefRight>();
|
final List<InheritedRefRight> refRights = new ArrayList<InheritedRefRight>();
|
||||||
for (final RefRight r : projectState.getLocalRights()) {
|
|
||||||
refRights.add(r);
|
|
||||||
wantGroup(r.getAccountGroupId());
|
|
||||||
}
|
|
||||||
for (final RefRight r : projectState.getInheritedRights()) {
|
for (final RefRight r : projectState.getInheritedRights()) {
|
||||||
refRights.add(r);
|
InheritedRefRight refRight = new InheritedRefRight(r, true);
|
||||||
|
if (!refRights.contains(refRight)) {
|
||||||
|
refRights.add(refRight);
|
||||||
|
wantGroup(r.getAccountGroupId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final RefRight r : projectState.getLocalRights()) {
|
||||||
|
refRights.add(new InheritedRefRight(r, false));
|
||||||
wantGroup(r.getAccountGroupId());
|
wantGroup(r.getAccountGroupId());
|
||||||
}
|
}
|
||||||
|
|
||||||
loadGroups();
|
loadGroups();
|
||||||
|
|
||||||
Collections.sort(refRights, new Comparator<RefRight>() {
|
Collections.sort(refRights, new Comparator<InheritedRefRight>() {
|
||||||
@Override
|
@Override
|
||||||
public int compare(final RefRight a, final RefRight b) {
|
public int compare(final InheritedRefRight a, final InheritedRefRight b) {
|
||||||
int rc = categoryOf(a).compareTo(categoryOf(b));
|
final RefRight right1 = a.getRight();
|
||||||
|
final RefRight right2 = b.getRight();
|
||||||
|
int rc = categoryOf(right1).compareTo(categoryOf(right2));
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
rc = a.getRefPattern().compareTo(b.getRefPattern());
|
rc = right1.getRefPattern().compareTo(right2.getRefPattern());
|
||||||
}
|
}
|
||||||
if (rc == 0) {
|
if (rc == 0) {
|
||||||
rc = groupOf(a).compareTo(groupOf(b));
|
rc = groupOf(right1).compareTo(groupOf(right2));
|
||||||
}
|
}
|
||||||
return rc;
|
return rc;
|
||||||
}
|
}
|
||||||
|
@ -96,6 +96,9 @@ public final class Project {
|
|||||||
@Column(id = 5)
|
@Column(id = 5)
|
||||||
protected char submitType;
|
protected char submitType;
|
||||||
|
|
||||||
|
@Column(id = 6, notNull = false, name = "parent_name")
|
||||||
|
protected NameKey parent;
|
||||||
|
|
||||||
protected Project() {
|
protected Project() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,4 +154,12 @@ public final class Project {
|
|||||||
useSignedOffBy = update.useSignedOffBy;
|
useSignedOffBy = update.useSignedOffBy;
|
||||||
submitType = update.submitType;
|
submitType = update.submitType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Project.NameKey getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParent(final Project.NameKey parentProjectName) {
|
||||||
|
parent = parentProjectName;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -152,6 +152,23 @@ public final class RefRight {
|
|||||||
maxValue = m;
|
maxValue = m;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return getKey().hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (o instanceof RefRight) {
|
||||||
|
RefRight a = this;
|
||||||
|
RefRight b = (RefRight) o;
|
||||||
|
return a.getKey().equals(b.getKey())
|
||||||
|
&& a.getMinValue() == b.getMinValue()
|
||||||
|
&& a.getMaxValue() == b.getMaxValue();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
private static class RefPatternOrder implements Comparator<RefRight> {
|
private static class RefPatternOrder implements Comparator<RefRight> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -121,6 +121,11 @@ public abstract class SelfPopulatingCache<K, V> implements Cache<K, V> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Remove all cached items, forcing them to be created again on demand. */
|
||||||
|
public void removeAll(){
|
||||||
|
self.removeAll();
|
||||||
|
}
|
||||||
|
|
||||||
public void put(K key, V value) {
|
public void put(K key, V value) {
|
||||||
self.put(new Element(key, value));
|
self.put(new Element(key, value));
|
||||||
}
|
}
|
||||||
|
@ -28,4 +28,7 @@ public interface ProjectCache {
|
|||||||
|
|
||||||
/** Invalidate the cached information about the given project. */
|
/** Invalidate the cached information about the given project. */
|
||||||
public void evict(Project p);
|
public void evict(Project p);
|
||||||
|
|
||||||
|
/** Invalidate the cached information about all projects. */
|
||||||
|
public void evictAll();
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import com.google.inject.Singleton;
|
|||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
import com.google.inject.name.Named;
|
import com.google.inject.name.Named;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
|
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
|
||||||
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
|
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
|
||||||
core(type, CACHE_NAME);
|
core(type, CACHE_NAME);
|
||||||
bind(ProjectCacheImpl.class);
|
bind(ProjectCacheImpl.class);
|
||||||
bind(ProjectCache.class).to(ProjectCacheImpl.class);
|
bind(ProjectCache.class).to(ProjectCacheImpl.class);
|
||||||
@ -51,8 +52,6 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final ProjectState.Factory projectStateFactory;
|
private final ProjectState.Factory projectStateFactory;
|
||||||
private final Project.NameKey wildProject;
|
|
||||||
private final ProjectState.InheritedRights inheritedRights;
|
|
||||||
private final SchemaFactory<ReviewDb> schema;
|
private final SchemaFactory<ReviewDb> schema;
|
||||||
private final SelfPopulatingCache<Project.NameKey, ProjectState> byName;
|
private final SelfPopulatingCache<Project.NameKey, ProjectState> byName;
|
||||||
|
|
||||||
@ -63,7 +62,6 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName) {
|
@Named(CACHE_NAME) final Cache<Project.NameKey, ProjectState> byName) {
|
||||||
projectStateFactory = psf;
|
projectStateFactory = psf;
|
||||||
schema = sf;
|
schema = sf;
|
||||||
wildProject = wp;
|
|
||||||
|
|
||||||
this.byName =
|
this.byName =
|
||||||
new SelfPopulatingCache<Project.NameKey, ProjectState>(byName) {
|
new SelfPopulatingCache<Project.NameKey, ProjectState>(byName) {
|
||||||
@ -73,15 +71,15 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
return lookup(key);
|
return lookup(key);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
this.inheritedRights = new ProjectState.InheritedRights() {
|
|
||||||
@Override
|
|
||||||
public Collection<RefRight> get() {
|
|
||||||
return ProjectCacheImpl.this.get(wildProject).getLocalRights();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lookup for a state of a specified project on database
|
||||||
|
*
|
||||||
|
* @param key the project name key
|
||||||
|
* @return the project state
|
||||||
|
* @throws OrmException
|
||||||
|
*/
|
||||||
private ProjectState lookup(final Project.NameKey key) throws OrmException {
|
private ProjectState lookup(final Project.NameKey key) throws OrmException {
|
||||||
final ReviewDb db = schema.open();
|
final ReviewDb db = schema.open();
|
||||||
try {
|
try {
|
||||||
@ -94,7 +92,7 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
Collections.unmodifiableCollection(db.refRights().byProject(
|
Collections.unmodifiableCollection(db.refRights().byProject(
|
||||||
p.getNameKey()).toList());
|
p.getNameKey()).toList());
|
||||||
|
|
||||||
return projectStateFactory.create(p, rights, inheritedRights);
|
return projectStateFactory.create(p, rights);
|
||||||
} finally {
|
} finally {
|
||||||
db.close();
|
db.close();
|
||||||
}
|
}
|
||||||
@ -116,4 +114,9 @@ public class ProjectCacheImpl implements ProjectCache {
|
|||||||
byName.remove(p.getNameKey());
|
byName.remove(p.getNameKey());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Invalidate the cached information about all projects. */
|
||||||
|
public void evictAll() {
|
||||||
|
byName.removeAll();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,34 +34,31 @@ import java.util.Set;
|
|||||||
/** Cached information on a project. */
|
/** Cached information on a project. */
|
||||||
public class ProjectState {
|
public class ProjectState {
|
||||||
public interface Factory {
|
public interface Factory {
|
||||||
ProjectState create(Project project, Collection<RefRight> localRights,
|
ProjectState create(Project project, Collection<RefRight> localRights);
|
||||||
InheritedRights inheritedRights);
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface InheritedRights {
|
|
||||||
Collection<RefRight> get();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final AnonymousUser anonymousUser;
|
private final AnonymousUser anonymousUser;
|
||||||
private final Project.NameKey wildProject;
|
private final Project.NameKey wildProject;
|
||||||
|
private final ProjectCache projectCache;
|
||||||
|
|
||||||
private final Project project;
|
private final Project project;
|
||||||
private final Collection<RefRight> localRights;
|
private final Collection<RefRight> localRights;
|
||||||
private final InheritedRights inheritedRights;
|
|
||||||
private final Set<AccountGroup.Id> owners;
|
private final Set<AccountGroup.Id> owners;
|
||||||
|
|
||||||
|
private volatile Collection<RefRight> inheritedRights;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
protected ProjectState(final AnonymousUser anonymousUser,
|
protected ProjectState(final AnonymousUser anonymousUser,
|
||||||
|
final ProjectCache projectCache,
|
||||||
@WildProjectName final Project.NameKey wildProject,
|
@WildProjectName final Project.NameKey wildProject,
|
||||||
@Assisted final Project project,
|
@Assisted final Project project,
|
||||||
@Assisted final Collection<RefRight> rights,
|
@Assisted final Collection<RefRight> rights) {
|
||||||
@Assisted final InheritedRights inheritedRights) {
|
|
||||||
this.anonymousUser = anonymousUser;
|
this.anonymousUser = anonymousUser;
|
||||||
|
this.projectCache = projectCache;
|
||||||
this.wildProject = wildProject;
|
this.wildProject = wildProject;
|
||||||
|
|
||||||
this.project = project;
|
this.project = project;
|
||||||
this.localRights = rights;
|
this.localRights = rights;
|
||||||
this.inheritedRights = inheritedRights;
|
|
||||||
|
|
||||||
final HashSet<AccountGroup.Id> groups = new HashSet<AccountGroup.Id>();
|
final HashSet<AccountGroup.Id> groups = new HashSet<AccountGroup.Id>();
|
||||||
for (final RefRight right : rights) {
|
for (final RefRight right : rights) {
|
||||||
@ -94,10 +91,42 @@ public class ProjectState {
|
|||||||
|
|
||||||
/** Get the rights this project inherits from the wild project. */
|
/** Get the rights this project inherits from the wild project. */
|
||||||
public Collection<RefRight> getInheritedRights() {
|
public Collection<RefRight> getInheritedRights() {
|
||||||
|
if (inheritedRights == null) {
|
||||||
|
inheritedRights = computeInheritedRights();
|
||||||
|
}
|
||||||
|
return inheritedRights;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<RefRight> computeInheritedRights() {
|
||||||
if (isSpecialWildProject()) {
|
if (isSpecialWildProject()) {
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
}
|
}
|
||||||
return inheritedRights.get();
|
|
||||||
|
List<RefRight> inherited = new ArrayList<RefRight>();
|
||||||
|
Set<Project.NameKey> seen = new HashSet<Project.NameKey>();
|
||||||
|
Project.NameKey parent = project.getParent();
|
||||||
|
|
||||||
|
while (parent != null && seen.add(parent)) {
|
||||||
|
ProjectState s = projectCache.get(parent);
|
||||||
|
if (s != null) {
|
||||||
|
inherited.addAll(s.getLocalRights());
|
||||||
|
parent = s.getProject().getParent();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wild project is the parent, or the root of the tree
|
||||||
|
if (parent == null) {
|
||||||
|
inherited.addAll(getWildProjectRights());
|
||||||
|
}
|
||||||
|
|
||||||
|
return Collections.unmodifiableCollection(inherited);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<RefRight> getWildProjectRights() {
|
||||||
|
final ProjectState s = projectCache.get(wildProject);
|
||||||
|
return s != null ? s.getLocalRights() : Collections.<RefRight> emptyList();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
/** A version of the database schema. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** The current schema version. */
|
||||||
private static final Class<? extends SchemaVersion> C = Schema_31.class;
|
private static final Class<? extends SchemaVersion> C = Schema_32.class;
|
||||||
|
|
||||||
public static class Module extends AbstractModule {
|
public static class Module extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
// Copyright (C) 2010 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.schema;
|
||||||
|
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
|
||||||
|
public class Schema_32 extends SchemaVersion {
|
||||||
|
@Inject
|
||||||
|
Schema_32(Provider<Schema_31> prior) {
|
||||||
|
super(prior);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
// Copyright (C) 2010 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License
|
||||||
|
|
||||||
|
package com.google.gerrit.sshd.commands;
|
||||||
|
|
||||||
|
import com.google.gerrit.reviewdb.Project;
|
||||||
|
import com.google.gerrit.reviewdb.ReviewDb;
|
||||||
|
import com.google.gerrit.server.config.WildProjectName;
|
||||||
|
import com.google.gerrit.server.project.ProjectCache;
|
||||||
|
import com.google.gerrit.server.project.ProjectControl;
|
||||||
|
import com.google.gerrit.server.project.ProjectState;
|
||||||
|
import com.google.gerrit.sshd.AdminCommand;
|
||||||
|
import com.google.gerrit.sshd.BaseCommand;
|
||||||
|
import com.google.gwtorm.client.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
|
||||||
|
import org.apache.sshd.server.Environment;
|
||||||
|
import org.kohsuke.args4j.Argument;
|
||||||
|
import org.kohsuke.args4j.Option;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@AdminCommand
|
||||||
|
final class AdminSetParent extends BaseCommand {
|
||||||
|
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "new parent project")
|
||||||
|
private ProjectControl newParent;
|
||||||
|
|
||||||
|
@Argument(index = 0, required = true, multiValued = true, metaVar = "NAME", usage = "projects to modify")
|
||||||
|
private List<ProjectControl> children = new ArrayList<ProjectControl>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ReviewDb db;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
private ProjectCache projectCache;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
@WildProjectName
|
||||||
|
private Project.NameKey wildProject;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void start(final Environment env) {
|
||||||
|
startThread(new CommandRunnable() {
|
||||||
|
@Override
|
||||||
|
public void run() throws Exception {
|
||||||
|
parseCommandLine();
|
||||||
|
updateParents();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateParents() throws OrmException, UnloggedFailure {
|
||||||
|
final StringBuilder err = new StringBuilder();
|
||||||
|
final Set<Project.NameKey> grandParents = new HashSet<Project.NameKey>();
|
||||||
|
Project.NameKey newParentKey;
|
||||||
|
|
||||||
|
grandParents.add(wildProject);
|
||||||
|
|
||||||
|
if (newParent != null) {
|
||||||
|
newParentKey = newParent.getProject().getNameKey();
|
||||||
|
|
||||||
|
// Catalog all grandparents of the "parent", we want to
|
||||||
|
// catch a cycle in the parent pointers before it occurs.
|
||||||
|
//
|
||||||
|
Project.NameKey gp = newParent.getProject().getParent();
|
||||||
|
while (gp != null && grandParents.add(gp)) {
|
||||||
|
final ProjectState s = projectCache.get(gp);
|
||||||
|
if (s != null) {
|
||||||
|
gp = s.getProject().getParent();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// If no parent was selected, set to NULL to use the default.
|
||||||
|
//
|
||||||
|
newParentKey = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (final ProjectControl pc : children) {
|
||||||
|
final Project.NameKey key = pc.getProject().getNameKey();
|
||||||
|
final String name = pc.getProject().getName();
|
||||||
|
|
||||||
|
if (wildProject.equals(key)) {
|
||||||
|
// Don't allow the wild card project to have a parent.
|
||||||
|
//
|
||||||
|
err.append("error: Cannot set parent of '" + name + "'\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (grandParents.contains(key)) {
|
||||||
|
// Try to avoid creating a cycle in the parent pointers.
|
||||||
|
//
|
||||||
|
err.append("error: Cycle exists between '" + name + "' and '"
|
||||||
|
+ (newParentKey != null ? newParentKey.get() : wildProject.get())
|
||||||
|
+ "'\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final Project child = db.projects().get(key);
|
||||||
|
if (child == null) {
|
||||||
|
// Race condition? Its in the cache, but not the database.
|
||||||
|
//
|
||||||
|
err.append("error: Project '" + name + "' not found\n");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
child.setParent(newParentKey);
|
||||||
|
db.projects().update(Collections.singleton(child));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalidate all projects in cache since inherited rights were changed.
|
||||||
|
//
|
||||||
|
projectCache.evictAll();
|
||||||
|
|
||||||
|
if (err.length() > 0) {
|
||||||
|
while (err.charAt(err.length() - 1) == '\n') {
|
||||||
|
err.setLength(err.length() - 1);
|
||||||
|
}
|
||||||
|
throw new UnloggedFailure(1, err.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,5 +31,6 @@ public class MasterCommandModule extends CommandModule {
|
|||||||
command(gerrit, "gsql").to(AdminQueryShell.class);
|
command(gerrit, "gsql").to(AdminQueryShell.class);
|
||||||
command(gerrit, "receive-pack").to(Receive.class);
|
command(gerrit, "receive-pack").to(Receive.class);
|
||||||
command(gerrit, "replicate").to(AdminReplicate.class);
|
command(gerrit, "replicate").to(AdminReplicate.class);
|
||||||
|
command(gerrit, "set-project-parent").to(AdminSetParent.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,5 +31,6 @@ public class SlaveCommandModule extends CommandModule {
|
|||||||
command(gerrit, "gsql").to(ErrorSlaveMode.class);
|
command(gerrit, "gsql").to(ErrorSlaveMode.class);
|
||||||
command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
|
command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
|
||||||
command(gerrit, "replicate").to(ErrorSlaveMode.class);
|
command(gerrit, "replicate").to(ErrorSlaveMode.class);
|
||||||
|
command(gerrit, "set-project-parent").to(ErrorSlaveMode.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user