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:
monica.dionisio
2011-07-12 09:57:07 -03:00
committed by Gustaf Lundh
parent a3f73aab40
commit 860ea72aab
7 changed files with 490 additions and 310 deletions

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
});
}
}

View File

@@ -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);