diff options
Diffstat (limited to 'gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java')
-rw-r--r-- | gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java | 181 |
1 files changed, 145 insertions, 36 deletions
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java index 07c573da3a..950bf341f1 100644 --- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java +++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java @@ -14,62 +14,93 @@ 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.reviewdb.client.Project; +import com.google.gerrit.server.config.AllProjectsName; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.ProjectConfig; 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.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.kohsuke.args4j.Argument; import org.kohsuke.args4j.Option; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.IOException; +import java.io.PrintWriter; 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 { + private static final Logger log = LoggerFactory.getLogger(AdminSetParent.class); + @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") + @Option(name = "--children-of", metaVar = "NAME", + usage = "parent project for which the child projects should be reparented") + private ProjectControl oldParent; + + @Option(name = "--exclude", metaVar = "NAME", + usage = "child project of old parent project which should not be reparented") + private List<ProjectControl> excludedChildren = new ArrayList<ProjectControl>(); + + @Argument(index = 0, required = false, multiValued = true, metaVar = "NAME", + usage = "projects to modify") private List<ProjectControl> children = new ArrayList<ProjectControl>(); @Inject - private ReviewDb db; + private ProjectCache projectCache; @Inject - private ProjectCache projectCache; + private MetaDataUpdate.User metaDataUpdateFactory; @Inject - @WildProjectName - private Project.NameKey wildProject; + private AllProjectsName allProjectsName; + + private PrintWriter stdout; + private Project.NameKey newParentKey = null; @Override public void start(final Environment env) { startThread(new CommandRunnable() { @Override public void run() throws Exception { - parseCommandLine(); - updateParents(); + stdout = toPrintWriter(out); + try { + parseCommandLine(); + updateParents(); + } finally { + stdout.flush(); + } } }); } - private void updateParents() throws OrmException, UnloggedFailure { + private void updateParents() throws Failure { + if (oldParent == null && children.isEmpty()) { + throw new UnloggedFailure(1, "fatal: child projects have to be specified as " + + "arguments or the --children-of option has to be set"); + } + if (oldParent == null && !excludedChildren.isEmpty()) { + throw new UnloggedFailure(1, "fatal: --exclude can only be used together " + + "with --children-of"); + } + final StringBuilder err = new StringBuilder(); final Set<Project.NameKey> grandParents = new HashSet<Project.NameKey>(); - Project.NameKey newParentKey; - grandParents.add(wildProject); + grandParents.add(allProjectsName); if (newParent != null) { newParentKey = newParent.getProject().getNameKey(); @@ -86,48 +117,64 @@ final class AdminSetParent extends BaseCommand { break; } } - } else { - // If no parent was selected, set to NULL to use the default. - // - newParentKey = null; } + final List<Project> childProjects = new ArrayList<Project>(); for (final ProjectControl pc : children) { - final Project.NameKey key = pc.getProject().getNameKey(); - final String name = pc.getProject().getName(); + childProjects.add(pc.getProject()); + } + if (oldParent != null) { + childProjects.addAll(getChildrenForReparenting(oldParent)); + } - if (wildProject.equals(key)) { + for (final Project project : childProjects) { + final String name = project.getName(); + final Project.NameKey nameKey = project.getNameKey(); + + if (allProjectsName.equals(nameKey)) { // 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) || key.equals(newParentKey)) { + if (grandParents.contains(nameKey) || nameKey.equals(newParentKey)) { // Try to avoid creating a cycle in the parent pointers. // err.append("error: Cycle exists between '" + name + "' and '" - + (newParentKey != null ? newParentKey.get() : wildProject.get()) + + (newParentKey != null ? newParentKey.get() : allProjectsName.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; + try { + MetaDataUpdate md = metaDataUpdateFactory.create(nameKey); + try { + ProjectConfig config = ProjectConfig.read(md); + 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"); + } + } finally { + md.close(); + } + } catch (RepositoryNotFoundException notFound) { + err.append("error: Project " + name + " not found\n"); + } catch (IOException e) { + final String msg = "Cannot update project " + name; + log.error(msg, e); + err.append("error: " + msg + "\n"); + } catch (ConfigInvalidException e) { + final String msg = "Cannot update project " + name; + log.error(msg, e); + err.append("error: " + msg + "\n"); } - child.setParent(newParentKey); - db.projects().update(Collections.singleton(child)); + projectCache.evict(project); } - // 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); @@ -135,4 +182,66 @@ final class AdminSetParent extends BaseCommand { throw new UnloggedFailure(1, err.toString()); } } + + /** + * Returns the children of the specified parent project that should be + * reparented. The returned list of child projects does not contain projects + * that were specified to be excluded from reparenting. + */ + private List<Project> getChildrenForReparenting(final ProjectControl parent) { + final List<Project> childProjects = new ArrayList<Project>(); + final List<Project.NameKey> excluded = + new ArrayList<Project.NameKey>(excludedChildren.size()); + for (final ProjectControl excludedChild : excludedChildren) { + excluded.add(excludedChild.getProject().getNameKey()); + } + final List<Project.NameKey> automaticallyExcluded = + new ArrayList<Project.NameKey>(excludedChildren.size()); + if (newParentKey != null) { + automaticallyExcluded.addAll(getAllParents(newParentKey)); + } + for (final Project child : getChildren(parent.getProject().getNameKey())) { + final Project.NameKey childName = child.getNameKey(); + if (!excluded.contains(childName)) { + if (!automaticallyExcluded.contains(childName)) { + childProjects.add(child); + } else { + stdout.println("Automatically excluded '" + childName + "' " + + "from reparenting because it is in the parent " + + "line of the new parent '" + newParentKey + "'."); + } + } + } + return childProjects; + } + + private Set<Project.NameKey> getAllParents(final Project.NameKey projectName) { + final Set<Project.NameKey> parents = new HashSet<Project.NameKey>(); + Project.NameKey p = projectName; + while (p != null && parents.add(p)) { + final ProjectState e = projectCache.get(p); + if (e == null) { + // If we can't get it from the cache, pretend it's not present. + break; + } + p = e.getProject().getParent(allProjectsName); + } + return parents; + } + + private List<Project> getChildren(final Project.NameKey parentName) { + final List<Project> childProjects = new ArrayList<Project>(); + for (final Project.NameKey projectName : projectCache.all()) { + final ProjectState e = projectCache.get(projectName); + if (e == null) { + // If we can't get it from the cache, pretend it's not present. + continue; + } + + if (parentName.equals(e.getProject().getParent(projectName))) { + childProjects.add(e.getProject()); + } + } + return childProjects; + } } |