Merge changes I36da735c,I89dde294

* changes:
  ChangeRebuilderImpl: Support deleting patch set 1 of a change
  DeleteDraftPatchSetIT: Convert to extension API
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/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index f46092d..da17806 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1953,6 +1953,9 @@
 If only the content type is required, callers should use HEAD to
 avoid downloading the encoded file contents.
 
+If the `base` parameter is set to true, the returned content is from the
+revision that the edit is based on.
+
 .Response
 ----
   HTTP/1.1 200 OK
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/api/accounts/EditPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
index df85d0f..dd5bcbb 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/accounts/EditPreferencesIT.java
@@ -43,6 +43,7 @@
     assertThat(out.hideLineNumbers).isNull();
     assertThat(out.matchBrackets).isTrue();
     assertThat(out.autoCloseBrackets).isNull();
+    assertThat(out.showBase).isNull();
     assertThat(out.theme).isEqualTo(Theme.DEFAULT);
     assertThat(out.keyMapType).isEqualTo(KeyMapType.DEFAULT);
 
@@ -58,6 +59,7 @@
     out.hideLineNumbers = true;
     out.matchBrackets = false;
     out.autoCloseBrackets = true;
+    out.showBase = true;
     out.theme = Theme.TWILIGHT;
     out.keyMapType = KeyMapType.EMACS;
 
@@ -92,6 +94,7 @@
     assertThat(out.hideLineNumbers).isEqualTo(in.hideLineNumbers);
     assertThat(out.matchBrackets).isNull();
     assertThat(out.autoCloseBrackets).isEqualTo(in.autoCloseBrackets);
+    assertThat(out.showBase).isEqualTo(in.showBase);
     assertThat(out.theme).isEqualTo(in.theme);
     assertThat(out.keyMapType).isEqualTo(in.keyMapType);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
index beedfef..b34951d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/edit/ChangeEditIT.java
@@ -602,6 +602,11 @@
     r.assertOK();
     assertThat(readContentFromJson(r)).isEqualTo(
         StringUtils.newStringUtf8(CONTENT_NEW2));
+
+    r = adminRestSession.getJsonAccept(urlEditFile(true));
+    r.assertOK();
+    assertThat(readContentFromJson(r)).isEqualTo(
+        StringUtils.newStringUtf8(CONTENT_OLD));
   }
 
   @Test
