Merge "Suppress starred changes deprecation warnings"
diff --git a/Documentation/cmd-index-activate.txt b/Documentation/cmd-index-activate.txt
index 37783ef..6cb7781 100644
--- a/Documentation/cmd-index-activate.txt
+++ b/Documentation/cmd-index-activate.txt
@@ -5,7 +5,7 @@
 
 == SYNOPSIS
 --
-'ssh' -p @SSH_PORT@ @SSH_HOST@ 'gerrit index activate <index>'
+'ssh' -p <port> <host> 'gerrit index activate <index>'
 --
 
 == DESCRIPTION
diff --git a/Documentation/cmd-index-start.txt b/Documentation/cmd-index-start.txt
index cee283e..0a481e5 100644
--- a/Documentation/cmd-index-start.txt
+++ b/Documentation/cmd-index-start.txt
@@ -5,7 +5,7 @@
 
 == SYNOPSIS
 --
-'ssh' -p @SSH_PORT@ @SSH_HOST@ 'gerrit index start <index>'
+'ssh' -p <port> <host> 'gerrit index start <index>'
 --
 
 == DESCRIPTION
diff --git a/Documentation/cmd-query.txt b/Documentation/cmd-query.txt
index 0ff59d4..090781b 100644
--- a/Documentation/cmd-query.txt
+++ b/Documentation/cmd-query.txt
@@ -54,15 +54,17 @@
 
 --current-patch-set::
 	Include information about the current patch set in the results.
+	Note that the information will only be included when the current
+	patch set is visible to the caller.
 
 --patch-sets::
-	Include information about all patch sets.  If combined with
-	the --current-patch-set flag then the current patch set
-	information will be output twice, once in each field.
+	Include information about all patch sets visible to the caller.
+        If combined with the --current-patch-set flag then the current patch
+	set information will be output twice, once in each field.
 
 --all-approvals::
-	Include information about all patch sets along with the
-	approval information for each patch set.  If combined with
+	Include information about all patch sets visible to the caller along
+	with the approval information for each patch set.  If combined with
 	the --current-patch-set flag then the current patch set
 	information will be output twice, once in each field.
 
@@ -76,7 +78,7 @@
 --comments::
 	Include comments for all changes. If combined with the
 	--patch-sets flag then all inline/file comments are included for
-	each patch set.
+	each patch set that is visible to the caller.
 
 --commit-message::
 	Include the full commit message in the change description.
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index aab8aa0..715d589 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -13,6 +13,7 @@
 	[--list-plugins]
 	[--install-plugin=<PLUGIN_NAME>]
 	[--install-all-plugins]
+	[--secure-store-lib]
 	[--dev]
 	[--skip-all-downloads]
 	[--skip-download=<LIBRARY_NAME>]
@@ -63,6 +64,12 @@
 	This option also works in batch mode. This option cannot be supplied
 	alongside --install-plugin.
 
+--secure-store-lib::
+	Path to the jar providing the chosen
+	link:dev-plugins.html#secure-store[SecureStore] implementation class.
+	This option is used the same way as the --new-secure-store-lib option
+	documented in link:pgm-SwitchSecureStore.html[SwitchSecureStore].
+
 --install-plugin::
 	Automatically install plugin with given name without asking.
 	This option also works in batch mode. This option may be supplied
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index fd49487..1955c39 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -496,6 +496,11 @@
     revision(r).submit();
   }
 
