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:
		
				
					committed by
					
						
						Shawn Pearce
					
				
			
			
				
	
			
			
			
						parent
						
							94a928b9a9
						
					
				
				
					commit
					5c0d6b33ff
				
			@@ -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();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user