Support branch creation via REST

A new branch can be created by PUT on
'/projects/<project-name>/branches/<ref>.

The WebUI was adapted to use this new REST endpoint to create branches.
The old RPC for creating a branch in ProjectAdminService was deleted.

Change-Id: Id94bc4737eedde383507c7c567b3b6b102b105c5
This commit is contained in:
Edwin Kempin
2013-05-09 19:54:37 +02:00
committed by Shawn Pearce
parent 94a928b9a9
commit 5c0d6b33ff
11 changed files with 245 additions and 151 deletions

View File

@@ -1,233 +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.httpd.rpc.project;
import com.google.gerrit.common.ChangeHooks;
import com.google.gerrit.common.data.AddBranchResult;
import com.google.gerrit.common.errors.InvalidRevisionException;
import com.google.gerrit.httpd.rpc.Handler;
import com.google.gerrit.reviewdb.client.Branch;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.NoSuchProjectException;
import com.google.gerrit.server.project.ProjectControl;
import com.google.gerrit.server.project.RefControl;
import com.google.gerrit.server.util.MagicBranch;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
class AddBranch extends Handler<AddBranchResult> {
private static final Logger log = LoggerFactory.getLogger(AddBranch.class);
interface Factory {
AddBranch create(@Assisted Project.NameKey projectName,
@Assisted("branchName") String branchName,
@Assisted("startingRevision") String startingRevision);
}
private final ProjectControl.Factory projectControlFactory;
private final ListBranches.Factory listBranchesFactory;
private final IdentifiedUser identifiedUser;
private final GitRepositoryManager repoManager;
private final GitReferenceUpdated referenceUpdated;
private final ChangeHooks hooks;
private final Project.NameKey projectName;
private final String branchName;
private final String startingRevision;
@Inject
AddBranch(final ProjectControl.Factory projectControlFactory,
final ListBranches.Factory listBranchesFactory,
final IdentifiedUser identifiedUser,
final GitRepositoryManager repoManager,
GitReferenceUpdated referenceUpdated,
final ChangeHooks hooks,
@Assisted Project.NameKey projectName,
@Assisted("branchName") String branchName,
@Assisted("startingRevision") String startingRevision) {
this.projectControlFactory = projectControlFactory;
this.listBranchesFactory = listBranchesFactory;
this.identifiedUser = identifiedUser;
this.repoManager = repoManager;
this.referenceUpdated = referenceUpdated;
this.hooks = hooks;
this.projectName = projectName;
this.branchName = branchName;
this.startingRevision = startingRevision;
}
@Override
public AddBranchResult call() throws NoSuchProjectException, IOException {
final ProjectControl projectControl =
projectControlFactory.controlFor(projectName);
String refname = branchName;
while (refname.startsWith("/")) {
refname = refname.substring(1);
}
if (!refname.startsWith(Constants.R_REFS)) {
refname = Constants.R_HEADS + refname;
}
if (!Repository.isValidRefName(refname)) {
return new AddBranchResult(new AddBranchResult.Error(
AddBranchResult.Error.Type.INVALID_NAME, refname));
}
if (MagicBranch.isMagicBranch(refname)) {
return new AddBranchResult(
new AddBranchResult.Error(
AddBranchResult.Error.Type.BRANCH_CREATION_NOT_ALLOWED_UNDER_REFNAME_PREFIX,
MagicBranch.getMagicRefNamePrefix(refname)));
}
final Branch.NameKey name = new Branch.NameKey(projectName, refname);
final RefControl refControl = projectControl.controlForRef(name);
final Repository repo = repoManager.openRepository(projectName);
try {
final ObjectId revid = parseStartingRevision(repo);
final RevWalk rw = verifyConnected(repo, revid);
RevObject object = rw.parseAny(revid);
if (refname.startsWith(Constants.R_HEADS)) {
// Ensure that what we start the branch from is a commit. If we
// were given a tag, deference to the commit instead.
//
try {
object = rw.parseCommit(object);
} catch (IncorrectObjectTypeException notCommit) {
throw new IllegalStateException(startingRevision + " not a commit");
}
}
if (!refControl.canCreate(rw, object)) {
throw new IllegalStateException("Cannot create " + refname);
}
try {
final RefUpdate u = repo.updateRef(refname);
u.setExpectedOldObjectId(ObjectId.zeroId());
u.setNewObjectId(object.copy());
u.setRefLogIdent(identifiedUser.newRefLogIdent());
u.setRefLogMessage("created via web from " + startingRevision, false);
final RefUpdate.Result result = u.update(rw);
switch (result) {
case FAST_FORWARD:
case NEW:
case NO_CHANGE:
referenceUpdated.fire(name.getParentKey(), u);
hooks.doRefUpdatedHook(name, u, identifiedUser.getAccount());
break;
case LOCK_FAILURE:
if (repo.getRef(refname) != null) {
return new AddBranchResult(new AddBranchResult.Error(
AddBranchResult.Error.Type.BRANCH_ALREADY_EXISTS, refname));
}
String refPrefix = getRefPrefix(refname);
while (!Constants.R_HEADS.equals(refPrefix)) {
if (repo.getRef(refPrefix) != null) {
return new AddBranchResult(new AddBranchResult.Error(
AddBranchResult.Error.Type.BRANCH_CREATION_CONFLICT, refPrefix));
}
refPrefix = getRefPrefix(refPrefix);
}
default: {
throw new IOException(result.name());
}
}
} catch (IOException err) {
log.error("Cannot create branch " + name, err);
throw err;
}
} catch (InvalidRevisionException e) {
return new AddBranchResult(new AddBranchResult.Error(
AddBranchResult.Error.Type.INVALID_REVISION));
} finally {
repo.close();
}
return new AddBranchResult(listBranchesFactory.create(projectName).call());
}
private static String getRefPrefix(final String refName) {
final int i = refName.lastIndexOf('/');
if (i > Constants.R_HEADS.length() - 1) {
return refName.substring(0, i);
}
return Constants.R_HEADS;
}
private ObjectId parseStartingRevision(final Repository repo)
throws InvalidRevisionException {
try {
final ObjectId revid = repo.resolve(startingRevision);
if (revid == null) {
throw new InvalidRevisionException();
}
return revid;
} catch (IOException err) {
log.error("Cannot resolve \"" + startingRevision + "\" in project \""
+ projectName + "\"", err);
throw new InvalidRevisionException();
}
}
private RevWalk verifyConnected(final Repository repo, final ObjectId revid)
throws InvalidRevisionException {
try {
final ObjectWalk rw = new ObjectWalk(repo);
try {
rw.markStart(rw.parseCommit(revid));
} catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException();
}
for (final Ref r : repo.getAllRefs().values()) {
try {
rw.markUninteresting(rw.parseAny(r.getObjectId()));
} catch (MissingObjectException err) {
continue;
}
}
rw.checkConnectivity();
return rw;
} catch (IncorrectObjectTypeException err) {
throw new InvalidRevisionException();
} catch (MissingObjectException err) {
throw new InvalidRevisionException();
} catch (IOException err) {
log.error("Repository \"" + repo.getDirectory()
+ "\" may be corrupt; suggest running git fsck", err);
throw new InvalidRevisionException();
}
}
}

