Allow to propose changes to access rights through code review

Users that are able to upload changes for code review for the
refs/meta/config branch can now propose changes to the project access
rights through code review directly from the ProjectAccessScreen.

When editing the project access rights there will be a new button
'Save for Review' which will create a new change for the access
rights modifications. Project owners are automatically added as
reviewer to this change. If a project owner agrees to the access rights
modifications he can simply approve and submit the change.

Change-Id: Ica40ce3f57726bdb897e01783cc6da10f1d392cc
Signed-off-by: Edwin Kempin <edwin.kempin@sap.com>
Signed-off-by: Sasa Zivkov <sasa.zivkov@sap.com>
Signed-off-by: Gustaf Lundh <gustaf.lundh@sonymobile.com>
Signed-off-by: Ulrik Sjölin <ulrik.sjolin@sonyericsson.com>
This commit is contained in:
Edwin Kempin
2012-05-09 02:07:28 +02:00
parent 895d18b7f2
commit 5aa9a2c98d
26 changed files with 492 additions and 199 deletions

View File

@@ -26,6 +26,7 @@ public class ProjectAccess {
protected List<AccessSection> local;
protected Set<String> ownerOf;
protected boolean isConfigVisible;
protected boolean canUpload;
public ProjectAccess() {
}
@@ -94,4 +95,12 @@ public class ProjectAccess {
public void setConfigVisible(boolean isConfigVisible) {
this.isConfigVisible = isConfigVisible;
}
public boolean canUpload() {
return canUpload;
}
public void setCanUpload(boolean canUpload) {
this.canUpload = canUpload;
}
}

View File

@@ -16,6 +16,7 @@ package com.google.gerrit.common.data;
import com.google.gerrit.common.auth.SignInRequired;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.RemoteJsonService;
@@ -50,6 +51,11 @@ public interface ProjectAdminService extends RemoteJsonService {
String message, List<AccessSection> sections,
AsyncCallback<ProjectAccess> callback);
@SignInRequired
void reviewProjectAccess(Project.NameKey projectName, String baseRevision,
String message, List<AccessSection> sections,
AsyncCallback<Change.Id> callback);
void listBranches(Project.NameKey projectName,
AsyncCallback<ListBranchesResult> callback);

View File

@@ -101,4 +101,5 @@ public interface GerritConstants extends Constants {
String jumpMineDraftComments();
String projectAccessError();
String projectAccessProposeForReviewHint();
}

View File

@@ -84,3 +84,4 @@ jumpMineStarred = Go to starred changes
jumpMineDraftComments = Go to draft comments
projectAccessError = You don't have permissions to modify the access rights for the following refs:
projectAccessProposeForReviewHint = You may propose these modifications to the project owners by clicking on 'Save for Review'.

View File

@@ -182,7 +182,7 @@ public class AccessSectionEditor extends Composite implements
Collections.sort(value.getPermissions());
this.value = value;
this.readOnly = !editing || !projectAccess.isOwnerOf(value);
this.readOnly = !editing || !(projectAccess.isOwnerOf(value) || projectAccess.canUpload());
name.setEnabled(!readOnly);
deleteSection.setVisible(!readOnly);

View File

@@ -120,7 +120,7 @@ public class ProjectAccessEditor extends Composite implements
history.getStyle().setDisplay(Display.NONE);
}
addSection.setVisible(value != null && editing && !value.getOwnerOf().isEmpty());
addSection.setVisible(value != null && editing && (!value.getOwnerOf().isEmpty() || value.canUpload()));
}
@Override

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.client.rpc.ScreenLoadCallback;
import com.google.gerrit.common.PageLinks;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
@@ -78,6 +79,9 @@ public class ProjectAccessScreen extends ProjectScreen {
@UiField
Button commit;
@UiField
Button review;
private Driver driver;
private ProjectAccess access;
@@ -111,8 +115,8 @@ public class ProjectAccessScreen extends ProjectScreen {
private void displayReadOnly(ProjectAccess access) {
this.access = access;
accessEditor.setEditing(false);
UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty());
edit.setEnabled(!access.getOwnerOf().isEmpty());
UIObject.setVisible(editTools, !access.getOwnerOf().isEmpty() || access.canUpload());
edit.setEnabled(!access.getOwnerOf().isEmpty() || access.canUpload());
cancel1.setVisible(false);
UIObject.setVisible(commitTools, false);
driver.edit(access);
@@ -125,6 +129,8 @@ public class ProjectAccessScreen extends ProjectScreen {
edit.setEnabled(false);
cancel1.setVisible(true);
UIObject.setVisible(commitTools, true);
commit.setVisible(!access.getOwnerOf().isEmpty());
review.setVisible(access.canUpload());
accessEditor.setEditing(true);
driver.edit(access);
}
@@ -180,6 +186,9 @@ public class ProjectAccessScreen extends ProjectScreen {
for (final String diff : diffs) {
error.add(new Label(diff));
}
if (access.canUpload()) {
error.add(new Label(Gerrit.C.projectAccessProposeForReviewHint()));
}
}
}
@@ -211,9 +220,52 @@ public class ProjectAccessScreen extends ProjectScreen {
});
}
@UiHandler("review")
void onReview(ClickEvent event) {
final ProjectAccess access = driver.flush();
if (driver.hasErrors()) {
Window.alert(Util.C.errorsMustBeFixed());
return;
}
String message = commitMessage.getText().trim();
if ("".equals(message)) {
message = null;
}
enable(false);
Util.PROJECT_SVC.reviewProjectAccess( //
getProjectKey(), //
access.getRevision(), //
message, //
access.getLocal(), //
new GerritCallback<Change.Id>() {
@Override
public void onSuccess(Change.Id changeId) {
enable(true);
commitMessage.setText("");
error.clear();
if (changeId != null) {
Gerrit.display(PageLinks.toChange(changeId));
} else {
displayReadOnly(access);
}
}
@Override
public void onFailure(Throwable caught) {
error.clear();
enable(true);
super.onFailure(caught);
}
});
}
private void enable(boolean enabled) {
commitMessage.setEnabled(enabled);
commit.setEnabled(enabled);
commit.setEnabled(enabled ? !access.getOwnerOf().isEmpty() : false);
review.setEnabled(enabled ? access.canUpload() : false);
cancel1.setEnabled(enabled);
cancel2.setEnabled(enabled);
}

