Merge "Revert "Fix app size to make scrollbars visible""
diff --git a/.bazelrc b/.bazelrc
index a991c76..00acd27 100644
--- a/.bazelrc
+++ b/.bazelrc
@@ -1 +1 @@
-build --workspace_status_command=./tools/workspace-status.sh --strategy=Javac=worker
+build --strategy=Javac=worker
diff --git a/BUILD b/BUILD
index 4fa30f2..1373cd7 100644
--- a/BUILD
+++ b/BUILD
@@ -1,10 +1,11 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
 load('//tools/bzl:pkg_war.bzl', 'pkg_war')
 
-genrule(
-  name = 'gen_version',
-  stamp = 1,
-  cmd = "grep STABLE_BUILD_GERRIT_LABEL < bazel-out/volatile-status.txt | cut -d ' ' -f 2 > $@",
-  outs = ['version.txt'],
+genrule2(
+  name = 'version',
+  srcs = ['VERSION'],
+  cmd = "grep GERRIT_VERSION $< | cut -d \"'\" -f 2 >$@",
+  out = 'version.txt',
   visibility = ['//visibility:public'],
 )
 
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 219da7e..9deed50 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -609,6 +609,42 @@
 buck test --no-results-cache
 ----
 
+== Cross-compiling Java8 to Java7
+
+After switching to Java8, we should take care to not end up
+with Java8 code in stable branches. We assume that we don't
+really want to switch java versions locally every time we switch
+branches.
+
+Given that source level on 'stable-2.13' is 7, source level incompatibility
+will be already correctly detected, so that Java8 compiler would refuse
+to compile lambdas with -source 7 argument. However, unless bootclasspath
+is adjusted to point to Java7 runtime, it's possible to end up with broken
+code, that would compile with Java8 but will not run on Java7 runtime.
+
+To prevent this, add this line to your '.buckconfig.local' in the Gerrit
+source root directory when working on stable branches:
+
+----
+[java]
+  extra_arguments = -Xbootclasspath/p:/usr/lib64/jvm/java-1.7.0-openjdk-1.7.0/jre/lib/rt.jar
+----
+
+With this in place, methods that were added only in Java8 in runtime library,
+would be correctly refused to compile by Java8:
+
+----
+$ java -version
+openjdk version "1.8.0_101"
+
+$ buck build gerrit-server:server
+/home/davido/projects/gerrit/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java:218: error: cannot find symbol
+      return Collections.emptySortedSet();
+                        ^
+  symbol:   method emptySortedSet()
+  location: class java.util.Collections
+----
+
 == Upgrading Buck
 
 The following tests should be executed, when Buck version is upgraded:
diff --git a/Documentation/project-configuration.txt b/Documentation/project-configuration.txt
index d71d19a..2703e4e 100644
--- a/Documentation/project-configuration.txt
+++ b/Documentation/project-configuration.txt
@@ -117,6 +117,13 @@
 succeed if there is no path conflict.  A path conflict occurs when
 the same file has also been changed on the other side of the merge.
 
+[[rebase_always]]
+* Rebase Always
++
+Basically, the same as Rebase If Necesary, but it creates a new patchset even if
+fast forward is possible. In this regard, it's similar to Cherry Pick, but with
+the important distinction that Rebase Always does not ignore dependencies.
+
 [[content_merge]]
 If `Allow content merges` is enabled, Gerrit will try
 to do a content merge when a path conflict occurs.
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 300eb74..1910049 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
@@ -82,7 +82,7 @@
 import com.google.gerrit.server.index.change.ChangeIndex;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
 import com.google.gerrit.server.index.change.ChangeIndexer;
-import com.google.gerrit.server.mail.EmailHeader;
+import com.google.gerrit.server.mail.send.EmailHeader;
 import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
index 1033164..f132e0d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/SubmitTypeRuleIT.java
@@ -19,6 +19,7 @@
 import static com.google.gerrit.extensions.client.SubmitType.FAST_FORWARD_ONLY;
 import static com.google.gerrit.extensions.client.SubmitType.MERGE_ALWAYS;
 import static com.google.gerrit.extensions.client.SubmitType.MERGE_IF_NECESSARY;
+import static com.google.gerrit.extensions.client.SubmitType.REBASE_ALWAYS;
 import static com.google.gerrit.extensions.client.SubmitType.REBASE_IF_NECESSARY;
 import static org.junit.Assert.fail;
 
@@ -123,6 +124,10 @@
       + "gerrit:commit_message(M),"
       + "regex_matches('.*REBASE_IF_NECESSARY.*', M),"
       + "!.\n"
+      + "submit_type(rebase_always) :-"
+      + "gerrit:commit_message(M),"
+      + "regex_matches('.*REBASE_ALWAYS.*', M),"
+      + "!.\n"
       + "submit_type(merge_always) :-"
       + "gerrit:commit_message(M),"
       + "regex_matches('.*MERGE_ALWAYS.*', M),"
@@ -157,8 +162,9 @@
     PushOneCommit.Result r2 = createChange("master", "FAST_FORWARD_ONLY 2");
     PushOneCommit.Result r3 = createChange("master", "MERGE_IF_NECESSARY 3");
     PushOneCommit.Result r4 = createChange("master", "REBASE_IF_NECESSARY 4");
-    PushOneCommit.Result r5 = createChange("master", "MERGE_ALWAYS 5");
-    PushOneCommit.Result r6 = createChange("master", "CHERRY_PICK 6");
+    PushOneCommit.Result r5 = createChange("master", "REBASE_ALWAYS 5");
+    PushOneCommit.Result r6 = createChange("master", "MERGE_ALWAYS 6");
+    PushOneCommit.Result r7 = createChange("master", "CHERRY_PICK 7");
 
     assertSubmitType(MERGE_IF_NECESSARY, r1.getChangeId());
     assertSubmitType(MERGE_IF_NECESSARY, r2.getChangeId());
@@ -166,6 +172,7 @@
     assertSubmitType(MERGE_IF_NECESSARY, r4.getChangeId());
     assertSubmitType(MERGE_IF_NECESSARY, r5.getChangeId());
     assertSubmitType(MERGE_IF_NECESSARY, r6.getChangeId());
+    assertSubmitType(MERGE_IF_NECESSARY, r7.getChangeId());
 
     setRulesPl(SUBMIT_TYPE_FROM_SUBJECT);
 
@@ -173,8 +180,9 @@
     assertSubmitType(FAST_FORWARD_ONLY, r2.getChangeId());
     assertSubmitType(MERGE_IF_NECESSARY, r3.getChangeId());
     assertSubmitType(REBASE_IF_NECESSARY, r4.getChangeId());
-    assertSubmitType(MERGE_ALWAYS, r5.getChangeId());
-    assertSubmitType(CHERRY_PICK, r6.getChangeId());
+    assertSubmitType(REBASE_ALWAYS, r5.getChangeId());
+    assertSubmitType(MERGE_ALWAYS, r6.getChangeId());
+    assertSubmitType(CHERRY_PICK, r7.getChangeId());
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index dfeac35..cfa90e6 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -61,14 +61,21 @@
     return cfg;
   }
 