+  protected PushOneCommit.Result amendChangeAsDraft(String changeId)
+      throws Exception {
+    return amendChange(changeId, "refs/drafts/master");
+  }
+
   protected ChangeInfo info(String id)
       throws RestApiException {
     return gApi.changes().id(id).info();
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index cbaf789..bc206b4 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
+import com.google.gerrit.extensions.api.projects.BranchInput;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
@@ -46,12 +47,14 @@
 import com.google.gerrit.testutil.TestTimeUtil;
 
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.transport.PushResult;
 import org.junit.AfterClass;
 import org.junit.Before;
 import org.junit.BeforeClass;
 import org.junit.Test;
 
 import java.util.Collection;
+import java.util.List;
 import java.util.Set;
 
 public abstract class AbstractPushForReview extends AbstractDaemonTest {
@@ -488,4 +491,37 @@
     r.assertErrorStatus(
         "not Signed-off-by author/committer/uploader in commit message footer");
   }
+
+  @Test
+  public void testPushSameCommitTwiceUsingMagicBranchBaseOption()
+      throws Exception {
+    grant(Permission.PUSH, project, "refs/heads/master");
+    PushOneCommit.Result rBase = pushTo("refs/heads/master");
+    rBase.assertOkStatus();
+
+    gApi.projects()
+        .name(project.get())
+        .branch("foo")
+        .create(new BranchInput());
+
+    PushOneCommit push =
+        pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
+            "b.txt", "anotherContent");
+
+    PushOneCommit.Result r = push.to("refs/for/master");
+    r.assertOkStatus();
+
+    PushResult pr = GitUtil.pushHead(
+        testRepo, "refs/for/foo%base=" + rBase.getCommit().name(), false, false);
+    assertThat(pr.getMessages()).contains("changes: new: 1, refs: 1, done");
+
+    List<ChangeInfo> changes = query(r.getCommit().name());
+    assertThat(changes).hasSize(2);
+    ChangeInfo c1 = get(changes.get(0).id);
+    ChangeInfo c2 = get(changes.get(1).id);
+    assertThat(c1.project).isEqualTo(c2.project);
+    assertThat(c1.branch).isNotEqualTo(c2.branch);
+    assertThat(c1.changeId).isEqualTo(c2.changeId);
+    assertThat(c1.currentRevision).isEqualTo(c2.currentRevision);
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index 24d0e097..a6ea4d2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -28,10 +28,11 @@
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.config.GetServerInfo.ServerInfo;
 
-import java.nio.file.Path;
-import java.nio.file.Files;
 import org.junit.Test;
 
+import java.nio.file.Files;
+import java.nio.file.Path;
+
 public class ServerInfoIT extends AbstractDaemonTest {
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 057d902..9a5dfeb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -14,6 +14,7 @@
 package com.google.gerrit.acceptance.rest.project;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/QueryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/QueryIT.java
index 3733cd1..2865ff87 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/QueryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/QueryIT.java
@@ -16,11 +16,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assert_;
+import static com.google.gerrit.acceptance.GitUtil.initSsh;
 
 import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.SshSession;
 import com.google.gerrit.extensions.api.changes.AddReviewerInput;
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.client.Side;
@@ -300,13 +302,39 @@
     assertThat(changes.get(0).submitRecords.size()).isEqualTo(1);
   }
 
+  @Test
+  public void testQueryWithNonVisibleCurrentPatchSet() throws Exception {
+    String changeId = createChange().getChangeId();
+    amendChangeAsDraft(changeId);
+    String query = "--current-patch-set --patch-sets " + changeId;
+    List<ChangeAttribute> changes = executeSuccessfulQuery(query);
+    assertThat(changes.size()).isEqualTo(1);
+    assertThat(changes.get(0).patchSets).isNotNull();
+    assertThat(changes.get(0).patchSets).hasSize(2);
+    assertThat(changes.get(0).currentPatchSet).isNotNull();
+
+    SshSession userSession = new SshSession(server, user);
+    initSsh(user);
+    userSession.open();
+    changes = executeSuccessfulQuery(query, userSession);
+    assertThat(changes.size()).isEqualTo(1);
+    assertThat(changes.get(0).patchSets).hasSize(1);
+    assertThat(changes.get(0).currentPatchSet).isNull();
+    userSession.close();
+  }
+
+  private List<ChangeAttribute> executeSuccessfulQuery(String params,
+      SshSession session) throws Exception {
+    String rawResponse =
+        session.exec("gerrit query --format=JSON " + params);
+    assert_().withFailureMessage(session.getError())
+        .that(session.hasError()).isFalse();
+    return getChanges(rawResponse);
+  }
+
   private List<ChangeAttribute> executeSuccessfulQuery(String params)
       throws Exception {
-    String rawResponse =
-        adminSshSession.exec("gerrit query --format=JSON " + params);
-    assert_().withFailureMessage(adminSshSession.getError())
-        .that(adminSshSession.hasError()).isFalse();
-    return getChanges(rawResponse);
+    return executeSuccessfulQuery(params, adminSshSession);
   }
 
   private static List<ChangeAttribute> getChanges(String rawResponse) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
index b499530..e29048a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ReplyBox.java
@@ -46,6 +46,8 @@
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.KeyDownEvent;
 import com.google.gwt.event.dom.client.KeyDownHandler;
+import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.event.dom.client.MouseOutEvent;
 import com.google.gwt.event.dom.client.MouseOutHandler;
 import com.google.gwt.event.dom.client.MouseOverEvent;
@@ -139,6 +141,14 @@
         }
       },
       KeyDownEvent.getType());
