Batch update project.config on group renames
When a group is renamed through the web UI, each project.config (and corresponding groups file) is updated with the new name if there is a permission rule referencing the group. This ensures authorship of the relevant modification is charged back to the user that modified the group name. Change-Id: Ic44c46553b34548e792269e6ce835ecffea42db7 Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -21,14 +21,19 @@ import com.google.gerrit.httpd.rpc.Handler;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.AccountGroupName;
|
||||
import com.google.gerrit.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.GroupCache;
|
||||
import com.google.gerrit.server.account.GroupControl;
|
||||
import com.google.gerrit.server.git.RenameGroupOp;
|
||||
import com.google.gwtorm.client.OrmDuplicateKeyException;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
class RenameGroup extends Handler<GroupDetail> {
|
||||
interface Factory {
|
||||
@@ -39,6 +44,8 @@ class RenameGroup extends Handler<GroupDetail> {
|
||||
private final GroupCache groupCache;
|
||||
private final GroupControl.Factory groupControlFactory;
|
||||
private final GroupDetailFactory.Factory groupDetailFactory;
|
||||
private final RenameGroupOp.Factory renameGroupOpFactory;
|
||||
private final IdentifiedUser currentUser;
|
||||
|
||||
private final AccountGroup.Id groupId;
|
||||
private final String newName;
|
||||
@@ -47,11 +54,15 @@ class RenameGroup extends Handler<GroupDetail> {
|
||||
RenameGroup(final ReviewDb db, final GroupCache groupCache,
|
||||
final GroupControl.Factory groupControlFactory,
|
||||
final GroupDetailFactory.Factory groupDetailFactory,
|
||||
final RenameGroupOp.Factory renameGroupOpFactory,
|
||||
final IdentifiedUser currentUser,
|
||||
@Assisted final AccountGroup.Id groupId, @Assisted final String newName) {
|
||||
this.db = db;
|
||||
this.groupCache = groupCache;
|
||||
this.groupControlFactory = groupControlFactory;
|
||||
this.groupDetailFactory = groupDetailFactory;
|
||||
this.renameGroupOpFactory = renameGroupOpFactory;
|
||||
this.currentUser = currentUser;
|
||||
this.groupId = groupId;
|
||||
this.newName = newName;
|
||||
}
|
||||
@@ -94,6 +105,10 @@ class RenameGroup extends Handler<GroupDetail> {
|
||||
|
||||
groupCache.evict(group);
|
||||
groupCache.evictAfterRename(old);
|
||||
renameGroupOpFactory.create( //
|
||||
currentUser.newCommitterIdent(new Date(), TimeZone.getDefault()), //
|
||||
group.getGroupUUID(), //
|
||||
old.get(), newName).start(0, TimeUnit.MILLISECONDS);
|
||||
|
||||
return groupDetailFactory.create(groupId).call();
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ import com.google.gerrit.server.config.FactoryModule;
|
||||
public class GitModule extends FactoryModule {
|
||||
@Override
|
||||
protected void configure() {
|
||||
factory(RenameGroupOp.Factory.class);
|
||||
factory(MetaDataUpdate.InternalFactory.class);
|
||||
bind(MetaDataUpdate.Server.class);
|
||||
}
|
||||
|
||||
@@ -130,6 +130,11 @@ public class ProjectConfig extends VersionedMetaData {
|
||||
return group;
|
||||
}
|
||||
|
||||
/** @return the group reference, if the group is used by at least one rule. */
|
||||
public GroupReference getGroup(AccountGroup.UUID uuid) {
|
||||
return groupsByUUID.get(uuid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check all GroupReferences use current group name, repairing stale ones.
|
||||
*
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
// Copyright (C) 2011 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.git;
|
||||
|
||||
import com.google.gerrit.common.data.GroupReference;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.Project.NameKey;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
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.PersonIdent;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class RenameGroupOp extends DefaultQueueOp {
|
||||
public interface Factory {
|
||||
RenameGroupOp create(@Assisted("author") PersonIdent author,
|
||||
@Assisted AccountGroup.UUID uuid, @Assisted("oldName") String oldName,
|
||||
@Assisted("newName") String newName);
|
||||
}
|
||||
|
||||
private static final int MAX_TRIES = 10;
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(RenameGroupOp.class);
|
||||
|
||||
private final ProjectCache projectCache;
|
||||
private final MetaDataUpdate.Server metaDataUpdateFactory;
|
||||
|
||||
private final PersonIdent author;
|
||||
private final AccountGroup.UUID uuid;
|
||||
private final String oldName;
|
||||
private final String newName;
|
||||
private final List<Project.NameKey> retryOn;
|
||||
|
||||
private boolean tryingAgain;
|
||||
|
||||
@Inject
|
||||
public RenameGroupOp(WorkQueue workQueue, ProjectCache projectCache,
|
||||
MetaDataUpdate.Server metaDataUpdateFactory,
|
||||
|
||||
@Assisted("author") PersonIdent author, @Assisted AccountGroup.UUID uuid,
|
||||
@Assisted("oldName") String oldName, @Assisted("newName") String newName) {
|
||||
super(workQueue);
|
||||
this.projectCache = projectCache;
|
||||
this.metaDataUpdateFactory = metaDataUpdateFactory;
|
||||
|
||||
this.author = author;
|
||||
this.uuid = uuid;
|
||||
this.oldName = oldName;
|
||||
this.newName = newName;
|
||||
this.retryOn = new ArrayList<Project.NameKey>();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
Iterable<NameKey> names = tryingAgain ? retryOn : projectCache.all();
|
||||
for (Project.NameKey projectName : names) {
|
||||
ProjectConfig config = projectCache.get(projectName).getConfig();
|
||||
GroupReference ref = config.getGroup(uuid);
|
||||
if (ref == null || newName.equals(ref.getName())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
MetaDataUpdate md = metaDataUpdateFactory.create(projectName);
|
||||
try {
|
||||
rename(md);
|
||||
} finally {
|
||||
md.close();
|
||||
}
|
||||
} catch (RepositoryNotFoundException noProject) {
|
||||
continue;
|
||||
} catch (ConfigInvalidException err) {
|
||||
log.error("Cannot rename group " + oldName + " in " + projectName, err);
|
||||
} catch (IOException err) {
|
||||
log.error("Cannot rename group " + oldName + " in " + projectName, err);
|
||||
}
|
||||
}
|
||||
|
||||
// If one or more projects did not update, wait 5 minutes
|
||||
// and give it another attempt.
|
||||
if (!retryOn.isEmpty() && !tryingAgain) {
|
||||
tryingAgain = true;
|
||||
start(5, TimeUnit.MINUTES);
|
||||
}
|
||||
}
|
||||
|
||||
private void rename(MetaDataUpdate md) throws IOException,
|
||||
ConfigInvalidException {
|
||||
boolean success = false;
|
||||
for (int attempts = 0; !success && attempts < MAX_TRIES; attempts++) {
|
||||
ProjectConfig config = ProjectConfig.read(md);
|
||||
|
||||
// The group isn't referenced, or its name has been fixed already.
|
||||
//
|
||||
GroupReference ref = config.getGroup(uuid);
|
||||
if (ref == null || newName.equals(ref.getName())) {
|
||||
projectCache.evict(config.getProject());
|
||||
return;
|
||||
}
|
||||
|
||||
ref.setName(newName);
|
||||
md.getCommitBuilder().setAuthor(author);
|
||||
md.setMessage("Rename group " + oldName + " to " + newName + "\n");
|
||||
if (config.commit(md)) {
|
||||
projectCache.evict(config.getProject());
|
||||
success = true;
|
||||
|
||||
} else {
|
||||
try {
|
||||
Thread.sleep(25 /* milliseconds */);
|
||||
} catch (InterruptedException wakeUp) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
if (tryingAgain) {
|
||||
log.warn("Could not rename group " + oldName + " to " + newName
|
||||
+ " in " + md.getProjectName().get());
|
||||
} else {
|
||||
retryOn.add(md.getProjectName());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Rename Group " + oldName;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user