@@ -811,9 +816,14 @@
   }
 
   private String urlEditFile() {
+    return urlEditFile(false);
+  }
+
+  private String urlEditFile(boolean base) {
     return urlEdit()
         + "/"
-        + FILE_NAME;
+        + FILE_NAME
+        + (base ? "?base" : "");
   }
 
   private String urlGetFiles() {
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-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
index ab11612..1f7b84a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/EditPreferencesInfo.java
@@ -27,6 +27,7 @@
   public Boolean hideLineNumbers;
   public Boolean matchBrackets;
   public Boolean autoCloseBrackets;
+  public Boolean showBase;
   public Theme theme;
   public KeyMapType keyMapType;
 
@@ -43,6 +44,7 @@
     i.hideLineNumbers = false;
     i.matchBrackets = true;
     i.autoCloseBrackets = false;
+    i.showBase = false;
     i.theme = Theme.DEFAULT;
     i.keyMapType = KeyMapType.DEFAULT;
     return i;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java
index 8ed7f76..c710fe1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/EditPreferences.java
@@ -33,6 +33,7 @@
     p.hideLineNumbers(in.hideLineNumbers);
     p.matchBrackets(in.matchBrackets);
     p.autoCloseBrackets(in.autoCloseBrackets);
+    p.showBase(in.showBase);
     p.theme(in.theme);
     p.keyMapType(in.keyMapType);
     return p;
@@ -50,6 +51,7 @@
     p.hideLineNumbers = hideLineNumbers();
     p.matchBrackets = matchBrackets();
     p.autoCloseBrackets = autoCloseBrackets();
+    p.showBase = showBase();
     p.theme = theme();
     p.keyMapType = keyMapType();
     return p;
@@ -76,6 +78,7 @@
   public final native void hideLineNumbers(boolean s) /*-{ this.hide_line_numbers = s }-*/;
   public final native void matchBrackets(boolean m) /*-{ this.match_brackets = m }-*/;
   public final native void autoCloseBrackets(boolean c) /*-{ this.auto_close_brackets = c }-*/;
+  public final native void showBase(boolean s) /*-{ this.show_base = s }-*/;
 
   public final Theme theme() {
     String s = themeRaw();
@@ -112,6 +115,7 @@
   public final native boolean hideLineNumbers() /*-{ return this.hide_line_numbers || false }-*/;
   public final native boolean matchBrackets() /*-{ return this.match_brackets || false }-*/;
   public final native boolean autoCloseBrackets() /*-{ return this.auto_close_brackets || false }-*/;
+  public final native boolean showBase() /*-{ return this.show_base || false }-*/;
   private native int get(String n, int d) /*-{ return this.hasOwnProperty(n) ? this[n] : d }-*/;
 
   protected EditPreferences() {
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/changes/ChangeEditApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
index 0928cd8..f86ddf7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeEditApi.java
@@ -28,7 +28,7 @@
 /** REST API helpers to remotely edit a change. */
 public class ChangeEditApi {
   /** Get file (or commit message) contents. */
-  public static void get(PatchSet.Id id, String path,
+  public static void get(PatchSet.Id id, String path, boolean base,
       HttpCallback<NativeString> cb) {
     RestApi api;
     if (id.get() != 0) {
@@ -36,13 +36,19 @@
       // exist for the caller, or is not currently active.
       api = ChangeApi.revision(id).view("files").id(path).view("content");
     } else if (Patch.COMMIT_MSG.equals(path)) {
-      api = editMessage(id.getParentKey().get());
+      api = editMessage(id.getParentKey().get()).addParameter("base", base);
     } else {
-      api = editFile(id.getParentKey().get(), path);
+      api = editFile(id.getParentKey().get(), path).addParameter("base", base);
     }
     api.get(cb);
   }
 
+  /** Get file (or commit message) contents of the edit. */
+  public static void get(PatchSet.Id id, String path,
+      HttpCallback<NativeString> cb) {
+    get(id, path, false, cb);
+  }
+
   /** Get meta info for change edit. */
   public static void getMeta(PatchSet.Id id, String path,
       AsyncCallback<EditFileInfo> cb) {
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-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java
index 4ca4a63..0a27ea1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.java
@@ -69,6 +69,7 @@
   @UiField ToggleButton lineNumbers;
   @UiField ToggleButton matchBrackets;
   @UiField ToggleButton autoCloseBrackets;
+  @UiField ToggleButton showBase;
   @UiField ListBox theme;
   @UiField ListBox keyMap;
   @UiField Button apply;
@@ -104,6 +105,7 @@
     lineNumbers.setValue(prefs.hideLineNumbers());
     matchBrackets.setValue(prefs.matchBrackets());
     autoCloseBrackets.setValue(prefs.autoCloseBrackets());
+    showBase.setValue(prefs.showBase());
     setTheme(prefs.theme());
     setKeyMapType(prefs.keyMapType());
   }
@@ -114,7 +116,7 @@
     if (v != null && v.length() > 0) {
       prefs.tabSize(Math.max(1, Integer.parseInt(v)));
       if (view != null) {
-        view.getEditor().setOption("tabSize", v);
+        view.setOption("tabSize", v);
       }
     }
   }
@@ -149,7 +151,7 @@
       // don't let user shoot himself in the foot.
       prefs.cursorBlinkRate(Math.max(0, Integer.parseInt(v)));
       if (view != null) {
-        view.getEditor().setOption("cursorBlinkRate", prefs.cursorBlinkRate());
+        view.setOption("cursorBlinkRate", prefs.cursorBlinkRate());
       }
     }
   }
@@ -159,7 +161,7 @@
     prefs.hideTopMenu(!e.getValue());
     if (view != null) {
       Gerrit.setHeaderVisible(!prefs.hideTopMenu());
-      view.resizeCodeMirror();
+      view.adjustHeight();
     }
   }
 
@@ -199,7 +201,7 @@
   void onMatchBrackets(ValueChangeEvent<Boolean> e) {
     prefs.matchBrackets(e.getValue());
     if (view != null) {
-      view.getEditor().setOption("matchBrackets", prefs.matchBrackets());
+      view.setOption("matchBrackets", prefs.matchBrackets());
     }
   }
 
@@ -211,6 +213,15 @@
     }
   }
 
+  @UiHandler("showBase")
+  void onShowBase(ValueChangeEvent<Boolean> e) {
+    Boolean value = e.getValue();
+    prefs.showBase(value);
+    if (view != null) {
+      view.showBase.setValue(value, true);
+    }
+  }
+
   @UiHandler("theme")
   void onTheme(@SuppressWarnings("unused") ChangeEvent e) {
     final Theme newTheme = Theme.valueOf(theme.getValue(theme.getSelectedIndex()));
@@ -219,13 +230,7 @@
       ThemeLoader.loadTheme(newTheme, new GerritCallback<Void>() {
         @Override
         public void onSuccess(Void result) {
-          view.getEditor().operation(new Runnable() {
-            @Override
-            public void run() {
-              String t = newTheme.name().toLowerCase();
-              view.getEditor().setOption("theme", t);
-            }
-          });
+          view.setTheme(newTheme);
         }
       });
     }
