Merge changes from topic 'bot-comments-ui-gh16'

* changes:
  Hide "HideBotComments" if no bot comment is present in the history tab
  Add show / hide bot comments button to ChangeScreen
diff --git a/.buckconfig b/.buckconfig
index cc3353d..60fd02a 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -25,7 +25,7 @@
 
 [project]
   allow_symlinks = allow
-  ignore = .git, eclipse-out, bazel-gerrit
+  ignore = .git, eclipse-out, bazel-gerrit, bin
   parallel_parsing = true
 
 [cache]
diff --git a/Documentation/BUILD b/Documentation/BUILD
new file mode 100644
index 0000000..98b3ce4
--- /dev/null
+++ b/Documentation/BUILD
@@ -0,0 +1,7 @@
+
+load("//tools/bzl:license.bzl", "license_map")
+
+license_map(
+  name = "pgm-licenses",
+  target = "//gerrit-pgm:pgm",
+)
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index e28332c..cce00ef 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1275,6 +1275,10 @@
 link:#current-revision[`CURRENT_REVISION`], and
 link:#current-commit[`CURRENT_COMMIT`] options set.
 
+Standard link:#query-options[formatting options] can be specified
+with the `o` parameter, as well as the `submitted_together` specific
+option `NON_VISIBLE_CHANGES`.
+
 .Response
 ----
   HTTP/1.1 200 OK
diff --git a/ReleaseNotes/ReleaseNotes-2.13.txt b/ReleaseNotes/ReleaseNotes-2.13.txt
index d73a1bd..b875603 100644
--- a/ReleaseNotes/ReleaseNotes-2.13.txt
+++ b/ReleaseNotes/ReleaseNotes-2.13.txt
@@ -327,6 +327,18 @@
 capability, or to ensure that specific groups always have administration
 capabilities.
 
+* New configuration options to configure JGit repository cache parameters.
++
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.13/config-gerrit.html#core.repositoryCacheCleanupDelay[
+core.repositoryCacheCleanupDelay] and
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.13/config-gerrit.html#core.repositoryCacheExpireAfter[
+core.repositoryCacheExpireAfter] can be configured.
+
+* Accept `-b` as an alias of `--batch` in the
+link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.13/pgm-init.html[
+init program].
+
+
 == Bug Fixes
 
 * Don't add the same SSH key multiple times.
@@ -400,6 +412,22 @@
 easier to track down which commit failed validation when multiple commits
 are pushed at the same time.
 
+* Don't check mergeability of draft changes.
++
+Draft changes can be deleted but not abandoned so there is no way for
+an administrator to get rid of the them on behalf of the users. This can
+become a problem when there many draft changes because the mergeability
+check can be costly.
++
+The mergeability check is no longer done for draft changes, but will be
+done when the draft change is published.
+
+* Fix internal server error when plugin-provided file history weblink
+is null.
++
+It is valid for a plugin to provide a null weblink, but doing so resulted
+in an internal server error.
+
 == Dependency updates
 
 * Add dependency on blame-cache 0.1-9
@@ -430,7 +458,7 @@
 
 * Upgrade Jetty to 9.2.14.v20151106
 
-* Upgrade JGit to 4.4.1.201607150455-r.144-gb67df51
+* Upgrade JGit to 4.5.0.201609210915-r
 
 * Upgrade joda-convert to 1.8.1
 
diff --git a/WORKSPACE b/WORKSPACE
index 536c827..77d6dbc 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -172,7 +172,7 @@
 maven_jar(
   name = 'gwtjsonrpc',
   artifact = 'com.google.gerrit:gwtjsonrpc:1.11',
-  sha1 = '9587c6f88f1964f1aa61212a162c776c71d77456',
+  sha1 = '0990e7eec9eec3a15661edcf9232acbac4aeacec',
 )
 
 http_jar(
@@ -190,7 +190,7 @@
 maven_jar(
   name = 'gwtorm_client',
   artifact = 'com.google.gerrit:gwtorm:1.16',
-  sha1 = 'b91331724669f8136426f0f4846ddcec0f6eb22f',
+  sha1 = '3e41b6d7bb352fa0539ce23b9bce97cf8c26c3bf',
 )
 
 http_jar(
diff --git a/contrib/git-push-review b/contrib/git-push-review
index e77785a..aeea552 100755
--- a/contrib/git-push-review
+++ b/contrib/git-push-review
@@ -46,8 +46,8 @@
                  help='remote name or URL to push to')
   p.add_argument('-b', '--branch', default='', metavar='BRANCH',
                  help='remote branch name, refs/for/BRANCH')
-  p.add_argument('reviewers', nargs='*', metavar='REVIEWER',
-                 help='reviewer names or aliases')
+  p.add_argument('args', nargs='*', metavar='REVIEWER_OR_HASHTAG',
+                 help='reviewer names or aliases, or #hashtags')
   p.add_argument('-t', '--topic', default='', metavar='TOPIC',
                  help='topic for new changes')
   p.add_argument('--dry-run', action='store_true',
@@ -68,8 +68,12 @@
     args.remote = args.remote or def_remote
     args.branch = args.branch or def_branch
 
+
   opts = collections.defaultdict(list)
-  opts['r'].extend((get_config('reviewer.' + r) or r) for r in args.reviewers)
+  is_hashtag = lambda x: x.startswith('#')
+  opts['r'].extend(
+      get_config('reviewer.' + r) for r in args.args if not is_hashtag(r))
+  opts['t'].extend(t[1:] for t in args.args if is_hashtag(t))
   if args.topic:
     opts['topic'].append(args.topic)
   opts_str = ','.join('%s=%s' % (k, v) for k in opts for v in opts[k])
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 53f4599..82de9f8 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
@@ -17,6 +17,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.gerrit.acceptance.GitUtil.initSsh;
 import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
+import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
+import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 
@@ -48,6 +50,8 @@
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ActionInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeType;
+import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.IdString;
@@ -67,6 +71,7 @@
 import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.change.Abandon;
 import com.google.gerrit.server.change.ChangeResource;
+import com.google.gerrit.server.change.FileContentUtil;
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.change.Revisions;
 import com.google.gerrit.server.config.AllProjectsName;
@@ -546,21 +551,26 @@
 
   protected PushOneCommit.Result createMergeCommitChange(String ref)
       throws Exception {
+    return createMergeCommitChange(ref, "foo");
+  }
+
+  protected PushOneCommit.Result createMergeCommitChange(String ref, String file)
+      throws Exception {
     ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
 
     PushOneCommit.Result p1 = pushFactory.create(db, admin.getIdent(),
-        testRepo, "parent 1", ImmutableMap.of("foo", "foo-1", "bar", "bar-1"))
+        testRepo, "parent 1", ImmutableMap.of(file, "foo-1", "bar", "bar-1"))
         .to(ref);
 
     // reset HEAD in order to create a sibling of the first change
     testRepo.reset(initial);
 
     PushOneCommit.Result p2 = pushFactory.create(db, admin.getIdent(),
-        testRepo, "parent 2", ImmutableMap.of("foo", "foo-2", "bar", "bar-2"))
+        testRepo, "parent 2", ImmutableMap.of(file, "foo-2", "bar", "bar-2"))
         .to(ref);
 
     PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo, "merge",
-        ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
+        ImmutableMap.of(file, "foo-1", "bar", "bar-2"));
     m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
     PushOneCommit.Result result = m.to(ref);
     result.assertOkStatus();
@@ -1086,4 +1096,45 @@
     }
     assertThat(refValues.keySet()).containsAnyIn(trees.keySet());
   }
+
+  protected void assertDiffForNewFile(DiffInfo diff, RevCommit commit,
+      String path, String expectedContentSideB) throws Exception {
+    List<String> expectedLines = new ArrayList<>();
+    for (String line : expectedContentSideB.split("\n")) {
+      expectedLines.add(line);
+    }
+
+    assertThat(diff.binary).isNull();
+    assertThat(diff.changeType).isEqualTo(ChangeType.ADDED);
+    assertThat(diff.diffHeader).isNotNull();
+    assertThat(diff.intralineStatus).isNull();
+    assertThat(diff.webLinks).isNull();
+
+    assertThat(diff.metaA).isNull();
+    assertThat(diff.metaB).isNotNull();
+    assertThat(diff.metaB.commitId).isEqualTo(commit.name());
+
+    String expectedContentType = "text/plain";
+    if (COMMIT_MSG.equals(path)) {
+      expectedContentType = FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE;
+    } else if (MERGE_LIST.equals(path)) {
+      expectedContentType = FileContentUtil.TEXT_X_GERRIT_MERGE_LIST;
+    }
+    assertThat(diff.metaB.contentType).isEqualTo(expectedContentType);
+
+    assertThat(diff.metaB.lines).isEqualTo(expectedLines.size());
+    assertThat(diff.metaB.name).isEqualTo(path);
+    assertThat(diff.metaB.webLinks).isNull();
+
+    assertThat(diff.content).hasSize(1);
+    DiffInfo.ContentEntry contentEntry = diff.content.get(0);
+    assertThat(contentEntry.b).containsExactlyElementsIn(expectedLines)
+        .inOrder();
+    assertThat(contentEntry.a).isNull();
+    assertThat(contentEntry.ab).isNull();
+    assertThat(contentEntry.common).isNull();
+    assertThat(contentEntry.editA).isNull();
+    assertThat(contentEntry.editB).isNull();
+    assertThat(contentEntry.skip).isNull();
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/GetMergeListIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/GetMergeListIT.java
deleted file mode 100644
index 0cb7d89..0000000
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/GetMergeListIT.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// 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 com.google.gerrit.acceptance.api.change;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.eclipse.jgit.lib.Constants.HEAD;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableMap;
-import com.google.gerrit.acceptance.AbstractDaemonTest;
-import com.google.gerrit.acceptance.NoHttpd;
-import com.google.gerrit.acceptance.PushOneCommit;
-import com.google.gerrit.extensions.common.CommitInfo;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.junit.Test;
-
-import java.util.List;
-
-@NoHttpd
-public class GetMergeListIT extends AbstractDaemonTest {
-
-  @Test
-  public void getMergeList() throws Exception {
-    ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
-
-    PushOneCommit.Result gp1 = pushFactory
-        .create(db, admin.getIdent(), testRepo, "grand parent 1",
-            ImmutableMap.of("foo", "foo-1.1", "bar", "bar-1.1"))
-        .to("refs/for/master");
-
-    PushOneCommit.Result p1 = pushFactory
-        .create(db, admin.getIdent(), testRepo, "parent 1",
-            ImmutableMap.of("foo", "foo-1.2", "bar", "bar-1.2"))
-        .to("refs/for/master");
-
-    // reset HEAD in order to create a sibling of the first change
-    testRepo.reset(initial);
-
-    PushOneCommit.Result gp2 = pushFactory
-        .create(db, admin.getIdent(), testRepo, "grand parent 1",
-            ImmutableMap.of("foo", "foo-2.1", "bar", "bar-2.1"))
-        .to("refs/for/master");
-
-    PushOneCommit.Result p2 = pushFactory
-        .create(db, admin.getIdent(), testRepo, "parent 2",
-            ImmutableMap.of("foo", "foo-2.2", "bar", "bar-2.2"))
-        .to("refs/for/master");
-
-    PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo,
-        "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
-    m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
-    PushOneCommit.Result result = m.to("refs/for/master");
-    result.assertOkStatus();
-
-    List<CommitInfo> mergeList =
-        gApi.changes().id(result.getChangeId()).current().getMergeList().get();
-    assertThat(mergeList).hasSize(2);
-    assertThat(mergeList.get(0).commit).isEqualTo(p2.getCommit().name());
-    assertThat(mergeList.get(1).commit).isEqualTo(gp2.getCommit().name());
-
-    mergeList = gApi.changes().id(result.getChangeId()).current().getMergeList()
-        .withUninterestingParent(2).get();
-    assertThat(mergeList).hasSize(2);
-    assertThat(mergeList.get(0).commit).isEqualTo(p1.getCommit().name());
-    assertThat(mergeList.get(1).commit).isEqualTo(gp1.getCommit().name());
-  }
-}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java
new file mode 100644
index 0000000..481df31
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/MergeListIT.java
@@ -0,0 +1,209 @@
+// 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 com.google.gerrit.acceptance.api.change;
+
+import static com.google.common.collect.Iterables.getOnlyElement;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.common.RawInputUtil;
+import com.google.gerrit.extensions.api.changes.RevisionApi;
+import com.google.gerrit.extensions.common.CommitInfo;
+import com.google.gerrit.extensions.common.DiffInfo;
+import com.google.gerrit.extensions.restapi.BinaryResult;
+import com.google.gerrit.server.edit.ChangeEditModifier;
+import com.google.gerrit.server.edit.ChangeEditUtil;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.dircache.InvalidPathException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.util.List;
+import java.util.Set;
+
+@NoHttpd
+public class MergeListIT extends AbstractDaemonTest {
+
+  private String changeId;
+  private RevCommit merge;
+  private RevCommit parent1;
+  private RevCommit grandParent1;
+  private RevCommit parent2;
+  private RevCommit grandParent2;
+
+  @Inject
+  private ChangeEditModifier modifier;
+
+  @Inject
+  private ChangeEditUtil editUtil;
+
+  @Before
+  public void setup() throws Exception {
+    ObjectId initial = repo().exactRef(HEAD).getLeaf().getObjectId();
+
+    PushOneCommit.Result gp1 = pushFactory
+        .create(db, admin.getIdent(), testRepo, "grand parent 1",
+            ImmutableMap.of("foo", "foo-1.1", "bar", "bar-1.1"))
+        .to("refs/for/master");
+    grandParent1 = gp1.getCommit();
+
+    PushOneCommit.Result p1 = pushFactory
+        .create(db, admin.getIdent(), testRepo, "parent 1",
+            ImmutableMap.of("foo", "foo-1.2", "bar", "bar-1.2"))
+        .to("refs/for/master");
+    parent1 = p1.getCommit();
+
+    // reset HEAD in order to create a sibling of the first change
+    testRepo.reset(initial);
+
+    PushOneCommit.Result gp2 = pushFactory
+        .create(db, admin.getIdent(), testRepo, "grand parent 2",
+            ImmutableMap.of("foo", "foo-2.1", "bar", "bar-2.1"))
+        .to("refs/for/master");
+    grandParent2 = gp2.getCommit();
+
+    PushOneCommit.Result p2 = pushFactory
+        .create(db, admin.getIdent(), testRepo, "parent 2",
+            ImmutableMap.of("foo", "foo-2.2", "bar", "bar-2.2"))
+        .to("refs/for/master");
+    parent2 = p2.getCommit();
+
+    PushOneCommit m = pushFactory.create(db, admin.getIdent(), testRepo,
+        "merge", ImmutableMap.of("foo", "foo-1", "bar", "bar-2"));
+    m.setParents(ImmutableList.of(p1.getCommit(), p2.getCommit()));
+    PushOneCommit.Result result = m.to("refs/for/master");
+    result.assertOkStatus();
+    merge = result.getCommit();
+    changeId = result.getChangeId();
+  }
+
+  @Test
+  public void getMergeList() throws Exception {
+    List<CommitInfo> mergeList = current(changeId).getMergeList().get();
+    assertThat(mergeList).hasSize(2);
+    assertThat(mergeList.get(0).commit).isEqualTo(parent2.name());
+    assertThat(mergeList.get(1).commit).isEqualTo(grandParent2.name());
+
+    mergeList = current(changeId).getMergeList()
+        .withUninterestingParent(2).get();
+    assertThat(mergeList).hasSize(2);
+    assertThat(mergeList.get(0).commit).isEqualTo(parent1.name());
+    assertThat(mergeList.get(1).commit).isEqualTo(grandParent1.name());
+  }
+
+  @Test
+  public void getMergeListContent() throws Exception {
+    BinaryResult bin = current(changeId).file(MERGE_LIST).content();
+    ByteArrayOutputStream os = new ByteArrayOutputStream();
+    bin.writeTo(os);
+    String content = new String(os.toByteArray(), UTF_8);
+    assertThat(content).isEqualTo(
+        getMergeListContent(parent2, grandParent2));
+  }
+
+  @Test
+  public void getFileList() throws Exception {
+    assertThat(getFiles(changeId)).contains(MERGE_LIST);
+    assertThat(getFiles(changeId, 1)).contains(MERGE_LIST);
+    assertThat(getFiles(changeId, 2)).contains(MERGE_LIST);
+
+    assertThat(getFiles(createChange().getChangeId()))
+        .doesNotContain(MERGE_LIST);
+  }
+
+  @Test
+  public void getDiffForMergeList() throws Exception {
+    DiffInfo diff = getMergeListDiff(changeId);
+    assertDiffForNewFile(diff, merge, MERGE_LIST,
+        getMergeListContent(parent2, grandParent2));
+
+    diff = getMergeListDiff(changeId, 1);
+    assertDiffForNewFile(diff, merge, MERGE_LIST,
+        getMergeListContent(parent2, grandParent2));
+
+    diff = getMergeListDiff(changeId, 2);
+    assertDiffForNewFile(diff, merge, MERGE_LIST,
+        getMergeListContent(parent1, grandParent1));
+  }
+
+  @Test
+  public void editMergeList() throws Exception {
+    ChangeData cd = getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
+    modifier.createEdit(cd.change(), cd.currentPatchSet());
+
+    exception.expect(InvalidPathException.class);
+    exception.expectMessage("Invalid path: " + MERGE_LIST);
+    modifier.modifyFile(editUtil.byChange(cd.change()).get(), MERGE_LIST,
+        RawInputUtil.create("new content"));
+  }
+
+  @Test
+  public void deleteMergeList() throws Exception {
+    ChangeData cd = getOnlyElement(queryProvider.get().byKeyPrefix(changeId));
+    modifier.createEdit(cd.change(), cd.currentPatchSet());
+
+    exception.expect(InvalidChangeOperationException.class);
+    exception.expectMessage("no changes were made");
+    modifier.deleteFile(editUtil.byChange(cd.change()).get(), MERGE_LIST);
+  }
+
+  private String getMergeListContent(RevCommit... commits) {
+    StringBuilder mergeList = new StringBuilder("Merge List:\n\n");
+    for (RevCommit c : commits) {
+      mergeList.append("* ")
+          .append(c.abbreviate(8).name())
+          .append(" ")
+          .append(c.getShortMessage())
+          .append("\n");
+    }
+    return mergeList.toString();
+  }
+
+  private Set<String> getFiles(String changeId) throws Exception {
+    return current(changeId).files().keySet();
+  }
+
+  private Set<String> getFiles(String changeId, int parent) throws Exception {
+    return current(changeId).files(parent).keySet();
+  }
+
+  private DiffInfo getMergeListDiff(String changeId) throws Exception {
+    return current(changeId).file(MERGE_LIST).diff();
+  }
+
+  private DiffInfo getMergeListDiff(String changeId, int parent)
+      throws Exception {
+    return current(changeId).file(MERGE_LIST).diff(parent);
+  }
+
+  private RevisionApi current(String changeId) throws Exception {
+    return gApi.changes()
+        .id(changeId)
+        .current();
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
index 322cd4e..de0c3c1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/revision/RevisionIT.java
@@ -20,11 +20,13 @@
 import static com.google.gerrit.acceptance.PushOneCommit.PATCH;
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.reviewdb.client.Patch.COMMIT_MSG;
+import static com.google.gerrit.reviewdb.client.Patch.MERGE_LIST;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static org.eclipse.jgit.lib.Constants.HEAD;
 import static org.junit.Assert.fail;
 
+import com.google.common.base.Joiner;
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -45,7 +47,6 @@
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
-import com.google.gerrit.extensions.common.ChangeType;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.common.DiffInfo;
 import com.google.gerrit.extensions.common.FileInfo;
@@ -539,7 +540,7 @@
         .revision(r.getCommit().name())
         .files()
         .keySet()
-      ).containsExactly(COMMIT_MSG, "foo", "bar");
+      ).containsExactly(COMMIT_MSG, MERGE_LIST, "foo", "bar");
 
     // list files against parent 1
     assertThat(gApi.changes()
@@ -547,7 +548,7 @@
         .revision(r.getCommit().name())
         .files(1)
         .keySet()
-      ).containsExactly(COMMIT_MSG, "bar");
+      ).containsExactly(COMMIT_MSG, MERGE_LIST, "bar");
 
     // list files against parent 2
     assertThat(gApi.changes()
@@ -555,7 +556,7 @@
         .revision(r.getCommit().name())
         .files(2)
         .keySet()
-      ).containsExactly(COMMIT_MSG, "foo");
+      ).containsExactly(COMMIT_MSG, MERGE_LIST, "foo");
   }
 
   @Test
