Merge "Suggest parent for 'create-project' in the UI."
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index eded40b..ebe71b2 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -592,9 +592,8 @@
 +
 Default is 5 minutes.
 
-
 [[changeMerge]]Section changeMerge
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
 Controls whether or not the mergeability test of changes is
 enabled.  If enabled, when the change page is loaded, the test is
@@ -606,11 +605,10 @@
   test = true
 ----
 
-+
 By default this is false (test is not enabled).
 
 [[commentlink]]Section commentlink
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 Comment links are find/replace strings applied to change descriptions,
 patch comments, and in-line code comments to turn set strings into
 hyperlinks.  One common use is for linking to bug-tracking systems.
@@ -1136,7 +1134,7 @@
 Valid values are the characters '*', '(' and ')'.
 
 [[hooks]]Section hooks
-~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~
 
 See also link:config-hooks.html[Hooks].
 
@@ -2157,7 +2155,7 @@
 
 
 [[upload]]Section upload
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+~~~~~~~~~~~~~~~~~~~~~~~~
 Sets the group of users allowed to execute 'upload-pack' on the
 server, 'upload-pack' is what runs on the server during a user's
 fetch, clone or repo sync command.
@@ -2206,7 +2204,7 @@
 
 
 File `etc/secure.config`
--------------------------
+------------------------
 The optional file `'$site_path'/etc/secure.config` overrides (or
 supplements) the settings supplied by `'$site_path'/etc/gerrit.config`.
 The file should be readable only by the daemon process and can be
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
new file mode 100644
index 0000000..fd98c1d
--- /dev/null
+++ b/Documentation/dev-contributing.txt
@@ -0,0 +1,241 @@
+Gerrit Code Review - Contributing
+=================================
+
+Gerrit is developed as a self-hosting open source project and
+very much welcomes contributions from anyone with a contributor's
+agreement on file with the project.
+
+* https://gerrit-review.googlesource.com/
+
+The Contributor License Agreements:
+
+* https://gerrit-review.googlesource.com/static/cla_individual.html
+* https://gerrit-review.googlesource.com/static/cla_corporate.html
+
+As Gerrit is a code review tool, naturally contributions will
+be reviewed before they will get submitted to the code base.  To
+start your contribution, please make a git commit and upload it
+for review to the main Gerrit review server.  To help speed up the
+review of your change, review these guidelines before submitting
+your change.  You can view the pending Gerrit contributions and
+their statuses here:
+
+* https://gerrit-review.googlesource.com/#/q/status:open+project:gerrit,n,z
+
+Depending on the size of that list it might take a while for
+your change to get reviewed.  Naturally there are fewer
+approvers than contributors; so anything that you can do to
+ensure that your contribution will undergo fewer revisions
+will speed up the contribution process.  This includes helping
+out reviewing other people's changes to relieve the load from
+the approvers.  Even if you are not familiar with Gerrit's
+internals, it would be of great help if you can download, try
+out, and comment on new features.  If it works as advertised,
+say so, and if you have the priviliges to do so, go ahead
+and give it a +1 Verified.  If you would find the feature
+useful, say so and give it a +1 code review.
+
+And finally, the quicker you respond to the comments of your
+reviewers, the quicker your change might get merged!  Try to
+reply to every comment after submitting your new patch,
+particularly if you decided against making the suggested change.
+Reviewers don't want to seem like nags and pester you if you
+haven't replied or made a fix, so it helps them know if you
+missed it or decided against it.
+
+
+Review Criteria
+---------------
+
+Here are some hints as to what approvers may be looking for
+before approving or submitting changes to the Gerrit project.
+Let's start with the simple nit picky stuff.  You are likely
+excited that your code works; help us share your excitement
+by not distracting us with the simple stuff.  Thanks to Gerrit,
+problems are often highlighted and we find it hard to look
+beyond simple spacing issues.  Blame it on our short attention
+spans, we really do want your code.
+
+
+Commit Message
+--------------
+
+It is essential to have a good commit message if you want your
+change to be reviewed.
+
+  * Keep lines no longer than 72 chars
+  * Start with a short one line summary
+  * Followed by a blank line
+  * Followed by one or more explanatory paragraphs
+  * Use the present tense (fix instead of fixed)
+  * Include a Bug: Issue <#> line if fixing a Gerrit issue
+  * Include a Change-Id line
+
+
+A sample good Gerrit commit message:
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+====
+  Add sample commit message to guidelines doc
+
+  The original patch set for the contributing guidelines doc did not
+  include a sample commit message, this new patchset does.  Hopefully this
+  makes things a bit clearer since examples can sometimes help when
+  explanations don't.
+
+  Note that the body of this commit message can be several paragraphs, and
+  that I word wrap it at 72 characters.  Also note that I keep the summary
+  line under 50 characters since it is often truncated by tools which
+  display just the git summary.
+
+  Bug: Issue 98765605
+  Change-Id: Ic4a7c07eeb98cdeaf44e9d231a65a51f3fceae52
+====
+
+
+Style
+-----
+
+The basic coding style is covered by the tools/GoogleFormat.xml
+doc, see the link:dev-eclipse.html#Formatting[Eclipse Setup]
+for that.
+
+Highlighted/additional styling notes:
+
+  * It is generally more important to match the style of the nearby
+    code which you are modifying than it is to match the style
+    in the formatting guidelines.  This is especially true within the
+    same file.
+  * Review your change in Gerrit to see if it highlights
+    mistakingly deleted/added spaces on lines, trailing spaces.
+  * Line length should be 80 or less, unless the code reads
+    better with something slightly longer.  Shorter lines not only
+    help reviewers who may use a tablet to review the code, but future
+    contributors may also like to open several editors side by
+    side while editing new changes.
+  * Use 2 spaces for indent (no tabs)
+  * Use brackets in all ifs, spaces before/after if parens.
+  * Use /** */ style Javadocs for variables.
+
+Additionally, you will notice that most of the newline spacing
+is fairly consistent throughout the code in Gerrit, it helps to
+stick to the blank line conventions.  Here are some specific
+examples:
+
+  * Keep a blank line between all class and method declarations.
+  * Do not add blank lines at the beginning or end of class/methods.
+  * Put a blank line between external import sources, but not
+    between internal ones.
+
+
+Code Organization
+-----------------
+
+Do your best to organize classes and methods in a logical way.
+Here are some guidelines that Gerrit uses:
+
+  * Ensure a standard copyright header is included at the top
+    of any new files (copy it from another file, update the year).
+  * Always place loggers first in your class!
+  * Define any static interfaces next in your class.
+  * Define non static interfaces after static interfaces in your
+    class.
+  * Next you should define static types and members.
+  * Finally instance members, then constuctors, and then instance
+    methods.
+  * Some common exceptions are private helper static methods which
+    might appear near the instance methods which they help.
+  * Getters and setters for the same instance field should usually
+    be near each other baring a good reason not to.
+  * If you are using assisted injection, the factory for your class
+    should be before the instance members.
+  * Imports should be mostly aphabetical (uppercase sorts before
+    all lowercase, which means classes come before packages at the
+    same level).
+
+Wow that's a lot!  But don't worry, you'll get the habit and most
+of the code is organized this way already; so if you pay attention
+to the class you are editing you will likely pick up on it.
+Naturally new classes are a little harder; you may want to come
+back and consult this section when creating them.
+
+
+Design
+------
+
+Here are some design level ojectives that you should keep in mind
+when coding:
+
+  * ORM entity objects should match exactly one row in the database.
+  * Most client pages should perform only one RPC to load so as to
+    keep latencies down.  Exceptions would apply to RPCs which need
+    to load large data sets if splitting them out will help the
+    page load faster.  Generally page loads are expected to complete
+    in under 100ms.  This will be the case for most operations,
+    unless the data being fetched is not using Gerrit's caching
+    infrastructure.  In these slower cases, it is worth considering
+    mitigating this longer load by using a second RPC to fill in
+    this data after the page is displayed (or alternatively it might
+    be worth proposing caching this data).
+  * @Inject should be used on constructors, not on fields.  The
+    current exceptions are the ssh commands, these were implemented
+    earlier in Gerrit's development.  To stay consistent, new ssh
+    commands should follow this older pattern; but eventually these
+    should get converted to eliminate this exception.
+  * Don't leave repository objects (git or schema) open.  A .close()
+    after every open should be placed in a finally{} block.
+  * Don't leave UI components, which can cause new actions to occur,
+    enabled during RPCs which update the DB.  This is to prevent
+    people from submitting actions more than once when operating
+    on slow links.  If the action buttons are disabled, they cannot
+    be resubmitted and the user can see that Gerrit is still busy.
+  * GWT EventBus is the new way forward.
+
+
+Tests
+-----
+
+  * Tests for new code will greatly help your change get approved.
+
+
+Change Size/Number of Files Touched
+-----------------------------------
+
+And finally, I probably cannot say enough about change sizes.
+Generally, smaller is better, hopefully within reason.  Do try to
+keep things which will be confusing on their own together,
+especially if changing one without the other will break something!
+
+  * If a new feature is implemented and it is a larger one, try to
+    identify if it can be split into smaller logical features; when
+    in doubt, err on the smaller side.
+  * Separate bug fixes from feature improvements.  The bug fix may
+    be an easy candidate for approval and should not need to wait
+    for new features to be approved.  Also, combining the two makes
+    reviewing harder since then there is no clear line between the
+    fix and the feature.
+  * Separate supporting refactoring from feature changes.  If your
+    new feature requires some refactoring, it helps to make the
+    refactoring a separate change which your feature change
+    depends on.  This way, reviewers can easily review the refactor
+    change as a something that should not alter the current
+    functionality, and feel more confident they can more easily
+    spot errors this way.  Of course, it also makes it easier to
+    test and locate later on if an unfortunate error does slip in.
+    Lastly, by not having to see refactoring changes at the same
+    time, it helps reviewers understand how your feature changes
+    the current functionality.
+  * Separate logical features into separate changes.  This
+    is often the hardest part.  Here is an example:  when adding a
+    new ability, make separate changes for the UI and the ssh
+    commands if possible.
+  * Do only what the commit message describes.  In other words, things which
+    are not strictly related to the commit message shouldn't be part of
+    a change, even trivial things like externalizing a string somewhere
+    or fixing a typo.  This help keep "git blame" more useful in the future
+    and it also makes "git revert" more useful.
+  * Use topic branches to link your separate changes together.
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index 82d0a67..b37223c 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -16,6 +16,7 @@
 http://m2eclipse.codehaus.org/[m2eclipse]
 
 