@@ -237,7 +242,7 @@
         keyMap.getValue(keyMap.getSelectedIndex()));
     prefs.keyMapType(keyMapType);
     if (view != null) {
-      view.getEditor().setOption("keyMap", keyMapType.name().toLowerCase());
+      view.setOption("keyMap", keyMapType.name().toLowerCase());
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml
index c07ac56..b5b0b01 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditPreferencesBox.ui.xml
@@ -245,6 +245,13 @@
         </g:ToggleButton></td>
       </tr>
       <tr>
+        <th><ui:msg>Show Base Version</ui:msg></th>
+        <td><g:ToggleButton ui:field='showBase'>
+          <g:upFace><ui:msg>Hide</ui:msg></g:upFace>
+          <g:downFace><ui:msg>Show</ui:msg></g:downFace>
+        </g:ToggleButton></td>
+      </tr>
+      <tr>
         <td></td>
         <td>
           <g:Button ui:field='apply' styleName='{style.apply}'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index 67df4c6..b7adf3b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -43,6 +43,7 @@
 import com.google.gerrit.client.ui.Screen;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.extensions.client.KeyMapType;
+import com.google.gerrit.extensions.client.Theme;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gwt.core.client.GWT;
@@ -50,11 +51,14 @@
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.Style.Unit;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.KeyPressEvent;
 import com.google.gwt.event.logical.shared.ResizeEvent;
 import com.google.gwt.event.logical.shared.ResizeHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
 import com.google.gwt.event.shared.HandlerRegistration;
+import com.google.gwt.resources.client.CssResource;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
@@ -63,17 +67,21 @@
 import com.google.gwt.user.client.Window.ClosingHandler;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLPanel;
 import com.google.gwt.user.client.ui.ImageResourceRenderer;
 import com.google.gwtexpui.globalkey.client.GlobalKey;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 
+import net.codemirror.addon.AddonInjector;
+import net.codemirror.addon.Addons;
 import net.codemirror.lib.CodeMirror;
 import net.codemirror.lib.CodeMirror.ChangesHandler;
 import net.codemirror.lib.CodeMirror.CommandRunner;
 import net.codemirror.lib.Configuration;
 import net.codemirror.lib.KeyMap;
+import net.codemirror.lib.MergeView;
 import net.codemirror.lib.Pos;
 import net.codemirror.mode.ModeInfo;
 import net.codemirror.mode.ModeInjector;
@@ -85,14 +93,23 @@
   interface Binder extends UiBinder<HTMLPanel, EditScreen> {}
   private static final Binder uiBinder = GWT.create(Binder.class);
 
+  interface Style extends CssResource {
+    String fullWidth();
+    String base();
+    String hideBase();
+  }
+
   private final PatchSet.Id base;
   private final PatchSet.Id revision;
   private final String path;
   private final int startLine;
   private EditPreferences prefs;
   private EditPreferencesAction editPrefsAction;
-  private CodeMirror cm;
+  private MergeView mv;
+  private CodeMirror cmBase;
+  private CodeMirror cmEdit;
   private HttpResponse<NativeString> content;
+  private HttpResponse<NativeString> baseContent;
   private EditFileInfo editFileInfo;
   private JsArray<DiffWebLinkInfo> diffLinks;
 
@@ -103,9 +120,11 @@
   @UiField Element cursLine;
   @UiField Element cursCol;
   @UiField Element dirty;
+  @UiField CheckBox showBase;
   @UiField Button close;
   @UiField Button save;
   @UiField Element editor;
+  @UiField Style style;
 
   private HandlerRegistration resizeHandler;
   private HandlerRegistration closeHandler;
@@ -145,9 +164,21 @@
       public void onSuccess(Void result) {
         // Load theme after CM library to ensure theme can override CSS.
         ThemeLoader.loadTheme(prefs.theme(), themeCallback);
-
         group2.done();
-        group3.done();
+
+        new AddonInjector().add(Addons.I.merge_bundled().getName()).inject(
+            new AsyncCallback<Void>() {
+          @Override
+          public void onFailure(Throwable caught) {
+          }
+
+          @Override
+          public void onSuccess(Void result) {
+            if (!prefs.showBase() || revision.get() > 0) {
+              group3.done();
+            }
+          }
+        });
       }
 
       @Override
@@ -180,13 +211,30 @@
             public void onFailure(Throwable e) {
             }
           }));
