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

View File

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

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

View File

@ -36,6 +36,7 @@ public interface AdminConstants extends Constants {
String useSignedOffBy();
String headingOwner();
String headingParentProjectName();
String headingDescription();
String headingSubmitType();
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
headingOwner = Owners
headingParentProjectName = Rights Inherit From
headingDescription = Description
headingSubmitType = Change Submit Action
headingGroupType = Group Type

View File

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

View File

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

View File

@ -79,7 +79,7 @@ class DeleteRefRights extends Handler<VoidResult> {
db.refRights().delete(Collections.singleton(m));
}
}
projectCache.evict(projectControl.getProject());
projectCache.evictAll();
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.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;
}

View File

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

View File

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

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) {
self.put(new Element(key, value));
}

View File

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

View File

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

View File

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

View File

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

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, "receive-pack").to(Receive.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, "receive-pack").to(ErrorSlaveMode.class);
command(gerrit, "replicate").to(ErrorSlaveMode.class);
command(gerrit, "set-project-parent").to(ErrorSlaveMode.class);
}
}