Merge "Add REST endpoints for draft changes"
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 97ce2a1..0e0a355 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -932,6 +932,46 @@
   blocked by Verified
 ----
 
+[[publish-draft-change]]
+Publish Draft Change
+~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/publish'
+
+Publishes a draft change.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/publish HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[delete-draft-change]]
+Delete Draft Change
+~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]'
+or
+'POST /changes/link:#change-id[\{change-id\}]/delete'
+
+Deletes a draft change.
+
+.Request
+----
+  DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940 HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[reviewer-endpoints]]
 Reviewer Endpoints
 ------------------
@@ -1504,6 +1544,46 @@
   "revision 674ac754f91e64a0efb8087e59a176484bd534d1 is not current revision"
 ----
 
+[[publish-draft-revision]]
+Publish Draft Revision
+~~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/publish'
+
+Publishes a draft revision.
+
+.Request
+----
+  POST /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/current/publish HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
+[[delete-draft-revision]]
+Delete Draft Revision
+~~~~~~~~~~~~~~~~~~~~~
+[verse]
+'DELETE /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]'
+or
+'POST /changes/link:#change-id[\{change-id\}]/revisions/link:#revision-id[\{revision-id\}]/delete'
+
+Deletes a draft revision.
+
+.Request
+----
+  DELETE /changes/myProject~master~I8473b95934b5732ac55d26311a706c9c2bde9940/revisions/674ac754f91e64a0efb8087e59a176484bd534d1 HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+----
+
+.Response
+----
+  HTTP/1.1 204 No Content
+----
+
 [[get-patch]]
 Get Patch
 ~~~~~~~~~
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index bdba433..f79e05f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -397,6 +397,13 @@
       final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
       throws NoSuchChangeException, OrmException, IOException {
     final Change.Id changeId = patchSetId.getParentKey();
+    deleteDraftChange(changeId, gitManager, gitRefUpdated, db);
+  }
+
+  public static void deleteDraftChange(final Change.Id changeId,
+      GitRepositoryManager gitManager,
+      final GitReferenceUpdated gitRefUpdated, final ReviewDb db)
+      throws NoSuchChangeException, OrmException, IOException {
     final Change change = db.changes().get(changeId);
     if (change == null || change.getStatus() != Change.Status.DRAFT) {
       throw new NoSuchChangeException(changeId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
new file mode 100644
index 0000000..f47c278
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftChange.java
@@ -0,0 +1,99 @@
+// Copyright (C) 2013 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.server.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftChange.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftChange implements RestModifyView<ChangeResource, Input> {
+  public static class Input {
+  }
+
+  protected final Provider<ReviewDb> dbProvider;
+  private final GitRepositoryManager gitManager;
+  private final GitReferenceUpdated gitRefUpdated;
+
+  @Inject
+  public DeleteDraftChange(Provider<ReviewDb> dbProvider,
+      GitRepositoryManager gitManager,
+      GitReferenceUpdated gitRefUpdated,
+      PatchSetInfoFactory patchSetInfoFactory) {
+    this.dbProvider = dbProvider;
+    this.gitManager = gitManager;
+    this.gitRefUpdated = gitRefUpdated;
+  }
+
+  @Override
+  public Object apply(ChangeResource rsrc, Input input)
+      throws ResourceConflictException, AuthException,
+      ResourceNotFoundException, OrmException, IOException {
+    if (rsrc.getChange().getStatus() != Status.DRAFT) {
+      throw new ResourceConflictException("Change is not a draft");
+    }
+
+    if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+      throw new AuthException("Not permitted to delete this draft change");
+    }
+
+    try {
+      ChangeUtil.deleteDraftChange(rsrc.getChange().getId(),
+          gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+
+    return Response.none();
+  }
+
+  static class Action extends DeleteDraftChange implements UiAction<ChangeResource> {
+    @Inject
+    public Action(Provider<ReviewDb> dbProvider,
+        GitRepositoryManager gitManager,
+        GitReferenceUpdated gitRefUpdated,
+        PatchSetInfoFactory patchSetInfoFactory) {
+      super(dbProvider, gitManager, gitRefUpdated, patchSetInfoFactory);
+    }
+
+    @Override
+    public UiAction.Description getDescription(ChangeResource rsrc) {
+      try {
+        return new UiAction.Description()
+          .setTitle(String.format("Delete Draft Change %d",
+              rsrc.getChange().getChangeId()))
+          .setVisible(rsrc.getChange().getStatus() == Status.DRAFT
+              && rsrc.getControl().canDeleteDraft(dbProvider.get()));
+      } catch (OrmException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
new file mode 100644
index 0000000..dd20a38
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -0,0 +1,170 @@
+// Copyright (C) 2013 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.server.change;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSetInfo;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.DeleteDraftPatchSet.Input;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class DeleteDraftPatchSet implements RestModifyView<RevisionResource, Input> {
+  public static class Input {
+  }
+
+  protected final Provider<ReviewDb> dbProvider;
+  private final GitRepositoryManager gitManager;
+  private final GitReferenceUpdated gitRefUpdated;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+
+  @Inject
+  public DeleteDraftPatchSet(Provider<ReviewDb> dbProvider,
+      GitRepositoryManager gitManager,
+      GitReferenceUpdated gitRefUpdated,
+      PatchSetInfoFactory patchSetInfoFactory) {
+    this.dbProvider = dbProvider;
+    this.gitManager = gitManager;
+    this.gitRefUpdated = gitRefUpdated;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+  }
+
+  @Override
+  public Object apply(RevisionResource rsrc, Input input)
+      throws ResourceNotFoundException, AuthException, OrmException,
+      IOException, ResourceConflictException {
+    PatchSet patchSet = rsrc.getPatchSet();
+    PatchSet.Id patchSetId = patchSet.getId();
+    Change change = rsrc.getChange();
+
+    if (!patchSet.isDraft()) {
+      throw new ResourceConflictException("Patch set is not a draft.");
+    }
+
+    if (!rsrc.getControl().canDeleteDraft(dbProvider.get())) {
+      throw new AuthException("Not permitted to delete this draft patch set");
+    }
+
+    deleteDraftPatchSet(patchSet, change);
+    deleteOrUpdateDraftChange(patchSetId, change);
+
+    return Response.none();
+  }
+
+  private void deleteDraftPatchSet(PatchSet patchSet, Change change)
+      throws ResourceNotFoundException, OrmException, IOException {
+    try {
+      ChangeUtil.deleteOnlyDraftPatchSet(patchSet,
+          change, gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private void deleteOrUpdateDraftChange(PatchSet.Id patchSetId,
+      Change change) throws OrmException, ResourceNotFoundException,
+      IOException {
+    if (dbProvider.get()
+            .patchSets()
+            .byChange(change.getId())
+            .toList().size() == 0) {
+      deleteDraftChange(patchSetId);
+    } else {
+      if (change.currentPatchSetId().equals(patchSetId)) {
+        updateCurrentPatchSet(dbProvider.get(), change,
+            previousPatchSetInfo(patchSetId));
+      }
+    }
+  }
+
+  private void deleteDraftChange(PatchSet.Id patchSetId)
+      throws OrmException, IOException, ResourceNotFoundException {
+    try {
+      ChangeUtil.deleteDraftChange(patchSetId,
+          gitManager, gitRefUpdated, dbProvider.get());
+    } catch (NoSuchChangeException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private PatchSetInfo previousPatchSetInfo(PatchSet.Id patchSetId)
+      throws ResourceNotFoundException {
+    try {
+      return patchSetInfoFactory.get(dbProvider.get(),
+          new PatchSet.Id(patchSetId.getParentKey(),
+              patchSetId.get() - 1));
+    } catch (PatchSetInfoNotAvailableException e) {
+        throw new ResourceNotFoundException(e.getMessage());
+    }
+  }
+
+  private static void updateCurrentPatchSet(final ReviewDb db,
+      final Change change, final PatchSetInfo psInfo)
+      throws OrmException {
+    db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
+      @Override
+      public Change update(Change c) {
+        c.setCurrentPatchSet(psInfo);
+        ChangeUtil.updated(c);
+        return c;
+      }
+    });
+  }
+
+  static class Action extends DeleteDraftPatchSet implements UiAction<RevisionResource> {
+    @Inject
+    public Action(Provider<ReviewDb> dbProvider,
+        GitRepositoryManager gitManager,
+        GitReferenceUpdated gitRefUpdated,
+        PatchSetInfoFactory patchSetInfoFactory) {
+      super(dbProvider, gitManager, gitRefUpdated, patchSetInfoFactory);
+    }
+
+    @Override
+    public UiAction.Description getDescription(RevisionResource rsrc) {
+      PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+      try {
+        int psCount = dbProvider.get().patchSets()
+            .byChange(rsrc.getChange().getId()).toList().size();
+        return new UiAction.Description()
+          .setTitle(String.format("Delete Draft Revision %d",
+              rsrc.getPatchSet().getPatchSetId()))
+          .setVisible(rsrc.getPatchSet().isDraft()
+              && rsrc.getPatchSet().getId().equals(current)
+              && rsrc.getControl().canDeleteDraft(dbProvider.get())
+              && psCount > 1);
+      } catch (OrmException e) {
+        throw new IllegalStateException(e);
+      }
+    }
+  }
+}
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 b96e296..d292880 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
@@ -49,7 +49,10 @@
     get(CHANGE_KIND, "topic").to(GetTopic.class);
     put(CHANGE_KIND, "topic").to(PutTopic.class);
     delete(CHANGE_KIND, "topic").to(PutTopic.class);
+    delete(CHANGE_KIND).to(DeleteDraftChange.class);
+    post(CHANGE_KIND, "delete").to(DeleteDraftChange.Action.class);
     post(CHANGE_KIND, "abandon").to(Abandon.class);
+    post(CHANGE_KIND, "publish").to(Publish.CurrentRevision.class);
     post(CHANGE_KIND, "restore").to(Restore.class);
     post(CHANGE_KIND, "revert").to(Revert.class);
     post(CHANGE_KIND, "submit").to(Submit.CurrentRevision.class);
@@ -64,7 +67,10 @@
     child(CHANGE_KIND, "revisions").to(Revisions.class);
     post(REVISION_KIND, "cherrypick").to(CherryPick.class);
     get(REVISION_KIND, "commit").to(GetCommit.class);
+    delete(REVISION_KIND).to(DeleteDraftPatchSet.class);
+    post(REVISION_KIND, "delete").to(DeleteDraftPatchSet.Action.class);
     get(REVISION_KIND, "mergeable").to(Mergeable.class);
+    post(REVISION_KIND, "publish").to(Publish.class);
     get(REVISION_KIND, "related").to(GetRelated.class);
     get(REVISION_KIND, "review").to(GetReview.class);
     post(REVISION_KIND, "review").to(PostReview.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
new file mode 100644
index 0000000..7355ef3
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Publish.java
@@ -0,0 +1,161 @@
+// Copyright (C) 2013 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.server.change;
+
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.webui.UiAction;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.mail.PatchSetNotificationSender;
+import com.google.gerrit.server.change.Publish.Input;
+import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gwtorm.server.AtomicUpdate;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import java.io.IOException;
+
+public class Publish implements RestModifyView<RevisionResource, Input>,
+    UiAction<RevisionResource> {
+  public static class Input {
+  }
+
+  private final Provider<ReviewDb> dbProvider;
+  private final PatchSetNotificationSender sender;
+  private final ChangeHooks hooks;
+  private final ChangeIndexer indexer;
+
+  @Inject
+  public Publish(Provider<ReviewDb> dbProvider,
+      PatchSetNotificationSender sender,
+      ChangeHooks hooks,
+      ChangeIndexer indexer) {
+    this.dbProvider = dbProvider;
+    this.sender = sender;
+    this.hooks = hooks;
+    this.indexer = indexer;
+  }
+
+  @Override
+  public Object apply(RevisionResource rsrc, Input input) throws IOException,
+      ResourceNotFoundException, ResourceConflictException,
+      OrmException, AuthException {
+    if (!rsrc.getPatchSet().isDraft()) {
+      throw new ResourceConflictException("Patch set is not a draft");
+    }
+
+    if (!rsrc.getControl().canPublish(dbProvider.get())) {
+      throw new AuthException("Cannot publish this draft patch set");
+    }
+
+    PatchSet updatedPatchSet = updateDraftPatchSet(rsrc);
+    Change updatedChange = updateDraftChange(rsrc);
+
+    try {
+      if (!updatedPatchSet.isDraft()
+          || updatedChange.getStatus() == Change.Status.NEW) {
+        indexer.index(updatedChange);
+        hooks.doDraftPublishedHook(updatedChange, updatedPatchSet, dbProvider.get());
+        sender.send(rsrc.getChange().getStatus() == Change.Status.DRAFT,
+            rsrc.getUser(), updatedChange, updatedPatchSet,
+            rsrc.getControl().getLabelTypes());
+      }
+    } catch (PatchSetInfoNotAvailableException e) {
+      throw new ResourceNotFoundException(e.getMessage());
+    }
+
+    return Response.none();
+  }
+
+  private Change updateDraftChange(RevisionResource rsrc) throws OrmException {
+    Change updatedChange = dbProvider.get().changes()
+        .atomicUpdate(rsrc.getChange().getId(),
+        new AtomicUpdate<Change>() {
+      @Override
+      public Change update(Change change) {
+        if (change.getStatus() == Change.Status.DRAFT) {
+          change.setStatus(Change.Status.NEW);
+          ChangeUtil.updated(change);
+        }
+        return change;
+      }
+    });
+    return updatedChange;
+  }
+
+  private PatchSet updateDraftPatchSet(RevisionResource rsrc) throws OrmException {
+    final PatchSet updatedPatchSet = dbProvider.get().patchSets()
+        .atomicUpdate(rsrc.getPatchSet().getId(),
+        new AtomicUpdate<PatchSet>() {
+      @Override
+      public PatchSet update(PatchSet patchset) {
+        patchset.setDraft(false);
+        return patchset;
+      }
+    });
+    return updatedPatchSet;
+  }
+
+  @Override
+  public UiAction.Description getDescription(RevisionResource rsrc) {
+    PatchSet.Id current = rsrc.getChange().currentPatchSetId();
+    try {
+      return new UiAction.Description()
+        .setTitle(String.format("Publish Revision %d",
+            rsrc.getPatchSet().getPatchSetId()))
+        .setVisible(rsrc.getPatchSet().isDraft()
+            && rsrc.getPatchSet().getId().equals(current)
+            && rsrc.getControl().canPublish(dbProvider.get()));
+    } catch (OrmException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  public static class CurrentRevision implements
+      RestModifyView<ChangeResource, Input> {
+    private final Provider<ReviewDb> dbProvider;
+    private final Publish publish;
+
+    @Inject
+    CurrentRevision(Provider<ReviewDb> dbProvider,
+        Publish publish) {
+      this.dbProvider = dbProvider;
+      this.publish = publish;
+    }
+
+    @Override
+    public Object apply(ChangeResource rsrc, Input input) throws AuthException,
+        ResourceConflictException, ResourceConflictException, IOException,
+        OrmException, ResourceNotFoundException, AuthException {
+      PatchSet ps = dbProvider.get().patchSets()
+        .get(rsrc.getChange().currentPatchSetId());
+      if (ps == null) {
+        throw new ResourceConflictException("current revision is missing");
+      } else if (!rsrc.getControl().isPatchVisible(ps, dbProvider.get())) {
+        throw new AuthException("current revision not accessible");
+      }
+      return publish.apply(new RevisionResource(rsrc, ps), input);
+    }
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 714a6ad..7af882e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -31,11 +31,11 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.Abandon;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.DeleteDraftPatchSet;
 import com.google.gerrit.server.change.PostReview;
 import com.google.gerrit.server.change.Restore;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Submit;
-import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.config.AllProjectsName;
 import com.google.gerrit.server.project.ChangeControl;
@@ -131,7 +131,7 @@
   private ReviewDb db;
 
   @Inject
-  private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
+  private DeleteDraftPatchSet deleteDraftPatchSetImpl;
 
   @Inject
   private ProjectControl.Factory projectControlFactory;
@@ -285,6 +285,16 @@
             new ChangeResource(ctl), patchSet),
           input);
       }
+
+      if (publishPatchSet) {
+        final ReviewResult result =
+            publishDraftFactory.create(patchSet.getId()).call();
+        handleReviewResultErrors(result);
+      } else if (deleteDraftPatchSet) {
+        deleteDraftPatchSetImpl.apply(new RevisionResource(
+            new ChangeResource(ctl), patchSet),
+            new DeleteDraftPatchSet.Input());
+      }
     } catch (InvalidChangeOperationException e) {
       throw error(e.getMessage());
     } catch (IllegalStateException e) {
@@ -296,16 +306,6 @@
     } catch (ResourceConflictException e) {
       throw error(e.getMessage());
     }
-
-    if (publishPatchSet) {
-      final ReviewResult result =
-          publishDraftFactory.create(patchSet.getId()).call();
-      handleReviewResultErrors(result);
-    } else if (deleteDraftPatchSet) {
-      final ReviewResult result =
-          deleteDraftPatchSetFactory.create(patchSet.getId()).call();
-      handleReviewResultErrors(result);
-    }
   }
 
   private void handleReviewResultErrors(final ReviewResult result) {