Merge "Add CHANGE_SCREEN_HISTORY_RIGHT_OF_BUTTONS extension point"
diff --git a/.buckconfig b/.buckconfig
index f2ca353..cc3353d 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -24,6 +24,7 @@
   target_level = 8
 
 [project]
+  allow_symlinks = allow
   ignore = .git, eclipse-out, bazel-gerrit
   parallel_parsing = true
 
diff --git a/.buckversion b/.buckversion
index f5fe016..efb68ecf 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-e64a2e2ada022f81e42be750b774024469551398
+fd3105a0b62899f74662f4cdc156de6990bdc24c
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..28dd0f5
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,14 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+load('//tools/bzl:pkg_war.bzl', 'pkg_war')
+
+genrule2(
+  name = 'version',
+  srcs = ['VERSION'],
+  cmd = "grep GERRIT_VERSION $< | cut -d \"'\" -f 2 >$@",
+  out = 'version.txt',
+  visibility = ['//visibility:public'],
+)
+
+pkg_war(name = 'gerrit')
+pkg_war(name = 'headless', ui = None)
+
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index d48034a..3af3bca 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -635,10 +635,10 @@
 allowed to upload new patch sets for their changes. This permission needs to be
 set on `refs/for/*`.
 
-The absence of this permission will prevent users from uploading a
-patch set to a change they do not own. By default, this permission is granted to
-`Registered Users` on `refs/for/*` allowing all registered users to upload a new
-patch set to any change on that ref.
+By default, this permission is granted to `Registered Users` on `refs/for/*`,
+allowing all registered users to upload a new patch set to any change. Revoking
+this permission (by granting it to no groups and setting the "Exclusive" flag)
+will prevent users from uploading a patch set to a change they do not own.
 
 
 [[category_push_merge]]
@@ -887,6 +887,14 @@
 can always edit or remove hashtags (even without having the `Edit Hashtags`
 access right assigned).
 
+[[category_edit_assigned_to]]
+=== Edit Assignee
+
+This category permits users to set who is assigned to a change that is
+uploaded for review.
+
+The change owner, ref owners, and the user currently assigned to a change
+can always change the assignee.
 
 [[example_roles]]
 == Examples of typical roles in a project
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index d453f7b..8661d40 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1412,6 +1412,33 @@
 +
 Default is true.
 
+[[core.repositoryCacheCleanupDelay]]core.repositoryCacheCleanupDelay::
++
+Delay between each periodic cleanup of expired repositories.
++
+Values can be specified using standard time unit abbreviations (`ms`, `sec`,
+`min`, etc.).
++
+Set it to 0 in order to switch off cache expiration. If cache expiration is
+switched off, the JVM can still evict cache entries when it is running low
+on available heap memory.
++
+Set it to -1 to automatically derive cleanup delay from
+`core.repositoryCacheExpireAfter` (lowest value between 1/10 of
+`core.repositoryCacheExpireAfter` and 10 minutes).
++
+Default is -1.
+
+[[core.repositoryCacheExpireAfter]]core.repositoryCacheExpireAfter::
++
+Time an unused repository should expire and be evicted from the repository
+cache.
++
+Values can be specified using standard time unit abbreviations (`ms`, `sec`,
+`min`, etc.).
++
+Default is 1 hour.
+
 [[database]]
 === Section database
 
@@ -3300,6 +3327,14 @@
 +
 By default, true, allowing notifications to be sent.
 
+[[sendemail.html]]sendemail.html::
++
+If false, Gerrit will only send plain-text emails.
+If true, Gerrit will send multi-part emails with an HTML and
+plain text part.
++
+By default, true, allowing HTML in the emails Gerrit sends.
+
 [[sendemail.connectTimeout]]sendemail.connectTimeout::
 +
 The connection timeout of opening a socket connected to a
diff --git a/Documentation/dev-release-deploy-config.txt b/Documentation/dev-release-deploy-config.txt
index 900e95c..d43c863 100644
--- a/Documentation/dev-release-deploy-config.txt
+++ b/Documentation/dev-release-deploy-config.txt
@@ -89,17 +89,15 @@
 
 To upload artifacts to a bucket the user must authenticate with a
 username and password. The username and password need to be retrieved
-from the link:https://console.developers.google.com/project/164060093628[
-Google Developers Console]:
+from the link:https://console.cloud.google.com/storage/settings?project=api-project-164060093628[
+Storage Setting in the Google Cloud Platform Console]:
 
-* In the menu on the left select `Storage` -> `Cloud Storage` >
-> `Storage access`
-* Select the `Interoperability` tab
-* If no keys are listed under `Interoperable storage access keys`, select "Create a new key"
-* Use the `Access Key` as username, and `Secret` as the password
+Select the `Interoperability` tab, and if no keys are listed under
+`Interoperable storage access keys`, select 'Create a new key'.
 
-To make the username and password known to Maven, they must be
-configured in the `~/.m2/settings.xml` file.
+Using `Access Key` as username and `Secret` as the password, add the
+configuration in the `~/.m2/settings.xml` file to make the credentials
+known to Maven:
 
 ----
   <settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 454a30d..de21034 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -123,7 +123,10 @@
     "gerrit": {
       "all_projects": "All-Projects",
       "all_users": "All-Users"
-      "doc_search": true
+      "doc_search": true,
+      "web_uis": [
+        "gwt"
+      ]
     },
     "sshd": {},
     "suggest": {
@@ -1469,6 +1472,9 @@
 |`report_bug_text`   |optional, not set if default|
 link:config-gerrit.html#gerrit.reportBugText[Display text for report
 bugs link].
+|`web_uis`           ||
+List of web UIs supported by the HTTP server. Possible values are `GWT`
+and `POLYGERRIT`.
 |=================================
 
 [[hit-ration-info]]
diff --git a/WORKSPACE b/WORKSPACE
index e93a911..536c827 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -118,6 +118,12 @@
   sha1 = 'cdb2dcb4e22b83d6b32b93095f644c3462739e82',
 )
 
+http_jar(
+  name = "javax_validation_src",
+  url = "http://repo1.maven.org/maven2/javax/validation/validation-api/1.0.0.GA/validation-api-1.0.0.GA-sources.jar",
+  sha256 = 'a394d52a9b7fe2bb14f0718d2b3c8308ffe8f37e911956012398d55c9f9f9b54',
+)
+
 JGIT_VERS = '4.4.1.201607150455-r.118-g1096652'
 
 maven_jar(
@@ -583,8 +589,8 @@
 
 maven_jar(
   name = 'jimfs',
-  artifact = 'com.google.jimfs:jimfs:1.0',
-  sha1 = 'edd65a2b792755f58f11134e76485a928aab4c97',
+  artifact = 'com.google.jimfs:jimfs:1.1',
+  sha1 = '8fbd0579dc68aba6186935cc1bee21d2f3e7ec1c',
 )
 
 maven_jar(
@@ -746,3 +752,29 @@
   artifact = 'xerces:xercesImpl:2.8.1',
   sha1 = '25101e37ec0c907db6f0612cbf106ee519c1aef1',
 )
+
+maven_jar(
+  name = 'postgresql',
+  artifact = 'postgresql:postgresql:9.1-901-1.jdbc4',
+  sha1 = '9bfabe48876ec38f6cbaa6931bad05c64a9ea942',
+)
+
+CM_VERSION = '5.18.2'
+
+maven_jar(
+  name = 'codemirror_minified',
+  artifact = 'org.webjars.npm:codemirror-minified:' + CM_VERSION,
+  sha1 = '6755af157a7eaf2401468906bef67bbacc3c97f6',
+)
+
+maven_jar(
+  name = 'codemirror_original',
+  artifact = 'org.webjars.npm:codemirror:' + CM_VERSION,
+  sha1 = '18c721ae88eed27cddb458c42f5d221fa3d9713e',
+)
+
+maven_jar(
+  name = 'diff_match_patch',
+  artifact = 'org.webjars:google-diff-match-patch:20121119-1',
+  sha1 = '0cf1782dbcb8359d95070da9176059a5a9d37709',
+)
diff --git a/gerrit-acceptance-framework/BUILD b/gerrit-acceptance-framework/BUILD
index 934e8d1..58c13f5 100644
--- a/gerrit-acceptance-framework/BUILD
+++ b/gerrit-acceptance-framework/BUILD
@@ -59,3 +59,12 @@
   ],
   visibility = ['//visibility:public'],
 )
+
+load('//tools/bzl:javadoc.bzl', 'java_doc')
+
+java_doc(
+  name = 'acceptance-framework-javadoc',
+  title = 'Gerrit Acceptance Test Framework Documentation',
+  libs = [':lib'],
+  pkgs = ['com.google.gerrit.acceptance'],
+)
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 28f7ff8..dfeac35 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
@@ -29,6 +29,7 @@
 import org.eclipse.jgit.junit.TestRepository;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -75,8 +76,9 @@
   }
 
   protected TestRepository<?> createProjectWithPush(String name,
-      @Nullable Project.NameKey parent, SubmitType submitType) throws Exception {
-    Project.NameKey project = createProject(name, parent, submitType);
+      @Nullable Project.NameKey parent, boolean createEmptyCommit,
+      SubmitType submitType) throws Exception {
+    Project.NameKey project = createProject(name, parent, createEmptyCommit, submitType);
     grant(Permission.PUSH, project, "refs/heads/*");
     grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
     return cloneProject(project);
@@ -84,12 +86,17 @@
 
   protected TestRepository<?> createProjectWithPush(String name,
       @Nullable Project.NameKey parent) throws Exception {
-    return createProjectWithPush(name, parent, getSubmitType());
+    return createProjectWithPush(name, parent, true, getSubmitType());
+  }
+
+  protected TestRepository<?> createProjectWithPush(String name,
+      boolean createEmptyCommit) throws Exception {
+    return createProjectWithPush(name, null, createEmptyCommit, getSubmitType());
   }
 
   protected TestRepository<?> createProjectWithPush(String name)
       throws Exception {
-    return createProjectWithPush(name, null);
+    return createProjectWithPush(name, null, true, getSubmitType());
   }
 
   private static AtomicInteger contentCounter = new AtomicInteger(0);
@@ -305,8 +312,13 @@
       String submodule) throws Exception {
 
     submodule = name(submodule);
-    ObjectId commitId = repo.git().fetch().setRemote("origin").call()
-        .getAdvertisedRef("refs/heads/" + branch).getObjectId();
+    Ref branchTip = repo.git().fetch().setRemote("origin").call()
+        .getAdvertisedRef("refs/heads/" + branch);
+    if (branchTip == null) {
+      return false;
+    }
+
+    ObjectId commitId = branchTip.getObjectId();
 
     RevWalk rw = repo.getRevWalk();
     RevCommit c = rw.parseCommit(commitId);
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 dc88926..d62a27b 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
@@ -258,6 +258,56 @@
   }
 
   @Test
+  public void testDoNotUseFastForward() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project", false);
+    TestRepository<?> sub = createProjectWithPush("sub", false);
+
+    allowMatchingSubmoduleSubscription("sub", "refs/heads/master",
+        "super-project", "refs/heads/master");
+
+    createSubmoduleSubscription(superRepo, "master", "sub", "master");
+
+    ObjectId subId =
+        pushChangeTo(sub, "refs/for/master", "some message", "same-topic");
+
+    ObjectId superId =
+        pushChangeTo(superRepo, "refs/for/master", "some message", "same-topic");
+
+    String subChangeId = getChangeId(sub, subId).get();
+    approve(subChangeId);
+    approve(getChangeId(superRepo, superId).get());
+
+    gApi.changes().id(subChangeId).current().submit();
+
+    expectToHaveSubmoduleState(superRepo, "master", "sub", sub, "master");
+    RevCommit superHead = getRemoteHead(name("super-project"), "master");
+    assertThat(superHead.getShortMessage()).contains("some message");
+    assertThat(superHead.getId()).isNotEqualTo(superId);
+  }
+
+  @Test
+  public void testUseFastForwardWhenNoSubmodule() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project", false);
+    TestRepository<?> sub = createProjectWithPush("sub", false);
+
+    ObjectId subId =
+        pushChangeTo(sub, "refs/for/master", "some message", "same-topic");
+
+    ObjectId superId =
+        pushChangeTo(superRepo, "refs/for/master", "some message", "same-topic");
+
+    String subChangeId = getChangeId(sub, subId).get();
+    approve(subChangeId);
+    approve(getChangeId(superRepo, superId).get());
+
+    gApi.changes().id(subChangeId).current().submit();
+
+    RevCommit superHead = getRemoteHead(name("super-project"), "master");
+    assertThat(superHead.getShortMessage()).isEqualTo("some message");
+    assertThat(superHead.getId()).isEqualTo(superId);
+  }
+
+  @Test
   public void testDifferentPaths() throws Exception {
     TestRepository<?> superRepo = createProjectWithPush("super-project");
     TestRepository<?> sub = createProjectWithPush("sub");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
index ff167ac..3522991 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUCK
@@ -3,6 +3,5 @@
 acceptance_tests(
   group = 'pgm',
   srcs = glob(['*IT.java']),
-  source_under_test = ['//gerrit-pgm:pgm'],
   labels = ['pgm'],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD
index 806acd2..56cecb8 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/BUILD
@@ -3,6 +3,5 @@
 acceptance_tests(
   group = 'pgm',
   srcs = glob(['*IT.java']),
-  source_under_test = ['//gerrit-pgm:pgm'],
   labels = ['pgm'],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
index b0280e8..ce7e8c9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/ServerInfoIT.java
@@ -65,8 +65,10 @@
     // gerrit
     @GerritConfig(name = "gerrit.allProjects", value = "Root"),
     @GerritConfig(name = "gerrit.allUsers", value = "Users"),
-    @GerritConfig(name = "gerrit.reportBugUrl", value = "https://example.com/report"),
+    @GerritConfig(name = "gerrit.enableGwtUi", value = "true"),
+    @GerritConfig(name = "gerrit.enablePolyGerrit", value = "true"),
     @GerritConfig(name = "gerrit.reportBugText", value = "REPORT BUG"),
+    @GerritConfig(name = "gerrit.reportBugUrl", value = "https://example.com/report"),
 
     // suggest
     @GerritConfig(name = "suggest.from", value = "3"),
@@ -108,6 +110,9 @@
     assertThat(i.gerrit.reportBugUrl).isEqualTo("https://example.com/report");
     assertThat(i.gerrit.reportBugText).isEqualTo("REPORT BUG");
 
+    // Acceptance tests force --headless even when UIs are specified in config.
+    assertThat(i.gerrit.webUis).isEmpty();
+
     // plugin
     assertThat(i.plugin.jsResourcePaths).isEmpty();
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
index 70dafaa..67af6e2 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/CommentsIT.java
@@ -504,8 +504,7 @@
     assertThat(ps2List.get(2).message).isEqualTo("join lines");
     assertThat(ps2List.get(3).message).isEqualTo("typo: content");
 
-    ImmutableList<Message> messages =
-        email.getMessages(r2.getChangeId(), "comment");
+    List<Message> messages = email.getMessages(r2.getChangeId(), "comment");
     assertThat(messages).hasSize(1);
     String url = canonicalWebUrl.get();
     int c = r1.getChange().getId().get();
diff --git a/gerrit-acceptance-tests/tests.bzl b/gerrit-acceptance-tests/tests.bzl
index ff2562d..62a99e3 100644
--- a/gerrit-acceptance-tests/tests.bzl
+++ b/gerrit-acceptance-tests/tests.bzl
@@ -11,7 +11,6 @@
     flaky = 0,
     deps = [],
     labels = [],
-    source_under_test = [], #unused
     vm_args = ['-Xmx256m']):
   junit_tests(
     name = group,
diff --git a/gerrit-acceptance-tests/tests.defs b/gerrit-acceptance-tests/tests.defs
index 85cc78b..648bd63 100644
--- a/gerrit-acceptance-tests/tests.defs
+++ b/gerrit-acceptance-tests/tests.defs
@@ -8,7 +8,6 @@
     srcs,
     deps = [],
     labels = [],
-    source_under_test = [],
     vm_args = ['-Xmx256m']):
   from os import path
   if path.exists('/dev/urandom'):
@@ -20,11 +19,6 @@
     deps = deps + BOUNCYCASTLE + [
       '//gerrit-acceptance-tests:lib'
     ],
-    source_under_test = [
-      '//gerrit-httpd:httpd',
-      '//gerrit-sshd:sshd',
-      '//gerrit-server:server',
-    ] + source_under_test,
     labels = labels + [
       'acceptance',
       'slow',
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
index 5009771..8141dfb 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheFactory.java
@@ -119,19 +119,8 @@
   public void start() {
     if (executor != null) {
       for (final H2CacheImpl<?, ?> cache : caches) {
-        executor.execute(new Runnable() {
-          @Override
-          public void run() {
-            cache.start();
-          }
-        });
-
-        cleanup.schedule(new Runnable() {
-          @Override
-          public void run() {
-            cache.prune(cleanup);
-          }
-        }, 30, TimeUnit.SECONDS);
+        executor.execute(cache::start);
+        cleanup.schedule(() -> cache.prune(cleanup), 30, TimeUnit.SECONDS);
       }
     }
   }
diff --git a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
index 4d6db4d..7e05236 100644
--- a/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
+++ b/gerrit-cache-h2/src/main/java/com/google/gerrit/server/cache/h2/H2CacheImpl.java
@@ -144,24 +144,14 @@
     final ValueHolder<V> h = new ValueHolder<>(val);
     h.created = TimeUtil.nowMs();
     mem.put(key, h);
-    executor.execute(new Runnable() {
-      @Override
-      public void run() {
-        store.put(key, h);
-      }
-    });
+    executor.execute(() -> store.put(key, h));
   }
 
   @SuppressWarnings("unchecked")
   @Override
   public void invalidate(final Object key) {
     if (keyType.getRawType().isInstance(key) && store.mightContain((K) key)) {
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          store.invalidate((K) key);
-        }
-      });
+      executor.execute(() -> store.invalidate((K) key));
     }
     mem.invalidate(key);
   }
@@ -212,12 +202,7 @@
     cal.add(Calendar.DAY_OF_MONTH, 1);
 
     long delay = cal.getTimeInMillis() - TimeUtil.nowMs();
-    service.schedule(new Runnable() {
-      @Override
-      public void run() {
-        prune(service);
-      }
-    }, delay, TimeUnit.MILLISECONDS);
+    service.schedule(() -> prune(service), delay, TimeUnit.MILLISECONDS);
   }
 
   static class ValueHolder<V> {
@@ -252,12 +237,7 @@
 
       final ValueHolder<V> h = new ValueHolder<>(loader.load(key));
       h.created = TimeUtil.nowMs();
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          store.put(key, h);
-        }
-      });
+      executor.execute(() -> store.put(key, h));
       return h;
     }
   }
@@ -282,12 +262,7 @@
 
       final ValueHolder<V> h = new ValueHolder<>(loader.call());
       h.created = TimeUtil.nowMs();
-      executor.execute(new Runnable() {
-        @Override
-        public void run() {
-          store.put(key, h);
-        }
-      });
+      executor.execute(() -> store.put(key, h));
       return h;
     }
   }
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index 847fd25..452b2fe 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -62,7 +62,6 @@
     '//lib:guava',
     '//lib:junit',
   ],
-  source_under_test = [':client'],
 )
 
 java_test(
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 8fae4a6..290b9f9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -29,6 +29,7 @@
   public static final String CREATE_SIGNED_TAG = "createSignedTag";
   public static final String DELETE_DRAFTS = "deleteDrafts";
   public static final String EDIT_HASHTAGS = "editHashtags";
+  public static final String EDIT_ASSIGNEE = "editAssignee";
   public static final String EDIT_TOPIC_NAME = "editTopicName";
   public static final String FORGE_AUTHOR = "forgeAuthor";
   public static final String FORGE_COMMITTER = "forgeCommitter";
@@ -74,6 +75,7 @@
     NAMES_LC.add(VIEW_DRAFTS.toLowerCase());
     NAMES_LC.add(EDIT_TOPIC_NAME.toLowerCase());
     NAMES_LC.add(EDIT_HASHTAGS.toLowerCase());
+    NAMES_LC.add(EDIT_ASSIGNEE.toLowerCase());
     NAMES_LC.add(DELETE_DRAFTS.toLowerCase());
     NAMES_LC.add(PUBLISH_DRAFTS.toLowerCase());
 
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 2cc8291..a0e7495 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -67,7 +67,6 @@
     '//lib:truth',
     '//lib/guice:guice',
   ],
-  source_under_test = [':api'],
 )
 
 java_doc(
diff --git a/gerrit-extension-api/BUILD b/gerrit-extension-api/BUILD
index 4a5cfe3..b66617a 100644
--- a/gerrit-extension-api/BUILD
+++ b/gerrit-extension-api/BUILD
@@ -44,3 +44,12 @@
   ],
   visibility = ['//visibility:public'],
 )
+
+load('//tools/bzl:javadoc.bzl', 'java_doc')
+
+java_doc(
+  name = 'extension-api-javadoc',
+  title = 'Gerrit Review Extension API Documentation',
+  libs = [':api'],
+  pkgs = ['com.google.gerrit.extensions'],
+)
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java
new file mode 100644
index 0000000..0d9df39
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/UiType.java
@@ -0,0 +1,32 @@
+// 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.extensions.client;
+
+public enum UiType {
+  NONE,
+  GWT,
+  POLYGERRIT;
+
+  public static UiType parse(String str) {
+    if (str != null) {
+      for (UiType type : UiType.values()) {
+        if (type.name().equalsIgnoreCase(str)) {
+          return type;
+        }
+      }
+    }
+    return null;
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java
index 72c474f..0c10ec7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/GerritInfo.java
@@ -14,6 +14,10 @@
 
 package com.google.gerrit.extensions.common;
 
+import com.google.gerrit.extensions.client.UiType;
+
+import java.util.Set;
+
 public class GerritInfo {
   public String allProjects;
   public String allUsers;
@@ -22,4 +26,5 @@
   public Boolean editGpgKeys;
   public String reportBugUrl;
   public String reportBugText;
-}
\ No newline at end of file
+  public Set<UiType> webUis;
+}
diff --git a/gerrit-gpg/BUCK b/gerrit-gpg/BUCK
index 73d9f04..fe93bf8 100644
--- a/gerrit-gpg/BUCK
+++ b/gerrit-gpg/BUCK
@@ -52,6 +52,5 @@
     '//lib/bouncycastle:bcprov',
     '//lib/jgit/org.eclipse.jgit.junit:junit',
   ],
-  source_under_test = [':gpg'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
index db6cb7a..3bbe6eb 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/GerritPublicKeyChecker.java
@@ -19,10 +19,8 @@
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
 import com.google.common.io.BaseEncoding;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -274,9 +272,7 @@
   private static String missingUserIds(Set<String> allowedUserIds) {
     StringBuilder sb = new StringBuilder("Key must contain a valid"
         + " certification for one of the following identities:\n");
-    Iterator<String> sorted = FluentIterable.from(allowedUserIds)
-        .toSortedList(Ordering.natural())
-        .iterator();
+    Iterator<String> sorted = allowedUserIds.stream().sorted().iterator();
     while (sorted.hasNext()) {
       sb.append("  ").append(sorted.next());
       if (sorted.hasNext()) {
diff --git a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
index 49657c6..ddee18d 100644
--- a/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
+++ b/gerrit-gpg/src/main/java/com/google/gerrit/gpg/server/GpgKeys.java
@@ -19,7 +19,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.io.BaseEncoding;
@@ -211,14 +210,8 @@
   @VisibleForTesting
   public static FluentIterable<AccountExternalId> getGpgExtIds(ReviewDb db,
       Account.Id accountId) throws OrmException {
-    return FluentIterable
-        .from(db.accountExternalIds().byAccount(accountId))
-        .filter(new Predicate<AccountExternalId>() {
-          @Override
-          public boolean apply(AccountExternalId in) {
-            return in.isScheme(SCHEME_GPGKEY);
-          }
-        });
+    return FluentIterable.from(db.accountExternalIds().byAccount(accountId))
+        .filter(in -> in.isScheme(SCHEME_GPGKEY));
   }
 
   private Iterable<AccountExternalId> getGpgExtIds(AccountResource rsrc)
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 2deae3f..7c5c6ea 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
@@ -18,7 +18,6 @@
 import static com.google.gerrit.gpg.PublicKeyStore.keyToString;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
@@ -161,13 +160,8 @@
       if (!newExtIds.isEmpty()) {
         db.get().accountExternalIds().insert(newExtIds);
       }
-      db.get().accountExternalIds().deleteKeys(Iterables.transform(toRemove,
-          new Function<Fingerprint, AccountExternalId.Key>() {
-            @Override
-            public AccountExternalId.Key apply(Fingerprint fp) {
-              return toExtIdKey(fp.get());
-            }
-          }));
+      db.get().accountExternalIds().deleteKeys(
+          Iterables.transform(toRemove, fp -> toExtIdKey(fp.get())));
       accountCache.evict(rsrc.getUser().getAccountId());
       return toJson(newKeys, toRemove, store, rsrc.getUser());
     }
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index 79a97a9..23db13f 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -80,7 +80,6 @@
     '//lib/gwt:user',
     '//lib/gwt:dev',
   ],
-  source_under_test = [':SafeHtml'],
 )
 
 gwt_module(
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index a8ea06f..4485430 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -66,7 +66,6 @@
     '//lib/gwt:user',
     '//lib/jgit/org.eclipse.jgit:jgit',
   ],
-  source_under_test = [':client'],
   vm_args = ['-Xmx512m'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gwtui-common/BUILD b/gerrit-gwtui-common/BUILD
new file mode 100644
index 0000000..01a82af
--- /dev/null
+++ b/gerrit-gwtui-common/BUILD
@@ -0,0 +1,69 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+load('//tools/bzl:java.bzl', 'java_library2')
+load('//tools/bzl:junit.bzl', 'junit_tests')
+load('//tools/bzl:gwt.bzl', 'gwt_module')
+
+EXPORTED_DEPS = [
+  '//gerrit-common:client',
+  '//gerrit-gwtexpui:Clippy',
+  '//gerrit-gwtexpui:GlobalKey',
+  '//gerrit-gwtexpui:Progress',
+  '//gerrit-gwtexpui:SafeHtml',
+  '//gerrit-gwtexpui:UserAgent',
+]
+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',
+  srcs = glob(['src/main/**/*.java']),
+  gwt_xml = SRC + 'GerritGwtUICommon.gwt.xml',
+  resources = glob(
+      ['src/main/**/*'],
+      exclude = [SRC + 'client/**/*.java'] +
+      [SRC + 'GerritGwtUICommon.gwt.xml']
+  ),
+  exported_deps = EXPORTED_DEPS,
+  deps = DEPS,
+  visibility = ['//visibility:public'],
+)
+
+java_library2(
+  name = 'client-lib',
+  srcs = glob(['src/main/**/*.java']),
+  resources = glob(['src/main/**/*']),
+  exported_deps = EXPORTED_DEPS,
+  deps = DEPS,
+  visibility = ['//visibility:public'],
+)
+
+java_import(
+  name = 'diffy_logo',
+  jars = [':diffy_image_files_ln'],
+  visibility = ['//visibility:public'],
+)
+
+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,
+)
+
+junit_tests(
+  name = 'client_tests',
+  srcs = glob(['src/test/java/**/*.java']),
+  deps = [
+    ':client',
+    '//lib:junit',
+    '//lib/gwt:dev',
+    '//lib/jgit/org.eclipse.jgit:jgit',
+  ],
+  visibility = ['//visibility:public'],
+)
diff --git a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
index 750412d..a111896 100644
--- a/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
+++ b/gerrit-gwtui-common/src/main/java/com/google/gerrit/client/info/GerritInfo.java
@@ -14,8 +14,13 @@
 
 package com.google.gerrit.client.info;
 
+import com.google.gerrit.extensions.client.UiType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.JavaScriptObject;
+import com.google.gwt.core.client.JsArrayString;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class GerritInfo extends JavaScriptObject {
   public final Project.NameKey allProjectsNameKey() {
@@ -42,6 +47,19 @@
   public final native String reportBugUrl() /*-{ return this.report_bug_url; }-*/;
   public final native String reportBugText() /*-{ return this.report_bug_text; }-*/;
 
+  private native JsArrayString _webUis() /*-{ return this.web_uis; }-*/;
+  public final List<UiType> webUis() {
+    JsArrayString webUis = _webUis();
+    List<UiType> result = new ArrayList<>(webUis.length());
+    for (int i = 0; i < webUis.length(); i++) {
+      UiType t = UiType.parse(webUis.get(i));
+      if (t != null) {
+        result.add(t);
+      }
+    }
+    return result;
+  }
+
   protected GerritInfo() {
   }
 }
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 3f87388..3ab6187 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -62,7 +62,6 @@
     '//lib/gwt:dev',
     '//lib/gwt:user',
   ],
