Merge changes I5a7749fc,Ia8703c0d

* changes:
  Do not fail with ISE when an invalid base is specified on push
  Fix GetRelated if multiple changes exist for the same commit
diff --git a/java/com/google/gerrit/server/args4j/ObjectIdHandler.java b/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
index aa8a958..3cad7ce 100644
--- a/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
+++ b/java/com/google/gerrit/server/args4j/ObjectIdHandler.java
@@ -16,9 +16,11 @@
 
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.kohsuke.args4j.CmdLineException;
 import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.NamedOptionDef;
 import org.kohsuke.args4j.OptionDef;
 import org.kohsuke.args4j.spi.OptionHandler;
 import org.kohsuke.args4j.spi.Parameters;
@@ -37,7 +39,14 @@
   @Override
   public int parseArguments(Parameters params) throws CmdLineException {
     final String n = params.getParameter(0);
-    setter.addValue(ObjectId.fromString(n));
+    try {
+      setter.addValue(ObjectId.fromString(n));
+    } catch (InvalidObjectIdException e) {
+      throw new CmdLineException(
+          owner,
+          String.format("expected SHA1 for option %s: %s", ((NamedOptionDef) option).name(), n),
+          e);
+    }
     return 1;
   }
 
diff --git a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
index b1f9726..194a4f0 100644
--- a/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
+++ b/java/com/google/gerrit/server/change/GetRelatedChangesUtil.java
@@ -72,8 +72,8 @@
     }
 
     List<ChangeData> cds =
-        InternalChangeQuery.byProjectGroups(
-            queryProvider, indexConfig, changeData.project(), groups);
+        InternalChangeQuery.byBranchGroups(
+            queryProvider, indexConfig, changeData.change().getDest(), groups);
     if (cds.isEmpty()) {
       return Collections.emptyList();
     }