@@ -853,66 +854,40 @@
         .file(path)
         .diff();
 
-    List<String> expectedLines = new ArrayList<>();
+    List<String> headers = new ArrayList<>();
     if (path.equals(COMMIT_MSG)) {
       RevCommit c = pushResult.getCommit();
 
       RevCommit parentCommit = c.getParents()[0];
       String parentCommitId = testRepo.getRevWalk().getObjectReader()
           .abbreviate(parentCommit.getId(), 8).name();
-      expectedLines.add("Parent:     " + parentCommitId + " ("
+      headers.add("Parent:     " + parentCommitId + " ("
           + parentCommit.getShortMessage() + ")");
 
       SimpleDateFormat dtfmt =
           new SimpleDateFormat("yyyy-MM-dd HH:mm:ss Z", Locale.US);
       PersonIdent author = c.getAuthorIdent();
       dtfmt.setTimeZone(author.getTimeZone());
-      expectedLines.add("Author:     " + author.getName() + " <"
+      headers.add("Author:     " + author.getName() + " <"
           + author.getEmailAddress() + ">");
-      expectedLines.add("AuthorDate: "
+      headers.add("AuthorDate: "
           + dtfmt.format(Long.valueOf(author.getWhen().getTime())));
 
       PersonIdent committer = c.getCommitterIdent();
       dtfmt.setTimeZone(committer.getTimeZone());
-      expectedLines.add("Commit:     " + committer.getName() + " <"
+      headers.add("Commit:     " + committer.getName() + " <"
           + committer.getEmailAddress() + ">");
-      expectedLines.add("CommitDate: "
+      headers.add("CommitDate: "
           + dtfmt.format(Long.valueOf(committer.getWhen().getTime())));
-      expectedLines.add("");
+      headers.add("");
     }
 
-    for (String line : expectedContentSideB.split("\n")) {
-      expectedLines.add(line);
+    if (!headers.isEmpty()) {
+      String header = Joiner.on("\n").join(headers);
+      expectedContentSideB = header + "\n" + expectedContentSideB;
     }
 
-    assertThat(diff.binary).isNull();
-    assertThat(diff.changeType).isEqualTo(ChangeType.ADDED);
-    assertThat(diff.diffHeader).isNotNull();
-    assertThat(diff.intralineStatus).isNull();
-    assertThat(diff.webLinks).isNull();
-
-    assertThat(diff.metaA).isNull();
-    assertThat(diff.metaB).isNotNull();
-    assertThat(diff.metaB.commitId).isEqualTo(pushResult.getCommit().name());
-    assertThat(diff.metaB.contentType).isEqualTo(
-        path.equals(COMMIT_MSG)
-            ? "text/x-gerrit-commit-message"
-            : "text/plain");
-    assertThat(diff.metaB.lines).isEqualTo(expectedLines.size());
-    assertThat(diff.metaB.name).isEqualTo(path);
-    assertThat(diff.metaB.webLinks).isNull();
-
-    assertThat(diff.content).hasSize(1);
-    DiffInfo.ContentEntry contentEntry = diff.content.get(0);
-    assertThat(contentEntry.b).hasSize(expectedLines.size());
-    for (int i = 0; i < contentEntry.b.size(); i++) {
-      assertThat(contentEntry.b.get(i)).isEqualTo(expectedLines.get(i));
-    }
-    assertThat(contentEntry.a).isNull();
-    assertThat(contentEntry.ab).isNull();
-    assertThat(contentEntry.common).isNull();
-    assertThat(contentEntry.editA).isNull();
-    assertThat(contentEntry.editB).isNull();
-    assertThat(contentEntry.skip).isNull();
+    assertDiffForNewFile(diff, pushResult.getCommit(), path,
+        expectedContentSideB);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 67af6e2..5b6f3f9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -32,9 +32,11 @@
 import com.google.gerrit.extensions.client.Comment;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ChangesCollection;
 import com.google.gerrit.server.change.PostReview;
@@ -148,8 +150,8 @@
   @Test
   public void postCommentOnMergeCommitChange() throws Exception {
     for (Integer line : lines) {
-      final String file = "/COMMIT_MSG";
-      PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
+      String file = "foo";
+      PushOneCommit.Result r = createMergeCommitChange("refs/for/master", file);
       String changeId = r.getChangeId();
       String revId = r.getCommit().getName();
       ReviewInput input = new ReviewInput();
@@ -165,6 +167,39 @@
       assertThat(Lists.transform(result.get(file), infoToInput(file)))
           .containsExactly(c1, c2, c3, c4);
     }
+
+    // for the commit message comments on the auto-merge are not possible
+    for (Integer line : lines) {
+      String file = Patch.COMMIT_MSG;
+      PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
+      String changeId = r.getChangeId();
+      String revId = r.getCommit().getName();
+      ReviewInput input = new ReviewInput();
+      CommentInput c1 = newComment(file, Side.REVISION, line, "ps-1");
+      CommentInput c2 = newCommentOnParent(file, 1, line, "parent-1 of ps-1");
+      CommentInput c3 = newCommentOnParent(file, 2, line, "parent-2 of ps-1");
+      input.comments = new HashMap<>();
+      input.comments.put(file, ImmutableList.of(c1, c2, c3));
+      revision(r).review(input);
+      Map<String, List<CommentInfo>> result = getPublishedComments(changeId, revId);
+      assertThat(result).isNotEmpty();
+      assertThat(Lists.transform(result.get(file), infoToInput(file)))
+          .containsExactly(c1, c2, c3);
+    }
+  }
+
+  @Test
+  public void postCommentOnCommitMessageOnAutoMerge() throws Exception {
+    PushOneCommit.Result r = createMergeCommitChange("refs/for/master");
+    ReviewInput input = new ReviewInput();
+    CommentInput c =
+        newComment(Patch.COMMIT_MSG, Side.PARENT, 0, "comment on auto-merge");
+    input.comments = new HashMap<>();
+    input.comments.put(Patch.COMMIT_MSG, ImmutableList.of(c));
+    exception.expect(BadRequestException.class);
+    exception.expectMessage(
+        "cannot comment on " + Patch.COMMIT_MSG + " on auto-merge");
+    revision(r).review(input);
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
index 06170d0..b843721 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/SubmittedTogetherIT.java
@@ -23,8 +23,11 @@
 import com.google.gerrit.acceptance.TestProjectInput;
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
 import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.FileInfo;
+import com.google.gerrit.extensions.common.RevisionInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.testutil.ConfigSuite;
@@ -44,6 +47,63 @@
   }
 
   @Test
+  public void doesNotIncludeCurrentFiles() throws Exception {
+    RevCommit c1_1 = commitBuilder()
+        .add("a.txt", "1")
+        .message("subject: 1")
+        .create();
+    RevCommit c2_1 = commitBuilder()
+        .add("b.txt", "2")
+        .message("subject: 2")
+        .create();
+    String id2 = getChangeId(c2_1);
+    pushHead(testRepo, "refs/for/master", false);
+
+    SubmittedTogetherInfo info =
+        gApi.changes()
+            .id(id2)
+            .submittedTogether(EnumSet.of(NON_VISIBLE_CHANGES));
+    assertThat(info.changes).hasSize(2);
+    assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
+    assertThat(info.changes.get(1).currentRevision).isEqualTo(c1_1.name());
+
+    assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
+    RevisionInfo rev = info.changes.get(0).revisions.get(c2_1.name());
+    assertThat(rev.files).isNull();
+  }
+
+  @Test
+  public void returnsCurrentFilesIfOptionRequested() throws Exception {
+    RevCommit c1_1 = commitBuilder()
+        .add("a.txt", "1")
+        .message("subject: 1")
+        .create();
+    RevCommit c2_1 = commitBuilder()
+        .add("b.txt", "2")
+        .message("subject: 2")
+        .create();
+    String id2 = getChangeId(c2_1);
+    pushHead(testRepo, "refs/for/master", false);
+
+    SubmittedTogetherInfo info =
+        gApi.changes()
+            .id(id2)
+            .submittedTogether(
+                EnumSet.of(ListChangesOption.CURRENT_FILES),
+                EnumSet.of(NON_VISIBLE_CHANGES));
+    assertThat(info.changes).hasSize(2);
+    assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
+    assertThat(info.changes.get(1).currentRevision).isEqualTo(c1_1.name());
+
+    assertThat(info.changes.get(0).currentRevision).isEqualTo(c2_1.name());
+    RevisionInfo rev = info.changes.get(0).revisions.get(c2_1.name());
+    assertThat(rev).isNotNull();
+    FileInfo file = rev.files.get("b.txt");
+    assertThat(file).isNotNull();
+    assertThat(file.status).isEqualTo('A');
+  }
+
+  @Test
   public void returnsAncestors() throws Exception {
     // Create two commits and push.
     RevCommit c1_1 = commitBuilder()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 4f14bb1..8606ce7 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -715,6 +715,8 @@
     rin.message = "comment";
 
     Timestamp ts = new Timestamp(c.getCreatedOn().getTime() + 2000);
+    assertThat(ts).isGreaterThan(c.getCreatedOn());
+    assertThat(ts).isLessThan(db.patchSets().get(psId).getCreatedOn());
     RevisionResource revRsrc = parseCurrentRevisionResource(r.getChangeId());
     postReview.get().apply(revRsrc, rin, ts);
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
index f656c2d..f860552 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/ChangeApi.java
@@ -97,6 +97,9 @@
   List<ChangeInfo> submittedTogether() throws RestApiException;
   SubmittedTogetherInfo submittedTogether(
       EnumSet<SubmittedTogetherOption> options) throws RestApiException;
+  SubmittedTogetherInfo submittedTogether(
+      EnumSet<ListChangesOption> listOptions,
+      EnumSet<SubmittedTogetherOption> submitOptions) throws RestApiException;
 
   /**
    * Publishes a draft change.
@@ -360,5 +363,12 @@
         EnumSet<SubmittedTogetherOption> options) throws RestApiException {
       throw new NotImplementedException();
     }
+
+    @Override
+    public SubmittedTogetherInfo submittedTogether(
+        EnumSet<ListChangesOption> a,
+        EnumSet<SubmittedTogetherOption> b) throws RestApiException {
+      throw new NotImplementedException();
+    }
   }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmittedTogetherOption.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmittedTogetherOption.java
index 8649e91f..e2cab4d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmittedTogetherOption.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/SubmittedTogetherOption.java
@@ -16,15 +16,5 @@
 
 /** Output options available for submitted_together requests. */
 public enum SubmittedTogetherOption {
-  NON_VISIBLE_CHANGES(0);
-
-  private final int value;
-
-  SubmittedTogetherOption(int v) {
-    value = v;
-  }
-
-  public int getValue() {
-    return value;
-  }
+  NON_VISIBLE_CHANGES;
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
index 7c8a3e8..78ffb82 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Comment.java
@@ -59,6 +59,13 @@
     }
   }
 
+  public short side() {
+    if (side == Side.PARENT) {
+      return (short) (parent == null ? 0 : -parent.shortValue());
+    }
+    return 1;
+  }
+
   @Override
   public boolean equals(Object o) {
     if (this == o) {
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index 4485430..d4d97a6 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -41,7 +41,7 @@
   binary_jar = ':diffy_image_files_ln',
   deps = [
     '//lib:LICENSE-diffy',
-    '//lib:LICENSE-CC-BY3.0',
+    '//lib:LICENSE-CC-BY3.0-unported',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
index 9b290a5..b21126d 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/FileInfo.java
@@ -56,6 +56,12 @@
         } else if (Patch.COMMIT_MSG.equals(b.path())) {
           return 1;
         }
+        if (Patch.MERGE_LIST.equals(a.path())) {
+          return -1;
+        } else if (Patch.MERGE_LIST.equals(b.path())) {
+          return 1;
+        }
+
         // Look at file suffixes to check if it makes sense to use a different order
         int s1 = a.path().lastIndexOf('.');
         int s2 = b.path().lastIndexOf('.');
@@ -76,9 +82,15 @@
   }
 
   public static String getFileName(String path) {
-    String fileName = Patch.COMMIT_MSG.equals(path)
-        ? "Commit Message"
-        : path;
+    String fileName;
+    if (Patch.COMMIT_MSG.equals(path)) {
+      fileName = "Commit Message";
+    } else if (Patch.MERGE_LIST.equals(path)) {
+      fileName = "Merge List";
+    } else {
+      fileName = path;
+    }
+
     int s = fileName.lastIndexOf('/');
     return s >= 0 ? fileName.substring(s + 1) : fileName;
   }
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 3ab6187..63d52b0 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -47,7 +47,6 @@
   name = 'silk_icons',
   deps = [
     '//lib:LICENSE-silk_icons',
-    '//lib:LICENSE-CC-BY3.0',
   ],
 )
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
index 2b4c821..cb2ac07 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Dispatcher.java
@@ -486,7 +486,11 @@
     } else if ("unified".equals(panel)) {
       unified(token, baseId, id, side, line);
     } else if ("edit".equals(panel)) {
-      codemirrorForEdit(token, id, line);
+      if (!Patch.isMagic(id.get()) || Patch.COMMIT_MSG.equals(id.get())) {
+        codemirrorForEdit(token, id, line);
+      } else {
+        Gerrit.display(token, new NotFoundScreen());
+      }
     } else {
       Gerrit.display(token, new NotFoundScreen());
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
index 54c5b92..24a7ed0 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/SearchSuggestOracle.java
@@ -31,7 +31,7 @@
           new ProjectNameSuggestOracle()),
       new ParamSuggester(Arrays.asList(
           "owner:", "reviewer:", "commentby:", "reviewedby:", "author:",
-          "committer:", "from:"),
+          "committer:", "from:", "assignee:"),
           new AccountSuggestOracle() {
             @Override
             public void onRequestSuggestions(final Request request, final Callback done) {
@@ -120,6 +120,8 @@
     suggestions.add("is:merged");
     suggestions.add("is:abandoned");
     suggestions.add("is:mergeable");
+    suggestions.add("is:assigned");
+    suggestions.add("is:unassigned");
 
     suggestions.add("status:");
     suggestions.add("status:open");
@@ -139,6 +141,8 @@
       suggestions.add("hashtag:");
     }
 
+    suggestions.add("assignee:");
+
     suggestions.add("AND");
     suggestions.add("OR");
     suggestions.add("NOT");
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
index c0879e7..3a85b26 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/FileTable.java
@@ -267,11 +267,18 @@
     if (table != null) {
       String self = Gerrit.selfRedirect(null);
       for (FileInfo info : Natives.asList(table.list)) {
-        Window.open(self + "#" + url(info), "_blank", null);
+        if (canOpen(info.path())) {
+          Window.open(self + "#" + url(info), "_blank", null);
+        }
       }
     }
   }
 
+  private boolean canOpen(String path) {
+    return mode != Mode.EDIT || !Patch.isMagic(path)
+        || Patch.COMMIT_MSG.equals(path);
+  }
+
   private void setTable(MyTable table) {
     clear();
     add(table);
@@ -429,7 +436,10 @@
     @Override
     protected void onOpenRow(int row) {
       if (1 <= row && row <= list.length()) {
-        Gerrit.display(url(list.get(row - 1)));
+        FileInfo info = list.get(row - 1);
+        if (canOpen(info.path())) {
+          Gerrit.display(url(info));
+        }
       }
     }
 
@@ -452,7 +462,10 @@
 
       @Override
       public void onKeyPress(KeyPressEvent event) {
-        Gerrit.display(url(list.get(index)));
+        FileInfo info = list.get(index);
+        if (canOpen(info.path())) {
+          Gerrit.display(url(info));
+        }
       }
     }
   }
@@ -538,7 +551,7 @@
       bytesDeleted = 0;
       for (int i = 0; i < list.length(); i++) {
         FileInfo info = list.get(i);
-        if (!Patch.COMMIT_MSG.equals(info.path())) {
+        if (!Patch.isMagic(info.path())) {
           if (!info.binary()) {
             hasNonBinaryFile = true;
             inserted += info.linesInserted();
@@ -628,7 +641,7 @@
     private void columnDeleteRestore(SafeHtmlBuilder sb, FileInfo info) {
       sb.openTd().setStyleName(R.css().restoreDelete());
       if (hasUser) {
-        if (!Patch.COMMIT_MSG.equals(info.path())) {
+        if (!Patch.isMagic(info.path())) {
           boolean editable = isEditable(info);
           sb.openDiv()
             .openElement("button")
@@ -659,7 +672,7 @@
 
     private void columnStatus(SafeHtmlBuilder sb, FileInfo info) {
       sb.openTd().setStyleName(R.css().status());
-      if (!Patch.COMMIT_MSG.equals(info.path())
+      if (!Patch.isMagic(info.path())
           && info.status() != null
           && !ChangeType.MODIFIED.matches(info.status())) {
         sb.append(info.status());
@@ -668,20 +681,43 @@
     }
 
     private void columnPath(SafeHtmlBuilder sb, FileInfo info) {
-      sb.openTd()
-        .setStyleName(R.css().pathColumn())
-        .openAnchor();
-
       String path = info.path();
+
+      sb.openTd()
+        .setStyleName(R.css().pathColumn());
+
+      if (!canOpen(path)) {
+        sb.openDiv();
+        appendPath(path);
+        sb.closeDiv();
+        sb.closeTd();
+        return;
+      }
+
+      sb.openAnchor();
+
       if (mode == Mode.EDIT && !isEditable(info)) {
         sb.setAttribute("onclick", RESTORE + "(event," + info._row() + ")");
       } else {
         sb.setAttribute("href", "#" + url(info))
           .setAttribute("onclick", OPEN + "(event," + info._row() + ")");
       }
+      appendPath(path);
+      sb.closeAnchor();
+      if (info.oldPath() != null) {
+        sb.br();
+        sb.openSpan().setStyleName(R.css().renameCopySource())
+          .append(info.oldPath())
+          .closeSpan();
+      }
+      sb.closeTd();
+    }
 
+    private void appendPath(String path) {
       if (Patch.COMMIT_MSG.equals(path)) {
         sb.append(Util.C.commitMessage());
+      } else if (Patch.MERGE_LIST.equals(path)) {
+        sb.append(Util.C.mergeList());
       } else if (Gerrit.getUserPreferences().muteCommonPathPrefixes()) {
         int commonPrefixLen = commonPrefix(path);
         if (commonPrefixLen > 0) {
@@ -694,15 +730,6 @@
       } else {
         sb.append(path);
       }
-
-      sb.closeAnchor();
-      if (info.oldPath() != null) {
-        sb.br();
-        sb.openSpan().setStyleName(R.css().renameCopySource())
-          .append(info.oldPath())
-          .closeSpan();
-      }
-      sb.closeTd();
     }
 
     private int commonPrefix(String path) {
@@ -784,7 +811,7 @@
 
     private void columnDelta1(SafeHtmlBuilder sb, FileInfo info) {
       sb.openTd().setStyleName(R.css().deltaColumn1());
-      if (!Patch.COMMIT_MSG.equals(info.path()) && !info.binary()) {
+      if (!Patch.isMagic(info.path()) && !info.binary()) {
         if (showChangeSizeBars) {
           sb.append(info.linesInserted() + info.linesDeleted());
         } else if (!ChangeType.DELETED.matches(info.status())) {
@@ -813,7 +840,7 @@
     private void columnDelta2(SafeHtmlBuilder sb, FileInfo info) {
       sb.openTd().setStyleName(R.css().deltaColumn2());
       if (showChangeSizeBars
-          && !Patch.COMMIT_MSG.equals(info.path()) && !info.binary()
+          && !Patch.isMagic(info.path()) && !info.binary()
           && (info.linesInserted() != 0 || info.linesDeleted() != 0)) {
         int w = 80;
         int t = inserted + deleted;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
index 6c27ed9..c8735b7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/Message.java
@@ -176,6 +176,10 @@
     if (l != null) {
       comments.add(new FileComments(clp, ps, Util.C.commitMessage(), l));
     }
+    l = m.remove(Patch.MERGE_LIST);
+    if (l != null) {
+      comments.add(new FileComments(clp, ps, Util.C.mergeList(), l));
+    }
     for (Map.Entry<String, List<CommentInfo>> e : m.entrySet()) {
       comments.add(new FileComments(clp, ps, e.getKey(), e.getValue()));
     }
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 e29048a..b985f43 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
@@ -422,12 +422,17 @@
       comments.add(new FileComments(clp, psId,
           Util.C.commitMessage(), copyPath(Patch.COMMIT_MSG, l)));
     }
+    l = m.get(Patch.MERGE_LIST);
+    if (l != null) {
+      comments.add(new FileComments(clp, psId, Util.C.commitMessage(),
+          copyPath(Patch.MERGE_LIST, l)));
+    }
 
     List<String> paths = new ArrayList<>(m.keySet());
     Collections.sort(paths);
 
     for (String path : paths) {
-      if (!path.equals(Patch.COMMIT_MSG)) {
+      if (!Patch.isMagic(path)) {
         comments.add(new FileComments(clp, psId,
             path, copyPath(path, m.get(path))));
       }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
index b2334d1d..4a9eea4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.java
@@ -63,6 +63,7 @@
   String patchTableColumnComments();
   String patchTableColumnSize();
   String commitMessage();
+  String mergeList();
 
   String patchTablePrev();
   String patchTableNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
index b7e2677..675cd07 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeConstants.properties
@@ -45,6 +45,7 @@
 patchTableColumnComments = Comments
 patchTableColumnSize = Size
 commitMessage = Commit Message
+mergeList = Merge List
 
 patchTablePrev = Previous file
 patchTableNext = Next file
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
index f377038..3033cbb1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/Header.java
@@ -122,6 +122,8 @@
     SafeHtmlBuilder b = new SafeHtmlBuilder();
     if (Patch.COMMIT_MSG.equals(path)) {
       return b.append(Util.C.commitMessage());
+    } else if (Patch.MERGE_LIST.equals(path)) {
+      return b.append(Util.C.mergeList());
     }
 
     int s = path.lastIndexOf('/') + 1;
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 bc37abb..d45b8d8 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
@@ -128,7 +128,7 @@
     if (meta == null) {
       return;
     }
-    if (!Patch.COMMIT_MSG.equals(path)) {
+    if (!Patch.isMagic(path)) {
       linkPanel.add(createDownloadLink());
     }
     if (!binary && open && idActive != null && Gerrit.isSignedIn()) {
@@ -147,7 +147,7 @@
 
   void setUpBlame(final CodeMirror cm, final boolean isBase,
       final PatchSet.Id rev, final String path) {
-    if (!Patch.COMMIT_MSG.equals(path) && Gerrit.isSignedIn()
+    if (!Patch.isMagic(path) && Gerrit.isSignedIn()
         && Gerrit.info().change().allowBlame()) {
       Anchor blameIcon = createBlameIcon();
       blameIcon.addClickHandler(new ClickHandler() {
diff --git a/gerrit-pgm/BUILD b/gerrit-pgm/BUILD
index ec86c9a..895b5f1 100644
--- a/gerrit-pgm/BUILD
+++ b/gerrit-pgm/BUILD
@@ -1,5 +1,6 @@
 load('//tools/bzl:java.bzl', 'java_library2')
 load('//tools/bzl:junit.bzl', 'junit_tests')
+load('//tools/bzl:license.bzl', 'license_test')
 
 SRCS = 'src/main/java/com/google/gerrit/pgm/'
 RSRCS = 'src/main/resources/com/google/gerrit/pgm/'
@@ -160,3 +161,7 @@
     '//lib/jgit/org.eclipse.jgit.junit:junit',
   ],
 )
+
+license_test(
+  name = "pgm_license_test",
+  target = ":pgm")
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
index 6a55965..309bda4 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -22,6 +22,22 @@
   /** Magical file name which represents the commit message. */
   public static final String COMMIT_MSG = "/COMMIT_MSG";
 
+  /** Magical file name which represents the merge list of a merge commit. */
+  public static final String MERGE_LIST = "/MERGE_LIST";
+
+  /**
+   * Checks if the given path represents a magic file. A magic file is a
+   * generated file that is automatically included into changes. It does not
+   * exist in the commit of the patch set.
+   *
+   * @param path the file path
+   * @return {@code true} if the path represents a magic file, otherwise
+   *         {@code false}.
+   */
+  public static boolean isMagic(String path) {
+    return COMMIT_MSG.equals(path) || MERGE_LIST.equals(path);
+  }
+
   public static class Key extends StringKey<PatchSet.Id> {
     private static final long serialVersionUID = 1L;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
index 732a9b8..68065cb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
@@ -20,6 +20,7 @@
 
 import com.google.common.base.Optional;
 import com.google.common.collect.ComparisonChain;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
@@ -27,6 +28,7 @@
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -37,7 +39,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.notedb.DraftCommentNotes;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
@@ -112,17 +113,14 @@
 
   private final GitRepositoryManager repoManager;
   private final AllUsersName allUsers;
-  private final DraftCommentNotes.Factory draftFactory;
   private final NotesMigration migration;
 
   @Inject
   PatchLineCommentsUtil(GitRepositoryManager repoManager,
       AllUsersName allUsers,
-      DraftCommentNotes.Factory draftFactory,
       NotesMigration migration) {
     this.repoManager = repoManager;
     this.allUsers = allUsers;
-    this.draftFactory = draftFactory;
     this.migration = migration;
   }
 
@@ -210,10 +208,27 @@
   public List<PatchLineComment> publishedByPatchSet(ReviewDb db,
       ChangeNotes notes, PatchSet.Id psId) throws OrmException {
     if (!migration.readChanges()) {
-      return sort(
-          db.patchComments().publishedByPatchSet(psId).toList());
+      return removeCommentsOnAncestorOfCommitMessage(sort(
+          db.patchComments().publishedByPatchSet(psId).toList()));
     }
-    return commentsOnPatchSet(notes.load().getComments().values(), psId);
+    return removeCommentsOnAncestorOfCommitMessage(
+        commentsOnPatchSet(notes.load().getComments().values(), psId));
+  }
+
+  /**
+   * For the commit message the A side in a diff view is always empty when a
+   * comparison against an ancestor is done, so there can't be any comments on
+   * this ancestor. However earlier we showed the auto-merge commit message on
+   * side A when for a merge commit a comparison against the auto-merge was
+   * done. From that time there may still be comments on the auto-merge commit
+   * message and those we want to filter out.
+   */
+  private List<PatchLineComment> removeCommentsOnAncestorOfCommitMessage(
+      List<PatchLineComment> list) {
+    return list.stream()
+        .filter(c -> c.getSide() != 0
+            || !Patch.COMMIT_MSG.equals(c.getKey().getParentKey().get()))
+        .collect(toList());
   }
 
   public List<PatchLineComment> draftByPatchSetAuthor(ReviewDb db,
@@ -257,13 +272,14 @@
   }
 
   @Deprecated // To be used only by HasDraftByLegacyPredicate.
-  public List<PatchLineComment> draftByAuthor(ReviewDb db,
+  public List<Change.Id> changesWithDraftsByAuthor(ReviewDb db,
       Account.Id author) throws OrmException {
     if (!migration.readChanges()) {
-      return sort(db.patchComments().draftByAuthor(author).toList());
+      return FluentIterable.from(db.patchComments().draftByAuthor(author))
+          .transform(plc -> plc.getPatchSetId().getParentKey()).toList();
     }
 
-    List<PatchLineComment> comments = new ArrayList<>();
+    List<Change.Id> changes = new ArrayList<>();
     try (Repository repo = repoManager.openRepository(allUsers)) {
       for (String refName : repo.getRefDatabase()
           .getRefs(RefNames.REFS_DRAFT_COMMENTS).keySet()) {
@@ -272,17 +288,12 @@
         if (accountId == null || changeId == null) {
           continue;
         }
-        // Avoid loading notes for all affected changes just to be able to auto-
-        // rebuild. This is only used in a corner case in the search codepath,
-        // so returning slightly stale values is ok.
-        DraftCommentNotes notes =
-            draftFactory.createWithAutoRebuildingDisabled(changeId, author);
-        comments.addAll(notes.load().getComments().values());
+        changes.add(changeId);
       }
     } catch (IOException e) {
       throw new OrmException(e);
     }
-    return sort(comments);
+    return changes;
   }
 
   public void putComments(ReviewDb db, ChangeUpdate update,
@@ -348,7 +359,7 @@
     return sort(result);
   }
 
-  public static RevId setCommentRevId(PatchLineComment c,
+  public static void setCommentRevId(PatchLineComment c,
       PatchListCache cache, Change change, PatchSet ps) throws OrmException {
     checkArgument(c.getPatchSetId().equals(ps.getId()),
         "cannot set RevId for patch set %s on comment %s", ps.getId(), c);
@@ -369,7 +380,6 @@
         throw new OrmException(e);
       }
     }
-    return c.getRevId();
   }
 
   public Collection<Ref> getDraftRefs(Change.Id changeId)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index a818edd..5941c10 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -68,7 +68,7 @@
 public class ReviewersUtil {
   private static final String MAX_SUFFIX = "\u9fa5";
   private static final Ordering<SuggestedReviewerInfo> ORDERING =
-      Ordering.from(comparing(
+      Ordering.<SuggestedReviewerInfo> from(comparing(
           suggestedReviewerInfo -> {
             if (suggestedReviewerInfo == null) {
               return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 9bfb342..9c12348 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -61,6 +61,7 @@
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.assistedinject.Assisted;
 
 import java.io.IOException;
@@ -84,7 +85,7 @@
   private final Abandon abandon;
   private final Revert revert;
   private final Restore restore;
-  private final SubmittedTogether submittedTogether;
+  private final Provider<SubmittedTogether> submittedTogether;
   private final PublishDraftPatchSet.CurrentRevision
     publishDraftChange;
   private final DeleteDraftChange deleteDraftChange;
@@ -111,7 +112,7 @@
       Abandon abandon,
       Revert revert,
       Restore restore,
-      SubmittedTogether submittedTogether,
+      Provider<SubmittedTogether> submittedTogether,
       PublishDraftPatchSet.CurrentRevision publishDraftChange,
       DeleteDraftChange deleteDraftChange,
       GetTopic getTopic,
@@ -248,21 +249,29 @@
     }
   }
 
-  @SuppressWarnings("unchecked")
   @Override
   public List<ChangeInfo> submittedTogether() throws RestApiException {
-    try {
-      return (List<ChangeInfo>) submittedTogether.apply(change);
-    } catch (IOException | OrmException e) {
-      throw new RestApiException("Cannot query submittedTogether", e);
-    }
+    SubmittedTogetherInfo info = submittedTogether(
+        EnumSet.noneOf(ListChangesOption.class),
+        EnumSet.noneOf(SubmittedTogetherOption.class));
+    return info.changes;
   }
 
   @Override
   public SubmittedTogetherInfo submittedTogether(
       EnumSet<SubmittedTogetherOption> options) throws RestApiException {
+    return submittedTogether(EnumSet.noneOf(ListChangesOption.class), options);
+  }
+
+  @Override
+  public SubmittedTogetherInfo submittedTogether(
+      EnumSet<ListChangesOption> listOptions,
+      EnumSet<SubmittedTogetherOption> submitOptions) throws RestApiException {
     try {
-      return submittedTogether.apply(change, options);
+      return submittedTogether.get()
+          .addListChangesOption(listOptions)
+          .addSubmittedTogetherOption(submitOptions)
+          .applyInfo(change);
     } catch (IOException | OrmException e) {
       throw new RestApiException("Cannot query submittedTogether", e);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index e8ccd25..6906fb2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.reviewdb.client.Change.INITIAL_PATCH_SET_ID;
-
 import static java.util.stream.Collectors.toSet;
 
 import com.google.common.base.MoreObjects;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 9bd5e44..d8078ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -34,7 +34,6 @@
 import static com.google.gerrit.extensions.client.ListChangesOption.REVIEWER_UPDATES;
 import static com.google.gerrit.extensions.client.ListChangesOption.WEB_LINKS;
 import static com.google.gerrit.server.CommonConverters.toGitPerson;
-
 import static java.util.stream.Collectors.toList;
 
 import com.google.auto.value.AutoValue;
@@ -1056,6 +1055,7 @@
     if (has(ALL_FILES) || (out.isCurrent && has(CURRENT_FILES))) {
       out.files = fileInfoJson.toFileInfoMap(c, in);
       out.files.remove(Patch.COMMIT_MSG);
+      out.files.remove(Patch.MERGE_LIST);
     }
 
     if ((out.isCurrent || (out.draft != null && out.draft))
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
index 7cb2aac..1af87b3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.change;
 
 import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
-import static com.google.gerrit.server.change.PutDraftComment.side;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.TimeUtil;
@@ -119,7 +118,7 @@
               ChangeUtil.messageUUID(ctx.getDb())),
           line, ctx.getAccountId(), Url.decode(in.inReplyTo),
           ctx.getWhen());
-      comment.setSide(side(in));
+      comment.setSide(in.side());
       comment.setMessage(in.message.trim());
       comment.setRange(in.range);
       comment.setTag(in.tag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index 1f78d54..099f8e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -228,7 +228,8 @@
             approvalsUtil.byChange(ctx.getDb(), ctx.getNotes()).values();
       }
 
-      return Iterables.filter(approvals, accountId::equals);
+      return Iterables.filter(
+          approvals, psa -> accountId.equals(psa.getAccountId()));
     }
 
     private String formatLabelValue(short value) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
index d145ddf..d617a70 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileContentUtil.java
@@ -54,6 +54,7 @@
 @Singleton
 public class FileContentUtil {
   public static final String TEXT_X_GERRIT_COMMIT_MESSAGE = "text/x-gerrit-commit-message";
+  public static final String TEXT_X_GERRIT_MERGE_LIST = "text/x-gerrit-merge-list";
   private static final String X_GIT_SYMLINK = "x-git/symlink";
   private static final String X_GIT_GITLINK = "x-git/gitlink";
   private static final int MAX_SIZE = 5 << 20;
@@ -264,6 +265,9 @@
         if (Patch.COMMIT_MSG.equals(path)) {
           return TEXT_X_GERRIT_COMMIT_MESSAGE;
         }
+        if (Patch.MERGE_LIST.equals(path)) {
+          return TEXT_X_GERRIT_MERGE_LIST;
+        }
         if (project != null) {
           for (ProjectState p : project.tree()) {
             String t = p.getConfig().getMimeTypes().getMimeType(path);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index 5a546f3..c7044e1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -24,6 +24,8 @@
 import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.patch.ComparisonType;
+import com.google.gerrit.server.patch.Text;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -68,6 +70,12 @@
       return BinaryResult.create(msg)
           .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
           .base64();
+    } else if (Patch.MERGE_LIST.equals(path)) {
+      byte[] mergeList = getMergeList(
+          rsrc.getRevision().getChangeResource().getNotes());
+      return BinaryResult.create(mergeList)
+          .setContentType(FileContentUtil.TEXT_X_GERRIT_MERGE_LIST)
+          .base64();
     }
     return fileContentUtil.getContent(
         rsrc.getRevision().getControl().getProjectControl().getProjectState(),
@@ -92,4 +100,22 @@
       throw new NoSuchChangeException(changeId, e);
     }
   }
+
+  private byte[] getMergeList(ChangeNotes notes)
+      throws NoSuchChangeException, OrmException, IOException {
+    Change.Id changeId = notes.getChangeId();
+    PatchSet ps = psUtil.current(db.get(), notes);
+    if (ps == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    try (Repository git = gitManager.openRepository(notes.getProjectName());
+        RevWalk revWalk = new RevWalk(git)) {
+      return Text.forMergeList(ComparisonType.againstAutoMerge(),
+          revWalk.getObjectReader(),
+          ObjectId.fromString(ps.getRevision().get())).getContent();
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeId, e);
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
index 4e94935..b15810c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetMergeList.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.patch.MergeListBuilder;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.ObjectId;
@@ -46,7 +47,8 @@
   private boolean addLinks;
 
   @Inject
-  GetMergeList(GitRepositoryManager repoManager, ChangeJson.Factory json) {
+  GetMergeList(GitRepositoryManager repoManager,
+      ChangeJson.Factory json) {
     this.repoManager = repoManager;
     this.json = json;
   }
@@ -62,7 +64,6 @@
   @Override
   public Response<List<CommitInfo>> apply(RevisionResource rsrc)
       throws BadRequestException, IOException {
-    List<CommitInfo> result = new ArrayList<>();
     Project.NameKey p = rsrc.getChange().getProject();
     try (Repository repo = repoManager.openRepository(p);
         RevWalk rw = new RevWalk(repo)) {
@@ -76,26 +77,22 @@
       }
 
       if (commit.getParentCount() < 2) {
-        return Response.<List<CommitInfo>> ok(ImmutableList.<CommitInfo> of());
+        return createResponse(rsrc, ImmutableList.<CommitInfo> of());
       }
 
-      for (int parent = 0; parent < commit.getParentCount(); parent++) {
-        if (parent == uninterestingParent - 1) {
-          rw.markUninteresting(commit.getParent(parent));
-        } else {
-          rw.markStart(commit.getParent(parent));
-        }
-      }
-
+      List<RevCommit> commits =
+          MergeListBuilder.build(rw, commit, uninterestingParent);
+      List<CommitInfo> result = new ArrayList<>(commits.size());
       ChangeJson changeJson = json.create(ChangeJson.NO_OPTIONS);
-      RevCommit c;
-      while ((c = rw.next()) != null) {
-        CommitInfo info =
-            changeJson.toCommit(rsrc.getControl(), rw, c, addLinks, true);
-        result.add(info);
+      for (RevCommit c : commits) {
+        result.add(changeJson.toCommit(rsrc.getControl(), rw, c, addLinks, true));
       }
+      return createResponse(rsrc, result);
     }
+  }
 
+  private static Response<List<CommitInfo>> createResponse(
+      RevisionResource rsrc, List<CommitInfo> result) {
     Response<List<CommitInfo>> r = Response.ok(result);
     if (rsrc.isCacheable()) {
       r.caching(CacheControl.PRIVATE(7, TimeUnit.DAYS));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 3828041..d6819ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
-import static com.google.gerrit.server.change.PutDraftComment.side;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
@@ -324,11 +323,19 @@
     while (mapItr.hasNext()) {
       Map.Entry<String, List<CommentInput>> ent = mapItr.next();
       String path = ent.getKey();
-      if (!filePaths.contains(path) && !Patch.COMMIT_MSG.equals(path)) {
+      if (!filePaths.contains(path) && !Patch.isMagic(path)) {
         throw new BadRequestException(String.format(
             "file %s not found in revision %s",
             path, revision.getChange().currentPatchSetId()));
       }
+      if (Patch.isMagic(path)) {
+        for (CommentInput comment : ent.getValue()) {
+          if (comment.side == Side.PARENT && comment.parent == null) {
+            throw new BadRequestException(
+                String.format("cannot comment on %s on auto-merge", path));
+          }
+        }
+      }
 
       List<CommentInput> list = ent.getValue();
       if (list == null) {
@@ -476,7 +483,7 @@
           }
           e.setStatus(PatchLineComment.Status.PUBLISHED);
           e.setWrittenOn(ctx.getWhen());
-          e.setSide(side(c));
+          e.setSide(c.side());
           setCommentRevId(e, patchListCache, ctx.getChange(), ps);
           e.setMessage(c.message);
           e.setTag(in.tag);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index 655e07d..e817d93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -19,8 +19,6 @@
 import com.google.common.base.Optional;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.extensions.api.changes.DraftInput;
-import com.google.gerrit.extensions.client.Comment;
-import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -164,7 +162,7 @@
   private static PatchLineComment update(PatchLineComment e, DraftInput in,
       Timestamp when) {
     if (in.side != null) {
-      e.setSide(side(in));
+      e.setSide(in.side());
     }
     if (in.inReplyTo != null) {
       e.setParentUuid(Url.decode(in.inReplyTo));
@@ -181,11 +179,4 @@
     }
     return e;
   }
-
-  static short side(Comment c) {
-    if (c.side == Side.PARENT) {
-      return (short) (c.parent == null ? 0 : -c.parent.shortValue());
-    }
-    return 1;
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
index 351ebf9..908929a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SubmittedTogether.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.gerrit.extensions.api.changes.SubmittedTogetherOption.NON_VISIBLE_CHANGES;
+
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherInfo;
 import com.google.gerrit.extensions.api.changes.SubmittedTogetherOption;
 import com.google.gerrit.extensions.client.ChangeStatus;
@@ -32,7 +34,6 @@
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
-import com.google.inject.Singleton;
 
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
@@ -44,13 +45,17 @@
 import java.util.EnumSet;
 import java.util.List;
 
-@Singleton
 public class SubmittedTogether implements RestReadView<ChangeResource> {
   private static final Logger log = LoggerFactory.getLogger(
       SubmittedTogether.class);
 
   private final EnumSet<SubmittedTogetherOption> options =
       EnumSet.noneOf(SubmittedTogetherOption.class);
+
+  private final EnumSet<ListChangesOption> jsonOpt = EnumSet.of(
+      ListChangesOption.CURRENT_REVISION,
+      ListChangesOption.CURRENT_COMMIT);
+
   private final ChangeJson.Factory json;
   private final Provider<ReviewDb> dbProvider;
   private final Provider<InternalChangeQuery> queryProvider;
@@ -58,8 +63,22 @@
   private final Provider<WalkSorter> sorter;
 
   @Option(name = "-o", usage = "Output options")
-  void addOption(SubmittedTogetherOption o) {
-    options.add(o);
+  void addOption(String option) {
+    for (ListChangesOption o : ListChangesOption.values()) {
+      if (o.name().equalsIgnoreCase(option)) {
+        jsonOpt.add(o);
+        return;
+      }
+    }
+
+    for (SubmittedTogetherOption o : SubmittedTogetherOption.values()) {
+      if (o.name().equalsIgnoreCase(option)) {
+        options.add(o);
+        return;
+      }
+    }
+
+    throw new IllegalArgumentException("option not recognized: " + option);
   }
 
   @Inject
@@ -75,19 +94,29 @@
     this.sorter = sorter;
   }
 
+  public SubmittedTogether addListChangesOption(EnumSet<ListChangesOption> o) {
+    jsonOpt.addAll(o);
+    return this;
+  }
+
+  public SubmittedTogether addSubmittedTogetherOption(
+      EnumSet<SubmittedTogetherOption> o) {
+    options.addAll(o);
+    return this;
+  }
+
   @Override
   public Object apply(ChangeResource resource)
       throws AuthException, BadRequestException,
       ResourceConflictException, IOException, OrmException {
-    SubmittedTogetherInfo info = apply(resource, options);
+    SubmittedTogetherInfo info = applyInfo(resource);
     if (options.isEmpty()) {
       return info.changes;
     }
     return info;
   }
 
-  public SubmittedTogetherInfo apply(ChangeResource resource,
-      EnumSet<SubmittedTogetherOption> options)
+  public SubmittedTogetherInfo applyInfo(ChangeResource resource)
       throws AuthException, IOException, OrmException {
     Change c = resource.getChange();
     try {
@@ -109,7 +138,7 @@
       }
 
       if (hidden != 0
-          && !options.contains(SubmittedTogetherOption.NON_VISIBLE_CHANGES)) {
+          && !options.contains(NON_VISIBLE_CHANGES)) {
         throw new AuthException(
             "change would be submitted with a change that you cannot see");
       }
@@ -123,9 +152,7 @@
       }
 
       SubmittedTogetherInfo info = new SubmittedTogetherInfo();
-      info.changes = json.create(EnumSet.of(
-          ListChangesOption.CURRENT_REVISION,
-          ListChangesOption.CURRENT_COMMIT))
+      info.changes = json.create(jsonOpt)
         .includeSubmittable(true)
         .formatChangeDatas(cds);
       info.nonVisibleChanges = hidden;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 676f9ff..44b5979 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -63,6 +63,14 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.ArrayList;
@@ -71,12 +79,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 @Singleton
 public class EventFactory {
@@ -496,7 +498,7 @@
       List<Patch> list =
           patchListCache.get(change, patchSet).toPatchList(pId);
       for (Patch pe : list) {
-        if (!Patch.COMMIT_MSG.equals(pe.getFileName())) {
+        if (!Patch.isMagic(pe.getFileName())) {
           p.sizeDeletions -= pe.getDeletions();
           p.sizeInsertions += pe.getInsertions();
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index f917ba1..b35de7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -50,6 +50,15 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -58,13 +67,6 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 abstract class SubmitStrategyOp extends BatchUpdate.Op {
   private static final Logger log =
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index c9706a5..0369695 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.index.change;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toSet;
 
@@ -75,6 +74,8 @@
  * characters.
  */
 public class ChangeField {
+  public static final int NO_ASSIGNEE = -1;
+
   /** Legacy change ID. */
   public static final FieldDef<ChangeData, Integer> LEGACY_ID =
       new FieldDef.Single<ChangeData, Integer>("legacy_id",
@@ -315,6 +316,18 @@
         }
       };
 
+  /** The user assigned to the change. */
+  public static final FieldDef<ChangeData, Integer> ASSIGNEE =
+      new FieldDef.Single<ChangeData, Integer>(
+          ChangeQueryBuilder.FIELD_ASSIGNEE, FieldType.INTEGER, true) {
+        @Override
+        public Integer get(ChangeData input, FillArgs args)
+            throws OrmException {
+          Account.Id id = input.assignee();
+          return id != null ? id.get() : NO_ASSIGNEE;
+        }
+      };
+
   /** Reviewer(s) associated with the change. */
   public static final FieldDef<ChangeData, Iterable<String>> REVIEWER =
       new FieldDef.Repeatable<ChangeData, String>(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
index c98d311..1ffc39c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeSchemaDefinitions.java
@@ -81,13 +81,17 @@
       .remove(ChangeField.STARREDBY)
       .build();
 
-  @SuppressWarnings("deprecation")
+  @Deprecated
   static final Schema<ChangeData> V32 = new Schema.Builder<ChangeData>()
       .add(V31)
       .remove(ChangeField.LEGACY_REVIEWER)
       .add(ChangeField.REVIEWER)
       .build();
 
+  @SuppressWarnings("deprecation")
+  static final Schema<ChangeData> V33 =
+      schema(V32, ChangeField.ASSIGNEE);
+
   public static final String NAME = "changes";
   public static final ChangeSchemaDefinitions INSTANCE =
       new ChangeSchemaDefinitions();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
index cbd2ec9..df0f018 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
@@ -259,7 +259,7 @@
         detail.append("---\n");
         PatchList patchList = getPatchList();
         for (PatchListEntry p : patchList.getPatches()) {
-          if (Patch.COMMIT_MSG.equals(p.getNewName())) {
+          if (Patch.isMagic(p.getNewName())) {
             continue;
           }
           detail.append(p.getChangeType().getCode())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
index 2424a6d..c4b6869 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
@@ -75,7 +75,7 @@
     Set<String> paths = new HashSet<>();
     for (PatchLineComment c : plc) {
       Patch.Key p = c.getKey().getParentKey();
-      if (!Patch.COMMIT_MSG.equals(p.getFileName())) {
+      if (!Patch.isMagic(p.getFileName())) {
         paths.add(p.getFileName());
       }
     }
@@ -137,6 +137,8 @@
           }
           if (Patch.COMMIT_MSG.equals(pk.get())) {
             cmts.append("Commit Message:\n\n");
+          } else if (Patch.MERGE_LIST.equals(pk.get())) {
+            cmts.append("Merge List:\n\n");
           } else {
             cmts.append("File ").append(pk.get()).append(":\n\n");
           }
@@ -144,8 +146,7 @@
 
           if (patchList != null) {
             try {
-              currentFileData =
-                  new PatchFile(repo, patchList, pk.get());
+              currentFileData = new PatchFile(repo, patchList, pk.get());
             } catch (IOException e) {
               log.warn(String.format(
                   "Cannot load %s from %s in %s",
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 00726ba..fd931f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -624,17 +624,31 @@
     List<String> tempDiffs = new ArrayList<>();
     String temp = "temp";
 
+    // ReviewDb allows timestamps before patch set was created, but NoteDb
+    // truncates this to the patch set creation timestamp.
+    Timestamp ta = a.getWrittenOn();
+    Timestamp tb = b.getWrittenOn();
+    PatchSet psa = bundleA.patchSets.get(a.getPatchSetId());
+    PatchSet psb = bundleB.patchSets.get(b.getPatchSetId());
     boolean excludePatchSet = false;
+    boolean excludeWrittenOn = false;
     if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
       excludePatchSet = a.getPatchSetId() == null;
+      excludeWrittenOn = psa != null && psb != null
+          && ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn());
     } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
       excludePatchSet = b.getPatchSetId() == null;
+      excludeWrittenOn = psa != null && psb != null
+          && tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn());
     }
 
     List<String> exclude = Lists.newArrayList("key");
     if (excludePatchSet) {
       exclude.add("patchset");
     }
+    if (excludeWrittenOn) {
+      exclude.add("writtenOn");
+    }
 
     diffColumnsExcluding(
         tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude);
@@ -673,7 +687,28 @@
       PatchSetApproval a = as.get(k);
       PatchSetApproval b = bs.get(k);
       String desc = describe(k);
-      diffColumns(diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b);
+
+      // ReviewDb allows timestamps before patch set was created, but NoteDb
+      // truncates this to the patch set creation timestamp.
+      Timestamp ta = a.getGranted();
+      Timestamp tb = b.getGranted();
+      PatchSet psa = checkNotNull(bundleA.patchSets.get(a.getPatchSetId()));
+      PatchSet psb = checkNotNull(bundleB.patchSets.get(b.getPatchSetId()));
+      boolean excludeGranted = false;
+      List<String> exclude = new ArrayList<>(1);
+      if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
+        excludeGranted =
+            ta.before(psa.getCreatedOn()) && tb.equals(psb.getCreatedOn());
+      } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
+        excludeGranted =
+            tb.before(psb.getCreatedOn()) && ta.equals(psa.getCreatedOn());
+      }
+      if (excludeGranted) {
+        exclude.add("granted");
+      }
+
+      diffColumnsExcluding(
+          diffs, PatchSetApproval.class, desc, bundleA, a, bundleB, b, exclude);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
index d93336a3..4c9f702 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNote.java
@@ -83,7 +83,7 @@
 
     if (isJson(raw, p.value)) {
       RevisionNoteData data = parseJson(noteUtil, p.value);
-      comments = data.exportComments(status);
+      comments = data.exportComments(changeId, status);
       if (status == PatchLineComment.Status.PUBLISHED) {
         pushCert = data.pushCert;
       } else {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
index 8d020bdb..73083b7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
@@ -48,19 +48,17 @@
     String uuid;
     String filename;
     int patchSetId;
-    int changeId;
 
     CommentKey(PatchLineComment.Key k) {
       uuid = k.get();
       filename = k.getParentKey().getFileName();
       patchSetId = k.getParentKey().getParentKey().get();
-      changeId = k.getParentKey().getParentKey().getParentKey().get();
     }
 
-    PatchLineComment.Key export() {
+    PatchLineComment.Key export(Change.Id changeId) {
       return new PatchLineComment.Key(
           new Patch.Key(
-              new PatchSet.Id(new Change.Id(changeId), patchSetId),
+              new PatchSet.Id(changeId, patchSetId),
               filename),
           uuid);
     }
@@ -90,7 +88,6 @@
     int lineNbr;
     Identity author;
     Timestamp writtenOn;
-    char status;
     short side;
     String message;
     String parentUuid;
@@ -104,7 +101,6 @@
       lineNbr = plc.getLine();
       author = new Identity(plc.getAuthor());
       writtenOn = plc.getWrittenOn();
-      status = plc.getStatus().getCode();
       side = plc.getSide();
       message = plc.getMessage();
       parentUuid = plc.getParentUuid();
@@ -114,17 +110,18 @@
       this.serverId = serverId;
     }
 
-    PatchLineComment export() {
+    PatchLineComment export(Change.Id changeId,
+        PatchLineComment.Status status) {
       PatchLineComment plc = new PatchLineComment(
-          key.export(), lineNbr, author.export(), parentUuid, writtenOn);
+          key.export(changeId), lineNbr, author.export(), parentUuid, writtenOn);
       plc.setSide(side);
-      plc.setStatus(PatchLineComment.Status.forCode(status));
       plc.setMessage(message);
       if (range != null) {
         plc.setRange(range.export());
       }
       plc.setTag(tag);
       plc.setRevId(new RevId(revId));
+      plc.setStatus(status);
       return plc;
     }
   }
@@ -133,16 +130,12 @@
   String pushCert;
   List<Comment> comments;
 
-  ImmutableList<PatchLineComment> exportComments(
+
+  ImmutableList<PatchLineComment> exportComments(Change.Id changeId,
       PatchLineComment.Status status) {
     return ImmutableList.copyOf(
         comments.stream()
-            .map(
-                c -> {
-                  PatchLineComment plc = c.export();
-                  plc.setStatus(status);
-                  return plc;
-                })
+            .map(c -> c.export(changeId, status))
             .collect(toList()));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
index a990e19..bc8af82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java
@@ -31,11 +31,6 @@
   private static final Pattern TOPIC_REMOVED_REGEXP =
       Pattern.compile("^Topic (.+) removed$");
 
-  private static final Pattern STATUS_ABANDONED_REGEXP =
-      Pattern.compile("^Abandoned(\n.*)*$");
-  private static final Pattern STATUS_RESTORED_REGEXP =
-      Pattern.compile("^Restored(\n.*)*$");
-
   private final ChangeMessage message;
   private final Change noteDbChange;
 
@@ -57,7 +52,6 @@
     checkUpdate(update);
     update.setChangeMessage(message.getMessage());
     setTopic(update);
-    setStatus(update);
   }
 
   private void setTopic(ChangeUpdate update) {
@@ -86,21 +80,4 @@
       noteDbChange.setTopic(null);
     }
   }
-
-  private void setStatus(ChangeUpdate update) {
-    String msg = message.getMessage();
-    if (msg == null) {
-      return;
-    }
-    if (STATUS_ABANDONED_REGEXP.matcher(msg).matches()) {
-      update.setStatus(Change.Status.ABANDONED);
-      noteDbChange.setStatus(Change.Status.ABANDONED);
-      return;
-    }
-
-    if (STATUS_RESTORED_REGEXP.matcher(msg).matches()) {
-      update.setStatus(Change.Status.NEW);
-      noteDbChange.setStatus(Change.Status.NEW);
-    }
-  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index 3389e2b..3d240c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -20,7 +20,6 @@
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
-
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static java.util.stream.Collectors.toList;
 
@@ -29,6 +28,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
@@ -92,6 +92,7 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
+import java.util.Map;
 import java.util.Objects;
 import java.util.Set;
 
@@ -309,8 +310,8 @@
     deleteDraftRefs(change, manager.getAllUsersRepo());
 
     Integer minPsNum = getMinPatchSetNum(bundle);
-    Set<PatchSet.Id> psIds =
-        Sets.newHashSetWithExpectedSize(bundle.getPatchSets().size());
+    Map<PatchSet.Id, PatchSetEvent> patchSetEvents =
+        Maps.newHashMapWithExpectedSize(bundle.getPatchSets().size());
 
     for (PatchSet ps : bundle.getPatchSets()) {
       if (ps.getId().get() > currPsId.get()) {
@@ -319,14 +320,15 @@
             ps.getId(), currPsId);
         continue;
       }
-      psIds.add(ps.getId());
-      events.add(new PatchSetEvent(
-          change, ps, manager.getChangeRepo().rw));
+      PatchSetEvent pse =
+          new PatchSetEvent(change, ps, manager.getChangeRepo().rw);
+      patchSetEvents.put(ps.getId(), pse);
+      events.add(pse);
       for (PatchLineComment c : getPatchLineComments(bundle, ps)) {
         PatchLineCommentEvent e =
             new PatchLineCommentEvent(c, change, ps, patchListCache);
         if (c.getStatus() == Status.PUBLISHED) {
-          events.add(e);
+          events.add(e.addDep(pse));
         } else {
           draftCommentEvents.put(c.getAuthor(), e);
         }
@@ -334,8 +336,9 @@
     }
 
     for (PatchSetApproval psa : bundle.getPatchSetApprovals()) {
-      if (psIds.contains(psa.getPatchSetId())) {
-        events.add(new ApprovalEvent(psa, change.getCreatedOn()));
+      PatchSetEvent pse = patchSetEvents.get(psa.getPatchSetId());
+      if (pse != null) {
+        events.add(new ApprovalEvent(psa, change.getCreatedOn()).addDep(pse));
       }
     }
 
@@ -346,10 +349,16 @@
 
     Change noteDbChange = new Change(null, null, null, null, null);
     for (ChangeMessage msg : bundle.getChangeMessages()) {
-      if (msg.getPatchSetId() == null || psIds.contains(msg.getPatchSetId())) {
-        events.add(
-            new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
+      List<Event> msgEvents = parseChangeMessage(msg, change, noteDbChange);
+      if (msg.getPatchSetId() != null) {
+        PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId());
+        if (pse != null) {
+          for (Event e : msgEvents) {
+            e.addDep(pse);
+          }
+        }
       }
+      events.addAll(msgEvents);
     }
 
     sortAndFillEvents(change, noteDbChange, events, minPsNum);
@@ -378,6 +387,18 @@
     }
   }
 
+  private List<Event> parseChangeMessage(ChangeMessage msg, Change change,
+      Change noteDbChange) {
+    List<Event> events = new ArrayList<>(2);
+    events.add(new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
+    Optional<StatusChangeEvent> sce =
+        StatusChangeEvent.parseFromMessage(msg, change, noteDbChange);
+    if (sce.isPresent()) {
+      events.add(sce.get());
+    }
+    return events;
+  }
+
   private static Integer getMinPatchSetNum(ChangeBundle bundle) {
     Integer minPsNum = null;
     for (PatchSet ps : bundle.getPatchSets()) {
@@ -399,8 +420,8 @@
 
   private void sortAndFillEvents(Change change, Change noteDbChange,
       List<Event> events, Integer minPsNum) {
-    new EventSorter(events).sort();
     events.add(new FinalUpdatesEvent(change, noteDbChange));
+    new EventSorter(events).sort();
 
     // Ensure the first event in the list creates the change, setting the author
     // and any required footers.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
index a72e4fa..b7b08b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
@@ -66,8 +66,9 @@
         who, update.getNullableAccountId());
   }
 
-  void addDep(Event e) {
+  Event addDep(Event e) {
     deps.add(e);
+    return this;
   }
 
   /**
@@ -78,10 +79,6 @@
 
   abstract void apply(ChangeUpdate update) throws OrmException, IOException;
 
-  protected boolean isPatchSet() {
-    return false;
-  }
-
   @Override
   public String toString() {
     return MoreObjects.toStringHelper(this)
@@ -94,6 +91,7 @@
   @Override
   public int compareTo(Event other) {
     return ComparisonChain.start()
+        .compareFalseFirst(this.isFinalUpdates(), other.isFinalUpdates())
         .compare(this.when, other.when)
         .compareTrueFirst(isPatchSet(), isPatchSet())
         .compareTrueFirst(this.predatesChange, other.predatesChange)
@@ -102,4 +100,12 @@
             ReviewDbUtil.intKeyOrdering().nullsLast())
         .result();
   }
+
+  private boolean isPatchSet() {
+    return this instanceof PatchSetEvent;
+  }
+
+  private boolean isFinalUpdates() {
+    return this instanceof FinalUpdatesEvent;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
index 3080be7..4e82635 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java
@@ -46,7 +46,8 @@
       // TODO(dborowitz): Stamp approximate approvals at this time.
       update.fixStatus(change.getStatus());
     }
-    if (change.getSubmissionId() != null) {
+    if (change.getSubmissionId() != null
+        && noteDbChange.getSubmissionId() == null) {
       update.setSubmissionId(change.getSubmissionId());
     }
     if (!update.isEmpty()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
index 5baddd3..c3fb267 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java
@@ -66,11 +66,6 @@
     }
   }
 
-  @Override
-  protected boolean isPatchSet() {
-    return true;
-  }
-
   private void setRevision(ChangeUpdate update, PatchSet ps)
       throws IOException {
     String rev = ps.getRevision().get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java
new file mode 100644
index 0000000..29e0868
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/StatusChangeEvent.java
@@ -0,0 +1,90 @@
+// 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 com.google.gerrit.server.notedb.rebuild;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.ImmutableMap;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gwtorm.server.OrmException;
+
+import java.sql.Timestamp;
+import java.util.Map;
+import java.util.regex.Pattern;
+
+class StatusChangeEvent extends Event {
+  private static final ImmutableMap<Change.Status, Pattern> PATTERNS =
+      ImmutableMap.of(
+          Change.Status.ABANDONED, Pattern.compile("^Abandoned(\n.*)*$"),
+          Change.Status.MERGED, Pattern.compile(
+              "^Change has been successfully"
+              + " (merged|cherry-picked|rebased|pushed).*$"),
+          Change.Status.NEW, Pattern.compile("^Restored(\n.*)*$"));
+
+  static Optional<StatusChangeEvent> parseFromMessage(ChangeMessage message,
+      Change change, Change noteDbChange) {
+    String msg = message.getMessage();
+    if (msg == null) {
+      return Optional.absent();
+    }
+    for (Map.Entry<Change.Status, Pattern> e : PATTERNS.entrySet()) {
+      if (e.getValue().matcher(msg).matches()) {
+        return Optional.of(new StatusChangeEvent(
+            message, change, noteDbChange, e.getKey()));
+      }
+    }
+    return Optional.absent();
+  }
+
+  private final Change change;
+  private final Change noteDbChange;
+  private final Change.Status status;
+
+  private StatusChangeEvent(ChangeMessage message, Change change,
+      Change noteDbChange, Change.Status status) {
+    this(message.getPatchSetId(), message.getAuthor(),
+        message.getWrittenOn(), change, noteDbChange, message.getTag(),
+        status);
+  }
+
+  private StatusChangeEvent(PatchSet.Id psId, Account.Id author,
+      Timestamp when, Change change, Change noteDbChange,
+      String tag, Change.Status status) {
+    super(psId, author, when, change.getCreatedOn(), tag);
+    this.change = change;
+    this.noteDbChange = noteDbChange;
+    this.status = status;
+  }
+
+  @Override
+  boolean uniquePerUpdate() {
+    return true;
+  }
+
+  @SuppressWarnings("deprecation")
+  @Override
+  void apply(ChangeUpdate update) throws OrmException {
+    checkUpdate(update);
+    update.fixStatus(status);
+    noteDbChange.setStatus(status);
+    if (status == Change.Status.MERGED) {
+      update.setSubmissionId(change.getSubmissionId());
+      noteDbChange.setSubmissionId(change.getSubmissionId());
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java
new file mode 100644
index 0000000..abbb680
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/ComparisonType.java
@@ -0,0 +1,77 @@
+// 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 com.google.gerrit.server.patch;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
+import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public class ComparisonType {
+
+  /** 1-based parent */
+  private final Integer parentNum;
+
+  private final boolean autoMerge;
+
+  public static ComparisonType againstOtherPatchSet() {
+    return new ComparisonType(null, false);
+  }
+
+  public static ComparisonType againstParent(int parentNum) {
+    return new ComparisonType(parentNum, false);
+  }
+
+  public static ComparisonType againstAutoMerge() {
+    return new ComparisonType(null, true);
+  }
+
+  private ComparisonType(Integer parentNum, boolean autoMerge) {
+    this.parentNum = parentNum;
+    this.autoMerge = autoMerge;
+  }
+
+  public boolean isAgainstParentOrAutoMerge() {
+    return isAgainstParent() || isAgainstAutoMerge();
+  }
+
+  public boolean isAgainstParent() {
+    return parentNum != null;
+  }
+
+  public boolean isAgainstAutoMerge() {
+    return autoMerge;
+  }
+
+  public int getParentNum() {
+    checkNotNull(parentNum);
+    return parentNum;
+  }
+
+  void writeTo(OutputStream out) throws IOException {
+    writeVarInt32(out, parentNum != null ? parentNum : 0);
+    writeVarInt32(out, autoMerge ? 1 : 0);
+  }
+
+  static ComparisonType readFrom(InputStream in) throws IOException {
+    int p = readVarInt32(in);
+    Integer parentNum = p > 0 ? p : null;
+    boolean autoMerge = readVarInt32(in) != 0;
+    return new ComparisonType(parentNum, autoMerge);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java
new file mode 100644
index 0000000..8f54e48
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/MergeListBuilder.java
@@ -0,0 +1,52 @@
+// 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 com.google.gerrit.server.patch;
+
+import com.google.common.collect.ImmutableList;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+public class MergeListBuilder {
+  public static List<RevCommit> build(RevWalk rw, RevCommit merge,
+      int uninterestingParent) throws IOException {
+    rw.reset();
+    rw.parseBody(merge);
+    if (merge.getParentCount() < 2) {
+      return ImmutableList.of();
+    }
+
+    for (int parent = 0; parent < merge.getParentCount(); parent++) {
+      RevCommit parentCommit = merge.getParent(parent);
+      rw.parseBody(parentCommit);
+      if (parent == uninterestingParent - 1) {
+        rw.markUninteresting(parentCommit);
+      } else {
+        rw.markStart(parentCommit);
+      }
+    }
+
+    List<RevCommit> result = new ArrayList<>();
+    RevCommit c;
+    while ((c = rw.next()) != null) {
+      result.add(c);
+    }
+    return result;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
index f8c8b49..d2a6d2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchFile.java
@@ -44,9 +44,8 @@
   private Text a;
   private Text b;
 
-  public PatchFile(final Repository repo, final PatchList patchList,
-      final String fileName) throws MissingObjectException,
-      IncorrectObjectTypeException, IOException {
+  public PatchFile(Repository repo, PatchList patchList, String fileName)
+      throws MissingObjectException, IncorrectObjectTypeException, IOException {
     this.repo = repo;
     this.entry = patchList.get(fileName);
 
@@ -55,7 +54,7 @@
       final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
 
       if (Patch.COMMIT_MSG.equals(fileName)) {
-        if (patchList.isAgainstParent()) {
+        if (patchList.getComparisonType().isAgainstParentOrAutoMerge()) {
           a = Text.EMPTY;
         } else {
           // For the initial commit, we have an empty tree on Side A
@@ -68,7 +67,16 @@
 
         aTree = null;
         bTree = null;
+      } else if (Patch.MERGE_LIST.equals(fileName)) {
+        // For the initial commit, we have an empty tree on Side A
+        RevObject object = rw.parseAny(patchList.getOldId());
+        a = object instanceof RevCommit
+            ? Text.forMergeList(patchList.getComparisonType(), reader, object)
+            : Text.EMPTY;
+        b = Text.forMergeList(patchList.getComparisonType(), reader, bCommit);
 
+        aTree = null;
+        bTree = null;
       } else {
         if (patchList.getOldId() != null) {
           aTree = rw.parseTree(patchList.getOldId());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
index 2a4afb3..2cfd007 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchList.java
@@ -58,16 +58,19 @@
   @Nullable
   private transient ObjectId oldId;
   private transient ObjectId newId;
-  private transient boolean againstParent;
+  private transient boolean isMerge;
+  private transient ComparisonType comparisonType;
   private transient int insertions;
   private transient int deletions;
   private transient PatchListEntry[] patches;
 
-  public PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId,
-      final boolean againstParent, final PatchListEntry[] patches) {
+  public PatchList(@Nullable AnyObjectId oldId, AnyObjectId newId,
+      boolean isMerge, ComparisonType comparisonType,
+      PatchListEntry[] patches) {
     this.oldId = oldId != null ? oldId.copy() : null;
     this.newId = newId.copy();
-    this.againstParent = againstParent;
+    this.isMerge = isMerge;
+    this.comparisonType = comparisonType;
 
     // We assume index 0 contains the magic commit message entry.
     if (patches.length > 1) {
@@ -97,9 +100,9 @@
     return Collections.unmodifiableList(Arrays.asList(patches));
   }
 
-  /** @return true if {@link #getOldId} is {@link #getNewId}'s ancestor. */
-  public boolean isAgainstParent() {
-    return againstParent;
+  /** @return the comparison type */
+  public ComparisonType getComparisonType() {
+    return comparisonType;
   }
 
   /** @return total number of new lines added. */
@@ -144,9 +147,12 @@
     if (Patch.COMMIT_MSG.equals(fileName)) {
       return 0;
     }
+    if (isMerge && Patch.MERGE_LIST.equals(fileName)) {
+      return 1;
+    }
 
     int high = patches.length;
-    int low = 1;
+    int low = isMerge ? 2 : 1;
     while (low < high) {
       final int mid = (low + high) >>> 1;
       final int cmp = patches[mid].getNewName().compareTo(fileName);
@@ -166,7 +172,8 @@
     try (DeflaterOutputStream out = new DeflaterOutputStream(buf)) {
       writeCanBeNull(out, oldId);
       writeNotNull(out, newId);
-      writeVarInt32(out, againstParent ? 1 : 0);
+      writeVarInt32(out, isMerge ? 1 : 0);
+      comparisonType.writeTo(out);
       writeVarInt32(out, insertions);
       writeVarInt32(out, deletions);
       writeVarInt32(out, patches.length);
@@ -182,7 +189,8 @@
     try (InflaterInputStream in = new InflaterInputStream(buf)) {
       oldId = readCanBeNull(in);
       newId = readNotNull(in);
-      againstParent = readVarInt32(in) != 0;
+      isMerge = readVarInt32(in) != 0;
+      comparisonType = ComparisonType.readFrom(in);
       insertions = readVarInt32(in);
       deletions = readVarInt32(in);
       final int cnt = readVarInt32(in);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
index 43e3dce..22f7bf3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListKey.java
@@ -35,7 +35,7 @@
 import java.util.Objects;
 
 public class PatchListKey implements Serializable {
-  public static final long serialVersionUID = 22L;
+  public static final long serialVersionUID = 24L;
 
   public static final BiMap<Whitespace, Character> WHITESPACE_TYPES = ImmutableBiMap.of(
       Whitespace.IGNORE_NONE, 'N',
@@ -138,6 +138,10 @@
     n.append("..");
     n.append(newId.name());
     n.append(" ");
+    if (parentNum != null) {
+      n.append(parentNum);
+      n.append(" ");
+    }
     n.append(whitespace.name());
     n.append("]");
     return n.toString();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 0ae9a99..9616fc8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -16,7 +16,6 @@
 package com.google.gerrit.server.patch;
 
 import static com.google.common.base.Preconditions.checkArgument;
-
 import static java.nio.charset.StandardCharsets.UTF_8;
 import static java.util.stream.Collectors.toSet;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
@@ -156,14 +155,19 @@
 
       if (a == null) {
         // TODO(sop) Remove this case.
-        // This is a merge commit, compared to its ancestor.
+        // This is an octopus merge commit which should be compared against the
+        // auto-merge. However since we don't support computing the auto-merge
+        // for octopus merge commits, we fall back to diffing against the first
+        // parent, even though this wasn't what was requested.
         //
-        PatchListEntry[] entries = new PatchListEntry[1];
+        ComparisonType comparisonType = ComparisonType.againstParent(1);
+        PatchListEntry[] entries = new PatchListEntry[2];
         entries[0] = newCommitMessage(cmp, reader, null, b);
-        return new PatchList(a, b, true, entries);
+        entries[1] = newMergeList(cmp, reader, null, b, comparisonType);
+        return new PatchList(a, b, true, comparisonType, entries);
       }
 
-      boolean againstParent = isAgainstParent(a, b);
+      ComparisonType comparisonType = getComparisonType(a, b);
 
       RevCommit aCommit = a instanceof RevCommit ? (RevCommit) a : null;
       RevTree aTree = rw.parseTree(a);
@@ -190,7 +194,13 @@
       int cnt = diffEntries.size();
       List<PatchListEntry> entries = new ArrayList<>();
       entries.add(newCommitMessage(cmp, reader,
-          againstParent ? null : aCommit, b));
+          comparisonType.isAgainstParentOrAutoMerge() ? null : aCommit, b));
+      boolean isMerge = b.getParentCount() > 1;
+      if (isMerge) {
+        entries.add(newMergeList(cmp, reader,
+            comparisonType.isAgainstParentOrAutoMerge() ? null : aCommit, b,
+            comparisonType));
+      }
       for (int i = 0; i < cnt; i++) {
         DiffEntry e = diffEntries.get(i);
         if (paths == null || paths.contains(e.getNewPath())
@@ -204,19 +214,23 @@
           entries.add(newEntry(aTree, fh, newSize, newSize - oldSize));
         }
       }
-      return new PatchList(a, b, againstParent,
+      return new PatchList(a, b, isMerge, comparisonType,
           entries.toArray(new PatchListEntry[entries.size()]));
     }
   }
 
-  private boolean isAgainstParent(RevObject a, RevCommit b) {
+  private ComparisonType getComparisonType(RevObject a, RevCommit b) {
     for (int i = 0; i < b.getParentCount(); i++) {
       if (b.getParent(i).equals(a)) {
-        return true;
+        return ComparisonType.againstParent(i + 1);
       }
     }
 
-    return false;
+    if (key.getOldId() == null && b.getParentCount() > 0) {
+      return ComparisonType.againstAutoMerge();
+    }
+
+    return ComparisonType.againstOtherPatchSet();
   }
 
   private static long getFileSize(ObjectReader reader,
@@ -278,32 +292,30 @@
     return diffFormatter.toFileHeader(diffEntry);
   }
 
-  private PatchListEntry newCommitMessage(final RawTextComparator cmp,
-      final ObjectReader reader,
-      final RevCommit aCommit, final RevCommit bCommit) throws IOException {
-    StringBuilder hdr = new StringBuilder();
-
-    hdr.append("diff --git");
-    if (aCommit != null) {
-      hdr.append(" a/").append(Patch.COMMIT_MSG);
-    } else {
-      hdr.append(" ").append(FileHeader.DEV_NULL);
-    }
-    hdr.append(" b/").append(Patch.COMMIT_MSG);
-    hdr.append("\n");
-
-    if (aCommit != null) {
-      hdr.append("--- a/").append(Patch.COMMIT_MSG).append("\n");
-    } else {
-      hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n");
-    }
-    hdr.append("+++ b/").append(Patch.COMMIT_MSG).append("\n");
-
-    Text aText =
-        aCommit != null ? Text.forCommit(reader, aCommit) : Text.EMPTY;
+  private PatchListEntry newCommitMessage(RawTextComparator cmp,
+      ObjectReader reader, RevCommit aCommit, RevCommit bCommit)
+          throws IOException {
+    Text aText = aCommit != null
+        ? Text.forCommit(reader, aCommit)
+        : Text.EMPTY;
     Text bText = Text.forCommit(reader, bCommit);
+    return createPatchListEntry(cmp, aCommit, aText, bText, Patch.COMMIT_MSG);
+  }
 
-    byte[] rawHdr = hdr.toString().getBytes(UTF_8);
+  private PatchListEntry newMergeList(RawTextComparator cmp,
+      ObjectReader reader, RevCommit aCommit, RevCommit bCommit,
+      ComparisonType comparisonType) throws IOException {
+    Text aText = aCommit != null
+        ? Text.forMergeList(comparisonType, reader, aCommit)
+        : Text.EMPTY;
+    Text bText =
+        Text.forMergeList(comparisonType, reader, bCommit);
+    return createPatchListEntry(cmp, aCommit, aText, bText, Patch.MERGE_LIST);
+  }
+
+  private static PatchListEntry createPatchListEntry(RawTextComparator cmp,
+      RevCommit aCommit, Text aText, Text bText, String fileName) {
+    byte[] rawHdr = getRawHeader(aCommit != null, fileName);
     byte[] aContent = aText.getContent();
     byte[] bContent = bText.getContent();
     long size = bContent.length;
@@ -315,6 +327,26 @@
     return new PatchListEntry(fh, edits, size, sizeDelta);
   }
 
+  private static byte[] getRawHeader(boolean hasA, String fileName) {
+    StringBuilder hdr = new StringBuilder();
+    hdr.append("diff --git");
+    if (hasA) {
+      hdr.append(" a/").append(fileName);
+    } else {
+      hdr.append(" ").append(FileHeader.DEV_NULL);
+    }
+    hdr.append(" b/").append(fileName);
+    hdr.append("\n");
+
+    if (hasA) {
+      hdr.append("--- a/").append(fileName).append("\n");
+    } else {
+      hdr.append("--- ").append(FileHeader.DEV_NULL).append("\n");
+    }
+    hdr.append("+++ b/").append(fileName).append("\n");
+    return hdr.toString().getBytes(UTF_8);
+  }
+
   private PatchListEntry newEntry(RevTree aTree, FileHeader fileHeader,
       long size, long sizeDelta) {
     if (aTree == null // want combined diff
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index e09d26f..7eee6a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -66,7 +66,7 @@
   private ObjectReader reader;
   private Change change;
   private DiffPreferencesInfo diffPrefs;
-  private boolean againstParent;
+  private ComparisonType comparisonType;
   private ObjectId aId;
   private ObjectId bId;
 
@@ -79,7 +79,8 @@
   private int context;
 
   @Inject
-  PatchScriptBuilder(final FileTypeRegistry ftr, final PatchListCache plc) {
+  PatchScriptBuilder(FileTypeRegistry ftr,
+      PatchListCache plc) {
     a = new Side();
     b = new Side();
     registry = ftr;
@@ -106,8 +107,8 @@
     }
   }
 
-  void setTrees(final boolean ap, final ObjectId a, final ObjectId b) {
-    againstParent = ap;
+  void setTrees(final ComparisonType ct, final ObjectId a, final ObjectId b) {
+    comparisonType = ct;
     aId = a;
     bId = b;
   }
@@ -435,7 +436,8 @@
       try {
         final boolean reuse;
         if (Patch.COMMIT_MSG.equals(path)) {
-          if (againstParent && (aId == within || within.equals(aId))) {
+          if (comparisonType.isAgainstParentOrAutoMerge()
+              && (aId == within || within.equals(aId))) {
             id = ObjectId.zeroId();
             src = Text.EMPTY;
             srcContent = Text.NO_BYTES;
@@ -453,7 +455,26 @@
             }
           }
           reuse = false;
-
+        } else if (Patch.MERGE_LIST.equals(path)) {
+          if (comparisonType.isAgainstParentOrAutoMerge()
+              && (aId == within || within.equals(aId))) {
+            id = ObjectId.zeroId();
+            src = Text.EMPTY;
+            srcContent = Text.NO_BYTES;
+            mode = FileMode.MISSING;
+            displayMethod = DisplayMethod.NONE;
+          } else {
+            id = within;
+            src = Text.forMergeList(comparisonType, reader, within);
+            srcContent = src.getContent();
+            if (src == Text.EMPTY) {
+              mode = FileMode.MISSING;
+              displayMethod = DisplayMethod.NONE;
+            } else {
+              mode = FileMode.REGULAR_FILE;
+            }
+          }
+          reuse = false;
         } else {
           final TreeWalk tw = find(within);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index 754d995..91f8cf8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -260,7 +260,7 @@
     b.setRepository(git, project);
     b.setChange(change);
     b.setDiffPrefs(diffPrefs);
-    b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId());
+    b.setTrees(list.getComparisonType(), list.getOldId(), list.getNewId());
     return b;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
index 7982479..a84dd92 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/Text.java
@@ -87,6 +87,36 @@
     }
   }
 
+  public static Text forMergeList(ComparisonType comparisonType,
+      ObjectReader reader, AnyObjectId commitId) throws IOException {
+    try (RevWalk rw = new RevWalk(reader)) {
+      RevCommit c = rw.parseCommit(commitId);
+      StringBuilder b = new StringBuilder();
+      switch (c.getParentCount()) {
+        case 0:
+          break;
+        case 1: {
+          break;
+        }
+        default:
+          int uniterestingParent = comparisonType.isAgainstParent()
+              ? comparisonType.getParentNum()
+              : 1;
+
+          b.append("Merge List:\n\n");
+          for (RevCommit commit : MergeListBuilder.build(rw, c,
+              uniterestingParent)) {
+            b.append("* ");
+            b.append(reader.abbreviate(commit, 8).name());
+            b.append(" ");
+            b.append(commit.getShortMessage());
+            b.append("\n");
+          }
+      }
+      return new Text(b.toString().getBytes(UTF_8));
+    }
+  }
+
   private static void appendPersonIdent(StringBuilder b, String field,
       PersonIdent person) {
     if (person != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
index 64a5fb2..0f44a48 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/FilesInCommitCollection.java
@@ -47,7 +47,7 @@
   @Override
   public FileResource parse(CommitResource parent, IdString id)
       throws ResourceNotFoundException, IOException {
-    if (Patch.COMMIT_MSG.equals(id.get())) {
+    if (Patch.isMagic(id.get())) {
       return new FileResource(parent.getProject(), parent.getCommit(),
           id.get());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java
new file mode 100644
index 0000000..f8fa8c5
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AssigneePredicate.java
@@ -0,0 +1,41 @@
+// 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 com.google.gerrit.server.query.change;
+
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gwtorm.server.OrmException;
+
+class AssigneePredicate extends ChangeIndexPredicate {
+  private final Account.Id id;
+
+  AssigneePredicate(Account.Id id) {
+    super(ChangeField.ASSIGNEE, id.toString());
+    this.id = id;
+  }
+
+  @Override
+  public boolean match(final ChangeData object) throws OrmException {
+    if (id.get() == ChangeField.NO_ASSIGNEE) {
+      return object.notes().load().getAssignee() == null;
+    }
+    return id.equals(object.notes().load().getAssignee());
+  }
+
+  @Override
+  public int getCost() {
+    return 1;
+  }
+}
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 c4e8bd7..46d7e38 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
@@ -345,6 +345,7 @@
   private Optional<ChangedLines> changedLines;
   private SubmitTypeRecord submitTypeRecord;
   private Boolean mergeable;
+  private Account.Id assignee;
   private Set<String> hashtags;
   private Set<Account.Id> editsByUser;
   private Set<Account.Id> reviewedBy;
@@ -599,7 +600,7 @@
 
       r = new ArrayList<>(p.get().getPatches().size());
       for (PatchListEntry e : p.get().getPatches()) {
-        if (Patch.COMMIT_MSG.equals(e.getNewName())) {
+        if (Patch.isMagic(e.getNewName())) {
           continue;
         }
         switch (e.getChangeType()) {
@@ -1162,6 +1163,16 @@
     this.reviewedBy = reviewedBy;
   }
 
+  public Account.Id assignee() throws OrmException {
+    if (assignee == null) {
+      if (!lazyLoad) {
+        return null;
+      }
+      assignee = notes().getAssignee();
+    }
+    return assignee;
+  }
+
   public Set<String> hashtags() throws OrmException {
     if (hashtags == null) {
       if (!lazyLoad) {
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 f697d15..d59b66c 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
@@ -106,6 +106,7 @@
 
   public static final String FIELD_ADDED = "added";
   public static final String FIELD_AGE = "age";
+  public static final String FIELD_ASSIGNEE = "assignee";
   public static final String FIELD_AUTHOR = "author";
   public static final String FIELD_BEFORE = "before";
   public static final String FIELD_CHANGE = "change";
@@ -477,6 +478,14 @@
       return new IsMergeablePredicate(args.fillArgs);
     }
 
+    if ("assigned".equalsIgnoreCase(value)) {
+      return Predicate.not(new AssigneePredicate(new Account.Id(ChangeField.NO_ASSIGNEE)));
+    }
+
+    if ("unassigned".equalsIgnoreCase(value)) {
+      return new AssigneePredicate(new Account.Id(ChangeField.NO_ASSIGNEE));
+    }
+
     try {
       return status(value);
     } catch (IllegalArgumentException e) {
@@ -796,6 +805,20 @@
   }
 
   @Operator
+  public Predicate<ChangeData> assignee(String who) throws QueryParseException,
+      OrmException {
+    return assignee(parseAccount(who));
+  }
+
+  private Predicate<ChangeData> assignee(Set<Account.Id> who) {
+    List<AssigneePredicate> p = Lists.newArrayListWithCapacity(who.size());
+    for (Account.Id id : who) {
+      p.add(new AssigneePredicate(id));
+    }
+    return Predicate.or(p);
+  }
+
+  @Operator
   public Predicate<ChangeData> ownerin(String group)
       throws QueryParseException {
     GroupReference g = GroupBackends.findBestSuggestion(args.groupBackend, group);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java
index 45a00c6..d2f6876 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/HasDraftByLegacyPredicate.java
@@ -16,7 +16,6 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
 import com.google.gwtorm.server.ListResultSet;
 import com.google.gwtorm.server.OrmException;
@@ -50,9 +49,9 @@
   @Override
   public ResultSet<ChangeData> read() throws OrmException {
     Set<Change.Id> ids = new HashSet<>();
-    for (PatchLineComment sc :
-        args.plcUtil.draftByAuthor(args.db.get(), accountId)) {
-      ids.add(sc.getKey().getParentKey().getParentKey().getParentKey());
+    for (Change.Id changeId : args.plcUtil
+        .changesWithDraftsByAuthor(args.db.get(), accountId)) {
+      ids.add(changeId);
     }
 
     List<ChangeData> r = new ArrayList<>(ids.size());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java
index d7fcc3b..cee21bd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_130.java
@@ -59,7 +59,7 @@
         cfg.removeForceFromPermission("pushTag");
         cfg.save(serverUser, COMMIT_MSG);
       } catch (ConfigInvalidException | IOException ex) {
-        throw new OrmException(ex);
+        throw new OrmException("Cannot migrate project " + projectName, ex);
       }
     }
   }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
index 1dbdb68..a855868 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_stats_3.java
@@ -14,8 +14,10 @@
 
 package gerrit;
 
+import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.patch.PatchList;
+import com.google.gerrit.server.patch.PatchListEntry;
 
 import com.googlecode.prolog_cafe.exceptions.PrologException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -24,6 +26,8 @@
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.Term;
 
+import java.util.List;
+
 /**
  * Exports basic commit statistics.
  *
@@ -48,7 +52,11 @@
     Term a3 = arg3.dereference();
 
     PatchList pl = StoredValues.PATCH_LIST.get(engine);
-    if (!a1.unify(new IntegerTerm(pl.getPatches().size() - 1),engine.trail)) { //Account for /COMMIT_MSG.
+    // Account for magic files
+    if (!a1.unify(
+        new IntegerTerm(
+            pl.getPatches().size() - countMagicFiles(pl.getPatches())),
+        engine.trail)) {
       return engine.fail();
     }
     if (!a2.unify(new IntegerTerm(pl.getInsertions()),engine.trail)) {
@@ -59,4 +67,14 @@
     }
     return cont;
   }
+
+  private int countMagicFiles(List<PatchListEntry> entries) {
+    int count = 0;
+    for (PatchListEntry e : entries) {
+      if (Patch.isMagic(e.getNewName())) {
+        count++;
+      }
+    }
+    return count;
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 054a82b..97bf864 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -1260,7 +1260,9 @@
   }
 
   private static List<PatchSet> latest(Change c) {
-    return ImmutableList.of(new PatchSet(c.currentPatchSetId()));
+    PatchSet ps = new PatchSet(c.currentPatchSetId());
+    ps.setCreatedOn(c.getLastUpdatedOn());
+    return ImmutableList.of(ps);
   }
 
   private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {
diff --git a/lib/BUCK b/lib/BUCK
index bc4f40b..14ae9df 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -8,7 +8,7 @@
 define_license(name = 'asciidoctor')
 define_license(name = 'automaton')
 define_license(name = 'bouncycastle')
-define_license(name = 'CC-BY3.0')
+define_license(name = 'CC-BY3.0-unported')
 define_license(name = 'clippy')
 define_license(name = 'codemirror-minified')
 define_license(name = 'codemirror-original')
diff --git a/lib/BUILD b/lib/BUILD
index 44c293d..20fa126 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -1,6 +1,11 @@
+exports_files([
+  "LICENSE-DO_NOT_DISTRIBUTE"
+])
+
 java_library(
   name = 'servlet-api-3_1',
   neverlink = 1,
+  data = [ ":LICENSE-Apache2.0" ],
   exports = ['@servlet_api_3_1//jar'],
   visibility = ['//visibility:public'],
 )
@@ -75,6 +80,7 @@
   name = 'jsch',
   exports = ['@jsch//jar'],
   visibility = ['//visibility:public'],
+  data = [ ":LICENSE-jsch" ],
 )
 
 java_library(
diff --git a/lib/JGIT_VERSION b/lib/JGIT_VERSION
index 5817fe8..b7f7c84 100644
--- a/lib/JGIT_VERSION
+++ b/lib/JGIT_VERSION
@@ -1,6 +1,6 @@
 include_defs('//lib/maven.defs')
 
-REPO = GERRIT # Leave here even if set to MAVEN_CENTRAL.
-VERS = '4.4.1.201607150455-r.148-gabeaafc'
-DOC_VERS = '4.4.1.201607150455-r' # Set to VERS unless using a snapshot
+REPO = MAVEN_CENTRAL # Leave here even if set to MAVEN_CENTRAL.
+VERS = '4.5.0.201609210915-r'
+DOC_VERS = VERS # Set to VERS unless using a snapshot
 JGIT_DOC_URL="http://download.eclipse.org/jgit/site/" + DOC_VERS + "/apidocs"
diff --git a/lib/LICENSE-CC-BY3.0 b/lib/LICENSE-CC-BY3.0-unported
similarity index 99%
rename from lib/LICENSE-CC-BY3.0
rename to lib/LICENSE-CC-BY3.0-unported
index 39dbc91..d2f2550 100644
--- a/lib/LICENSE-CC-BY3.0
+++ b/lib/LICENSE-CC-BY3.0-unported
@@ -1,4 +1,4 @@
-link:http://creativecommons.org/licenses/by/3.0/us/[CC-BY 3.0]
+link:http://creativecommons.org/licenses/by/3.0/[CC-BY 3.0]
 
 THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
 CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE").  THE WORK IS
diff --git a/lib/LICENSE-diffy b/lib/LICENSE-diffy
index 2b58536..e837a1a 100644
--- a/lib/LICENSE-diffy
+++ b/lib/LICENSE-diffy
@@ -1 +1 @@
-link:http://creativecommons.org/licenses/by/3.0/us/[CC-BY 3.0] (c) Sara Owens, inkylabs.com
+link:http://creativecommons.org/licenses/by/3.0/[CC-BY 3.0] (c) Sara Owens, inkylabs.com
diff --git a/lib/LICENSE-silk_icons b/lib/LICENSE-silk_icons
index 2b10dde..b7417f6 100644
--- a/lib/LICENSE-silk_icons
+++ b/lib/LICENSE-silk_icons
@@ -1 +1,338 @@
-link:http://creativecommons.org/licenses/by/3.0/us/[CC-BY 3.0] (c) Mark James, link:http://famfamfam.com/lab/icons/silk/[SILK ICONS]
+link:http://creativecommons.org/licenses/by/3.0/[CC-BY 3.0] (c) Mark James, link:http://famfamfam.com/lab/icons/silk/[SILK ICONS]
+
+As an author, I would appreciate a reference to my authorship of the Silk
+icon set contents within a readme file or equivalent documentation for
+the software which includes the set or a subset of the icons contained
+within.
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS
+CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE").  THE WORK IS
+PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW.  ANY USE OF THE
+WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS
+PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND
+AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE.  TO THE EXTENT THIS
+LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU
+THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH
+TERMS AND CONDITIONS.
+
+1.  Definitions
+
+  a.  "Adaptation" means a work based upon the Work, or upon the Work
+      and other pre-existing works, such as a translation, adaptation,
+      derivative work, arrangement of music or other alterations of a
+      literary or artistic work, or phonogram or performance and
+      includes cinematographic adaptations or any other form in which
+      the Work may be recast, transformed, or adapted including in any
+      form recognizably derived from the original, except that a work
+      that constitutes a Collection will not be considered an
+      Adaptation for the purpose of this License.  For the avoidance
+      of doubt, where the Work is a musical work, performance or
+      phonogram, the synchronization of the Work in timed-relation
+      with a moving image ("synching") will be considered an
+      Adaptation for the purpose of this License.
+
+  b.  "Collection" means a collection of literary or artistic works,
+      such as encyclopedias and anthologies, or performances,
+      phonograms or broadcasts, or other works or subject matter other
+      than works listed in Section 1(f) below, which, by reason of the
+      selection and arrangement of their contents, constitute
+      intellectual creations, in which the Work is included in its
+      entirety in unmodified form along with one or more other
+      contributions, each constituting separate and independent works
+      in themselves, which together are assembled into a collective
+      whole.  A work that constitutes a Collection will not be
+      considered an Adaptation (as defined above) for the purposes of
+      this License.
+
+  c.  "Distribute" means to make available to the public the original
+      and copies of the Work or Adaptation, as appropriate, through
+      sale or other transfer of ownership.
+
+  d.  "Licensor" means the individual, individuals, entity or entities
+      that offer(s) the Work under the terms of this License.
+
+  e.  "Original Author" means, in the case of a literary or artistic
+      work, the individual, individuals, entity or entities who
+      created the Work or if no individual or entity can be
+      identified, the publisher; and in addition (i) in the case of a
+      performance the actors, singers, musicians, dancers, and other
+      persons who act, sing, deliver, declaim, play in, interpret or
+      otherwise perform literary or artistic works or expressions of
+      folklore; (ii) in the case of a phonogram the producer being the
+      person or legal entity who first fixes the sounds of a
+      performance or other sounds; and, (iii) in the case of
+      broadcasts, the organization that transmits the broadcast.
+
+  f.  "Work" means the literary and/or artistic work offered under the
+      terms of this License including without limitation any
+      production in the literary, scientific and artistic domain,
+      whatever may be the mode or form of its expression including
+      digital form, such as a book, pamphlet and other writing; a
+      lecture, address, sermon or other work of the same nature; a
+      dramatic or dramatico-musical work; a choreographic work or
+      entertainment in dumb show; a musical composition with or
+      without words; a cinematographic work to which are assimilated
+      works expressed by a process analogous to cinematography; a work
+      of drawing, painting, architecture, sculpture, engraving or
+      lithography; a photographic work to which are assimilated works
+      expressed by a process analogous to photography; a work of
+      applied art; an illustration, map, plan, sketch or
+      three-dimensional work relative to geography, topography,
+      architecture or science; a performance; a broadcast; a
+      phonogram; a compilation of data to the extent it is protected
+      as a copyrightable work; or a work performed by a variety or
+      circus performer to the extent it is not otherwise considered a
+      literary or artistic work.
+
+  g.  "You" means an individual or entity exercising rights under this
+      License who has not previously violated the terms of this
+      License with respect to the Work, or who has received express
+      permission from the Licensor to exercise rights under this
+      License despite a previous violation.
+
+  h.  "Publicly Perform" means to perform public recitations of the
+      Work and to communicate to the public those public recitations,
+      by any means or process, including by wire or wireless means or
+      public digital performances; to make available to the public
+      Works in such a way that members of the public may access these
+      Works from a place and at a place individually chosen by them;
+      to perform the Work to the public by any means or process and
+      the communication to the public of the performances of the Work,
+      including by public digital performance; to broadcast and
+      rebroadcast the Work by any means including signs, sounds or
+      images.
+
+  i.  "Reproduce" means to make copies of the Work by any means
+      including without limitation by sound or visual recordings and
+      the right of fixation and reproducing fixations of the Work,
+      including storage of a protected performance or phonogram in
+      digital form or other electronic medium.
+
+2.  Fair Dealing Rights.  Nothing in this License is intended to
+    reduce, limit, or restrict any uses free from copyright or rights
+    arising from limitations or exceptions that are provided for in
+    connection with the copyright protection under copyright law or
+    other applicable laws.
+
+3.  License Grant.  Subject to the terms and conditions of this
+    License, Licensor hereby grants You a worldwide, royalty-free,
+    non-exclusive, perpetual (for the duration of the applicable
+    copyright) license to exercise the rights in the Work as stated
+    below:
+
+  a.  to Reproduce the Work, to incorporate the Work into one or more
+      Collections, and to Reproduce the Work as incorporated in the
+      Collections;
+
+  b.  to create and Reproduce Adaptations provided that any such
+      Adaptation, including any translation in any medium, takes
+      reasonable steps to clearly label, demarcate or otherwise
+      identify that changes were made to the original Work.  For
+      example, a translation could be marked "The original work was
+      translated from English to Spanish," or a modification could
+      indicate "The original work has been modified.";
+
+  c.  to Distribute and Publicly Perform the Work including as
+      incorporated in Collections; and,
+
+  d.  to Distribute and Publicly Perform Adaptations.
+
+  e.  For the avoidance of doubt:
+
+    i.   Non-waivable Compulsory License Schemes.  In those
+	     jurisdictions in which the right to collect royalties
+	     through any statutory or compulsory licensing scheme
+	     cannot be waived, the Licensor reserves the exclusive
+	     right to collect such royalties for any exercise by You
+	     of the rights granted under this License;
+
+    ii.  Waivable Compulsory License Schemes.  In those jurisdictions
+	     in which the right to collect royalties through any
+	     statutory or compulsory licensing scheme can be waived,
+	     the Licensor waives the exclusive right to collect such
+	     royalties for any exercise by You of the rights granted
+	     under this License; and,
+
+    iii. Voluntary License Schemes.  The Licensor waives the right to
+	     collect royalties, whether individually or, in the event
+	     that the Licensor is a member of a collecting society
+	     that administers voluntary licensing schemes, via that
+	     society, from any exercise by You of the rights granted
+	     under this License.
+
+The above rights may be exercised in all media and formats whether now
+known or hereafter devised.  The above rights include the right to
+make such modifications as are technically necessary to exercise the
+rights in other media and formats.  Subject to Section 8(f), all
+rights not expressly granted by Licensor are hereby reserved.
+
+4.  Restrictions.  The license granted in Section 3 above is expressly
+    made subject to and limited by the following restrictions:
+
+  a.  You may Distribute or Publicly Perform the Work only under the
+      terms of this License.  You must include a copy of, or the
+      Uniform Resource Identifier (URI) for, this License with every
+      copy of the Work You Distribute or Publicly Perform.  You may
+      not offer or impose any terms on the Work that restrict the
+      terms of this License or the ability of the recipient of the
+      Work to exercise the rights granted to that recipient under the
+      terms of the License.  You may not sublicense the Work.  You
+      must keep intact all notices that refer to this License and to
+      the disclaimer of warranties with every copy of the Work You
+      Distribute or Publicly Perform.  When You Distribute or Publicly
+      Perform the Work, You may not impose any effective technological
+      measures on the Work that restrict the ability of a recipient of
+      the Work from You to exercise the rights granted to that
+      recipient under the terms of the License.  This Section 4(a)
+      applies to the Work as incorporated in a Collection, but this
+      does not require the Collection apart from the Work itself to be
+      made subject to the terms of this License.  If You create a
+      Collection, upon notice from any Licensor You must, to the
+      extent practicable, remove from the Collection any credit as
+      required by Section 4(b), as requested.  If You create an
+      Adaptation, upon notice from any Licensor You must, to the
+      extent practicable, remove from the Adaptation any credit as
+      required by Section 4(b), as requested.
+
+  b.  If You Distribute, or Publicly Perform the Work or any
+      Adaptations or Collections, You must, unless a request has been
+      made pursuant to Section 4(a), keep intact all copyright notices
+      for the Work and provide, reasonable to the medium or means You
+      are utilizing: (i) the name of the Original Author (or
+      pseudonym, if applicable) if supplied, and/or if the Original
+      Author and/or Licensor designate another party or parties (e.g.,
+      a sponsor institute, publishing entity, journal) for attribution
+      ("Attribution Parties") in Licensor's copyright notice, terms of
+      service or by other reasonable means, the name of such party or
+      parties; (ii) the title of the Work if supplied; (iii) to the
+      extent reasonably practicable, the URI, if any, that Licensor
+      specifies to be associated with the Work, unless such URI does
+      not refer to the copyright notice or licensing information for
+      the Work; and (iv) , consistent with Section 3(b), in the case
+      of an Adaptation, a credit identifying the use of the Work in
+      the Adaptation (e.g., "French translation of the Work by
+      Original Author," or "Screenplay based on original Work by
+      Original Author").  The credit required by this Section 4 (b)
+      may be implemented in any reasonable manner; provided, however,
+      that in the case of a Adaptation or Collection, at a minimum
+      such credit will appear, if a credit for all contributing
+      authors of the Adaptation or Collection appears, then as part of
+      these credits and in a manner at least as prominent as the
+      credits for the other contributing authors.  For the avoidance
+      of doubt, You may only use the credit required by this Section
+      for the purpose of attribution in the manner set out above and,
+      by exercising Your rights under this License, You may not
+      implicitly or explicitly assert or imply any connection with,
+      sponsorship or endorsement by the Original Author, Licensor
+      and/or Attribution Parties, as appropriate, of You or Your use
+      of the Work, without the separate, express prior written
+      permission of the Original Author, Licensor and/or Attribution
+      Parties.
+
+  c.  Except as otherwise agreed in writing by the Licensor or as may
+      be otherwise permitted by applicable law, if You Reproduce,
+      Distribute or Publicly Perform the Work either by itself or as
+      part of any Adaptations or Collections, You must not distort,
+      mutilate, modify or take other derogatory action in relation to
+      the Work which would be prejudicial to the Original Author's
+      honor or reputation.  Licensor agrees that in those
+      jurisdictions (e.g.  Japan), in which any exercise of the right
+      granted in Section 3(b) of this License (the right to make
+      Adaptations) would be deemed to be a distortion, mutilation,
+      modification or other derogatory action prejudicial to the
+      Original Author's honor and reputation, the Licensor will waive
+      or not assert, as appropriate, this Section, to the fullest
+      extent permitted by the applicable national law, to enable You
+      to reasonably exercise Your right under Section 3(b) of this
+      License (right to make Adaptations) but not otherwise.
+
+5.  Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING,
+LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR
+WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED,
+STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF
+TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR PURPOSE,
+NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY,
+OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE.
+SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES,
+SO SUCH EXCLUSION MAY NOT APPLY TO YOU.
+
+6.  Limitation on Liability.  EXCEPT TO THE EXTENT REQUIRED BY
+    APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY
+    LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE
+    OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE
+    WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
+    DAMAGES.
+
+7.  Termination
+
+  a.  This License and the rights granted hereunder will terminate
+      automatically upon any breach by You of the terms of this
+      License.  Individuals or entities who have received Adaptations
+      or Collections from You under this License, however, will not
+      have their licenses terminated provided such individuals or
+      entities remain in full compliance with those licenses.
+      Sections 1, 2, 5, 6, 7, and 8 will survive any termination of
+      this License.
+
+  b.  Subject to the above terms and conditions, the license granted
+      here is perpetual (for the duration of the applicable copyright
+      in the Work).  Notwithstanding the above, Licensor reserves the
+      right to release the Work under different license terms or to
+      stop distributing the Work at any time; provided, however that
+      any such election will not serve to withdraw this License (or
+      any other license that has been, or is required to be, granted
+      under the terms of this License), and this License will continue
+      in full force and effect unless terminated as stated above.
+
+8. Miscellaneous
+
+  a.  Each time You Distribute or Publicly Perform the Work or a
+      Collection, the Licensor offers to the recipient a license to
+      the Work on the same terms and conditions as the license granted
+      to You under this License.
+
+  b.  Each time You Distribute or Publicly Perform an Adaptation,
+      Licensor offers to the recipient a license to the original Work
+      on the same terms and conditions as the license granted to You
+      under this License.
+
+  c.  If any provision of this License is invalid or unenforceable
+      under applicable law, it shall not affect the validity or
+      enforceability of the remainder of the terms of this License,
+      and without further action by the parties to this agreement,
+      such provision shall be reformed to the minimum extent necessary
+      to make such provision valid and enforceable.
+
+  d.  No term or provision of this License shall be deemed waived and
+      no breach consented to unless such waiver or consent shall be in
+      writing and signed by the party to be charged with such waiver
+      or consent.
+
+  e.  This License constitutes the entire agreement between the
+      parties with respect to the Work licensed here.  There are no
+      understandings, agreements or representations with respect to
+      the Work not specified here.  Licensor shall not be bound by any
+      additional provisions that may appear in any communication from
+      You.  This License may not be modified without the mutual
+      written agreement of the Licensor and You.
+
+  f.  The rights granted under, and the subject matter referenced, in
+      this License were drafted utilizing the terminology of the Berne
+      Convention for the Protection of Literary and Artistic Works (as
+      amended on September 28, 1979), the Rome Convention of 1961, the
+      WIPO Copyright Treaty of 1996, the WIPO Performances and
+      Phonograms Treaty of 1996 and the Universal Copyright Convention
+      (as revised on July 24, 1971).  These rights and subject matter
+      take effect in the relevant jurisdiction in which the License
+      terms are sought to be enforced according to the corresponding
+      provisions of the implementation of those treaty provisions in
+      the applicable national law.  If the standard suite of rights
+      granted under applicable copyright law includes additional
+      rights not granted under this License, such additional rights
+      are deemed to be included in the License; this License is not
+      intended to restrict the license of any rights under applicable
+      law.
diff --git a/lib/jgit/org.eclipse.jgit.archive/BUCK b/lib/jgit/org.eclipse.jgit.archive/BUCK
index af0284f..7c967b3 100644
--- a/lib/jgit/org.eclipse.jgit.archive/BUCK
+++ b/lib/jgit/org.eclipse.jgit.archive/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'jgit-archive',
   id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
-  sha1 = '73c3dd7d57c54b4ec95db6325e2d51dd3a0e6036',
+  sha1 = '2db2e7666672a31fa41b7e1dadcba51df6d30954',
   license = 'jgit',
   repository = REPO,
   deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.http.server/BUCK b/lib/jgit/org.eclipse.jgit.http.server/BUCK
index c93ea99..06865cb 100644
--- a/lib/jgit/org.eclipse.jgit.http.server/BUCK
+++ b/lib/jgit/org.eclipse.jgit.http.server/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'jgit-servlet',
   id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
-  sha1 = 'a85e11f0f31f71a4e4e83313c4c24167e99d330c',
+  sha1 = '6e36638888918d9941dddec7e2abe1f162cc74d9',
   license = 'jgit',
   repository = REPO,
   deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUCK b/lib/jgit/org.eclipse.jgit.junit/BUCK
index 9d4a2dd..77b637a 100644
--- a/lib/jgit/org.eclipse.jgit.junit/BUCK
+++ b/lib/jgit/org.eclipse.jgit.junit/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
-  sha1 = 'cd7e83bb138d0c3dad9d2dea27c238824d056a4b',
+  sha1 = 'e8fb1d81f588c3174a9730bdecdbde9faa04140a',
   license = 'DO_NOT_DISTRIBUTE',
   repository = REPO,
   unsign = True,
diff --git a/lib/jgit/org.eclipse.jgit/BUCK b/lib/jgit/org.eclipse.jgit/BUCK
index da1ad9a..458703c 100644
--- a/lib/jgit/org.eclipse.jgit/BUCK
+++ b/lib/jgit/org.eclipse.jgit/BUCK
@@ -4,8 +4,8 @@
 maven_jar(
   name = 'jgit',
   id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
-  bin_sha1 = '0bbcd8846f2731e50fccfb9f3ced43e2298844d4',
-  src_sha1 = '72d8aa79f8e645bb44f9f2fe74d02c6021516261',
+  bin_sha1 = '3e3d0b73dcf4ad649f37758ea8502d92f3d299de',
+  src_sha1 = 'fc352952db91a4046e4b832145eb2dc8afce8db1',
   license = 'jgit',
   repository = REPO,
   unsign = True,
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
index 0676595..1d87533 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions.js
@@ -320,6 +320,10 @@
     },
 
     _handleConfirmDialogCancel: function() {
+      this._hideAllDialogs();
+    },
+
+    _hideAllDialogs: function() {
       var dialogEls =
           Polymer.dom(this.root).querySelectorAll('.confirmDialog');
       for (var i = 0; i < dialogEls.length; i++) {
@@ -343,7 +347,7 @@
         payload.base = el.base;
       }
       this.$.overlay.close();
-      el.hidden = false;
+      el.hidden = true;
       this._fireAction('/rebase', this._revisionActions.rebase, true, payload);
     },
 
@@ -359,7 +363,7 @@
         return;
       }
       this.$.overlay.close();
-      el.hidden = false;
+      el.hidden = true;
       this._fireAction(
           '/cherrypick',
           this._revisionActions.cherrypick,
@@ -374,7 +378,7 @@
     _handleRevertDialogConfirm: function() {
       var el = this.$.confirmRevertDialog;
       this.$.overlay.close();
-      el.hidden = false;
+      el.hidden = true;
       this._fireAction('/revert', this.actions.revert, false,
           {message: el.message});
     },
@@ -382,7 +386,7 @@
     _handleAbandonDialogConfirm: function() {
       var el = this.$.confirmAbandonDialog;
       this.$.overlay.close();
-      el.hidden = false;
+      el.hidden = true;
       this._fireAction('/abandon', this.actions.abandon, false,
           {message: el.message});
     },
@@ -405,6 +409,8 @@
     },
 
     _showActionDialog: function(dialog) {
+      this._hideAllDialogs();
+
       dialog.hidden = false;
       this.$.overlay.open().then(function() {
         if (dialog.resetFocus) {
diff --git a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
index 9b04c8a..83d5bdc 100644
--- a/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-actions/gr-change-actions_test.html
@@ -42,24 +42,25 @@
               method: 'DELETE',
               label: 'Delete',
               title: 'Delete draft revision 2',
-              enabled: true
+              enabled: true,
             },
             cherrypick: {
               method: 'POST',
               label: 'Cherry Pick',
               title: 'Cherry pick change to a different branch',
-              enabled: true
+              enabled: true,
             },
             rebase: {
               method: 'POST',
               label: 'Rebase',
-              title: 'Rebase onto tip of branch or parent change'
+              title: 'Rebase onto tip of branch or parent change',
+              enabled: true,
             },
             submit: {
               method: 'POST',
               label: 'Submit',
               title: 'Submit patch set 2 into master',
-              enabled: true
+              enabled: true,
             },
           });
         },
@@ -162,6 +163,7 @@
           __key: 'rebase',
           __type: 'revision',
           __primary: false,
+          enabled: true,
           label: 'Rebase',
           method: 'POST',
           title: 'Rebase onto tip of branch or parent change',
@@ -187,6 +189,25 @@
       });
     });
 
+    test('two dialogs are not shown at the same time', function(done) {
+      flush(function() {
+        var rebaseButton = element.$$('gr-button[data-action-key="rebase"]');
+        assert.ok(rebaseButton);
+        MockInteractions.tap(rebaseButton);
+        flushAsynchronousOperations();
+        assert.isFalse(element.$.confirmRebase.hidden);
+
+        var cherryPickButton =
+            element.$$('gr-button[data-action-key="cherrypick"]');
+        assert.ok(cherryPickButton);
+        MockInteractions.tap(cherryPickButton);
+        flushAsynchronousOperations();
+        assert.isTrue(element.$.confirmRebase.hidden);
+        assert.isFalse(element.$.confirmCherrypick.hidden);
+        done();
+      });
+    });
+
     suite('cherry-pick', function() {
       var fireActionStub;
       var alertStub;
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index b5e1ef0..fe08b78 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -561,7 +561,7 @@
     },
 
     _handleReloadChange: function() {
-      page.show(this.changePath(this._changeNum));
+      this._reload();
     },
 
     _handleGetChangeDetailError: function(response) {
@@ -654,9 +654,9 @@
       } else {
         // The patch number is reliant on the change detail request.
         return detailCompletes.then(function() {
-          this._reloadPatchNumDependentResources();
+          return this._reloadPatchNumDependentResources();
         }.bind(this)).then(function() {
-          this._reloadDetailDependentResources();
+          return this._reloadDetailDependentResources();
         }.bind(this));
       }
     },
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
index cbf63d6..57a2e1f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.html
@@ -112,8 +112,8 @@
       </div>
     </div>
     <div class="actions">
-      <gr-button primary on-tap="_handleSave">Save</gr-button>
-      <gr-button on-tap="_handleCancel">Cancel</gr-button>
+      <gr-button id="saveButton" primary on-tap="_handleSave">Save</gr-button>
+      <gr-button id="cancelButton" on-tap="_handleCancel">Cancel</gr-button>
     </div>
   </template>
   <script src="gr-diff-preferences.js"></script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
index 4103b2e..9bccd23 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences.js
@@ -53,6 +53,17 @@
       '_localPrefsChanged(localPrefs.*)',
     ],
 
+    getFocusStops: function() {
+      return {
+        start: this.$.contextSelect,
+        end: this.$.cancelButton,
+      };
+    },
+
+    resetFocus: function() {
+      this.$.contextSelect.focus();
+    },
+
     _prefsChanged: function(changeRecord) {
       var prefs = changeRecord.base;
       // TODO(andybons): This is not supported in IE. Implement a polyfill.
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
index 0c40d9f..3c4d457 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-preferences/gr-diff-preferences_test.html
@@ -62,6 +62,14 @@
       assert.isFalse(element._newPrefs.syntax_highlighting);
     });
 
+    test('clicking save button calls _handleSave function', function() {
+      var savePrefs = sinon.stub(element, '_handleSave');
+      MockInteractions.tap(element.$.saveButton);
+      flushAsynchronousOperations();
+      assert(savePrefs.calledOnce);
+      savePrefs.restore();
+    });
+
     test('events', function(done) {
       var savePromise = new Promise(function(resolve) {
         element.addEventListener('save', function() { resolve(); });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index 76f7aec..4b91305 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -209,6 +209,7 @@
       </div>
       <gr-overlay id="prefsOverlay" with-backdrop>
         <gr-diff-preferences
+            id="diffPreferences"
             prefs="{{_prefs}}"
             local-prefs="{{_localPrefs}}"
             on-save="_handlePrefsSave"
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 40b0ee1..efe8684 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -255,7 +255,7 @@
           break;
         case 188:  // ','
           e.preventDefault();
-          this.$.prefsOverlay.open();
+          this._openPrefs();
           break;
       }
     },
@@ -267,6 +267,15 @@
       page.show(this._computeNavLinkURL(path, fileList, direction));
     },
 
+    _openPrefs: function() {
+      this.$.prefsOverlay.open().then(function() {
+        var diffPreferences = this.$.diffPreferences;
+        var focusStops = diffPreferences.getFocusStops();
+        this.$.prefsOverlay.setFocusStops(focusStops);
+        this.$.diffPreferences.resetFocus();
+      }.bind(this));
+    },
+
     /**
      * @param {?string} path The path of the current file being shown.
      * @param {Array.<string>} fileList The list of files in this change and
@@ -451,7 +460,7 @@
 
     _handlePrefsTap: function(e) {
       e.preventDefault();
-      this.$.prefsOverlay.open();
+      this._openPrefs();
     },
 
     _handlePrefsSave: function(e) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index 979ed55..14fd2b7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -36,7 +36,7 @@
   <template>
     <div></div>
   </template>
-</text-fixture>
+</test-fixture>
 
 <script>
   suite('gr-diff-view tests', function() {
@@ -103,7 +103,9 @@
           'Should navigate to /c/42/');
       assert.equal(element.changeViewState.selectedFileIndex, 0);
 
-      var showPrefsStub = sinon.stub(element.$.prefsOverlay, 'open');
+      var showPrefsStub = sinon.stub(element.$.prefsOverlay, 'open',
+          function() { return Promise.resolve({}); });
+
       MockInteractions.pressAndReleaseKeyOn(element, 188);  // ','
       assert(showPrefsStub.calledOnce);
 
@@ -131,6 +133,26 @@
       showStub.restore();
     });
 
+    test('saving diff preferences', function() {
+      var savePrefs = sinon.stub(element, '_handlePrefsSave');
+      var cancelPrefs = sinon.stub(element, '_handlePrefsCancel');
+      element.$.diffPreferences._handleSave();
+      assert(savePrefs.calledOnce);
+      assert(cancelPrefs.notCalled);
+      savePrefs.restore();
+      cancelPrefs.restore();
+    });
+
+    test('cancelling diff preferences', function() {
+      var savePrefs = sinon.stub(element, '_handlePrefsSave');
+      var cancelPrefs = sinon.stub(element, '_handlePrefsCancel');
+      element.$.diffPreferences._handleCancel();
+      assert(cancelPrefs.calledOnce);
+      assert(savePrefs.notCalled);
+      savePrefs.restore();
+      cancelPrefs.restore();
+    });
+
     test('keyboard shortcuts with patch range', function() {
       element._changeNum = '42';
       element._patchRange = {
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 25dfaff..051ee2e 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -126,9 +126,11 @@
       |
       <a class="feedback"
           href="https://bugs.chromium.org/p/gerrit/issues/entry?template=PolyGerrit%20Issue"
-          target="_blank">
-        Report PolyGerrit Bug
-      </a>
+          target="_blank">Report PolyGerrit Bug</a>
+      <template is="dom-if" if="[[_computeShowGwtUiLink(_serverConfig)]]">
+        |
+        <a href="/?polygerrit=0" rel="external">GWT UI</a>
+      </template>
     </footer>
     <gr-overlay id="keyboardShortcuts" with-backdrop>
       <gr-keyboard-shortcuts-dialog
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index f24ecfd..84467a0 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -127,6 +127,11 @@
       return !!(account && Object.keys(account).length > 0);
     },
 
+    _computeShowGwtUiLink: function(config) {
+      return config.gerrit.web_uis &&
+          config.gerrit.web_uis.indexOf('GWT') !== -1;
+    },
+
     _handlePageError: function(e) {
       [
         '_showChangeListView',
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
index 32c7c70..864114f 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.html
@@ -56,7 +56,6 @@
         placeholder="[[placeholder]]"
         on-keydown="_handleInputKeydown"
         on-focus="_onInputFocus"
-        on-blur="_onInputBlur"
         autocomplete="off" />
     <div
         id="suggestions"
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 16ba150..31918dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -117,6 +117,14 @@
 
     },
 
+    attached: function() {
+      this.listen(document.body, 'click', '_handleBodyClick');
+    },
+
+    detached: function() {
+      this.unlisten(document.body, 'click', '_handleBodyClick');
+    },
+
     get focusStart() {
       return this.$.input;
     },
@@ -144,10 +152,6 @@
       this._updateSuggestions();
     },
 
-    _onInputBlur: function() {
-      this._focused = false;
-    },
-
     _updateSuggestions: function() {
       if (!this.text || this._disableSuggestions) { return; }
       if (this.text.length < this.threshold) {
@@ -229,6 +233,16 @@
       }
     },
 
+    _handleBodyClick: function(e) {
+      var eventPath = Polymer.dom(e).path;
+      for (var i = 0; i < eventPath.length; i++) {
+        if (eventPath[i] === this) {
+          return;
+        }
+      }
+      this._focused = false;
+    },
+
     _handleSuggestionTap: function(e) {
       this.$.cursor.setCursor(e.target);
       this._commit();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index abf052c..7a26e72 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -271,8 +271,6 @@
       assert.isFalse(element._focused);
       element.$.input.focus();
       assert.isTrue(element._focused);
-      element.$.input.blur();
-      assert.isFalse(element._focused);
     });
 
     test('_focused flag shows/hides the suggestions', function() {
@@ -282,14 +280,18 @@
     });
 
     test('tap on suggestion commits and refocuses on input', function() {
-      var focusStub = sinon.stub(element, 'focus');
+      var focusSpy = sinon.spy(element, 'focus');
+      var commitSpy = sinon.spy(element, '_commit');
       element._focused = true;
       element._suggestions = [{name: 'first suggestion'}];
       assert.isFalse(element.$.suggestions.hasAttribute('hidden'));
       MockInteractions.tap(element.$$('#suggestions li:first-child'));
-      assert.isTrue(focusStub.called);
+      flushAsynchronousOperations();
+      assert.isTrue(focusSpy.called);
+      assert.isTrue(commitSpy.called);
       assert.isTrue(element.$.suggestions.hasAttribute('hidden'));
-      focusStub.restore();
+      focusSpy.restore();
+      commitSpy.restore();
     });
   });
 </script>
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD
index e69de29..01ae92c 100644
--- a/tools/bzl/BUILD
+++ b/tools/bzl/BUILD
@@ -0,0 +1,4 @@
+
+exports_files([
+  "license-map.py",
+  "test_empty.sh"])
diff --git a/tools/bzl/license-map.py b/tools/bzl/license-map.py
new file mode 100644
index 0000000..72c7ae8
--- /dev/null
+++ b/tools/bzl/license-map.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+
+# reads a bazel query XML file, to join target names with their licenses.
+
+import sys
+import xml.etree.ElementTree as ET
+
+tree = ET.parse(sys.argv[1])
+root = tree.getroot()
+
+entries = {}
+
+for child in root:
+  rule_name = child.attrib["name"]
+  for c in child.getchildren():
+    if c.tag != "rule-input":
+      continue
+
+    license_name = c.attrib["name"]
+    if "//lib:LICENSE" in license_name:
+      assert rule_name not in entries, (license_name, entries[rule_name])
+      entries[rule_name] = license_name
+
+for k, v in sorted(entries.items()):
+  print k, v
diff --git a/tools/bzl/license.bzl b/tools/bzl/license.bzl
new file mode 100644
index 0000000..ca64438
--- /dev/null
+++ b/tools/bzl/license.bzl
@@ -0,0 +1,47 @@
+
+def license_map(name, target):
+    """Generate XML for all targets that depend directly on a LICENSE file"""
+    native.genquery(
+        name = name + ".xml",
+        scope = [ target, ],
+
+        # Find everything that depends on a license file, but remove
+        # the license files themselves from this list.
+        expression = 'rdeps(%s, filter("//lib:LICENSE.*", deps(%s)),1) - filter("//lib:LICENSE.*", deps(%s))' % (target, target, target),
+
+        # We are interested in the edges of the graph ({java_library,
+        # license-file} tuples).  'query' provides this in the XML output.
+        opts = [ "--output=xml"],
+    )
+
+    # post process the XML into our favorite format.
+    native.genrule(
+        name = "gen_license_txt_" + name,
+        cmd = "python $(location //tools/bzl:license-map.py) $(location :%s.xml) > $@" % name,
+        outs = [ name + ".txt",],
+        tools = [ "//tools/bzl:license-map.py", name + ".xml"])
+
+def license_test(name, target):
+    """Generate XML for all targets that depend directly on a LICENSE file"""
+    txt = name + "-forbidden.txt"
+
+    # fully qualify target name.
+    if target[0] not in ":/":
+        target = ":" + target
+    if target[0] != "/":
+        target = "//" + PACKAGE_NAME + target
+
+    forbidden = "//lib:LICENSE-DO_NOT_DISTRIBUTE"
+    native.genquery(
+        name = txt,
+        scope = [ target, forbidden ],
+        # Find everything that depends on a license file, but remove
+        # the license files themselves from this list.
+        expression = 'rdeps(%s, "%s", 1) - rdeps(%s, "%s", 0)' % (target, forbidden, target, forbidden),
+    )
+    native.sh_test(
+        name = name,
+        srcs = [ "//tools/bzl:test_empty.sh" ],
+        args  = [ "$(location :%s)" % txt],
+        data = [ txt ],
+    )
diff --git a/tools/bzl/test_empty.sh b/tools/bzl/test_empty.sh
new file mode 100755
index 0000000..0d4398d
--- /dev/null
+++ b/tools/bzl/test_empty.sh
@@ -0,0 +1,8 @@
+#!/bin/sh
+
+if test -s $1
+then
+    echo "$1 not empty:"
+    cat $1
+    exit 1
+fi