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
|
||||
----------------------------
|
||||
|
||||
A system wide access control list affecting all projects is stored
|
||||
in project id 0 (named "`\-- All Projects \--`" in a default
|
||||
installation). Only the id is recognized as special by Gerrit.
|
||||
A system wide access control list affecting all projects is stored in
|
||||
project "`\-- All Projects \--`". This inheritance can be configured
|
||||
through link:cmd-set-project-parent.html[gerrit set-project-parent].
|
||||
|
||||
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]::
|
||||
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]::
|
||||
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.Project;
|
||||
import com.google.gerrit.reviewdb.RefRight;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -24,7 +23,7 @@ import java.util.Map;
|
||||
public class ProjectDetail {
|
||||
public Project project;
|
||||
public Map<AccountGroup.Id, AccountGroup> groups;
|
||||
public List<RefRight> rights;
|
||||
public List<InheritedRefRight> rights;
|
||||
|
||||
public ProjectDetail() {
|
||||
}
|
||||
@ -37,7 +36,7 @@ public class ProjectDetail {
|
||||
groups = g;
|
||||
}
|
||||
|
||||
public void setRights(final List<RefRight> r) {
|
||||
public void setRights(final List<InheritedRefRight> r) {
|
||||
rights = r;
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ public interface AdminConstants extends Constants {
|
||||
String useSignedOffBy();
|
||||
|
||||
String headingOwner();
|
||||
String headingParentProjectName();
|
||||
String headingDescription();
|
||||
String headingSubmitType();
|
||||
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
|
||||
|
||||
headingOwner = Owners
|
||||
headingParentProjectName = Rights Inherit From
|
||||
headingDescription = Description
|
||||
headingSubmitType = Change Submit Action
|
||||
headingGroupType = Group Type
|
||||
|
@ -14,13 +14,16 @@
|
||||
|
||||
package com.google.gerrit.client.admin;
|
||||
|
||||
import com.google.gerrit.client.Dispatcher;
|
||||
import com.google.gerrit.client.Gerrit;
|
||||
import com.google.gerrit.client.rpc.GerritCallback;
|
||||
import com.google.gerrit.client.ui.AccountGroupSuggestOracle;
|
||||
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.common.data.ApprovalType;
|
||||
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.reviewdb.AccountGroup;
|
||||
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.Panel;
|
||||
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.gwtexpui.globalkey.client.NpTextBox;
|
||||
import com.google.gwtexpui.safehtml.client.SafeHtml;
|
||||
@ -59,6 +63,9 @@ import java.util.Map;
|
||||
public class ProjectRightsPanel extends Composite {
|
||||
private Project.NameKey projectName;
|
||||
|
||||
private Panel parentPanel;
|
||||
private Hyperlink parentName;
|
||||
|
||||
private RightsTable rights;
|
||||
private Button delRight;
|
||||
private Button addRight;
|
||||
@ -73,6 +80,7 @@ public class ProjectRightsPanel extends Composite {
|
||||
projectName = toShow;
|
||||
|
||||
final FlowPanel body = new FlowPanel();
|
||||
initParent(body);
|
||||
initRights(body);
|
||||
initWidget(body);
|
||||
}
|
||||
@ -103,6 +111,15 @@ public class ProjectRightsPanel extends Composite {
|
||||
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) {
|
||||
final FlowPanel addPanel = new FlowPanel();
|
||||
addPanel.setStyleName(Gerrit.RESOURCES.css().addSshKeyPanel());
|
||||
@ -230,6 +247,20 @@ public class ProjectRightsPanel extends Composite {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@ -400,11 +431,11 @@ public class ProjectRightsPanel extends Composite {
|
||||
}
|
||||
|
||||
void display(final Map<AccountGroup.Id, AccountGroup> groups,
|
||||
final List<RefRight> refRights) {
|
||||
final List<InheritedRefRight> refRights) {
|
||||
while (1 < table.getRowCount())
|
||||
table.removeRow(table.getRowCount() - 1);
|
||||
|
||||
for (final RefRight r : refRights) {
|
||||
for (final InheritedRefRight r : refRights) {
|
||||
final int row = table.getRowCount();
|
||||
table.insertRow(row);
|
||||
applyDataRowStyle(row);
|
||||
@ -413,14 +444,16 @@ public class ProjectRightsPanel extends Composite {
|
||||
}
|
||||
|
||||
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 RefRight right = r.getRight();
|
||||
final ApprovalType ar =
|
||||
config.getApprovalTypes().getApprovalType(r.getApprovalCategoryId());
|
||||
final AccountGroup group = groups.get(r.getAccountGroupId());
|
||||
config.getApprovalTypes().getApprovalType(
|
||||
right.getApprovalCategoryId());
|
||||
final AccountGroup group = groups.get(right.getAccountGroupId());
|
||||
|
||||
if (!projectName.equals(Gerrit.getConfig().getWildProject())
|
||||
&& Gerrit.getConfig().getWildProject().equals(r.getProjectNameKey())) {
|
||||
if (r.isInherited()) {
|
||||
table.setText(row, 1, "");
|
||||
} else {
|
||||
table.setWidget(row, 1, new CheckBox());
|
||||
@ -429,27 +462,28 @@ public class ProjectRightsPanel extends Composite {
|
||||
if (ar != null) {
|
||||
table.setText(row, 2, ar.getCategory().getName());
|
||||
} else {
|
||||
table.setText(row, 2, r.getApprovalCategoryId().get());
|
||||
table.setText(row, 2, right.getApprovalCategoryId().get());
|
||||
}
|
||||
|
||||
if (group != null) {
|
||||
table.setText(row, 3, group.getName());
|
||||
} 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 ApprovalCategoryValue min, max;
|
||||
min = ar != null ? ar.getValue(r.getMinValue()) : null;
|
||||
max = ar != null ? ar.getValue(r.getMaxValue()) : null;
|
||||
min = ar != null ? ar.getValue(right.getMinValue()) : null;
|
||||
max = ar != null ? ar.getValue(right.getMaxValue()) : null;
|
||||
|
||||
formatValue(m, r.getMinValue(), min);
|
||||
if (r.getMinValue() != r.getMaxValue()) {
|
||||
formatValue(m, right.getMinValue(), min);
|
||||
if (right.getMinValue() != right.getMaxValue()) {
|
||||
m.br();
|
||||
formatValue(m, r.getMaxValue(), max);
|
||||
formatValue(m, right.getMaxValue(), max);
|
||||
}
|
||||
SafeHtml.set(table, row, 5, m);
|
||||
}
|
||||
@ -463,7 +497,7 @@ public class ProjectRightsPanel extends Composite {
|
||||
fmt.addStyleName(row, 5, Gerrit.RESOURCES.css()
|
||||
.projectAdminApprovalCategoryRangeLine());
|
||||
|
||||
setRowItem(row, r);
|
||||
setRowItem(row, right);
|
||||
}
|
||||
|
||||
private void formatValue(final SafeHtmlBuilder m, final short v,
|
||||
|
@ -174,7 +174,7 @@ class AddRefRight extends Handler<ProjectDetail> {
|
||||
rr.setMaxValue(max);
|
||||
db.refRights().update(Collections.singleton(rr));
|
||||
}
|
||||
projectCache.evict(projectControl.getProject());
|
||||
projectCache.evictAll();
|
||||
return projectDetailFactory.create(projectName).call();
|
||||
}
|
||||
|
||||
|
@ -79,7 +79,7 @@ class DeleteRefRights extends Handler<VoidResult> {
|
||||
db.refRights().delete(Collections.singleton(m));
|
||||
}
|
||||
}
|
||||
projectCache.evict(projectControl.getProject());
|
||||
projectCache.evictAll();
|
||||
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.ApprovalTypes;
|
||||
import com.google.gerrit.common.data.InheritedRefRight;
|
||||
import com.google.gerrit.common.data.ProjectDetail;
|
||||
import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
@ -71,26 +72,34 @@ class ProjectDetailFactory extends Handler<ProjectDetail> {
|
||||
detail.setProject(projectState.getProject());
|
||||
|
||||
groups = new HashMap<AccountGroup.Id, AccountGroup>();
|
||||
final List<RefRight> refRights = new ArrayList<RefRight>();
|
||||
for (final RefRight r : projectState.getLocalRights()) {
|
||||
refRights.add(r);
|
||||
wantGroup(r.getAccountGroupId());
|
||||
}
|
||||
final List<InheritedRefRight> refRights = new ArrayList<InheritedRefRight>();
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
loadGroups();
|
||||
|
||||
Collections.sort(refRights, new Comparator<RefRight>() {
|
||||
Collections.sort(refRights, new Comparator<InheritedRefRight>() {
|
||||
@Override
|
||||
public int compare(final RefRight a, final RefRight b) {
|
||||
int rc = categoryOf(a).compareTo(categoryOf(b));
|
||||
public int compare(final InheritedRefRight a, final InheritedRefRight b) {
|
||||
final RefRight right1 = a.getRight();
|
||||
final RefRight right2 = b.getRight();
|
||||
int rc = categoryOf(right1).compareTo(categoryOf(right2));
|
||||
if (rc == 0) {
|
||||
rc = a.getRefPattern().compareTo(b.getRefPattern());
|
||||
rc = right1.getRefPattern().compareTo(right2.getRefPattern());
|
||||
}
|
||||
if (rc == 0) {
|
||||
rc = groupOf(a).compareTo(groupOf(b));
|
||||
rc = groupOf(right1).compareTo(groupOf(right2));
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
@ -96,6 +96,9 @@ public final class Project {
|
||||
@Column(id = 5)
|
||||
protected char submitType;
|
||||
|
||||
@Column(id = 6, notNull = false, name = "parent_name")
|
||||
protected NameKey parent;
|
||||
|
||||
protected Project() {
|
||||
}
|
||||
|
||||
@ -151,4 +154,12 @@ public final class Project {
|
||||
useSignedOffBy = update.useSignedOffBy;
|
||||
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;
|
||||
}
|
||||
|
||||
@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> {
|
||||
|
||||
@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) {
|
||||
self.put(new Element(key, value));
|
||||
}
|
||||
|
@ -28,4 +28,7 @@ public interface ProjectCache {
|
||||
|
||||
/** Invalidate the cached information about the given project. */
|
||||
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.name.Named;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
@ -42,7 +43,7 @@ public class ProjectCacheImpl implements ProjectCache {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<Project.NameKey, ProjectState>> type =
|
||||
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
|
||||
new TypeLiteral<Cache<Project.NameKey, ProjectState>>() {};
|
||||
core(type, CACHE_NAME);
|
||||
bind(ProjectCacheImpl.class);
|
||||
bind(ProjectCache.class).to(ProjectCacheImpl.class);
|
||||
@ -51,8 +52,6 @@ public class ProjectCacheImpl implements ProjectCache {
|
||||
}
|
||||
|
||||
private final ProjectState.Factory projectStateFactory;
|
||||
private final Project.NameKey wildProject;
|
||||
private final ProjectState.InheritedRights inheritedRights;
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
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) {
|
||||
projectStateFactory = psf;
|
||||
schema = sf;
|
||||
wildProject = wp;
|
||||
|
||||
this.byName =
|
||||
new SelfPopulatingCache<Project.NameKey, ProjectState>(byName) {
|
||||
@ -73,15 +71,15 @@ public class ProjectCacheImpl implements ProjectCache {
|
||||
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 {
|
||||
final ReviewDb db = schema.open();
|
||||
try {
|
||||
@ -94,7 +92,7 @@ public class ProjectCacheImpl implements ProjectCache {
|
||||
Collections.unmodifiableCollection(db.refRights().byProject(
|
||||
p.getNameKey()).toList());
|
||||
|
||||
return projectStateFactory.create(p, rights, inheritedRights);
|
||||
return projectStateFactory.create(p, rights);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
@ -116,4 +114,9 @@ public class ProjectCacheImpl implements ProjectCache {
|
||||
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. */
|
||||
public class ProjectState {
|
||||
public interface Factory {
|
||||
ProjectState create(Project project, Collection<RefRight> localRights,
|
||||
InheritedRights inheritedRights);
|
||||
}
|
||||
|
||||
public interface InheritedRights {
|
||||
Collection<RefRight> get();
|
||||
ProjectState create(Project project, Collection<RefRight> localRights);
|
||||
}
|
||||
|
||||
private final AnonymousUser anonymousUser;
|
||||
private final Project.NameKey wildProject;
|
||||
private final ProjectCache projectCache;
|
||||
|
||||
private final Project project;
|
||||
private final Collection<RefRight> localRights;
|
||||
private final InheritedRights inheritedRights;
|
||||
private final Set<AccountGroup.Id> owners;
|
||||
|
||||
private volatile Collection<RefRight> inheritedRights;
|
||||
|
||||
@Inject
|
||||
protected ProjectState(final AnonymousUser anonymousUser,
|
||||
final ProjectCache projectCache,
|
||||
@WildProjectName final Project.NameKey wildProject,
|
||||
@Assisted final Project project,
|
||||
@Assisted final Collection<RefRight> rights,
|
||||
@Assisted final InheritedRights inheritedRights) {
|
||||
@Assisted final Collection<RefRight> rights) {
|
||||
this.anonymousUser = anonymousUser;
|
||||
this.projectCache = projectCache;
|
||||
this.wildProject = wildProject;
|
||||
|
||||
this.project = project;
|
||||
this.localRights = rights;
|
||||
this.inheritedRights = inheritedRights;
|
||||
|
||||
final HashSet<AccountGroup.Id> groups = new HashSet<AccountGroup.Id>();
|
||||
for (final RefRight right : rights) {
|
||||
@ -94,10 +91,42 @@ public class ProjectState {
|
||||
|
||||
/** Get the rights this project inherits from the wild project. */
|
||||
public Collection<RefRight> getInheritedRights() {
|
||||
if (inheritedRights == null) {
|
||||
inheritedRights = computeInheritedRights();
|
||||
}
|
||||
return inheritedRights;
|
||||
}
|
||||
|
||||
private Collection<RefRight> computeInheritedRights() {
|
||||
if (isSpecialWildProject()) {
|
||||
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. */
|
||||
public abstract class SchemaVersion {
|
||||
/** 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 {
|
||||
@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, "receive-pack").to(Receive.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, "receive-pack").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