-  source_under_test = [':ui_module'],
   vm_args = ['-Xmx512m'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-gwtui/BUILD b/gerrit-gwtui/BUILD
new file mode 100644
index 0000000..dbf02e8
--- /dev/null
+++ b/gerrit-gwtui/BUILD
@@ -0,0 +1,89 @@
+load('//tools/bzl:gwt.bzl', 'gwt_module')
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+load(':gwt.bzl', 'gwt_binary')
+
+MODULE = 'com.google.gerrit.GerritGwtUI'
+
+GWT_JVM_ARGS = ['-Xmx512m']
+
+GWT_COMPILER_ARGS = [
+  '-XdisableClassMetadata',
+]
+
+GWT_COMPILER_ARGS_RELEASE_MODE = GWT_COMPILER_ARGS + [
+  '-XdisableCastChecking',
+]
+
+GWT_TRANSITIVE_DEPS = [
+  '//lib/gwt:javax-validation',
+  '//lib/gwt:javax-validation_src',
+  '//lib/ow2:ow2-asm',
+  '//lib/ow2:ow2-asm-analysis',
+  '//lib/ow2:ow2-asm-commons',
+  '//lib/ow2:ow2-asm-tree',
+  '//lib/ow2:ow2-asm-util',
+]
+
+DEPS = GWT_TRANSITIVE_DEPS + [
+  '//gerrit-gwtexpui:CSS',
+  '//lib:gwtjsonrpc',
+  '//lib/gwt:dev',
+  '@jgit_src//file',
+]
+
+gwt_module(
+  name = 'ui_module',
+  srcs = glob(['src/main/java/**/*.java']),
+  gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
+  resources = glob(
+      ['src/main/java/**/*'],
+      exclude = ['src/main/java/**/*.java'] +
+      ['src/main/java/%s.gwt.xml' % MODULE.replace('.', '/')]
+  ),
+  deps = [
+    '//gerrit-gwtui-common:diffy_logo',
+    '//gerrit-gwtui-common:client',
+    '//gerrit-gwtexpui:CSS',
+    '//lib/codemirror:codemirror',
+    '//lib/gwt:user',
+  ],
+  visibility = ['//visibility:public'],
+)
+
+genrule2(
+  name = 'ui_optdbg',
+  srcs = [
+    ':ui_dbg',
+    ':ui_opt',
+  ],
+  cmd = 'cd $$TMP;' +
+    'unzip -q $$ROOT/$(location :ui_dbg);' +
+    'mv' +
+    ' gerrit_ui/gerrit_ui.nocache.js' +
+    ' gerrit_ui/dbg_gerrit_ui.nocache.js;' +
+    'unzip -qo $$ROOT/$(location :ui_opt);' +
+    'mkdir -p $$(dirname $@);' +
+    'zip -qr $$ROOT/$@ .',
+  out = 'ui_optdbg.zip',
+  visibility = ['//visibility:public'],
+)
+
+gwt_binary(
+  name = 'ui_opt',
+  modules = [MODULE],
+  module_deps = [':ui_module'],
+  deps = DEPS,
+  compiler_args = GWT_COMPILER_ARGS,
+  jvm_args = GWT_JVM_ARGS,
+)
+
+gwt_binary(
+  name = 'ui_dbg',
+  modules = [MODULE],
+  style = 'PRETTY',
+  optimize = "0",
+  module_deps = [':ui_module'],
+  deps = DEPS,
+  compiler_args = GWT_COMPILER_ARGS,
+  jvm_args = GWT_JVM_ARGS,
+)
diff --git a/gerrit-gwtui/gwt.bzl b/gerrit-gwtui/gwt.bzl
new file mode 100644
index 0000000..91936de
--- /dev/null
+++ b/gerrit-gwtui/gwt.bzl
@@ -0,0 +1,95 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Port of Buck native gwt_binary() rule. See discussion in context of
+# https://github.com/facebook/buck/issues/109
+
+jar_filetype = FileType(['.jar'])
+GWT_COMPILER = "com.google.gwt.dev.Compiler"
+
+def _impl(ctx):
+  output_zip = ctx.outputs.output
+  output_dir = output_zip.path + '.gwt_output'
+  deploy_dir = output_zip.path + '.gwt_deploy'
+
+  deps = _get_transitive_closure(ctx)
+
+  paths = []
+  for dep in deps:
+    paths.append(dep.path)
+
+  cmd = "external/local_jdk/bin/java %s -Dgwt.normalizeTimestamps=true -cp %s %s -war %s -deploy %s " % (
+    " ".join(ctx.attr.jvm_args),
+    ":".join(paths),
+    GWT_COMPILER,
+    output_dir,
+    deploy_dir,
+  )
+  cmd += " ".join([
+    "-style %s" % ctx.attr.style,
+    "-optimize %s" % ctx.attr.optimize,
+    "-strict",
+    " ".join(ctx.attr.compiler_args),
+    " ".join(ctx.attr.modules) + "\n",
+    "rm -rf %s/gwt-unitCache\n" % output_dir,
+    "root=`pwd`\n",
+    "cd %s; $root/%s Cc ../%s $(find .)\n" % (
+      output_dir,
+      ctx.executable._zip.path,
+      output_zip.basename,
+    )
+  ])
+
+  ctx.action(
+    inputs = list(deps) + ctx.files._jdk + ctx.files._zip,
+    outputs = [output_zip],
+    mnemonic = "GwtBinary",
+    progress_message = "GWT compiling " + output_zip.short_path,
+    command = "set -e\n" + cmd,
+  )
+
+def _get_transitive_closure(ctx):
+  deps = set()
+  for dep in ctx.attr.module_deps:
+    deps += dep.java.transitive_runtime_deps
+    deps += dep.java.transitive_source_jars
+  for dep in ctx.attr.deps:
+    if hasattr(dep, 'java'):
+      deps += dep.java.transitive_runtime_deps
+    elif hasattr(dep, 'files'):
+      deps += dep.files
+
+  return deps
+
+gwt_binary = rule(
+  implementation = _impl,
+  attrs = {
+    "style": attr.string(default = "OBF"),
+    "optimize": attr.string(default = "9"),
+    "deps": attr.label_list(allow_files=jar_filetype),
+    "modules": attr.string_list(mandatory=True),
+    "module_deps": attr.label_list(allow_files=jar_filetype),
+    "compiler_args": attr.string_list(),
+    "jvm_args": attr.string_list(),
+    "_jdk": attr.label(
+      default=Label("//tools/defaults:jdk")),
+    "_zip": attr.label(
+      default=Label("@bazel_tools//tools/zip:zipper"),
+      executable=True,
+      single_file=True),
+  },
+  outputs = {
+    "output": "%{name}.zip",
+  },
+)
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
index dd1505c..3f0daa2 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/FormatUtil.java
@@ -17,7 +17,6 @@
 import com.google.gerrit.client.change.Resources;
 import com.google.gerrit.client.info.AccountInfo;
 import com.google.gerrit.client.info.GeneralPreferences;
-import com.google.gerrit.reviewdb.client.Account;
 import com.google.gwt.i18n.client.NumberFormat;
 
 import java.util.Date;
@@ -84,17 +83,6 @@
     return createAccountFormatter().name(info);
   }
 