diff --git a/java/com/google/gerrit/server/events/EventFactory.java b/java/com/google/gerrit/server/events/EventFactory.java
index 60e30bc..cd7e29a 100644
--- a/java/com/google/gerrit/server/events/EventFactory.java
+++ b/java/com/google/gerrit/server/events/EventFactory.java
@@ -295,8 +295,8 @@
     // Find changes in the same related group as this patch set, having a patch
     // set whose parent matches this patch set's revision.
     for (ChangeData cd :
-        InternalChangeQuery.byProjectGroups(
-            queryProvider, indexConfig, change.getProject(), currentPs.groups())) {
+        InternalChangeQuery.byBranchGroups(
+            queryProvider, indexConfig, change.getDest(), currentPs.groups())) {
       PATCH_SETS:
       for (PatchSet ps : cd.patchSets()) {
         RevCommit commit = rw.parseCommit(ps.commitId());
diff --git a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 99c1ca1..ccd645b 100644
--- a/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -271,21 +271,21 @@
     return query(ChangePredicates.submissionId(cs));
   }
 
-  private static Predicate<ChangeData> byProjectGroupsPredicate(
-      IndexConfig indexConfig, Project.NameKey project, Collection<String> groups) {
-    int n = indexConfig.maxTerms() - 1;
+  private static Predicate<ChangeData> byBranchGroupsPredicate(
+      IndexConfig indexConfig, BranchNameKey branchAndProject, Collection<String> groups) {
+    int n = indexConfig.maxTerms() - 2;
     checkArgument(groups.size() <= n, "cannot exceed %s groups", n);
     List<GroupPredicate> groupPredicates = new ArrayList<>(groups.size());
     for (String g : groups) {
       groupPredicates.add(new GroupPredicate(g));
     }
-    return and(project(project), or(groupPredicates));
+    return and(project(branchAndProject.project()), ref(branchAndProject), or(groupPredicates));
   }
 
-  public static ImmutableList<ChangeData> byProjectGroups(
+  public static ImmutableList<ChangeData> byBranchGroups(
       Provider<InternalChangeQuery> queryProvider,
       IndexConfig indexConfig,
-      Project.NameKey project,
+      BranchNameKey branchAndProject,
       Collection<String> groups) {
     // These queries may be complex along multiple dimensions:
     //  * Many groups per change, if there are very many patch sets. This requires partitioning the
@@ -296,16 +296,17 @@
     // InternalChangeQuery is single-use.
 
     Supplier<InternalChangeQuery> querySupplier = () -> queryProvider.get().enforceVisibility(true);
-    int batchSize = indexConfig.maxTerms() - 1;
+    int batchSize = indexConfig.maxTerms() - 2;
     if (groups.size() <= batchSize) {
       return queryExhaustively(
-          querySupplier, byProjectGroupsPredicate(indexConfig, project, groups));
+          querySupplier, byBranchGroupsPredicate(indexConfig, branchAndProject, groups));
     }
     Set<Change.Id> seen = new HashSet<>();
     ImmutableList.Builder<ChangeData> result = ImmutableList.builder();
     for (List<String> part : Iterables.partition(groups, batchSize)) {
       for (ChangeData cd :
-          queryExhaustively(querySupplier, byProjectGroupsPredicate(indexConfig, project, part))) {
+          queryExhaustively(
+              querySupplier, byBranchGroupsPredicate(indexConfig, branchAndProject, part))) {
         if (!seen.add(cd.getId())) {
           result.add(cd);
         }
diff --git a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 50bb8ae..b738324 100644
--- a/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/javatests/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -3082,6 +3082,12 @@
     assertThat(r.getChange().attentionSet()).isEmpty();
   }
 
+  @Test
+  public void pushWithInvalidBaseIsRejected() throws Exception {
+    PushOneCommit.Result r = pushTo("refs/for/master%base=invalid");
+    r.assertErrorStatus("expected SHA1 for option --base: invalid");
+  }
+
   private DraftInput newDraft(String path, int line, String message) {
     DraftInput d = new DraftInput();
     d.path = path;
diff --git a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 70b5701..21db45c 100644
--- a/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/javatests/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -39,11 +39,13 @@
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.entities.Account;
 import com.google.gerrit.entities.AccountGroup;
+import com.google.gerrit.entities.BranchNameKey;
 import com.google.gerrit.entities.Change;
 import com.google.gerrit.entities.PatchSet;
 import com.google.gerrit.extensions.api.changes.GetRelatedOption;
 import com.google.gerrit.extensions.api.changes.RelatedChangeAndCommitInfo;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.index.IndexConfig;
@@ -67,6 +69,8 @@
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
 import org.junit.Test;
 
 @NoHttpd
@@ -664,6 +668,71 @@
     }
   }
 
+  @Test
+  public void getRelatedLinearSameCommitPushedTwice() throws Exception {
+    RevCommit base = projectOperations.project(project).getHead("master");
+
+    // 1,1---2,1 on master
+    PushOneCommit.Result r1 =
+        createChange(
+            testRepo,
+            "master",
+            "subject: 1",
+            "a.txt",
+            "1",
+            /** topic= */
+            null);
+    RevCommit c1_1 = r1.getCommit();
+    PatchSet.Id ps1_1 = r1.getPatchSetId();
+
+    PushOneCommit.Result r2 =
+        createChange(
+            testRepo,
+            "master",
+            "subject: 2",
+            "b.txt",
+            "2",
+            /** topic= */
+            null);
+    RevCommit c2_1 = r2.getCommit();
+    PatchSet.Id ps2_1 = r2.getPatchSetId();
+
+    // 3,1---4,1 on stable
+    gApi.projects().name(project.get()).branch("stable").create(new BranchInput());
+    testRepo.reset(c1_1);
+    PushResult r3 = pushHead(testRepo, "refs/for/stable%base=" + base.getName());
+    assertThat(r3.getRemoteUpdate("refs/for/stable%base=" + base.getName()).getStatus())
+        .isEqualTo(RemoteRefUpdate.Status.OK);
+    ChangeData change3 =
+        Iterables.getOnlyElement(
+            queryProvider
+                .get()
+                .byBranchCommit(BranchNameKey.create(project, "stable"), c1_1.getName()));
+    assertThat(change3.currentPatchSet().commitId()).isEqualTo(c1_1);
+    RevCommit c3_1 = c1_1;
+    PatchSet.Id ps3_1 = change3.currentPatchSet().id();
+
+    PushOneCommit.Result r4 =
+        createChange(
+            testRepo,
+            "stable",
+            "subject: 4",
+            "d.txt",
+            "4",
+            /** topic= */
+            null);
+    RevCommit c4_1 = r4.getCommit();
+    PatchSet.Id ps4_1 = r4.getPatchSetId();
+
+    for (PatchSet.Id ps : ImmutableList.of(ps2_1, ps1_1)) {
+      assertRelated(ps, changeAndCommit(ps2_1, c2_1, 1), changeAndCommit(ps1_1, c1_1, 1));
+    }
+
+    for (PatchSet.Id ps : ImmutableList.of(ps4_1, ps3_1)) {
+      assertRelated(ps, changeAndCommit(ps4_1, c4_1, 1), changeAndCommit(ps3_1, c3_1, 1));
+    }
+  }
+
   private static Correspondence<RelatedChangeAndCommitInfo, String>
       getRelatedChangeToStatusCorrespondence() {
     return Correspondence.transforming(