+
+      if (prefs.showBase()) {
+        ChangeEditApi.get(revision, path, true /* base */,
+            group1.addFinal(new HttpCallback<NativeString>() {
+              @Override
+              public void onSuccess(HttpResponse<NativeString> fc) {
+                baseContent = fc;
+                group3.done();
+              }
+
+              @Override
+              public void onFailure(Throwable e) {
+              }
+            }));
+      } else {
+        group1.done();
+      }
     } else {
       // TODO(davido): We probably want to create dedicated GET EditScreenMeta
       // REST endpoint. Abuse GET diff for now, as it retrieves links we need.
       DiffApi.diff(revision, path)
         .base(base)
         .webLinksOnly()
-        .get(group1.add(new AsyncCallback<DiffInfo>() {
+        .get(group1.addFinal(new AsyncCallback<DiffInfo>() {
           @Override
           public void onSuccess(DiffInfo diffInfo) {
             diffLinks = diffInfo.webLinks();
@@ -205,6 +253,10 @@
           @Override
           public void onSuccess(HttpResponse<NativeString> fc) {
             content = fc;
+            if (revision.get() > 0) {
+              baseContent = fc;
+            }
+
             if (prefs.syntaxHighlighting()) {
               injectMode(fc.getContentType(), modeCallback);
             } else {
@@ -227,14 +279,16 @@
     group3.addListener(new ScreenLoadCallback<Void>(this) {
       @Override
       protected void preDisplay(Void result) {
-        initEditor(content);
+        initEditor();
 
         renderLinks(editFileInfo, diffLinks);
         editFileInfo = null;
         diffLinks = null;
+
+        showBase.setValue(prefs.showBase(), true);
+        cmBase.refresh();
       }
     });
-    group1.done();
   }
 
   @Override
@@ -251,14 +305,15 @@
       localKeyMap.on("Ctrl-S", save());
     }
 
-    cm.addKeyMap(localKeyMap);
+    cmBase.addKeyMap(localKeyMap);
+    cmEdit.addKeyMap(localKeyMap);
   }
 
   private Runnable gotoLine() {
     return new Runnable() {
       @Override
       public void run() {
-        cm.execCommand("jumpToLine");
+        cmEdit.execCommand("jumpToLine");
       }
     };
   }
@@ -274,36 +329,36 @@
     resizeHandler = Window.addResizeHandler(new ResizeHandler() {
       @Override
       public void onResize(ResizeEvent event) {
-        cm.adjustHeight(header.getOffsetHeight());
+        adjustHeight();
       }
     });
     closeHandler = Window.addWindowClosingHandler(new ClosingHandler() {
       @Override
       public void onWindowClosing(ClosingEvent event) {
-        if (!cm.isClean(generation)) {
+        if (!cmEdit.isClean(generation)) {
           event.setMessage(EditConstants.I.closeUnsavedChanges());
         }
       }
     });
 
-    generation = cm.changeGeneration(true);
+    generation = cmEdit.changeGeneration(true);
     setClean(true);
-    cm.on(new ChangesHandler() {
+    cmEdit.on(new ChangesHandler() {
       @Override
       public void handle(CodeMirror cm) {
         setClean(cm.isClean(generation));
       }
     });
 
-    cm.adjustHeight(header.getOffsetHeight());
-    cm.on("cursorActivity", updateCursorPosition());
+    adjustHeight();
+    cmEdit.on("cursorActivity", updateCursorPosition());
     setShowTabs(prefs.showTabs());
     setLineLength(prefs.lineLength());
-    cm.refresh();
-    cm.focus();
+    cmEdit.refresh();
+    cmEdit.focus();
 
     if (startLine > 0) {
-      cm.scrollToLine(startLine);
+      cmEdit.scrollToLine(startLine);
     }
     updateActiveLine();
     editPrefsAction = new EditPreferencesAction(this, prefs);
@@ -312,8 +367,11 @@
   @Override
   protected void onUnload() {
     super.onUnload();
-    if (cm != null) {
-      cm.getWrapperElement().removeFromParent();
+    if (cmBase != null) {
+      cmBase.getWrapperElement().removeFromParent();
+    }
+    if (cmEdit != null) {
+      cmEdit.getWrapperElement().removeFromParent();
     }
     if (resizeHandler != null) {
       resizeHandler.removeHandler();
@@ -327,7 +385,7 @@
   }
 
   CodeMirror getEditor() {
-    return cm;
+    return cmEdit;
   }
 
   @UiHandler("editSettings")
@@ -342,41 +400,127 @@
 
   @UiHandler("close")
   void onClose(@SuppressWarnings("unused") ClickEvent e) {
-    if (cm.isClean(generation)
+    if (cmEdit.isClean(generation)
         || Window.confirm(EditConstants.I.cancelUnsavedChanges())) {
       upToChange();
     }
   }
 
+  private void displayBase() {
+    cmBase.getWrapperElement().getParentElement()
+        .removeClassName(style.hideBase());
+    cmEdit.getWrapperElement().getParentElement()
+        .removeClassName(style.fullWidth());
+    mv.getGapElement().removeClassName(style.hideBase());
+    setCmBaseValue();
+    setLineLength(prefs.lineLength());
+    cmBase.refresh();
+  }
+
+  @UiHandler("showBase")
+  void onShowBase(ValueChangeEvent<Boolean> e) {
+    boolean shouldShow = e.getValue();
+    if (shouldShow) {
+      if (baseContent == null) {
+        ChangeEditApi.get(revision, path, true /* base */,
+            new HttpCallback<NativeString>() {
+              @Override
+              public void onSuccess(HttpResponse<NativeString> fc) {
+                baseContent = fc;
+                displayBase();
+              }
+
+              @Override
+              public void onFailure(Throwable e) {
+              }
+            });
+      } else {
+        displayBase();
+      }
+    } else {
+      cmBase.getWrapperElement().getParentElement()
+          .addClassName(style.hideBase());
+      cmEdit.getWrapperElement().getParentElement()
+          .addClassName(style.fullWidth());
+      mv.getGapElement().addClassName(style.hideBase());
+    }
+    mv.setShowDifferences(shouldShow);
+  }
+
+  void setOption(String option, String value) {
+    cmBase.setOption(option, value);
+    cmEdit.setOption(option, value);
+  }
+
+  void setOption(String option, boolean value) {
+    cmBase.setOption(option, value);
+    cmEdit.setOption(option, value);
+  }
+
+  void setOption(String option, double value) {
+    cmBase.setOption(option, value);
+    cmEdit.setOption(option, value);
+  }
+
+  void setTheme(final Theme newTheme) {
+    cmBase.operation(new Runnable() {
+      @Override
+      public void run() {
+        cmBase.setOption("theme", newTheme.name().toLowerCase());
+      }
+    });
+    cmEdit.operation(new Runnable() {
+      @Override
+      public void run() {
+        cmEdit.setOption("theme", newTheme.name().toLowerCase());
+      }
+    });
+  }
+
   void setLineLength(int length) {
-    cm.extras().lineLength(
-        Patch.COMMIT_MSG.equals(path) ? 72 : length);
+    int adjustedLength = Patch.COMMIT_MSG.equals(path) ? 72 : length;
+    cmBase.extras().lineLength(adjustedLength);
+    cmEdit.extras().lineLength(adjustedLength);
   }
 
   void setIndentUnit(int indent) {
-    cm.setOption("indentUnit",
-        Patch.COMMIT_MSG.equals(path) ? 2 : indent);
+    cmEdit.setOption("indentUnit", Patch.COMMIT_MSG.equals(path) ? 2 : indent);
   }
 
   void setShowLineNumbers(boolean show) {
-    cm.setOption("lineNumbers", show);
+    cmBase.setOption("lineNumbers", show);
+    cmEdit.setOption("lineNumbers", show);
   }
 
   void setShowWhitespaceErrors(final boolean show) {
-    cm.operation(new Runnable() {
+    cmBase.operation(new Runnable() {
       @Override
       public void run() {
-        cm.setOption("showTrailingSpace", show);
+        cmBase.setOption("showTrailingSpace", show);
+      }
+    });
+    cmEdit.operation(new Runnable() {
+      @Override
+      public void run() {
+        cmEdit.setOption("showTrailingSpace", show);
       }
     });
   }
 
   void setShowTabs(boolean show) {
-    cm.extras().showTabs(show);
+    cmBase.extras().showTabs(show);
+    cmEdit.extras().showTabs(show);
   }
 
-  void resizeCodeMirror() {
-    cm.adjustHeight(header.getOffsetHeight());
+  void adjustHeight() {
+    int height = header.getOffsetHeight();
+    int rest = Gerrit.getHeaderFooterHeight()
+        + height
+        + 5; // Estimate
+    mv.getGapElement().getStyle().setHeight(
+        Window.getClientHeight() - rest, Unit.PX);
+    cmBase.adjustHeight(height);
+    cmEdit.adjustHeight(height);
   }
 
   void setSyntaxHighlighting(boolean b) {
@@ -386,7 +530,8 @@
       injectMode(mode, new AsyncCallback<Void>() {
         @Override
         public void onSuccess(Void result) {
-          cm.setOption("mode", mode);
+          cmBase.setOption("mode", mode);
+          cmEdit.setOption("mode", mode);
         }
 
         @Override
@@ -395,7 +540,8 @@
         }
       });
     } else {
-      cm.setOption("mode", (String) null);
+      cmBase.setOption("mode", (String) null);
+      cmEdit.setOption("mode", (String) null);
     }
   }
 
@@ -403,16 +549,16 @@
     Gerrit.display(PageLinks.toChangeInEditMode(revision.getParentKey()));
   }
 
-  private void initEditor(HttpResponse<NativeString> file) {
+  private void initEditor() {
     ModeInfo mode = null;
-    String content = "";
-    if (file != null && file.getResult() != null) {
-      content = file.getResult().asString();
+    String editContent = "";
+    if (content != null && content.getResult() != null) {
+      editContent = content.getResult().asString();
       if (prefs.syntaxHighlighting()) {
-        mode = ModeInfo.findMode(file.getContentType(), path);
+        mode = ModeInfo.findMode(content.getContentType(), path);
       }
     }
-    cm = CodeMirror.create(editor, Configuration.create()
+    mv = MergeView.create(editor, Configuration.create()
         .set("autoCloseBrackets", prefs.autoCloseBrackets())
         .set("cursorBlinkRate", prefs.cursorBlinkRate())
         .set("cursorHeight", 0.85)
@@ -422,13 +568,19 @@
         .set("lineWrapping", false)
         .set("matchBrackets", prefs.matchBrackets())
         .set("mode", mode != null ? mode.mime() : null)
-        .set("readOnly", false)
+        .set("origLeft", editContent)
         .set("scrollbarStyle", "overlay")
         .set("showTrailingSpace", prefs.showWhitespaceErrors())
         .set("styleSelectedText", true)
         .set("tabSize", prefs.tabSize())
         .set("theme", prefs.theme().name().toLowerCase())
-        .set("value", content));
+        .set("value", ""));
+
+    cmBase = mv.leftOriginal();
+    cmBase.getWrapperElement().addClassName(style.base());
+    cmEdit = mv.editor();
+    setCmBaseValue();
+    cmEdit.setValue(editContent);
 
     CodeMirror.addCommand("save", new CommandRunner() {
       @Override
@@ -487,7 +639,7 @@
         Scheduler.get().scheduleDeferred(new ScheduledCommand() {
           @Override
           public void execute() {
-            cm.operation(new Runnable() {
+            cmEdit.operation(new Runnable() {
               @Override
               public void run() {
                 updateActiveLine();
@@ -500,10 +652,10 @@
   }
 
   private void updateActiveLine() {
-    Pos p = cm.getCursor("end");
+    Pos p = cmEdit.getCursor("end");
     cursLine.setInnerText(Integer.toString(p.line() + 1));
     cursCol.setInnerText(Integer.toString(p.ch() + 1));
-    cm.extras().activeLine(cm.getLineHandleVisualStart(p.line()));
+    cmEdit.extras().activeLine(cmEdit.getLineHandleVisualStart(p.line()));
   }
 
   private void setClean(boolean clean) {
@@ -516,23 +668,23 @@
     return new Runnable() {
       @Override
       public void run() {
-        if (!cm.isClean(generation)) {
+        if (!cmEdit.isClean(generation)) {
           close.setEnabled(false);
-          String text = cm.getValue();
+          String text = cmEdit.getValue();
           if (Patch.COMMIT_MSG.equals(path)) {
             String trimmed = text.trim() + "\r";
             if (!trimmed.equals(text)) {
               text = trimmed;
-              cm.setValue(text);
+              cmEdit.setValue(text);
             }
           }
-          final int g = cm.changeGeneration(false);
+          final int g = cmEdit.changeGeneration(false);
           ChangeEditApi.put(revision.getParentKey().get(), path, text,
               new GerritCallback<VoidResult>() {
                 @Override
                 public void onSuccess(VoidResult result) {
                   generation = g;
-                  setClean(cm.isClean(g));
+                  setClean(cmEdit.isClean(g));
                 }
                 @Override
                 public void onFailure(final Throwable caught) {
@@ -547,4 +699,10 @@
   private void injectMode(String type, AsyncCallback<Void> cb) {
     new ModeInjector().add(type).inject(cb);
   }
+
+  private void setCmBaseValue() {
+    cmBase.setValue(baseContent != null && baseContent.getResult() != null
+        ? baseContent.getResult().asString()
+        : "");
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
index 88af398..34282c8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.ui.xml
@@ -17,8 +17,11 @@
 <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
     xmlns:g='urn:import:com.google.gwt.user.client.ui'>
   <ui:with field='ico' type='com.google.gerrit.client.GerritResources'/>
-  <ui:style gss='false'>
+  <ui:style gss='false' type='com.google.gerrit.client.editor.EditScreen.Style'>
     @external .CodeMirror, .CodeMirror-cursor;
+    @external .CodeMirror-merge-2pane, .CodeMirror-merge-pane;
+    @external .CodeMirror-merge-gap;
+    @external .CodeMirror-scroll, .CodeMirror-overlayscroll-vertical;
 
     .header {
       position: relative;
@@ -123,12 +126,28 @@
       cursor: pointer;
       outline: none;
     }
+
+    .hideBase.CodeMirror-merge-pane {
+      display: none;
+    }
+
+    .hideBase.CodeMirror-merge-gap {
+      display: none;
+    }
+
+    .CodeMirror-merge-2pane .fullWidth.CodeMirror-merge-pane {
+      width: 100%;
+    }
+
+    /* Hide the vertical scrollbar on the base side. The edit side controls
+       both views */
+    .base .CodeMirror-scroll { margin-right: -42px; }
+    .base .CodeMirror-overlayscroll-vertical { display: none !important; }
   </ui:style>
   <g:HTMLPanel styleName='{style.header}'>
     <div class='{style.headerLine}' ui:field='header'>
        <div class='{style.headerButtons}'>
          <g:Button ui:field='close'
-             styleName=''
              title='Close file and return to change'>
            <ui:attribute name='title'/>
            <div><ui:msg>Close</ui:msg></div>
@@ -142,6 +161,11 @@
        </div>
        <span class='{style.path}'><span ui:field='project'/> / <span ui:field='filePath'/></span>
        <div class='{style.navigation}'>
+         <g:Label text='Show Base' styleName='{style.linkPanel}'></g:Label>
+         <g:CheckBox ui:field='showBase' checked='true' styleName='{style.linkPanel}'
+             title='Show Base Version'>
+           <ui:attribute name='title'/>
+         </g:CheckBox>
          <g:FlowPanel ui:field='linkPanel' styleName='{style.linkPanel}'/>
          <g:Image
              ui:field='editSettings'
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
index 071ca72..009deaf 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/rpc/CallbackGroup.java
@@ -78,23 +78,7 @@
 
   public <T> HttpCallback<T> add(HttpCallback<T> cb) {
     checkFinalAdded();
-    if (failed) {
-      cb.onFailure(failedThrowable);
-      return new HttpCallback<T>() {
-        @Override
-        public void onSuccess(HttpResponse<T> result) {
-        }
-
-        @Override
-        public void onFailure(Throwable caught) {
-        }
-      };
-    }
-
-    HttpCallbackImpl<T> w = new HttpCallbackImpl<>(cb);
-    callbacks.add(w);
-    remaining.add(w);
-    return w;
+    return handleAdd(cb);
   }
 
   public <T> Callback<T> addFinal(final AsyncCallback<T> cb) {
@@ -103,6 +87,12 @@
     return handleAdd(cb);
   }
 
+  public <T> HttpCallback<T> addFinal(final HttpCallback<T> cb) {
+    checkFinalAdded();
+    finalAdded = true;
+    return handleAdd(cb);
+  }
+
   public void done() {
     finalAdded = true;
     apply();
@@ -161,6 +151,26 @@
     return wrapper;
   }
 
+  private <T> HttpCallback<T> handleAdd(HttpCallback<T> cb) {
+    if (failed) {
+      cb.onFailure(failedThrowable);
+      return new HttpCallback<T>() {
+        @Override
+        public void onSuccess(HttpResponse<T> result) {
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+        }
+      };
+    }
+
+    HttpCallbackImpl<T> w = new HttpCallbackImpl<>(cb);
+    callbacks.add(w);
+    remaining.add(w);
+    return w;
+  }
+
   private void checkFinalAdded() {
     if (finalAdded) {
       throw new IllegalStateException("final callback already added");
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/addon/AddonInjector.java b/gerrit-gwtui/src/main/java/net/codemirror/addon/AddonInjector.java
new file mode 100644
index 0000000..efed451
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/addon/AddonInjector.java
@@ -0,0 +1,95 @@
+// Copyright (C) 2016 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 net.codemirror.addon;
+
+import com.google.gwt.safehtml.shared.SafeUri;
+import com.google.gwt.user.client.rpc.AsyncCallback;
+
+import net.codemirror.lib.Loader;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+public class AddonInjector {
+  private static final Map<String, SafeUri> addonUris = new HashMap<>();
+  static {
+    addonUris.put(Addons.I.merge_bundled().getName(),
+        Addons.I.merge_bundled().getSafeUri());
+  }
+
+  public static SafeUri getAddonScriptUri(String addon) {
+    return addonUris.get(addon);
+  }
+
+  private static boolean canLoad(String addon) {
+    return getAddonScriptUri(addon) != null;
+  }
+
+  private final Set<String> loading = new HashSet<>();
+  private int pending;
+  private AsyncCallback<Void> appCallback;
+
+  public AddonInjector add(String name) {
+    if (name == null) {
+      return this;
+    }
+
+    if (!canLoad(name)) {
+      Logger.getLogger("net.codemirror").log(
+        Level.WARNING,
+        "CodeMirror addon " + name + " not configured.");
+      return this;
+    }
+
+    loading.add(name);
+    return this;
+  }
+
+  public void inject(AsyncCallback<Void> appCallback) {
+    this.appCallback = appCallback;
+    for (String addon : loading) {
+      beginLoading(addon);
+    }
+    if (pending == 0) {
+      appCallback.onSuccess(null);
+    }
+  }
+
+  private void beginLoading(final String addon) {
+    pending++;
+    Loader.injectScript(
+      getAddonScriptUri(addon),
+      new AsyncCallback<Void>() {
+        @Override
+        public void onSuccess(Void result) {
+          pending--;
+          if (pending == 0) {
+            appCallback.onSuccess(null);
+          }
+        }
+
+        @Override
+        public void onFailure(Throwable caught) {
+          if (--pending == 0) {
+            appCallback.onFailure(caught);
+          }
+        }
+      });
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
new file mode 100644
index 0000000..5365cc5
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
@@ -0,0 +1,26 @@
+// Copyright (C) 2016 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 net.codemirror.addon;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.resources.client.ClientBundle;
+import com.google.gwt.resources.client.DataResource;
+import com.google.gwt.resources.client.DataResource.DoNotEmbed;
+
+public interface Addons extends ClientBundle {
+  public static final Addons I = GWT.create(Addons.class);
+
+  @Source("merge_bundled.js") @DoNotEmbed DataResource merge_bundled();
+}
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/MergeView.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/MergeView.java
new file mode 100644
index 0000000..d7e1430
--- /dev/null
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/MergeView.java
@@ -0,0 +1,51 @@
+// Copyright (C) 2016 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 net.codemirror.lib;
+
+import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.dom.client.Element;
+
+/** Object that represents a text marker within CodeMirror */
+public class MergeView extends JavaScriptObject {
+  public static MergeView create(Element p, Configuration cfg) {
+    MergeView mv = newMergeView(p, cfg);
+    Extras.attach(mv.leftOriginal());
+    Extras.attach(mv.editor());
+    return mv;
+  }
+
+  private static native MergeView newMergeView(Element p, Configuration cfg) /*-{
+    return $wnd.CodeMirror.MergeView(p, cfg);
+  }-*/;
+
+  public final native CodeMirror leftOriginal() /*-{
+    return this.leftOriginal();
+  }-*/;
+
+  public final native CodeMirror editor() /*-{
+    return this.editor();
+  }-*/;
+
+  public final native void setShowDifferences(boolean b) /*-{
+    this.setShowDifferences(b);
+  }-*/;
+
+  public final native Element getGapElement() /*-{
+    return $doc.getElementsByClassName("CodeMirror-merge-gap")[0];
+  }-*/;
+
+  protected MergeView() {
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
index fb7bfad..a081197 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/IdentifiedUser.java
@@ -327,6 +327,7 @@
     return effectiveGroups;
   }
 
+  @SuppressWarnings("deprecation")
   @Override
   public Set<Change.Id> getStarredChanges() {
     if (starredChanges == null) {
@@ -354,6 +355,7 @@
     starredChanges = null;
   }
 
+  @SuppressWarnings("deprecation")
   public void asyncStarredChanges() {
     if (starredChanges == null && starredChangesUtil != null) {
       starredQuery = starredChangesUtil.queryFromIndex(accountId);
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/change/ChangeEdits.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
index c821cf6..e508659 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeEdits.java
@@ -441,10 +441,14 @@
     }
   }
 
-  @Singleton
   public static class Get implements RestReadView<ChangeEditResource> {
     private final FileContentUtil fileContentUtil;
 
+    @Option(name = "--base", aliases = {"-b"},
+      usage = "whether to load the content on the base revision instead of the"
+        + " change edit")
+    private boolean base;
+
     @Inject
     Get(FileContentUtil fileContentUtil) {
       this.fileContentUtil = fileContentUtil;
@@ -454,9 +458,13 @@
     public Response<?> apply(ChangeEditResource rsrc)
         throws IOException {
       try {
+        ChangeEdit edit = rsrc.getChangeEdit();
         return Response.ok(fileContentUtil.getContent(
               rsrc.getControl().getProjectControl().getProjectState(),
-              ObjectId.fromString(rsrc.getChangeEdit().getRevision().get()),
+              base
+                  ? ObjectId.fromString(
+                      edit.getBasePatchSet().getRevision().get())
+                  : ObjectId.fromString(edit.getRevision().get()),
               rsrc.getPath()));
       } catch (ResourceNotFoundException rnfe) {
         return Response.none();
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/ChangeQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
index 2679f29..230bf31 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeQueryBuilder.java
@@ -660,6 +660,7 @@
     return Predicate.or(p);
   }
 
+  @SuppressWarnings("deprecation")
   private Predicate<ChangeData> starredby(Account.Id who)
       throws QueryParseException {
     return args.getSchema().hasField(ChangeField.STARREDBY)
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/lib/codemirror/BUCK b/lib/codemirror/BUCK
index 59d24b3..9b8a146 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -23,6 +23,18 @@
   visibility = [],
 )
 
+DIFF_MATCH_PATCH_VERSION = '20121119-1'
+DIFF_MATCH_PATCH_TOP = ('META-INF/resources/webjars/google-diff-match-patch/%s'
+    % DIFF_MATCH_PATCH_VERSION)
+
+maven_jar(
+  name = 'diff-match-patch',
+  id = 'org.webjars:google-diff-match-patch:' + DIFF_MATCH_PATCH_VERSION,
+  sha1 = '0cf1782dbcb8359d95070da9176059a5a9d37709',
+  license = 'Apache2.0',
+  attach_source = False,
+)
+
 for archive, suffix, top in [('codemirror-original', '', TOP), ('codemirror-minified', '_r', TOP_MINIFIED)]:
   # Main JavaScript and addons
   genrule(
@@ -58,11 +70,12 @@
     genrule (
       name = 'mode_%s%s' % (n, suffix),
       cmd = ';'.join([
-        "echo '/** @license' >$OUT",
-        'unzip -p $(location :%s) %s/LICENSE >>$OUT' % (archive, top),
-        "echo '*/' >>$OUT",
-        'unzip -p $(location :%s) %s/mode/%s/%s.js >>$OUT' % (archive, top, n, n),
-        ]),
+          "echo '/** @license' >$OUT",
+          'unzip -p $(location :%s) %s/LICENSE >>$OUT' % (archive, top),
+          "echo '*/' >>$OUT",
+          'unzip -p $(location :%s) %s/mode/%s/%s.js >>$OUT' % (archive, top, n, n),
+        ]
+      ),
       out = 'mode_%s%s.js' % (n, suffix),
     )
 
@@ -80,19 +93,39 @@
       out = 'theme_%s%s.css' % (n, suffix),
     )
 
+  # Merge Addon bundled with diff-match-patch
+  genrule(
+    name = 'addon_merge%s' % suffix,
+    cmd = ';'.join([
+        "echo '/** @license' >$OUT",
+        'unzip -p $(location :%s) %s/LICENSE >>$OUT' % (archive, top),
+        "echo '*/\n' >>$OUT",
+        "echo '// The google-diff-match-patch library is from https://google-diff-match-patch.googlecode.com/svn-history/r106/trunk/javascript/diff_match_patch.js\n' >> $OUT",
+        "echo '/** @license' >>$OUT",
+        'cat $(location //lib:LICENSE-Apache2.0) >>$OUT',
+        "echo '*/' >>$OUT",
+        'unzip -p $(location :diff-match-patch) %s/diff_match_patch.js >>$OUT' % DIFF_MATCH_PATCH_TOP,
+        "echo ';' >> $OUT",
+        'unzip -p $(location :%s) %s/addon/merge/merge.js >>$OUT' % (archive, top)
+      ]
+    ),
+    out = 'addon_merge%s.js' % suffix,
+  )
+
   # Jar packaging
   genrule(
     name = 'jar' + suffix,
     cmd = ';'.join([
       'cd $TMP',
-      'mkdir -p net/codemirror/{lib,mode,theme}',
+      'mkdir -p net/codemirror/{addon,lib,mode,theme}',
       'cp $(location :css%s) net/codemirror/lib/cm.css' % suffix,
       'cp $(location :cm%s) net/codemirror/lib/cm.js' % suffix]
       + ['cp $(location :mode_%s%s) net/codemirror/mode/%s.js' % (n, suffix, n)
          for n in CM_MODES]
       + ['cp $(location :theme_%s%s) net/codemirror/theme/%s.css' % (n, suffix, n)
          for n in CM_THEMES]
-      + ['zip -qr $OUT net/codemirror/{lib,mode,theme}']),
+      + ['cp $(location :addon_merge%s) net/codemirror/addon/merge_bundled.js' % suffix]
+      + ['zip -qr $OUT net/codemirror/{addon,lib,mode,theme}']),
     out = 'codemirror%s.jar' % suffix,
   )
 
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index 29f1c77..baf2ce5 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -1,6 +1,7 @@
 CM_CSS = [
   'lib/codemirror.css',
   'addon/dialog/dialog.css',
+  'addon/merge/merge.css',
   'addon/scroll/simplescrollbars.css',
   'addon/search/matchesonscrollbar.css',
   'addon/lint/lint.css',
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