-  public static AccountInfo asInfo(Account acct) {
-    if (acct == null) {
-      return AccountInfo.create(0, null, null, null);
-    }
-    return AccountInfo.create(
-        acct.getId() != null ? acct.getId().get() : 0,
-        acct.getFullName(),
-        acct.getPreferredEmail(),
-        acct.getUserName());
-  }
-
   public static AccountInfo asInfo(com.google.gerrit.common.data.AccountInfo acct) {
     if (acct == null) {
       return AccountInfo.create(0, null, null, null);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index d280e07..93246cb 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -50,6 +50,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GerritTopMenu;
+import com.google.gerrit.extensions.client.UiType;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.aria.client.Roles;
 import com.google.gwt.core.client.EntryPoint;
@@ -537,6 +538,14 @@
 
     btmmenu.add(new InlineHTML(M.poweredBy(vs)));
 
+    if (info().gerrit().webUis().contains(UiType.POLYGERRIT)) {
+      btmmenu.add(new InlineLabel(" | "));
+      Anchor a = new Anchor(
+          C.polyGerrit(), GWT.getHostPageBaseURL() + "?polygerrit=1");
+      a.setStyleName("");
+      btmmenu.add(a);
+    }
+
     String reportBugUrl = info().gerrit().reportBugUrl();
     if (reportBugUrl != null) {
       String reportBugText = info().gerrit().reportBugText();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index 4c8c58d..53d9260 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -130,4 +130,6 @@
 
   String searchDropdownChanges();
   String searchDropdownDoc();
+
+  String polyGerrit();
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 10d7e1d..d50ab34 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -113,3 +113,5 @@
 
 searchDropdownChanges = Changes
 searchDropdownDoc = Docs
+
+polyGerrit = PolyGerrit
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 3290aac..5e7b873 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
@@ -127,6 +127,7 @@
 	createSignedTag, \
 	delete, \
 	deleteDrafts, \
+	editAssignee, \
 	editHashtags, \
 	editTopicName, \
 	forgeAuthor, \
@@ -150,6 +151,7 @@
 createSignedTag = Create Signed Tag
 delete = Delete Reference
 deleteDrafts = Delete Drafts
+editAssignee = Edit Assignee
 editHashtags = Edit Hashtags
 editTopicName = Edit Topic Name
 forgeAuthor = Forge Author Identity
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index d52963a..0b0499c 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -73,7 +73,6 @@
     '//lib/jgit/org.eclipse.jgit.junit:junit',
     '//lib/joda:joda-time',
   ],
-  source_under_test = [':httpd'],
   # TODO(sop) Remove after Buck supports Eclipse
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 3941657..8d8937c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gwtexpui.server.CacheControlFilter;
 import com.google.inject.Key;
 import com.google.inject.Provider;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
index 3e3b7c4..fa551e8 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/WebModule.java
@@ -25,6 +25,7 @@
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.config.AuthConfig;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gerrit.server.config.GerritRequestModule;
 import com.google.gerrit.server.config.GitwebCgiConfig;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
index fe556ac..ac7c7e7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitwebServlet.java
@@ -609,45 +609,39 @@
       final OutputStream dst) throws IOException {
     final int contentLength = req.getContentLength();
     final InputStream src = req.getInputStream();
-    new Thread(new Runnable() {
-      @Override
-      public void run() {
+    new Thread(() -> {
+      try {
         try {
-          try {
-            final byte[] buf = new byte[bufferSize];
-            int remaining = contentLength;
-            while (0 < remaining) {
-              final int max = Math.max(buf.length, remaining);
-              final int n = src.read(buf, 0, max);
-              if (n < 0) {
-                throw new EOFException("Expected " + remaining + " more bytes");
-              }
-              dst.write(buf, 0, n);
-              remaining -= n;
+          final byte[] buf = new byte[bufferSize];
+          int remaining = contentLength;
+          while (0 < remaining) {
+            final int max = Math.max(buf.length, remaining);
+            final int n = src.read(buf, 0, max);
+            if (n < 0) {
+              throw new EOFException("Expected " + remaining + " more bytes");
             }
-          } finally {
-            dst.close();
+            dst.write(buf, 0, n);
+            remaining -= n;
           }
-        } catch (IOException e) {
-          log.debug("Unexpected error copying input to CGI", e);
+        } finally {
+          dst.close();
         }
+      } catch (IOException e) {
+        log.debug("Unexpected error copying input to CGI", e);
       }
     }, "Gitweb-InputFeeder").start();
   }
 
   private void copyStderrToLog(final InputStream in) {
-    new Thread(new Runnable() {
-      @Override
-      public void run() {
-        try (BufferedReader br =
-            new BufferedReader(new InputStreamReader(in, ISO_8859_1.name()))) {
-          String line;
-          while ((line = br.readLine()) != null) {
-            log.error("CGI: " + line);
-          }
-        } catch (IOException e) {
-          log.debug("Unexpected error copying stderr from CGI", e);
+    new Thread(() -> {
+      try (BufferedReader br =
+          new BufferedReader(new InputStreamReader(in, ISO_8859_1.name()))) {
+        String line;
+        while ((line = br.readLine()) != null) {
+          log.error("CGI: " + line);
         }
+      } catch (IOException e) {
+        log.debug("Unexpected error copying stderr from CGI", e);
       }
     }, "Gitweb-ErrorLogger").start();
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 27aff21..cd70143 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -18,14 +18,13 @@
 import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CHARACTER_ENCODING;
 import static com.google.gerrit.server.plugins.PluginEntry.ATTR_CONTENT_TYPE;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.cache.Cache;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.io.ByteStreams;
@@ -74,6 +73,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
+import java.util.function.Predicate;
 import java.util.jar.Attributes;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -378,34 +378,30 @@
     List<PluginEntry> docs = new ArrayList<>();
     PluginEntry about = null;
 
-    Predicate<PluginEntry> filter = new Predicate<PluginEntry>() {
-      @Override
-      public boolean apply(PluginEntry entry) {
-        String name = entry.getName();
-        Optional<Long> size = entry.getSize();
-        if (name.startsWith(prefix)
-            && (name.endsWith(".md") || name.endsWith(".html"))
-            && size.isPresent()) {
-          if (size.get() <= 0 || size.get() > SMALL_RESOURCE) {
-            log.warn(String.format(
-                "Plugin %s: %s omitted from document index. "
-                  + "Size %d out of range (0,%d).",
-                pluginName,
-                name.substring(prefix.length()),
-                size.get(),
-                SMALL_RESOURCE));
-            return false;
+    Predicate<PluginEntry> filter =
+        entry -> {
+          String name = entry.getName();
+          Optional<Long> size = entry.getSize();
+          if (name.startsWith(prefix)
+              && (name.endsWith(".md") || name.endsWith(".html"))
+              && size.isPresent()) {
+            if (size.get() <= 0 || size.get() > SMALL_RESOURCE) {
+              log.warn(String.format(
+                  "Plugin %s: %s omitted from document index. "
+                    + "Size %d out of range (0,%d).",
+                  pluginName,
+                  name.substring(prefix.length()),
+                  size.get(),
+                  SMALL_RESOURCE));
+              return false;
+            }
+            return true;
           }
-          return true;
-        }
-        return false;
-      }
-    };
+          return false;
+        };
 
-    List<PluginEntry> entries = FluentIterable
-        .from(Collections.list(scanner.entries()))
-        .filter(filter)
-        .toList();
+    List<PluginEntry> entries = Collections.list(scanner.entries()).stream()
+        .filter(filter).collect(toList());
     for (PluginEntry entry: entries) {
       String name = entry.getName().substring(prefix.length());
       if (name.startsWith("cmd-")) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
index 4f07ac2..c35738b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/ResourceServlet.java
@@ -294,17 +294,14 @@
   }
 
   private Callable<Resource> newLoader(final Path p) {
-    return new Callable<Resource>() {
-      @Override
-      public Resource call() throws IOException {
-        try {
-          return new Resource(
-              getLastModifiedTime(p),
-              contentType(p.toString()),
-              Files.readAllBytes(p));
-        } catch (NoSuchFileException e) {
-          return Resource.NOT_FOUND;
-        }
+    return () -> {
+      try {
+        return new Resource(
+            getLastModifiedTime(p),
+            contentType(p.toString()),
+            Files.readAllBytes(p));
+      } catch (NoSuchFileException e) {
+        return Resource.NOT_FOUND;
       }
     };
   }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java
index d3d8773..31e337e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/StaticModule.java
@@ -19,15 +19,14 @@
 import static java.nio.file.Files.exists;
 import static java.nio.file.Files.isReadable;
 
-import com.google.common.base.Enums;
 import com.google.common.cache.Cache;
 import com.google.common.collect.ImmutableList;
-import com.google.gerrit.httpd.GerritOptions;
-import com.google.gerrit.httpd.GerritOptions.UiPreference;
+import com.google.gerrit.extensions.client.UiType;
 import com.google.gerrit.httpd.XsrfCookieFilter;
 import com.google.gerrit.httpd.raw.ResourceServlet.Resource;
 import com.google.gerrit.launcher.GerritLauncher;
 import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.inject.Inject;
@@ -464,7 +463,10 @@
         FilterChain chain) throws IOException, ServletException {
       HttpServletRequest req = (HttpServletRequest) request;
       HttpServletResponse res = (HttpServletResponse) response;
-      if (!isPolyGerritEnabled(req, res)) {
+      if (handlePolyGerritParam(req, res)) {
+        return;
+      }
+      if (!isPolyGerritEnabled(req)) {
         chain.doFilter(req, res);
         return;
       }
@@ -507,41 +509,57 @@
       return uri.startsWith(ctx) ? uri.substring(ctx.length()) : uri;
     }
 
-    private boolean isPolyGerritEnabled(HttpServletRequest req,
-        HttpServletResponse res) {
+    private boolean handlePolyGerritParam(HttpServletRequest req,
+        HttpServletResponse res) throws IOException {
       if (!options.enableGwtUi()) {
-        return true;
+        return false;
       }
+      boolean redirect = false;
       String param = req.getParameter("polygerrit");
       if ("1".equals(param)) {
-        return setPolyGerritCookie(req, res, UiPreference.POLYGERRIT);
+        setPolyGerritCookie(req, res, UiType.POLYGERRIT);
+        redirect = true;
       } else if ("0".equals(param)) {
-        return setPolyGerritCookie(req, res, UiPreference.GWT);
-      } else {
-        return isPolyGerritCookie(req);
+        setPolyGerritCookie(req, res, UiType.GWT);
+        redirect = true;
       }
+      if (redirect) {
+        // Strip polygerrit param from URL. This actually strips all params,
+        // which is a similar behavior to the JS PolyGerrit redirector code.
+        // Stripping just one param is frustratingly difficult without the use
+        // of Apache httpclient, which is a dep we don't want here:
+        // https://gerrit-review.googlesource.com/#/c/57570/57/gerrit-httpd/BUCK@32
+        res.sendRedirect(req.getRequestURL().toString());
+      }
+      return redirect;
+    }
+
+    private boolean isPolyGerritEnabled(HttpServletRequest req) {
+      return !options.enableGwtUi() || isPolyGerritCookie(req);
     }
 
     private boolean isPolyGerritCookie(HttpServletRequest req) {
-      UiPreference pref = options.defaultUi();
+      UiType type = options.defaultUi();
       Cookie[] all = req.getCookies();
       if (all != null) {
         for (Cookie c : all) {
           if (GERRIT_UI_COOKIE.equals(c.getName())) {
-            String v = c.getValue().toUpperCase();
-            pref = Enums.getIfPresent(UiPreference.class, v).or(pref);
-            break;
+            UiType t = UiType.parse(c.getValue());
+            if (t != null) {
+              type = t;
+              break;
+            }
           }
         }
       }
-      return pref == UiPreference.POLYGERRIT;
+      return type == UiType.POLYGERRIT;
     }
 
-    private boolean setPolyGerritCookie(HttpServletRequest req,
-        HttpServletResponse res, UiPreference pref) {
+    private void setPolyGerritCookie(HttpServletRequest req,
+        HttpServletResponse res, UiType pref) {
       // Only actually set a cookie if both UIs are enabled in the server;
       // otherwise clear it.
-      Cookie cookie = new Cookie(GERRIT_UI_COOKIE, pref.name().toLowerCase());
+      Cookie cookie = new Cookie(GERRIT_UI_COOKIE, pref.name());
       if (options.enablePolyGerrit() && options.enableGwtUi()) {
         cookie.setPath("/");
         cookie.setSecure(isSecure(req));
@@ -551,7 +569,6 @@
         cookie.setMaxAge(0);
       }
       res.addCookie(cookie);
-      return pref == UiPreference.POLYGERRIT;
     }
 
     private static boolean isSecure(HttpServletRequest req) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
index e3f3fb1..c1a3eec 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/RestApiServlet.java
@@ -26,6 +26,7 @@
 import static java.math.RoundingMode.CEILING;
 import static java.nio.charset.StandardCharsets.ISO_8859_1;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.joining;
 import static javax.servlet.http.HttpServletResponse.SC_ACCEPTED;
 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
 import static javax.servlet.http.HttpServletResponse.SC_CONFLICT;
@@ -41,9 +42,7 @@
 import static javax.servlet.http.HttpServletResponse.SC_PRECONDITION_FAILED;
 
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
-import com.google.common.base.Predicates;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
@@ -145,6 +144,7 @@
 import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
+import java.util.stream.StreamSupport;
 import java.util.zip.GZIPOutputStream;
 
 import javax.servlet.ServletException;
@@ -497,11 +497,13 @@
     String headers = req.getHeader(ACCESS_CONTROL_REQUEST_HEADERS);
     if (headers != null) {
       res.addHeader(VARY, ACCESS_CONTROL_REQUEST_HEADERS);
-      String badHeader = Iterables.getFirst(
-          Iterables.filter(
-              Splitter.on(',').trimResults().split(headers),
-              Predicates.not(Predicates.in(ALLOWED_CORS_REQUEST_HEADERS))),
-          null);
+      String badHeader =
+          StreamSupport.stream(
+                  Splitter.on(',').trimResults().split(headers).spliterator(),
+                  false)
+              .filter(h -> !ALLOWED_CORS_REQUEST_HEADERS.contains(h))
+              .findFirst()
+              .orElse(null);
       if (badHeader != null) {
         throw new BadRequestException(badHeader + " not allowed in CORS");
       }
@@ -1034,16 +1036,12 @@
     } else if (r.isEmpty()) {
       throw new ResourceNotFoundException(projection);
     } else {
-      throw new AmbiguousViewException(String.format(
-        "Projection %s is ambiguous: %s",
-        name,
-        Joiner.on(", ").join(
-          Iterables.transform(r.keySet(), new Function<String, String>() {
-            @Override
-            public String apply(String in) {
-              return in + "~" + projection;
-            }
-          }))));
+      throw new AmbiguousViewException(
+          String.format(
+              "Projection %s is ambiguous: %s",
+              name,
+              r.keySet().stream().map(in -> in + "~" + projection)
+                  .collect(joining(", "))));
     }
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
index ed2a4f9..bd88e6a 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ProjectAccessFactory.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.httpd.rpc.project;
 
-import com.google.common.base.Predicate;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.AccessSection;
@@ -238,14 +237,7 @@
         }
       }
     }
-    return Maps.filterEntries(
-      infos,
-      new Predicate<Map.Entry<AccountGroup.UUID, GroupInfo>>() {
-        @Override
-        public boolean apply(Map.Entry<AccountGroup.UUID, GroupInfo> in) {
-          return in.getValue() != null;
-        }
-      });
+    return Maps.filterEntries(infos, in -> in.getValue() != null);
   }
 
   private ProjectControl open() throws NoSuchProjectException {
diff --git a/gerrit-main/BUILD b/gerrit-main/BUILD
new file mode 100644
index 0000000..67863c5
--- /dev/null
+++ b/gerrit-main/BUILD
@@ -0,0 +1,13 @@
+java_binary(
+  name = 'main_bin',
+  main_class = 'Main',
+  runtime_deps = [':main_lib'],
+  visibility = ['//visibility:public'],
+)
+
+java_library(
+  name = 'main_lib',
+  srcs = ['src/main/java/Main.java'],
+  deps = ['//gerrit-launcher:launcher'],
+  visibility = ['//visibility:public'],
+)
diff --git a/gerrit-patch-jgit/BUCK b/gerrit-patch-jgit/BUCK
index 09ccf9c..4a4929e 100644
--- a/gerrit-patch-jgit/BUCK
+++ b/gerrit-patch-jgit/BUCK
@@ -33,7 +33,7 @@
     'org/eclipse/jgit/diff/Edit.java;' +
     'cd $TMP;' +
     'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java',
-  out = 'edit.src.zip',
+  out = 'edit-sources.jar',
 )
 
 java_library(
@@ -61,6 +61,5 @@
     '//lib/jgit/org.eclipse.jgit:jgit',
     '//lib:junit',
   ],
-  source_under_test = [':server'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 4be941c..8852133 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -180,5 +180,4 @@
     '//lib/jgit/org.eclipse.jgit:jgit',
     '//lib/jgit/org.eclipse.jgit.junit:junit',
   ],
-  source_under_test = [':pgm'],
 )
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 d98f999..078fdaf 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
@@ -23,7 +23,6 @@
 import com.google.gerrit.extensions.client.AuthType;
 import com.google.gerrit.gpg.GpgModule;
 import com.google.gerrit.httpd.AllRequestFilter;
-import com.google.gerrit.httpd.GerritOptions;
 import com.google.gerrit.httpd.GetUserFilter;
 import com.google.gerrit.httpd.GitOverHttpModule;
 import com.google.gerrit.httpd.H2CacheBasedWebSession;
@@ -56,6 +55,7 @@
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.DownloadConfig;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.RestCacheAdminModule;
 import com.google.gerrit.server.events.StreamEventsApiListener;
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
index 285c8a4..9d4becc 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/InitStep.java
@@ -19,5 +19,5 @@
   void run() throws Exception;
 
   /** Executed after the site has been initialized */
-  default public void postRun() throws Exception {}
+  default void postRun() throws Exception {}
 }
diff --git a/gerrit-plugin-api/BUILD b/gerrit-plugin-api/BUILD
index c761703..9404acc 100644
--- a/gerrit-plugin-api/BUILD
+++ b/gerrit-plugin-api/BUILD
@@ -58,3 +58,38 @@
   ],
   visibility = ['//visibility:public'],
 )
+
+java_binary(
+  name = 'plugin-api-sources',
+  main_class = 'Dummy',
+  runtime_deps = [
+    '//gerrit-antlr:libquery_exception-src.jar',
+    '//gerrit-antlr:libquery_parser-src.jar',
+    '//gerrit-common:libannotations-src.jar',
+    '//gerrit-extension-api:libapi-src.jar',
+    '//gerrit-gwtexpui:libserver-src.jar',
+    '//gerrit-httpd:libhttpd-src.jar',
+    '//gerrit-pgm:libinit-api-src.jar',
+    '//gerrit-reviewdb:libserver-src.jar',
+    '//gerrit-server:libserver-src.jar',
+    '//gerrit-sshd:libsshd-src.jar',
+  ],
+  visibility = ['//visibility:public'],
+)
+
+load('//tools/bzl:javadoc.bzl', 'java_doc')
+
+java_doc(
+  name = 'plugin-api-javadoc',
+  title = 'Gerrit Review Plugin API Documentation',
+  pkgs = ['com.google.gerrit'],
+  libs = PLUGIN_API + [
+    '//gerrit-antlr:query_exception',
+    '//gerrit-antlr:query_parser',
+    '//gerrit-common:annotations',
+    '//gerrit-common:server',
+    '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:server',
+    '//gerrit-reviewdb:server',
+  ],
+)
diff --git a/gerrit-reviewdb/BUCK b/gerrit-reviewdb/BUCK
index 82e0135..a5fb1f5 100644
--- a/gerrit-reviewdb/BUCK
+++ b/gerrit-reviewdb/BUCK
@@ -33,6 +33,5 @@
     '//lib:gwtorm',
     '//lib:truth',
   ],
-  source_under_test = [':client'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
index 42d0993..7e2a9b0 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/server/ReviewDbUtil.java
@@ -14,41 +14,37 @@
 
 package com.google.gerrit.reviewdb.server;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Ordering;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gwtorm.client.IntKey;
 
 /** Static utilities for ReviewDb types. */
 public class ReviewDbUtil {
-  public static final Function<IntKey<?>, Integer> INT_KEY_FUNCTION =
-      new Function<IntKey<?>, Integer>() {
-        @Override
-        public Integer apply(IntKey<?> in) {
-          return in.get();
-        }
-      };
-
-  private static final Function<Change, Change.Id> CHANGE_ID_FUNCTION =
-      new Function<Change, Change.Id>() {
-        @Override
-        public Change.Id apply(Change in) {
-          return in.getId();
-        }
-      };
-
   private static final Ordering<? extends IntKey<?>> INT_KEY_ORDERING =
-      Ordering.natural().nullsFirst().onResultOf(INT_KEY_FUNCTION).nullsFirst();
+      Ordering.natural()
+          .nullsFirst()
+          .<IntKey<?>>onResultOf(IntKey::get)
+          .nullsFirst();
 
+  /**
+   * Null-safe ordering over arbitrary subclass of {@code IntKey}.
+   * <p>
+   * In some cases, {@code Comparator.comparing(Change.Id::get)} may be shorter
+   * and cleaner. However, this method may be preferable in some cases:
+   * <ul>
+   * <li>This ordering is null-safe over both input and the result of {@link
+   *   IntKey#get()}; {@code comparing} is only a good idea if all inputs are
+   *   obviously non-null.</li>
+   * <li>{@code intKeyOrdering().sortedCopy(iterable)} is shorter than the
+   *   stream equivalent.</li>
+   * <li>Creating derived comparators may be more readable with {@link Ordering}
+   *   method chaining rather than static {@code Comparator} methods.
+   * </ul>
+   */
   @SuppressWarnings("unchecked")
   public static <K extends IntKey<?>> Ordering<K> intKeyOrdering() {
     return (Ordering<K>) INT_KEY_ORDERING;
   }
 
-  public static Function<Change, Change.Id> changeIdFunction() {
-    return CHANGE_ID_FUNCTION;
-  }
-
   public static ReviewDb unwrapDb(ReviewDb db) {
     if (db instanceof DisabledChangesReviewDbWrapper) {
       return ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 080b52b..66fc545 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -181,7 +181,6 @@
     '//gerrit-server/src/main/prolog:common',
     '//lib/antlr:java_runtime',
   ],
-  source_under_test = [':server'],
 )
 
 java_test(
@@ -208,6 +207,5 @@
     '//lib/guice:guice-assistedinject',
     '//lib/prolog:runtime',
   ],
-  source_under_test = [':server'],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-server/BUILD b/gerrit-server/BUILD
index a591fba..6c7ab3e 100644
--- a/gerrit-server/BUILD
+++ b/gerrit-server/BUILD
@@ -208,3 +208,12 @@
   ],
   visibility = ['//visibility:public'],
 )
+
+load('//tools/bzl:javadoc.bzl', 'java_doc')
+
+java_doc(
+  name = 'doc',
+  title = 'Gerrit Review Server Documentation',
+  libs = [':server'],
+  pkgs = ['com.google.gerrit'],
+)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java
index 364f4f8..95fbf04 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/Field.java
@@ -161,17 +161,10 @@
   private static <T> Function<T, String> initFormatter(Class<T> keyType) {
     if (keyType == String.class) {
       return (Function<T, String>) Functions.<String> identity();
-
     } else if (keyType == Integer.class || keyType == Boolean.class) {
       return (Function<T, String>) Functions.toStringFunction();
-
     } else if (Enum.class.isAssignableFrom(keyType)) {
-      return new Function<T, String>() {
-        @Override
-        public String apply(T in) {
-          return ((Enum<?>) in).name();
-        }
-      };
+      return in -> ((Enum<?>) in).name();
     }
     throw new IllegalStateException("unsupported type " + keyType.getName());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
index e7ab75c..d3fe6ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCallback.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.metrics.dropwizard;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.metrics.Description;
@@ -124,14 +123,7 @@
 
   @Override
   public Map<Object, Metric> getCells() {
-    return Maps.transformValues(
-        cells,
-        new Function<ValueGauge, Metric> () {
-          @Override
-          public Metric apply(ValueGauge in) {
-            return in;
-          }
-        });
+    return Maps.transformValues(cells, in -> (Metric) in);
   }
 
   final class ValueGauge implements Gauge<V> {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java
index 10b92e6..7894a84 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedCounter.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.metrics.dropwizard;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.metrics.Description;
@@ -98,13 +97,6 @@
 
   @Override
   public Map<Object, Metric> getCells() {
-    return Maps.transformValues(
-        cells,
-        new Function<CounterImpl, Metric> () {
-          @Override
-          public Metric apply(CounterImpl in) {
-            return in.metric;
-          }
-        });
+    return Maps.transformValues(cells, c -> c.metric);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java
index 071c678..ff38cd4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedHistogram.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.metrics.dropwizard;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.metrics.Description;
@@ -96,13 +95,6 @@
 
   @Override
   public Map<Object, Metric> getCells() {
-    return Maps.transformValues(
-        cells,
-        new Function<HistogramImpl, Metric> () {
-          @Override
-          public Metric apply(HistogramImpl in) {
-            return in.metric;
-          }
-        });
+    return Maps.transformValues(cells, h -> h.metric);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
index 6981ef1..aff6c4a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/BucketedTimer.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.metrics.dropwizard;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Maps;
 import com.google.gerrit.metrics.Description;
@@ -96,13 +95,6 @@
 
   @Override
   public Map<Object, Metric> getCells() {
-    return Maps.transformValues(
-        cells,
-        new Function<TimerImpl, Metric> () {
-          @Override
-          public Metric apply(TimerImpl in) {
-            return in.metric;
-          }
-        });
+    return Maps.transformValues(cells, t -> t.metric);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
index e159c82..ee2ce29 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/metrics/dropwizard/DropWizardMetricMaker.java
@@ -18,7 +18,6 @@
 import static com.google.gerrit.metrics.dropwizard.MetricResource.METRIC_KIND;
 import static com.google.gerrit.server.config.ConfigResource.CONFIG_KIND;
 
-import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
@@ -304,14 +303,8 @@
   @Override
   public synchronized RegistrationHandle newTrigger(
       Set<CallbackMetric<?>> metrics, Runnable trigger) {
-    final ImmutableSet<CallbackMetricGlue> all = FluentIterable.from(metrics)
-        .transform(
-          new Function<CallbackMetric<?>, CallbackMetricGlue>() {
-            @Override
-            public CallbackMetricGlue apply(CallbackMetric<?> input) {
-              return (CallbackMetricGlue) input;
-            }
-          })
+    ImmutableSet<CallbackMetricGlue> all = FluentIterable.from(metrics)
+        .transform(m -> (CallbackMetricGlue) m)
         .toSet();
 
     trigger = new CallbackGroup(trigger, all);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 5c0723a..97af09e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -16,10 +16,9 @@
 
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
+import static java.util.Comparator.comparing;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.Iterables;
@@ -52,7 +51,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
@@ -81,14 +79,7 @@
       LoggerFactory.getLogger(ApprovalsUtil.class);
 
   private static final Ordering<PatchSetApproval> SORT_APPROVALS =
-      Ordering.natural()
-          .onResultOf(
-              new Function<PatchSetApproval, Timestamp>() {
-                @Override
-                public Timestamp apply(PatchSetApproval a) {
-                  return a.getGranted();
-                }
-              });
+      Ordering.from(comparing(PatchSetApproval::getGranted));
 
   public static List<PatchSetApproval> sortApprovals(
       Iterable<PatchSetApproval> approvals) {
@@ -97,12 +88,8 @@
 
   private static Iterable<PatchSetApproval> filterApprovals(
       Iterable<PatchSetApproval> psas, final Account.Id accountId) {
-    return Iterables.filter(psas, new Predicate<PatchSetApproval>() {
-      @Override
-      public boolean apply(PatchSetApproval input) {
-        return Objects.equals(input.getAccountId(), accountId);
-      }
-    });
+    return Iterables.filter(
+        psas, a -> Objects.equals(a.getAccountId(), accountId));
   }
 
   private final NotesMigration migration;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
index 603f528..732a9b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/PatchLineCommentsUtil.java
@@ -16,11 +16,10 @@
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ComparisonChain;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
@@ -60,6 +59,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
+import java.util.stream.StreamSupport;
 
 /**
  * Utility functions to manipulate PatchLineComments.
@@ -178,13 +178,7 @@
       ResultSet<PatchLineComment> comments,
       final PatchLineComment.Status status) {
     return Lists.newArrayList(
-      Iterables.filter(comments, new Predicate<PatchLineComment>() {
-        @Override
-        public boolean apply(PatchLineComment input) {
-          return (input.getStatus() == status);
-        }
-      })
-    );
+      Iterables.filter(comments, c -> c.getStatus() == status));
   }
 
   public List<PatchLineComment> byPatchSet(ReviewDb db,
@@ -251,16 +245,11 @@
       throws OrmException {
     if (!migration.readChanges()) {
       final Change.Id matchId = notes.getChangeId();
-      return FluentIterable
-          .from(db.patchComments().draftByAuthor(author))
-          .filter(new Predicate<PatchLineComment>() {
-            @Override
-            public boolean apply(PatchLineComment in) {
-              Change.Id changeId =
-                  in.getKey().getParentKey().getParentKey().getParentKey();
-              return changeId.equals(matchId);
-            }
-          }).toSortedList(PLC_ORDER);
+      return StreamSupport.stream(
+              db.patchComments().draftByAuthor(author).spliterator(), false)
+          .filter(c -> c.getPatchSetId().getParentKey().equals(matchId))
+          .sorted(PLC_ORDER)
+          .collect(toList());
     }
     List<PatchLineComment> comments = new ArrayList<>();
     comments.addAll(notes.getDraftComments(author).values());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
index 5a89afa..8f25e43 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/StarredChangesUtil.java
@@ -15,14 +15,12 @@
 package com.google.gerrit.server;
 
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toSet;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
-import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedSet;
@@ -249,29 +247,11 @@
   public Set<Account.Id> byChange(final Change.Id changeId,
       final String label) throws OrmException {
     try (final Repository repo = repoManager.openRepository(allUsers)) {
-      return FluentIterable
-          .from(getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId)))
-          .transform(new Function<String, Account.Id>() {
-            @Override
-            public Account.Id apply(String refPart) {
-              return Account.Id.parse(refPart);
-            }
-          })
-          .filter(new Predicate<Account.Id>() {
-            @Override
-            public boolean apply(Account.Id accountId) {
-              try {
-                return readLabels(repo,
-                    RefNames.refsStarredChanges(changeId, accountId))
-                        .contains(label);
-              } catch (IOException e) {
-                log.error(String.format(
-                    "Cannot query stars by account %d on change %d",
-                    accountId.get(), changeId.get()), e);
-                return false;
-              }
-            }
-          }).toSet();
+      return getRefNames(repo, RefNames.refsStarredChangesPrefix(changeId))
+          .stream()
+          .map(Account.Id::parse)
+          .filter(accountId -> hasStar(repo, changeId, accountId, label))
+          .collect(toSet());
     } catch (IOException e) {
       throw new OrmException(
           String.format("Get accounts that starred change %d failed",
@@ -283,36 +263,12 @@
   // To be used only for IsStarredByLegacyPredicate.
   public Set<Change.Id> byAccount(final Account.Id accountId,
       final String label) throws OrmException {
-    try (final Repository repo = repoManager.openRepository(allUsers)) {
-      return FluentIterable
-          .from(getRefNames(repo, RefNames.REFS_STARRED_CHANGES))
-          .filter(new Predicate<String>() {
-            @Override
-            public boolean apply(String refPart) {
-              return refPart.endsWith("/" + accountId.get());
-            }
-          })
-          .transform(new Function<String, Change.Id>() {
-            @Override
-            public Change.Id apply(String refPart) {
-              return Change.Id.fromRefPart(refPart);
-            }
-          })
-          .filter(new Predicate<Change.Id>() {
-            @Override
-            public boolean apply(Change.Id changeId) {
-              try {
-                return readLabels(repo,
-                    RefNames.refsStarredChanges(changeId, accountId))
-                        .contains(label);
-              } catch (IOException e) {
-                log.error(String.format(
-                    "Cannot query stars by account %d on change %d",
-                    accountId.get(), changeId.get()), e);
-                return false;
-              }
-            }
-          }).toSet();
+    try (Repository repo = repoManager.openRepository(allUsers)) {
+      return getRefNames(repo, RefNames.REFS_STARRED_CHANGES).stream()
+          .filter(refPart -> refPart.endsWith("/" + accountId.get()))
+          .map(Change.Id::fromRefPart)
+          .filter(changeId -> hasStar(repo, changeId, accountId, label))
+          .collect(toSet());
     } catch (IOException e) {
       throw new OrmException(
           String.format("Get changes that were starred by %d failed",
@@ -320,6 +276,20 @@
     }
   }
 
+  private boolean hasStar(Repository repo, Change.Id changeId,
+      Account.Id accountId, String label) {
+    try {
+      return readLabels(repo,
+          RefNames.refsStarredChanges(changeId, accountId))
+              .contains(label);
+    } catch (IOException e) {
+      log.error(String.format(
+          "Cannot query stars by account %d on change %d",
+          accountId.get(), changeId.get()), e);
+      return false;
+    }
+  }
+
   public ImmutableMultimap<Account.Id, String> byChangeFromIndex(
       Change.Id changeId) throws OrmException, NoSuchChangeException {
     Set<String> fields = ImmutableSet.of(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
index 761f2a3..6dccbc2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/WebLinks.java
@@ -39,37 +39,31 @@
 @Singleton
 public class WebLinks {
   private static final Logger log = LoggerFactory.getLogger(WebLinks.class);
+
   private static final Predicate<WebLinkInfo> INVALID_WEBLINK =
-      new Predicate<WebLinkInfo>() {
-
-        @Override
-        public boolean apply(WebLinkInfo link) {
-          if (link == null) {
-            return false;
-          } else if (Strings.isNullOrEmpty(link.name)
-              || Strings.isNullOrEmpty(link.url)) {
-            log.warn(String.format("%s is missing name and/or url",
-                link.getClass().getName()));
-            return false;
-          }
-          return true;
+      link -> {
+        if (link == null) {
+          return false;
+        } else if (Strings.isNullOrEmpty(link.name)
+            || Strings.isNullOrEmpty(link.url)) {
+          log.warn(String.format("%s is missing name and/or url",
+              link.getClass().getName()));
+          return false;
         }
+        return true;
       };
-  private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON =
-      new Predicate<WebLinkInfoCommon>() {
 
-        @Override
-        public boolean apply(WebLinkInfoCommon link) {
-          if (link == null) {
-            return false;
-          } else if (Strings.isNullOrEmpty(link.name)
-              || Strings.isNullOrEmpty(link.url)) {
-            log.warn(String.format("%s is missing name and/or url", link
-                .getClass().getName()));
-            return false;
-          }
-          return true;
+  private static final Predicate<WebLinkInfoCommon> INVALID_WEBLINK_COMMON =
+      link -> {
+        if (link == null) {
+          return false;
+        } else if (Strings.isNullOrEmpty(link.name)
+            || Strings.isNullOrEmpty(link.url)) {
+          log.warn(String.format("%s is missing name and/or url", link
+              .getClass().getName()));
+          return false;
         }
+        return true;
       };
 
   private final DynamicSet<PatchSetWebLink> patchSetLinks;
@@ -85,8 +79,7 @@
       DynamicSet<FileHistoryWebLink> fileLogLinks,
       DynamicSet<DiffWebLink> diffLinks,
       DynamicSet<ProjectWebLink> projectLinks,
-      DynamicSet<BranchWebLink> branchLinks
-      ) {
+      DynamicSet<BranchWebLink> branchLinks) {
     this.patchSetLinks = patchSetLinks;
     this.fileLinks = fileLinks;
     this.fileHistoryLinks = fileLogLinks;
@@ -101,15 +94,11 @@
    * @param commit SHA1 of commit.
    * @return Links for patch sets.
    */
-  public FluentIterable<WebLinkInfo> getPatchSetLinks(final Project.NameKey project,
-      final String commit) {
-    return filterLinks(patchSetLinks, new Function<WebLink, WebLinkInfo>() {
-
-      @Override
-      public WebLinkInfo apply(WebLink webLink) {
-        return ((PatchSetWebLink)webLink).getPatchSetWebLink(project.get(), commit);
-      }
-    });
+  public FluentIterable<WebLinkInfo> getPatchSetLinks(Project.NameKey project,
+      String commit) {
+    return filterLinks(
+        patchSetLinks,
+        webLink -> webLink.getPatchSetWebLink(project.get(), commit));
   }
 
   /**
@@ -119,15 +108,11 @@
    * @param file File name.
    * @return Links for files.
    */
-  public FluentIterable<WebLinkInfo> getFileLinks(final String project, final String revision,
-      final String file) {
-    return filterLinks(fileLinks, new Function<WebLink, WebLinkInfo>() {
-
-      @Override
-      public WebLinkInfo apply(WebLink webLink) {
-        return ((FileWebLink)webLink).getFileWebLink(project, revision, file);
-      }
-    });
+  public FluentIterable<WebLinkInfo> getFileLinks(String project,
+      String revision, String file) {
+    return filterLinks(
+        fileLinks,
+        webLink -> webLink.getFileWebLink(project, revision, file));
   }
 
   /**
@@ -137,39 +122,31 @@
    * @param file File name.
    * @return Links for file history
    */
-  public FluentIterable<WebLinkInfo> getFileHistoryLinks(final String project,
-      final String revision, final String file) {
-    return filterLinks(fileHistoryLinks, new Function<WebLink, WebLinkInfo>() {
-
-      @Override
-      public WebLinkInfo apply(WebLink webLink) {
-        return ((FileHistoryWebLink) webLink).getFileHistoryWebLink(project,
-            revision, file);
-      }
-    });
+  public FluentIterable<WebLinkInfo> getFileHistoryLinks(String project,
+      String revision, String file) {
+    return filterLinks(
+        fileHistoryLinks,
+        webLink -> webLink.getFileHistoryWebLink(project, revision, file));
   }
 
   public FluentIterable<WebLinkInfoCommon> getFileHistoryLinksCommon(
-      final String project, final String revision, final String file) {
+      String project, String revision, String file) {
     return FluentIterable
         .from(fileHistoryLinks)
-        .transform(new Function<WebLink, WebLinkInfoCommon>() {
-          @Override
-          public WebLinkInfoCommon apply(WebLink webLink) {
-            WebLinkInfo info =
-                ((FileHistoryWebLink) webLink).getFileHistoryWebLink(project,
-                    revision, file);
-            if (info == null) {
-              return null;
-            }
-            WebLinkInfoCommon commonInfo = new WebLinkInfoCommon();
-            commonInfo.name = info.name;
-            commonInfo.imageUrl = info.imageUrl;
-            commonInfo.url = info.url;
-            commonInfo.target = info.target;
-            return commonInfo;
-          }
-        })
+        .transform(
+            webLink -> {
+              WebLinkInfo info =
+                  webLink.getFileHistoryWebLink(project, revision, file);
+              if (info == null) {
+                return null;
+              }
+              WebLinkInfoCommon commonInfo = new WebLinkInfoCommon();
+              commonInfo.name = info.name;
+              commonInfo.imageUrl = info.imageUrl;
+              commonInfo.url = info.url;
+              commonInfo.target = info.target;
+              return commonInfo;
+            })
         .filter(INVALID_WEBLINK_COMMON);
   }
 
@@ -190,14 +167,10 @@
       final int patchSetIdB, final String revisionB, final String fileB) {
    return FluentIterable
        .from(diffLinks)
-       .transform(new Function<WebLink, DiffWebLinkInfo>() {
-         @Override
-         public DiffWebLinkInfo apply(WebLink webLink) {
-            return ((DiffWebLink) webLink).getDiffLink(project, changeId,
+       .transform(webLink ->
+            webLink.getDiffLink(project, changeId,
                 patchSetIdA, revisionA, fileA,
-                patchSetIdB, revisionB, fileB);
-          }
-       })
+                patchSetIdB, revisionB, fileB))
        .filter(INVALID_WEBLINK);
  }
 
@@ -207,13 +180,9 @@
    * @return Links for projects.
    */
   public FluentIterable<WebLinkInfo> getProjectLinks(final String project) {
-    return filterLinks(projectLinks, new Function<WebLink, WebLinkInfo>() {
-
-      @Override
-      public WebLinkInfo apply(WebLink webLink) {
-        return ((ProjectWebLink)webLink).getProjectWeblink(project);
-      }
-    });
+    return filterLinks(
+        projectLinks,
+        webLink -> webLink.getProjectWeblink(project));
   }
 
   /**
@@ -223,17 +192,13 @@
    * @return Links for branches.
    */
   public FluentIterable<WebLinkInfo> getBranchLinks(final String project, final String branch) {
-    return filterLinks(branchLinks, new Function<WebLink, WebLinkInfo>() {
-
-      @Override
-      public WebLinkInfo apply(WebLink webLink) {
-        return ((BranchWebLink)webLink).getBranchWebLink(project, branch);
-      }
-    });
+    return filterLinks(
+        branchLinks,
+        webLink -> webLink.getBranchWebLink(project, branch));
   }
 
-  private FluentIterable<WebLinkInfo> filterLinks(DynamicSet<? extends WebLink> links,
-      Function<WebLink, WebLinkInfo> transformer) {
+  private <T extends WebLink> FluentIterable<WebLinkInfo> filterLinks(DynamicSet<T> links,
+      Function<T, WebLinkInfo> transformer) {
     return FluentIterable
         .from(links)
         .transform(transformer)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
index c5b0699..db2a98f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountControl.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.Sets;
+import static java.util.stream.Collectors.toSet;
+
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.reviewdb.client.Account;
@@ -28,7 +28,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
-import java.util.HashSet;
 import java.util.Set;
 
 /** Access control management for one account's access to other accounts. */
@@ -186,14 +185,9 @@
   }
 
   private Set<AccountGroup.UUID> groupsOf(IdentifiedUser user) {
-    return new HashSet<>(Sets.filter(
-      user.getEffectiveGroups().getKnownGroups(),
-      new Predicate<AccountGroup.UUID>() {
-        @Override
-        public boolean apply(AccountGroup.UUID in) {
-          return !SystemGroupBackend.isSystemGroup(in);
-        }
-      }));
+    return user.getEffectiveGroups().getKnownGroups().stream()
+        .filter(a -> !SystemGroupBackend.isSystemGroup(a))
+        .collect(toSet());
   }
 
   private abstract static class OtherUser {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java
new file mode 100644
index 0000000..7193564
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountJson.java
@@ -0,0 +1,33 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
+
+public class AccountJson {
+
+  public static AccountInfo toAccountInfo(Account account) {
+    if (account == null || account.getId() == null) {
+      return null;
+    }
+    AccountInfo accountInfo = new AccountInfo(account.getId().get());
+    accountInfo.email = account.getPreferredEmail();
+    accountInfo.name = account.getFullName();
+    accountInfo.username = account.getUserName();
+    return accountInfo;
+  }
+
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
index 5a18269..b400eb7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountResolver.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Function;
-import com.google.common.collect.FluentIterable;
+import static java.util.stream.Collectors.toSet;
+
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -191,14 +191,9 @@
 
       // At this point we have no clue. Just perform a whole bunch of suggestions
       // and pray we come up with a reasonable result list.
-      return FluentIterable
-          .from(accountQueryProvider.get().byDefault(nameOrEmail))
-          .transform(new Function<AccountState, Account.Id>() {
-            @Override
-            public Account.Id apply(AccountState accountState) {
-              return accountState.getAccount().getId();
-            }
-          }).toSet();
+      return accountQueryProvider.get().byDefault(nameOrEmail).stream()
+          .map(a -> a.getAccount().getId())
+          .collect(toSet());
     }
 
     List<Account> m = db.accounts().byFullName(nameOrEmail).toList();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
index 05a7179..ed99266 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountState.java
@@ -36,12 +36,7 @@
 
 public class AccountState {
   public static final Function<AccountState, Account.Id> ACCOUNT_ID_FUNCTION =
-      new Function<AccountState, Account.Id>() {
-        @Override
-        public Account.Id apply(AccountState in) {
-          return in.getAccount().getId();
-        }
-      };
+      a -> a.getAccount().getId();
 
   private final Account account;
   private final Set<AccountGroup.UUID> internalGroups;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index e348e73..d86d27c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -14,15 +14,14 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Function;
+import static com.google.common.base.Predicates.not;
+
 import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
+import com.google.common.collect.FluentIterable;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.common.data.PermissionRule.Action;
-import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.git.QueueProvider;
@@ -32,6 +31,7 @@
 import com.google.inject.assistedinject.Assisted;
 
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
@@ -98,7 +98,7 @@
     if (canEmailReviewers == null) {
       canEmailReviewers =
           matchAny(capabilities.emailReviewers, ALLOWED_RULE)
-          || !matchAny(capabilities.emailReviewers, Predicates.not(ALLOWED_RULE));
+          || !matchAny(capabilities.emailReviewers, not(ALLOWED_RULE));
 
     }
     return canEmailReviewers;
@@ -279,23 +279,16 @@
     return mine;
   }
 
-  private static final Predicate<PermissionRule> ALLOWED_RULE = new Predicate<PermissionRule>() {
-    @Override
-    public boolean apply(PermissionRule rule) {
-      return rule.getAction() == Action.ALLOW;
-    }
-  };
+  private static final Predicate<PermissionRule> ALLOWED_RULE =
+      r -> r.getAction() == Action.ALLOW;
 
-  private boolean matchAny(Iterable<PermissionRule> rules, Predicate<PermissionRule> predicate) {
-    Iterable<AccountGroup.UUID> ids = Iterables.transform(
-        Iterables.filter(rules, predicate),
-        new Function<PermissionRule, AccountGroup.UUID>() {
-          @Override
-          public AccountGroup.UUID apply(PermissionRule rule) {
-            return rule.getGroup().getUUID();
-          }
-        });
-    return user.getEffectiveGroups().containsAnyOf(ids);
+  private boolean matchAny(Collection<PermissionRule> rules,
+      Predicate<PermissionRule> predicate) {
+    return user.getEffectiveGroups()
+        .containsAnyOf(
+            FluentIterable.from(rules)
+                .filter(predicate)
+                .transform(r -> r.getGroup().getUUID()));
   }
 
   private static boolean match(GroupMembership groups,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
index e2fbc3c..0e9bc2e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Lists;
+import static java.util.stream.Collectors.toList;
+
 import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.Response;
@@ -105,13 +105,10 @@
 
   private void deleteFromGit(Account.Id accountId, List<ProjectWatchInfo> input)
       throws IOException, ConfigInvalidException {
-    watchConfig.deleteProjectWatches(accountId, Lists.transform(input,
-        new Function<ProjectWatchInfo, ProjectWatchKey>() {
-          @Override
-          public ProjectWatchKey apply(ProjectWatchInfo info) {
-            return ProjectWatchKey.create(new Project.NameKey(info.project),
-                info.filter);
-          }
-        }));
+    watchConfig.deleteProjectWatches(
+        accountId,
+        input.stream().map(w -> ProjectWatchKey.create(
+                new Project.NameKey(w.project), w.filter))
+            .collect(toList()));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
index bf1a3af..df125e0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetSshKeys.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.common.SshKeyInfo;
@@ -60,13 +59,9 @@
 
   public List<SshKeyInfo> apply(IdentifiedUser user)
       throws RepositoryNotFoundException, IOException, ConfigInvalidException {
-    return Lists.transform(authorizedKeys.getKeys(user.getAccountId()),
-        new Function<AccountSshKey, SshKeyInfo>() {
-          @Override
-          public SshKeyInfo apply(AccountSshKey key) {
-            return newSshKeyInfo(key);
-          }
-        });
+    return Lists.transform(
+        authorizedKeys.getKeys(user.getAccountId()),
+        GetSshKeys::newSshKeyInfo);
   }
 
   public static SshKeyInfo newSshKeyInfo(AccountSshKey sshKey) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
index c47d6f8..84660ec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/InternalGroupBackend.java
@@ -14,10 +14,8 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
+import static java.util.stream.Collectors.toList;
+
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupDescriptions;
 import com.google.gerrit.common.data.GroupReference;
@@ -30,18 +28,11 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.util.Collection;
+import java.util.stream.StreamSupport;
 
 /** Implementation of GroupBackend for the internal group system. */
 @Singleton
 public class InternalGroupBackend implements GroupBackend {
-  private static final Function<AccountGroup, GroupReference> ACT_GROUP_TO_GROUP_REF =
-      new Function<AccountGroup, GroupReference>() {
-        @Override
-        public GroupReference apply(AccountGroup group) {
-          return GroupReference.forGroup(group);
-        }
-      };
-
   private final GroupControl.Factory groupControlFactory;
   private final GroupCache groupCache;
   private final IncludingGroupMembership.Factory groupMembershipFactory;
@@ -77,16 +68,13 @@
   @Override
   public Collection<GroupReference> suggest(final String name,
       final ProjectControl project) {
-    Iterable<AccountGroup> filtered = Iterables.filter(groupCache.all(),
-        new Predicate<AccountGroup>() {
-          @Override
-          public boolean apply(AccountGroup group) {
+    return StreamSupport.stream(groupCache.all().spliterator(), false)
+        .filter(group ->
             // startsWithIgnoreCase && isVisible
-            return group.getName().regionMatches(true, 0, name, 0, name.length())
-                && groupControlFactory.controlFor(group).isVisible();
-          }
-        });
-    return Lists.newArrayList(Iterables.transform(filtered, ACT_GROUP_TO_GROUP_REF));
+            group.getName().regionMatches(true, 0, name, 0, name.length())
+                && groupControlFactory.controlFor(group).isVisible())
+        .map(GroupReference::forGroup)
+        .collect(toList());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index 30a3bdf..871b1cd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.account;
 
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Comparator.comparing;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
@@ -278,13 +278,7 @@
    * @param newKeys the new public SSH keys
    */
   public void setKeys(Collection<AccountSshKey> newKeys) {
-    Ordering<AccountSshKey> o =
-        Ordering.natural().onResultOf(new Function<AccountSshKey, Integer>() {
-          @Override
-          public Integer apply(AccountSshKey sshKey) {
-            return sshKey.getKey().get();
-          }
-        });
+    Ordering<AccountSshKey> o = Ordering.from(comparing(k -> k.getKey().get()));
     keys = new ArrayList<>(Collections.nCopies(o.max(newKeys).getKey().get(),
         Optional.<AccountSshKey> absent()));
     for (AccountSshKey key : newKeys) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java
similarity index 77%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java
index f6e375f..c181f79 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GerritOptions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritOptions.java
@@ -12,28 +12,22 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd;
+package com.google.gerrit.server.config;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 
-import com.google.common.base.Enums;
+import com.google.gerrit.extensions.client.UiType;
 
 import org.eclipse.jgit.lib.Config;
 
 public class GerritOptions {
-  public enum UiPreference {
-    NONE,
-    GWT,
-    POLYGERRIT;
-  }
-
   private final boolean headless;
   private final boolean slave;
   private final boolean enablePolyGerrit;
   private final boolean enableGwtUi;
   private final boolean forcePolyGerritDev;
-  private final UiPreference defaultUi;
+  private final UiType defaultUi;
 
   public GerritOptions(Config cfg, boolean headless, boolean slave,
       boolean forcePolyGerritDev) {
@@ -44,24 +38,22 @@
     this.forcePolyGerritDev = forcePolyGerritDev;
     this.headless = headless || (!enableGwtUi && !enablePolyGerrit);
 
-    UiPreference defaultUi = enablePolyGerrit && !enableGwtUi
-        ? UiPreference.POLYGERRIT
-        : UiPreference.GWT;
+    UiType defaultUi = enablePolyGerrit && !enableGwtUi
+        ? UiType.POLYGERRIT
+        : UiType.GWT;
     String uiStr = firstNonNull(
         cfg.getString("gerrit", null, "ui"),
-        defaultUi.name().toUpperCase());
-    this.defaultUi =
-        Enums.getIfPresent(UiPreference.class, uiStr).or(UiPreference.NONE);
-    uiStr = defaultUi.name().toLowerCase();
+        defaultUi.name());
+    this.defaultUi = firstNonNull(UiType.parse(uiStr), UiType.NONE);
 
     switch (defaultUi) {
       case GWT:
         checkArgument(enableGwtUi,
-            "gerrit.ui = %s but GWT UI is disabled", uiStr);
+            "gerrit.ui = %s but GWT UI is disabled", defaultUi);
         break;
       case POLYGERRIT:
         checkArgument(enablePolyGerrit,
-            "gerrit.ui = %s but PolyGerrit is disabled", uiStr);
+            "gerrit.ui = %s but PolyGerrit is disabled", defaultUi);
         break;
       case NONE:
       default:
@@ -89,7 +81,7 @@
     return !headless && forcePolyGerritDev;
   }
 
-  public UiPreference defaultUi() {
+  public UiType defaultUi() {
     return defaultUi;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
index 14c6da7..83e1419 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetServerInfo.java
@@ -14,13 +14,14 @@
 
 package com.google.gerrit.server.config;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Strings;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.ContributorAgreement;
+import com.google.gerrit.extensions.client.UiType;
 import com.google.gerrit.extensions.common.AuthInfo;
 import com.google.gerrit.extensions.common.ChangeConfigInfo;
 import com.google.gerrit.extensions.common.DownloadInfo;
@@ -56,6 +57,7 @@
 import java.net.MalformedURLException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.TimeUnit;
@@ -82,6 +84,7 @@
   private final NotesMigration migration;
   private final ProjectCache projectCache;
   private final AgreementJson agreementJson;
+  private final GerritOptions gerritOptions;
 
   @Inject
   public GetServerInfo(
@@ -101,7 +104,8 @@
       QueryDocumentationExecutor docSearcher,
       NotesMigration migration,
       ProjectCache projectCache,
-      AgreementJson agreementJson) {
+      AgreementJson agreementJson,
+      GerritOptions gerritOptions) {
     this.config = config;
     this.authConfig = authConfig;
     this.realm = realm;
@@ -119,6 +123,7 @@
     this.migration = migration;
     this.projectCache = projectCache;
     this.agreementJson = agreementJson;
+    this.gerritOptions = gerritOptions;
   }
 
   @Override
@@ -225,14 +230,8 @@
             getDownloadSchemeInfo(scheme, downloadCommands, cloneCommands));
       }
     }
-    info.archives = Lists.newArrayList(Iterables.transform(
-        archiveFormats.getAllowed(),
-        new Function<ArchiveFormat, String>() {
-          @Override
-          public String apply(ArchiveFormat in) {
-            return in.getShortName();
-          }
-        }));
+    info.archives = archiveFormats.getAllowed().stream()
+        .map(ArchiveFormat::getShortName).collect(toList());
     return info;
   }
 
@@ -280,6 +279,13 @@
     info.docSearch = docSearcher.isAvailable();
     info.editGpgKeys = toBoolean(enableSignedPush
         && cfg.getBoolean("gerrit", null, "editGpgKeys", true));
+    info.webUis = EnumSet.noneOf(UiType.class);
+    if (gerritOptions.enableGwtUi()) {
+      info.webUis.add(UiType.GWT);
+    }
+    if (gerritOptions.enablePolyGerrit()) {
+      info.webUis.add(UiType.POLYGERRIT);
+    }
     return info;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
index cc7857c..7d11ff4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectConfigEntry.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Lists;
+import static java.util.stream.Collectors.toList;
+
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.api.projects.ConfigValue;
 import com.google.gerrit.extensions.api.projects.ProjectConfigEntryType;
@@ -137,14 +137,9 @@
       T defaultValue, Class<T> permittedValues, boolean inheritable,
       String description) {
     this(displayName, defaultValue.name(), ProjectConfigEntryType.LIST,
-        Lists.transform(
-            Arrays.asList(permittedValues.getEnumConstants()),
-            new Function<Enum<?>, String>() {
-              @Override
-              public String apply(Enum<?> e) {
-                return e.name();
-              }
-            }), inheritable, description);
+        Arrays.stream(permittedValues.getEnumConstants())
+            .map(Enum::name).collect(toList()),
+        inheritable, description);
   }
 
   public ProjectConfigEntry(String displayName, String defaultValue,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 56daccc..676f9ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -15,11 +15,10 @@
 package com.google.gerrit.server.events;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Comparator.comparing;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Ordering;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
@@ -64,14 +63,6 @@
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
-
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.IOException;
 import java.sql.Timestamp;
 import java.util.ArrayList;
@@ -80,6 +71,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 @Singleton
 public class EventFactory {
@@ -298,22 +295,21 @@
       }
     }
     // Sort by original parent order.
-    Collections.sort(ca.dependsOn, Ordering.natural().onResultOf(
-        new Function<DependencyAttribute, Integer>() {
-          @Override
-          public Integer apply(DependencyAttribute d) {
-            for (int i = 0; i < parentNames.size(); i++) {
-              if (parentNames.get(i).equals(d.revision)) {
-                return i;
+    Collections.sort(
+        ca.dependsOn,
+        comparing(
+            (DependencyAttribute d) -> {
+              for (int i = 0; i < parentNames.size(); i++) {
+                if (parentNames.get(i).equals(d.revision)) {
+                  return i;
+                }
               }
-            }
-            return parentNames.size() + 1;
-          }
-        }));
+              return parentNames.size() + 1;
+            }));
   }
 
-  private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change,
-      PatchSet currentPs) throws OrmException, IOException {
+  private void addNeededBy(RevWalk rw, ChangeAttribute ca, Change change, PatchSet currentPs)
+      throws OrmException, IOException {
     if (currentPs.getGroups().isEmpty()) {
       return;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
index 114338d..1c8782b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GpgException;
 import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.AccountJson;
 import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
@@ -82,11 +83,7 @@
     if (a == null || a.getId() == null) {
       return null;
     }
-    AccountInfo ai = new AccountInfo(a.getId().get());
-    ai.email = a.getPreferredEmail();
-    ai.name = a.getFullName();
-    ai.username = a.getUserName();
-    return ai;
+    return AccountJson.toAccountInfo(a);
   }
 
   public AccountInfo accountInfo(Account.Id accountId) {
@@ -97,7 +94,7 @@
       Map<String, Short> approvals, Timestamp ts) {
     Map<String, ApprovalInfo> result = new HashMap<>();
     for (Map.Entry<String, Short> e : approvals.entrySet()) {
-      Integer value = e.getValue() != null ? new Integer(e.getValue()) : null;
+      Integer value = e.getValue() != null ? Integer.valueOf(e.getValue()) : null;
       result.put(e.getKey(),
           ChangeJson.getApprovalInfo(a.getId(), value, null, ts));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
index 35b14dc..9dbbeef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/ReviewerAdded.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.extensions.events;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.api.changes.NotifyHandling;
 import com.google.gerrit.extensions.common.AccountInfo;
@@ -72,18 +71,10 @@
       return;
     }
 
-    List<AccountInfo> transformed = Lists.transform(reviewers,
-        new Function<Account.Id, AccountInfo>() {
-          @Override
-          public AccountInfo apply(Account.Id account) {
-            return util.accountInfo(account);
-          }
-        });
-
     try {
       fire(util.changeInfo(change),
           util.revisionInfo(change.getProject(), patchSet),
-          transformed,
+          Lists.transform(reviewers, util::accountInfo),
           util.accountInfo(adder),
           when);
     } catch (PatchListNotAvailableException | GpgException | IOException
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
index 601bcc6..3dbd1e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/webui/UiActions.java
@@ -14,11 +14,8 @@
 
 package com.google.gerrit.server.extensions.webui;
 
-import com.google.common.base.Function;
 import com.google.common.base.Predicate;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.common.Nullable;
+import com.google.common.collect.FluentIterable;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.RestCollection;
@@ -33,16 +30,13 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Objects;
+
 public class UiActions {
   private static final Logger log = LoggerFactory.getLogger(UiActions.class);
 
   public static Predicate<UiAction.Description> enabled() {
-    return new Predicate<UiAction.Description>() {
-      @Override
-      public boolean apply(UiAction.Description input) {
-        return input.isEnabled();
-      }
-    };
+    return UiAction.Description::isEnabled;
   }
 
   public static <R extends RestResource> Iterable<UiAction.Description> from(
@@ -56,58 +50,52 @@
       DynamicMap<RestView<R>> views,
       final R resource,
       final Provider<CurrentUser> userProvider) {
-    return Iterables.filter(
-      Iterables.transform(
-        views,
-        new Function<DynamicMap.Entry<RestView<R>>, UiAction.Description> () {
-          @Override
-          @Nullable
-          public UiAction.Description apply(DynamicMap.Entry<RestView<R>> e) {
-            int d = e.getExportName().indexOf('.');
-            if (d < 0) {
-              return null;
-            }
+    return FluentIterable.from(views)
+        .transform((DynamicMap.Entry<RestView<R>> e) -> {
+              int d = e.getExportName().indexOf('.');
+              if (d < 0) {
+                return null;
+              }
 
-            RestView<R> view;
-            try {
-              view = e.getProvider().get();
-            } catch (RuntimeException err) {
-              log.error(String.format(
-                  "error creating view %s.%s",
-                  e.getPluginName(), e.getExportName()), err);
-              return null;
-            }
+              RestView<R> view;
+              try {
+                view = e.getProvider().get();
+              } catch (RuntimeException err) {
+                log.error(String.format(
+                    "error creating view %s.%s",
+                    e.getPluginName(), e.getExportName()), err);
+                return null;
+              }
 
-            if (!(view instanceof UiAction)) {
-              return null;
-            }
+              if (!(view instanceof UiAction)) {
+                return null;
+              }
 
-            try {
-              CapabilityUtils.checkRequiresCapability(userProvider,
-                  e.getPluginName(), view.getClass());
-            } catch (AuthException exc) {
-              return null;
-            }
+              try {
+                CapabilityUtils.checkRequiresCapability(userProvider,
+                    e.getPluginName(), view.getClass());
+              } catch (AuthException exc) {
+                return null;
+              }
 
-            UiAction.Description dsc =
-                ((UiAction<R>) view).getDescription(resource);
-            if (dsc == null || !dsc.isVisible()) {
-              return null;
-            }
+              UiAction.Description dsc =
+                  ((UiAction<R>) view).getDescription(resource);
+              if (dsc == null || !dsc.isVisible()) {
+                return null;
+              }
 
-            String name = e.getExportName().substring(d + 1);
-            PrivateInternals_UiActionDescription.setMethod(
-                dsc,
-                e.getExportName().substring(0, d));
-            PrivateInternals_UiActionDescription.setId(
-                dsc,
-                "gerrit".equals(e.getPluginName())
-                  ? name
-                  : e.getPluginName() + '~' + name);
-            return dsc;
-          }
-        }),
-      Predicates.notNull());
+              String name = e.getExportName().substring(d + 1);
+              PrivateInternals_UiActionDescription.setMethod(
+                  dsc,
+                  e.getExportName().substring(0, d));
+              PrivateInternals_UiActionDescription.setId(
+                  dsc,
+                  "gerrit".equals(e.getPluginName())
+                    ? name
+                    : e.getPluginName() + '~' + name);
+              return dsc;
+            })
+        .filter(Objects::nonNull);
   }
 
   private UiActions() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 3855b8f..8e98562 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -17,6 +17,7 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Comparator.comparing;
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.NANOSECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
@@ -226,7 +227,7 @@
       this.dbWrapper = dbWrapper;
       this.threadLocalRepo = repo;
       this.threadLocalRevWalk = rw;
-      updates = new TreeMap<>(ReviewDbUtil.intKeyOrdering());
+      updates = new TreeMap<>(comparing(PatchSet.Id::get));
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
index f07b922..27767c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CodeReviewCommit.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
@@ -46,14 +45,11 @@
    * AnyObjectId} and only orders on SHA-1.
    */
   public static final Ordering<CodeReviewCommit> ORDER = Ordering.natural()
-      .onResultOf(new Function<CodeReviewCommit, Integer>() {
-        @Override
-        public Integer apply(CodeReviewCommit in) {
-          return in.getPatchsetId() != null
-              ? in.getPatchsetId().getParentKey().get()
-              : null;
-        }
-      }).nullsFirst();
+      .onResultOf((CodeReviewCommit c) ->
+          c.getPatchsetId() != null
+              ? c.getPatchsetId().getParentKey().get()
+              : null)
+      .nullsFirst();
 
   public static CodeReviewRevWalk newRevWalk(Repository repo) {
     return new CodeReviewRevWalk(repo);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java
index d832260..795a838 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GroupCollector.java
@@ -18,7 +18,6 @@
 import static org.eclipse.jgit.revwalk.RevFlag.UNINTERESTING;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
@@ -158,13 +157,7 @@
   private static Multimap<ObjectId, PatchSet.Id> transformRefs(
       Multimap<ObjectId, Ref> refs) {
     return Multimaps.transformValues(
-        refs,
-        new Function<Ref, PatchSet.Id>() {
-          @Override
-          public PatchSet.Id apply(Ref in) {
-            return PatchSet.Id.fromRef(in.getName());
-          }
-        });
+        refs, r -> PatchSet.Id.fromRef(r.getName()));
   }
 
   @VisibleForTesting
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
index 093d036b..ad5cf20 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/LocalDiskRepositoryManager.java
@@ -31,6 +31,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.RepositoryCache;
 import org.eclipse.jgit.lib.RepositoryCache.FileKey;
+import org.eclipse.jgit.lib.RepositoryCacheConfig;
 import org.eclipse.jgit.lib.StoredConfig;
 import org.eclipse.jgit.storage.file.WindowCacheConfig;
 import org.eclipse.jgit.util.FS;
@@ -84,6 +85,10 @@
 
     @Override
     public void start() {
+      RepositoryCacheConfig repoCacheCfg = new RepositoryCacheConfig();
+      repoCacheCfg.fromConfig(serverConfig);
+      repoCacheCfg.install();
+
       WindowCacheConfig cfg = new WindowCacheConfig();
       cfg.fromConfig(serverConfig);
       if (serverConfig.getString("core", null, "streamFileThreshold") == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 39ad6ee..71bc9f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -17,12 +17,11 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Comparator.comparing;
 
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMultimap;
@@ -31,7 +30,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
@@ -122,13 +120,10 @@
       }
       byBranch = bb.build();
       commits = new HashMap<>();
-      problems = MultimapBuilder.treeKeys(
-          Ordering.natural().onResultOf(new Function<Change.Id, Integer>() {
-            @Override
-            public Integer apply(Change.Id in) {
-              return in.get();
-            }
-          })).arrayListValues(1).build();
+      problems = MultimapBuilder
+          .treeKeys(comparing(Change.Id::get))
+          .arrayListValues(1)
+          .build();
     }
 
     public ImmutableSet<Change.Id> getChangeIds() {
@@ -264,12 +259,7 @@
     if (in == null) {
       return Optional.absent();
     }
-    return Iterables.tryFind(in, new Predicate<SubmitRecord>() {
-      @Override
-      public boolean apply(SubmitRecord input) {
-        return input.status == SubmitRecord.Status.OK;
-      }
-    });
+    return Iterables.tryFind(in, r -> r.status == SubmitRecord.Status.OK);
   }
 
   public static void checkSubmitRule(ChangeData cd)
@@ -550,6 +540,10 @@
             submitting.submitType(), ob.oldTip, submoduleOp, dryrun);
         strategies.add(strategy);
         strategy.addOps(or.getUpdate(), commitsToSubmit);
+        if (submitting.submitType().equals(SubmitType.FAST_FORWARD_ONLY) &&
+            submoduleOp.hasSubscription(branch)) {
+          submoduleOp.addOp(or.getUpdate(), branch);
+        }
       } else {
         // no open change for this branch
         // add submodule triggered op into BatchUpdate
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 44fe101..0667e14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -16,9 +16,9 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import com.google.common.base.Function;
 import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
@@ -599,14 +599,10 @@
           Joiner.on("', '").join(topics));
     } else {
       return String.format("Merge changes %s%s",
-          Joiner.on(',').join(Iterables.transform(
-              Iterables.limit(merged, 5),
-              new Function<CodeReviewCommit, String>() {
-                @Override
-                public String apply(CodeReviewCommit in) {
-                  return in.change().getKey().abbreviate();
-                }
-              })),
+          FluentIterable.from(merged)
+              .limit(5)
+              .transform(c -> c.change().getKey().abbreviate())
+              .join(Joiner.on(',')),
           merged.size() > 5 ? ", ..." : "");
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 014b28d..c2c12f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -21,6 +21,9 @@
 import static com.google.gerrit.server.change.HashtagsUtil.cleanupHashtag;
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 import static com.google.gerrit.server.mail.MailUtil.getRecipientsFromFooters;
+import static java.util.Comparator.comparingInt;
+import static java.util.stream.Collectors.joining;
+import static java.util.stream.Collectors.toList;
 import static org.eclipse.jgit.lib.Constants.R_HEADS;
 import static org.eclipse.jgit.lib.RefDatabase.ALL;
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.NOT_ATTEMPTED;
@@ -30,15 +33,11 @@
 import static org.eclipse.jgit.transport.ReceiveCommand.Result.REJECTED_OTHER_REASON;
 
 import com.google.common.base.Function;
-import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.BiMap;
-import com.google.common.collect.Collections2;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.ImmutableList;
@@ -48,7 +47,6 @@
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Ordering;
 import com.google.common.collect.SetMultimap;
 import com.google.common.collect.Sets;
 import com.google.common.collect.SortedSetMultimap;
@@ -681,14 +679,9 @@
   }
 
   private void reportMessages() {
-    Iterable<CreateRequest> created =
-        Iterables.filter(newChanges, new Predicate<CreateRequest>() {
-          @Override
-          public boolean apply(CreateRequest input) {
-            return input.change != null;
-          }
-        });
-    if (!Iterables.isEmpty(created)) {
+    List<CreateRequest> created =
+        newChanges.stream().filter(r -> r.change != null).collect(toList());
+    if (!created.isEmpty()) {
       addMessage("");
       addMessage("New Changes:");
       for (CreateRequest c : created) {
@@ -699,21 +692,10 @@
       addMessage("");
     }
 
-    List<ReplaceRequest> updated = FluentIterable
-        .from(replaceByChange.values())
-        .filter(new Predicate<ReplaceRequest>() {
-          @Override
-          public boolean apply(ReplaceRequest input) {
-            return !input.skip && input.inputCommand.getResult() == OK;
-          }
-        })
-        .toSortedList(Ordering.natural().onResultOf(
-            new Function<ReplaceRequest, Integer>() {
-              @Override
-              public Integer apply(ReplaceRequest in) {
-                return in.notes.getChangeId().get();
-              }
-            }));
+    List<ReplaceRequest> updated = replaceByChange.values().stream()
+        .filter(r -> !r.skip && r.inputCommand.getResult() == OK)
+        .sorted(comparingInt(r -> r.notes.getChangeId().get()))
+        .collect(toList());
     if (!updated.isEmpty()) {
       addMessage("");
       addMessage("Updated Changes:");
@@ -831,7 +813,7 @@
       // One or more new references failed to create. Assume the
       // system isn't working correctly anymore and abort.
       reject(magicBranch.cmd, "Unable to create changes: "
-          + Joiner.on(' ').join(lastCreateChangeErrors));
+          + lastCreateChangeErrors.stream().collect(joining(" ")));
       logError(String.format(
           "Only %d of %d new change refs created in %s; aborting",
           okToInsert, replaceCount + newChanges.size(), project.getName()));
@@ -1056,11 +1038,12 @@
                         .getPluginConfig(e.getPluginName())
                         .getString(e.getExportName());
                 if (configEntry.getType() == ProjectConfigEntryType.ARRAY) {
-                  List<String> l =
-                      Arrays.asList(projectControl.getProjectState()
-                          .getConfig().getPluginConfig(e.getPluginName())
-                          .getStringList(e.getExportName()));
-                  oldValue = Joiner.on("\n").join(l);
+                  oldValue =
+                    Arrays.stream(
+                        projectControl.getProjectState()
+                            .getConfig().getPluginConfig(e.getPluginName())
+                            .getStringList(e.getExportName()))
+                      .collect(joining("\n"));
                 }
 
                 if ((value == null ? oldValue != null : !value.equals(oldValue)) &&
@@ -1796,14 +1779,10 @@
         List<ChangeData> changes = p.destChanges;
         if (changes.size() > 1) {
           logDebug("Multiple changes in project with Change-Id {}: {}",
-              p.changeKey, Lists.transform(
-                  changes,
-                  new Function<ChangeData, String>() {
-                    @Override
-                    public String apply(ChangeData in) {
-                      return in.getId().toString();
-                    }
-                  }));
+              p.changeKey,
+              changes.stream()
+                  .map(cd -> cd.getId().toString())
+                  .collect(joining()));
           // WTF, multiple changes in this project have the same key?
           // Since the commit is new, the user should recreate it with
           // a different Change-Id. In practice, we should never see
@@ -2188,14 +2167,8 @@
     Collection<ChangeNotes> allNotes =
         notesFactory.create(
             db,
-            Collections2.transform(
-                replaceByChange.values(),
-                new Function<ReplaceRequest, Change.Id>() {
-                  @Override
-                  public Change.Id apply(ReplaceRequest in) {
-                    return in.ontoChange;
-                  }
-                }));
+            replaceByChange.values().stream()
+                .map(r -> r.ontoChange).collect(toList()));
     for (ChangeNotes notes : allNotes) {
       replaceByChange.get(notes.getChangeId()).notes = notes;
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index ced6ca0..a1578ce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -354,12 +354,16 @@
     }
 
     CodeReviewCommit currentCommit;
-    Ref r = or.repo.exactRef(subscriber.get());
-    if (r == null) {
-      throw new SubmoduleException(
-          "The branch was probably deleted from the subscriber repository");
+    if (branchTips.containsKey(subscriber)) {
+      currentCommit = branchTips.get(subscriber);
+    } else {
+      Ref r = or.repo.exactRef(subscriber.get());
+      if (r == null) {
+        throw new SubmoduleException(
+            "The branch was probably deleted from the subscriber repository");
+      }
+      currentCommit = or.rw.parseCommit(r.getObjectId());
     }
-    currentCommit = or.rw.parseCommit(r.getObjectId());
 
     StringBuilder msgbuf = new StringBuilder("");
     PersonIdent author = null;
@@ -436,7 +440,9 @@
     commit.setAuthor(currentCommit.getAuthorIdent());
     commit.setCommitter(myIdent);
     ObjectId id = or.ins.insert(commit);
-    return or.rw.parseCommit(id);
+    CodeReviewCommit newCommit = or.rw.parseCommit(id);
+    newCommit.copyFrom(currentCommit);
+    return newCommit;
   }
 
   private RevCommit updateSubmodule(DirCache dc, DirCacheEditor ed,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
index 5260aab..ad650c3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/TagSetHolder.java
@@ -14,8 +14,8 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
+import static java.util.stream.Collectors.toList;
+
 import com.google.gerrit.reviewdb.client.Project;
 
 import org.eclipse.jgit.lib.Ref;
@@ -45,12 +45,7 @@
   }
 
   TagMatcher matcher(TagCache cache, Repository db, Collection<Ref> include) {
-    include = FluentIterable.from(include).filter(new Predicate<Ref>() {
-      @Override
-      public boolean apply(Ref ref) {
-        return !TagSet.skip(ref);
-      }
-    }).toList();
+    include = include.stream().filter(r -> !TagSet.skip(r)).collect(toList());
 
     TagSet tags = this.tags;
     if (tags == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 4a5e94d..31da05c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -48,14 +48,14 @@
 
   @Override
   public List<SubmitStrategyOp> buildOps(
-      Collection<CodeReviewCommit> toMerge) {
+      Collection<CodeReviewCommit> toMerge) throws IntegrationException {
     List<CodeReviewCommit> sorted = CodeReviewCommit.ORDER.sortedCopy(toMerge);
     List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
     boolean first = true;
     while (!sorted.isEmpty()) {
       CodeReviewCommit n = sorted.remove(0);
       if (first && args.mergeTip.getInitialTip() == null) {
-        ops.add(new CherryPickUnbornRootOp(n));
+        ops.add(new FastForwardOp(args, n));
       } else if (n.getParentCount() == 0) {
         ops.add(new CherryPickRootOp(n));
       } else if (n.getParentCount() == 1) {
@@ -68,21 +68,6 @@
     return ops;
   }
 
-  private class CherryPickUnbornRootOp extends SubmitStrategyOp {
-    private CherryPickUnbornRootOp(CodeReviewCommit toMerge) {
-      super(CherryPick.this.args, toMerge);
-    }
-
-    @Override
-    protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
-      // The branch is unborn. Take fast-forward resolution to create the
-      // branch.
-      CodeReviewCommit newCommit = amendGitlink(toMerge);
-      args.mergeTip.moveTipTo(newCommit, toMerge);
-      newCommit.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
-    }
-  }
-
   private class CherryPickRootOp extends SubmitStrategyOp {
     private CherryPickRootOp(CodeReviewCommit toMerge) {
       super(CherryPick.this.args, toMerge);
@@ -191,8 +176,9 @@
       // different first parent. So instead behave as though MERGE_IF_NECESSARY
       // was configured.
       MergeTip mergeTip = args.mergeTip;
-      if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
-        mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
+      if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge) &&
+          !args.submoduleOp.hasSubscription(args.destBranch)) {
+        mergeTip.moveTipTo(toMerge, toMerge);
       } else {
         PersonIdent myIdent = new PersonIdent(args.serverIdent, ctx.getWhen());
         CodeReviewCommit result = args.mergeUtil.mergeOneCommit(myIdent,
@@ -200,9 +186,9 @@
             mergeTip.getCurrentTip(), toMerge);
         result = amendGitlink(result);
         mergeTip.moveTipTo(result, toMerge);
+        args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
+            mergeTip.getCurrentTip(), args.alreadyAccepted);
       }
-      args.mergeUtil.markCleanMerges(args.rw, args.canMergeFlag,
-          mergeTip.getCurrentTip(), args.alreadyAccepted);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
index 0e69128..bb58540 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/FastForwardOp.java
@@ -25,6 +25,6 @@
 
   @Override
   protected void updateRepoImpl(RepoContext ctx) throws IntegrationException {
-    args.mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
+    args.mergeTip.moveTipTo(toMerge, toMerge);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index 0e2cbd7..5b2e213 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -32,11 +32,15 @@
     List<CodeReviewCommit> sorted =
         args.mergeUtil.reduceToMinimalMerge(args.mergeSorter, toMerge);
     List<SubmitStrategyOp> ops = new ArrayList<>(sorted.size());
-    CodeReviewCommit firstFastForward = args.mergeUtil.getFirstFastForward(
+
+    if (args.mergeTip.getInitialTip() == null || !args.submoduleOp
+        .hasSubscription(args.destBranch)) {
+      CodeReviewCommit firstFastForward = args.mergeUtil.getFirstFastForward(
           args.mergeTip.getInitialTip(), args.rw, sorted);
-    if (firstFastForward != null &&
-        !firstFastForward.equals(args.mergeTip.getInitialTip())) {
-      ops.add(new FastForwardOp(args, firstFastForward));
+      if (firstFastForward != null &&
+          !firstFastForward.equals(args.mergeTip.getInitialTip())) {
+        ops.add(new FastForwardOp(args, firstFastForward));
+      }
     }
 
     // For every other commit do a pair-wise merge.
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 f183772d..3270fc3 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
@@ -63,7 +63,7 @@
     while (!sorted.isEmpty()) {
       CodeReviewCommit n = sorted.remove(0);
       if (first && args.mergeTip.getInitialTip() == null) {
-        ops.add(new RebaseUnbornRootOp(n));
+        ops.add(new FastForwardOp(args, n));
       } else if (n.getParentCount() == 0) {
         ops.add(new RebaseRootOp(n));
       } else if (n.getParentCount() == 1) {
@@ -76,22 +76,6 @@
     return ops;
   }
 
-  private class RebaseUnbornRootOp extends SubmitStrategyOp {
-    private RebaseUnbornRootOp(CodeReviewCommit toMerge) {
-      super(RebaseIfNecessary.this.args, toMerge);
-    }
-
-    @Override
-    public void updateRepoImpl(RepoContext ctx) throws IntegrationException {
-      // The branch is unborn. Take fast-forward resolution to create the
-      // branch.
-      toMerge.setStatusCode(CommitMergeStatus.CLEAN_MERGE);
-      CodeReviewCommit newCommit = amendGitlink(toMerge);
-      args.mergeTip.moveTipTo(newCommit, toMerge);
-      acceptMergeTip(args.mergeTip);
-    }
-  }
-
   private class RebaseRootOp extends SubmitStrategyOp {
     private RebaseRootOp(CodeReviewCommit toMerge) {
       super(RebaseIfNecessary.this.args, toMerge);
@@ -120,9 +104,11 @@
       // 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 (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;
       }
@@ -193,9 +179,9 @@
       // first parent. So instead behave as though MERGE_IF_NECESSARY was
       // configured.
       MergeTip mergeTip = args.mergeTip;
-      if (args.rw.isMergedInto(mergeTip.getCurrentTip(), toMerge)) {
-        mergeTip.moveTipTo(amendGitlink(toMerge), toMerge);
-        acceptMergeTip(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,
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 c784379..e60b947 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
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git.strategy;
 
-import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -70,12 +69,7 @@
     return FluentIterable
         .from(repo.getRefDatabase().getRefs(Constants.R_HEADS).values())
         .append(repo.getRefDatabase().getRefs(Constants.R_TAGS).values())
-        .transform(new Function<Ref, ObjectId>() {
-          @Override
-          public ObjectId apply(Ref r) {
-            return r.getObjectId();
-          }
-        });
+        .transform(Ref::getObjectId);
   }
 
   public static Set<RevCommit> getAlreadyAccepted(Repository repo, RevWalk rw)
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 ed08a19..f917ba1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -50,15 +50,6 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gwtorm.server.OrmException;
-
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.transport.ReceiveCommand;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -67,6 +58,13 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.transport.ReceiveCommand;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 abstract class SubmitStrategyOp extends BatchUpdate.Op {
   private static final Logger log =
@@ -182,13 +180,7 @@
       }
     }
     Collections.sort(commits, ReviewDbUtil.intKeyOrdering().reverse()
-        .onResultOf(
-          new Function<CodeReviewCommit, PatchSet.Id>() {
-            @Override
-            public PatchSet.Id apply(CodeReviewCommit in) {
-              return in.getPatchsetId();
-            }
-          }));
+        .onResultOf(c -> c.getPatchsetId()));
     CodeReviewCommit result = MergeUtil.findAnyMergedInto(rw, commits, tip);
     if (result == null) {
       return null;
@@ -385,14 +377,11 @@
 
   private static Function<PatchSetApproval, PatchSetApproval>
       convertPatchSet(final PatchSet.Id psId) {
-    return new Function<PatchSetApproval, PatchSetApproval>() {
-      @Override
-      public PatchSetApproval apply(PatchSetApproval in) {
-        if (in.getPatchSetId().equals(psId)) {
-          return in;
-        }
-        return new PatchSetApproval(psId, in);
+    return psa -> {
+      if (psa.getPatchSetId().equals(psId)) {
+        return psa;
       }
+      return new PatchSetApproval(psId, psa);
     };
   }
 
@@ -403,14 +392,12 @@
 
   private static Iterable<PatchSetApproval> zero(
       Iterable<PatchSetApproval> approvals) {
-    return Iterables.transform(approvals,
-        new Function<PatchSetApproval, PatchSetApproval>() {
-          @Override
-          public PatchSetApproval apply(PatchSetApproval in) {
-            PatchSetApproval copy = new PatchSetApproval(in.getPatchSetId(), in);
-            copy.setValue((short) 0);
-            return copy;
-          }
+    return Iterables.transform(
+        approvals,
+        a -> {
+          PatchSetApproval copy = new PatchSetApproval(a.getPatchSetId(), a);
+          copy.setValue((short) 0);
+          return copy;
         });
   }
 
@@ -564,26 +551,18 @@
    */
   protected CodeReviewCommit amendGitlink(CodeReviewCommit commit)
       throws IntegrationException {
-    CodeReviewCommit newCommit = commit;
-    // Modify the commit with gitlink update
-    if (args.submoduleOp.hasSubscription(args.destBranch)) {
-      try {
-        newCommit =
-            args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
-        newCommit.copyFrom(commit);
-        if (commit.equals(toMerge)) {
-          newCommit.setPatchsetId(ChangeUtil.nextPatchSetId(
-              args.repo, toMerge.change().currentPatchSetId()));
-          args.commits.put(newCommit);
-        }
-      } catch (SubmoduleException | IOException e) {
-        throw new IntegrationException(
-            "cannot update gitlink for the commit at branch: "
-                + args.destBranch);
-      }
+    if (!args.submoduleOp.hasSubscription(args.destBranch)) {
+      return commit;
     }
 
-    return newCommit;
+    // Modify the commit with gitlink update
+    try {
+      return args.submoduleOp.composeGitlinksCommit(args.destBranch, commit);
+    } catch (SubmoduleException | IOException e) {
+      throw new IntegrationException(
+          "cannot update gitlink for the commit at branch: "
+              + args.destBranch);
+    }
   }
 
   protected final void logDebug(String msg, Object... args) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
index d5d90d3..6a25862 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/IndexModule.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.server.git.QueueProvider.QueueType.BATCH;
 import static com.google.gerrit.server.git.QueueProvider.QueueType.INTERACTIVE;
 
-import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
@@ -110,19 +109,11 @@
             accounts,
             changes);
     Set<String> expected = FluentIterable.from(ALL_SCHEMA_DEFS)
-        .transform(new Function<SchemaDefinitions<?>, String>() {
-          @Override
-          public String apply(SchemaDefinitions<?> in) {
-            return in.getName();
-          }
-        }).toSet();
+        .transform(SchemaDefinitions::getName)
+        .toSet();
     Set<String> actual = FluentIterable.from(result)
-        .transform(new Function<IndexDefinition<?, ?, ?>, String>() {
-          @Override
-          public String apply(IndexDefinition<?, ?, ?> in) {
-            return in.getName();
-          }
-        }).toSet();
+        .transform(IndexDefinition::getName)
+        .toSet();
     if (!expected.equals(actual)) {
       throw new ProvisionException(
           "need index definitions for all schemas: "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
index 824739e..afe3f70 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/account/AccountField.java
@@ -14,14 +14,12 @@
 
 package com.google.gerrit.server.index.account;
 
-import com.google.common.base.Function;
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.server.account.AccountState;
-import com.google.gerrit.server.account.WatchConfig.ProjectWatchKey;
 import com.google.gerrit.server.index.FieldDef;
 import com.google.gerrit.server.index.FieldType;
 import com.google.gerrit.server.index.SchemaUtil;
@@ -47,13 +45,7 @@
         @Override
         public Iterable<String> get(AccountState input, FillArgs args) {
           return Iterables.transform(
-              input.getExternalIds(),
-              new Function<AccountExternalId, String>() {
-                @Override
-                public String apply(AccountExternalId in) {
-                  return in.getKey().get();
-                }
-              });
+              input.getExternalIds(), id -> id.getKey().get());
         }
       };
 
@@ -68,12 +60,7 @@
               fullName,
               Iterables.transform(
                   input.getExternalIds(),
-                  new Function<AccountExternalId, String>() {
-                    @Override
-                    public String apply(AccountExternalId in) {
-                      return in.getEmailAddress();
-                    }
-                  }));
+                  AccountExternalId::getEmailAddress));
 
           // Additional values not currently added by getPersonParts.
           // TODO(dborowitz): Move to getPersonParts and remove this hack.
@@ -108,23 +95,11 @@
         @Override
         public Iterable<String> get(AccountState input, FillArgs args) {
           return FluentIterable.from(input.getExternalIds())
-            .transform(
-                new Function<AccountExternalId, String>() {
-                  @Override
-                  public String apply(AccountExternalId in) {
-                    return in.getEmailAddress();
-                  }
-                })
+            .transform(AccountExternalId::getEmailAddress)
             .append(
                 Collections.singleton(input.getAccount().getPreferredEmail()))
             .filter(Predicates.notNull())
-            .transform(
-                new Function<String, String>() {
-                  @Override
-                  public String apply(String in) {
-                    return in.toLowerCase();
-                  }
-                })
+            .transform(String::toLowerCase)
             .toSet();
         }
       };
@@ -153,12 +128,8 @@
         @Override
         public Iterable<String> get(AccountState input, FillArgs args) {
           return FluentIterable.from(input.getProjectWatches().keySet())
-              .transform(new Function<ProjectWatchKey, String>() {
-            @Override
-            public String apply(ProjectWatchKey in) {
-              return in.project().get();
-            }
-          }).toSet();
+              .transform(k -> k.project().get())
+              .toSet();
         }
       };
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
index fe448c6..c9706a5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ChangeField.java
@@ -15,10 +15,11 @@
 package com.google.gerrit.server.index.change;
 
 import static com.google.common.base.MoreObjects.firstNonNull;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toSet;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ImmutableList;
@@ -34,7 +35,6 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.StarredChangesUtil;
 import com.google.gerrit.server.index.FieldDef;
@@ -247,13 +247,9 @@
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return ImmutableSet.copyOf(Iterables.transform(input.hashtags(),
-              new Function<String, String>() {
-            @Override
-            public String apply(String input) {
-              return input.toLowerCase();
-            }
-          }));
+          return input.hashtags().stream()
+              .map(String::toLowerCase)
+              .collect(toSet());
         }
       };
 
@@ -264,13 +260,9 @@
         @Override
         public Iterable<byte[]> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return ImmutableSet.copyOf(Iterables.transform(input.hashtags(),
-              new Function<String, byte[]>() {
-            @Override
-            public byte[] apply(String hashtag) {
-              return hashtag.getBytes(UTF_8);
-            }
-          }));
+          return input.hashtags().stream()
+              .map(t -> t.getBytes(UTF_8))
+              .collect(toSet());
         }
       };
 
@@ -657,13 +649,7 @@
         @Override
         public Iterable<Integer> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return Iterables.transform(input.starredBy(),
-              new Function<Account.Id, Integer>() {
-            @Override
-            public Integer apply(Account.Id accountId) {
-              return accountId.get();
-            }
-          });
+          return Iterables.transform(input.starredBy(), Account.Id::get);
         }
       };
 
@@ -676,14 +662,12 @@
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return Iterables.transform(input.stars().entries(),
-              new Function<Map.Entry<Account.Id, String>, String>() {
-            @Override
-            public String apply(Map.Entry<Account.Id, String> e) {
-              return StarredChangesUtil.StarField.create(
-                  e.getKey(), e.getValue()).toString();
-            }
-          });
+          return Iterables.transform(
+              input.stars().entries(),
+              (Map.Entry<Account.Id, String> e) -> {
+                return StarredChangesUtil.StarField.create(
+                    e.getKey(), e.getValue()).toString();
+              });
         }
       };
 
@@ -694,8 +678,7 @@
         @Override
         public Iterable<Integer> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return Iterables.transform(input.stars().keySet(),
-              ReviewDbUtil.INT_KEY_FUNCTION);
+          return Iterables.transform(input.stars().keySet(), Account.Id::get);
         }
       };
 
@@ -740,13 +723,9 @@
         @Override
         public Iterable<Integer> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return ImmutableSet.copyOf(Iterables.transform(input.editsByUser(),
-              new Function<Account.Id, Integer>() {
-            @Override
-            public Integer apply(Account.Id account) {
-              return account.get();
-            }
-          }));
+          return input.editsByUser().stream()
+              .map(Account.Id::get)
+              .collect(toSet());
         }
       };
 
@@ -758,13 +737,9 @@
         @Override
         public Iterable<Integer> get(ChangeData input, FillArgs args)
             throws OrmException {
-          return ImmutableSet.copyOf(Iterables.transform(input.draftsByUser(),
-              new Function<Account.Id, Integer>() {
-            @Override
-            public Integer apply(Account.Id account) {
-              return account.get();
-            }
-          }));
+          return input.draftsByUser().stream()
+              .map(Account.Id::get)
+              .collect(toSet());
         }
       };
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
index 996caa7..3e0678d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/IndexedChangeQuery.java
@@ -19,7 +19,6 @@
 import static com.google.gerrit.server.index.change.ChangeField.PROJECT;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.reviewdb.client.Change;
@@ -94,12 +93,9 @@
       public Iterator<ChangeData> iterator() {
         return Iterables.transform(
             rs,
-            new Function<ChangeData, ChangeData>() {
-              @Override
-              public ChangeData apply(ChangeData cd) {
-                fromSource.put(cd, currSource);
-                return cd;
-              }
+            cd -> {
+              fromSource.put(cd, currSource);
+              return cd;
             }).iterator();
       }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterUpdate.java
index e446f9a..942ce88 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/ReindexAfterUpdate.java
@@ -127,7 +127,7 @@
       if (ref.equals(RefNames.REFS_CONFIG)) {
         return asChanges(queryProvider.get().byProjectOpen(project));
       }
-      return asChanges(queryProvider.get().byBranchOpen(
+      return asChanges(queryProvider.get().byBranchNew(
           new Branch.NameKey(project, ref)));
     }
 
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/AbandonedSender.java
index a828098..f1e609e 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/AbandonedSender.java
@@ -51,11 +51,13 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("Abandoned"));
-    appendHtml(soyHtmlTemplate("AbandonedHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("AbandonedHtml"));
+    }
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/AddKeySender.java
index 61ef92d..40492bd 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/AddKeySender.java
@@ -81,7 +81,9 @@
   @Override
   protected void format() throws EmailException {
     appendText(textTemplate("AddKey"));
-    appendHtml(soyHtmlTemplate("AddKeyHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("AddKeyHtml"));
+    }
   }
 
   public String getEmail() {
@@ -123,7 +125,7 @@
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/DeleteReviewerSender.java
index 008f343..ec4a728 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/DeleteReviewerSender.java
@@ -66,7 +66,9 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("DeleteReviewer"));
-    appendHtml(soyHtmlTemplate("DeleteReviewerHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("DeleteReviewerHtml"));
+    }
   }
 
   public List<String> getReviewerNames() {
@@ -87,7 +89,7 @@
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/DeleteVoteSender.java
index 214c655..6257deb 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/DeleteVoteSender.java
@@ -50,11 +50,13 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("DeleteVote"));
-    appendHtml(soyHtmlTemplate("DeleteVoteHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("DeleteVoteHtml"));
+    }
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
index 3c14f2f..2f2fd8c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailSettings.java
@@ -22,11 +22,13 @@
 
 @Singleton
 public class EmailSettings {
+  public final boolean html;
   public final boolean includeDiff;
   public final int maximumDiffSize;
 
   @Inject
   EmailSettings(@GerritServerConfig Config cfg) {
+    html = cfg.getBoolean("sendemail", "html", true);
     includeDiff = cfg.getBoolean("sendemail", "includeDiff", false);
     maximumDiffSize = cfg.getInt("sendemail", "maximumDiffSize", 256 << 10);
   }
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/NewChangeSender.java
index 6adbdb4..05a709d 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/NewChangeSender.java
@@ -68,7 +68,9 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("NewChange"));
-    appendHtml(soyHtmlTemplate("NewChangeHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("NewChangeHtml"));
+    }
   }
 
   public List<String> getReviewerNames() {
@@ -89,7 +91,7 @@
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/OutgoingEmail.java
index 772752a..c3b69e3 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/OutgoingEmail.java
@@ -631,8 +631,12 @@
     return obj != null ? obj.toString() : "";
   }
 
+  protected final boolean useHtml() {
+    return args.settings.html && supportsHtml();
+  }
+
   /** Override this method to enable HTML in a subclass. */
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return false;
   }
 }
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/ReplacePatchSetSender.java
index 916e0da..3f1e356 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/ReplacePatchSetSender.java
@@ -73,7 +73,9 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("ReplacePatchSet"));
-    appendHtml(soyHtmlTemplate("ReplacePatchSetHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("ReplacePatchSetHtml"));
+    }
   }
 
   public List<String> getReviewerNames() {
@@ -97,7 +99,7 @@
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/RestoredSender.java
index 10564cf..45a45c7 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/RestoredSender.java
@@ -50,11 +50,13 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("Restored"));
-    appendHtml(soyHtmlTemplate("RestoredHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("RestoredHtml"));
+    }
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
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/RevertedSender.java
index e383d31..0734a3c 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/RevertedSender.java
@@ -48,11 +48,13 @@
   @Override
   protected void formatChange() throws EmailException {
     appendText(textTemplate("Reverted"));
-    appendHtml(soyHtmlTemplate("RevertedHtml"));
+    if (useHtml()) {
+      appendHtml(soyHtmlTemplate("RevertedHtml"));
+    }
   }
 
   @Override
-  protected boolean useHtml() {
+  protected boolean supportsHtml() {
     return true;
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index a8696a7..00726ba 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -345,54 +345,36 @@
 
   private Map<PatchSetApproval.Key, PatchSetApproval>
       filterPatchSetApprovals() {
-    return limitToValidPatchSets(patchSetApprovals,
-        new Function<PatchSetApproval.Key, PatchSet.Id>() {
-          @Override
-          public PatchSet.Id apply(PatchSetApproval.Key in) {
-            return in.getParentKey();
-          }
-        });
+    return limitToValidPatchSets(
+        patchSetApprovals, PatchSetApproval.Key::getParentKey);
   }
 
   private Map<PatchLineComment.Key, PatchLineComment>
       filterPatchLineComments() {
-    return limitToValidPatchSets(patchLineComments,
-        new Function<PatchLineComment.Key, PatchSet.Id>() {
-          @Override
-          public PatchSet.Id apply(PatchLineComment.Key in) {
-            return in.getParentKey().getParentKey();
-          }
-        });
+    return limitToValidPatchSets(
+        patchLineComments,
+        k -> k.getParentKey().getParentKey());
   }
 
   private <K, V> Map<K, V> limitToValidPatchSets(Map<K, V> in,
-      final Function<K, PatchSet.Id> func) {
+      Function<K, PatchSet.Id> func) {
     return Maps.filterKeys(
         in, Predicates.compose(validPatchSetPredicate(), func));
   }
 
   private Predicate<PatchSet.Id> validPatchSetPredicate() {
-    final Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate();
-    return new Predicate<PatchSet.Id>() {
-      @Override
-      public boolean apply(PatchSet.Id in) {
-        return upToCurrent.apply(in) && patchSets.containsKey(in);
-      }
-    };
+    Predicate<PatchSet.Id> upToCurrent = upToCurrentPredicate();
+    return p -> upToCurrent.apply(p) && patchSets.containsKey(p);
   }
 
   private Collection<ChangeMessage> filterChangeMessages() {
     final Predicate<PatchSet.Id> validPatchSet = validPatchSetPredicate();
-    return Collections2.filter(changeMessages,
-        new Predicate<ChangeMessage>() {
-          @Override
-          public boolean apply(ChangeMessage in) {
-            PatchSet.Id psId = in.getPatchSetId();
-            if (psId == null) {
-              return true;
-            }
-            return validPatchSet.apply(psId);
+    return Collections2.filter(changeMessages, m -> {
+          PatchSet.Id psId = m.getPatchSetId();
+          if (psId == null) {
+            return true;
           }
+          return validPatchSet.apply(psId);
         });
   }
 
@@ -401,13 +383,8 @@
     if (current == null) {
       return Predicates.alwaysFalse();
     }
-    final int max = current.get();
-    return new Predicate<PatchSet.Id>() {
-      @Override
-      public boolean apply(PatchSet.Id in) {
-        return in.get() <= max;
-      }
-    };
+    int max = current.get();
+    return p -> p.get() <= max;
   }
 
   private Map<PatchSet.Id, PatchSet> filterPatchSets() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
index 56454b9..960962f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java
@@ -65,6 +65,7 @@
 import java.util.Set;
 
 public class ChangeNoteUtil {
+  public static final FooterKey FOOTER_ASSIGNEE = new FooterKey("Assignee");
   public static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
   public static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id");
   public static final FooterKey FOOTER_COMMIT = new FooterKey("Commit");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 9ffc70a..9c50c27 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -19,10 +19,9 @@
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
+import static java.util.Comparator.comparing;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
@@ -70,7 +69,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashSet;
@@ -78,28 +76,17 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Predicate;
 
 /** View of a single {@link Change} based on the log of its notes branch. */
 public class ChangeNotes extends AbstractChangeNotes<ChangeNotes> {
   private static final Logger log = LoggerFactory.getLogger(ChangeNotes.class);
 
   static final Ordering<PatchSetApproval> PSA_BY_TIME =
-      Ordering.natural().onResultOf(
-        new Function<PatchSetApproval, Timestamp>() {
-          @Override
-          public Timestamp apply(PatchSetApproval input) {
-            return input.getGranted();
-          }
-        });
+      Ordering.from(comparing(PatchSetApproval::getGranted));
 
   public static final Ordering<ChangeMessage> MESSAGE_BY_TIME =
-      Ordering.natural().onResultOf(
-        new Function<ChangeMessage, Timestamp>() {
-          @Override
-          public Timestamp apply(ChangeMessage input) {
-            return input.getWrittenOn();
-          }
-        });
+      Ordering.from(comparing(ChangeMessage::getWrittenOn));
 
   public static ConfigInvalidException parseException(Change.Id changeId,
       String fmt, Object... args) {
@@ -249,7 +236,7 @@
       if (args.migration.enabled()) {
         for (Change.Id cid : changeIds) {
           ChangeNotes cn = create(db, project, cid);
-          if (cn.getChange() != null && predicate.apply(cn)) {
+          if (cn.getChange() != null && predicate.test(cn)) {
             notes.add(cn);
           }
         }
@@ -259,7 +246,7 @@
       for (Change c : ReviewDbUtil.unwrapDb(db).changes().get(changeIds)) {
         if (c != null && project.equals(c.getDest().getParentKey())) {
           ChangeNotes cn = createFromChangeOnlyWhenNoteDbDisabled(c);
-          if (predicate.apply(cn)) {
+          if (predicate.test(cn)) {
             notes.add(cn);
           }
         }
@@ -275,7 +262,7 @@
           try (Repository repo = args.repoManager.openRepository(project)) {
             List<ChangeNotes> changes = scanNoteDb(repo, db, project);
             for (ChangeNotes cn : changes) {
-              if (predicate.apply(cn)) {
+              if (predicate.test(cn)) {
                 m.put(project, cn);
               }
             }
@@ -284,7 +271,7 @@
       } else {
         for (Change change : ReviewDbUtil.unwrapDb(db).changes().all()) {
           ChangeNotes notes = createFromChangeOnlyWhenNoteDbDisabled(change);
-          if (predicate.apply(notes)) {
+          if (predicate.test(notes)) {
             m.put(change.getProject(), notes);
           }
         }
@@ -400,6 +387,13 @@
   }
 
   /**
+   * @return an Account.Id of the user assigned to this change.
+   */
+  public Account.Id getAssignee() {
+    return state.assignee();
+  }
+
+  /**
    *
    * @return a ImmutableSet of all hashtags for this change sorted in alphabetical order.
    */
@@ -451,16 +445,13 @@
     // failed.
     Multimap<RevId, PatchLineComment> filtered = Multimaps.filterEntries(
         draftCommentNotes.getComments(),
-        new Predicate<Map.Entry<RevId, PatchLineComment>>() {
-          @Override
-          public boolean apply(Map.Entry<RevId, PatchLineComment> in) {
-            for (PatchLineComment c : published.get(in.getKey())) {
-              if (c.getKey().equals(in.getValue().getKey())) {
+        (Map.Entry<RevId, PatchLineComment> e) -> {
+            for (PatchLineComment c : published.get(e.getKey())) {
+              if (c.getKey().equals(e.getValue().getKey())) {
                 return false;
               }
             }
             return true;
-          }
         });
     return ImmutableListMultimap.copyOf(
         filtered);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
index 9efe842..8bf8ca1 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesParser.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.notedb;
 
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
@@ -28,11 +29,11 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
 import static com.google.gerrit.server.notedb.NoteDbTable.CHANGES;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.joining;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Enums;
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
 import com.google.common.base.Optional;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ArrayListMultimap;
@@ -59,7 +60,6 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.ReviewerStatusUpdate;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
@@ -136,6 +136,7 @@
   private String branch;
   private Change.Status status;
   private String topic;
+  private Account.Id assignee;
   private Set<String> hashtags;
   private Timestamp createdOn;
   private Timestamp lastUpdatedOn;
@@ -163,7 +164,7 @@
     allChangeMessages = new ArrayList<>();
     changeMessagesByPatchSet = LinkedListMultimap.create();
     comments = ArrayListMultimap.create();
-    patchSets = Maps.newTreeMap(ReviewDbUtil.intKeyOrdering());
+    patchSets = Maps.newTreeMap(comparing(PatchSet.Id::get));
     deletedPatchSets = new HashSet<>();
     patchSetStates = new HashMap<>();
   }
@@ -210,6 +211,7 @@
         submissionId,
         status,
 
+        assignee,
         hashtags,
         patchSets,
         buildApprovals(),
@@ -317,6 +319,8 @@
 
     parseHashtags(commit);
 
+    parseAssignee(commit);
+
     if (submissionId == null) {
       submissionId = parseSubmissionId(commit);
     }
@@ -473,6 +477,18 @@
     }
   }
 
+  private void parseAssignee(ChangeNotesCommit commit)
+      throws ConfigInvalidException {
+    if (assignee != null) {
+      return;
+    }
+    String assigneeValue = parseOneFooter(commit, FOOTER_ASSIGNEE);
+    if (assigneeValue != null) {
+      PersonIdent ident = RawParseUtils.parsePersonIdent(assigneeValue);
+      assignee = noteUtil.parseIdent(ident, id);
+    }
+  }
+
   private void parseTag(ChangeNotesCommit commit)
       throws ConfigInvalidException {
     tag = null;
@@ -866,13 +882,8 @@
       missing.add(FOOTER_SUBJECT);
     }
     if (!missing.isEmpty()) {
-      throw parseException("Missing footers: " + Joiner.on(", ")
-          .join(Lists.transform(missing, new Function<FooterKey, String>() {
-            @Override
-            public String apply(FooterKey input) {
-              return input.getName();
-            }
-          })));
+      throw parseException("Missing footers: "
+          + missing.stream().map(FooterKey::getName).collect(joining(", ")));
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 988184f..4abb8ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.base.Preconditions.checkNotNull;
+import static java.util.Comparator.comparing;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
@@ -33,7 +34,6 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.ReviewerSet;
 import com.google.gerrit.server.ReviewerStatusUpdate;
 
@@ -59,6 +59,7 @@
     return new AutoValue_ChangeNotesState(
         change.getId(),
         null,
+        null,
         ImmutableSet.<String>of(),
         ImmutableSortedMap.<PatchSet.Id, PatchSet>of(),
         ImmutableListMultimap.<PatchSet.Id, PatchSetApproval>of(),
@@ -84,6 +85,7 @@
       @Nullable String originalSubject,
       @Nullable String submissionId,
       @Nullable Change.Status status,
+      @Nullable Account.Id assignee,
       @Nullable Set<String> hashtags,
       Map<PatchSet.Id, PatchSet> patchSets,
       Multimap<PatchSet.Id, PatchSetApproval> approvals,
@@ -111,8 +113,9 @@
             originalSubject,
             submissionId,
             status),
+        assignee,
         ImmutableSet.copyOf(hashtags),
-        ImmutableSortedMap.copyOf(patchSets, ReviewDbUtil.intKeyOrdering()),
+        ImmutableSortedMap.copyOf(patchSets, comparing(PatchSet.Id::get)),
         ImmutableListMultimap.copyOf(approvals),
         reviewers,
         ImmutableList.copyOf(allPastReviewers),
@@ -153,6 +156,7 @@
   @Nullable abstract ChangeColumns columns();
 
   // Other related to this Change.
+  @Nullable abstract Account.Id assignee();
   abstract ImmutableSet<String> hashtags();
   abstract ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets();
   abstract ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 426569e..39ca57c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -19,6 +19,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_ASSIGNEE;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_BRANCH;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_CHANGE_ID;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_COMMIT;
@@ -32,6 +33,7 @@
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_SUBMITTED_WITH;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TAG;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_TOPIC;
+import static java.util.Comparator.comparing;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.google.common.annotations.VisibleForTesting;
@@ -48,7 +50,6 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.AnonymousCowardName;
@@ -56,6 +57,7 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.util.LabelVote;
 import com.google.gerrit.server.util.RequestId;
+import com.google.gwtorm.client.IntKey;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
@@ -122,6 +124,7 @@
   private String submissionId;
   private String topic;
   private String commit;
+  private Account.Id assignee;
   private Set<String> hashtags;
   private String changeMessage;
   private String tag;
@@ -173,7 +176,7 @@
 
   private static Table<String, Account.Id, Optional<Short>> approvals(
       Comparator<String> nameComparator) {
-    return TreeBasedTable.create(nameComparator, ReviewDbUtil.intKeyOrdering());
+    return TreeBasedTable.create(nameComparator, comparing(IntKey::get));
   }
 
   @AssistedInject
@@ -379,6 +382,10 @@
     this.hashtags = hashtags;
   }
 
+  public void setAssignee(Account.Id assignee) {
+    this.assignee = assignee;
+  }
+
   public Map<Account.Id, ReviewerStateInternal> getReviewers() {
     return reviewers;
   }
@@ -547,6 +554,11 @@
       addFooter(msg, FOOTER_COMMIT, commit);
     }
 
+    if (assignee != null) {
+      addFooter(msg, FOOTER_ASSIGNEE);
+      addIdent(msg, assignee).append('\n');
+    }
+
     Joiner comma = Joiner.on(',');
     if (hashtags != null) {
       addFooter(msg, FOOTER_HASHTAGS, comma.join(hashtags));
@@ -647,6 +659,7 @@
         && status == null
         && submissionId == null
         && submitRecords == null
+        && assignee == null
         && hashtags == null
         && topic == null
         && commit == null
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
index a56d02a..4aed71e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java
@@ -18,6 +18,7 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static java.util.Comparator.comparing;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
@@ -30,7 +31,6 @@
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.git.RefCache;
 
 import org.eclipse.jgit.lib.ObjectId;
@@ -163,7 +163,7 @@
   public static String toString(ObjectId changeMetaId,
       Map<Account.Id, ObjectId> draftIds) {
     List<Account.Id> accountIds = Lists.newArrayList(draftIds.keySet());
-    Collections.sort(accountIds, ReviewDbUtil.intKeyOrdering());
+    Collections.sort(accountIds, comparing(Account.Id::get));
     StringBuilder sb = new StringBuilder(changeMetaId.name());
     for (Account.Id id : accountIds) {
       sb.append(',')
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
index af463d7..be0a689 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteBuilder.java
@@ -17,10 +17,9 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.gerrit.server.PatchLineCommentsUtil.PLC_ORDER;
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toList;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
@@ -134,13 +133,10 @@
     }
 
     RevisionNoteData data = new RevisionNoteData();
-    data.comments = FluentIterable.from(PLC_ORDER.sortedCopy(comments.values()))
-        .transform(new Function<PatchLineComment, RevisionNoteData.Comment>() {
-          @Override
-          public RevisionNoteData.Comment apply(PatchLineComment plc) {
-            return new RevisionNoteData.Comment(plc, noteUtil.getServerId());
-          }
-        }).toList();
+    data.comments = comments.values().stream()
+        .sorted(PLC_ORDER)
+        .map(plc -> new RevisionNoteData.Comment(plc, noteUtil.getServerId()))
+        .collect(toList());
     data.pushCert = pushCert;
 
     try (OutputStreamWriter osw = new OutputStreamWriter(out)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
index f4c7d8a..8d020bdb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/RevisionNoteData.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.notedb;
 
-import com.google.common.base.Function;
+import static java.util.stream.Collectors.toList;
+
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
@@ -99,7 +99,7 @@
     String revId;
     String serverId;
 
-    public Comment(PatchLineComment plc, String serverId) {
+    Comment(PatchLineComment plc, String serverId) {
       key = new CommentKey(plc.getKey());
       lineNbr = plc.getLine();
       author = new Identity(plc.getAuthor());
@@ -134,15 +134,15 @@
   List<Comment> comments;
 
   ImmutableList<PatchLineComment> exportComments(
-      final PatchLineComment.Status status) {
+      PatchLineComment.Status status) {
     return ImmutableList.copyOf(
-        Lists.transform(comments, new Function<Comment, PatchLineComment>() {
-          @Override
-          public PatchLineComment apply(Comment c) {
-            PatchLineComment plc = c.export();
-            plc.setStatus(status);
-            return plc;
-          }
-        }));
+        comments.stream()
+            .map(
+                c -> {
+                  PatchLineComment plc = c.export();
+                  plc.setStatus(status);
+                  return plc;
+                })
+            .collect(toList()));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
index d1aabcd..3389e2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java
@@ -20,13 +20,13 @@
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
+
 import static java.util.concurrent.TimeUnit.SECONDS;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
 import com.google.common.base.Splitter;
 import com.google.common.collect.ArrayListMultimap;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Multimap;
@@ -391,18 +391,15 @@
 
   private static List<PatchLineComment> getPatchLineComments(ChangeBundle bundle,
       final PatchSet ps) {
-    return FluentIterable.from(bundle.getPatchLineComments())
-        .filter(new Predicate<PatchLineComment>() {
-          @Override
-          public boolean apply(PatchLineComment in) {
-            return in.getPatchSetId().equals(ps.getId());
-          }
-        }).toSortedList(PatchLineCommentsUtil.PLC_ORDER);
+    return bundle.getPatchLineComments().stream()
+        .filter(c -> c.getPatchSetId().equals(ps.getId()))
+        .sorted(PatchLineCommentsUtil.PLC_ORDER)
+        .collect(toList());
   }
 
   private void sortAndFillEvents(Change change, Change noteDbChange,
       List<Event> events, Integer minPsNum) {
-    Collections.sort(events);
+    new EventSorter(events).sort();
     events.add(new FinalUpdatesEvent(change, noteDbChange));
 
     // Ensure the first event in the list creates the change, setting the author
@@ -414,23 +411,39 @@
       events.add(0, new CreateChangeEvent(change, minPsNum));
     }
 
-    // Fill in any missing patch set IDs using the latest patch set of the
-    // change at the time of the event, because NoteDb can't represent actions
-    // with no associated patch set ID. This workaround is as if a user added a
-    // ChangeMessage on the change by replying from the latest patch set.
+    // Final pass to correct some inconsistencies.
+    //
+    // First, fill in any missing patch set IDs using the latest patch set of
+    // the change at the time of the event, because NoteDb can't represent
+    // actions with no associated patch set ID. This workaround is as if a user
+    // added a ChangeMessage on the change by replying from the latest patch
+    // set.
     //
     // Start with the first patch set that actually exists. If there are no
     // patch sets at all, minPsNum will be null, so just bail and use 1 as the
     // patch set ID. The corresponding patch set won't exist, but this change is
     // probably corrupt anyway, as deleting the last draft patch set should have
     // deleted the whole change.
+    //
+    // Second, ensure timestamps are nondecreasing, by copying the previous
+    // timestamp if this happens. This assumes that the only way this can happen
+    // is due to dependency constraints, and it is ok to give an event the same
+    // timestamp as one of its dependencies.
     int ps = firstNonNull(minPsNum, 1);
-    for (Event e : events) {
+    for (int i = 0; i < events.size(); i++) {
+      Event e = events.get(i);
       if (e.psId == null) {
         e.psId = new PatchSet.Id(change.getId(), ps);
       } else {
         ps = Math.max(ps, e.psId.get());
       }
+
+      if (i > 0) {
+        Event p = events.get(i - 1);
+        if (e.when.before(p.when)) {
+          e.when = p.when;
+        }
+      }
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
index 18ce302..a72e4fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java
@@ -28,6 +28,8 @@
 
 import java.io.IOException;
 import java.sql.Timestamp;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 
 abstract class Event implements Comparable<Event> {
@@ -35,9 +37,10 @@
   // hierarchy.
 
   final Account.Id who;
-  final Timestamp when;
   final String tag;
   final boolean predatesChange;
+  final List<Event> deps;
+  Timestamp when;
   PatchSet.Id psId;
 
   protected Event(PatchSet.Id psId, Account.Id who, Timestamp when,
@@ -48,6 +51,7 @@
     // Truncate timestamps at the change's createdOn timestamp.
     predatesChange = when.before(changeCreatedOn);
     this.when = predatesChange ? changeCreatedOn : when;
+    deps = new ArrayList<>();
   }
 
   protected void checkUpdate(AbstractChangeUpdate update) {
@@ -62,6 +66,10 @@
         who, update.getNullableAccountId());
   }
 
+  void addDep(Event e) {
+    deps.add(e);
+  }
+
   /**
    * @return whether this event type must be unique per {@link ChangeUpdate},
    *     i.e. there may be at most one of this type.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
new file mode 100644
index 0000000..2ab4c00
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventSorter.java
@@ -0,0 +1,107 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb.rebuild;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.HashMultimap;
+import com.google.common.collect.ListMultimap;
+import com.google.common.collect.SetMultimap;
+
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+/**
+ * Helper to sort a list of events.
+ * <p>
+ * Events are sorted in two passes:
+ * <ol>
+ * <li>Sort by natural order (timestamp, patch set, author, etc.)</li>
+ * <li>Postpone any events with dependencies to occur only after all of their
+ *   dependencies, where this violates natural order.</li>
+ * </ol>
+ *
+ * {@link #sort()} modifies the event list in place (similar to {@link
+ * Collections#sort(List)}), but does not modify any event. In particular,
+ * events might end up out of order with respect to timestamp; callers are
+ * responsible for adjusting timestamps later if they prefer monotonicity.
+ */
+class EventSorter {
+  private final List<Event> out;
+  private final LinkedHashSet<Event> sorted;
+  private ListMultimap<Event, Event> waiting;
+  private SetMultimap<Event, Event> deps;
+
+  EventSorter(List<Event> events) {
+    LinkedHashSet<Event> all = new LinkedHashSet<>(events);
+    out = events;
+
+    for (Event e : events) {
+      for (Event d : e.deps) {
+        checkArgument(all.contains(d), "dep %s of %s not in input list", d, e);
+      }
+    }
+
+    all.clear();
+    sorted = all; // Presized.
+  }
+
+  void sort() {
+    // First pass: sort by natural order.
+    Collections.sort(out);
+
+    // Populate waiting map after initial sort to preserve natural order.
+    waiting = ArrayListMultimap.create();
+    deps = HashMultimap.create();
+    for (Event e : out) {
+      for (Event d : e.deps) {
+        deps.put(e, d);
+        waiting.put(d, e);
+      }
+    }
+
+    // Second pass: enforce dependencies.
+    int size = out.size();
+    for (Event e : out) {
+      process(e);
+    }
+    checkState(sorted.size() == size,
+        "event sort expected %s elements, got %s", size, sorted.size());
+
+    // Modify out in-place a la Collections#sort.
+    out.clear();
+    out.addAll(sorted);
+  }
+
+  void process(Event e) {
+    if (sorted.contains(e)) {
+      return;
+    }
+    // If all events that e depends on have been emitted:
+    //  - e can be emitted.
+    //  - remove e from the dependency set of all events waiting on e, and then
+    //    re-process those events in case they can now be emitted.
+    if (deps.get(e).isEmpty()) {
+      sorted.add(e);
+      for (Event w : waiting.get(e)) {
+        deps.get(w).remove(e);
+        process(w);
+      }
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 2fa43bb..0ae9a99 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -16,12 +16,12 @@
 package com.google.gerrit.server.patch;
 
 import static com.google.common.base.Preconditions.checkArgument;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.stream.Collectors.toSet;
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
-import com.google.common.base.Function;
 import com.google.common.base.Throwables;
-import com.google.common.collect.FluentIterable;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.Project;
@@ -70,6 +70,7 @@
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.TimeoutException;
+import java.util.stream.Stream;
 
 public class PatchListLoader implements Callable<PatchList> {
   static final Logger log = LoggerFactory.getLogger(PatchListLoader.class);
@@ -179,16 +180,11 @@
             key.getNewId(), key.getWhitespace());
         PatchListKey oldKey = PatchListKey.againstDefaultBase(
             key.getOldId(), key.getWhitespace());
-        paths = FluentIterable
-            .from(patchListCache.get(newKey, project).getPatches())
-            .append(patchListCache.get(oldKey, project).getPatches())
-            .transform(new Function<PatchListEntry, String>() {
-              @Override
-              public String apply(PatchListEntry entry) {
-                return entry.getNewName();
-              }
-            })
-            .toSet();
+        paths = Stream.concat(
+                patchListCache.get(newKey, project).getPatches().stream(),
+                patchListCache.get(oldKey, project).getPatches().stream())
+            .map(PatchListEntry::getNewName)
+            .collect(toSet());
       }
 
       int cnt = diffEntries.size();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index 1f612a3..be9bbad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.collect.Iterables.transform;
 
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.base.Predicates;
 import com.google.common.base.Strings;
@@ -59,15 +58,6 @@
 public class JarScanner implements PluginContentScanner {
   private static final int SKIP_ALL = ClassReader.SKIP_CODE
       | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
-  private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
-      new Function<ClassData, ExtensionMetaData>() {
-        @Override
-        public ExtensionMetaData apply(ClassData classData) {
-          return new ExtensionMetaData(classData.className,
-              classData.annotationValue);
-        }
-      };
-
   private final JarFile jarFile;
 
   public JarScanner(Path src) throws IOException {
@@ -128,8 +118,11 @@
       Collection<ClassData> values =
           firstNonNull(discoverdData, Collections.<ClassData> emptySet());
 
-      result.put(annotoation,
-          transform(values, CLASS_DATA_TO_EXTENSION_META_DATA));
+      result.put(
+          annotoation,
+          transform(
+              values,
+              cd -> new ExtensionMetaData(cd.className, cd.annotationValue)));
     }
 
     return result.build();
@@ -307,15 +300,12 @@
   public Enumeration<PluginEntry> entries() {
     return Collections.enumeration(Lists.transform(
         Collections.list(jarFile.entries()),
-        new Function<JarEntry, PluginEntry>() {
-          @Override
-          public PluginEntry apply(JarEntry jarEntry) {
-            try {
-              return resourceOf(jarEntry);
-            } catch (IOException e) {
-              throw new IllegalArgumentException("Cannot convert jar entry "
-                  + jarEntry + " to a resource", e);
-            }
+        jarEntry -> {
+          try {
+            return resourceOf(jarEntry);
+          } catch (IOException e) {
+            throw new IllegalArgumentException("Cannot convert jar entry "
+                + jarEntry + " to a resource", e);
           }
         }));
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
index cf38310..e89eb7d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/MultipleProvidersForPluginException.java
@@ -14,11 +14,10 @@
 
 package com.google.gerrit.server.plugins;
 
-import com.google.common.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Iterables;
+import static java.util.stream.Collectors.joining;
 
 import java.nio.file.Path;
+import java.util.stream.StreamSupport;
 
 class MultipleProvidersForPluginException extends IllegalArgumentException {
   private static final long serialVersionUID = 1L;
@@ -32,14 +31,8 @@
 
   private static String providersListToString(
       Iterable<ServerPluginProvider> providersHandlers) {
-    Iterable<String> providerNames =
-        Iterables.transform(providersHandlers,
-            new Function<ServerPluginProvider, String>() {
-              @Override
-              public String apply(ServerPluginProvider provider) {
-                return provider.getProviderPluginName();
-              }
-            });
-    return Joiner.on(", ").join(providerNames);
+    return StreamSupport.stream(providersHandlers.spliterator(), false)
+        .map(ServerPluginProvider::getProviderPluginName)
+        .collect(joining(", "));
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index e170510..5667003 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -17,7 +17,6 @@
 import com.google.common.base.CharMatcher;
 import com.google.common.base.Joiner;
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.ImmutableList;
@@ -720,12 +719,9 @@
 
   private static Iterable<Path> filterDisabledPlugins(
       Collection<Path> paths) {
-    return Iterables.filter(paths, new Predicate<Path>() {
-      @Override
-      public boolean apply(Path p) {
-        return !p.getFileName().toString().endsWith(".disabled");
-      }
-    });
+    return Iterables.filter(
+        paths,
+        p -> !p.getFileName().toString().endsWith(".disabled"));
   }
 
   public String getGerritPluginName(Path srcPath) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
index f0c2b78..ce97a83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/BanCommit.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.errors.PermissionDeniedException;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -91,14 +90,7 @@
     if (commits == null || commits.isEmpty()) {
       return null;
     }
-
-    return Lists.transform(commits,
-        new Function<ObjectId, String>() {
-          @Override
-          public String apply(ObjectId id) {
-            return id.getName();
-          }
-        });
+    return Lists.transform(commits, ObjectId::getName);
   }
 
   public static class BanResultInfo {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index 5bc7f40..d18ee66 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -352,6 +352,16 @@
     return false;
   }
 
+  /** Is this user assigned to this change? */
+  public boolean isAssignee() {
+    Account.Id currentAssignee = notes.getAssignee();
+    if (currentAssignee != null && getUser().isIdentifiedUser()) {
+      Account.Id id = getUser().getAccountId();
+      return id.equals(currentAssignee);
+    }
+    return false;
+  }
+
   /** Is this user a reviewer for the change? */
   public boolean isReviewer(ReviewDb db) throws OrmException {
     return isReviewer(db, null);
@@ -414,6 +424,13 @@
     return getRefControl().canForceEditTopicName();
   }
 
+  public boolean canEditAssignee() {
+    return isOwner()
+        || getProjectControl().isOwner()
+        || getRefControl().canEditAssignee()
+        || isAssignee();
+  }
+
   /** Can this user edit the hashtag name? */
   public boolean canEditHashtags() {
     return isOwner() // owner (aka creator) of the change can edit hashtags
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
index 1735c3d..8718a9b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetReflog.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.base.Function;
 import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.common.GitPerson;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -102,12 +101,7 @@
           }
         }
       }
-      return Lists.transform(entries, new Function<ReflogEntry, ReflogEntryInfo>() {
-        @Override
-        public ReflogEntryInfo apply(ReflogEntry e) {
-          return new ReflogEntryInfo(e);
-        }
-      });
+      return Lists.transform(entries, ReflogEntryInfo::new);
     }
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
index bf17a37..1ea0c62 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListProjects.java
@@ -16,7 +16,6 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -445,13 +444,8 @@
     } else if (matchSubstring != null) {
       checkMatchOptions(matchPrefix == null && matchRegex == null);
       return Iterables.filter(projectCache.all(),
-          new Predicate<Project.NameKey>() {
-            @Override
-            public boolean apply(Project.NameKey in) {
-              return in.get().toLowerCase(Locale.US)
-                  .contains(matchSubstring.toLowerCase(Locale.US));
-            }
-          });
+          p -> p.get().toLowerCase(Locale.US)
+              .contains(matchSubstring.toLowerCase(Locale.US)));
     } else if (matchRegex != null) {
       checkMatchOptions(matchPrefix == null && matchSubstring == null);
       RegexListSearcher<Project.NameKey> searcher;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 68d236e..bb6c62f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.common.data.PermissionRule.Action.ALLOW;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.base.Function;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -72,6 +71,7 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.Function;
 
 /** Cached information on a project. */
 public class ProjectState {
@@ -378,75 +378,35 @@
   }
 
   public boolean isUseContributorAgreements() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getUseContributorAgreements();
-      }
-    });
+    return getInheritableBoolean(Project::getUseContributorAgreements);
   }
 
   public boolean isUseContentMerge() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getUseContentMerge();
-      }
-    });
+    return getInheritableBoolean(Project::getUseContentMerge);
   }
 
   public boolean isUseSignedOffBy() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getUseSignedOffBy();
-      }
-    });
+    return getInheritableBoolean(Project::getUseSignedOffBy);
   }
 
   public boolean isRequireChangeID() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getRequireChangeID();
-      }
-    });
+    return getInheritableBoolean(Project::getRequireChangeID);
   }
 
   public boolean isCreateNewChangeForAllNotInTarget() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getCreateNewChangeForAllNotInTarget();
-      }
-    });
+    return getInheritableBoolean(Project::getCreateNewChangeForAllNotInTarget);
   }
 
   public boolean isEnableSignedPush() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getEnableSignedPush();
-      }
-    });
+    return getInheritableBoolean(Project::getEnableSignedPush);
   }
 
   public boolean isRequireSignedPush() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getRequireSignedPush();
-      }
-    });
+    return getInheritableBoolean(Project::getRequireSignedPush);
   }
 
   public boolean isRejectImplicitMerges() {
-    return getInheritableBoolean(new Function<Project, InheritableBoolean>() {
-      @Override
-      public InheritableBoolean apply(Project input) {
-        return input.getRejectImplicitMerges();
-      }
-    });
+    return getInheritableBoolean(Project::getRejectImplicitMerges);
   }
 
   public LabelTypes getLabelTypes() {
@@ -551,7 +511,8 @@
     return Files.exists(p) ? new String(Files.readAllBytes(p), UTF_8) : null;
   }
 
-  private boolean getInheritableBoolean(Function<Project, InheritableBoolean> func) {
+  private boolean getInheritableBoolean(
+      Function<Project, InheritableBoolean> func) {
     for (ProjectState s : tree()) {
       switch (func.apply(s.getProject())) {
         case TRUE:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
index 0365855..3314309 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/RefControl.java
@@ -447,6 +447,10 @@
     return canPerform(Permission.EDIT_HASHTAGS);
   }
 
+  public boolean canEditAssignee() {
+    return canPerform(Permission.EDIT_ASSIGNEE);
+  }
+
   /** @return true if this user can force edit topic names. */
   public boolean canForceEditTopicName() {
     return canForcePerform(Permission.EDIT_TOPIC_NAME);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
index 01aacfb..cc215d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetParent.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -124,13 +123,10 @@
             + " not found");
       }
 
-      if (Iterables.tryFind(parent.tree(), new Predicate<ProjectState>() {
-        @Override
-        public boolean apply(ProjectState input) {
-          return input.getProject().getNameKey()
-              .equals(ctl.getProject().getNameKey());
-        }
-      }).isPresent()) {
+      if (Iterables.tryFind(parent.tree(), p -> {
+            return p.getProject().getNameKey()
+                .equals(ctl.getProject().getNameKey());
+          }).isPresent()) {
         throw new ResourceConflictException("cycle exists between "
             + ctl.getProject().getName() + " and "
             + parent.getProject().getName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
index 168be5d..9f0bf89 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/AndSource.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 
-import com.google.common.base.Function;
 import com.google.common.base.Throwables;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -157,12 +156,7 @@
 
   private Iterable<T> buffer(ResultSet<T> scanner) {
     return FluentIterable.from(Iterables.partition(scanner, 50))
-        .transformAndConcat(new Function<List<T>, List<T>>() {
-          @Override
-          public List<T> apply(List<T> buffer) {
-            return transformBuffer(buffer);
-          }
-        });
+        .transformAndConcat(this::transformBuffer);
   }
 
   protected List<T> transformBuffer(List<T> buffer) throws OrmRuntimeException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
index 0288cb2..40fb3b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/account/AccountQueryBuilder.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.query.account;
 
-import com.google.common.base.Function;
 import com.google.common.base.Splitter;
 import com.google.common.collect.Lists;
 import com.google.common.primitives.Ints;
@@ -124,13 +123,9 @@
 
   public Predicate<AccountState> defaultQuery(String query) {
     return Predicate.and(
-        Lists.transform(Splitter.on(' ').omitEmptyStrings().splitToList(query),
-            new Function<String, Predicate<AccountState>>() {
-              @Override
-              public Predicate<AccountState> apply(String s) {
-                return defaultField(s);
-              }
-            }));
+        Lists.transform(
+            Splitter.on(' ').omitEmptyStrings().splitToList(query),
+            this::defaultField));
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 035f974..c4e8bd7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -904,14 +904,11 @@
    * @throws OrmException an error occurred reading the database.
    */
   public Collection<PatchSet> visiblePatchSets() throws OrmException {
-    Predicate<PatchSet> predicate = new Predicate<PatchSet>() {
-      @Override
-      public boolean apply(PatchSet input) {
-        try {
-          return changeControl().isPatchVisible(input, db);
-        } catch (OrmException e) {
-          return false;
-        }
+    Predicate<PatchSet> predicate = ps -> {
+      try {
+        return changeControl().isPatchVisible(ps, db);
+      } catch (OrmException e) {
+        return false;
       }
     };
     return FluentIterable.from(patchSets()).filter(predicate).toList();
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 d7c7730..f697d15 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
@@ -16,9 +16,9 @@
 
 import static com.google.gerrit.reviewdb.client.Change.CHANGE_ID_PATTERN;
 import static com.google.gerrit.server.query.change.ChangeData.asChanges;
+import static java.util.stream.Collectors.toSet;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.base.Optional;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
@@ -26,7 +26,6 @@
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NotSignedInException;
-import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountGroup;
@@ -632,14 +631,9 @@
     // expand a group predicate into multiple user predicates
     if (group != null) {
       Set<Account.Id> allMembers =
-          new HashSet<>(Lists.transform(
-              args.listMembers.get().setRecursive(true).apply(group),
-              new Function<AccountInfo, Account.Id>() {
-                @Override
-                public Account.Id apply(AccountInfo accountInfo) {
-                  return new Account.Id(accountInfo._accountId);
-                }
-              }));
+          args.listMembers.get().setRecursive(true).apply(group).stream()
+              .map(a -> new Account.Id(a._accountId))
+              .collect(toSet());
       int maxLimit = args.indexConfig.maxLimit();
       if (allMembers.size() > maxLimit) {
         // limit the number of query terms otherwise Gerrit will barf
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index 5b1b1c6..a1f0dc8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -22,7 +22,6 @@
 import static com.google.gerrit.server.query.change.ChangeStatusPredicate.open;
 
 import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Function;
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Sets;
@@ -152,6 +151,14 @@
         open()));
   }
 
+  public List<ChangeData> byBranchNew(Branch.NameKey branch)
+      throws OrmException {
+    return query(and(
+        ref(branch),
+        project(branch.getParentKey()),
+        status(Change.Status.NEW)));
+  }
+
   public Iterable<ChangeData> byCommitsOnBranchNotMerged(Repository repo,
       ReviewDb db, Branch.NameKey branch, Collection<String> hashes)
       throws OrmException, IOException {
@@ -191,20 +198,14 @@
       }
     }
 
-    return Lists.transform(notesFactory.create(db, branch.getParentKey(),
-        changeIds, new com.google.common.base.Predicate<ChangeNotes>() {
-          @Override
-          public boolean apply(ChangeNotes notes) {
-            Change c = notes.getChange();
+    List<ChangeNotes> notes = notesFactory.create(
+        db, branch.getParentKey(), changeIds,
+        cn -> {
+            Change c = cn.getChange();
             return c.getDest().equals(branch)
                 && c.getStatus() != Change.Status.MERGED;
-          }
-        }), new Function<ChangeNotes, ChangeData>() {
-          @Override
-          public ChangeData apply(ChangeNotes notes) {
-            return changeDataFactory.create(db, notes);
-          }
         });
+    return Lists.transform(notes, n -> changeDataFactory.create(db, n));
   }
 
   private Iterable<ChangeData> byCommitsOnBranchNotMergedFromIndex(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
index 53ed5ac..2d9714f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/ProjectConfigSchemaUpdate.java
@@ -15,9 +15,8 @@
 package com.google.gerrit.server.schema;
 
 import static com.google.gerrit.server.git.ProjectConfig.ACCESS;
+import static java.util.stream.Collectors.toList;
 
-import com.google.common.base.Function;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.PermissionRule;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -67,18 +66,16 @@
       Set<String> names = config.getNames(ACCESS, subsection);
       if (names.contains(name)) {
         List<String> values =
-            Arrays.asList(config.getStringList(ACCESS, subsection, name));
-        values = Lists.transform(values, new Function<String, String>() {
-          @Override
-          public String apply(String ruleString) {
-            PermissionRule rule = PermissionRule.fromString(ruleString, false);
-            if (rule.getForce()) {
-              rule.setForce(false);
-              updated = true;
-            }
-            return rule.asString(false);
-          }
-        });
+            Arrays.stream(config.getStringList(ACCESS, subsection, name))
+                .map(r -> {
+                      PermissionRule rule = PermissionRule.fromString(r, false);
+                      if (rule.getForce()) {
+                        rule.setForce(false);
+                        updated = true;
+                      }
+                      return rule.asString(false);
+                    })
+                .collect(toList());
         config.setStringList(ACCESS, subsection, name, values);
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java
index 16f0bcf..895c905 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_124.java
@@ -14,7 +14,8 @@
 
 package com.google.gerrit.server.schema;
 
-import com.google.common.base.Function;
+import static java.util.Comparator.comparing;
+
 import com.google.common.base.Strings;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.Multimap;
@@ -124,13 +125,7 @@
 
   private Collection<AccountSshKey> fixInvalidSequenceNumbers(
       Collection<AccountSshKey> keys) {
-    Ordering<AccountSshKey> o =
-        Ordering.natural().onResultOf(new Function<AccountSshKey, Integer>() {
-          @Override
-          public Integer apply(AccountSshKey sshKey) {
-            return sshKey.getKey().get();
-          }
-        });
+    Ordering<AccountSshKey> o = Ordering.from(comparing(k -> k.getKey().get()));
     List<AccountSshKey> fixedKeys = new ArrayList<>(keys);
     AccountSshKey minKey = o.min(keys);
     while (minKey.getKey().get() <= 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
index 0a99a8a..bbc97df 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/RegexListSearcher.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.common.base.Function;
-import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
@@ -94,12 +93,7 @@
 
     return Iterables.filter(
         list.subList(begin, end),
-        new Predicate<T>() {
-          @Override
-          public boolean apply(T in) {
-            return pattern.run(RegexListSearcher.this.apply(in));
-          }
-        });
+        x -> pattern.run(apply(x)));
   }
 
   public boolean hasMatch(List<T> list) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index 6993f5b..7a1dbf7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -24,7 +24,6 @@
 import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 import static org.junit.Assert.fail;
 
-import com.google.common.base.Function;
 import com.google.common.base.Throwables;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
@@ -34,7 +33,6 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Ordering;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Account;
@@ -47,8 +45,10 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.RevId;
+import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.ReviewerSet;
+import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.util.RequestId;
 import com.google.gerrit.testutil.TestChanges;
@@ -79,6 +79,9 @@
   @Inject
   private ChangeNoteUtil noteUtil;
 
+  @Inject
+  private @GerritServerId String serverId;
+
   @Test
   public void tagChangeMessage() throws Exception {
     String tag = "jenkins";
@@ -377,13 +380,9 @@
     update.commit();
 
     ChangeNotes notes = newNotes(c);
-    List<PatchSetApproval> approvals = Ordering.natural().onResultOf(
-        new Function<PatchSetApproval, Integer>() {
-          @Override
-          public Integer apply(PatchSetApproval in) {
-            return in.getAccountId().get();
-          }
-        }).sortedCopy(notes.getApprovals().get(c.currentPatchSetId()));
+    List<PatchSetApproval> approvals = ReviewDbUtil.intKeyOrdering()
+        .onResultOf(PatchSetApproval::getAccountId)
+        .sortedCopy(notes.getApprovals().get(c.currentPatchSetId()));
     assertThat(approvals).hasSize(2);
 
     assertThat(approvals.get(0).getAccountId())
@@ -562,6 +561,47 @@
   }
 
   @Test
+  public void assigneeCommit() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setAssignee(otherUserId);
+    ObjectId result = update.commit();
+    assertThat(result).isNotNull();
+    try (RevWalk rw = new RevWalk(repo)) {
+      RevCommit commit = rw.parseCommit(update.getResult());
+      rw.parseBody(commit);
+      String strIdent =
+          otherUser.getName()
+          + " <"
+          + otherUserId
+          + "@"
+          + serverId
+          + ">";
+      assertThat(commit.getFullMessage())
+          .contains("Assignee: " + strIdent);
+    }
+  }
+
+  @Test
+  public void assigneeChangeNotes() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setAssignee(otherUserId);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    assertThat(notes.getAssignee()).isEqualTo(otherUserId);
+
+    update = newUpdate(c, changeOwner);
+    update.setAssignee(changeOwner.getAccountId());
+    update.commit();
+
+    notes = newNotes(c);
+    assertThat(notes.getAssignee()).isEqualTo(changeOwner.getAccountId());
+
+  }
+
+  @Test
   public void hashtagCommit() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
new file mode 100644
index 0000000..969adf0
--- /dev/null
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/rebuild/EventSorterTest.java
@@ -0,0 +1,177 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.notedb.rebuild;
+
+import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.fail;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.testutil.TestTimeUtil;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.sql.Timestamp;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+public class EventSorterTest {
+  private class TestEvent extends Event {
+    protected TestEvent(Timestamp when) {
+      super(
+          new PatchSet.Id(new Change.Id(1), 1),
+          new Account.Id(1000),
+          when, changeCreatedOn, null);
+    }
+
+    @Override
+    boolean uniquePerUpdate() {
+      return false;
+    }
+
+    @Override
+    void apply(ChangeUpdate update) {
+      throw new UnsupportedOperationException();
+    }
+  }
+
+  private Timestamp changeCreatedOn;
+
+  @Before
+  public void setUp() {
+    TestTimeUtil.resetWithClockStep(10, TimeUnit.SECONDS);
+    changeCreatedOn = TimeUtil.nowTs();
+  }
+
+  @Test
+  public void naturalSort() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+
+    List<Event> events = events(e2, e1, e3);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e1, e2, e3).inOrder();
+  }
+
+  @Test
+  public void topoSortNoChange() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    e2.addDep(e1);
+
+    List<Event> events = events(e2, e1, e3);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e1, e2, e3).inOrder();
+  }
+
+  @Test
+  public void topoSortOneDep() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    e1.addDep(e2);
+
+    List<Event> events = events(e2, e3, e1);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e2, e1, e3).inOrder();
+  }
+
+  @Test
+  public void topoSortChainOfDeps() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    Event e4 = new TestEvent(TimeUtil.nowTs());
+    e1.addDep(e2);
+    e2.addDep(e3);
+    e3.addDep(e4);
+
+    List<Event> events = events(e1, e2, e3, e4);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e4, e3, e2, e1).inOrder();
+  }
+
+  @Test
+  public void topoSortMultipleDeps() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    Event e4 = new TestEvent(TimeUtil.nowTs());
+    e1.addDep(e2);
+    e1.addDep(e4);
+    e2.addDep(e3);
+
+    // Processing 3 pops 2, processing 4 pops 1.
+    List<Event> events = events(e2, e3, e1, e4);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e3, e2, e4, e1).inOrder();
+  }
+
+  @Test
+  public void topoSortMultipleDepsPreservesNaturalOrder() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    Event e4 = new TestEvent(TimeUtil.nowTs());
+    e1.addDep(e4);
+    e2.addDep(e4);
+    e3.addDep(e4);
+
+    // Processing 4 pops 1, 2, 3 in natural order.
+    List<Event> events = events(e4, e3, e2, e1);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e4, e1, e2, e3).inOrder();
+  }
+
+  @Test
+  public void topoSortCycle() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+
+    // Implementation is not really defined, but infinite looping would be bad.
+    // According to current implementation details, 2 pops 1, 1 pops 2 which was
+    // already seen.
+    List<Event> events = events(e2, e1);
+    new EventSorter(events).sort();
+    assertThat(events).containsExactly(e1, e2).inOrder();
+  }
+
+  @Test
+  public void topoSortDepNotInInputList() {
+    Event e1 = new TestEvent(TimeUtil.nowTs());
+    Event e2 = new TestEvent(TimeUtil.nowTs());
+    Event e3 = new TestEvent(TimeUtil.nowTs());
+    e1.addDep(e3);
+
+    List<Event> events = events(e2, e1);
+    try {
+      new EventSorter(events).sort();
+      fail("expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // Expected.
+    }
+  }
+
+  private List<Event> events(Event... es) {
+    return Lists.newArrayList(es);
+  }
+}
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 f7b3b11..b65d49d 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
@@ -17,7 +17,6 @@
 import static com.google.common.truth.Truth.assertThat;
 import static org.junit.Assert.fail;
 
-import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.extensions.api.GerritApi;
@@ -476,22 +475,10 @@
   }
 
   protected static Iterable<Integer> ids(AccountInfo... accounts) {
-    return FluentIterable.from(Arrays.asList(accounts)).transform(
-        new Function<AccountInfo, Integer>() {
-          @Override
-          public Integer apply(AccountInfo in) {
-            return in._accountId;
-          }
-        });
+    return FluentIterable.of(accounts).transform(a -> a._accountId);
   }
 
   protected static Iterable<Integer> ids(Iterable<AccountInfo> accounts) {
-    return FluentIterable.from(accounts).transform(
-        new Function<AccountInfo, Integer>() {
-          @Override
-          public Integer apply(AccountInfo in) {
-            return in._accountId;
-          }
-        });
+    return FluentIterable.from(accounts).transform(a -> a._accountId);
   }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index c5221f1..dae2abb 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -23,7 +23,6 @@
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.fail;
 
-import com.google.common.base.Function;
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
@@ -1437,13 +1436,8 @@
     for (int i = 1; i <= 11; i++) {
       Iterable<ChangeData> cds = internalChangeQuery.byCommitsOnBranchNotMerged(
           repo.getRepository(), db, dest, shas, i);
-      Iterable<Integer> ids = FluentIterable.from(cds).transform(
-          new Function<ChangeData, Integer>() {
-            @Override
-            public Integer apply(ChangeData in) {
-              return in.getId().get();
-            }
-          });
+      Iterable<Integer> ids = FluentIterable.from(cds)
+          .transform(in -> in.getId().get());
       String name = "limit " + i;
       assertThat(ids).named(name).hasSize(n);
       assertThat(ids).named(name)
@@ -1640,24 +1634,22 @@
     StringBuilder b = new StringBuilder();
     b.append("query '").append(query.getQuery())
      .append("' with expected changes ");
-    b.append(format(Iterables.transform(Arrays.asList(expectedChanges),
-        new Function<Change, Integer>() {
-          @Override
-          public Integer apply(Change change) {
-            return change.getChangeId();
-          }
-        })));
+    b.append(format(
+        Arrays.stream(expectedChanges).map(Change::getChangeId).iterator()));
     b.append(" and result ");
     b.append(format(actualIds));
     return b.toString();
   }
 
   private String format(Iterable<Integer> changeIds) throws RestApiException {
+    return format(changeIds.iterator());
+  }
+
+  private String format(Iterator<Integer> changeIds) throws RestApiException {
     StringBuilder b = new StringBuilder();
     b.append("[");
-    Iterator<Integer> it = changeIds.iterator();
-    while (it.hasNext()) {
-      int id = it.next();
+    while (changeIds.hasNext()) {
+      int id = changeIds.next();
       ChangeInfo c = gApi.changes().id(id).get();
       b.append("{").append(id).append(" (").append(c.changeId)
           .append("), ").append("dest=").append(
@@ -1666,7 +1658,7 @@
           .append("status=").append(c.status).append(", ")
           .append("lastUpdated=").append(c.updated.getTime())
           .append("}");
-      if (it.hasNext()) {
+      if (changeIds.hasNext()) {
         b.append(", ");
       }
     }
@@ -1675,23 +1667,13 @@
   }
 
   protected static Iterable<Integer> ids(Change... changes) {
-    return FluentIterable.from(Arrays.asList(changes)).transform(
-        new Function<Change, Integer>() {
-          @Override
-          public Integer apply(Change in) {
-            return in.getId().get();
-          }
-        });
+    return FluentIterable.from(Arrays.asList(changes))
+        .transform(in -> in.getId().get());
   }
 
   protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) {
-    return FluentIterable.from(changes).transform(
-        new Function<ChangeInfo, Integer>() {
-          @Override
-          public Integer apply(ChangeInfo in) {
-            return in._number;
-          }
-        });
+    return FluentIterable.from(changes)
+        .transform(in -> in._number);
   }
 
   protected static long lastUpdatedMs(Change c) {
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 f2d563e..b8888e2 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
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.testutil;
 
+import static java.util.stream.Collectors.toList;
+
 import com.google.auto.value.AutoValue;
-import com.google.common.base.Predicate;
-import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.gerrit.common.errors.EmailException;
@@ -111,17 +111,14 @@
     }
   }
 
-  public ImmutableList<Message> getMessages(String changeId, String type) {
+  public List<Message> getMessages(String changeId, String type) {
     final String idFooter = "\nGerrit-Change-Id: " + changeId + "\n";
     final String typeFooter = "\nGerrit-MessageType: " + type + "\n";
-    return FluentIterable.from(getMessages())
-        .filter(new Predicate<Message>() {
-          @Override
-          public boolean apply(Message in) {
-            return in.body().contains(idFooter)
-                && in.body().contains(typeFooter);
-          }
-        }).toList();
+    return getMessages()
+        .stream()
+        .filter(in -> in.body().contains(idFooter)
+            && in.body().contains(typeFooter))
+        .collect(toList());
   }
 
   private void waitForEmails() {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
index dd1ba20..84fd9d7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryModule.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerId;
 import com.google.gerrit.server.config.SitePath;
@@ -150,6 +151,8 @@
     // TODO(dborowitz): Use jimfs.
     bind(Path.class).annotatedWith(SitePath.class).toInstance(Paths.get("."));
     bind(Config.class).annotatedWith(GerritServerConfig.class).toInstance(cfg);
+    bind(GerritOptions.class)
+        .toInstance(new GerritOptions(cfg, false, false, false));
     bind(PersonIdent.class)
         .annotatedWith(GerritPersonIdent.class)
         .toProvider(GerritPersonIdentProvider.class);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java
index ef1639e..6dba19a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java
@@ -15,9 +15,10 @@
 package com.google.gerrit.testutil;
 
 import static com.google.common.truth.Truth.assertThat;
+import static java.util.Comparator.comparing;
+import static java.util.stream.Collectors.toList;
 
 import com.google.common.base.Joiner;
-import com.google.common.collect.Iterables;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -29,6 +30,9 @@
 import com.google.gerrit.server.notedb.ChangeBundleReader;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
+import com.google.gwtorm.client.IntKey;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.OrmRuntimeException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -41,6 +45,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
+import java.util.stream.Stream;
 
 @Singleton
 public class NoteDbChecker {
@@ -73,16 +78,14 @@
 
   public void rebuildAndCheckAllChanges() throws Exception {
     rebuildAndCheckChanges(
-        Iterables.transform(
-            getUnwrappedDb().changes().all(),
-            ReviewDbUtil.changeIdFunction()));
+        getUnwrappedDb().changes().all().toList().stream().map(Change::getId));
   }
 
   public void rebuildAndCheckChanges(Change.Id... changeIds) throws Exception {
-    rebuildAndCheckChanges(Arrays.asList(changeIds));
+    rebuildAndCheckChanges(Arrays.stream(changeIds));
   }
 
-  public void rebuildAndCheckChanges(Iterable<Change.Id> changeIds)
+  private void rebuildAndCheckChanges(Stream<Change.Id> changeIds)
       throws Exception {
     ReviewDb db = getUnwrappedDb();
 
@@ -111,11 +114,7 @@
   }
 
   public void checkChanges(Change.Id... changeIds) throws Exception {
-    checkChanges(Arrays.asList(changeIds));
-  }
-
-  public void checkChanges(Iterable<Change.Id> changeIds) throws Exception {
-    checkActual(readExpected(changeIds), new ArrayList<String>());
+    checkActual(readExpected(Arrays.stream(changeIds)), new ArrayList<>());
   }
 
   public void assertNoChangeRef(Project.NameKey project, Change.Id changeId)
@@ -125,24 +124,26 @@
     }
   }
 
-  private List<ChangeBundle> readExpected(Iterable<Change.Id> changeIds)
+  private List<ChangeBundle> readExpected(Stream<Change.Id> changeIds)
       throws Exception {
-    ReviewDb db = getUnwrappedDb();
     boolean old = notesMigration.readChanges();
     try {
       notesMigration.setReadChanges(false);
-      List<Change.Id> sortedIds =
-          ReviewDbUtil.intKeyOrdering().sortedCopy(changeIds);
-      List<ChangeBundle> expected = new ArrayList<>(sortedIds.size());
-      for (Change.Id id : sortedIds) {
-        expected.add(bundleReader.fromReviewDb(db, id));
-      }
-      return expected;
+      return changeIds.sorted(comparing(IntKey::get))
+          .map(this::readBundleUnchecked).collect(toList());
     } finally {
       notesMigration.setReadChanges(old);
     }
   }
 
+  private ChangeBundle readBundleUnchecked(Change.Id id) {
+    try {
+      return bundleReader.fromReviewDb(getUnwrappedDb(), id);
+    } catch (OrmException e) {
+      throw new OrmRuntimeException(e);
+    }
+  }
+
   private void checkActual(List<ChangeBundle> allExpected, List<String> msgs)
       throws Exception {
     ReviewDb db = getUnwrappedDb();
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 54b83e2..fcb844f 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -56,5 +56,4 @@
     '//lib:truth',
     '//lib/mina:sshd',
   ],
-  source_under_test = [':sshd'],
 )
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
index 25fb7a7..3e31fab 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/BaseCommand.java
@@ -385,14 +385,6 @@
     }
   }
 
-  public void checkExclusivity(final Object arg1, final String arg1name,
-      final Object arg2, final String arg2name) throws UnloggedFailure {
-    if (arg1 != null && arg2 != null) {
-      throw new UnloggedFailure(String.format(
-          "%s and %s options are mutually exclusive.", arg1name, arg2name));
-    }
-  }
-
   private final class TaskThunk implements CancelableRunnable, ProjectRunnable {
     private final CommandRunnable thunk;
     private final String taskName;
diff --git a/gerrit-util-http/BUCK b/gerrit-util-http/BUCK
index cfab096..79ef836 100644
--- a/gerrit-util-http/BUCK
+++ b/gerrit-util-http/BUCK
@@ -34,7 +34,6 @@
     '//lib:truth',
     '//lib/easymock:easymock',
   ],
-  source_under_test = [':http'],
   # TODO(sop) Remove after Buck supports Eclipse
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/gerrit-war/BUILD b/gerrit-war/BUILD
new file mode 100644
index 0000000..86c838f
--- /dev/null
+++ b/gerrit-war/BUILD
@@ -0,0 +1,70 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+
+java_library(
+  name = 'init',
+  srcs = glob(['src/main/java/**/*.java']),
+  deps = [
+    '//gerrit-cache-h2:cache-h2',
+    '//gerrit-extension-api:api',
+    '//gerrit-gpg:gpg',
+    '//gerrit-httpd:httpd',
+    '//gerrit-lucene:lucene',
+    '//gerrit-oauth:oauth',
+    '//gerrit-openid:openid',
+    '//gerrit-pgm:http',
+    '//gerrit-pgm:init',
+    '//gerrit-pgm:init-api',
+    '//gerrit-pgm:util',
+    '//gerrit-reviewdb:server',
+    '//gerrit-server:server',
+    '//gerrit-server/src/main/prolog:common',
+    '//gerrit-sshd:sshd',
+    '//lib:guava',
+    '//lib:gwtorm',
+    '//lib:servlet-api-3_1',
+    '//lib/guice:guice',
+    '//lib/guice:guice-servlet',
+    '//lib/jgit/org.eclipse.jgit:jgit',
+    '//lib/log:api',
+  ],
+  visibility = ['//visibility:public'],
+)
+
+genrule2(
+  name = 'webapp_assets',
+  cmd = 'cd gerrit-war/src/main/webapp; zip -qr $$ROOT/$@ .',
+  srcs = glob(['src/main/webapp/**/*']),
+  out = 'webapp_assets.zip',
+  visibility = ['//visibility:public'],
+)
+
+java_import(
+  name = 'log4j-config',
+  jars = [':log4j-config__jar'],
+  visibility = ['//visibility:public'],
+)
+
+genrule2(
+  name = 'log4j-config__jar',
+  cmd = 'cd gerrit-war/src/main/resources && zip -9Dqr $$ROOT/$@ .',
+  srcs = ['src/main/resources/log4j.properties'],
+  out = 'log4j-config.jar',
+)
+
+java_import(
+  name = 'version',
+  jars = [':gen_version'],
+  visibility = ['//visibility:public'],
+)
+
+genrule2(
+  name = 'gen_version',
+  cmd = ' && '.join([
+    'cd $$TMP',
+    'mkdir -p com/google/gerrit/common',
+    'cat $$ROOT/$(location //:version) >com/google/gerrit/common/Version',
+    'zip -9Dqr $$ROOT/$@ .',
+  ]),
+  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 5790453..fc0beae 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
@@ -38,6 +38,7 @@
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.DownloadConfig;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritOptions;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.GerritServerConfigModule;
 import com.google.gerrit.server.config.RestCacheAdminModule;
diff --git a/lib/BUCK b/lib/BUCK
index 97bbc26..bc4f40b 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -222,8 +222,8 @@
 
 maven_jar(
   name = 'jimfs',
-  id = 'com.google.jimfs:jimfs:1.0',
-  sha1 = 'edd65a2b792755f58f11134e76485a928aab4c97',
+  id = 'com.google.jimfs:jimfs:1.1',
+  sha1 = '8fbd0579dc68aba6186935cc1bee21d2f3e7ec1c',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [':guava'],
 )
diff --git a/lib/BUILD b/lib/BUILD
index a490038..44c293d 100644
--- a/lib/BUILD
+++ b/lib/BUILD
@@ -231,3 +231,9 @@
   exports = [ '@icu4j//jar' ],
   visibility = ['//visibility:public'],
 )
+
+java_library(
+  name = 'postgresql',
+  exports = ['@postgresql//jar'],
+  visibility = ['//visibility:public'],
+)
diff --git a/lib/codemirror/BUILD b/lib/codemirror/BUILD
new file mode 100644
index 0000000..0a62d41
--- /dev/null
+++ b/lib/codemirror/BUILD
@@ -0,0 +1,2 @@
+load('//lib/codemirror:cm.bzl', 'pkg_cm')
+pkg_cm()
diff --git a/lib/codemirror/cm.bzl b/lib/codemirror/cm.bzl
new file mode 100644
index 0000000..b4e55fe
--- /dev/null
+++ b/lib/codemirror/cm.bzl
@@ -0,0 +1,355 @@
+load('//tools/bzl:genrule2.bzl', 'genrule2')
+
+CM_CSS = [
+  'lib/codemirror.css',
+  'addon/dialog/dialog.css',
+  'addon/merge/merge.css',
+  'addon/scroll/simplescrollbars.css',
+  'addon/search/matchesonscrollbar.css',
+  'addon/lint/lint.css',
+]
+
+CM_JS = [
+  'lib/codemirror.js',
+  'mode/meta.js',
+  'keymap/emacs.js',
+  'keymap/sublime.js',
+  'keymap/vim.js',
+]
+
+CM_ADDONS = [
+  'dialog/dialog.js',
+  'edit/closebrackets.js',
+  'edit/matchbrackets.js',
+  'edit/trailingspace.js',
+  'scroll/annotatescrollbar.js',
+  'scroll/simplescrollbars.js',
+  'search/jump-to-line.js',
+  'search/matchesonscrollbar.js',
+  'search/searchcursor.js',
+  'search/search.js',
+  'selection/mark-selection.js',
+  'mode/multiplex.js',
+  'mode/overlay.js',
+  'mode/simple.js',
+  'lint/lint.js',
+]
+
+# Available themes must be enumerated here,
+# in gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/Theme.java,
+# in gerrit-gwtui/src/main/java/net/codemirror/theme/Themes.java
+CM_THEMES = [
+  '3024-day',
+  '3024-night',
+  'abcdef',
+  'ambiance',
+  'base16-dark',
+  'base16-light',
+  'bespin',
+  'blackboard',
+  'cobalt',
+  'colorforth',
+  'dracula',
+  'eclipse',
+  'elegant',
+  'erlang-dark',
+  'hopscotch',
+  'icecoder',
+  'isotope',
+  'lesser-dark',
+  'liquibyte',
+  'material',
+  'mbo',
+  'mdn-like',
+  'midnight',
+  'monokai',
+  'neat',
+  'neo',
+  'night',
+  'paraiso-dark',
+  'paraiso-light',
+  'pastel-on-dark',
+  'railscasts',
+  'rubyblue',
+  'seti',
+  'solarized',
+  'the-matrix',
+  'tomorrow-night-bright',
+  'tomorrow-night-eighties',
+  'ttcn',
+  'twilight',
+  'vibrant-ink',
+  'xq-dark',
+  'xq-light',
+  'yeti',
+  'zenburn',
+]
+
+# Available modes must be enumerated here,
+# in gerrit-gwtui/src/main/java/net/codemirror/mode/Modes.java,
+# gerrit-gwtui/src/main/java/net/codemirror/mode/ModeInfo.java,
+# and in CodeMirror's own mode/meta.js script.
+CM_MODES = [
+  'apl',
+  'asciiarmor',
+  'asn.1',
+  'asterisk',
+  'brainfuck',
+  'clike',
+  'clojure',
+  'cmake',
+  'cobol',
+  'coffeescript',
+  'commonlisp',
+  'crystal',
+  'css',
+  'cypher',
+  'd',
+  'dart',
+  'diff',
+  'django',
+  'dockerfile',
+  'dtd',
+  'dylan',
+  'ebnf',
+  'ecl',
+  'eiffel',
+  'elm',
+  'erlang',
+  'factor',
+  'fcl',
+  'forth',
+  'fortran',
+  'gas',
+  'gfm',
+  'gherkin',
+  'go',
+  'groovy',
+  'haml',
+  'handlebars',
+  'haskell-literate',
+  'haskell',
+  'haxe',
+  'htmlembedded',
+  'htmlmixed',
+  'http',
+  'idl',
+  'javascript',
+  'jinja2',
+  'jsx',
+  'julia',
+  'livescript',
+  'lua',
+  'markdown',
+  'mathematica',
+  'mbox',
+  'mirc',
+  'mllike',
+  'modelica',
+  'mscgen',
+  'mumps',
+  'nginx',
+  'nsis',
+  'ntriples',
+  'octave',
+  'oz',
+  'pascal',
+  'pegjs',
+  'perl',
+  'php',
+  'pig',
+  'powershell',
+  'properties',
+  'protobuf',
+  'pug',
+  'puppet',
+  'python',
+  'q',
+  'r',
+  'rpm',
+  'rst',
+  'ruby',
+  'rust',
+  'sas',
+  'sass',
+  'scheme',
+  'shell',
+  'sieve',
+  'slim',
+  'smalltalk',
+  'smarty',
+  'solr',
+  'soy',
+  'sparql',
+  'spreadsheet',
+  'sql',
+  'stex',
+  'stylus',
+  'swift',
+  'tcl',
+  'textile',
+  'tiddlywiki',
+  'tiki',
+  'toml',
+  'tornado',
+  'troff',
+  'ttcn-cfg',
+  'ttcn',
+  'turtle',
+  'twig',
+  'vb',
+  'vbscript',
+  'velocity',
+  'verilog',
+  'vhdl',
+  'vue',
+  'webidl',
+  'xml',
+  'xquery',
+  'yacas',
+  'yaml-frontmatter',
+  'yaml',
+  'z80',
+]
+
+VERSION = '5.18.2'
+TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION
+TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION
+
+DIFF_MATCH_PATCH_VERSION = '20121119-1'
+DIFF_MATCH_PATCH_TOP = ('META-INF/resources/webjars/google-diff-match-patch/%s'
+    % DIFF_MATCH_PATCH_VERSION)
+
+def pkg_cm():
+  for archive, suffix, top in [
+      ('@codemirror_original//jar', '', TOP),
+      ('@codemirror_minified//jar', '_r', TOP_MINIFIED)
+  ]:
+    # Main JavaScript and addons
+    genrule2(
+      name = 'cm' + suffix,
+      cmd = ' && '.join([
+          "echo '/** @license' >$@",
+          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
+          "echo '*/' >>$@",
+        ] +
+        ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n) for n in CM_JS] +
+        ['unzip -p $(location %s) %s/addon/%s >>$@' % (archive, top, n)
+         for n in CM_ADDONS]
+      ),
+      tools = [
+        '@codemirror_original//jar',
+        '@codemirror_minified//jar',
+      ],
+      out = 'cm%s.js' % suffix,
+    )
+
+    # Main CSS
+    genrule2(
+      name = 'css' + suffix,
+      cmd = ' && '.join([
+          "echo '/** @license' >$@",
+          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
+          "echo '*/' >>$@",
+        ] +
+        ['unzip -p $(location %s) %s/%s >>$@' % (archive, top, n)
+         for n in CM_CSS]
+      ),
+      tools = [
+        '@codemirror_original//jar',
+        '@codemirror_minified//jar',
+      ],
+      out = 'cm%s.css' % suffix,
+    )
+
+    # Modes
+    for n in CM_MODES:
+      genrule2(
+        name = 'mode_%s%s' % (n, suffix),
+        cmd = ' && '.join([
+            "echo '/** @license' >$@",
+            'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
+            "echo '*/' >>$@",
+            'unzip -p $(location %s) %s/mode/%s/%s.js >>$@' % (archive, top, n, n),
+          ]
+        ),
+        tools = [
+          '@codemirror_original//jar',
+          '@codemirror_minified//jar',
+        ],
+        out = 'mode_%s%s.js' % (n, suffix),
+      )
+
+    # Themes
+    for n in CM_THEMES:
+      genrule2(
+        name = 'theme_%s%s' % (n, suffix),
+        cmd = ' && '.join([
+            "echo '/** @license' >$@",
+            'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
+            "echo '*/' >>$@",
+            'unzip -p $(location %s) %s/theme/%s.css >>$@' % (archive, top, n)
+          ]
+        ),
+        tools = [
+          '@codemirror_original//jar',
+          '@codemirror_minified//jar',
+        ],
+        out = 'theme_%s%s.css' % (n, suffix),
+      )
+
+    # Merge Addon bundled with diff-match-patch
+    genrule2(
+      name = 'addon_merge%s' % suffix,
+      cmd = ' && '.join([
+          "echo '/** @license' >$@",
+          'unzip -p $(location %s) %s/LICENSE >>$@' % (archive, top),
+          "echo '*/\n' >>$@",
+          "echo '// The google-diff-match-patch library is from https://google-diff-match-patch.googlecode.com/svn-history/r106/trunk/javascript/diff_match_patch.js\n' >> $@",
+          "echo '/** @license' >>$@",
+          "echo 'LICENSE-Apache2.0' >>$@",
+          "echo '*/' >>$@",
+          'unzip -p $(location @diff_match_patch//jar) %s/diff_match_patch.js >>$@' % DIFF_MATCH_PATCH_TOP,
+          "echo ';' >> $@",
+          'unzip -p $(location %s) %s/addon/merge/merge.js >>$@' % (archive, top)
+        ]
+      ),
+      tools = [
+        '@diff_match_patch//jar',
+        '@codemirror_original//jar',
+        '@codemirror_minified//jar',
+      ],
+      out = 'addon_merge%s.js' % suffix,
+    )
+
+    # Jar packaging
+    genrule2(
+      name = 'jar' + suffix,
+      cmd = ' && '.join([
+        'cd $$TMP',
+        'mkdir -p net/codemirror/{addon,lib,mode,theme}',
+        'cp $$ROOT/$(location :css%s) net/codemirror/lib/cm.css' % suffix,
+        'cp $$ROOT/$(location :cm%s) net/codemirror/lib/cm.js' % suffix]
+        + ['cp $$ROOT/$(location :mode_%s%s) net/codemirror/mode/%s.js' % (n, suffix, n)
+           for n in CM_MODES]
+        + ['cp $$ROOT/$(location :theme_%s%s) net/codemirror/theme/%s.css' % (n, suffix, n)
+           for n in CM_THEMES]
+        + ['cp $$ROOT/$(location :addon_merge%s) net/codemirror/addon/merge_bundled.js' % suffix]
+        + ['zip -qr $$ROOT/$@ net/codemirror/{addon,lib,mode,theme}']),
+      tools = [
+        ':addon_merge%s' % suffix,
+        ':cm%s' % suffix,
+        ':css%s' % suffix,
+      ] + [
+        ':mode_%s%s' % (n, suffix) for n in CM_MODES
+      ] + [
+        ':theme_%s%s' % (n, suffix) for n in CM_THEMES
+      ],
+      out = 'codemirror%s.jar' % suffix,
+    )
+
+    native.java_import(
+      name = 'codemirror' + suffix,
+      jars = [':jar%s' % suffix],
+      visibility = ['//visibility:public'],
+    )
diff --git a/lib/gwt/BUILD b/lib/gwt/BUILD
index 2168bb4..bf04c95 100644
--- a/lib/gwt/BUILD
+++ b/lib/gwt/BUILD
@@ -7,3 +7,9 @@
   'dev',
   'user',
 ]]