View File

@@ -72,6 +72,11 @@ limitations under the License.
text='Save Changes'>
<ui:attribute name='text'/>
</g:Button>
<g:Button
ui:field='review'
text='Save for Review'>
<ui:attribute name='text'/>
</g:Button>
<g:Button
ui:field='cancel2'
text='Cancel'>

View File

@@ -15,41 +15,26 @@
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.server.OrmConcurrencyException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.annotation.Nullable;
class ChangeProjectAccess extends Handler<ProjectAccess> {
class ChangeProjectAccess extends ProjectAccessHandler<ProjectAccess> {
interface Factory {
ChangeProjectAccess create(@Assisted Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@@ -58,15 +43,7 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
}
private final ProjectAccessFactory.Factory projectAccessFactory;
private final ProjectControl.Factory projectControlFactory;
private final ProjectCache projectCache;
private final GroupBackend groupBackend;
private final MetaDataUpdate.User metaDataUpdateFactory;
private final Project.NameKey projectName;
private final ObjectId base;
private List<AccessSection> sectionList;
private String message;
@Inject
ChangeProjectAccess(final ProjectAccessFactory.Factory projectAccessFactory,
@@ -78,132 +55,17 @@ class ChangeProjectAccess extends Handler<ProjectAccess> {
@Nullable @Assisted final ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message) {
super(projectControlFactory, groupBackend, metaDataUpdateFactory,
projectName, base, sectionList, message, true);
this.projectAccessFactory = projectAccessFactory;
this.projectControlFactory = projectControlFactory;
this.projectCache = projectCache;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectName = projectName;
this.base = base;
this.sectionList = sectionList;
this.message = message;
}
@Override
public ProjectAccess call() throws NoSuchProjectException, IOException,
ConfigInvalidException, InvalidNameException, NoSuchGroupException,
OrmConcurrencyException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
final MetaDataUpdate md;
try {
md = metaDataUpdateFactory.create(projectName);
} catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(projectName);
}
try {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
for (AccessSection section : mergeSections(sectionList)) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (!projectControl.isOwner()) {
continue;
}
replace(config, toDelete, section);
} else if (AccessSection.isValid(name)) {
if (!projectControl.controlForRef(name).isOwner()) {
continue;
}
RefControl.validateRefPattern(name);
replace(config, toDelete, section);
}
}
for (String name : toDelete) {
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (projectControl.isOwner()) {
config.remove(config.getAccessSection(name));
}
} else if (projectControl.controlForRef(name).isOwner()) {
config.remove(config.getAccessSection(name));
}
}
if (message != null && !message.isEmpty()) {
if (!message.endsWith("\n")) {
message += "\n";
}
md.setMessage(message);
} else {
md.setMessage("Modify access rules\n");
}
if (config.commit(md)) {
protected ProjectAccess updateProjectConfig(ProjectConfig config,
MetaDataUpdate md) throws IOException, NoSuchProjectException, ConfigInvalidException {
config.commit(md);
projectCache.evict(config.getProject());
return projectAccessFactory.create(projectName).call();
} else {
throw new OrmConcurrencyException("Cannot update " + projectName);
}
} finally {
md.close();
}
}
private void replace(ProjectConfig config, Set<String> toDelete,
AccessSection section) throws NoSuchGroupException {
for (Permission permission : section.getPermissions()) {
for (PermissionRule rule : permission.getRules()) {
lookupGroup(rule);
}
}
config.replace(section);
toDelete.remove(section.getName());
}
private static List<AccessSection> mergeSections(List<AccessSection> src) {
Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
for (AccessSection section : src) {
if (section.getPermissions().isEmpty()) {
continue;
}
AccessSection prior = map.get(section.getName());
if (prior != null) {
prior.mergeFrom(section);
} else {
map.put(section.getName(), section);
}
}
return new ArrayList<AccessSection>(map.values());
}
private static Set<String> scanSectionNames(ProjectConfig config) {
Set<String> names = new HashSet<String>();
for (AccessSection section : config.getAccessSections()) {
names.add(section.getName());
}
return names;
}
private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
GroupReference ref = rule.getGroup();
if (ref.getUUID() == null) {
final GroupReference group =
GroupBackends.findBestSuggestion(groupBackend, ref.getName());
if (group == null) {
throw new NoSuchGroupException(ref.getName());
}
ref.setUUID(group.getUUID());
}
}
}