-  protected static Config submitByCherryPickConifg() {
+  protected static Config submitByCherryPickConfig() {
     Config cfg = new Config();
     cfg.setBoolean("change", null, "submitWholeTopic", true);
     cfg.setEnum("project", null, "submitType", SubmitType.CHERRY_PICK);
     return cfg;
   }
 
-  protected static Config submitByRebaseConifg() {
+  protected static Config submitByRebaseAlwaysConfig() {
+    Config cfg = new Config();
+    cfg.setBoolean("change", null, "submitWholeTopic", true);
+    cfg.setEnum("project", null, "submitType", SubmitType.REBASE_ALWAYS);
+    return cfg;
+  }
+
+  protected static Config submitByRebaseIfNecessaryConfig() {
     Config cfg = new Config();
     cfg.setBoolean("change", null, "submitWholeTopic", true);
     cfg.setEnum("project", null, "submitType", SubmitType.REBASE_IF_NECESSARY);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 43f19b9..44f3897 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -53,12 +53,17 @@
 
   @ConfigSuite.Config
   public static Config cherryPick() {
-    return submitByCherryPickConifg();
+    return submitByCherryPickConfig();
   }
 
   @ConfigSuite.Config
-  public static Config rebase() {
-    return submitByRebaseConifg();
+  public static Config rebaseAlways() {
+    return submitByRebaseAlwaysConfig();
+  }
+
+  @ConfigSuite.Config
+  public static Config rebaseIfNecessary() {
+    return submitByRebaseIfNecessaryConfig();
   }
 
   @Test
@@ -129,10 +134,11 @@
     assertThat(preview).containsKey(
         new Branch.NameKey(p2, "refs/heads/master"));
 
-    if (getSubmitType() == SubmitType.CHERRY_PICK) {
+    if ((getSubmitType() == SubmitType.CHERRY_PICK)
+        || (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
       // each change is updated and the respective target branch is updated:
       assertThat(preview).hasSize(5);
-    } else if (getSubmitType() == SubmitType.REBASE_IF_NECESSARY) {
+    } else if ((getSubmitType() == SubmitType.REBASE_IF_NECESSARY)) {
       // Either the first is used first as is, then the second and third need
       // rebasing, or those two stay as is and the first is rebased.
       // add in 2 master branches, expect 3 or 4:
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 3d1377e..9927c15 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -146,7 +146,8 @@
     Map<Branch.NameKey, RevTree> actual =
         fetchFromBundles(request);
 
-    if (getSubmitType() == SubmitType.CHERRY_PICK) {
+    if ((getSubmitType() == SubmitType.CHERRY_PICK)
+        || (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
       // The change is updated as well:
       assertThat(actual).hasSize(2);
     } else {
@@ -202,7 +203,8 @@
       assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
       assertChangeMergedEvents(change.getChangeId(),
           headAfterFirstSubmit.name());
-    } else if(getSubmitType() == SubmitType.REBASE_IF_NECESSARY) {
+    } else if ((getSubmitType() == SubmitType.REBASE_IF_NECESSARY)
+        || (getSubmitType() == SubmitType.REBASE_ALWAYS)) {
       String change2hash = change2.getChange().currentPatchSet()
           .getRevision().get();
       assertThat(msg).isEqualTo(
@@ -252,8 +254,14 @@
 
     assertThat(actual).containsKey(
         new Branch.NameKey(project, "refs/heads/master"));
-    if (getSubmitType() == SubmitType.CHERRY_PICK) {
+    if (getSubmitType() == SubmitType.CHERRY_PICK){
+      // CherryPick ignores dependencies, thus only change and destination
+      // branch refs are modified.
       assertThat(actual).hasSize(2);
+    } else if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
+      // RebaseAlways takes care of dependencies, therefore Change{2,3,4} and
+      // destination branch will be modified.
+      assertThat(actual).hasSize(4);
     } else {
       assertThat(actual).hasSize(1);
     }
@@ -409,6 +417,8 @@
     if (getSubmitType() == SubmitType.CHERRY_PICK) {
       assertThat(last).startsWith(
           "Change has been successfully cherry-picked as ");
+    } else if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
+      assertThat(last).startsWith("Change has been successfully rebased as");
     } else {
       assertThat(last).isEqualTo(
           "Change has been successfully merged by Administrator");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
new file mode 100644
index 0000000..492fc05
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmitByRebase.java
@@ -0,0 +1,353 @@
+// Copyright (C) 2013 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.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.acceptance.GitUtil.getChangeId;
+import static com.google.gerrit.acceptance.GitUtil.pushHead;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Iterables;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.extensions.api.changes.SubmitInput;
+import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.change.Submit.TestSubmitInput;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.Test;
+
+public abstract class AbstractSubmitByRebase extends AbstractSubmit {
+
+  @Override
+  protected abstract SubmitType getSubmitType();
+
+  @Test
+  @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
+  public void submitWithRebase() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange("Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit headAfterFirstSubmit = getRemoteHead();
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change2 =
+        createChange("Change 2", "b.txt", "other content");
+    submit(change2.getChangeId());
+    assertRebase(testRepo, false);
+    RevCommit headAfterSecondSubmit = getRemoteHead();
+    assertThat(headAfterSecondSubmit.getParent(0))
+        .isEqualTo(headAfterFirstSubmit);
+    assertApproved(change2.getChangeId());
+    assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
+    assertSubmitter(change2.getChangeId(), 1);
+    assertSubmitter(change2.getChangeId(), 2);
+    assertPersonEquals(admin.getIdent(),
+        headAfterSecondSubmit.getAuthorIdent());
+    assertPersonEquals(admin.getIdent(),
+        headAfterSecondSubmit.getCommitterIdent());
+
+    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
+        headAfterFirstSubmit, headAfterSecondSubmit);
+    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
+        change2.getChangeId(), headAfterSecondSubmit.name());
+  }
+
+  @Test
+  public void submitWithRebaseMultipleChanges() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change1 =
+        createChange("Change 1", "a.txt", "content");
+    submit(change1.getChangeId());
+    RevCommit headAfterFirstSubmit = getRemoteHead();
+    if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
+      assertCurrentRevision(change1.getChangeId(), 2, headAfterFirstSubmit);
+    } else {
+      assertThat(headAfterFirstSubmit.name())
+          .isEqualTo(change1.getCommit().name());
+    }
+
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change2 =
+        createChange("Change 2", "b.txt", "other content");
+    assertThat(change2.getCommit().getParent(0))
+        .isNotEqualTo(change1.getCommit());
+    PushOneCommit.Result change3 =
+        createChange("Change 3", "c.txt", "third content");
+    PushOneCommit.Result change4 =
+        createChange("Change 4", "d.txt", "fourth content");
+    approve(change2.getChangeId());
+    approve(change3.getChangeId());
+    submit(change4.getChangeId());
+
+    assertRebase(testRepo, false);
+    assertApproved(change2.getChangeId());
+    assertApproved(change3.getChangeId());
+    assertApproved(change4.getChangeId());
+
+    RevCommit headAfterSecondSubmit = parse(getRemoteHead());
+    assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
+    assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
+    assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
+
+    RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
+    assertThat(parent.getShortMessage()).isEqualTo("Change 3");
+    assertThat(parent).isNotEqualTo(change3.getCommit());
+    assertCurrentRevision(change3.getChangeId(), 2, parent);
+
+    RevCommit grandparent = parse(parent.getParent(0));
+    assertThat(grandparent).isNotEqualTo(change2.getCommit());
+    assertCurrentRevision(change2.getChangeId(), 2, grandparent);
+
+    RevCommit greatgrandparent = parse(grandparent.getParent(0));
+    assertThat(greatgrandparent).isEqualTo(headAfterFirstSubmit);
+    if (getSubmitType() == SubmitType.REBASE_ALWAYS) {
+      assertCurrentRevision(change1.getChangeId(), 2, greatgrandparent);
+    } else {
+      assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
+    }
+
+
+    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
+        headAfterFirstSubmit, headAfterSecondSubmit);
+    assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name(),
+        change2.getChangeId(), headAfterSecondSubmit.name(),
+        change3.getChangeId(), headAfterSecondSubmit.name(),
+        change4.getChangeId(), headAfterSecondSubmit.name());
+  }
+
+  @Test
+  public void submitWithRebaseMergeCommit() throws Exception {
+    /*
+        *  (HEAD, origin/master, origin/HEAD) Merge changes X,Y
+        |\
+        | *   Merge branch 'master' into origin/master
+        | |\
+        | | * SHA Added a
+        | |/
+        * | Before
+        |/
+        * Initial empty repository
+     */
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
+
+    PushOneCommit change2Push = pushFactory.create(db, admin.getIdent(), testRepo,
+        "Merge to master", "m.txt", "");
+    change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
+    PushOneCommit.Result change2 = change2Push.to("refs/for/master");
+
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
+
+    approve(change3.getChangeId());
+    submit(change3.getChangeId());
+
+    approve(change1.getChangeId());
+    approve(change2.getChangeId());
+    submit(change2.getChangeId());
+
+    RevCommit newHead = getRemoteHead();
+    assertThat(newHead.getParentCount()).isEqualTo(2);
+
+    RevCommit headParent1 = parse(newHead.getParent(0).getId());
+    RevCommit headParent2 = parse(newHead.getParent(1).getId());
+
+    if (getSubmitType() == SubmitType.REBASE_ALWAYS){
+      assertCurrentRevision(change3.getChangeId(), 2, headParent1.getId());
+    } else {
+      assertThat(change3.getCommit().getId()).isEqualTo(headParent1.getId());
+    }
+    assertThat(headParent1.getParentCount()).isEqualTo(1);
+    assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
+
+    assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
+    assertThat(headParent2.getParentCount()).isEqualTo(2);
+
+    RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
+    RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
+
+    assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
+    assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
+  }
+
+  @Test
+  @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
+  public void submitWithContentMerge_Conflict() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange("Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit headAfterFirstSubmit = getRemoteHead();
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change2 =
+        createChange("Change 2", "a.txt", "other content");
+    submitWithConflict(change2.getChangeId(),
+        "Cannot rebase " + change2.getCommit().name()
+        + ": The change could not be rebased due to a conflict during merge.");
+    RevCommit head = getRemoteHead();
+    assertThat(head).isEqualTo(headAfterFirstSubmit);
+    assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
+    assertNoSubmitter(change2.getChangeId(), 1);
+
+    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
+    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
+  }
+
+  @Test
+  public void repairChangeStateAfterFailure() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+    PushOneCommit.Result change =
+        createChange("Change 1", "a.txt", "content");
+    submit(change.getChangeId());
+
+    RevCommit headAfterFirstSubmit = getRemoteHead();
+    testRepo.reset(initialHead);
+    PushOneCommit.Result change2 =
+        createChange("Change 2", "b.txt", "other content");
+    Change.Id id2 = change2.getChange().getId();
+    SubmitInput failAfterRefUpdates =
+        new TestSubmitInput(new SubmitInput(), true);
+    submit(change2.getChangeId(), failAfterRefUpdates,
+        ResourceConflictException.class, "Failing after ref updates");
+    RevCommit headAfterFailedSubmit = getRemoteHead();
+
+    // Bad: ref advanced but change wasn't updated.
+    PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
+    PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
+    ChangeInfo info = gApi.changes().id(id2.get()).get();
+    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
+    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
+    assertThat(getPatchSet(psId2)).isNull();
+
+    ObjectId rev2;
+    try (Repository repo = repoManager.openRepository(project);
+        RevWalk rw = new RevWalk(repo)) {
+      ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
+      assertThat(rev1).isNotNull();
+
+      rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
+      assertThat(rev2).isNotNull();
+      assertThat(rev2).isNotEqualTo(rev1);
+      assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
+
+      assertThat(repo.exactRef("refs/heads/master").getObjectId())
+          .isEqualTo(rev2);
+    }
+
+    submit(change2.getChangeId());
+    RevCommit headAfterSecondSubmit = getRemoteHead();
+    assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
+
+    // Change status and patch set entities were updated, and branch tip stayed
+    // the same.
+    info = gApi.changes().id(id2.get()).get();
+    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
+    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
+    PatchSet ps2 = getPatchSet(psId2);
+    assertThat(ps2).isNotNull();
+    assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
+    assertThat(Iterables.getLast(info.messages).message)
+        .isEqualTo("Change has been successfully rebased as "
+            + rev2.name() + " by Administrator");
+
+    try (Repository repo = repoManager.openRepository(project)) {
+      assertThat(repo.exactRef("refs/heads/master").getObjectId())
+          .isEqualTo(rev2);
+    }
+
+    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
+    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
+        change2.getChangeId(), headAfterSecondSubmit.name());
+  }
+
+  protected RevCommit parse(ObjectId id) throws Exception {
+    try (Repository repo = repoManager.openRepository(project);
+        RevWalk rw = new RevWalk(repo)) {
+      RevCommit c = rw.parseCommit(id);
+      rw.parseBody(c);
+      return c;
+    }
+  }
+
+  @Test
+  public void submitAfterReorderOfCommits() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+
+    // Create two commits and push.
+    RevCommit c1 = commitBuilder()
+        .add("a.txt", "1")
+        .message("subject: 1")
+        .create();
+    RevCommit c2 = commitBuilder()
+        .add("b.txt", "2")
+        .message("subject: 2")
+        .create();
+    pushHead(testRepo, "refs/for/master", false);
+
+    String id1 = getChangeId(testRepo, c1).get();
+    String id2 = getChangeId(testRepo, c2).get();
+
+    // Swap the order of commits and push again.
+    testRepo.reset("HEAD~2");
+    testRepo.cherryPick(c2);
+    testRepo.cherryPick(c1);
+    pushHead(testRepo, "refs/for/master", false);
+
+    approve(id1);
+    approve(id2);
+    submit(id1);
+    RevCommit headAfterSubmit = getRemoteHead();
+
+    assertRefUpdatedEvents(initialHead, headAfterSubmit);
+    assertChangeMergedEvents(id2, headAfterSubmit.name(),
+        id1, headAfterSubmit.name());
+  }
+
+  @Test
+  public void submitChangesAfterBranchOnSecond() throws Exception {
+    RevCommit initialHead = getRemoteHead();
+
+    PushOneCommit.Result change = createChange();
+    approve(change.getChangeId());
+
+    PushOneCommit.Result change2 = createChange();
+    approve(change2.getChangeId());
+    Project.NameKey project = change2.getChange().change().getProject();
+    Branch.NameKey branch = new Branch.NameKey(project, "branch");
+    createBranchWithRevision(branch, change2.getCommit().getName());
+    gApi.changes().id(change2.getChangeId()).current().submit();
+    assertMerged(change2.getChangeId());
+    assertMerged(change.getChangeId());
+
+    RevCommit newHead = getRemoteHead();
+    assertRefUpdatedEvents(initialHead, newHead);
+    assertChangeMergedEvents(change.getChangeId(), newHead.name(),
+        change2.getChangeId(), newHead.name());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index 04e71eb..654ce29 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -1,10 +1,6 @@
 include_defs('//gerrit-acceptance-tests/tests.defs')
 
-SUBMIT_UTIL_SRCS = [
-  'AbstractSubmit.java',
-  'AbstractSubmitByMerge.java',
-]
-
+SUBMIT_UTIL_SRCS = glob(['AbstractSubmit*.java'])
 SUBMIT_TESTS = glob(['Submit*IT.java'])
 OTHER_TESTS = glob(['*IT.java'], excludes = SUBMIT_TESTS)
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
index c06f02f..6fbf9c5 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUILD
@@ -1,10 +1,6 @@
 load('//gerrit-acceptance-tests:tests.bzl', 'acceptance_tests')
 
-SUBMIT_UTIL_SRCS = [
-  'AbstractSubmit.java',
-  'AbstractSubmitByMerge.java',
-]
-
+SUBMIT_UTIL_SRCS = glob(['AbstractSubmit*.java'])
 SUBMIT_TESTS = glob(['Submit*IT.java'])
 OTHER_TESTS = glob(['*IT.java'], exclude = SUBMIT_TESTS)
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
new file mode 100644
index 0000000..cf9651f
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseAlwaysIT.java
@@ -0,0 +1,53 @@
+// 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.rest.change;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.TestProjectInput;
+import com.google.gerrit.extensions.client.InheritableBoolean;
+import com.google.gerrit.extensions.client.SubmitType;
+
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.junit.Test;
+
+public class SubmitByRebaseAlwaysIT extends AbstractSubmitByRebase {
+
+  @Override
+  protected SubmitType getSubmitType() {
+    return SubmitType.REBASE_ALWAYS;
+  }
+
+  @Test
+  @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
+  public void submitWithPossibleFastForward() throws Exception {
+    RevCommit oldHead = getRemoteHead();
+    PushOneCommit.Result change = createChange();
+    submit(change.getChangeId());
+
+    RevCommit head = getRemoteHead();
+    assertThat(head.getId()).isNotEqualTo(change.getCommit());
+    assertThat(head.getParent(0)).isEqualTo(oldHead);
+    assertApproved(change.getChangeId());
+    assertCurrentRevision(change.getChangeId(), 2, head);
+    assertSubmitter(change.getChangeId(), 1);
+    assertSubmitter(change.getChangeId(), 2);
+    assertPersonEquals(admin.getIdent(), head.getAuthorIdent());
+    assertPersonEquals(admin.getIdent(), head.getCommitterIdent());
+    assertRefUpdatedEvents(oldHead, head);
+    assertChangeMergedEvents(change.getChangeId(), head.name());
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
index 9b3fd15..431978d2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/SubmitByRebaseIfNecessaryIT.java
@@ -15,32 +15,16 @@
 package com.google.gerrit.acceptance.rest.change;
 
 import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.acceptance.GitUtil.getChangeId;
-import static com.google.gerrit.acceptance.GitUtil.pushHead;
 
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.acceptance.PushOneCommit;
 import com.google.gerrit.acceptance.TestProjectInput;
-import com.google.gerrit.extensions.api.changes.SubmitInput;
-import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.SubmitType;
-import com.google.gerrit.extensions.common.ChangeInfo;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.change.Submit.TestSubmitInput;
 
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
 import org.junit.Test;
 
-public class SubmitByRebaseIfNecessaryIT extends AbstractSubmit {
+public class SubmitByRebaseIfNecessaryIT extends AbstractSubmitByRebase {
 
   @Override
   protected SubmitType getSubmitType() {
@@ -67,143 +51,6 @@
 
   @Test
   @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
-  public void submitWithRebase() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change =
-        createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 =
-        createChange("Change 2", "b.txt", "other content");
-    submit(change2.getChangeId());
-    assertRebase(testRepo, false);
-    RevCommit headAfterSecondSubmit = getRemoteHead();
-    assertThat(headAfterSecondSubmit.getParent(0))
-        .isEqualTo(headAfterFirstSubmit);
-    assertApproved(change2.getChangeId());
-    assertCurrentRevision(change2.getChangeId(), 2, headAfterSecondSubmit);
-    assertSubmitter(change2.getChangeId(), 1);
-    assertSubmitter(change2.getChangeId(), 2);
-    assertPersonEquals(admin.getIdent(),
-        headAfterSecondSubmit.getAuthorIdent());
-    assertPersonEquals(admin.getIdent(),
-        headAfterSecondSubmit.getCommitterIdent());
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
-        headAfterFirstSubmit, headAfterSecondSubmit);
-    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
-        change2.getChangeId(), headAfterSecondSubmit.name());
-  }
-
-  @Test
-  public void submitWithRebaseMultipleChanges() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change1 =
-        createChange("Change 1", "a.txt", "content");
-    submit(change1.getChangeId());
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    assertThat(headAfterFirstSubmit.name())
-        .isEqualTo(change1.getCommit().name());
-
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 =
-        createChange("Change 2", "b.txt", "other content");
-    assertThat(change2.getCommit().getParent(0))
-        .isNotEqualTo(change1.getCommit());
-    PushOneCommit.Result change3 =
-        createChange("Change 3", "c.txt", "third content");
-    PushOneCommit.Result change4 =
-        createChange("Change 4", "d.txt", "fourth content");
-    approve(change2.getChangeId());
-    approve(change3.getChangeId());
-    submit(change4.getChangeId());
-
-    assertRebase(testRepo, false);
-    assertApproved(change2.getChangeId());
-    assertApproved(change3.getChangeId());
-    assertApproved(change4.getChangeId());
-
-    RevCommit headAfterSecondSubmit = parse(getRemoteHead());
-    assertThat(headAfterSecondSubmit.getShortMessage()).isEqualTo("Change 4");
-    assertThat(headAfterSecondSubmit).isNotEqualTo(change4.getCommit());
-    assertCurrentRevision(change4.getChangeId(), 2, headAfterSecondSubmit);
-
-    RevCommit parent = parse(headAfterSecondSubmit.getParent(0));
-    assertThat(parent.getShortMessage()).isEqualTo("Change 3");
-    assertThat(parent).isNotEqualTo(change3.getCommit());
-    assertCurrentRevision(change3.getChangeId(), 2, parent);
-
-    RevCommit grandparent = parse(parent.getParent(0));
-    assertThat(grandparent).isNotEqualTo(change2.getCommit());
-    assertCurrentRevision(change2.getChangeId(), 2, grandparent);
-
-    RevCommit greatgrandparent = parse(grandparent.getParent(0));
-    assertThat(greatgrandparent).isEqualTo(change1.getCommit());
-    assertCurrentRevision(change1.getChangeId(), 1, greatgrandparent);
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit,
-        headAfterFirstSubmit, headAfterSecondSubmit);
-    assertChangeMergedEvents(change1.getChangeId(), headAfterFirstSubmit.name(),
-        change2.getChangeId(), headAfterSecondSubmit.name(),
-        change3.getChangeId(), headAfterSecondSubmit.name(),
-        change4.getChangeId(), headAfterSecondSubmit.name());
-  }
-
-  @Test
-  public void submitWithRebaseMergeCommit() throws Exception {
-    /*
-        *  (HEAD, origin/master, origin/HEAD) Merge changes X,Y
-        |\
-        | *   Merge branch 'master' into origin/master
-        | |\
-        | | * SHA Added a
-        | |/
-        * | Before
-        |/
-        * Initial empty repository
-     */
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change1 = createChange("Added a", "a.txt", "");
-
-    PushOneCommit change2Push = pushFactory.create(db, admin.getIdent(), testRepo,
-        "Merge to master", "m.txt", "");
-    change2Push.setParents(ImmutableList.of(initialHead, change1.getCommit()));
-    PushOneCommit.Result change2 = change2Push.to("refs/for/master");
-
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change3 = createChange("Before", "b.txt", "");
-
-    approve(change3.getChangeId());
-    submit(change3.getChangeId());
-
-    approve(change1.getChangeId());
-    approve(change2.getChangeId());
-    submit(change2.getChangeId());
-
-    RevCommit newHead = getRemoteHead();
-    assertThat(newHead.getParentCount()).isEqualTo(2);
-
-    RevCommit headParent1 = parse(newHead.getParent(0).getId());
-    RevCommit headParent2 = parse(newHead.getParent(1).getId());
-
-    assertThat(headParent1.getId()).isEqualTo(change3.getCommit().getId());
-    assertThat(headParent1.getParentCount()).isEqualTo(1);
-    assertThat(headParent1.getParent(0)).isEqualTo(initialHead);
-
-    assertThat(headParent2.getId()).isEqualTo(change2.getCommit().getId());
-    assertThat(headParent2.getParentCount()).isEqualTo(2);
-
-    RevCommit headGrandparent1 = parse(headParent2.getParent(0).getId());
-    RevCommit headGrandparent2 = parse(headParent2.getParent(1).getId());
-
-    assertThat(headGrandparent1.getId()).isEqualTo(initialHead.getId());
-    assertThat(headGrandparent2.getId()).isEqualTo(change1.getCommit().getId());
-  }
-
-  @Test
-  @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
   public void submitWithContentMerge() throws Exception {
     RevCommit initialHead = getRemoteHead();
     PushOneCommit.Result change =
@@ -235,160 +82,4 @@
         change2.getChangeId(), headAfterSecondSubmit.name(),
         change3.getChangeId(), headAfterThirdSubmit.name());
   }
-
-  @Test
-  @TestProjectInput(useContentMerge = InheritableBoolean.TRUE)
-  public void submitWithContentMerge_Conflict() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change =
-        createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 =
-        createChange("Change 2", "a.txt", "other content");
-    submitWithConflict(change2.getChangeId(),
-        "Cannot rebase " + change2.getCommit().name()
-        + ": The change could not be rebased due to a conflict during merge.");
-    RevCommit head = getRemoteHead();
-    assertThat(head).isEqualTo(headAfterFirstSubmit);
-    assertCurrentRevision(change2.getChangeId(), 1, change2.getCommit());
-    assertNoSubmitter(change2.getChangeId(), 1);
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
-    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name());
-  }
-
-  @Test
-  public void repairChangeStateAfterFailure() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-    PushOneCommit.Result change =
-        createChange("Change 1", "a.txt", "content");
-    submit(change.getChangeId());
-
-    RevCommit headAfterFirstSubmit = getRemoteHead();
-    testRepo.reset(initialHead);
-    PushOneCommit.Result change2 =
-        createChange("Change 2", "b.txt", "other content");
-    Change.Id id2 = change2.getChange().getId();
-    SubmitInput failAfterRefUpdates =
-        new TestSubmitInput(new SubmitInput(), true);
-    submit(change2.getChangeId(), failAfterRefUpdates,
-        ResourceConflictException.class, "Failing after ref updates");
-    RevCommit headAfterFailedSubmit = getRemoteHead();
-
-    // Bad: ref advanced but change wasn't updated.
-    PatchSet.Id psId1 = new PatchSet.Id(id2, 1);
-    PatchSet.Id psId2 = new PatchSet.Id(id2, 2);
-    ChangeInfo info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.NEW);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(1);
-    assertThat(getPatchSet(psId2)).isNull();
-
-    ObjectId rev2;
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      ObjectId rev1 = repo.exactRef(psId1.toRefName()).getObjectId();
-      assertThat(rev1).isNotNull();
-
-      rev2 = repo.exactRef(psId2.toRefName()).getObjectId();
-      assertThat(rev2).isNotNull();
-      assertThat(rev2).isNotEqualTo(rev1);
-      assertThat(rw.parseCommit(rev2).getParent(0)).isEqualTo(headAfterFirstSubmit);
-
-      assertThat(repo.exactRef("refs/heads/master").getObjectId())
-          .isEqualTo(rev2);
-    }
-
-    submit(change2.getChangeId());
-    RevCommit headAfterSecondSubmit = getRemoteHead();
-    assertThat(headAfterSecondSubmit).isEqualTo(headAfterFailedSubmit);
-
-    // Change status and patch set entities were updated, and branch tip stayed
-    // the same.
-    info = gApi.changes().id(id2.get()).get();
-    assertThat(info.status).isEqualTo(ChangeStatus.MERGED);
-    assertThat(info.revisions.get(info.currentRevision)._number).isEqualTo(2);
-    PatchSet ps2 = getPatchSet(psId2);
-    assertThat(ps2).isNotNull();
-    assertThat(ps2.getRevision().get()).isEqualTo(rev2.name());
-    assertThat(Iterables.getLast(info.messages).message)
-        .isEqualTo("Change has been successfully rebased as "
-            + rev2.name() + " by Administrator");
-
-    try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef("refs/heads/master").getObjectId())
-          .isEqualTo(rev2);
-    }
-
-    assertRefUpdatedEvents(initialHead, headAfterFirstSubmit);
-    assertChangeMergedEvents(change.getChangeId(), headAfterFirstSubmit.name(),
-        change2.getChangeId(), headAfterSecondSubmit.name());
-  }
-
-  private RevCommit parse(ObjectId id) throws Exception {
-    try (Repository repo = repoManager.openRepository(project);
-        RevWalk rw = new RevWalk(repo)) {
-      RevCommit c = rw.parseCommit(id);
-      rw.parseBody(c);
-      return c;
-    }
-  }
-
-  @Test
-  public void submitAfterReorderOfCommits() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-
-    // Create two commits and push.
-    RevCommit c1 = commitBuilder()
-        .add("a.txt", "1")
-        .message("subject: 1")
-        .create();
-    RevCommit c2 = commitBuilder()
-        .add("b.txt", "2")
-        .message("subject: 2")
-        .create();
-    pushHead(testRepo, "refs/for/master", false);
-
-    String id1 = getChangeId(testRepo, c1).get();
-    String id2 = getChangeId(testRepo, c2).get();
-
-    // Swap the order of commits and push again.
-    testRepo.reset("HEAD~2");
-    testRepo.cherryPick(c2);
-    testRepo.cherryPick(c1);
-    pushHead(testRepo, "refs/for/master", false);
-
-    approve(id1);
-    approve(id2);
-    submit(id1);
-    RevCommit headAfterSubmit = getRemoteHead();
-
-    assertRefUpdatedEvents(initialHead, headAfterSubmit);
-    assertChangeMergedEvents(id2, headAfterSubmit.name(),
-        id1, headAfterSubmit.name());
-  }
-
-  @Test
-  public void submitChangesAfterBranchOnSecond() throws Exception {
-    RevCommit initialHead = getRemoteHead();
-
-    PushOneCommit.Result change = createChange();
-    approve(change.getChangeId());
-
-    PushOneCommit.Result change2 = createChange();
-    approve(change2.getChangeId());
-    Project.NameKey project = change2.getChange().change().getProject();
-    Branch.NameKey branch = new Branch.NameKey(project, "branch");
-    createBranchWithRevision(branch, change2.getCommit().getName());
-    gApi.changes().id(change2.getChangeId()).current().submit();
-    assertMerged(change2.getChangeId());
-    assertMerged(change.getChangeId());
-
-    RevCommit newHead = getRemoteHead();
-    assertRefUpdatedEvents(initialHead, newHead);
-    assertChangeMergedEvents(change.getChangeId(), newHead.name(),
-        change2.getChangeId(), newHead.name());
-  }
 }
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
index e2e7585..ed96a67 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticQueryChangesTest.java
@@ -87,6 +87,7 @@
         .put("path.data", elasticDirPath.resolve("data").toAbsolutePath())
         .put("path.work", elasticDirPath.resolve("work").toAbsolutePath())
         .put("path.logs", elasticDirPath.resolve("logs").toAbsolutePath())
