InlineEdit: Add DELETE /changes/<id>/edit REST endpoint

Add REST endpoint to delete a file in change edit or change edit itself.

Change-Id: Ieeb15d0a91ab678f53db367bacd231314c1cf0be
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index ac72ef8..5926fe6 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1338,6 +1338,26 @@
   HTTP/1.1 204 No Content
 ----
 
+[[delete-edit]]
+=== Delete Change Edit
+--
+'DELETE /changes/link:#change-id[\{change-id\}]/edit'
+--
+
+Deletes change edit.
+
+.Request
+----
+  DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/edit HTTP/1.0
+----
+
+As response "`204 No Content`" is returned.
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[reviewer-endpoints]]
 == Reviewer Endpoints
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index a9a88af..e1040e3 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -17,6 +17,7 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.apache.http.HttpStatus.SC_BAD_REQUEST;
 import static org.apache.http.HttpStatus.SC_NO_CONTENT;
 import static org.apache.http.HttpStatus.SC_OK;
 import static org.junit.Assert.assertArrayEquals;
@@ -189,6 +190,24 @@
   }
 
   @Test
+  public void deleteEditRest() throws Exception {
+    assertEquals(RefUpdate.Result.NEW,
+        modifier.createEdit(
+            change,
+            ps));
+    assertEquals(RefUpdate.Result.FORCED,
+        modifier.modifyFile(
+            editUtil.byChange(change).get(),
+            FILE_NAME,
+            CONTENT_NEW));
+    Optional<ChangeEdit> edit = editUtil.byChange(change);
+    RestResponse r = session.delete(urlEdit());
+    assertEquals(SC_NO_CONTENT, r.getStatusCode());
+    edit = editUtil.byChange(change);
+    assertFalse(edit.isPresent());
+  }
+
+  @Test
   public void rebaseEdit() throws Exception {
     assertEquals(RefUpdate.Result.NEW,
         modifier.createEdit(
@@ -317,6 +336,25 @@
   }
 
   @Test
+  public void createEditByDeletingExistingFileRest() throws Exception {
+    RestResponse r = session.delete(urlEditFile());
+    assertEquals(SC_NO_CONTENT, r.getStatusCode());
+    Optional<ChangeEdit> edit = editUtil.byChange(change);
+    try {
+      fileUtil.getContent(edit.get().getChange().getProject(),
+          edit.get().getRevision().get(), FILE_NAME);
+      fail("ResourceNotFoundException expected");
+    } catch (ResourceNotFoundException rnfe) {
+    }
+  }
+
+  @Test
+  public void deletingNonExistingEditRest() throws Exception {
+    RestResponse r = session.delete(urlEdit());
+    assertEquals(SC_BAD_REQUEST, r.getStatusCode());
+  }
+
+  @Test
   public void deleteExistingFileRest() throws Exception {
     assertEquals(RefUpdate.Result.NEW,
         modifier.createEdit(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index fd91476..ef0b8ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -21,8 +21,10 @@
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AcceptsCreate;
+import com.google.gerrit.extensions.restapi.AcceptsDelete;
 import com.google.gerrit.extensions.restapi.AcceptsPost;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ChildCollection;
 import com.google.gerrit.extensions.restapi.DefaultInput;
 import com.google.gerrit.extensions.restapi.IdString;
@@ -54,9 +56,11 @@
 public class ChangeEdits implements
     ChildCollection<ChangeResource, ChangeEditResource>,
     AcceptsCreate<ChangeResource>,
-    AcceptsPost<ChangeResource> {
+    AcceptsPost<ChangeResource>,
+    AcceptsDelete<ChangeResource> {
   private final DynamicMap<RestView<ChangeEditResource>> views;
   private final Create.Factory createFactory;
+  private final DeleteEdit.Factory deleteEditFactory;
   private final Detail detail;
   private final ChangeEditUtil editUtil;
   private final Post post;
@@ -66,12 +70,14 @@
       Create.Factory createFactory,
       Detail detail,
       ChangeEditUtil editUtil,
-      Post post) {
+      Post post,
+      DeleteEdit.Factory deleteEditFactory) {
     this.views = views;
     this.createFactory = createFactory;
     this.detail = detail;
     this.editUtil = editUtil;
     this.post = post;
+    this.deleteEditFactory = deleteEditFactory;
   }
 
   @Override
@@ -115,6 +121,14 @@
   * change edit wasn't created yet. Change edit is created and PUT
   * handler is called.
   */
+  @SuppressWarnings("unchecked")
+  @Override
+  public DeleteEdit delete(ChangeResource parent, IdString id)
+      throws RestApiException {
+    return deleteEditFactory.create(parent.getChange(),
+        id != null ? id.get() : null);
+  }
+
   static class Create implements
       RestModifyView<ChangeResource, Put.Input> {
 
@@ -171,6 +185,57 @@
     }
   }
 
+  static class DeleteEdit implements
+      RestModifyView<ChangeResource, DeleteEdit.Input> {
+    public static class Input {
+    }
+
+    interface Factory {
+      DeleteEdit create(Change change, String path);
+    }
+
+    private final ChangeEditUtil editUtil;
+    private final ChangeEditModifier editModifier;
+    private final Provider<ReviewDb> db;
+    private final String path;
+
+    @Inject
+    DeleteEdit(ChangeEditUtil editUtil,
+        ChangeEditModifier editModifier,
+        Provider<ReviewDb> db,
+        @Assisted @Nullable String path) {
+      this.editUtil = editUtil;
+      this.editModifier = editModifier;
+      this.db = db;
+      this.path = path;
+    }
+
+    @Override
+    public Response<?> apply(ChangeResource rsrc, DeleteEdit.Input in)
+        throws IOException, AuthException, ResourceConflictException,
+        OrmException, InvalidChangeOperationException, BadRequestException {
+      Optional<ChangeEdit> edit = editUtil.byChange(rsrc.getChange());
+      if (edit.isPresent() && path == null) {
+        // Edit is wiped out
+        editUtil.delete(edit.get());
+      } else if (!edit.isPresent() && path != null) {
+        // Edit is created on top of current patch set by deleting path.
+        // Even if the latest patch set changed since the user triggered
+        // the operation, deleting the whole file is probably still what
+        // they intended.
+        editModifier.createEdit(rsrc.getChange(), db.get().patchSets().get(
+            rsrc.getChange().currentPatchSetId()));
+        edit = editUtil.byChange(rsrc.getChange());
+        editModifier.deleteFile(edit.get(), path);
+      } else {
+        // Bad request
+        throw new BadRequestException(
+            "change edit doesn't exist and no path was provided");
+      }
+      return Response.none();
+    }
+  }
+
   @Singleton
   static class Detail implements RestReadView<ChangeResource> {
     private final ChangeEditUtil editUtil;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index dd63f5d..a916430 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -117,6 +117,7 @@
         factory(ChangeInserter.Factory.class);
         factory(PatchSetInserter.Factory.class);
         factory(ChangeEdits.Create.Factory.class);
+        factory(ChangeEdits.DeleteEdit.Factory.class);
       }
     });
   }