+    addDomHandler(
+      new KeyPressHandler() {
+        @Override
+        public void onKeyPress(KeyPressEvent e) {
+          e.stopPropagation();
+        }
+      },
+      KeyPressEvent.getType());
   }
 
   @Override
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
index d2b740e..39b85cf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/PatchSetSelectBox.java
@@ -42,6 +42,7 @@
 import com.google.gwt.user.client.ui.ImageResourceRenderer;
 import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtorm.client.KeyUtil;
+
 import net.codemirror.lib.CodeMirror;
 
 import java.util.List;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index 0e2f811..b8bd905 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -16,10 +16,10 @@
 
 import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
 
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
 import com.google.gerrit.extensions.api.projects.BranchApi;
 import com.google.gerrit.extensions.api.projects.BranchInfo;
 import com.google.gerrit.extensions.api.projects.ChildProjectApi;
-import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
 import com.google.gerrit.extensions.api.projects.ProjectApi;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.api.projects.PutDescriptionInput;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index efa6174..7984c76b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1503,8 +1503,8 @@
   private void selectNewAndReplacedChangesFromMagicBranch() {
     newChanges = Lists.newArrayList();
 
-    SetMultimap<ObjectId, Ref> existing = changeRefsById();
-    GroupCollector groupCollector = GroupCollector.create(refsById, db, psUtil,
+    SetMultimap<ObjectId, Ref> existing = HashMultimap.create();
+    GroupCollector groupCollector = GroupCollector.create(changeRefsById(), db, psUtil,
         notesFactory, project.getNameKey());
 
     rp.getRevWalk().reset();
@@ -1525,6 +1525,7 @@
       } else {
         markHeadsAsUninteresting(
             rp.getRevWalk(),
+            existing,
             magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null);
       }
 
@@ -1681,15 +1682,23 @@
     }
   }
 
