Merge changes I468b1c67,I1f4e8185

* changes:
  Support multiple reports per patchset
  Update to master
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerificationInfo.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerificationInfo.java
index 9c3ed37..a3b3b17 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerificationInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/client/VerificationInfo.java
@@ -8,6 +8,7 @@
 public class VerificationInfo extends JavaScriptObject {
 
   public final native String url() /*-{ return this.url; }-*/;
+  public final native String name() /*-{ return this.name; }-*/;
   public final native String comment() /*-{ return this.comment; }-*/;
   public final native short value() /*-{ return this.value; }-*/;
   public final native boolean abstain() /*-{ return this.abstain || false; }-*/;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/commands/SaveCommand.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/commands/SaveCommand.java
index fc694fb..d07a1a3 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/commands/SaveCommand.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/commands/SaveCommand.java
@@ -19,14 +19,12 @@
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.Maps;
+import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.RevisionResource;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.change.Revisions;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.sshd.CommandMetaData;
 import com.google.gerrit.sshd.SshCommand;
@@ -94,12 +92,15 @@
       throw new IllegalArgumentException(String.valueOf("Invalid verification parameters"));
     }
 
-    String job = params.get("job");
-    checkArgument(job != null, "Verification is missing a job");
+    String name = params.get("name");
+    checkArgument(name != null, "Verification is missing a name");
+    checkArgument(!name.isEmpty(), "Verification is missing a name");
     String value = params.get("value");
     checkArgument(value != null, "Verification is missing a value");
+    checkArgument(!value.isEmpty(), "Verification is missing a value");
     String abstain = params.get("abstain");
     VerificationInfo data = new VerificationInfo();
+    data.name = name;
     data.value = Short.parseShort(value);
     data.abstain = Boolean.valueOf(abstain);
     data.url = params.get("url");
@@ -107,23 +108,20 @@
     data.comment = params.get("comment");
     data.category = params.get("category");
     data.duration = params.get("duration");
-    jobResult.put(job, data);
+    jobResult.put(name, data);
   }
 
   @Inject
-  private ReviewDb db;
-
-  @Inject
-  private IdentifiedUser currentUser;
-
-  @Inject
   private PostVerification postVerification;
 
   @Inject
-  private ChangeControl.GenericFactory genericFactory;
+  private PatchSetParser psParser;
 
   @Inject
-  private PatchSetParser psParser;
+  private Revisions revisions;
+
+  @Inject
+  private ChangesCollection changes;
 
   private Map<String, VerificationInfo> jobResult = Maps.newHashMap();
 
@@ -146,14 +144,11 @@
   }
 
   private void applyVerification(PatchSet patchSet, VerifyInput verify)
-      throws RestApiException, NoSuchChangeException, OrmException,
+      throws RestApiException, OrmException,
       IOException {
-    ChangeControl ctl =
-        genericFactory.validateFor(db, patchSet.getId().getParentKey(),
-            currentUser);
-    ChangeResource changeResource = new ChangeResource(ctl);
-    RevisionResource revResource = new RevisionResource(changeResource,
-        patchSet);
+    RevisionResource revResource = revisions.parse(
+        changes.parse(patchSet.getId().getParentKey()),
+        IdString.fromUrl(patchSet.getId().getId()));
     postVerification.apply(revResource, verify);
   }
 
@@ -162,7 +157,7 @@
     verify.verifications = jobResult;
     try {
       applyVerification(patchSet, verify);
-    } catch (RestApiException | NoSuchChangeException | OrmException
+    } catch (RestApiException | OrmException
         | IOException e) {
       throw PatchSetParser.error(e.getMessage());
     }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/common/VerificationInfo.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/common/VerificationInfo.java
index b9b563f..d280fa0 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/common/VerificationInfo.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/common/VerificationInfo.java
@@ -17,6 +17,7 @@
 import java.sql.Timestamp;
 
 public class VerificationInfo {
+  public String name;
   public String url;
   public Short value;
   public boolean abstain;
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/GetVerifications.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/GetVerifications.java
index 4c4d2ee..4c7029a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/GetVerifications.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/GetVerifications.java
@@ -47,6 +47,7 @@
         info.value = v.getValue();
         info.abstain = v.getAbstain();
         info.url = v.getUrl();
+        info.name = v.getName();
         info.reporter = v.getReporter();
         info.comment = v.getComment();
         info.granted = v.getGranted();
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PatchSetVerification.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PatchSetVerification.java
index b4b3f38..58f9735 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PatchSetVerification.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PatchSetVerification.java
@@ -85,6 +85,9 @@
   @Column(id = 9)
   protected boolean abstain;
 
+  @Column(id = 10, notNull = false, length = 255)
+  protected String name;
+
   protected PatchSetVerification() {
   }
 
@@ -143,6 +146,14 @@
     this.url = url;
   }
 
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
   public String getReporter() {
     return reporter;
   }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PostVerification.java b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PostVerification.java