+
+java_library(
+  name = 'javax-validation_src',
+  exports = ['@javax_validation_src//jar'],
+  visibility = ['//visibility:public'],
+)
diff --git a/plugins/cookbook-plugin b/plugins/cookbook-plugin
index de2d4d7..23572e6 160000
--- a/plugins/cookbook-plugin
+++ b/plugins/cookbook-plugin
@@ -1 +1 @@
-Subproject commit de2d4d7e7222e1f8b286ea13b316f77fae9ee878
+Subproject commit 23572e6c83531fceea69c1356a5e1b6753ecf0e3
diff --git a/plugins/replication b/plugins/replication
index a592cc0..9411b6d 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit a592cc0cb61ef309f21669c037ee6689780a6d89
+Subproject commit 9411b6d9d37fbbd9a6bb98307bcb8f4f47c58f37
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 9822922..85069a4 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 982292220c81dadefae9535e9f439ba50e71df7e
+Subproject commit 85069a4bd88b9a18c9df89c86229daf2a07cf345
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 f91816b..0676595 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
@@ -211,13 +211,13 @@
     _computeRevisionActionValues: function(actionsChangeRecord,
         primariesChangeRecord, additionalActionsChangeRecord) {
       return this._getActionValues(actionsChangeRecord, primariesChangeRecord,
-          additionalActionsChangeRecord, 'revision');
+          additionalActionsChangeRecord, ActionType.REVISION);
     },
 
     _computeChangeActionValues: function(actionsChangeRecord,
         primariesChangeRecord, additionalActionsChangeRecord) {
       return this._getActionValues(actionsChangeRecord, primariesChangeRecord,
-          additionalActionsChangeRecord, 'change');
+          additionalActionsChangeRecord, ActionType.CHANGE);
     },
 
     _getActionValues: function(actionsChangeRecord, primariesChangeRecord,
@@ -234,6 +234,15 @@
         actions[a].__key = a;
         actions[a].__type = type;
         actions[a].__primary = primaryActionKeys.indexOf(a) !== -1;
+        if (actions[a].label === 'Delete') {
+          // This label is common within change and revision actions. Make it
+          // more explicit to the user.
+          if (type === ActionType.CHANGE) {
+            actions[a].label += ' Change';
+          } else if (type === ActionType.REVISION) {
+            actions[a].label += ' Revision';
+          }
+        }
         // Triggers a re-render by ensuring object inequality.
         // TODO(andybons): Polyfill for Object.assign.
         result.push(Object.assign({}, actions[a]));
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 80aaf3b..9b04c8a 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
@@ -38,6 +38,12 @@
       stub('gr-rest-api-interface', {
         getChangeRevisionActions: function() {
           return Promise.resolve({
+            '/': {
+              method: 'DELETE',
+              label: 'Delete',
+              title: 'Delete draft revision 2',
+              enabled: true
+            },
             cherrypick: {
               method: 'POST',
               label: 'Cherry Pick',
@@ -52,9 +58,9 @@
             submit: {
               method: 'POST',
               label: 'Submit',
-              title: 'Submit patch set 1 into master',
+              title: 'Submit patch set 2 into master',
               enabled: true
-            }
+            },
           });
         },
         send: function(method, url, payload) {
@@ -79,18 +85,45 @@
       element = fixture('basic');
       element.changeNum = '42';
       element.patchNum = '2';
+      element.actions = {
+        '/': {
+          method: 'DELETE',
+          label: 'Delete',
+          title: 'Delete draft change 42',
+          enabled: true
+        },
+      };
       return element.reload();
     });
 
-    test('submit, rebase, and cherry-pick buttons show', function(done) {
+    test('buttons show', function(done) {
       flush(function() {
         var buttonEls = Polymer.dom(element.root).querySelectorAll('gr-button');
-        assert.equal(buttonEls.length, 3);
+        assert.equal(buttonEls.length, 5);
         assert.isFalse(element.hidden);
         done();
       });
     });
 
+    test('delete buttons have explicit labels', function(done) {
+      flush(function() {
+        var buttonEls =
+            Polymer.dom(element.root).querySelectorAll('[data-action-key="/"]');
+        assert.equal(buttonEls.length, 2);
+        assert.notEqual(buttonEls[0].getAttribute('data-label'),
+            buttonEls[1].getAttribute['data-label']);
+        assert.isTrue(
+            buttonEls[0].getAttribute('data-label') === 'Delete Revision' ||
+            buttonEls[0].getAttribute('data-label') === 'Delete Change'
+        );
+        assert.isTrue(
+            buttonEls[1].getAttribute('data-label') === 'Delete Revision' ||
+            buttonEls[1].getAttribute('data-label') === 'Delete Change'
+        );
+        done();
+      });
+    });
+
     test('submit change', function(done) {
       flush(function() {
         var submitButton = element.$$('gr-button[data-action-key="submit"]');
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 1782061..84b2391 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -37,6 +37,9 @@
         color: #666;
         font-weight: bold;
       }
+      gr-editable-label {
+        max-width: 9em;
+      }
       .labelValueContainer:not(:first-of-type) {
         margin-top: .25em;
       }
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 04c2c04..b5e1ef0 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -88,6 +88,10 @@
         value: 'Reply',
         computed: '_computeReplyButtonLabel(_diffDrafts.*)',
       },
+      _initialLoadComplete: {
+        type: Boolean,
+        value: false,
+      },
     },
 
     behaviors: [
@@ -280,29 +284,66 @@
     },
 
     _paramsChanged: function(value) {
-      if (value.view !== this.tagName.toLowerCase()) { return; }
+      if (value.view !== this.tagName.toLowerCase()) {
+        this._initialLoadComplete = false;
+        return;
+      }
 
-      this._changeNum = value.changeNum;
-      this._patchRange = {
+      var patchChanged = this._patchRange &&
+          (this._patchRange.patchNum !== value.patchNum ||
+          this._patchRange.basePatchNum !== value.basePatchNum);
+
+      if (this._changeNum !== value.changeNum) {
+        this._initialLoadComplete = false;
+      }
+
+      var patchRange = {
         patchNum: value.patchNum,
         basePatchNum: value.basePatchNum || 'PARENT',
       };
 
+      if (this._initialLoadComplete && patchChanged) {
+        if (patchRange.patchNum == null) {
+          patchRange.patchNum = this._computeLatestPatchNum(this._allPatchSets);
+        }
+        this._patchRange = patchRange;
+        this._reloadPatchNumDependentResources().then(function() {
+          this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
+            change: this._change,
+            patchNum: patchRange.patchNum,
+          });
+        }.bind(this));
+        return;
+      }
+
+      this._changeNum = value.changeNum;
+      this._patchRange = patchRange;
+
       this._reload().then(function() {
-        // Allow the message list to render before scrolling.
-        this.async(function() {
-          this._maybeScrollToMessage();
-        }.bind(this), 1);
-
-        this._maybeShowReplyDialog();
-
-        this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
-          change: this._change,
-          patchNum: this._patchRange.patchNum,
-        });
+        this._performPostLoadTasks();
       }.bind(this));
     },
 
+    _performPostLoadTasks: function() {
+      // Allow the message list and related changes to render before scrolling.
+      // Related changes are loaded here (after everything else) because they
+      // take the longest and are secondary information. Because the element may
+      // alter the total height of the page, the call to potentially scroll to
+      // a linked message is performed after related changes is fully loaded.
+      this.$.relatedChanges.reload().then(function() {
+        this.async(function() { this._maybeScrollToMessage(); }, 1);
+      }.bind(this));
+
+      this._maybeShowReplyDialog();
+
+      this.$.jsAPI.handleEvent(this.$.jsAPI.EventType.SHOW_CHANGE, {
+        change: this._change,
+        patchNum: this._patchRange.patchNum,
+      });
+
+      this._initialLoadComplete = true;
+    },
+
     _paramsAndChangeChanged: function(value) {
       // If the change number or patch range is different, then reset the
       // selected file index.
@@ -604,32 +645,46 @@
       }.bind(this));
       this._getComments();
 
-      var reloadPatchNumDependentResources = function() {
-        return Promise.all([
-          this._getCommitInfo(),
-          this.$.fileList.reload(),
-        ]);
-      }.bind(this);
-      var reloadDetailDependentResources = function() {
-        if (!this._change) { return Promise.resolve(); }
+      if (this._patchRange.patchNum) {
+        return this._reloadPatchNumDependentResources().then(function() {
+          return detailCompletes;
+        }).then(function() {
+          return this._reloadDetailDependentResources();
+        }.bind(this));
+      } else {
+        // The patch number is reliant on the change detail request.
+        return detailCompletes.then(function() {
+          this._reloadPatchNumDependentResources();
+        }.bind(this)).then(function() {
+          this._reloadDetailDependentResources();
+        }.bind(this));
+      }
+    },
 
+    /**
+     * Kicks off requests for resources that rely on the change detail
+     * (`this._change`) being loaded.
+     */
+    _reloadDetailDependentResources: function() {
+      if (!this._change) { return Promise.resolve(); }
+
+      return this._getProjectConfig().then(function() {
         return Promise.all([
           this._getLatestCommitMessage(),
           this.$.actions.reload(),
-          this.$.relatedChanges.reload(),
-          this._getProjectConfig(),
         ]);
-      }.bind(this);
+      }.bind(this));
+    },
 