-  private void markHeadsAsUninteresting(RevWalk rw, @Nullable String forRef) {
+  private void markHeadsAsUninteresting(
+      final RevWalk walk,
+      SetMultimap<ObjectId, Ref> existing,
+      @Nullable String forRef) {
     for (Ref ref : allRefs.values()) {
-      if ((ref.getName().startsWith(R_HEADS) || ref.getName().equals(forRef))
-          && ref.getObjectId() != null) {
+      if (ref.getObjectId() == null) {
+        continue;
+      } else if (ref.getName().startsWith(REFS_CHANGES)) {
+        existing.put(ref.getObjectId(), ref);
+      } else if (ref.getName().startsWith(R_HEADS)
+          || (forRef != null && forRef.equals(ref.getName()))) {
         try {
-          rw.markUninteresting(rw.parseCommit(ref.getObjectId()));
+          walk.markUninteresting(walk.parseCommit(ref.getObjectId()));
         } catch (IOException e) {
           log.warn(String.format("Invalid ref %s in %s",
               ref.getName(), project.getName()), e);
+          continue;
         }
       }
     }
@@ -2332,11 +2341,11 @@
       if (!(parsedObject instanceof RevCommit)) {
         return;
       }
+      SetMultimap<ObjectId, Ref> existing = HashMultimap.create();
       walk.markStart((RevCommit)parsedObject);
-      markHeadsAsUninteresting(walk, cmd.getRefName());
-      Set<ObjectId> existing = changeRefsById().keySet();
+      markHeadsAsUninteresting(walk, existing, cmd.getRefName());
       for (RevCommit c; (c = walk.next()) != null;) {
-        if (existing.contains(c)) {
+        if (existing.keySet().contains(c)) {
           continue;
         } else if (!validCommit(walk, ctl, cmd, c)) {
           break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index ce34a53..1e3fdbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.git.strategy;
 
-import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
+import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index a4c10ee..76dd030 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -21,6 +21,8 @@
 import com.google.auto.value.AutoValue;
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Optional;
+import com.google.common.base.Predicate;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
@@ -833,13 +835,29 @@
     return patchSets;
   }
 
-  public void setPatchSets(List<PatchSet> patchSets) {
+  /**
+   * @return patches for the change visible to the current user.
+   * @throws OrmException an error occurred reading the database.
+   */
+  public Collection<PatchSet> visiblePatchSets() throws OrmException {
+    return FluentIterable.from(patchSets()).filter(new Predicate<PatchSet>() {
+      @Override
+      public boolean apply(PatchSet input) {
+        try {
+          return changeControl().isPatchVisible(input, db);
+        } catch (OrmException e) {
+          return false;
+        }
+      }}).toList();
+  }
+
+public void setPatchSets(Collection<PatchSet> patchSets) {
     this.currentPatchSet = null;
     this.patchSets = patchSets;
   }
 
   /**
-   * @return patch set with the given ID, or null if it does not exist.
+   * @return patch with the given ID, or null if it does not exist.
    * @throws OrmException an error occurred reading the database.
    */
   public PatchSet patchSet(PatchSet.Id psId) throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 83364c3..2e2454d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -141,10 +141,10 @@
     List<Predicate<ChangeData>> r =
         Lists.newArrayListWithCapacity(2 * MAX_LABEL_VALUE);
     for (int i = 1; i <= MAX_LABEL_VALUE; i++) {
-      r.add(not(equalsLabelPredicate(args, label, i)));
-      r.add(not(equalsLabelPredicate(args, label, -i)));
+      r.add(equalsLabelPredicate(args, label, i));
+      r.add(equalsLabelPredicate(args, label, -i));
     }
-    return and(r);
+    return not(or(r));
   }
 
   private static Predicate<ChangeData> equalsLabelPredicate(Args args,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
index 8f1e48f..00ecdb2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/OutputStreamQuery.java
@@ -275,14 +275,14 @@
     }
 
     if (includePatchSets) {
-      eventFactory.addPatchSets(db, rw, c, d.patchSets(),
+      eventFactory.addPatchSets(db, rw, c, d.visiblePatchSets(),
           includeApprovals ? d.approvals().asMap() : null,
           includeFiles, d.change(), labelTypes);
     }
 
     if (includeCurrentPatchSet) {
       PatchSet current = d.currentPatchSet();
-      if (current != null) {
+      if (current != null && cc.isPatchVisible(current, d.db())) {
         c.currentPatchSet =
             eventFactory.asPatchSetAttribute(db, rw, d.change(), current);
         eventFactory.addApprovals(c.currentPatchSet,
@@ -302,7 +302,7 @@
     if (includeComments) {
       eventFactory.addComments(c, d.messages());
       if (includePatchSets) {
-        eventFactory.addPatchSets(db, rw, c, d.patchSets(),
+        eventFactory.addPatchSets(db, rw, c, d.visiblePatchSets(),
             includeApprovals ? d.approvals().asMap() : null,
             includeFiles, d.change(), labelTypes);
         for (PatchSetAttribute attribute : c.patchSets) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index 6e2f9c9..97776f1 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -95,6 +95,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -536,47 +537,97 @@
   public void byLabel() throws Exception {
     accountManager.authenticate(AuthRequest.forUser("anotheruser"));
     TestRepository<Repo> repo = createProject("repo");
-    ChangeInserter ins = newChange(repo);
-    Change change = insert(repo, ins);
+    ChangeInserter ins = newChange(repo, null, null, null, null);
+    ChangeInserter ins2 = newChange(repo, null, null, null, null);
+    ChangeInserter ins3 = newChange(repo, null, null, null, null);
+    ChangeInserter ins4 = newChange(repo, null, null, null, null);
+    ChangeInserter ins5 = newChange(repo, null, null, null, null);
 
-    gApi.changes().id(change.getId().get()).current()
-      .review(new ReviewInput().label("Code-Review", 1));
+    Change reviewMinus2Change = insert(repo, ins);
+    gApi.changes().id(reviewMinus2Change.getId().get()).current()
+        .review(ReviewInput.reject());
+
+    Change reviewMinus1Change = insert(repo, ins2);
+    gApi.changes().id(reviewMinus1Change.getId().get()).current()
+        .review(ReviewInput.dislike());
+
+    Change noLabelChange = insert(repo, ins3);
+
+    Change reviewPlus1Change = insert(repo, ins4);
+    gApi.changes().id(reviewPlus1Change.getId().get()).current()
+        .review(ReviewInput.recommend());
+
+    Change reviewPlus2Change = insert(repo, ins5);
+    gApi.changes().id(reviewPlus2Change.getId().get()).current()
+        .review(ReviewInput.approve());
+
     Map<String, Short> m = gApi.changes()
-        .id(change.getId().get())
+        .id(reviewPlus1Change.getId().get())
         .reviewer(user.getAccountId().toString())
         .votes();
     assertThat(m).hasSize(1);
     assertThat(m).containsEntry("Code-Review", new Short((short)1));
 
-    assertQuery("label:Code-Review=-2");
-    assertQuery("label:Code-Review-2");
-    assertQuery("label:Code-Review=-1");
-    assertQuery("label:Code-Review-1");
-    assertQuery("label:Code-Review=0");
-    assertQuery("label:Code-Review=+1", change);
-    assertQuery("label:Code-Review=1", change);
-    assertQuery("label:Code-Review+1", change);
-    assertQuery("label:Code-Review=+2");
-    assertQuery("label:Code-Review=2");
-    assertQuery("label:Code-Review+2");
+    Map<Integer, Change> changes = new LinkedHashMap<>(5);
+    changes.put(2, reviewPlus2Change);
+    changes.put(1, reviewPlus1Change);
+    changes.put(0, noLabelChange);
+    changes.put(-1, reviewMinus1Change);
+    changes.put(-2, reviewMinus2Change);
 
-    assertQuery("label:Code-Review>=0", change);
-    assertQuery("label:Code-Review>0", change);
-    assertQuery("label:Code-Review>=1", change);
-    assertQuery("label:Code-Review>1");
-    assertQuery("label:Code-Review>=2");
+    assertQuery("label:Code-Review=-2", reviewMinus2Change);
+    assertQuery("label:Code-Review-2", reviewMinus2Change);
+    assertQuery("label:Code-Review=-1", reviewMinus1Change);
+    assertQuery("label:Code-Review-1", reviewMinus1Change);
+    assertQuery("label:Code-Review=0", noLabelChange);
+    assertQuery("label:Code-Review=+1", reviewPlus1Change);
+    assertQuery("label:Code-Review=1", reviewPlus1Change);
+    assertQuery("label:Code-Review+1", reviewPlus1Change);
+    assertQuery("label:Code-Review=+2", reviewPlus2Change);
+    assertQuery("label:Code-Review=2", reviewPlus2Change);
+    assertQuery("label:Code-Review+2", reviewPlus2Change);
 
-    assertQuery("label: Code-Review<=2", change);
-    assertQuery("label: Code-Review<2", change);
-    assertQuery("label: Code-Review<=1", change);
-    assertQuery("label:Code-Review<1");
-    assertQuery("label:Code-Review<=0");
+    assertQuery("label:Code-Review>-3", codeReviewInRange(changes, -2, 2));
+    assertQuery("label:Code-Review>=-2", codeReviewInRange(changes, -2, 2));
+    assertQuery("label:Code-Review>-2", codeReviewInRange(changes, -1, 2));
+    assertQuery("label:Code-Review>=-1", codeReviewInRange(changes, -1, 2));
+    assertQuery("label:Code-Review>-1", codeReviewInRange(changes, 0, 2));
+    assertQuery("label:Code-Review>=0", codeReviewInRange(changes, 0, 2));
+    assertQuery("label:Code-Review>0", codeReviewInRange(changes, 1, 2));
+    assertQuery("label:Code-Review>=1", codeReviewInRange(changes, 1, 2));
+    assertQuery("label:Code-Review>1", reviewPlus2Change);
+    assertQuery("label:Code-Review>=2", reviewPlus2Change);
+    assertQuery("label:Code-Review>2");
+
+    assertQuery("label:Code-Review<=2", codeReviewInRange(changes, -2, 2));
+    assertQuery("label:Code-Review<2", codeReviewInRange(changes, -2, 1));
+    assertQuery("label:Code-Review<=1", codeReviewInRange(changes, -2, 1));
+    assertQuery("label:Code-Review<1", codeReviewInRange(changes, -2, 0));
+    assertQuery("label:Code-Review<=0", codeReviewInRange(changes, -2, 0));
+    assertQuery("label:Code-Review<0", codeReviewInRange(changes, -2, -1));
+    assertQuery("label:Code-Review<=-1", codeReviewInRange(changes, -2, -1));
+    assertQuery("label:Code-Review<-1", reviewMinus2Change);
+    assertQuery("label:Code-Review<=-2", reviewMinus2Change);
+    assertQuery("label:Code-Review<-2");
 
     assertQuery("label:Code-Review=+1,anotheruser");
-    assertQuery("label:Code-Review=+1,user", change);
-    assertQuery("label:Code-Review=+1,user=user", change);
-    assertQuery("label:Code-Review=+1,Administrators", change);
-    assertQuery("label:Code-Review=+1,group=Administrators", change);
+    assertQuery("label:Code-Review=+1,user", reviewPlus1Change);
+    assertQuery("label:Code-Review=+1,user=user", reviewPlus1Change);
+    assertQuery("label:Code-Review=+1,Administrators", reviewPlus1Change);
+    assertQuery("label:Code-Review=+1,group=Administrators", reviewPlus1Change);
+  }
+
+  private Change[] codeReviewInRange(Map<Integer, Change> changes, int start,
+      int end) {
+    int size = 0;
+    Change[] range = new Change[end - start + 1];
+    for (int i : changes.keySet()) {
+      if (i >= start && i <= end) {
+        range[size] = changes.get(i);
+        size++;
+      }
+    }
+    return range;
   }
 
   private String createGroup(String name, String owner) throws Exception {
diff --git a/plugins/replication b/plugins/replication
index d5cd908..b80cd81 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit d5cd908c0d7938a5d253d49f68ed352bbc7449cf
+Subproject commit b80cd8168ae8ba065c0186b1ddfec366a6368cb6