+[[Formatting]]
 Code Formatter Settings
 -----------------------
 
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 2490695..552b5a8 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -11,7 +11,7 @@
 Create a new client workspace:
 
 ----
-  git clone https://code.google.com/p/gerrit
+  git clone https://gerrit.googlesource.com/gerrit
   cd gerrit
 ----
 
diff --git a/Documentation/index.txt b/Documentation/index.txt
index b42a35a..5143bf7 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -46,6 +46,7 @@
 
 * link:dev-readme.html[Developer Setup]
 * link:dev-eclipse.html[Eclipse Setup]
+* link:dev-contributing.html[Contributing to Gerrit]
 * link:dev-design.html[System Design]
 * link:i18n-readme.html[i18n Support]
 
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.1.txt b/ReleaseNotes/ReleaseNotes-2.2.2.1.txt
new file mode 100644
index 0000000..4613787
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.1.txt
@@ -0,0 +1,53 @@
+Release notes for Gerrit 2.2.2.1
+================================
+
+Gerrit 2.2.2.1 is now available:
+
+link:http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.1.war[http://code.google.com/p/gerrit/downloads/detail?name=gerrit-2.2.2.1.war]
+
+
+There are no schema changes from 2.2.2.  However, if upgrading from
+anything but 2.2.2, follow the upgrade procedure in the 2.2.2
+link:ReleaseNotes-2.2.2.html[ReleaseNotes].
+
+
+Bug Fixes
+---------
+* issue 1139 Fix change state in patch set approval if reviewer is added to
+closed change
++
+For the dummy patch set approval that is created when a reviewer is
+added the cached change state is always open, which is incorrect if a
+reviewer is added to a closed change. As a result the closed change will
+appear in the reviewers dashboard in the 'Review Requests' section and will
+stay there forever.  Ensure the correct change state is cached in the dummy
+patch set approval when it is created.
+
+* issue 1171 Fix ownerin and reviewerin searches
++
+Update the ownerin and reviewerin searches to use AccountGroup.UUID as
+required by commit e662fb3d4d7d0ad05791b8d2143ac5ce58117335.
+
+* issue 871 Display hash of the cherry-pick merge in comment
++
+After merging a change via cherry-pick, we add the commit's
+hash to the comment. This was accidentally removed in
+commit 14246de3c0f81c06bba8d4530e6bf00e918c11b0
+
+
+Documentation
+-------------
+* Update top level SUBMITTING_PATCHES
++
+This document is out of date, the URLs are from last August.
+Direct readers to the new server.
+
+* Add contributing guideline document
+
+* Documentation: update version references for 2.2.2
++
+Correct wording and instructions to be sure they match what would
+be observed with the indicated version of gerrit.
+Expand instructions when needed to ensure all commands could be
+executed and were successful.
+Indent commands and output based on a run of the instructions
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index b6dd4b2..8d223ad 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -5,6 +5,7 @@
 Version 2.2.x
 -------------
 * link:ReleaseNotes-2.2.2.html[2.2.2],
+* link:ReleaseNotes-2.2.2.1.html[2.2.2.1],
 * link:ReleaseNotes-2.2.1.html[2.2.1],
 * link:ReleaseNotes-2.2.0.html[2.2.0]
 
diff --git a/SUBMITTING_PATCHES b/SUBMITTING_PATCHES
index b4448620..e766ef1 100644
--- a/SUBMITTING_PATCHES
+++ b/SUBMITTING_PATCHES
@@ -5,7 +5,7 @@
  - Make sure all code is under the Apache License, 2.0.
  - Publish your changes for review:
 
-   git push ssh://review.source.android.com:29418/tools/gerrit.git HEAD:refs/for/master
+   git push https://gerrit-review.googlesource.com/gerrit HEAD:refs/for/master
 
 
 Long Version:
@@ -55,23 +55,22 @@
 
 Instead, login to the Gerrit Code Review tool at:
 
-  https://review.source.android.com/
+  https://gerrit-review.googlesource.com/
 
 Ensure you have completed one of the necessary contributor
 agreements, providing documentation to the project maintainers that
 they have right to redistribute your work under the Apache License:
 
-  https://review.source.android.com/#settings,agreements
+  https://gerrit-review.googlesource.com/#/settings/agreements
 
-Ensure you have registered one or more SSH public keys, so you can
-push your commits directly over SSH:
+Ensure you have obtained a unique HTTP password to identify yourself:
 
-  https://review.source.android.com/#settings,ssh-keys
+  https://gerrit-review.googlesource.com/#/settings/http-password
 
-Push your patches over SSH to the review server, possibly through
+Push your patches over HTTPS to the review server, possibly through
 a remembered remote to make this easier in the future:
 
-   git config remote.review.url ssh://review.source.android.com:29418/tools/gerrit.git
+   git config remote.review.url https://google-review.googlesource.com/gerrit
    git config remote.review.push HEAD:refs/for/master
 
    git push review
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
index 3744ef0..c9be1d4 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchDetailService.java
@@ -44,8 +44,21 @@
   @SignInRequired
   void deleteDraft(PatchLineComment.Key key, AsyncCallback<VoidResult> callback);
 
+  /**
+   * Deletes the specified draft patch set. If the draft patch set is the only
+   * patch set of the change, then also the change gets deleted.
+   *
+   * @param psid ID of the draft patch set that should be deleted
+   * @param callback callback to report the result of the draft patch set
+   *        deletion operation; if the draft patch set was successfully deleted
+   *        {@link AsyncCallback#onSuccess(Object)} is invoked and the change
+   *        details are passed as parameter; if the change gets deleted because
+   *        the draft patch set that was deleted was the only patch set in the
+   *        change, then <code>null</code> is passed as result to
+   *        {@link AsyncCallback#onSuccess(Object)}
+   */
   @SignInRequired
-  void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<VoidResult> callback);
+  void deleteDraftPatchSet(PatchSet.Id psid, AsyncCallback<ChangeDetail> callback);
 
   @SignInRequired
   void publishComments(PatchSet.Id psid, String message,
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
index dc08e07..9378526 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/ReviewResult.java
@@ -66,11 +66,17 @@
       /** Not permitted to publish this draft patch set */
       PUBLISH_NOT_PERMITTED,
 
+      /** Not permitted to delete this draft patch set */
+      DELETE_NOT_PERMITTED,
+
       /** Review operation not permitted by rule. */
       RULE_ERROR,
 
       /** Review operation invalid because patch set is not a draft. */
       NOT_A_DRAFT,
+
+      /** Error writing change to git repository */
+      GIT_ERROR
     }
 
     protected Type type;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