-      if (this._patchRange.patchNum) {
-        return reloadPatchNumDependentResources().then(function() {
-          return detailCompletes;
-        }).then(reloadDetailDependentResources);
-      } else {
-        // The patch number is reliant on the change detail request.
-        return detailCompletes.then(reloadPatchNumDependentResources).then(
-            reloadDetailDependentResources);
-      }
+    /**
+     * Kicks off requests for resources that rely on the patch range
+     * (`this._patchRange`) being defined.
+     */
+    _reloadPatchNumDependentResources: function() {
+      return Promise.all([
+        this._getCommitInfo(),
+        this.$.fileList.reload(),
+      ]);
     },
   });
 })();
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 1c36958..04e0bc01 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
@@ -310,6 +310,31 @@
       element.fire('change', {}, {node: selectEl});
     });
 
+    test('don’t reload entire page when patchRange changes', function() {
+      var reloadStub = sinon.stub(element, '_reload',
+          function() { return Promise.resolve(); });
+      var reloadPatchDependentStub = sinon.stub(element,
+          '_reloadPatchNumDependentResources',
+          function() { return Promise.resolve(); });
+
+      var value = {
+        view: 'gr-change-view',
+        patchNum: '1',
+      };
+      element._paramsChanged(value);
+      assert.isTrue(reloadStub.calledOnce);
+      element._initialLoadComplete = true;
+
+      value.basePatchNum = '1';
+      value.patchNum = '2';
+      element._paramsChanged(value);
+      assert.isFalse(reloadStub.calledTwice);
+      assert.isTrue(reloadPatchDependentStub.calledOnce);
+
+      reloadStub.restore();
+      reloadPatchDependentStub.restore();
+    });
+
     test('change status new', function() {
       element._changeNum = '1';
       element._patchRange = {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 8f53cf0..aff635c 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -126,9 +126,10 @@
     },
 
     _handlePatchChange: function(e) {
-      this.set('patchRange.basePatchNum', Polymer.dom(e).rootTarget.value);
+      var patchRange = Object.assign({}, this.patchRange);
+      patchRange.basePatchNum = Polymer.dom(e).rootTarget.value;
       page.show('/c/' + encodeURIComponent(this.changeNum) + '/' +
-          encodeURIComponent(this._patchRangeStr(this.patchRange)));
+          encodeURIComponent(this._patchRangeStr(patchRange)));
     },
 
     _forEachDiff: function(fn) {
diff --git a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
index 66576a3..09f7381 100644
--- a/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
+++ b/polygerrit-ui/app/elements/settings/gr-watched-projects-editor/gr-watched-projects-editor_test.html
@@ -138,7 +138,7 @@
       assert.isFalse(element._canAddProject({id: 'project b'}, 'filter 1'));
       assert.isFalse(element._canAddProject({id: 'project b'}, 'filter 2'));
 
-      // Can add a projec that is in the list using a new filter.
+      // Can add a project that is in the list using a new filter.
       assert.isTrue(element._canAddProject({id: 'project b'}, 'filter 3'));
     });
 
