Create project refactoring
This refactoring is a preparation to feature 'Create project through web interface' which includes a new UI to create projects. It splits out the create project code adding new classes that will be used both by ssh command and the new UI. Updated some naming pattern to keep it more consistent. Improved the exception handling. Change-Id: I789c9a09f905d90680cc74d9b6aa7ae8f4d808b7
This commit is contained in:

committed by
Gustaf Lundh

parent
a3f73aab40
commit
860ea72aab
@@ -1,309 +0,0 @@
|
||||
// Copyright (C) 2009 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.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.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Project;
|
||||
import com.google.gerrit.reviewdb.Project.SubmitType;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.GroupCache;
|
||||
import com.google.gerrit.server.config.ProjectOwnerGroups;
|
||||
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.git.ReplicationQueue;
|
||||
import com.google.gerrit.server.git.RepositoryCaseMismatchException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
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.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/** Create a new project. **/
|
||||
final class CreateProject extends BaseCommand {
|
||||
private static final Logger log = LoggerFactory.getLogger(CreateProject.class);
|
||||
|
||||
@Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
|
||||
void setProjectNameFromOption(String name) {
|
||||
if (projectName != null) {
|
||||
throw new IllegalArgumentException("NAME already supplied");
|
||||
} else {
|
||||
projectName = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Option(name = "--owner", aliases = {"-o"}, usage = "owner(s) of project")
|
||||
private List<AccountGroup.UUID> ownerIds;
|
||||
|
||||
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "parent project")
|
||||
private ProjectControl newParent;
|
||||
|
||||
@Option(name = "--permissions-only", usage = "create project for use only as parent")
|
||||
private boolean permissionsOnly;
|
||||
|
||||
@Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of project")
|
||||
private String projectDescription = "";
|
||||
|
||||
@Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n"
|
||||
+ "(default: MERGE_IF_NECESSARY)")
|
||||
private SubmitType submitType = SubmitType.MERGE_IF_NECESSARY;
|
||||
|
||||
@Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
|
||||
private boolean contributorAgreements;
|
||||
|
||||
@Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
|
||||
private boolean signedOffBy;
|
||||
|
||||
@Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
|
||||
private boolean contentMerge;
|
||||
|
||||
@Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
|
||||
private boolean requireChangeID;
|
||||
|
||||
@Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n"
|
||||
+ "(default: master)")
|
||||
private String branch = Constants.MASTER;
|
||||
|
||||
@Option(name = "--empty-commit", usage = "to create initial empty commit")
|
||||
private boolean createEmptyCommit;
|
||||
|
||||
private String projectName;
|
||||
@Argument(index = 0, metaVar="NAME", usage="name of project to be created")
|
||||
void setProjectNameFromArgument(String name) {
|
||||
if (projectName != null) {
|
||||
throw new IllegalArgumentException("--name already supplied");
|
||||
} else {
|
||||
projectName = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
private GitRepositoryManager repoManager;
|
||||
|
||||
@Inject
|
||||
private ProjectCache projectCache;
|
||||
|
||||
@Inject
|
||||
private GroupCache groupCache;
|
||||
|
||||
@Inject
|
||||
@ProjectOwnerGroups
|
||||
private Set<AccountGroup.UUID> projectOwnerGroups;
|
||||
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
@Inject
|
||||
private ReplicationQueue rq;
|
||||
|
||||
@Inject
|
||||
@GerritPersonIdent
|
||||
private PersonIdent serverIdent;
|
||||
|
||||
@Inject
|
||||
MetaDataUpdate.User metaDataUpdateFactory;
|
||||
|
||||
private Project.NameKey nameKey;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canCreateProject()) {
|
||||
String msg = String.format(
|
||||
"fatal: %s does not have \"Create Project\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
validateParameters();
|
||||
|
||||
try {
|
||||
nameKey = new Project.NameKey(projectName);
|
||||
|
||||
String head = permissionsOnly ? GitRepositoryManager.REF_CONFIG : branch;
|
||||
final Repository repo = repoManager.createRepository(nameKey);
|
||||
try {
|
||||
rq.replicateNewProject(nameKey, head);
|
||||
|
||||
RefUpdate u = repo.updateRef(Constants.HEAD);
|
||||
u.disableRefLog();
|
||||
u.link(head);
|
||||
|
||||
createProjectConfig();
|
||||
|
||||
if (!permissionsOnly && createEmptyCommit) {
|
||||
createEmptyCommit(repo, nameKey, branch);
|
||||
}
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
} catch (IllegalStateException err) {
|
||||
handleRepositoryExistsException(nameKey);
|
||||
} catch (RepositoryCaseMismatchException err) {
|
||||
handleRepositoryExistsException(err.getNameOfExistingProject());
|
||||
} catch (RepositoryNotFoundException badName) {
|
||||
throw new UnloggedFailure(1, "fatal: " + badName.getMessage());
|
||||
} catch (Exception err) {
|
||||
throw new Failure(1, "fatal: Cannot create " + projectName, err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void createEmptyCommit(final Repository repo,
|
||||
final Project.NameKey project, final String ref) throws IOException {
|
||||
ObjectInserter oi = repo.newObjectInserter();
|
||||
try {
|
||||
CommitBuilder cb = new CommitBuilder();
|
||||
cb.setTreeId(oi.insert(Constants.OBJ_TREE, new byte[] {}));
|
||||
cb.setAuthor(metaDataUpdateFactory.getUserPersonIdent());
|
||||
cb.setCommitter(serverIdent);
|
||||
cb.setMessage("Initial empty repository\n");
|
||||
|
||||
ObjectId id = oi.insert(cb);
|
||||
oi.flush();
|
||||
|
||||
RefUpdate ru = repo.updateRef(Constants.HEAD);
|
||||
ru.setNewObjectId(id);
|
||||
final Result result = ru.update();
|
||||
switch (result) {
|
||||
case NEW:
|
||||
rq.scheduleUpdate(project, ref);
|
||||
break;
|
||||
default: {
|
||||
throw new IOException(result.name());
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Cannot create empty commit for " + projectName, e);
|
||||
throw e;
|
||||
} finally {
|
||||
oi.release();
|
||||
}
|
||||
}
|
||||
|
||||
private void createProjectConfig() throws IOException, ConfigInvalidException {
|
||||
MetaDataUpdate md = metaDataUpdateFactory.create(nameKey);
|
||||
try {
|
||||
ProjectConfig config = ProjectConfig.read(md);
|
||||
config.load(md);
|
||||
|
||||
Project newProject = config.getProject();
|
||||
newProject.setDescription(projectDescription);
|
||||
newProject.setSubmitType(submitType);
|
||||
newProject.setUseContributorAgreements(contributorAgreements);
|
||||
newProject.setUseSignedOffBy(signedOffBy);
|
||||
newProject.setUseContentMerge(contentMerge);
|
||||
newProject.setRequireChangeID(requireChangeID);
|
||||
if (newParent != null) {
|
||||
newProject.setParentName(newParent.getProject().getName());
|
||||
}
|
||||
|
||||
if (!ownerIds.isEmpty()) {
|
||||
AccessSection all = config.getAccessSection(AccessSection.ALL, true);
|
||||
for (AccountGroup.UUID ownerId : ownerIds) {
|
||||
AccountGroup accountGroup = groupCache.get(ownerId);
|
||||
GroupReference group = config.resolve(accountGroup);
|
||||
all.getPermission(Permission.OWNER, true).add(
|
||||
new PermissionRule(group));
|
||||
}
|
||||
}
|
||||
|
||||
md.setMessage("Created project\n");
|
||||
if (!config.commit(md)) {
|
||||
throw new IOException("Cannot create " + projectName);
|
||||
}
|
||||
} finally {
|
||||
md.close();
|
||||
}
|
||||
projectCache.onCreateProject(nameKey);
|
||||
repoManager.setProjectDescription(nameKey, projectDescription);
|
||||
rq.scheduleUpdate(nameKey, GitRepositoryManager.REF_CONFIG);
|
||||
}
|
||||
|
||||
private void validateParameters() throws Failure {
|
||||
if (projectName == null || projectName.isEmpty()) {
|
||||
throw new Failure(1, "fatal: Argument NAME is required");
|
||||
}
|
||||
|
||||
if (projectName.endsWith(Constants.DOT_GIT_EXT)) {
|
||||
projectName = projectName.substring(0, //
|
||||
projectName.length() - Constants.DOT_GIT_EXT.length());
|
||||
}
|
||||
|
||||
if (ownerIds != null && !ownerIds.isEmpty()) {
|
||||
ownerIds =
|
||||
new ArrayList<AccountGroup.UUID>(new HashSet<AccountGroup.UUID>(ownerIds));
|
||||
} else {
|
||||
ownerIds = new ArrayList<AccountGroup.UUID>(projectOwnerGroups);
|
||||
}
|
||||
|
||||
while (branch.startsWith("/")) {
|
||||
branch = branch.substring(1);
|
||||
}
|
||||
if (!branch.startsWith(Constants.R_HEADS)) {
|
||||
branch = Constants.R_HEADS + branch;
|
||||
}
|
||||
if (!Repository.isValidRefName(branch)) {
|
||||
throw new Failure(1, "--branch \"" + branch + "\" is not a valid name");
|
||||
}
|
||||
}
|
||||
|
||||
private void handleRepositoryExistsException(final Project.NameKey projectName)
|
||||
throws Failure {
|
||||
try {
|
||||
Repository repo = repoManager.openRepository(projectName);
|
||||
try {
|
||||
if (repo.getObjectDatabase().exists()) {
|
||||
throw new UnloggedFailure(1, "fatal: project \"" + projectName
|
||||
+ "\" exists");
|
||||
}
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
} catch (RepositoryNotFoundException err) {
|
||||
throw new Failure(1, "fatal: Cannot create " + projectName, err);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
// Copyright (C) 2009 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.common.errors.ProjectCreationFailedException;
|
||||
import com.google.gerrit.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.reviewdb.Project.SubmitType;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.project.CreateProject;
|
||||
import com.google.gerrit.server.project.CreateProjectArgs;
|
||||
import com.google.gerrit.server.project.ProjectControl;
|
||||
import com.google.gerrit.sshd.BaseCommand;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import org.apache.sshd.server.Environment;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.kohsuke.args4j.Argument;
|
||||
import org.kohsuke.args4j.Option;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** Create a new project. **/
|
||||
final class CreateProjectCommand extends BaseCommand {
|
||||
@Option(name = "--name", aliases = {"-n"}, metaVar = "NAME", usage = "name of project to be created (deprecated option)")
|
||||
void setProjectNameFromOption(String name) {
|
||||
if (projectName != null) {
|
||||
throw new IllegalArgumentException("NAME already supplied");
|
||||
} else {
|
||||
projectName = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Option(name = "--owner", aliases = {"-o"}, usage = "owner(s) of project")
|
||||
private List<AccountGroup.UUID> ownerIds;
|
||||
|
||||
@Option(name = "--parent", aliases = {"-p"}, metaVar = "NAME", usage = "parent project")
|
||||
private ProjectControl newParent;
|
||||
|
||||
@Option(name = "--permissions-only", usage = "create project for use only as parent")
|
||||
private boolean permissionsOnly;
|
||||
|
||||
@Option(name = "--description", aliases = {"-d"}, metaVar = "DESCRIPTION", usage = "description of project")
|
||||
private String projectDescription = "";
|
||||
|
||||
@Option(name = "--submit-type", aliases = {"-t"}, usage = "project submit type\n"
|
||||
+ "(default: MERGE_IF_NECESSARY)")
|
||||
private SubmitType submitType = SubmitType.MERGE_IF_NECESSARY;
|
||||
|
||||
@Option(name = "--use-contributor-agreements", aliases = {"--ca"}, usage = "if contributor agreement is required")
|
||||
private boolean contributorAgreements;
|
||||
|
||||
@Option(name = "--use-signed-off-by", aliases = {"--so"}, usage = "if signed-off-by is required")
|
||||
private boolean signedOffBy;
|
||||
|
||||
@Option(name = "--use-content-merge", usage = "allow automatic conflict resolving within files")
|
||||
private boolean contentMerge;
|
||||
|
||||
@Option(name = "--require-change-id", aliases = {"--id"}, usage = "if change-id is required")
|
||||
private boolean requireChangeID;
|
||||
|
||||
@Option(name = "--branch", aliases = {"-b"}, metaVar = "BRANCH", usage = "initial branch name\n"
|
||||
+ "(default: master)")
|
||||
private String branch = Constants.MASTER;
|
||||
|
||||
@Option(name = "--empty-commit", usage = "to create initial empty commit")
|
||||
private boolean createEmptyCommit;
|
||||
|
||||
private String projectName;
|
||||
|
||||
@Argument(index = 0, metaVar = "NAME", usage = "name of project to be created")
|
||||
void setProjectNameFromArgument(String name) {
|
||||
if (projectName != null) {
|
||||
throw new IllegalArgumentException("--name already supplied");
|
||||
} else {
|
||||
projectName = name;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject
|
||||
private IdentifiedUser currentUser;
|
||||
|
||||
@Inject
|
||||
private CreateProject.Factory CreateProjectFactory;
|
||||
|
||||
@Override
|
||||
public void start(final Environment env) {
|
||||
startThread(new CommandRunnable() {
|
||||
@Override
|
||||
public void run() throws Exception {
|
||||
if (!currentUser.getCapabilities().canCreateProject()) {
|
||||
String msg =
|
||||
String.format(
|
||||
"fatal: %s does not have \"Create Project\" capability.",
|
||||
currentUser.getUserName());
|
||||
throw new UnloggedFailure(BaseCommand.STATUS_NOT_ADMIN, msg);
|
||||
}
|
||||
|
||||
parseCommandLine();
|
||||
if (projectName == null) {
|
||||
throw new UnloggedFailure(1, "fatal: Project name is required.");
|
||||
}
|
||||
|
||||
try {
|
||||
final CreateProjectArgs args = new CreateProjectArgs();
|
||||
args.setProjectName(projectName);
|
||||
args.ownerIds = ownerIds;
|
||||
args.newParent = newParent;
|
||||
args.permissionsOnly = permissionsOnly;
|
||||
args.projectDescription = projectDescription;
|
||||
args.submitType = submitType;
|
||||
args.contributorAgreements = contributorAgreements;
|
||||
args.signedOffBy = signedOffBy;
|
||||
args.contentMerge = contentMerge;
|
||||
args.changeIdRequired = requireChangeID;
|
||||
args.branch = branch;
|
||||
args.createEmptyCommit = createEmptyCommit;
|
||||
|
||||
final CreateProject createProject = CreateProjectFactory.create(args);
|
||||
createProject.createProject();
|
||||
} catch (ProjectCreationFailedException err) {
|
||||
throw new UnloggedFailure(1, "fatal: " + err.getMessage(), err);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
@@ -29,7 +29,7 @@ public class MasterCommandModule extends CommandModule {
|
||||
command(gerrit, "create-account").to(CreateAccountCommand.class);
|
||||
command(gerrit, "create-group").to(CreateGroupCommand.class);
|
||||
command(gerrit, "rename-group").to(RenameGroupCommand.class);
|
||||
command(gerrit, "create-project").to(CreateProject.class);
|
||||
command(gerrit, "create-project").to(CreateProjectCommand.class);
|
||||
command(gerrit, "gsql").to(AdminQueryShell.class);
|
||||
command(gerrit, "set-reviewers").to(SetReviewersCommand.class);
|
||||
command(gerrit, "receive-pack").to(Receive.class);
|
||||
|
Reference in New Issue
Block a user