Preserve negative approvals when replacing patch sets

Prior versions of Gerrit correctly saved a negative approval when
a replacement patch set was uploaded.  This was a specific feature
we added to allow project leads to reject a change until they are
satisfied with the replacement, even if other developers on that
project were able to review and approve subsequent patch sets.

Since approvals are now per-patch set we have to copy the approval
records from the prior patch set to this one, if the value is < 0.

Signed-off-by: Shawn O. Pearce <sop@google.com>
diff --git a/src/main/java/com/google/gerrit/client/reviewdb/PatchSetApproval.java b/src/main/java/com/google/gerrit/client/reviewdb/PatchSetApproval.java
index b99f591..75a4660 100644
--- a/src/main/java/com/google/gerrit/client/reviewdb/PatchSetApproval.java
+++ b/src/main/java/com/google/gerrit/client/reviewdb/PatchSetApproval.java
@@ -98,6 +98,14 @@
     setGranted();
   }
 
+  public PatchSetApproval(final PatchSet.Id psId, final PatchSetApproval src) {
+    key =
+        new PatchSetApproval.Key(psId, src.getAccountId(), src.getCategoryId());
+    changeOpen = true;
+    value = src.getValue();
+    granted = src.granted;
+  }
+
   public PatchSetApproval.Key getKey() {
     return key;
   }
diff --git a/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java b/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
index b52117c..a62b49c 100644
--- a/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
+++ b/src/main/java/com/google/gerrit/server/ssh/commands/Receive.java
@@ -810,6 +810,7 @@
           change = changeCache.get(changeId);
         }
 
+        final PatchSet.Id priorPatchSet = change.currentPatchSetId();
         final HashSet<ObjectId> existingRevisions = new HashSet<ObjectId>();
         for (final PatchSet ps : db.patchSets().byChange(changeId)) {
           if (ps.getRevision() != null) {
@@ -893,6 +894,17 @@
             oldCC.add(a.getAccountId());
           }
 
+          if (a.getValue() < 0
+              && a.getPatchSetId().equals(priorPatchSet)) {
+            // If there was a negative vote on the prior patch set, carry it
+            // into this patch set.
+            //
+            db.patchSetApprovals()
+                .insert(
+                    Collections.singleton(new PatchSetApproval(ps.getId(), a)),
+                    txn);
+          }
+
           if (!haveAuthor && authorId != null
               && a.getAccountId().equals(authorId)) {
             haveAuthor = true;