@@ -181,10 +181,11 @@
 
     test('_handleRemoveProject', function() {
       assert.equal(element._projectsToRemove, 0);
-
       var button = element.$$('table tbody tr:nth-child(2) gr-button');
       MockInteractions.tap(button);
 
+      flushAsynchronousOperations();
+
       var rows = element.$$('table tbody').querySelectorAll('tr');
       assert.equal(rows.length, 3);
 
diff --git a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
index 76a9c77..4fec27f 100644
--- a/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
+++ b/polygerrit-ui/app/elements/shared/gr-editable-label/gr-editable-label.html
@@ -18,14 +18,19 @@
 <dom-module id="gr-editable-label">
   <template>
     <style>
+      :host {
+        display: inline-block;
+      }
+      input,
+      label {
+        width: 100%;
+      }
       input {
         font: inherit;
-        max-width: 8em;
       }
       label {
         color: #777;
         display: inline-block;
-        max-width: 8em;
         overflow: hidden;
         text-overflow: ellipsis;
         white-space: nowrap;
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index d22622a..ab497c1 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -470,25 +470,21 @@
     },
 
     getSuggestedGroups: function(inputVal, opt_n, opt_errFn, opt_ctx) {
-      return this.fetchJSON('/groups/', opt_errFn, opt_ctx, {
-        s: inputVal,
-        n: opt_n,
-      });
+      var params = {s: inputVal};
+      if (opt_n) { params.n = opt_n; }
+      return this.fetchJSON('/groups/', opt_errFn, opt_ctx, params);
     },
 
     getSuggestedProjects: function(inputVal, opt_n, opt_errFn, opt_ctx) {
-      return this.fetchJSON('/projects/', opt_errFn, opt_ctx, {
-        p: inputVal,
-        n: opt_n,
-      });
+      var params = {p: inputVal};
+      if (opt_n) { params.n = opt_n; }
+      return this.fetchJSON('/projects/', opt_errFn, opt_ctx, params);
     },
 
     getSuggestedAccounts: function(inputVal, opt_n, opt_errFn, opt_ctx) {
-      return this.fetchJSON('/accounts/', opt_errFn, opt_ctx, {
-        q: inputVal,
-        n: opt_n,
-        suggest: null,
-      });
+      var params = {q: inputVal, suggest: null};
+      if (opt_n) { params.n = opt_n; }
+      return this.fetchJSON('/accounts/', opt_errFn, opt_ctx, params);
     },
 
     addChangeReviewer: function(changeNum, reviewerID) {
@@ -551,7 +547,7 @@
       ].join(' ');
       var params = {
         O: options,
-        q: query
+        q: query,
       };
       return this.fetchJSON('/changes/', null, null, params);
     },
diff --git a/tools/bzl/gwt.bzl b/tools/bzl/gwt.bzl
index 29987ef..c27e28f 100644
--- a/tools/bzl/gwt.bzl
+++ b/tools/bzl/gwt.bzl
@@ -19,8 +19,6 @@
 def gwt_module(gwt_xml=None, resources=[], srcs=[], **kwargs):
   if gwt_xml:
     resources += [gwt_xml]
-  if srcs:
-    resources += srcs
 
   java_library2(
     srcs = srcs,
diff --git a/tools/bzl/javadoc.bzl b/tools/bzl/javadoc.bzl
new file mode 100644
index 0000000..80771da
--- /dev/null
+++ b/tools/bzl/javadoc.bzl
@@ -0,0 +1,71 @@
+# 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.
+
+# Javadoc rule.
+
+def _impl(ctx):
+  zip_output = ctx.outputs.zip
+
+  transitive_jar_set = set()
+  source_jars = set()
+  for l in ctx.attr.libs:
+    source_jars += l.java.source_jars
+    transitive_jar_set += l.java.transitive_deps
+
+  transitive_jar_paths = [j.path for j in transitive_jar_set]
+  dir = ctx.outputs.zip.path + ".dir"
+  source = ctx.outputs.zip.path + ".source"
+  cmd = [
+      "mkdir %s" % source,
+      " && ".join(["unzip -qud %s %s" % (source, j.path) for j in source_jars]),
+      "mkdir %s" % dir,
+      " ".join([
+        ctx.file._javadoc.path,
+        "-protected",
+        "-encoding UTF-8",
+        "-charset UTF-8",
+        "-notimestamp",
+        "-quiet",
+        "-windowtitle '%s'" % ctx.attr.title,
+        "-link", "http://docs.oracle.com/javase/7/docs/api",
+        "-sourcepath %s" % source,
+        "-subpackages ",
+        ":".join(ctx.attr.pkgs),
+        " -classpath ",
+        ":".join(transitive_jar_paths),
+        "-d %s" % dir]),
+    "find %s -exec touch -t 198001010000 '{}' ';'" % dir,
+    "(cd %s && zip -qr ../%s *)" % (dir, ctx.outputs.zip.basename),
+  ]
+  ctx.action(
+      inputs = list(transitive_jar_set) + list(source_jars) + ctx.files._jdk,
+      outputs = [zip_output],
+      command = " && ".join(cmd))
+
+java_doc = rule(
+    attrs = {
+      "libs": attr.label_list(allow_files = False),
+      "pkgs": attr.string_list(),
+      "title": attr.string(),
+      "_javadoc": attr.label(
+        default = Label("@local_jdk//:bin/javadoc"),
+        single_file = True,
+        allow_files = True),
+      "_jdk": attr.label(
+        default = Label("@local_jdk//:jdk-default"),
+        allow_files = True),
+    },
+    implementation = _impl,
+    outputs = {"zip" : "%{name}.zip"},
+)
diff --git a/tools/bzl/pkg_war.bzl b/tools/bzl/pkg_war.bzl
new file mode 100644
index 0000000..8c3c6cb
--- /dev/null
+++ b/tools/bzl/pkg_war.bzl
@@ -0,0 +1,139 @@
+# 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.
+
+# War packaging.
+
+jar_filetype = FileType(['.jar'])
+
+LIBS = [
+  '//gerrit-war:init',
+  '//gerrit-war:log4j-config',
+  '//gerrit-war:version',
+  '//lib:postgresql',
+  '//lib/log:impl_log4j',
+]
+
+PGMLIBS = [
+  '//gerrit-pgm:pgm'
+]
+
+def _add_context(in_file, output):
+  input_path = in_file.path
+  return [
+    'unzip -qd %s %s' % (output, input_path)
+  ]
+
+def _add_file(in_file, output):
+  output_path = output
+  input_path = in_file.path
+  short_path = in_file.short_path
+  n = in_file.basename
+
+  # TODO(davido): Drop this when provided_deps added to java_library
+  if n.find('-jdk15on-') != -1:
+    return []
+
+  if short_path.startswith('gerrit-'):
+    n = short_path.split('/')[0] + '-' + n
+
+  output_path += n
+  return [
+    'test -L %s || ln -s $(pwd)/%s %s' % (output_path, input_path, output_path)
+  ]
+
+def _make_war(input_dir, output):
+  return ''.join([
+    '(root=$(pwd) && ',
+    'cd %s && ' % input_dir,
+    'zip -9qr ${root}/%s .)' % (output.path),
+  ])
+
+def _war_impl(ctx):
+  war = ctx.outputs.war
+  build_output = war.path + '.build_output'
+  inputs = []
+
+  # Create war layout
+  cmd = [
+    'set -e;rm -rf ' + build_output,
+    'mkdir -p ' + build_output,
+    'mkdir -p %s/WEB-INF/lib' % build_output,
+    'mkdir -p %s/WEB-INF/pgm-lib' % build_output,
+  ]
+
+  # Add lib
+  transitive_lib_deps = set()
+  for l in ctx.attr.libs:
+    transitive_lib_deps += l.java.transitive_runtime_deps
+
+  for dep in transitive_lib_deps:
+    cmd += _add_file(dep, build_output + '/WEB-INF/lib/')
+    inputs.append(dep)
+
+  # Add pgm lib
+  transitive_pgmlib_deps = set()
+  for l in ctx.attr.pgmlibs:
+    transitive_pgmlib_deps += l.java.transitive_runtime_deps
+
+  for dep in transitive_pgmlib_deps:
+    if dep not in inputs:
+      cmd += _add_file(dep, build_output + '/WEB-INF/pgm-lib/')
+      inputs.append(dep)
+
+  # Add context
+  transitive_context_deps = set()
+  if ctx.attr.context:
+    for jar in ctx.attr.context:
+      if hasattr(jar, 'java'):
+        transitive_context_deps += jar.java.transitive_runtime_deps
+      elif hasattr(jar, 'files'):
+        transitive_context_deps += jar.files
+  for dep in transitive_context_deps:
+    cmd += _add_context(dep, build_output)
+    inputs.append(dep)
+
+  # Add zip war
+  cmd.append(_make_war(build_output, war))
+
+  ctx.action(
+    inputs = inputs,
+    outputs = [war],
+    mnemonic = 'WAR',
+    command = '\n'.join(cmd),
+    use_default_shell_env = True,
+  )
+
+_pkg_war = rule(
+  attrs = {
+    'context': attr.label_list(allow_files = True),
+    'libs': attr.label_list(allow_files = jar_filetype),
+    'pgmlibs': attr.label_list(allow_files = False),
+  },
+  implementation = _war_impl,
+  outputs = {'war' : '%{name}.war'},
+)
+
+def pkg_war(name, ui = 'ui_optdbg'):
+  ui_deps = []
+  if ui:
+    ui_deps.append('//gerrit-gwtui:%s' % ui)
+  _pkg_war(
+    name = name,
+    libs = LIBS,
+    pgmlibs = PGMLIBS,
+    context = ui_deps + [
+      '//gerrit-main:main_bin_deploy.jar',
+      '//gerrit-war:webapp_assets',
+    ],
+  )
diff --git a/tools/default.defs b/tools/default.defs
index 191dfe5..fb4c6de 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -201,7 +201,7 @@
         ':%s__gwt_application' % name +
         ';cd $TMP' +
         ';zip -qr $OUT .',
-      out = '%s-static.zip' % name,
+      out = '%s-static.jar' % name,
     )
     gwt_binary(
       name = name + '__gwt_application',
diff --git a/tools/eclipse/BUCK b/tools/eclipse/BUCK
index a8b3f01..dfd271d 100644
--- a/tools/eclipse/BUCK
+++ b/tools/eclipse/BUCK
@@ -15,6 +15,7 @@
     '//gerrit-reviewdb:client_tests',
     '//gerrit-server:server',
     '//gerrit-server:server_tests',
+    '//lib:jimfs',
     '//lib/asciidoctor:asciidoc_lib',
     '//lib/asciidoctor:doc_indexer_lib',
     '//lib/auto:auto-value',