View File

@@ -86,10 +86,11 @@ class ChangeProjectSettings extends Handler<ProjectDetail> {
config.getProject().copySettingsFrom(update);
md.setMessage("Modified project settings\n");
if (config.commit(md)) {
try {
config.commit(md);
mgr.setProjectDescription(projectName, update.getDescription());
userCache.get().evict(config.getProject());
} else {
} catch (IOException e) {
throw new OrmConcurrencyException("Cannot update " + projectName);
}
} catch (ConfigInvalidException err) {

View File

@@ -96,10 +96,9 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
if (config.updateGroupNames(groupBackend)) {
md.setMessage("Update group names\n");
if (config.commit(md)) {
config.commit(md);
projectCache.evict(config.getProject());
pc = open();
}
} else if (config.getRevision() != null
&& !config.getRevision().equals(
pc.getProjectState().getConfig().getRevision())) {
@@ -196,6 +195,8 @@ class ProjectAccessFactory extends Handler<ProjectAccess> {
detail.setLocal(local);
detail.setOwnerOf(ownerOf);
detail.setCanUpload(pc.isOwner()
|| (metaConfigControl.isVisible() && metaConfigControl.canUpload()));
detail.setConfigVisible(pc.isOwner() || metaConfigControl.isVisible());
return detail;
}

View File

@@ -0,0 +1,190 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.GroupReference;
import com.google.gerrit.common.data.Permission;
import com.google.gerrit.common.data.PermissionRule;
import com.google.gerrit.common.errors.InvalidNameException;
import com.google.gerrit.common.errors.NoSuchGroupException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.account.GroupBackends;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gwtorm.server.OrmException;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.RepositoryNotFoundException;
import org.eclipse.jgit.lib.ObjectId;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
public abstract class ProjectAccessHandler<T> extends Handler<T> {
private final ProjectControl.Factory projectControlFactory;
protected final GroupBackend groupBackend;
private final MetaDataUpdate.User metaDataUpdateFactory;
protected final Project.NameKey projectName;
protected final ObjectId base;
private List<AccessSection> sectionList;
protected String message;
private boolean checkIfOwner;
protected ProjectAccessHandler(
final ProjectControl.Factory projectControlFactory,
final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory,
final Project.NameKey projectName, final ObjectId base,
final List<AccessSection> sectionList, final String message,
final boolean checkIfOwner) {
this.projectControlFactory = projectControlFactory;
this.groupBackend = groupBackend;
this.metaDataUpdateFactory = metaDataUpdateFactory;
this.projectName = projectName;
this.base = base;
this.sectionList = sectionList;
this.message = message;
this.checkIfOwner = checkIfOwner;
}
@Override
public final T call() throws NoSuchProjectException, IOException,
ConfigInvalidException, InvalidNameException, NoSuchGroupException,
OrmException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
final MetaDataUpdate md;
try {
md = metaDataUpdateFactory.create(projectName);
} catch (RepositoryNotFoundException notFound) {
throw new NoSuchProjectException(projectName);
}
try {
ProjectConfig config = ProjectConfig.read(md, base);
Set<String> toDelete = scanSectionNames(config);
for (AccessSection section : mergeSections(sectionList)) {
String name = section.getName();
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (checkIfOwner && !projectControl.isOwner()) {
continue;
}
replace(config, toDelete, section);
} else if (AccessSection.isValid(name)) {
if (checkIfOwner && !projectControl.controlForRef(name).isOwner()) {
continue;
}
RefControl.validateRefPattern(name);
replace(config, toDelete, section);
}
}
for (String name : toDelete) {
if (AccessSection.GLOBAL_CAPABILITIES.equals(name)) {
if (!checkIfOwner || projectControl.isOwner()) {
config.remove(config.getAccessSection(name));
}
} else if (!checkIfOwner || projectControl.controlForRef(name).isOwner()) {
config.remove(config.getAccessSection(name));
}
}
if (message != null && !message.isEmpty()) {
if (!message.endsWith("\n")) {
message += "\n";
}
md.setMessage(message);
} else {
md.setMessage("Modify access rules\n");
}
return updateProjectConfig(config, md);
} finally {
md.close();
}
}
protected abstract T updateProjectConfig(ProjectConfig config,
MetaDataUpdate md) throws IOException, NoSuchProjectException,
ConfigInvalidException, OrmException;
private void replace(ProjectConfig config, Set<String> toDelete,
AccessSection section) throws NoSuchGroupException {
for (Permission permission : section.getPermissions()) {
for (PermissionRule rule : permission.getRules()) {
lookupGroup(rule);
}
}
config.replace(section);
toDelete.remove(section.getName());
}
private static List<AccessSection> mergeSections(List<AccessSection> src) {
Map<String, AccessSection> map = new LinkedHashMap<String, AccessSection>();
for (AccessSection section : src) {
if (section.getPermissions().isEmpty()) {
continue;
}
AccessSection prior = map.get(section.getName());
if (prior != null) {
prior.mergeFrom(section);
} else {
map.put(section.getName(), section);
}
}
return new ArrayList<AccessSection>(map.values());
}
private static Set<String> scanSectionNames(ProjectConfig config) {
Set<String> names = new HashSet<String>();
for (AccessSection section : config.getAccessSections()) {
names.add(section.getName());
}
return names;
}
private void lookupGroup(PermissionRule rule) throws NoSuchGroupException {
GroupReference ref = rule.getGroup();
if (ref.getUUID() == null) {
final GroupReference group =
GroupBackends.findBestSuggestion(groupBackend, ref.getName());
if (group == null) {
throw new NoSuchGroupException(ref.getName());
}
ref.setUUID(group.getUUID());
}
}
}

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.ProjectAdminService;
import com.google.gerrit.common.data.ProjectDetail;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gwtjsonrpc.common.AsyncCallback;
import com.google.gwtjsonrpc.common.VoidResult;
@@ -33,6 +34,7 @@ import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final ChangeProjectAccess.Factory changeProjectAccessFactory;
private final ReviewProjectAccess.Factory reviewProjectAccessFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
private final DeleteBranches.Factory deleteBranchesFactory;
private final ListBranches.Factory listBranchesFactory;
@@ -44,6 +46,7 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final ChangeProjectAccess.Factory changeProjectAccessFactory,
final ReviewProjectAccess.Factory reviewProjectAccessFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
final ListBranches.Factory listBranchesFactory,
@@ -53,6 +56,7 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
final CreateProjectHandler.Factory createNewProjectFactory) {
this.addBranchFactory = addBranchFactory;
this.changeProjectAccessFactory = changeProjectAccessFactory;
this.reviewProjectAccessFactory = reviewProjectAccessFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
this.deleteBranchesFactory = deleteBranchesFactory;
this.listBranchesFactory = listBranchesFactory;
@@ -98,6 +102,14 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
changeProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
}
@Override
public void reviewProjectAccess(Project.NameKey projectName,
String baseRevision, String msg, List<AccessSection> sections,
AsyncCallback<Change.Id> cb) {
ObjectId base = ObjectId.fromString(baseRevision);
reviewProjectAccessFactory.create(projectName, base, sections, msg).to(cb);
}
@Override
public void listBranches(final Project.NameKey projectName,
final AsyncCallback<ListBranchesResult> callback) {

View File

@@ -30,6 +30,7 @@ public class ProjectModule extends RpcServletModule {
protected void configure() {
factory(AddBranch.Factory.class);
factory(ChangeProjectAccess.Factory.class);
factory(ReviewProjectAccess.Factory.class);
factory(CreateProjectHandler.Factory.class);
factory(ChangeProjectSettings.Factory.class);
factory(DeleteBranches.Factory.class);

View File

@@ -0,0 +1,127 @@
// Copyright (C) 2012 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.reviewdb.client.AccountGroup;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.PatchSetInfo;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.account.GroupBackend;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.git.ProjectConfig;
import com.google.gerrit.server.patch.AddReviewer;
import com.google.gerrit.server.patch.PatchSetInfoFactory;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.revwalk.RevCommit;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
public class ReviewProjectAccess extends ProjectAccessHandler<Change.Id> {
interface Factory {
ReviewProjectAccess create(@Assisted Project.NameKey projectName,
@Nullable @Assisted ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message);
}
private final ReviewDb db;
private final IdentifiedUser user;
private final PatchSetInfoFactory patchSetInfoFactory;
private final AddReviewer.Factory addReviewerFactory;
@Inject
ReviewProjectAccess(final ProjectControl.Factory projectControlFactory,
final GroupBackend groupBackend,
final MetaDataUpdate.User metaDataUpdateFactory, final ReviewDb db,
final IdentifiedUser user, final PatchSetInfoFactory patchSetInfoFactory,
final AddReviewer.Factory addReviewerFactory,
@Assisted final Project.NameKey projectName,
@Nullable @Assisted final ObjectId base,
@Assisted List<AccessSection> sectionList,
@Nullable @Assisted String message) {
super(projectControlFactory, groupBackend, metaDataUpdateFactory,
projectName, base, sectionList, message, false);
this.db = db;
this.user = user;
this.patchSetInfoFactory = patchSetInfoFactory;
this.addReviewerFactory = addReviewerFactory;
}
@Override
protected Change.Id updateProjectConfig(ProjectConfig config, MetaDataUpdate md)
throws IOException, NoSuchProjectException, ConfigInvalidException, OrmException {
int nextChangeId = db.nextChangeId();
PatchSet.Id patchSetId = new PatchSet.Id(new Change.Id(nextChangeId), 1);
final PatchSet ps = new PatchSet(patchSetId);
RevCommit commit = config.commitToNewRef(md, ps.getRefName());
if (commit.getId().equals(base)) {
return null;
}
Change.Key changeKey = new Change.Key("I" + commit.name());
final Change change =
new Change(changeKey, new Change.Id(nextChangeId), user.getAccountId(),
new Branch.NameKey(config.getProject().getNameKey(),
GitRepositoryManager.REF_CONFIG));
change.nextPatchSetId();
ps.setCreatedOn(change.getCreatedOn());
ps.setUploader(user.getAccountId());
ps.setRevision(new RevId(commit.name()));
db.patchSets().insert(Collections.singleton(ps));
final PatchSetInfo info = patchSetInfoFactory.get(commit, ps.getId());
change.setCurrentPatchSet(info);
ChangeUtil.updated(change);
db.changes().insert(Collections.singleton(change));
addProjectOwnersAsReviewers(change.getId());
return change.getId();
}
private void addProjectOwnersAsReviewers(final Change.Id changeId) {
final String projectOwners =
groupBackend.get(AccountGroup.PROJECT_OWNERS).getName();
try {
addReviewerFactory.create(changeId, Collections.singleton(projectOwners),
false).call();
} catch (Exception e) {
// one of the owner groups is not visible to the user and this it why it
// can't be added as reviewer
}
}
}

View File

@@ -123,11 +123,13 @@ public class RenameGroupOp extends DefaultQueueOp {
ref.setName(newName);
md.getCommitBuilder().setAuthor(author);
md.setMessage("Rename group " + oldName + " to " + newName + "\n");
if (config.commit(md)) {
try {
config.commit(md);
projectCache.evict(config.getProject());
success = true;
} else {
} catch (IOException e) {
log.error("Could not commit rename of group " + oldName + " to "
+ newName + " in " + md.getProjectName().get(), e);
try {
Thread.sleep(25 /* milliseconds */);
} catch (InterruptedException wakeUp) {

View File

@@ -15,6 +15,7 @@
package com.google.gerrit.server.git;
import com.google.common.base.Objects;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheBuilder;
import org.eclipse.jgit.dircache.DirCacheEditor;
@@ -145,12 +146,12 @@ public abstract class VersionedMetaData {
* Update this metadata branch, recording a new commit on its reference.
*
* @param update helper information to define the update that will occur.
* @return true if the update was successful, false if it failed because of a
* concurrent update to the same reference.
* @return the commit that was created
* @throws IOException if there is a storage problem and the update cannot be
* executed as requested.
* executed as requested or if it failed because of a concurrent
* update to the same reference
*/
public boolean commit(MetaDataUpdate update) throws IOException {
public RevCommit commit(MetaDataUpdate update) throws IOException {
BatchMetaDataUpdate batch = openUpdate(update);
try {
batch.write(update.getCommitBuilder());
@@ -160,11 +161,32 @@ public abstract class VersionedMetaData {
}
}
/**
* Creates a new commit and a new ref based on this commit.
*
* @param update helper information to define the update that will occur.
* @param refName name of the ref that should be created
* @return the commit that was created
* @throws IOException if there is a storage problem and the update cannot be
* executed as requested or if it failed because of a concurrent
* update to the same reference
*/
public RevCommit commitToNewRef(MetaDataUpdate update, String refName) throws IOException {
BatchMetaDataUpdate batch = openUpdate(update);
try {
batch.write(update.getCommitBuilder());
return batch.createRef(refName);
} finally {
batch.close();
}
}
public interface BatchMetaDataUpdate {
void write(CommitBuilder commit) throws IOException;
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
boolean commit() throws IOException;
boolean commitAt(ObjectId revision) throws IOException;
RevCommit createRef(String refName) throws IOException;
RevCommit commit() throws IOException;
RevCommit commitAt(ObjectId revision) throws IOException;
void close();
}
@@ -224,14 +246,35 @@ public abstract class VersionedMetaData {
}
@Override
public boolean commit() throws IOException {
public RevCommit createRef(String refName) throws IOException {
if (Objects.equal(src, revision)) {
return revision;
}
RefUpdate ru = db.updateRef(refName);
ru.setExpectedOldObjectId(ObjectId.zeroId());
ru.setNewObjectId(src);
RefUpdate.Result result = ru.update();
switch (result) {
case NEW:
revision = rw.parseCommit(ru.getNewObjectId());
update.replicate(ru.getName());
return revision;
default:
throw new IOException("Cannot update " + ru.getName() + " in "
+ db.getDirectory() + ": " + ru.getResult());
}
}
@Override
public RevCommit commit() throws IOException {
return commitAt(revision);
}
@Override
public boolean commitAt(ObjectId expected) throws IOException {
public RevCommit commitAt(ObjectId expected) throws IOException {
if (Objects.equal(src, expected)) {
return true;
return revision;
}
RefUpdate ru = db.updateRef(getRefName());
@@ -249,10 +292,7 @@ public abstract class VersionedMetaData {
case FAST_FORWARD:
revision = rw.parseCommit(ru.getNewObjectId());
update.replicate(ru.getName());
return true;
case LOCK_FAILURE:
return false;
return revision;
default:
throw new IOException("Cannot update " + ru.getName() + " in "

View File

@@ -198,10 +198,7 @@ public class CreateProject {
}
md.setMessage("Created project\n");
if (!config.commit(md)) {
throw new IOException("Cannot create "
+ createProjectArgs.getProjectName());
}
config.commit(md);
} finally {
md.close();
}

View File

@@ -257,9 +257,7 @@ public class SchemaCreator {
metaReadPermission.add(rule(config, owners));
md.setMessage("Initialized Gerrit Code Review " + Version.getVersion());
if (!config.commit(md)) {
throw new IOException("Cannot create " + allProjectsName.get());
}
config.commit(md);
} finally {
git.close();
}

View File

@@ -184,9 +184,7 @@ class Schema_53 extends SchemaVersion {
}
md.setMessage("Import project configuration from SQL\n");
if (!config.commit(md)) {
throw new OrmException("Cannot export project " + name);
}
config.commit(md);
} catch (ConfigInvalidException err) {
throw new OrmException("Cannot read project " + name, err);
} catch (IOException err) {

View File

@@ -135,9 +135,7 @@ public class Schema_57 extends SchemaVersion {
}
md.setMessage("Upgrade to Gerrit Code Review schema 57\n");
if (!config.commit(md)) {
throw new OrmException("Cannot update " + allProjects);
}
config.commit(md);
} finally {
git.close();
}

View File

@@ -106,9 +106,7 @@ public class Schema_64 extends SchemaVersion {
}
md.setMessage("Upgrade to Gerrit Code Review schema 64\n");
if (!config.commit(md)) {
throw new OrmException("Cannot update " + allProjects);
}
config.commit(md);
} catch (IOException e) {
throw new OrmException(e);
} catch (ConfigInvalidException e) {

View File

@@ -207,9 +207,7 @@ public class Schema_65 extends SchemaVersion {
batch.write(config, commit);
// Save the the final metadata.
if (!batch.commitAt(config.getRevision())) {
throw new OrmException("Cannot update " + allProjects);
}
batch.commitAt(config.getRevision());
} finally {
batch.close();
}

View File

@@ -181,9 +181,7 @@ public class Schema_69 extends SchemaVersion {
}
md.setMessage("Switch LDAP group UUIDs to DNs\n");
if (!config.commit(md)) {
throw new OrmException("Cannot update " + name);
}
config.commit(md);
} catch (IOException e) {
throw new OrmException(e);
} catch (ConfigInvalidException e) {

View File

@@ -209,7 +209,7 @@ public class ProjectConfigTest extends LocalDiskRepositoryTestCase {
util.tick(5);
util.setAuthorAndCommitter(md.getCommitBuilder());
md.setMessage("Edit\n");
assertTrue("commit finished", cfg.commit(md));
cfg.commit(md);
Ref ref = db.getRef(GitRepositoryManager.REF_CONFIG);
return util.getRevWalk().parseCommit(ref.getObjectId());

View File

@@ -137,9 +137,7 @@ final class AdminSetParent extends SshCommand {
config.getProject().setParentName(newParentKey);
md.setMessage("Inherit access from "
+ (newParentKey != null ? newParentKey.get() : allProjectsName.get()) + "\n");
if (!config.commit(md)) {
err.append("error: Could not update project " + name + "\n");
}
config.commit(md);
} finally {
md.close();
}