View File

@@ -15,7 +15,6 @@
package com.google.gerrit.httpd.rpc.project;
import com.google.gerrit.common.data.AccessSection;
import com.google.gerrit.common.data.AddBranchResult;
import com.google.gerrit.common.data.ListBranchesResult;
import com.google.gerrit.common.data.ProjectAccess;
import com.google.gerrit.common.data.ProjectAdminService;
@@ -32,7 +31,6 @@ import java.util.List;
import java.util.Set;
class ProjectAdminServiceImpl implements ProjectAdminService {
private final AddBranch.Factory addBranchFactory;
private final ChangeProjectAccess.Factory changeProjectAccessFactory;
private final ReviewProjectAccess.Factory reviewProjectAccessFactory;
private final ChangeProjectSettings.Factory changeProjectSettingsFactory;
@@ -43,8 +41,7 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
private final ProjectDetailFactory.Factory projectDetailFactory;
@Inject
ProjectAdminServiceImpl(final AddBranch.Factory addBranchFactory,
final ChangeProjectAccess.Factory changeProjectAccessFactory,
ProjectAdminServiceImpl(final ChangeProjectAccess.Factory changeProjectAccessFactory,
final ReviewProjectAccess.Factory reviewProjectAccessFactory,
final ChangeProjectSettings.Factory changeProjectSettingsFactory,
final DeleteBranches.Factory deleteBranchesFactory,
@@ -52,7 +49,6 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
final VisibleProjectDetails.Factory visibleProjectDetailsFactory,
final ProjectAccessFactory.Factory projectAccessFactory,
final ProjectDetailFactory.Factory projectDetailFactory) {
this.addBranchFactory = addBranchFactory;
this.changeProjectAccessFactory = changeProjectAccessFactory;
this.reviewProjectAccessFactory = reviewProjectAccessFactory;
this.changeProjectSettingsFactory = changeProjectSettingsFactory;
@@ -119,12 +115,4 @@ class ProjectAdminServiceImpl implements ProjectAdminService {
final AsyncCallback<Set<Branch.NameKey>> callback) {
deleteBranchesFactory.create(projectName, toRemove).to(callback);
}
@Override
public void addBranch(final Project.NameKey projectName,
final String branchName, final String startingRevision,
final AsyncCallback<AddBranchResult> callback) {
addBranchFactory.create(projectName, branchName, startingRevision).to(
callback);
}
}

View File

@@ -28,7 +28,6 @@ public class ProjectModule extends RpcServletModule {
install(new FactoryModule() {
@Override
protected void configure() {
factory(AddBranch.Factory.class);
factory(ChangeProjectAccess.Factory.class);
factory(ReviewProjectAccess.Factory.class);
factory(ChangeProjectSettings.Factory.class);