ProjectApi: Add method to delete branches

Change-Id: I1b707c276c8a6b8ec9923cd19e3ecc9070bf310b
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
new file mode 100644
index 0000000..856eefe
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/DeleteBranchesIT.java
@@ -0,0 +1,153 @@
+// Copyright (C) 2016 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.acceptance.rest.project;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.rest.project.BranchAssert.assertRefNames;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.extensions.api.projects.BranchInput;
+import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+
+@NoHttpd
+public class DeleteBranchesIT extends AbstractDaemonTest {
+  private static final List<String> BRANCHES = ImmutableList.of(
+      "refs/heads/test-1", "refs/heads/test-2", "refs/heads/test-3");
+
+  @Before
+  public void setUp() throws Exception {
+    for (String name : BRANCHES) {
+      project().branch(name).create(new BranchInput());
+    }
+    assertBranches(BRANCHES);
+  }
+
+  @Test
+  public void deleteBranches() throws Exception {
+    HashMap<String, RevCommit> initialRevisions = initialRevisions(BRANCHES);
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    input.branches = BRANCHES;
+    project().deleteBranches(input);
+    assertBranchesDeleted();
+    assertRefUpdatedEvents(initialRevisions);
+  }
+
+  @Test
+  public void deleteBranchesForbidden() throws Exception {
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    input.branches = BRANCHES;
+    setApiUser(user);
+    try {
+      project().deleteBranches(input);
+      fail("Expected ResourceConflictException");
+    } catch (ResourceConflictException e) {
+      assertThat(e).hasMessage(errorMessageForBranches(BRANCHES));
+    }
+    setApiUser(admin);
+    assertBranches(BRANCHES);
+  }
+
+  @Test
+  public void deleteBranchesNotFound() throws Exception {
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    List<String> branches = Lists.newArrayList(BRANCHES);
+    branches.add("refs/heads/does-not-exist");
+    input.branches = branches;
+    try {
+      project().deleteBranches(input);
+      fail("Expected ResourceConflictException");
+    } catch (ResourceConflictException e) {
+      assertThat(e).hasMessage(errorMessageForBranches(
+          ImmutableList.of("refs/heads/does-not-exist")));
+    }
+    assertBranchesDeleted();
+  }
+
+  @Test
+  public void deleteBranchesNotFoundContinue() throws Exception {
+    // If it fails on the first branch in the input, it should still
+    // continue to process the remaining branches.
+    DeleteBranchesInput input = new DeleteBranchesInput();
+    List<String> branches = Lists.newArrayList("refs/heads/does-not-exist");
+    branches.addAll(BRANCHES);
+    input.branches = branches;
+    try {
+      project().deleteBranches(input);
+      fail("Expected ResourceConflictException");
+    } catch (ResourceConflictException e) {
+      assertThat(e).hasMessage(errorMessageForBranches(
+          ImmutableList.of("refs/heads/does-not-exist")));
+    }
+    assertBranchesDeleted();
+  }
+
+  private String errorMessageForBranches(List<String> branches) {
+    StringBuilder message = new StringBuilder();
+    for (String branch : branches) {
+      message.append("Cannot delete ")
+        .append(branch)
+        .append(": it doesn't exist or you do not have permission ")
+        .append("to delete it\n");
+    }
+    return message.toString();
+  }
+
+  private HashMap<String, RevCommit> initialRevisions(List<String> branches)
+      throws Exception {
+    HashMap<String, RevCommit> result = new HashMap<>();
+    for (String branch : branches) {
+      result.put(branch, getRemoteHead(project, branch));
+    }
+    return result;
+  }
+
+  private void assertRefUpdatedEvents(HashMap<String, RevCommit> revisions)
+      throws Exception {
+    for (String branch : revisions.keySet()) {
+      RevCommit revision = revisions.get(branch);
+      eventRecorder.assertRefUpdatedEvents(project.get(), branch,
+          null, revision,
+          revision, null);
+    }
+  }
+
+  private ProjectApi project() throws Exception {
+    return gApi.projects().name(project.get());
+  }
+
+  private void assertBranches(List<String> branches) throws Exception {
+    List<String> expected = Lists.newArrayList(
+        "HEAD", "refs/meta/config", "refs/heads/master");
+    expected.addAll(branches);
+    assertRefNames(expected, project().branches().get());
+  }
+
+  private void assertBranchesDeleted() throws Exception {
+    assertBranches(ImmutableList.<String>of());
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index b60d33b..e111291 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -39,6 +39,8 @@
   ListRefsRequest<BranchInfo> branches();
   ListRefsRequest<TagInfo> tags();
 
+  void deleteBranches(DeleteBranchesInput in) throws RestApiException;
+
   abstract class ListRefsRequest<T extends RefInfo> {
     protected int limit;
     protected int start;
@@ -198,5 +200,10 @@
     public TagApi tag(String ref) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public void deleteBranches(DeleteBranchesInput in) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 6071aea..b28258c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.api.projects.ChildProjectApi;
 import com.google.gerrit.extensions.api.projects.ConfigInfo;
 import com.google.gerrit.extensions.api.projects.ConfigInput;
+import com.google.gerrit.extensions.api.projects.DeleteBranchesInput;
 import com.google.gerrit.extensions.api.projects.DescriptionInput;
 import com.google.gerrit.extensions.api.projects.ProjectApi;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
@@ -38,6 +39,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.project.ChildProjectsCollection;
 import com.google.gerrit.server.project.CreateProject;
+import com.google.gerrit.server.project.DeleteBranches;
 import com.google.gerrit.server.project.GetAccess;
 import com.google.gerrit.server.project.GetConfig;
 import com.google.gerrit.server.project.GetDescription;
@@ -50,6 +52,7 @@
 import com.google.gerrit.server.project.PutConfig;
 import com.google.gerrit.server.project.PutDescription;
 import com.google.gerrit.server.project.SetAccess;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 
@@ -83,6 +86,7 @@
   private final PutConfig putConfig;
   private final ListBranches listBranches;
   private final ListTags listTags;
+  private final DeleteBranches deleteBranches;
 
   @AssistedInject
   ProjectApiImpl(CurrentUser user,
@@ -102,11 +106,12 @@
       PutConfig putConfig,
       ListBranches listBranches,
       ListTags listTags,
+      DeleteBranches deleteBranches,
       @Assisted ProjectResource project) {
     this(user, createProjectFactory, projectApi, projects, getDescription,
         putDescription, childApi, children, projectJson, branchApiFactory,
         tagApiFactory, getAccess, setAccess, getConfig, putConfig, listBranches,
-        listTags, project, null);
+        listTags, deleteBranches, project, null);
   }
 
   @AssistedInject
@@ -127,11 +132,12 @@
       PutConfig putConfig,
       ListBranches listBranches,
       ListTags listTags,
+      DeleteBranches deleteBranches,
       @Assisted String name) {
     this(user, createProjectFactory, projectApi, projects, getDescription,
         putDescription, childApi, children, projectJson, branchApiFactory,
         tagApiFactory, getAccess, setAccess, getConfig, putConfig, listBranches,
-        listTags, null, name);
+        listTags, deleteBranches, null, name);
   }
 
   private ProjectApiImpl(CurrentUser user,
@@ -151,6 +157,7 @@
       PutConfig putConfig,
       ListBranches listBranches,
       ListTags listTags,
+      DeleteBranches deleteBranches,
       ProjectResource project,
       String name) {
     this.user = user;
@@ -172,6 +179,7 @@
     this.putConfig = putConfig;
     this.listBranches = listBranches;
     this.listTags = listTags;
+    this.deleteBranches = deleteBranches;
   }
 
   @Override
@@ -328,6 +336,15 @@
     return tagApi.create(checkExists(), ref);
   }
 
+  @Override
+  public void deleteBranches(DeleteBranchesInput in) throws RestApiException {
+    try {
+      deleteBranches.apply(checkExists(), in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot delete branches", e);
+    }
+  }
+
   private ProjectResource checkExists() throws ResourceNotFoundException {
     if (project == null) {
       throw new ResourceNotFoundException(name);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
index 5a2c2ae..f819d39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -46,7 +46,8 @@
 import java.io.IOException;
 
 @Singleton
-class DeleteBranches implements RestModifyView<ProjectResource, DeleteBranchesInput> {
+public class DeleteBranches
+    implements RestModifyView<ProjectResource, DeleteBranchesInput> {
   private static final Logger log = LoggerFactory.getLogger(DeleteBranches.class);
 
   private final Provider<IdentifiedUser> identifiedUser;