+        .put("transport.tcp.connect_timeout", "60s")
         .build();
 
     // Start the node
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
index fcfeb01..b52e89a 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
@@ -18,6 +18,7 @@
   FAST_FORWARD_ONLY,
   MERGE_IF_NECESSARY,
   REBASE_IF_NECESSARY,
+  REBASE_ALWAYS,
   MERGE_ALWAYS,
   CHERRY_PICK
 }
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
index 7c5c6ea..d2ccb88 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/PostGpgKeys.java
@@ -48,7 +48,7 @@
 import com.google.gerrit.server.account.AccountResource;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
-import com.google.gerrit.server.mail.AddKeySender;
+import com.google.gerrit.server.mail.send.AddKeySender;
 import com.google.gerrit.server.query.account.InternalAccountQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/gerrit-gwtui-common/BUILD b/gerrit-gwtui-common/BUILD
index 4bd2dfd..c13dbd2 100644
--- a/gerrit-gwtui-common/BUILD
+++ b/gerrit-gwtui-common/BUILD
@@ -13,7 +13,6 @@
 ]
 DEPS = ['//lib/gwt:user']
 SRC = 'src/main/java/com/google/gerrit/'
-DIFFY = glob(['src/main/resources/com/google/gerrit/client/diffy*.png'])
 
 gwt_module(
   name = 'client',
@@ -38,26 +37,14 @@
   visibility = ['//visibility:public'],
 )
 
-java_import(
+java_library(
   name = 'diffy_logo',
-  jars = [':diffy_image_files_ln'],
-  visibility = ['//visibility:public'],
+  resources = glob(['src/main/resources/com/google/gerrit/client/diffy*.png']),
   data = [
     '//lib:LICENSE-diffy',
     '//lib:LICENSE-CC-BY3.0-unported',
   ],
-)
-
-genrule2(
-  name = 'diffy_image_files_ln',
-  srcs = [':diffy_image_files'],
-  cmd = 'ln -s $$ROOT/$(location :diffy_image_files) $@',
-  out = 'diffy_images.jar',
-)
-
-java_library(
-  name = 'diffy_image_files',
-  resources = DIFFY,
+  visibility = ['//visibility:public'],
 )
 
 junit_tests(
diff --git a/gerrit-gwtui/gwt.bzl b/gerrit-gwtui/gwt.bzl
index 9df4ff8..3e83789 100644
--- a/gerrit-gwtui/gwt.bzl
+++ b/gerrit-gwtui/gwt.bzl
@@ -124,6 +124,7 @@
       default=Label("//tools/defaults:jdk")),
     "_zip": attr.label(
       default=Label("@bazel_tools//tools/zip:zipper"),
+      cfg = "host",
       executable=True,
       single_file=True),
   },
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 984c5a3..322af1b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -77,6 +77,7 @@
   String projectSubmitType_MERGE_ALWAYS();
   String projectSubmitType_MERGE_IF_NECESSARY();
   String projectSubmitType_REBASE_IF_NECESSARY();
+  String projectSubmitType_REBASE_ALWAYS();
   String projectSubmitType_CHERRY_PICK();
 
   String headingProjectState();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 5e7b873..1ae3a16 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -54,6 +54,7 @@
 headingProjectSubmitType = Submit Type
 projectSubmitType_FAST_FORWARD_ONLY = Fast Forward Only
 projectSubmitType_MERGE_IF_NECESSARY = Merge if Necessary
+projectSubmitType_REBASE_ALWAYS = Rebase Always
 projectSubmitType_REBASE_IF_NECESSARY = Rebase if Necessary
 projectSubmitType_MERGE_ALWAYS = Always Merge
 projectSubmitType_CHERRY_PICK = Cherry Pick
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
index 81286ea..3a46203 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/Util.java
@@ -43,6 +43,8 @@
         return C.projectSubmitType_MERGE_IF_NECESSARY();
       case REBASE_IF_NECESSARY:
         return C.projectSubmitType_REBASE_IF_NECESSARY();
+      case REBASE_ALWAYS:
+        return C.projectSubmitType_REBASE_ALWAYS();
       case MERGE_ALWAYS:
         return C.projectSubmitType_MERGE_ALWAYS();
       case CHERRY_PICK:
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index ba280c3..7d62dd7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -68,7 +68,7 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
-import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.mail.send.SmtpEmailSender;
 import com.google.gerrit.server.mime.MimeUtil2Module;
 import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
index ed5f7d2..afac097 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitSendEmail.java
@@ -21,7 +21,7 @@
 import com.google.gerrit.pgm.init.api.InitStep;
 import com.google.gerrit.pgm.init.api.Section;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.mail.SmtpEmailSender.Encryption;
+import com.google.gerrit.server.mail.send.SmtpEmailSender.Encryption;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 3cf0549c..eec700a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -29,7 +29,7 @@
 import com.google.gerrit.pgm.init.api.Section;
 import com.google.gerrit.pgm.init.api.Section.Factory;
 import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.mail.OutgoingEmail;
+import com.google.gerrit.server.mail.send.OutgoingEmail;
 import com.google.inject.Binding;
 import com.google.inject.Inject;
 import com.google.inject.Injector;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index f076e54..689b606 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -58,7 +58,7 @@
 import com.google.gerrit.server.git.SearchingChangeCacheImpl;
 import com.google.gerrit.server.git.TagCache;
 import com.google.gerrit.server.group.GroupModule;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.NoteDbModule;
 import com.google.gerrit.server.patch.DiffExecutorModule;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index cd688a9..4b2d677 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -68,6 +68,7 @@
     '//lib/ow2:ow2-asm-commons',
     '//lib/ow2:ow2-asm-util',
     '//lib/prolog:compiler',
+    '//lib/prolog:runtime',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
index a0c6118..8b4453f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AbstractRealm.java
@@ -19,7 +19,7 @@
 import com.google.gerrit.extensions.client.AccountFieldName;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.mail.EmailSender;
+import com.google.gerrit.server.mail.send.EmailSender;
 import com.google.inject.Inject;
 
 import java.util.Collection;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
