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
	 Shawn O. Pearce
					Shawn O. Pearce