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:
lincoln 2010-04-22 14:23:05 -03:00 committed by Shawn O. Pearce
parent 072b470570
commit fa7bdd39ed
22 changed files with 457 additions and 59 deletions

View File

@ -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.

View File

@ -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.

View 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]

View File

@ -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();
}
}

View File

@ -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;
} }
} }

View File

@ -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();

View File

@ -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

View File

@ -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,

View File

@ -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();
} }

View File

@ -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;
} }

View File

@ -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;
} }

View File

@ -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;
}
} }

View File

@ -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

View File

@ -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));
} }

View File

@ -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();
} }

View File

@ -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();
}
} }

View File

@ -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();
} }
/** /**

View File

@ -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

View File

@ -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);
}
}

View File

@ -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());
}
}
}

View File

@ -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);
} }
} }

View File

@ -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);
} }
} }