index 63d2fc6..9ac69ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountDirectory.java
@@ -52,10 +52,6 @@
 
   @SuppressWarnings("serial")
   public static class DirectoryException extends Exception {
-    public DirectoryException(String message) {
-      super(message);
-    }
-
     public DirectoryException(String message, Throwable why) {
       super(message, why);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
index 216672c..8cc392a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AddSshKey.java
@@ -29,7 +29,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AddSshKey.Input;
-import com.google.gerrit.server.mail.AddKeySender;
+import com.google.gerrit.server.mail.send.AddKeySender;
 import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index 8d121c2..905d2f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -38,7 +38,7 @@
 import com.google.gerrit.server.api.accounts.AccountExternalIdCreator;
 import com.google.gerrit.server.group.GroupsCollection;
 import com.google.gerrit.server.index.account.AccountIndexer;
-import com.google.gerrit.server.mail.OutgoingEmailValidator;
+import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
 import com.google.gerrit.server.ssh.SshKeyCache;
 import com.google.gwtorm.server.OrmDuplicateKeyException;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
index c4ab7a5..ecee4b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateEmail.java
@@ -30,8 +30,8 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.GetEmails.EmailInfo;
 import com.google.gerrit.server.config.AuthConfig;
-import com.google.gerrit.server.mail.OutgoingEmailValidator;
-import com.google.gerrit.server.mail.RegisterNewEmailSender;
+import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
+import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
index 69a1460..6b92289 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/oauth/OAuthRealm.java
@@ -66,9 +66,8 @@
    *
    * {@link AccountManager} calls this method without password
    * if authenticity of the user has already been established.
-   * In that case the {@link AuthRequest} is supposed to contain
-   * a resolved email address and we can skip the authentication
-   * request to the {@code OAuthLoginService}.
+   * In that case we can skip the authentication request to the
+   * {@code OAuthLoginService}.
    *
    * @param who the authentication request.
    *
@@ -82,8 +81,7 @@
    */
   @Override
   public AuthRequest authenticate(AuthRequest who) throws AccountException {
-    if (Strings.isNullOrEmpty(who.getPassword()) &&
-        !Strings.isNullOrEmpty(who.getEmailAddress())) {
+    if (Strings.isNullOrEmpty(who.getPassword())) {
       return who;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index 45f0afe..f9b6143 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -38,8 +38,8 @@
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.mail.AbandonedSender;
-import com.google.gerrit.server.mail.ReplyToChangeSender;
+import com.google.gerrit.server.mail.send.AbandonedSender;
+import com.google.gerrit.server.mail.send.ReplyToChangeSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
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 6f6e964..bc9b04b 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
@@ -47,7 +47,7 @@
 import com.google.gerrit.server.git.SendEmailExecutor;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.send.CreateChangeSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
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 72ffa77..12fc22a 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
@@ -44,7 +44,7 @@
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.BatchUpdateReviewDb;
 import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.mail.DeleteReviewerSender;
+import com.google.gerrit.server.mail.send.DeleteReviewerSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index eda2fcf..0f97c37 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -42,8 +42,8 @@
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.mail.DeleteVoteSender;
-import com.google.gerrit.server.mail.ReplyToChangeSender;
+import com.google.gerrit.server.mail.send.DeleteVoteSender;
+import com.google.gerrit.server.mail.send.ReplyToChangeSender;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.util.LabelVote;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
index 0b9a877..bffb61b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/EmailReviewComments.java
@@ -24,7 +24,7 @@
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.SendEmailExecutor;
-import com.google.gerrit.server.mail.CommentSender;
+import com.google.gerrit.server.mail.send.CommentSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.util.RequestContext;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
index 62d75aa..a6e0935 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityCacheImpl.java
@@ -64,12 +64,15 @@
 
   private static final String CACHE_NAME = "mergeability";
 
-  public static final BiMap<SubmitType, Character> SUBMIT_TYPES = ImmutableBiMap.of(
-        SubmitType.FAST_FORWARD_ONLY, 'F',
-        SubmitType.MERGE_IF_NECESSARY, 'M',
-        SubmitType.REBASE_IF_NECESSARY, 'R',
-        SubmitType.MERGE_ALWAYS, 'A',
-        SubmitType.CHERRY_PICK, 'C');
+  public static final BiMap<SubmitType, Character> SUBMIT_TYPES =
+      new ImmutableBiMap.Builder<SubmitType, Character>()
+          .put(SubmitType.FAST_FORWARD_ONLY, 'F')
+          .put(SubmitType.MERGE_IF_NECESSARY, 'M')
+          .put(SubmitType.REBASE_ALWAYS, 'P')
+          .put(SubmitType.REBASE_IF_NECESSARY, 'R')
+          .put(SubmitType.MERGE_ALWAYS, 'A')
+          .put(SubmitType.CHERRY_PICK, 'C')
+          .build();
 
   static {
     checkState(SUBMIT_TYPES.size() == SubmitType.values().length,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index 9a6455c..ea281f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -40,7 +40,7 @@
 import com.google.gerrit.server.git.BatchUpdate.RepoContext;
 import com.google.gerrit.server.git.validators.CommitValidationException;
 import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index ee771f1..30aa8c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -53,7 +53,7 @@
 import com.google.gerrit.server.git.UpdateException;
 import com.google.gerrit.server.group.GroupsCollection;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.mail.AddReviewerSender;
+import com.google.gerrit.server.mail.send.AddReviewerSender;
 import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchProjectException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 1dcbf85..66e17fe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -44,9 +44,9 @@
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.mail.CreateChangeSender;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.mail.send.CreateChangeSender;
+import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
index d3c300e..d39f4fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/RebaseChangeOp.java
@@ -68,6 +68,7 @@
   private CommitValidators.Policy validate;
   private boolean forceContentMerge;
   private boolean copyApprovals = true;
+  private boolean postMessage = true;
 
   private RevCommit rebasedCommit;
   private PatchSet.Id rebasedPatchSetId;
@@ -117,6 +118,11 @@
     return this;
   }
 
+  public RebaseChangeOp setPostMessage(boolean postMessage) {
+    this.postMessage = postMessage;
+    return this;
+  }
+
   @Override
   public void updateRepo(RepoContext ctx) throws MergeConflictException,
        InvalidChangeOperationException, RestApiException, IOException,
@@ -153,10 +159,11 @@
         .setDraft(originalPatchSet.isDraft())
         .setNotify(NotifyHandling.NONE)
         .setFireRevisionCreated(fireRevisionCreated)
-        .setCopyApprovals(copyApprovals)
-        .setMessage(
-          "Patch Set " + rebasedPatchSetId.get()
+        .setCopyApprovals(copyApprovals);
+    if (postMessage) {
+      patchSetInserter.setMessage("Patch Set " + rebasedPatchSetId.get()
           + ": Patch Set " + originalPatchSet.getId().get() + " was rebased");
+    }
 
     if (base != null) {
       patchSetInserter.setGroups(base.patchSet().getGroups());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index f317ef2..09ab9d50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -35,8 +35,8 @@
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.mail.ReplyToChangeSender;
-import com.google.gerrit.server.mail.RestoredSender;
+import com.google.gerrit.server.mail.send.ReplyToChangeSender;
+import com.google.gerrit.server.mail.send.RestoredSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index c42b257..4bd9825 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -45,7 +45,7 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.UpdateException;
 import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.RevertedSender;
+import com.google.gerrit.server.mail.send.RevertedSender;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectControl;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 51dbff2..7e868f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -128,19 +128,19 @@
 import com.google.gerrit.server.group.GroupInfoCache;
 import com.google.gerrit.server.group.GroupModule;
 import com.google.gerrit.server.index.change.ReindexAfterUpdate;
-import com.google.gerrit.server.mail.AddKeySender;
-import com.google.gerrit.server.mail.AddReviewerSender;
-import com.google.gerrit.server.mail.CreateChangeSender;
-import com.google.gerrit.server.mail.DeleteReviewerSender;
 import com.google.gerrit.server.mail.EmailModule;
-import com.google.gerrit.server.mail.FromAddressGenerator;
-import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
-import com.google.gerrit.server.mail.MailSoyTofuProvider;
-import com.google.gerrit.server.mail.MailTemplates;
-import com.google.gerrit.server.mail.MergedSender;
-import com.google.gerrit.server.mail.RegisterNewEmailSender;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
-import com.google.gerrit.server.mail.VelocityRuntimeProvider;
+import com.google.gerrit.server.mail.send.AddKeySender;
+import com.google.gerrit.server.mail.send.AddReviewerSender;
+import com.google.gerrit.server.mail.send.CreateChangeSender;
+import com.google.gerrit.server.mail.send.DeleteReviewerSender;
+import com.google.gerrit.server.mail.send.FromAddressGenerator;
+import com.google.gerrit.server.mail.send.FromAddressGeneratorProvider;
+import com.google.gerrit.server.mail.send.MailSoyTofuProvider;
+import com.google.gerrit.server.mail.send.MailTemplates;
+import com.google.gerrit.server.mail.send.MergedSender;
+import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
+import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
+import com.google.gerrit.server.mail.send.VelocityRuntimeProvider;
 import com.google.gerrit.server.mime.FileTypeRegistry;
 import com.google.gerrit.server.mime.MimeUtilFileTypeRegistry;
 import com.google.gerrit.server.notedb.NoteDbModule;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
index 66e0704..6817bac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/EmailMerge.java
@@ -22,7 +22,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.gerrit.server.mail.MergedSender;
+import com.google.gerrit.server.mail.send.MergedSender;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
 import com.google.gwtorm.server.OrmException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
index 6da1335..a63baec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergedByPushOp.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.server.extensions.events.ChangeMerged;
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
 import com.google.gerrit.server.git.BatchUpdate.Context;
-import com.google.gerrit.server.mail.MergedSender;
+import com.google.gerrit.server.mail.send.MergedSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.util.RequestScopePropagator;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 337edbe..c7a0e5c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -160,9 +160,9 @@
 
   private static final String PLUGIN = "plugin";
 
-  private static final SubmitType defaultSubmitAction =
+  private static final SubmitType DEFAULT_SUBMIT_ACTION =
       SubmitType.MERGE_IF_NECESSARY;
-  private static final ProjectState defaultStateValue =
+  private static final ProjectState DEFAULT_STATE_VALUE =
       ProjectState.ACTIVE;
 
   private Project.NameKey projectName;
@@ -499,9 +499,10 @@
     p.setRejectImplicitMerges(getEnum(rc, RECEIVE, null,
         KEY_REJECT_IMPLICIT_MERGES, InheritableBoolean.INHERIT));
 
-    p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION, defaultSubmitAction));
+    p.setSubmitType(getEnum(rc, SUBMIT, null, KEY_ACTION,
+        DEFAULT_SUBMIT_ACTION));
     p.setUseContentMerge(getEnum(rc, SUBMIT, null, KEY_MERGE_CONTENT, InheritableBoolean.INHERIT));
-    p.setState(getEnum(rc, PROJECT, null, KEY_STATE, defaultStateValue));
+    p.setState(getEnum(rc, PROJECT, null, KEY_STATE, DEFAULT_STATE_VALUE));
 
     p.setDefaultDashboard(rc.getString(DASHBOARD, null, KEY_DEFAULT));
     p.setLocalDefaultDashboard(rc.getString(DASHBOARD, null, KEY_LOCAL_DEFAULT));
@@ -953,10 +954,10 @@
     set(rc, RECEIVE, null, KEY_REJECT_IMPLICIT_MERGES,
         p.getRejectImplicitMerges(), InheritableBoolean.INHERIT);
 
-    set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), defaultSubmitAction);
+    set(rc, SUBMIT, null, KEY_ACTION, p.getSubmitType(), DEFAULT_SUBMIT_ACTION);
     set(rc, SUBMIT, null, KEY_MERGE_CONTENT, p.getUseContentMerge(), InheritableBoolean.INHERIT);
 
-    set(rc, PROJECT, null, KEY_STATE, p.getState(), defaultStateValue);
+    set(rc, PROJECT, null, KEY_STATE, p.getState(), DEFAULT_STATE_VALUE);
 
     set(rc, DASHBOARD, null, KEY_DEFAULT, p.getDefaultDashboard());
     set(rc, DASHBOARD, null, KEY_LOCAL_DEFAULT, p.getLocalDefaultDashboard());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
index 5df6caf..5871299 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommitsAdvertiseRefsHook.java
@@ -109,13 +109,20 @@
         r, advertiseOpenChanges(allPatchSets));
   }
 
+  private static final ImmutableSet<String> OPEN_CHANGES_FIELDS =
+      ImmutableSet.of(
+          // Required for ChangeIsVisibleToPrdicate.
+          ChangeField.CHANGE.getName(),
+          // Required during advertiseOpenChanges.
+          ChangeField.PATCH_SET.getName());
+
   private Set<ObjectId> advertiseOpenChanges(Set<ObjectId> allPatchSets) {
     // Advertise some recent open changes, in case a commit is based on one.
     int limit = 32;
     try {
       Set<ObjectId> r = Sets.newHashSetWithExpectedSize(limit);
       for (ChangeData cd : queryProvider.get()
-          .setRequestedFields(ImmutableSet.of(ChangeField.PATCH_SET.getName()))
+          .setRequestedFields(OPEN_CHANGES_FIELDS)
           .enforceVisibility(true)
           .setLimit(limit)
           .byProjectOpen(projectName)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index aece8a6..1a5999c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -45,7 +45,7 @@
 import com.google.gerrit.server.git.BatchUpdate.RepoContext;
 import com.google.gerrit.server.git.ReceiveCommits.MagicBranchInput;
 import com.google.gerrit.server.mail.MailUtil.MailRecipients;
-import com.google.gerrit.server.mail.ReplacePatchSetSender;
+import com.google.gerrit.server.mail.send.ReplacePatchSetSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java
similarity index 69%
copy from gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java
copy to gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java
index 72fdaae..26bb4c1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseAlways.java
@@ -12,14 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.git.strategy;
 
-import static java.lang.annotation.RetentionPolicy.RUNTIME;
+public class RebaseAlways extends RebaseSubmitStrategy {
 
-import com.google.inject.BindingAnnotation;
-
-import java.lang.annotation.Retention;
-
-@Retention(RUNTIME)
-@BindingAnnotation
-public @interface MailTemplates {}
+  RebaseAlways(SubmitStrategy.Arguments args) {
+    super(args, true);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index 3270fc3..104074a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -14,207 +14,9 @@
 
 package com.google.gerrit.server.git.strategy;
 
-import com.google.gerrit.extensions.restapi.MergeConflictException;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.RestApiException;
-import com.google.gerrit.reviewdb.client.PatchSet;
-import com.google.gerrit.server.change.RebaseChangeOp;
-import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
-import com.google.gerrit.server.git.BatchUpdate.Context;
-import com.google.gerrit.server.git.BatchUpdate.RepoContext;
-import com.google.gerrit.server.git.CodeReviewCommit;
-import com.google.gerrit.server.git.IntegrationException;
-import com.google.gerrit.server.git.MergeTip;
-import com.google.gerrit.server.git.RebaseSorter;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.project.InvalidChangeOperationException;
-import com.google.gerrit.server.project.NoSuchChangeException;
-import com.google.gwtorm.server.OrmException;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-
-public class RebaseIfNecessary extends SubmitStrategy {
+public class RebaseIfNecessary extends RebaseSubmitStrategy {
 
   RebaseIfNecessary(SubmitStrategy.Arguments args) {
-    super(args);
-  }
-
-  @Override
-  public List<SubmitStrategyOp> buildOps(
-      Collection<CodeReviewCommit> toMerge) throws IntegrationException {
-    List<CodeReviewCommit> sorted = sort(toMerge);
-    List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
-    boolean first = true;
-
-    for (CodeReviewCommit c : sorted) {
-      if (c.getParentCount() > 1) {
-        // Since there is a merge commit, sort and prune again using
-        // MERGE_IF_NECESSARY semantics to avoid creating duplicate
-        // commits.
-        //
-        sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted);
-        break;
-      }
-    }
-
-    while (!sorted.isEmpty()) {
-      CodeReviewCommit n = sorted.remove(0);
-      if (first && args.mergeTip.getInitialTip() == null) {
-        ops.add(new FastForwardOp(args, n));
-      } else if (n.getParentCount() == 0) {
-        ops.add(new RebaseRootOp(n));
-      } else if (n.getParentCount() == 1) {
-        ops.add(new RebaseOneOp(n));
-      } else {
-        ops.add(new RebaseMultipleParentsOp(n));
-      }
-      first = false;
-    }
-    return ops;
-  }
-
-  private class RebaseRootOp extends SubmitStrategyOp {
-    private RebaseRootOp(CodeReviewCommit toMerge) {
-      super(RebaseIfNecessary.this.args, toMerge);
-    }
-
-    @Override
-    public void updateRepoImpl(RepoContext ctx) {
-      // Refuse to merge a root commit into an existing branch, we cannot obtain
-      // a delta for the cherry-pick to apply.
-      toMerge.setStatusCode(CommitMergeStatus.CANNOT_REBASE_ROOT);
-    }
-  }
-
-  private class RebaseOneOp extends SubmitStrategyOp {
-    private RebaseChangeOp rebaseOp;
-    private CodeReviewCommit newCommit;
-
-    private RebaseOneOp(CodeReviewCommit toMerge) {
-      super(RebaseIfNecessary.this.args, toMerge);
-    }
-
-    @Override
-    public void updateRepoImpl(RepoContext ctx)
-        throws IntegrationException, InvalidChangeOperationException,
-        RestApiException, IOException, OrmException {
-      // TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
-      // When hoisting BatchUpdate into MergeOp, we will need to teach
-      // BatchUpdate how to produce CodeReviewRevWalks.
-      if (args.mergeUtil
-          .canFastForward(args.mergeSorter, args.mergeTip.getCurrentTip(),
-              args.rw, toMerge)) {
-        args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
-        toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
-        acceptMergeTip(args.mergeTip);
-        return;
-      }
-
-      // Stale read of patch set is ok; see comments in RebaseChangeOp.
-      PatchSet origPs = args.psUtil.get(
-          ctx.getDb(), toMerge.getControl().getNotes(), toMerge.getPatchsetId());
-      rebaseOp = args.rebaseFactory.create(
-            toMerge.getControl(), origPs, args.mergeTip.getCurrentTip().name())
-          .setFireRevisionCreated(false)
-          // Bypass approval copier since SubmitStrategyOp copy all approvals
-          // later anyway.
-          .setCopyApprovals(false)
-          .setValidatePolicy(CommitValidators.Policy.NONE);
-      try {
-        rebaseOp.updateRepo(ctx);
-      } catch (MergeConflictException | NoSuchChangeException e) {
-        toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
-        throw new IntegrationException(
-            "Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
-      }
-      newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
-      newCommit = amendGitlink(newCommit);
-      newCommit.copyFrom(toMerge);
-      newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
-      newCommit.setPatchsetId(rebaseOp.getPatchSetId());
-      args.mergeTip.moveTipTo(newCommit, newCommit);
-      args.commits.put(args.mergeTip.getCurrentTip());
-      acceptMergeTip(args.mergeTip);
-    }
-
-    @Override
-    public PatchSet updateChangeImpl(ChangeContext ctx)
-        throws NoSuchChangeException, ResourceConflictException,
-        OrmException, IOException  {
-      if (rebaseOp == null) {
-        // Took the fast-forward option, nothing to do.
-        return null;
-      }
-
-      rebaseOp.updateChange(ctx);
-      ctx.getChange().setCurrentPatchSet(
-          args.patchSetInfoFactory.get(
-              args.rw, newCommit, rebaseOp.getPatchSetId()));
-      newCommit.setControl(ctx.getControl());
-      return rebaseOp.getPatchSet();
-    }
-
-    @Override
-    public void postUpdateImpl(Context ctx) throws OrmException {
-      if (rebaseOp != null) {
-        rebaseOp.postUpdate(ctx);
-      }
-    }
-  }
-
-  private class RebaseMultipleParentsOp extends SubmitStrategyOp {
-    private RebaseMultipleParentsOp(CodeReviewCommit toMerge) {
-      super(RebaseIfNecessary.this.args, toMerge);
-    }
-
-    @Override
-    public void updateRepoImpl(RepoContext ctx)
-        throws IntegrationException, IOException {
-      // There are multiple parents, so this is a merge commit. We don't want
-      // to rebase the merge as clients can't easily rebase their history with
-      // that merge present and replaced by an equivalent merge with a different
-      // first parent. So instead behave as though MERGE_IF_NECESSARY was
-      // configured.
-      MergeTip mergeTip = args.mergeTip;
-      if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) &&
-          !args.submoduleOp.hasSubscription(args.destBranch)) {
-        mergeTip.moveTipTo(toMerge, toMerge);
-      } else {
-        CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit(
-            args.serverIdent, args.serverIdent, args.repo, args.rw,
-            args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
-        mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
-      }
-      args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
-          mergeTip.getCurrentTip(), args.alreadyAccepted);
-      acceptMergeTip(mergeTip);
-    }
-  }
-
-  private void acceptMergeTip(MergeTip mergeTip) {
-    args.alreadyAccepted.add(mergeTip.getCurrentTip());
-  }
-
-  private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
-      throws IntegrationException {
-    try {
-      return new RebaseSorter(
-          args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
-    } catch (IOException e) {
-      throw new IntegrationException("Commit sorting failed", e);
-    }
-  }
-
-  static boolean dryRun(SubmitDryRun.Arguments args,
-      CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
-      throws IntegrationException {
-    // Test for merge instead of cherry pick to avoid false negatives
-    // on commit chains.
-    return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
-        && args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip,
-             toMerge);
+    super(args, false);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
new file mode 100644
index 0000000..925d515
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseSubmitStrategy.java
@@ -0,0 +1,288 @@
+// Copyright (C) 2012 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.git.strategy;
+
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
+
+import com.google.common.collect.ImmutableList;
+import com.google.gerrit.extensions.restapi.MergeConflictException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.change.RebaseChangeOp;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
+import com.google.gerrit.server.git.BatchUpdate.RepoContext;
+import com.google.gerrit.server.git.CodeReviewCommit;
+import com.google.gerrit.server.git.IntegrationException;
+import com.google.gerrit.server.git.MergeIdenticalTreeException;
+import com.google.gerrit.server.git.MergeTip;
+import com.google.gerrit.server.git.RebaseSorter;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gwtorm.server.OrmException;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.transport.ReceiveCommand;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * This strategy covers RebaseAlways and RebaseIfNecessary ones.
+ */
+public class RebaseSubmitStrategy extends SubmitStrategy {
+  private final boolean rebaseAlways;
+
+  RebaseSubmitStrategy(SubmitStrategy.Arguments args, boolean rebaseAlways) {
+    super(args);
+    this.rebaseAlways = rebaseAlways;
+  }
+
+  @Override
+  public List<SubmitStrategyOp> buildOps(
+      Collection<CodeReviewCommit> toMerge) throws IntegrationException {
+    List<CodeReviewCommit> sorted = sort(toMerge);
+    List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
+    boolean first = true;
+
+    for (CodeReviewCommit c : sorted) {
+      if (c.getParentCount() > 1) {
+        // Since there is a merge commit, sort and prune again using
+        // MERGE_IF_NECESSARY semantics to avoid creating duplicate
+        // commits.
+        //
+        sorted = args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, sorted);
+        break;
+      }
+    }
+
+    while (!sorted.isEmpty()) {
+      CodeReviewCommit n = sorted.remove(0);
+      if (first && args.mergeTip.getInitialTip() == null) {
+        // TODO(tandrii): Cherry-Pick strategy does this too, but it's wrong
+        // and can be fixed.
+        ops.add(new FastForwardOp(args, n));
+      } else if (n.getParentCount() == 0) {
+        ops.add(new RebaseRootOp(n));
+      } else if (n.getParentCount() == 1) {
+        ops.add(new RebaseOneOp(n));
+      } else {
+        ops.add(new RebaseMultipleParentsOp(n));
+      }
+      first = false;
+    }
+    return ops;
+  }
+
+  private class RebaseRootOp extends SubmitStrategyOp {
+    private RebaseRootOp(CodeReviewCommit toMerge) {
+      super(RebaseSubmitStrategy.this.args, toMerge);
+    }
+
+    @Override
+    public void updateRepoImpl(RepoContext ctx) {
+      // Refuse to merge a root commit into an existing branch, we cannot obtain
+      // a delta for the cherry-pick to apply.
+      toMerge.setStatusCode(CommitMergeStatus.CANNOT_REBASE_ROOT);
+    }
+  }
+
+  private class RebaseOneOp extends SubmitStrategyOp {
+    private RebaseChangeOp rebaseOp;
+    private CodeReviewCommit newCommit;
+    private PatchSet.Id newPatchSetId;
+
+    private RebaseOneOp(CodeReviewCommit toMerge) {
+      super(RebaseSubmitStrategy.this.args, toMerge);
+    }
+
+    @Override
+    public void updateRepoImpl(RepoContext ctx)
+        throws IntegrationException, InvalidChangeOperationException,
+        RestApiException, IOException, OrmException {
+      // TODO(dborowitz): args.rw is needed because it's a CodeReviewRevWalk.
+      // When hoisting BatchUpdate into MergeOp, we will need to teach
+      // BatchUpdate how to produce CodeReviewRevWalks.
+      if (args.mergeUtil
+          .canFastForward(args.mergeSorter, args.mergeTip.getCurrentTip(),
+              args.rw, toMerge)) {
+        if (!rebaseAlways){
+          args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
+          toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
+          acceptMergeTip(args.mergeTip);
+          return;
+        }
+        // RebaseAlways means we modify commit message.
+        args.rw.parseBody(toMerge);
+        newPatchSetId = ChangeUtil.nextPatchSetId(
+            args.repo, toMerge.change().currentPatchSetId());
+        // TODO(tandrii): add extension point to customize this commit message.
+        String cherryPickCmtMsg =
+            args.mergeUtil.createCherryPickCommitMessage(toMerge);
+
+        PersonIdent committer = args.caller.newCommitterIdent(ctx.getWhen(),
+            args.serverIdent.getTimeZone());
+        try {
+          newCommit = args.mergeUtil.createCherryPickFromCommit(args.repo,
+              args.inserter, args.mergeTip.getCurrentTip(), toMerge, committer,
+              cherryPickCmtMsg, args.rw, 0);
+        } catch (MergeConflictException mce) {
+          // Unlike in Cherry-pick case, this should never happen.
+          toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
+          throw new IllegalStateException(
+              "MergeConflictException on message edit must not happen");
+        } catch (MergeIdenticalTreeException mie) {
+          toMerge.setStatusCode(SKIPPED_IDENTICAL_TREE);
+          return;
+        }
+        ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), newCommit,
+            newPatchSetId.toRefName()));
+      } else {
+        // Stale read of patch set is ok; see comments in RebaseChangeOp.
+        PatchSet origPs = args.psUtil.get(ctx.getDb(),
+            toMerge.getControl().getNotes(), toMerge.getPatchsetId());
+        // TODO(tandrii): add extension point to customize commit message while
+        // rebasing.
+        rebaseOp = args.rebaseFactory.create(
+              toMerge.getControl(), origPs, args.mergeTip.getCurrentTip().name())
+            .setFireRevisionCreated(false)
+            // Bypass approval copier since SubmitStrategyOp copy all approvals
+            // later anyway.
+            .setCopyApprovals(false)
+            .setValidatePolicy(CommitValidators.Policy.NONE)
+            // Do not post message after inserting new patchset because there
+            // will be one about change being merged already.
+            .setPostMessage(false);
+        try {
+          rebaseOp.updateRepo(ctx);
+        } catch (MergeConflictException | NoSuchChangeException e) {
+          toMerge.setStatusCode(CommitMergeStatus.REBASE_MERGE_CONFLICT);
+          throw new IntegrationException(
+              "Cannot rebase " + toMerge.name() + ": " + e.getMessage(), e);
+        }
+        newCommit = args.rw.parseCommit(rebaseOp.getRebasedCommit());
+        newPatchSetId = rebaseOp.getPatchSetId();
+      }
+      newCommit = amendGitlink(newCommit);
+      newCommit.copyFrom(toMerge);
+      newCommit.setPatchsetId(newPatchSetId);
+      newCommit.setStatusCode(CommitMergeStatus.CLEAN_REBASE);
+      args.mergeTip.moveTipTo(newCommit, newCommit);
+      args.commits.put(args.mergeTip.getCurrentTip());
+      acceptMergeTip(args.mergeTip);
+    }
+
+    @Override
+    public PatchSet updateChangeImpl(ChangeContext ctx)
+        throws NoSuchChangeException, ResourceConflictException,
+        OrmException, IOException  {
+      if (newCommit == null) {
+        checkState(!rebaseAlways, "RebaseAlways must never fast forward");
+        // Took the fast-forward option, nothing to do.
+        return null;
+      }
+      PatchSet newPs;
+      if (rebaseOp != null) {
+        rebaseOp.updateChange(ctx);
+        newPs = rebaseOp.getPatchSet();
+      } else {
+        // CherryPick
+        PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
+        newPs = args.psUtil.insert(ctx.getDb(), ctx.getRevWalk(),
+            ctx.getUpdate(newPatchSetId), newPatchSetId, newCommit, false,
+            prevPs != null ? prevPs.getGroups() : ImmutableList.<String> of(),
+            null);
+      }
+      ctx.getChange().setCurrentPatchSet(args.patchSetInfoFactory
+          .get(ctx.getRevWalk(), newCommit, newPatchSetId));
+      newCommit.setControl(ctx.getControl());
+      return newPs;
+    }
+
+    @Override
+    public void postUpdateImpl(Context ctx) throws OrmException {
+      if (rebaseOp != null) {
+        rebaseOp.postUpdate(ctx);
+      }
+    }
+  }
+
+  private class RebaseMultipleParentsOp extends SubmitStrategyOp {
+    private RebaseMultipleParentsOp(CodeReviewCommit toMerge) {
+      super(RebaseSubmitStrategy.this.args, toMerge);
+    }
+
+    @Override
+    public void updateRepoImpl(RepoContext ctx)
+        throws IntegrationException, IOException {
+      // There are multiple parents, so this is a merge commit. We don't want
+      // to rebase the merge as clients can't easily rebase their history with
+      // that merge present and replaced by an equivalent merge with a different
+      // first parent. So instead behave as though MERGE_IF_NECESSARY was
+      // configured.
+      // TODO(tandrii): this is not in spirit of RebaseAlways strategy because
+      // the commit messages can not be modified in the process. It's also
+      // possible to implement rebasing of merge commits. E.g., the Cherry Pick
+      // REST endpoint already supports cherry-picking of merge commits.
+      // For now, users of RebaseAlways strategy for whom changed commit footers
+      // are important would be well advised to prohibit uploading patches with
+      // merge commits.
+      MergeTip mergeTip = args.mergeTip;
+      if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) &&
+          !args.submoduleOp.hasSubscription(args.destBranch)) {
+        mergeTip.moveTipTo(toMerge, toMerge);
+      } else {
+        CodeReviewCommit newTip = args.mergeUtil.mergeOneCommit(
+            args.serverIdent, args.serverIdent, args.repo, args.rw,
+            args.inserter, args.destBranch, mergeTip.getCurrentTip(), toMerge);
+        mergeTip.moveTipTo(amendGitlink(newTip), toMerge);
+      }
+      args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+          mergeTip.getCurrentTip(), args.alreadyAccepted);
+      acceptMergeTip(mergeTip);
+    }
+  }
+
+  private void acceptMergeTip(MergeTip mergeTip) {
+    args.alreadyAccepted.add(mergeTip.getCurrentTip());
+  }
+
+  private List<CodeReviewCommit> sort(Collection<CodeReviewCommit> toSort)
+      throws IntegrationException {
+    try {
+      return new RebaseSorter(
+          args.rw, args.alreadyAccepted, args.canMergeFlag).sort(toSort);
+    } catch (IOException e) {
+      throw new IntegrationException("Commit sorting failed", e);
+    }
+  }
+
+  static boolean dryRun(SubmitDryRun.Arguments args,
+      CodeReviewCommit mergeTip, CodeReviewCommit toMerge)
+      throws IntegrationException {
+    // Test for merge instead of cherry pick to avoid false negatives
+    // on commit chains.
+    return !args.mergeUtil.hasMissingDependencies(args.mergeSorter, toMerge)
+        && args.mergeUtil.canMerge(args.mergeSorter, args.repo, mergeTip,
+             toMerge);
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
index e60b947..a7dc367 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitDryRun.java
@@ -122,6 +122,8 @@
         return MergeIfNecessary.dryRun(args, tipCommit, toMergeCommit);
       case REBASE_IF_NECESSARY:
         return RebaseIfNecessary.dryRun(args, tipCommit, toMergeCommit);
+      case REBASE_ALWAYS:
+        return RebaseAlways.dryRun(args, tipCommit, toMergeCommit);
       default:
         String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index 146eda1..f8ca32a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -71,6 +71,8 @@
         return new MergeIfNecessary(args);
       case REBASE_IF_NECESSARY:
         return new RebaseIfNecessary(args);
+      case REBASE_ALWAYS:
+        return new RebaseAlways(args);
       default:
         String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
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 7dacc6f..fdce19b 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
@@ -432,6 +432,7 @@
         case CHERRY_PICK:
           return message(ctx, commit, CommitMergeStatus.CLEAN_PICK);
         case REBASE_IF_NECESSARY:
+        case REBASE_ALWAYS:
           return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
         default:
           throw new IllegalStateException("unexpected submit type "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
index 863cb82..d4655f5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/Address.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.server.mail;
 
+import com.google.gerrit.server.mail.send.EmailHeader;
+
 public class Address {
   public static Address parse(final String in) {
     final int lt = in.indexOf('<');
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
index 7ceb0ae..9bf97dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
@@ -15,6 +15,12 @@
 package com.google.gerrit.server.mail;
 
 import com.google.gerrit.extensions.config.FactoryModule;
+import com.google.gerrit.server.mail.send.AbandonedSender;
+import com.google.gerrit.server.mail.send.CommentSender;
+import com.google.gerrit.server.mail.send.DeleteReviewerSender;
+import com.google.gerrit.server.mail.send.DeleteVoteSender;
+import com.google.gerrit.server.mail.send.RestoredSender;
+import com.google.gerrit.server.mail.send.RevertedSender;
 
 public class EmailModule extends FactoryModule {
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
index 41e1e2c..488711a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailTokenVerifier.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AuthRequest;
+import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
 
 /** Verifies the token sent by {@link RegisterNewEmailSender}. */
 public interface EmailTokenVerifier {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
index f12859f..3dd98ea 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/SignedTokenEmailTokenVerifier.java
@@ -18,6 +18,7 @@
 
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.mail.send.RegisterNewEmailSender;
 import com.google.gwtjsonrpc.server.SignedToken;
 import com.google.gwtjsonrpc.server.ValidToken;
 import com.google.gwtjsonrpc.server.XsrfException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AbandonedSender.java
similarity index 94%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AbandonedSender.java
index f1e609e..254d3f1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AbandonedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AbandonedSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
@@ -35,7 +35,7 @@
       @Assisted Project.NameKey project,
       @Assisted Change.Id id)
       throws OrmException {
-    super(ea, "abandon", newChangeData(ea, project, id));
+    super(ea, "abandon", ChangeEmail.newChangeData(ea, project, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddKeySender.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddKeySender.java
index 40492bd..bbcd6a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddKeySender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddKeySender.java
@@ -12,12 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.base.Joiner;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountSshKey;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
index 5f61353..79d02e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/AddReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/AddReviewerSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.errors.EmailException;
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/send/ChangeEmail.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/ChangeEmail.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index df0f018..4aa0fee 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/send/ChangeEmail.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
 
@@ -31,7 +31,8 @@
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.mail.ProjectWatch.Watchers;
+import com.google.gerrit.server.mail.RecipientType;
+import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
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/send/CommentSender.java
similarity index 99%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/CommentSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CommentSender.java
index d534d62..35eea03 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/send/CommentSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Ordering;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
similarity index 94%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
index 2110e37..98a03e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/CreateChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/CreateChangeSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.errors.EmailException;
@@ -20,7 +20,8 @@
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.mail.ProjectWatch.Watchers;
+import com.google.gerrit.server.mail.RecipientType;
+import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
index ec4a728..28fb714 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteReviewerSender.java
@@ -12,13 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
index 6257deb..f8d1745 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteVoteSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/DeleteVoteSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
index 71c294b..818023f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailArguments.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailArguments.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.index.account.AccountIndexCollection;
+import com.google.gerrit.server.mail.EmailSettings;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailHeader.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailHeader.java
index 6a964a3..43d365c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailHeader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailHeader.java
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.MoreObjects;
+import com.google.gerrit.server.mail.Address;
 
 import java.io.IOException;
 import java.io.Writer;
@@ -75,7 +76,7 @@
     }
   }
 
-  static boolean needsQuotedPrintable(java.lang.String value) {
+  public static boolean needsQuotedPrintable(java.lang.String value) {
     for (int i = 0; i < value.length(); i++) {
       if (value.charAt(i) < ' ' || '~' < value.charAt(i)) {
         return true;
@@ -104,7 +105,7 @@
     }
   }
 
-  static java.lang.String quotedPrintable(java.lang.String value) {
+  public static java.lang.String quotedPrintable(java.lang.String value) {
     final StringBuilder r = new StringBuilder();
 
     r.append("=?UTF-8?Q?");
@@ -191,7 +192,7 @@
 
     void remove(java.lang.String email) {
       for (Iterator<Address> i = list.iterator(); i.hasNext();) {
-        if (i.next().email.equals(email)) {
+        if (i.next().getEmail().equals(email)) {
           i.remove();
         }
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailSender.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailSender.java
index d36225a..0bfb6f2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/EmailSender.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.server.mail.Address;
 
 import java.util.Collection;
 import java.util.Map;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
similarity index 90%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
index 9bcabc3..2489063 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGenerator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGenerator.java
@@ -12,9 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.mail.Address;
 
 /** Constructs an address to send email from. */
 public interface FromAddressGenerator {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
similarity index 94%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
index 0bc65bd..3326b38 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/FromAddressGeneratorProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProvider.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -22,6 +22,8 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.MailUtil;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -53,7 +55,7 @@
       ParameterizedString name = new ParameterizedString("${user} (Code Review)");
       generator =
           new PatternGen(srvAddr, accountCache, anonymousCowardName, name,
-              srvAddr.email);
+              srvAddr.getEmail());
     } else if ("USER".equalsIgnoreCase(from)) {
       String[] domains = cfg.getStringList("sendemail", null, "allowedDomain");
       Pattern domainPattern = MailUtil.glob(domains);
@@ -65,13 +67,14 @@
       generator = new ServerGen(srvAddr);
     } else {
       final Address a = Address.parse(from);
-      final ParameterizedString name = a.name != null ? new ParameterizedString(a.name) : null;
+      final ParameterizedString name =
+          a.getName() != null ? new ParameterizedString(a.getName()) : null;
       if (name == null || name.getParameterNames().isEmpty()) {
         generator = new ServerGen(a);
       } else {
         generator =
             new PatternGen(srvAddr, accountCache, anonymousCowardName, name,
-                a.email);
+                a.getEmail());
       }
     }
   }
@@ -135,12 +138,12 @@
         }
         senderName = nameRewriteTmpl.replace("user", fullName).toString();
       } else {
-        senderName = serverAddress.name;
+        senderName = serverAddress.getName();
       }
 
       String senderEmail;
       ParameterizedString senderEmailPattern =
-          new ParameterizedString(serverAddress.email);
+          new ParameterizedString(serverAddress.getEmail());
       if (senderEmailPattern.getParameterNames().isEmpty()) {
         senderEmail = senderEmailPattern.getRawPattern();
       } else {
@@ -215,7 +218,7 @@
         senderName = namePattern.replace("user", fullName).toString();
 
       } else {
-        senderName = serverAddress.name;
+        senderName = serverAddress.getName();
       }
 
       String senderEmail;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
index 2381c16..e019798 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailSoyTofuProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailSoyTofuProvider.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.io.CharStreams;
 import com.google.common.io.Resources;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailTemplates.java
similarity index 94%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailTemplates.java
index 72fdaae..b92567f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MailTemplates.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MailTemplates.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MergedSender.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MergedSender.java
index 17a0854..a5c8940 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/MergedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/MergedSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.Table;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NewChangeSender.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NewChangeSender.java
index 05a709d..49a909a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NewChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NewChangeSender.java
@@ -12,10 +12,11 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
similarity index 94%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
index 85dd800..baf0d93 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/NotificationEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/NotificationEmail.java
@@ -12,14 +12,16 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.collect.Iterables;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.server.mail.ProjectWatch.Watchers;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.RecipientType;
+import com.google.gerrit.server.mail.send.ProjectWatch.Watchers;
 import com.google.gwtorm.server.OrmException;
 
 import org.slf4j.Logger;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
index 1565567..1051b9d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmail.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.CC_ON_OWN_COMMENTS;
 import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
@@ -24,7 +24,9 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.mail.EmailHeader.AddressList;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.RecipientType;
+import com.google.gerrit.server.mail.send.EmailHeader.AddressList;
 import com.google.gerrit.server.validators.OutgoingEmailValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
@@ -201,8 +203,8 @@
       // Reply-To header with the current user's email address.
       //
       final Address a = toAddress(fromId);
-      if (a != null && !smtpFromAddress.email.equals(a.email)) {
-        setHeader("Reply-To", a.email);
+      if (a != null && !smtpFromAddress.getEmail().equals(a.getEmail())) {
+        setHeader("Reply-To", a.getEmail());
       }
     }
 
@@ -421,11 +423,11 @@
 
   /** Schedule delivery of this message to the given account. */
   protected void add(final RecipientType rt, final Address addr) {
-    if (addr != null && addr.email != null && addr.email.length() > 0) {
-      if (!OutgoingEmailValidator.isValid(addr.email)) {
-        log.warn("Not emailing " + addr.email + " (invalid email address)");
-      } else if (!args.emailSender.canEmail(addr.email)) {
-        log.warn("Not emailing " + addr.email + " (prohibited by allowrcpt)");
+    if (addr != null && addr.getEmail() != null && addr.getEmail().length() > 0) {
+      if (!OutgoingEmailValidator.isValid(addr.getEmail())) {
+        log.warn("Not emailing " + addr.getEmail() + " (invalid email address)");
+      } else if (!args.emailSender.canEmail(addr.getEmail())) {
+        log.warn("Not emailing " + addr.getEmail() + " (prohibited by allowrcpt)");
       } else if (smtpRcptTo.add(addr)) {
         switch (rt) {
           case TO:
@@ -561,7 +563,7 @@
   protected void removeUser(Account user) {
     String fromEmail = user.getPreferredEmail();
     for (Iterator<Address> j = smtpRcptTo.iterator(); j.hasNext();) {
-      if (j.next().email.equals(fromEmail)) {
+      if (j.next().getEmail().equals(fromEmail)) {
         j.remove();
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmailValidator.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmailValidator.java
similarity index 95%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmailValidator.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmailValidator.java
index 5ab5f4e..1e92c83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/OutgoingEmailValidator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/OutgoingEmailValidator.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static org.apache.commons.validator.routines.DomainValidator.ArrayType.GENERIC_PLUS;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
index c1ead9f..f49a216 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ProjectWatch.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.common.base.Strings;
 import com.google.gerrit.common.data.GroupDescription;
@@ -30,6 +30,7 @@
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
 import com.google.gerrit.server.git.NotifyConfig;
+import com.google.gerrit.server.mail.Address;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
similarity index 92%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
index 405d9f9..03e9b7e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RegisterNewEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RegisterNewEmailSender.java
@@ -12,12 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.mail.Address;
+import com.google.gerrit.server.mail.EmailTokenVerifier;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
similarity index 96%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
index 3f1e356..8fa8c57 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplacePatchSetSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplacePatchSetSender.java
@@ -12,13 +12,14 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
index dd922d3..02d3f32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ReplyToChangeSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ReplyToChangeSender.java
@@ -12,11 +12,12 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.mail.RecipientType;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RestoredSender.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RestoredSender.java
index 45a45c7..1b1823e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RestoredSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RestoredSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
@@ -35,7 +35,7 @@
       @Assisted Project.NameKey project,
       @Assisted Change.Id id)
       throws OrmException {
-    super(ea, "restore", newChangeData(ea, project, id));
+    super(ea, "restore", ChangeEmail.newChangeData(ea, project, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RevertedSender.java
similarity index 93%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RevertedSender.java
index 0734a3c..b297ee1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/RevertedSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/RevertedSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
@@ -33,7 +33,7 @@
       @Assisted Project.NameKey project,
       @Assisted Change.Id id)
       throws OrmException {
-    super(ea, "revert", newChangeData(ea, project, id));
+    super(ea, "revert", ChangeEmail.newChangeData(ea, project, id));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
similarity index 97%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
index 3b4fcfc..f27c45f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/SmtpEmailSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/SmtpEmailSender.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
@@ -24,6 +24,7 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.mail.Address;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
@@ -194,9 +195,9 @@
     try {
       final SMTPClient client = open();
       try {
-        if (!client.setSender(from.email)) {
+        if (!client.setSender(from.getEmail())) {
           throw new EmailException("Server " + smtpHost
-              + " rejected from address " + from.email);
+              + " rejected from address " + from.getEmail());
         }
 
         /* Do not prevent the email from being sent to "good" users simply
@@ -207,7 +208,7 @@
          * error(s) logged.
          */
         for (Address addr : rcpt) {
-          if (!client.addRecipient(addr.email)) {
+          if (!client.addRecipient(addr.getEmail())) {
             String error = client.getReplyString();
             rejected.append("Server ").append(smtpHost)
                     .append(" rejected recipient ").append(addr)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java
similarity index 98%
rename from gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java
index 3fdc550..03d4f7a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/VelocityRuntimeProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/VelocityRuntimeProvider.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 9a5b61f..48cd7ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -221,16 +221,13 @@
       return Collections.singletonList(rec);
     }
     if (!opts.allowDraft()) {
-      if (c.getStatus() == Change.Status.DRAFT) {
-        return cannotSubmitDraft();
-      }
       try {
         initPatchSet();
       } catch (OrmException e) {
         return ruleError("Error looking up patch set "
             + control.getChange().currentPatchSetId(), e);
       }
-      if (patchSet.isDraft()) {
+      if (c.getStatus() == Change.Status.DRAFT || patchSet.isDraft()) {
         return cannotSubmitDraft();
       }
     }
@@ -262,7 +259,6 @@
       if (!control.isDraftVisible(cd.db(), cd)) {
         return createRuleError("Patch set " + patchSet.getId() + " not found");
       }
-      initPatchSet();
       if (patchSet.isDraft()) {
         return createRuleError("Cannot submit draft patch sets");
       }
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 f9d4f68..e62bf48 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
@@ -367,6 +367,10 @@
     }
   }
 
+  public Arguments getArgs() {
+    return args;
+  }
+
   public ChangeQueryBuilder asUser(CurrentUser user) {
     return new ChangeQueryBuilder(builderDef, args.asUser(user));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index 7b8f5fe..62ca0e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -14,6 +14,9 @@
 
 package com.google.gerrit.server.query.change;
 
+import static com.google.gerrit.extensions.client.ListChangesOption.DETAILED_LABELS;
+import static com.google.gerrit.extensions.client.ListChangesOption.LABELS;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.client.ListChangesOption;
@@ -23,6 +26,7 @@
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.server.change.ChangeJson;
+import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.QueryResult;
 import com.google.gwtorm.server.OrmException;
@@ -123,8 +127,13 @@
 
     int cnt = queries.size();
     List<QueryResult<ChangeData>> results = imp.query(qb.parse(queries));
+    boolean requireLazyLoad =
+        containsAnyOf(options, ImmutableSet.of(DETAILED_LABELS, LABELS))
+            && !qb.getArgs().getSchema()
+                .hasField(ChangeField.STORED_SUBMIT_RECORD_LENIENT);
     List<List<ChangeInfo>> res = json.create(options)
-        .lazyLoad(containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD))
+        .lazyLoad(requireLazyLoad
+            || containsAnyOf(options, ChangeJson.REQUIRE_LAZY_LOAD))
         .formatQueryResults(results);
     for (int n = 0; n < cnt; n++) {
       List<ChangeInfo> info = res.get(n);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
index 2182ac1..667ef0d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/validators/OutgoingEmailValidationListener.java
@@ -17,7 +17,7 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.EmailHeader;
+import com.google.gerrit.server.mail.send.EmailHeader;
 
 import java.util.Map;
 import java.util.Set;
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
index bda900b..30d40c9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/AbandonedHtml.soy
@@ -24,7 +24,8 @@
  */
 {template .AbandonedHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} has abandoned this change.
+    <strong>{$fromName}</strong> abandoned{sp}
+    <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -33,11 +34,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
index 934ea18..a0101c9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooterHtml.soy
@@ -27,9 +27,7 @@
  */
 {template .ChangeFooterHtml autoescape="strict" kind="html"}
   {let $footerStyle kind="css"}
-    color: #555;
-    font-size: 10px;
-    margin: 10px 0 0 0;
+    display: none;
   {/let}
 
   {if $email.changeUrl or $email.settingsUrl}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
index f8f6c5f..84cace2 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentFooterHtml.soy
@@ -21,9 +21,7 @@
  */
 {template .CommentFooterHtml autoescape="strict" kind="html"}
   {let $footerStyle kind="css"}
-    color: #555;
-    font-size: 10px;
-    margin: 0;
+    display: none;
   {/let}
 
   <p style="{$footerStyle}">
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
index 522bcc84..4f70645 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/CommentHtml.soy
@@ -43,7 +43,8 @@
   {/let}
 
   <p>
-    {$fromName} has posted comments on this change.
+    <strong>{$fromName}</strong> posted comments on{sp}
+    <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -52,11 +53,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
@@ -85,12 +81,12 @@
                 {if length($comment.lines) > 0}
                   <a href="{$comment.link}">
                     {if $comment.startLine == 0}
-                      Patch Set #{$group.patchSetId}:{sp}
+                      Patch Set #{$group.patchSetId}:
                     {else}
                       Patch Set #{$group.patchSetId},{sp}
-                      Line {$comment.startLine}:{sp}
+                      Line {$comment.startLine}:
                     {/if}
-                  </a>
+                  </a>{sp}
                 {/if}
                 {if length($comment.lines) == 1}
                   <code style="font-size:12px">{$comment.lines[0]}</code>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
index e6830ec..ead4071 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewerHtml.soy
@@ -24,12 +24,12 @@
  */
 {template .DeleteReviewerHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} has removed{sp}
+    <strong>{$fromName}</strong> removed{sp}
     {foreach $reviewerName in $email.reviewerNames}
       {if not isFirst($reviewerName)},{sp}{/if}
-      {$reviewerName}
+      <strong>{$reviewerName}</strong>
     {/foreach}{sp}
-    from this change.
+    from <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -38,11 +38,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
index 6d88748..4f74da9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteVoteHtml.soy
@@ -24,7 +24,8 @@
  */
 {template .DeleteVoteHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} removed a vote on this change.
+    <strong>{$fromName}</strong> removed a vote from{sp}
+    <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -33,11 +34,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
index 083e57d..6ed8a8d 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/MergedHtml.soy
@@ -23,7 +23,7 @@
  */
 {template .MergedHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} has submitted this change and it was merged.
+    <strong>{$fromName}</strong> merged <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -32,11 +32,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   <pre style="font-size:12px">
     {$email.changeDetail}
   </pre>
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
index 95ee41d..98a7c67 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/NewChangeHtml.soy
@@ -24,40 +24,27 @@
  * @param projectName
  */
 {template .NewChangeHtml autoescape="strict" kind="html"}
-  {if $email.reviewerNames}
-    <p>
-      Hello{sp}
-      {foreach $reviewerName in $email.reviewerNames}
-        {if not isFirst($reviewerName)},{sp}{/if}
-        {$reviewerName}
-      {/foreach},
-    </p>
-
-    <p>
-      I'd like you to do a code review.
-    </p>
-
-    {if $email.changeUrl}
-      <p>
-        Please visit <a href="{$email.changeUrl}">this change</a> to review.
-      </p>
-    {/if}
-  {else}
-    <p>
-      {$fromName} has uploaded a new change for review.
-    </p>
-
-    {if $email.changeUrl}
-      <p>
-        {call .ViewChangeButton data="all" /}
-      </p>
-    {/if}
-  {/if}
-
   <p>
-    Change subject: {$change.subject}
+    {if $email.reviewerNames}
+      <strong>{$fromName}</strong> would like{sp}
+      {foreach $reviewerName in $email.reviewerNames}
+        {if not isFirst($reviewerName)}
+          {if isLast($reviewerName)}{sp}and{else},{/if}{sp}
+        {/if}
+        <strong>{$reviewerName}</strong>
+      {/foreach}{sp}
+      to review <strong>{$change.subject}</strong>.
+    {else}
+      <strong>{$fromName}</strong> uploaded{sp}
+      <strong>{$change.subject}</strong> for review.
+    {/if}
   </p>
-  <hr/>
+
+  {if $email.changeUrl}
+    <p>
+      {call .ViewChangeButton data="all" /}
+    </p>
+  {/if}
 
   <pre style="font-size:12px">
     {$email.changeDetail}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
index df00e2d..92e2eef 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ReplacePatchSetHtml.soy
@@ -24,40 +24,17 @@
  * @param projectName
  */
 {template .ReplacePatchSetHtml autoescape="strict" kind="html"}
-  {if $email.reviewerNames}
-    <p>
-      Hello{sp}
-      {foreach $reviewerName in $email.reviewerNames}
-        {$reviewerName},{sp}
-      {/foreach}
-    </p>
-
-    <p>
-      I'd like you to reexamine a change.
-    </p>
-
-    {if $email.changeUrl}
-      <p>
-        Please visit <a href="{$email.changeUrl}">this change</a> to look at{sp}
-        the new patch set (#{$patchSet.patchSetId}).
-      </p>
-    {/if}
-  {else}
-    <p>
-      {$fromName} has uploaded a new patch set (#{$patchSet.patchSetId}).
-    </p>
-
-    {if $email.changeUrl}
-      <p>
-        {call .ViewChangeButton data="all" /}
-      </p>
-    {/if}
-  {/if}
-
   <p>
-    Change subject: {$change.subject}
+    <strong>{$fromName}</strong> uploaded patch set{sp}
+    <strong>#{$patchSet.patchSetId}</strong> to{sp}
+    <strong>{$change.subject}</strong>.
   </p>
-  <hr/>
+
+  {if $email.changeUrl}
+    <p>
+      {call .ViewChangeButton data="all" /}
+    </p>
+  {/if}
 
   <pre style="font-size:12px">
     {$email.changeDetail}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
index 83b05c4..4f249a7 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RestoredHtml.soy
@@ -24,7 +24,8 @@
  */
 {template .RestoredHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} has restored this change.
+    <strong>{$fromName}</strong> restored{sp}
+    <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -33,11 +34,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
index c0dc4d3..fd6a222 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/RevertedHtml.soy
@@ -24,7 +24,8 @@
  */
 {template .RevertedHtml autoescape="strict" kind="html"}
   <p>
-    {$fromName} has reverted this change.
+    <strong>{$fromName}</strong> reverted the change:{sp}
+    <strong>{$change.subject}</strong>.
   </p>
 
   {if $email.changeUrl}
@@ -33,11 +34,6 @@
     </p>
   {/if}
 
-  <p>
-    Change subject: {$change.subject}
-  </p>
-  <hr/>
-
   {if $coverLetter}
     <pre style="font-size:12px">{$coverLetter}</pre>
   {/if}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy
index defde55..ab66eaa 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ViewChangeButton.soy
@@ -21,5 +21,14 @@
  * @param email
  */
 {template .ViewChangeButton private="true" autoescape="strict" kind="html"}
-  <a href="{$email.changeUrl}">View Change</a>
+  {let $visitButtonStyle kind="css"}
+    background: #4d90fe;
+    border: 1px solid #3079ed;
+    border-radius: 4px;
+    color: #fff;
+    display: inline-block;
+    margin-right: 8px;
+    padding: 6px 12px;
+  {/let}
+  <a style="{$visitButtonStyle}" href="{$email.changeUrl}">View Change</a>
 {/template}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
index bf36738..cfa1f5e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/RepositoryConfigTest.java
@@ -58,6 +58,10 @@
     configureDefaultSubmitType("*", SubmitType.REBASE_IF_NECESSARY);
     assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
         .isEqualTo(SubmitType.REBASE_IF_NECESSARY);
+
+    configureDefaultSubmitType("*", SubmitType.REBASE_ALWAYS);
+    assertThat(repoCfg.getDefaultSubmitType(new NameKey("someProject")))
+        .isEqualTo(SubmitType.REBASE_ALWAYS);
   }
 
   @Test
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java
index 4f2c776..4d7bf08 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/ValidatorTest.java
@@ -17,6 +17,8 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assert_;
 
+import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
+
 import org.junit.Test;
 
 import java.io.BufferedReader;
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
similarity index 82%
rename from gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
rename to gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
index 87dd3d9..01580c3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/mail/FromAddressGeneratorProviderTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/mail/send/FromAddressGeneratorProviderTest.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.server.mail;
+package com.google.gerrit.server.mail.send;
 
 import static com.google.common.truth.Truth.assertThat;
 import static org.easymock.EasyMock.createStrictMock;
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
+import com.google.gerrit.server.mail.Address;
 
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -94,8 +95,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name);
-    assertThat(r.email).isEqualTo(email);
+    assertThat(r.getName()).isEqualTo(name);
+    assertThat(r.getEmail()).isEqualTo(email);
     verify(accountCache);
   }
 
@@ -109,8 +110,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isNull();
-    assertThat(r.email).isEqualTo(email);
+    assertThat(r.getName()).isNull();
+    assertThat(r.getEmail()).isEqualTo(email);
     verify(accountCache);
   }
 
@@ -124,8 +125,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name + " (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(name + " (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -135,8 +136,8 @@
     replay(accountCache);
     final Address r = create().from(null);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(ident.getName());
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(ident.getName());
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -151,8 +152,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name);
-    assertThat(r.email).isEqualTo(email);
+    assertThat(r.getName()).isEqualTo(name);
+    assertThat(r.getEmail()).isEqualTo(email);
     verify(accountCache);
   }
 
@@ -167,8 +168,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name + " (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(name + " (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -184,8 +185,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name);
-    assertThat(r.email).isEqualTo(email);
+    assertThat(r.getName()).isEqualTo(name);
+    assertThat(r.getEmail()).isEqualTo(email);
     verify(accountCache);
   }
 
@@ -201,8 +202,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name + " (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(name + " (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -217,8 +218,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name);
-    assertThat(r.email).isEqualTo(email);
+    assertThat(r.getName()).isEqualTo(name);
+    assertThat(r.getEmail()).isEqualTo(email);
     verify(accountCache);
   }
 
@@ -245,8 +246,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(ident.getName());
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(ident.getName());
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -256,8 +257,8 @@
     replay(accountCache);
     final Address r = create().from(null);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(ident.getName());
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(ident.getName());
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -284,8 +285,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name + " (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(name + " (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -299,8 +300,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo("Anonymous Coward (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo("Anonymous Coward (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -314,8 +315,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(name + " (Code Review)");
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(name + " (Code Review)");
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -325,8 +326,8 @@
     replay(accountCache);
     final Address r = create().from(null);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(ident.getName());
-    assertThat(r.email).isEqualTo(ident.getEmailAddress());
+    assertThat(r.getName()).isEqualTo(ident.getName());
+    assertThat(r.getEmail()).isEqualTo(ident.getEmailAddress());
     verify(accountCache);
   }
 
@@ -341,8 +342,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo("A " + name + " B");
-    assertThat(r.email).isEqualTo("my.server@email.address");
+    assertThat(r.getName()).isEqualTo("A " + name + " B");
+    assertThat(r.getEmail()).isEqualTo("my.server@email.address");
     verify(accountCache);
   }
 
@@ -356,8 +357,8 @@
     replay(accountCache);
     final Address r = create().from(user);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo("A Anonymous Coward B");
-    assertThat(r.email).isEqualTo("my.server@email.address");
+    assertThat(r.getName()).isEqualTo("A Anonymous Coward B");
+    assertThat(r.getEmail()).isEqualTo("my.server@email.address");
     verify(accountCache);
   }
 
@@ -368,8 +369,8 @@
     replay(accountCache);
     final Address r = create().from(null);
     assertThat(r).isNotNull();
-    assertThat(r.name).isEqualTo(ident.getName());
-    assertThat(r.email).isEqualTo("my.server@email.address");
+    assertThat(r.getName()).isEqualTo(ident.getName());
+    assertThat(r.getEmail()).isEqualTo("my.server@email.address");
     verify(accountCache);
   }
 
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
index b65d49d..dcf0a40 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/account/AbstractQueryAccountsTest.java
@@ -475,7 +475,7 @@
   }
 
   protected static Iterable<Integer> ids(AccountInfo... accounts) {
-    return FluentIterable.of(accounts).transform(a -> a._accountId);
+    return ids(Arrays.asList(accounts));
   }
 
   protected static Iterable<Integer> ids(Iterable<AccountInfo> accounts) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
index b8888e2..875d43f 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeEmailSender.java
@@ -22,8 +22,8 @@
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.server.git.WorkQueue;
 import com.google.gerrit.server.mail.Address;
-import com.google.gerrit.server.mail.EmailHeader;
-import com.google.gerrit.server.mail.EmailSender;
+import com.google.gerrit.server.mail.send.EmailHeader;
+import com.google.gerrit.server.mail.send.EmailSender;
 import com.google.inject.AbstractModule;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
diff --git a/gerrit-war/BUILD b/gerrit-war/BUILD
index 9262ad4..3604e88 100644
--- a/gerrit-war/BUILD
+++ b/gerrit-war/BUILD
@@ -63,9 +63,9 @@
   cmd = ' && '.join([
     'cd $$TMP',
     'mkdir -p com/google/gerrit/common',
-    'cat $$ROOT/$(location //:version.txt) >com/google/gerrit/common/Version',
+    'cat $$ROOT/$(location //:version) >com/google/gerrit/common/Version',
     'zip -9Dqr $$ROOT/$@ .',
   ]),
-  tools = ['//:version.txt'],
+  tools = ['//:version'],
   out = 'gen_version.jar',
 )
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
index 9dba629..25aa5bc 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/WebAppInitializer.java
@@ -53,7 +53,7 @@
 import com.google.gerrit.server.index.IndexModule;
 import com.google.gerrit.server.index.IndexModule.IndexType;
 import com.google.gerrit.server.mail.SignedTokenEmailTokenVerifier;
-import com.google.gerrit.server.mail.SmtpEmailSender;
+import com.google.gerrit.server.mail.send.SmtpEmailSender;
 import com.google.gerrit.server.mime.MimeUtil2Module;
 import com.google.gerrit.server.notedb.ConfigNotesMigration;
 import com.google.gerrit.server.patch.DiffExecutorModule;
diff --git a/lib/fonts/BUILD b/lib/fonts/BUILD
index 509ce39..92accea 100644
--- a/lib/fonts/BUILD
+++ b/lib/fonts/BUILD
@@ -2,16 +2,13 @@
 
 # Source Code Pro. Version 2.010 Roman / 1.030 Italics
 # https://github.com/adobe-fonts/source-code-pro/releases/tag/2.010R-ro%2F1.030R-it
-genrule2(
+filegroup(
   name = 'sourcecodepro',
-  cmd = 'zip -rq $@ $(SRCS)',
   srcs = [
     'SourceCodePro-Regular.woff',
     'SourceCodePro-Regular.woff2'
   ],
-  out = 'sourcecodepro.zip',
-# TODO(hanwen): fix this
-#  license = 'OFL1.1',
+  data = [ "//lib:LICENSE-OFL1.1" ],
   visibility = ['//visibility:public'],
 )
 
diff --git a/lib/js/BUILD b/lib/js/BUILD
index 249f5d0..cc3d6e9 100644
--- a/lib/js/BUILD
+++ b/lib/js/BUILD
@@ -25,7 +25,13 @@
 js_component(
   name = 'highlightjs',
   srcs = [ "//lib/highlightjs:highlight.min.js" ],
-  license =  '//lib:LICENSE-highlightjs',
+  license = '//lib:LICENSE-highlightjs',
+)
+
+filegroup(
+  name = "highlightjs_files",
+  srcs = [ "//lib/highlightjs:highlight.min.js" ],
+  data = ['//lib:LICENSE-highlightjs',],
 )
 
 bower_component(
diff --git a/polygerrit-ui/BUILD b/polygerrit-ui/BUILD
index 9191ab7..4cc6899 100644
--- a/polygerrit-ui/BUILD
+++ b/polygerrit-ui/BUILD
@@ -10,6 +10,7 @@
   deps = [
     '//lib/js:es6-promise',
     '//lib/js:fetch',
+    # TODO(hanwen): this is inserted separately in the UI zip. Do we need this here?
     '//lib/js:highlightjs',
     '//lib/js:iron-autogrow-textarea',
     '//lib/js:iron-dropdown',
@@ -21,17 +22,3 @@
     '//lib/js:polymer',
     '//lib/js:promise-polyfill',
 ])
-
-
-genrule2(
-  name = 'fonts',
-  cmd = ' && '.join([
-    'cd $$TMP; for file in $(SRCS); do unzip -q $$ROOT/$$file; done',
-    'zip -q $$ROOT/$@ *',
-  ]),
-  srcs = [
-    '//lib/fonts:sourcecodepro.zip',
-  ],
-  out = 'fonts.zip',
-  visibility = ['//visibility:public'],
-)
diff --git a/polygerrit-ui/app/BUILD b/polygerrit-ui/app/BUILD
index 29bbfff..362a42b 100644
--- a/polygerrit-ui/app/BUILD
+++ b/polygerrit-ui/app/BUILD
@@ -1,24 +1,8 @@
 package(
   default_visibility = ["//visibility:public"])
+
 load("//tools/bzl:js.bzl", "bower_component_bundle", "vulcanize")
 
-WCT_TEST_PATTERNS = [
-  'test/*.js',
-  'test/*.html',
-  '**/*_test.html',
-]
-PY_TEST_PATTERNS = ['polygerrit_wct_tests.py']
-APP_SRCS = glob(
-  ['**'],
-  exclude = [
-    'BUCK',
-    '*~',
-    '**/BUILD',
-    'index.html',
-    'test/**',
-  ] + WCT_TEST_PATTERNS + PY_TEST_PATTERNS)
-
-
 bower_component_bundle(
   name = 'test_components',
   deps = [
@@ -32,6 +16,58 @@
 vulcanize(
   name = "gr-app",
   app = 'elements/gr-app.html',
-  srcs = APP_SRCS,
+  srcs = glob(
+  ['**'],
+  exclude = [
+    '**/BUCK',
+    '**/BUILD',
+    '*~',
+    '#*',
+    'index.html',
+    'test/**',
+    '**/*_test.html',
+    "polygerrit_wct_tests.py"]),
   deps = [ "//polygerrit-ui:polygerrit_components"],
 )
+
+filegroup(
+  name = "top_sources",
+  srcs = glob([
+    'favicon.ico',
+    'index.html',
+  ]),
+)
+
+filegroup(
+  name = "css_sources",
+  srcs = glob(['styles/**/*.css'])
+)
+
+genrule(
+  name = "polygerrit_ui",
+  cmd = " && ".join([
+    "t=$$(mktemp  -d)",
+    "p=$$PWD",
+    "mkdir -p $$t/polygerrit_ui/{styles,fonts,bower_components/{highlightjs,webcomponentsjs},elements}",
+    "cp $(locations :gr-app) $$t/polygerrit_ui/elements/",
+    "cp $(locations //lib/fonts:sourcecodepro) $$t/polygerrit_ui/fonts/",
+    "for f in $(locations :top_sources); do cp $$f $$t/polygerrit_ui/; done",
+    "for f in $(locations :css_sources); do cp $$f $$t/polygerrit_ui/styles; done",
+    "for f in $(locations //lib/js:highlightjs_files); do cp $$f $$t/polygerrit_ui/bower_components/highlightjs/ ; done",
+    "unzip -qd $$t/polygerrit_ui/bower_components $(location @webcomponentsjs//:zipfile) webcomponentsjs/webcomponents-lite.js",
+    "cd $$t",
+    "find . -exec touch -t 198001010000 '{}' ';'",
+    "zip -qr $$p/$@ *",
+  ]),
+  srcs = [
+    "//lib/fonts:sourcecodepro",
+    "//lib/js:highlightjs_files",
+    ":top_sources",
+    ":css_sources",
+    ":gr-app",
+    # we extract from the zip, but depend on the component for license checking.
+    "@webcomponentsjs//:zipfile",
+    "//lib/js:webcomponentsjs",
+  ],
+  outs = [ "polygerrit_ui.zip" ],
+)
diff --git a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
index 87d7116..4e0ff94 100644
--- a/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
+++ b/polygerrit-ui/app/elements/change/gr-account-list/gr-account-list.js
@@ -105,7 +105,6 @@
     },
 
     additions: function() {
-      var result = [];
       return this.accounts.filter(function(account) {
         return account._pendingAdd;
       }).map(function(account) {
@@ -115,7 +114,6 @@
           return {account: account};
         }
       });
-      return result;
     },
   });
 })();
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 e35d3cb..9fdc2a9 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
@@ -484,7 +484,7 @@
         switch (action.__key) {
           case ChangeActions.REVERT:
             this._setLabelValuesOnRevert(obj.change_id);
-            // Fall through.
+            /* falls through */
           case RevisionActions.CHERRYPICK:
             page.show(this.changePath(obj._number));
             break;
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 5fe41ec..c180f46 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
@@ -334,15 +334,6 @@
       test('branch name cleared when re-open cherrypick', function() {
         var cherryPickButton =
             element.$$('gr-button[data-action-key="cherrypick"]');
-        var action = {
-          __key: 'cherrypick',
-          __type: 'revision',
-          __primary: false,
-          enabled: true,
-          label: 'Cherry Pick',
-          method: 'POST',
-          title: 'Cherry pick change to a different branch',
-        };
         var emptyBranchName = '';
         element.$.confirmCherrypick.branch = 'master';
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 80d73a4..d8d57244 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -515,14 +515,14 @@
         patchNum: 2,
       };
       element._change = {
-          change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
-          revisions: {
-            rev1: {_number: 1},
-          },
-          current_revision: 'rev1',
-          status: element.ChangeStatus.MERGED,
-          labels: {},
-          actions: {},
+        change_id: 'Iad9dc96274af6946f3632be53b106ef80f7ba6ca',
+        revisions: {
+          rev1: {_number: 1},
+        },
+        current_revision: 'rev1',
+        status: element.ChangeStatus.MERGED,
+        labels: {},
+        actions: {},
       };
 
       var urlParamStub = sandbox.stub(element, '_getUrlParameter',
@@ -540,6 +540,7 @@
     suite('scroll related tests', function() {
       test('document scrolling calls function to set scroll height',
           function(done) {
+            var originalHeight = document.body.scrollHeight;
             var scrollStub = sandbox.stub(element, '_handleScroll',
                 function() {
                   assert.isTrue(scrollStub.called);
@@ -547,7 +548,6 @@
                       originalHeight + 'px';
                   done();
                 });
-            var originalHeight = document.body.scrollHeight;
             document.body.style.height = '10000px';
             document.body.scrollTop = TEST_SCROLL_TOP_PX;
             element._handleScroll();
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
index 91c6b5e..0caa6be 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list_test.html
@@ -150,9 +150,11 @@
         var toggleLeftDiffStub = sandbox.stub();
         // Property getter cannot be stubbed w/ sandbox due to a bug in Sinon.
         // https://github.com/sinonjs/sinon/issues/781
-        var diffsStub = sinon.stub(element, 'diffs', {get: function() {
-          return [{toggleLeftDiff: toggleLeftDiffStub}];
-        }});
+        var diffsStub = sinon.stub(element, 'diffs', {
+          get: function() {
+            return [{toggleLeftDiff: toggleLeftDiffStub}];
+          },
+        });
         MockInteractions.pressAndReleaseKeyOn(element, 65, 'shift');  // 'A'
         assert.isTrue(toggleLeftDiffStub.calledOnce);
         diffsStub.restore();
@@ -401,7 +403,6 @@
       };
       element.selectedIndex = 0;
       flushAsynchronousOperations();
-      var select = element.$.modeSelect;
       var diffDisplay = element.diffs[0];
       element._userPrefs = {diff_view: 'SIDE_BY_SIDE'};
       assert.equal(element._getDiffViewMode(), 'SIDE_BY_SIDE');
diff --git a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
index 01f734b..2398287 100644
--- a/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-messages-list/gr-messages-list_test.html
@@ -116,11 +116,6 @@
     });
 
     test('messages', function() {
-      var dates = [
-        '2016-01-12 20:28:33.038000',
-        '2016-01-12 21:28:33.038000',
-        '2016-01-12 22:28:33.038000'
-      ];
       var author = {
         _account_id: 42,
         name: 'Marvin the Paranoid Android',
diff --git a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
index 082f81b..85b1119 100644
--- a/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
+++ b/polygerrit-ui/app/elements/core/gr-reporting/gr-reporting_test.html
@@ -74,7 +74,7 @@
     });
 
     test('time and timeEnd', function() {
-      var nowStub = sinon.stub(element, 'now').returns(0);
+      var nowStub = sandbox.stub(element, 'now').returns(0);
       element.time('foo');
       nowStub.returns(1);
       element.time('bar');
@@ -99,7 +99,7 @@
 
       test('pluginsLoaded reports time', function() {
         Gerrit._arePluginsLoaded.returns(true);
-        var nowStub = sinon.stub(element, 'now').returns(42);
+        sandbox.stub(element, 'now').returns(42);
         element.pluginsLoaded();
         assert.isTrue(element.defaultReporter.calledWithExactly(
             'timing-report', 'UI Latency', 'PluginsLoaded', 42
diff --git a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
index 4fd1e19..9caed8c 100644
--- a/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
+++ b/polygerrit-ui/app/elements/core/gr-search-bar/gr-search-bar.js
@@ -164,7 +164,7 @@
             return accounts.map(function(acct) {
               return predicate + ':"' + acct.name + ' <' + acct.email + '>"';
             });
-      });
+          });
     },
 
     /**
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
index 1cb8cc7..126882c 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-side-by-side.js
@@ -69,7 +69,6 @@
   GrDiffBuilderSideBySide.prototype._getNextContentOnSide = function(
       content, side) {
     var tr = content.parentElement.parentElement;
-    var content;
     while (tr = tr.nextSibling) {
       content = tr.querySelector(
           'td.content .contentText[data-side="' + side + '"]');
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
index 960bf46..e1f3ed1 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-builder/gr-diff-builder-unified.js
@@ -62,7 +62,6 @@
   GrDiffBuilderUnified.prototype._getNextContentOnSide = function(
       content, side) {
     var tr = content.parentElement.parentElement;
-    var content;
     while (tr = tr.nextSibling) {
       if (tr.classList.contains('both') || (
           (side === 'left' && tr.classList.contains('remove')) ||
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index 0a591ae..11de0ba 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -234,10 +234,6 @@
     });
 
     test('collapsible drafts', function() {
-      element.addEventListener('reply', function(e) {
-        assert.ok(e.detail.comment);
-        done();
-      });
       assert.isTrue(element.collapsed);
       assert.isFalse(isVisible(element.$$('gr-linked-text')),
           'gr-linked-text is not visible');
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 c35c739..e913659 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
@@ -489,6 +489,7 @@
       resolvePrefs({diff_view: 'UNIFIED'});
       flushAsynchronousOperations();
       assert.equal(select.value, 'SIDE_BY_SIDE');
+      getPreferencesStub.restore();
     });
 
     test('unified view is always default on small screens', function() {
@@ -522,6 +523,7 @@
 
       // On small screens, unified should override user perferences
       assert.equal(select.value, 'UNIFIED_DIFF');
+      getPreferencesStub.restore();
     });
 
     test('_loadHash', function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 565bfab..14c2bb5 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -51,8 +51,8 @@
         border-collapse: collapse;
         border-right: 1px solid #ddd;
         table-layout: fixed;
-      }
-      table tbody {
+
+        /* Hint GPU acceleration */
         -webkit-transform: translateZ(0);
         -moz-transform: translateZ(0);
         -ms-transform: translateZ(0);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index e3492a8..d3d78c4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -396,12 +396,12 @@
           this.patchRange.patchNum,
           this.path,
           this._handleGetDiffError.bind(this)).then(function(diff) {
-               this.filesWeblinks = {
-                 meta_a: diff && diff.meta_a && diff.meta_a.web_links,
-                 meta_b: diff && diff.meta_b && diff.meta_b.web_links,
-               };
-               return diff;
-             }.bind(this));
+            this.filesWeblinks = {
+              meta_a: diff && diff.meta_a && diff.meta_a.web_links,
+              meta_b: diff && diff.meta_b && diff.meta_b.web_links,
+            };
+            return diff;
+          }.bind(this));
     },
 
     _getDiffComments: function() {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index 77dce25d..7e20a5b 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -493,6 +493,7 @@
           });
           var spy = sinon.spy(element, '_handleShowDiff');
           element.set('expanded', true);
+          assert.isTrue(spy.called);
         });
       });
     });
diff --git a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
index 68b7528..eae77ef 100644
--- a/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-ranged-comment-layer/gr-ranged-comment-layer_test.html
@@ -173,9 +173,6 @@
         line.beforeNumber = 36;
         el.setAttribute('data-side', 'right');
 
-        var expectedStart = 6;
-        var expectedLength = line.text.length - expectedStart;
-
         element.annotate(el, line);
 
         assert.isFalse(annotateElementStub.called);
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 84e97bd..1103c03 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -47,7 +47,6 @@
     'text/x-yaml': 'yaml',
   };
   var ASYNC_DELAY = 10;
-  var HLJS_PATH = 'bower_components/highlightjs/highlight.min.js';
 
   var CLASS_WHITELIST = {
     'gr-diff gr-syntax gr-syntax-literal': true,
@@ -278,8 +277,8 @@
      * @param {!Object} state The processing state for the layer.
      */
     _processNextLine: function(state) {
-      var baseLine = undefined;
-      var revisionLine = undefined;
+      var baseLine;
+      var revisionLine;
 
       var section = this.diff.content[state.sectionIndex];
       if (section.ab) {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index ecd1a07..392c320 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -311,7 +311,7 @@
           text: function() { return Promise.resolve(')]}\'{}'); }
         },
       ];
-      var fetchStub = sandbox.stub(window, 'fetch', function(url) {
+      sandbox.stub(window, 'fetch', function(url) {
         if (url === '/accounts/self/detail') {
           return Promise.resolve(responses.shift());
         }
diff --git a/tools/bzl/asciidoc.bzl b/tools/bzl/asciidoc.bzl
index 17736cd..db6ea86 100644
--- a/tools/bzl/asciidoc.bzl
+++ b/tools/bzl/asciidoc.bzl
@@ -120,22 +120,26 @@
     progress_message = "Rendering asciidoctor files for %s" % ctx.label.name,
   )
 
+_asciidoc_attrs = {
+  "_exe": attr.label(
+    default = Label("//lib/asciidoctor:asciidoc"),
+    cfg = "host",
+    allow_files = True,
+    executable = True,
+  ),
+  "srcs": attr.label_list(mandatory = True, allow_files = True),
+  "version": attr.label(
+    default = Label("//:version.txt"),
+    allow_single_file = True,
+  ),
+  "suffix": attr.string(mandatory = True),
+  "backend": attr.string(),
+  "attributes": attr.string_list(),
+}
+
 _asciidoc = rule(
   implementation = _asciidoc_impl,
-  attrs = {
-    "_exe": attr.label(
-      default = Label("//lib/asciidoctor:asciidoc"),
-      allow_files = True,
-      executable = True,
-    ),
-    "srcs": attr.label_list(mandatory = True, allow_files = True),
-    "version": attr.label(
-      default = Label("//:version.txt"),
-      allow_single_file = True,
-    ),
-    "suffix": attr.string(mandatory = True),
-    "backend": attr.string(),
-    "attributes": attr.string_list(),
+  attrs = _asciidoc_attrs + {
     "outs": attr.output_list(mandatory = True),
   },
 )
@@ -222,21 +226,7 @@
 
 _asciidoc_html_zip = rule(
   implementation = _asciidoc_html_zip_impl,
-  attrs = {
-    "_exe": attr.label(
-      default = Label("//lib/asciidoctor:asciidoc"),
-      allow_files = True,
-      executable = True,
-    ),
-    "srcs": attr.label_list(mandatory = True, allow_files = True),
-    "version": attr.label(
-      default = Label("//:version.txt"),
-      allow_single_file = True,
-    ),
-    "suffix": attr.string(mandatory = True),
-    "backend": attr.string(),
-    "attributes": attr.string_list(),
-  },
+  attrs = _asciidoc_attrs,
   outputs = {
     "out": "%{name}.zip",
   }
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
index 80771da..c479e2c 100644
--- a/tools/bzl/javadoc.bzl
+++ b/tools/bzl/javadoc.bzl
@@ -27,18 +27,21 @@
   dir = ctx.outputs.zip.path + ".dir"
   source = ctx.outputs.zip.path + ".source"
   cmd = [
+      "rm -rf %s" % source,
       "mkdir %s" % source,
       " && ".join(["unzip -qud %s %s" % (source, j.path) for j in source_jars]),
+      "rm -rf %s" % dir,
       "mkdir %s" % dir,
       " ".join([
         ctx.file._javadoc.path,
+        "-Xdoclint:-missing",
         "-protected",
         "-encoding UTF-8",
         "-charset UTF-8",
         "-notimestamp",
         "-quiet",
         "-windowtitle '%s'" % ctx.attr.title,
-        "-link", "http://docs.oracle.com/javase/7/docs/api",
+        "-link", "http://docs.oracle.com/javase/8/docs/api",
         "-sourcepath %s" % source,
         "-subpackages ",
         ":".join(ctx.attr.pkgs),
diff --git a/tools/bzl/js.bzl b/tools/bzl/js.bzl
index 38d6c91..d03faa5 100644
--- a/tools/bzl/js.bzl
+++ b/tools/bzl/js.bzl
@@ -281,7 +281,10 @@
 )
 
 def _vulcanize_impl(ctx):
-  destdir = ctx.outputs.vulcanized.path + ".dir"
+  # intermediate artifact.
+  vulcanized = ctx.new_file(
+    ctx.configuration.genfiles_dir, ctx.outputs.html, ".vulcanized.html")
+  destdir = ctx.outputs.html.path + ".dir"
   zips =  [z for d in ctx.attr.deps for z in d.transitive_zipfiles ]
 
   hermetic_npm_binary = " ".join([
@@ -291,7 +294,7 @@
     '--inline-scripts',
     '--inline-css',
     '--strip-comments',
-    '--out-html', "$p/" + ctx.outputs.vulcanized.path,
+    '--out-html', "$p/" + vulcanized.path,
     ctx.file.app.path
   ])
 
@@ -313,7 +316,7 @@
     inputs = [ctx.file._run_npm, ctx.file.app,
               ctx.file._vulcanize_archive
     ] + list(zips) + ctx.files.srcs,
-    outputs = [ctx.outputs.vulcanized],
+    outputs = [vulcanized],
     command = cmd)
 
   hermetic_npm_command = "export PATH && " + " ".join([
@@ -321,14 +324,14 @@
     ctx.file._run_npm.path,
     ctx.file._crisper_archive.path,
     "--always-write-script",
-    "--source", ctx.outputs.vulcanized.path,
+    "--source", vulcanized.path,
     "--html", ctx.outputs.html.path,
     "--js", ctx.outputs.js.path])
 
   ctx.action(
     mnemonic = "Crisper",
     inputs = [ctx.file._run_npm, ctx.file.app,
-              ctx.file._crisper_archive, ctx.outputs.vulcanized],
+              ctx.file._crisper_archive, vulcanized],
     outputs = [ctx.outputs.js, ctx.outputs.html],
     command = hermetic_npm_command)
 
@@ -355,9 +358,8 @@
     ),
   },
   outputs = {
-    "vulcanized": "%{name}.vulcanized.html",
-    "html": "%{name}.crisped.html",
-    "js": "%{name}.crisped.js",
+    "html": "%{name}.html",
+    "js": "%{name}.js",
   }
 )
 
diff --git a/tools/workspace-status.sh b/tools/workspace-status.sh
deleted file mode 100755
index 506330c..0000000
--- a/tools/workspace-status.sh
+++ /dev/null
@@ -1,15 +0,0 @@
-#!/bin/sh
-
-# This script will be run by bazel when the build process starts to
-# generate key-value information that represents the status of the
-# workspace. The output should be like
-#
-# KEY1 VALUE1
-# KEY2 VALUE2
-#
-# If the script exits with non-zero code, it's considered as a failure
-# and the output will be discarded.
-
-git_rev=$(git describe --always --match "v[0-9].*" --dirty)
-
-echo "STABLE_BUILD_GERRIT_LABEL ${git_rev}"