index d10c3d0..0773edc 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PostVerification.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/verifystatus/server/PostVerification.java
@@ -40,6 +40,7 @@
 import java.sql.Timestamp;
 import java.util.List;
 import java.util.Map;
+import java.util.UUID;
 
 @Singleton
 public class PostVerification
@@ -88,12 +89,16 @@
     Timestamp ts = TimeUtil.nowTs();
     for (Map.Entry<String, VerificationInfo> ent : jobs.entrySet()) {
       String name = ent.getKey();
+      if (name == null) {
+        throw new BadRequestException("Missing name field");
+      }
       PatchSetVerification c = current.remove(name);
       Short value = ent.getValue().value;
       if (value == null) {
         throw new BadRequestException("Missing value field");
       }
       if (c != null) {
+        // update a result
         c.setGranted(ts);
         c.setValue(value);
         if (Boolean.TRUE.equals(ent.getValue().abstain)) {
@@ -103,6 +108,10 @@
         if (url != null) {
           c.setUrl(url);
         }
+        String job_name = c.getName();
+        if (job_name != null) {
+          c.setName(job_name);
+        }
         String reporter = ent.getValue().reporter;
         if (reporter != null) {
           c.setReporter(reporter);
@@ -123,12 +132,15 @@
             + c.getPatchSetId());
         ups.add(c);
       } else {
+        // add new result
+        String job_id = UUID.randomUUID().toString();
         c = new PatchSetVerification(new PatchSetVerification.Key(
                 resource.getPatchSet().getId(),
-                new LabelId(name)),
+                new LabelId(job_id)),
             value, ts);
         c.setAbstain(ent.getValue().abstain);
         c.setUrl(ent.getValue().url);
+        c.setName(name);
         c.setReporter(ent.getValue().reporter);
         c.setComment(ent.getValue().comment);
         c.setCategory(ent.getValue().category);
diff --git a/src/main/resources/Documentation/cmd-save.md b/src/main/resources/Documentation/cmd-save.md
index 6f9c974..e6fae9b 100644
--- a/src/main/resources/Documentation/cmd-save.md
+++ b/src/main/resources/Documentation/cmd-save.md
@@ -80,7 +80,7 @@
 
 
 >     $ ssh -p 29418 review.example.com @PLUGIN@ save --verification
->      "'job=gate-horizon-pep8
+>      "'name=gate-horizon-pep8
 >      |value=1
 >      |url=https://ci.host.com/jobs/pep8/4711
 >      |reporter=Jenkins CI
diff --git a/src/main/resources/Documentation/rest-api-changes.md b/src/main/resources/Documentation/rest-api-changes.md
index 74f416c..e10f965 100644
--- a/src/main/resources/Documentation/rest-api-changes.md
+++ b/src/main/resources/Documentation/rest-api-changes.md
@@ -35,7 +35,8 @@
 
   )]}'
   {
-    "gate-horizon-pep8": {
+    "5081c5e5-e101-43eb-8e59-4e197f22a0d0"": {
+      "name": "gate-horizon-pep8",
       "url": "https://ci.host.com/jobs/gate-horizon-pep8/2711",
       "value": -1,
       "reporter": "HPE CI",
@@ -44,7 +45,8 @@
       "duration": "3m 10s"
       "granted": "15 Mar 2016 08:10:41",
     },
-    "gate-horizon-python27": {
+    "2a359a73-31e7-4f81-b295-ae0e20615da6": {
+      "name": "gate-horizon-python27",
       "url": "https://ci.host.com/jobs/gate-horizon-python27/1711",
       "value": 1,
       "abstain": true,
@@ -54,7 +56,8 @@
       "duration": "7m 40s"
       "granted": "15 Mar 2016 08:30:16"
     }
-    "gate-horizon-python34": {
+    "807c8ece-0196-4ec4-b24f-ed035efa8e55": {
+      "name": "gate-horizon-python34",
       "url": "https://ci.host.com/jobs/gate-horizon-python34/9111",
       "value": 1,
       "reporter": "Drone CI",
@@ -71,8 +74,9 @@
 
 __POST__ /changes/{change-id}/revisions/{revision-id}/@PLUGIN@~verifications
 
-Posts a verification result to a patchset.  Results can be updated by posting
-with the same job name.
+Posts a verification result to a patchset. Each verification result is save as
+a unique entry in the database identified by a UUID.  Results can be updated by
+posting with the UUID.
 
 The verification must be provided in the request body as a
 [VerifyInput](#verify-input) entity.
@@ -160,6 +164,7 @@
 
 |Field Name |Description|
 |:----------|:----------|
+|name       |The name of this job|
 |value      |The pass/fail result for this job|
 |abstain    |Whether the value counts as a vote|
 |comment    |A short comment about this job|