index 45a3315..d3c3f76 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/PatchSetComplexDisclosurePanel.java
@@ -501,7 +501,8 @@
       actionsPanel.add(b);
     }
 
-    if (changeDetail.canDeleteDraft()) {
+    if (changeDetail.getChange().getStatus() == Change.Status.DRAFT
+        && changeDetail.canDeleteDraft()) {
       final Button b = new Button(Util.C.buttonDeleteDraftChange());
       b.addClickHandler(new ClickHandler() {
         @Override
@@ -613,9 +614,13 @@
       public void onClick(final ClickEvent event) {
         b.setEnabled(false);
         PatchUtil.DETAIL_SVC.deleteDraftPatchSet(patchSet.getId(),
-            new GerritCallback<VoidResult>() {
-              public void onSuccess(VoidResult result) {
-                Gerrit.display(PageLinks.MINE);
+            new GerritCallback<ChangeDetail>() {
+              public void onSuccess(final ChangeDetail result) {
+                if (result != null) {
+                  changeScreen.update(result);
+                } else {
+                  Gerrit.display(PageLinks.MINE);
+                }
               }
 
               @Override
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index a1dbc2f..d226fd0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -124,7 +124,7 @@
     detail.setCanAbandon(change.getStatus() != Change.Status.DRAFT && change.getStatus().isOpen() && control.canAbandon());
     detail.setCanPublish(control.canPublish(db));
     detail.setCanRestore(change.getStatus() == Change.Status.ABANDONED && control.canRestore());
-    detail.setCanDeleteDraft(control.canDelete(db));
+    detail.setCanDeleteDraft(control.canDeleteDraft(db));
     detail.setStarred(control.getCurrentUser().getStarredChanges().contains(
         changeId));
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java
index 70aca83..ab809ce 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/DeleteDraftChange.java
@@ -61,7 +61,7 @@
 
     final Change.Id changeId = patchSetId.getParentKey();
     final ChangeControl control = changeControlFactory.validateFor(changeId);
-    if (!control.canDelete(db)) {
+    if (!control.canDeleteDraft(db)) {
       throw new NoSuchChangeException(changeId);
     }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 6e0e1af..50d6cea 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.httpd.rpc.patch;
 
+import com.google.gerrit.common.data.ChangeDetail;
 import com.google.gerrit.common.data.ReviewerResult;
+import com.google.gerrit.common.data.ReviewResult;
 import com.google.gerrit.common.data.ApprovalSummary;
 import com.google.gerrit.common.data.ApprovalSummarySet;
 import com.google.gerrit.common.data.ApprovalTypes;
@@ -23,6 +25,7 @@
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
 import com.google.gerrit.httpd.rpc.Handler;
+import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
 import com.google.gerrit.reviewdb.Account;
 import com.google.gerrit.reviewdb.AccountDiffPreference;
 import com.google.gerrit.reviewdb.AccountPatchReview;
@@ -38,6 +41,7 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
+import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReplicationQueue;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
@@ -52,7 +56,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.io.IOException;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -66,6 +69,7 @@
   private final AccountInfoCacheFactory.Factory accountInfoCacheFactory;
   private final AddReviewerHandler.Factory addReviewerHandlerFactory;
   private final ChangeControl.Factory changeControlFactory;
+  private final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
   private final RemoveReviewerHandler.Factory removeReviewerHandlerFactory;
   private final FunctionState.Factory functionStateFactory;
   private final PublishComments.Factory publishCommentsFactory;
@@ -74,6 +78,7 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final GitRepositoryManager gitManager;
   private final ReplicationQueue replication;
+  private final ChangeDetailFactory.Factory changeDetailFactory;
 
   @Inject
   PatchDetailServiceImpl(final Provider<ReviewDb> schema,
@@ -83,13 +88,15 @@
       final AddReviewerHandler.Factory addReviewerHandlerFactory,
       final RemoveReviewerHandler.Factory removeReviewerHandlerFactory,
       final ChangeControl.Factory changeControlFactory,
+      final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory,
       final FunctionState.Factory functionStateFactory,
       final PatchScriptFactory.Factory patchScriptFactoryFactory,
       final PublishComments.Factory publishCommentsFactory,
       final SaveDraft.Factory saveDraftFactory,
       final PatchSetInfoFactory patchSetInfoFactory,
       final GitRepositoryManager gitManager,
-      final ReplicationQueue replication) {
+      final ReplicationQueue replication,
+      final ChangeDetailFactory.Factory changeDetailFactory) {
     super(schema, currentUser);
     this.approvalTypes = approvalTypes;
 
@@ -97,6 +104,7 @@
     this.addReviewerHandlerFactory = addReviewerHandlerFactory;
     this.removeReviewerHandlerFactory = removeReviewerHandlerFactory;
     this.changeControlFactory = changeControlFactory;
+    this.deleteDraftPatchSetFactory = deleteDraftPatchSetFactory;
     this.functionStateFactory = functionStateFactory;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
     this.publishCommentsFactory = publishCommentsFactory;
@@ -104,6 +112,7 @@
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.gitManager = gitManager;
     this.replication = replication;
+    this.changeDetailFactory = changeDetailFactory;
   }
 
   public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
@@ -149,23 +158,28 @@
   }
 
   public void deleteDraftPatchSet(final PatchSet.Id psid,
-      final AsyncCallback<VoidResult> callback) {
-    run(callback, new Action<VoidResult>() {
-      public VoidResult run(ReviewDb db) throws OrmException, Failure {
+      final AsyncCallback<ChangeDetail> callback) {
+    run(callback, new Action<ChangeDetail>() {
+      public ChangeDetail run(ReviewDb db) throws OrmException, Failure {
+        ReviewResult result = null;
         try {
-          final ChangeControl cc = changeControlFactory.validateFor(psid.getParentKey());
-          if (!cc.isOwner()) {
+          result = deleteDraftPatchSetFactory.create(psid).call();
+          if (result.getErrors().size() > 0) {
             throw new Failure(new NoSuchEntityException());
           }
-          ChangeUtil.deleteDraftPatchSet(psid, gitManager, replication, patchSetInfoFactory, db);
+          if (result.getChangeId() == null) {
+            // the change was deleted because the draft patch set that was
+            // deleted was the only patch set in the change
+            return null;
+          }
+          return changeDetailFactory.create(result.getChangeId()).call();
         } catch (NoSuchChangeException e) {
-          throw new Failure(new NoSuchChangeException(psid.getParentKey()));
+          throw new Failure(new NoSuchChangeException(result.getChangeId()));
+        } catch (NoSuchEntityException e) {
+          throw new Failure(e);
         } catch (PatchSetInfoNotAvailableException e) {
           throw new Failure(e);
-        } catch (IOException e) {
-          throw new Failure(e);
         }
-        return VoidResult.INSTANCE;
       }
     });
   }
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 0c17b25..55de507 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
@@ -286,37 +286,7 @@
     db.changes().delete(Collections.singleton(change));
   }
 
-  public static void deleteDraftPatchSet(final PatchSet.Id patchSetId,
-      GitRepositoryManager gitManager,
-      final ReplicationQueue replication,
-      final PatchSetInfoFactory patchSetInfoFactory,
-      final ReviewDb db) throws NoSuchChangeException, OrmException,
-      PatchSetInfoNotAvailableException, IOException {
-    final Change.Id changeId = patchSetId.getParentKey();
-    final Change change = db.changes().get(changeId);
-    final PatchSet patch = db.patchSets().get(patchSetId);
-
-    deleteOnlyDraftPatchSet(patch, change, gitManager, replication, db);
-
-    List<PatchSet> restOfPatches = db.patchSets().byChange(changeId).toList();
-    if (restOfPatches.size() == 0) {
-      deleteDraftChange(patchSetId, gitManager, replication, db);
-    } else {
-      PatchSet.Id highestId = null;
-      for (PatchSet ps : restOfPatches) {
-        if (highestId == null || ps.getPatchSetId() > highestId.get()) {
-          highestId = ps.getId();
-        }
-      }
-      if (change.currentPatchSetId().equals(patchSetId)) {
-        change.removeLastPatchSetId();
-        change.setCurrentPatchSet(patchSetInfoFactory.get(db, change.currPatchSetId()));
-        db.changes().update(Collections.singleton(change));
-      }
-    }
-  }
-
-  private static void deleteOnlyDraftPatchSet(final PatchSet patch,
+  public static void deleteOnlyDraftPatchSet(final PatchSet patch,
       final Change change, GitRepositoryManager gitManager,
       final ReplicationQueue replication, final ReviewDb db)
       throws NoSuchChangeException, OrmException, IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
new file mode 100644
index 0000000..761b765
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/DeleteDraftPatchSet.java
@@ -0,0 +1,125 @@
+// Copyright (C) 2011 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.changedetail;
+
+import com.google.gerrit.common.data.ReviewResult;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.ReviewDb;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ReplicationQueue;
+import com.google.gerrit.server.patch.PatchSetInfoFactory;
+import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gwtorm.client.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Callable;
+
+public class DeleteDraftPatchSet implements Callable<ReviewResult> {
+
+  public interface Factory {
+    DeleteDraftPatchSet create(PatchSet.Id patchSetId);
+  }
+
+  private final ChangeControl.Factory changeControlFactory;
+  private final ReviewDb db;
+  private final GitRepositoryManager gitManager;
+  private final ReplicationQueue replication;
+  private final PatchSetInfoFactory patchSetInfoFactory;
+
+  private final PatchSet.Id patchSetId;
+
+  @Inject
+  DeleteDraftPatchSet(ChangeControl.Factory changeControlFactory,
+      ReviewDb db, GitRepositoryManager gitManager,
+      ReplicationQueue replication, PatchSetInfoFactory patchSetInfoFactory,
+      @Assisted final PatchSet.Id patchSetId) {
+    this.changeControlFactory = changeControlFactory;
+    this.db = db;
+    this.gitManager = gitManager;
+    this.replication = replication;
+    this.patchSetInfoFactory = patchSetInfoFactory;
+
+    this.patchSetId = patchSetId;
+  }
+
+  @Override
+  public ReviewResult call() throws NoSuchChangeException, OrmException {
+    final ReviewResult result = new ReviewResult();
+
+    final Change.Id changeId = patchSetId.getParentKey();
+    result.setChangeId(changeId);
+    final ChangeControl control = changeControlFactory.validateFor(changeId);
+    final PatchSet patch = db.patchSets().get(patchSetId);
+    if (patch == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+    if (!patch.isDraft()) {
+      result.addError(new ReviewResult.Error(
+          ReviewResult.Error.Type.NOT_A_DRAFT));
+      return result;
+    }
+
+    if (!control.canDeleteDraft(db)) {
+      result.addError(new ReviewResult.Error(
+          ReviewResult.Error.Type.DELETE_NOT_PERMITTED));
+      return result;
+    }
+    final Change change = control.getChange();
+
+    try {
+      ChangeUtil.deleteOnlyDraftPatchSet(patch, change, gitManager, replication, db);
+    } catch (IOException e) {
+      result.addError(new ReviewResult.Error(
+          ReviewResult.Error.Type.GIT_ERROR, e.getMessage()));
+    }
+
+    List<PatchSet> restOfPatches = db.patchSets().byChange(changeId).toList();
+    if (restOfPatches.size() == 0) {
+      try {
+        ChangeUtil.deleteDraftChange(patchSetId, gitManager, replication, db);
+        result.setChangeId(null);
+      } catch (IOException e) {
+        result.addError(new ReviewResult.Error(
+            ReviewResult.Error.Type.GIT_ERROR, e.getMessage()));
+      }
+    } else {
+      PatchSet.Id highestId = null;
+      for (PatchSet ps : restOfPatches) {
+        if (highestId == null || ps.getPatchSetId() > highestId.get()) {
+          highestId = ps.getId();
+        }
+      }
+      if (change.currentPatchSetId().equals(patchSetId)) {
+        change.removeLastPatchSetId();
+        try {
+          change.setCurrentPatchSet(patchSetInfoFactory.get(db, change.currPatchSetId()));
+        } catch (PatchSetInfoNotAvailableException e) {
+          throw new NoSuchChangeException(changeId);
+        }
+        db.changes().update(Collections.singleton(change));
+      }
+    }
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 2bc6d5f..54445c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.account.PerformRenameGroup;
 import com.google.gerrit.server.account.VisibleGroups;
 import com.google.gerrit.server.changedetail.AbandonChange;
+import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.changedetail.RestoreChange;
 import com.google.gerrit.server.changedetail.Submit;
@@ -88,6 +89,7 @@
     factory(AddReviewer.Factory.class);
     factory(AddReviewerSender.Factory.class);
     factory(CreateChangeSender.Factory.class);
+    factory(DeleteDraftPatchSet.Factory.class);
     factory(PublishComments.Factory.class);
     factory(PublishDraft.Factory.class);
     factory(ReplacePatchSetSender.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 26e9a98..073372c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -189,16 +189,14 @@
     ;
   }
 
-  /** Can this user publish this change? */
+  /** Can this user publish this draft change or any draft patch set of this change? */
   public boolean canPublish(final ReviewDb db) throws OrmException {
-    return change.getStatus() == Change.Status.DRAFT && isOwner()
-        && isVisible(db);
+    return isOwner() && isVisible(db);
   }
 
-  /** Can this user delete this change? */
-  public boolean canDelete(final ReviewDb db) throws OrmException {
-    return change.getStatus() == Change.Status.DRAFT && isOwner()
-        && isVisible(db);
+  /** Can this user delete this draft change or any draft patch set of this change? */
+  public boolean canDeleteDraft(final ReviewDb db) throws OrmException {
+    return isOwner() && isVisible(db);
   }
 
   /** Can this user restore this change? */
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 634f8ea..3313162 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -198,8 +198,8 @@
 %%
 legacy_submit_rule('MaxWithBlock', Label, Id, Min, Max, T) :- !, max_with_block(Label, Min, Max, T).
 legacy_submit_rule('MaxNoBlock', Label, Id, Min, Max, T) :- !, max_no_block(Label, Max, T).
-legacy_submit_rule('NoBlock', Label, Id, Min, Max, T) :- T = ok(_), true.
-legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- T = ok(_), true.
+legacy_submit_rule('NoBlock', Label, Id, Min, Max, T) :- !, T = ok(_).
+legacy_submit_rule('NoOp', Label, Id, Min, Max, T) :- !, T = ok(_).
 legacy_submit_rule(Fun, Label, Id, Min, Max, T) :- T = impossible(unsupported(Fun)).
 
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ProjectNode.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ProjectNode.java
index 47d9512..8e12571 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ProjectNode.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ProjectNode.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.reviewdb.Project;
 import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.sshd.commands.TreeFormatter.TreeNode;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
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 81f760f..8eb6058 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
@@ -24,17 +24,13 @@
 import com.google.gerrit.reviewdb.PatchSetApproval;
 import com.google.gerrit.reviewdb.RevId;
 import com.google.gerrit.reviewdb.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.changedetail.AbandonChange;
+import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
 import com.google.gerrit.server.changedetail.PublishDraft;
 import com.google.gerrit.server.changedetail.RestoreChange;
 import com.google.gerrit.server.changedetail.Submit;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.ReplicationQueue;
 import com.google.gerrit.server.mail.EmailException;
-import com.google.gerrit.server.patch.PatchSetInfoFactory;
-import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.patch.PublishComments;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
@@ -124,6 +120,9 @@
   private ChangeControl.Factory changeControlFactory;
 
   @Inject
+  private DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory;
+
+  @Inject
   private AbandonChange.Factory abandonChangeFactory;
 
   @Inject
@@ -139,17 +138,8 @@
   private RestoreChange.Factory restoreChangeFactory;
 
   @Inject
-  private GitRepositoryManager gitManager;
-
-  @Inject
-  private ReplicationQueue replication;
-
-  @Inject
   private Submit.Factory submitFactory;
 
-  @Inject
-  private PatchSetInfoFactory patchSetInfoFactory;
-
   private List<ApproveOption> optionList;
 
   @Override
@@ -192,6 +182,9 @@
           } catch (UnloggedFailure e) {
             ok = false;
             writeError("error: " + e.getMessage() + "\n");
+          } catch (NoSuchChangeException e) {
+            ok = false;
+            writeError("no such change " + patchSetId.getParentKey().get());
           } catch (Exception e) {
             ok = false;
             writeError("fatal: internal server error while approving "
@@ -233,16 +226,16 @@
       publishCommentsFactory.create(patchSetId, changeComment, aps, forceMessage).call();
 
       if (abandonChange) {
-        ReviewResult result = abandonChangeFactory.create(
+        final ReviewResult result = abandonChangeFactory.create(
             patchSetId, changeComment).call();
         handleReviewResultErrors(result);
       } else if (restoreChange) {
-        ReviewResult result = restoreChangeFactory.create(
+        final ReviewResult result = restoreChangeFactory.create(
             patchSetId, changeComment).call();
         handleReviewResultErrors(result);
       }
       if (submitChange) {
-        ReviewResult result = submitFactory.create(patchSetId).call();
+        final ReviewResult result = submitFactory.create(patchSetId).call();
         handleReviewResultErrors(result);
       }
     } catch (InvalidChangeOperationException e) {
@@ -252,20 +245,12 @@
     }
 
     if (publishPatchSet) {
-      ReviewResult result = publishDraftFactory.create(patchSetId).call();
+      final ReviewResult result = publishDraftFactory.create(patchSetId).call();
       handleReviewResultErrors(result);
     } else if (deleteDraftPatchSet) {
-      if (changeControl.canDelete(db)) {
-        try {
-          ChangeUtil.deleteDraftPatchSet(patchSetId, gitManager, replication, patchSetInfoFactory, db);
-        } catch (PatchSetInfoNotAvailableException e) {
-          throw error("Error retrieving draft patchset: " + patchSetId);
-        } catch (IOException e) {
-          throw error("Error deleting draft patchset: " + patchSetId);
-        }
-      } else {
-        throw error("Not permitted to delete draft patchset");
-      }
+      final ReviewResult result =
+          deleteDraftPatchSetFactory.create(patchSetId).call();
+      handleReviewResultErrors(result);
     }
   }
 
@@ -291,12 +276,18 @@
         case PUBLISH_NOT_PERMITTED:
           errMsg += "not permitted to publish change";
           break;
+        case DELETE_NOT_PERMITTED:
+          errMsg += "not permitted to delete change/patch set";
+          break;
         case RULE_ERROR:
           errMsg += "rule error";
           break;
         case NOT_A_DRAFT:
           errMsg += "change is not a draft";
           break;
+        case GIT_ERROR:
+          errMsg += "error writing change to git repository";
+          break;
         default:
           errMsg += "failure in review";
       }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeFormatter.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeFormatter.java
index f8b518e..3aa83c3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeFormatter.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeFormatter.java
@@ -19,6 +19,12 @@
 
 public class TreeFormatter {
 
+  public static interface TreeNode {
+    public String getDisplayName();
+    public boolean isVisible();
+    public SortedSet<? extends TreeNode> getChildren();
+  }
+
   public static final String NOT_VISIBLE_NODE = "(x)";
 
   private static final String NODE_PREFIX = "|-- ";
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeNode.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeNode.java
deleted file mode 100644
index 8b7d3a9..0000000
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TreeNode.java
+++ /dev/null
@@ -1,23 +0,0 @@
-// Copyright (C) 2011 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.sshd.commands;
-
-import java.util.SortedSet;
-
-public interface TreeNode {
-  public String getDisplayName();
-  public boolean isVisible();
-  public SortedSet<? extends TreeNode> getChildren();
-}
diff --git a/pom.xml b/pom.xml
index cbe2dc1..98da773 100644
--- a/pom.xml
+++ b/pom.xml
@@ -47,7 +47,7 @@
 
   <properties>
     <jgitVersion>1.1.0.201109151100-r.141-gcd958ba</jgitVersion>
-    <gwtormVersion>1.2-SNAPSHOT</gwtormVersion>
+    <gwtormVersion>1.2</gwtormVersion>
     <gwtjsonrpcVersion>1.2.5</gwtjsonrpcVersion>
     <gwtexpuiVersion>1.2.5</gwtexpuiVersion>
     <gwtVersion>2.3.0</gwtVersion>