Merge changes I39b56689,I7b428b06,I893c0fd0,I772cc9b0

* changes:
  AccountSshKey: Split common getter code to utility method
  AccountSshKey.Id: Add isValid() method
  InitAdminUser: Fix invalid sequence number for SSH key
  Fix migration of SSH keys with invalid sequence numbers (<= 0)
diff --git a/.buckconfig b/.buckconfig
index b9ca216..c697fc8 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -1,9 +1,5 @@
 [alias]
   api = //:api
-  api_deploy = //tools/maven:api_deploy
-  api_install = //tools/maven:api_install
-  war_deploy = //tools/maven:war_deploy
-  war_install = //tools/maven:war_install
   chrome = //:chrome
   docs = //Documentation:searchfree
   firefox = //:firefox
@@ -34,4 +30,3 @@
 
 [test]
   excluded_labels = manual
-
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index ee645c1..c45bbdb 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -247,6 +247,27 @@
 eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
 created.
 
+=== Reviewer Deleted
+
+Sent when a reviewer (with a vote) is removed from a change.
+
+type:: "reviewer-deleted"
+
+change:: link:json.html#change[change attribute]
+
+patchSet:: link:json.html#patchSet[patchSet attribute]
+
+reviewer:: link:json.html#account[account attribute]
+
+author:: link:json.html#account[account attribute]
+
+approvals:: All link:json.html#approval[approval attributes] removed.
+
+comment:: Review comment cover message.
+
+eventCreatedOn:: Time in seconds since the UNIX epoch when this event was
+created.
+
 === Topic Changed
 
 Sent when the topic of a change has been changed.
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 3237a8c..fc08cdc 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1107,25 +1107,6 @@
 +
 Default is 300 seconds (5 minutes).
 
-[[changeMerge.threadPoolSize]]changeMerge.threadPoolSize::
-+
-_Deprecated:_ Formerly used to control thread pool size for background
-mergeability checks. These checks were moved to the indexing threadpool,
-so this value is now used for
-link:#index.batchThreads[index.batchThreads], only if that value is not
-provided.
-+
-This option may be removed in a future version.
-
-[[changeMerge.interactiveThreadPoolSize]]changeMerge.interactiveThreadPoolSize::
-+
-_Deprecated:_ Formerly used to control thread pool size for interactive
-mergeability checks. These checks were moved to the indexing threadpool,
-so this value is now used for link:#index.threads[index.threads], only
-if that value is not provided.
-+
-This option may be removed in a future version.
-
 [[commentlink]]
 === Section commentlink
 
@@ -2421,9 +2402,7 @@
 +
 Number of threads to use for indexing in normal interactive operations.
 +
-Defaults to 1 if not set, or set to a negative value (unless
-link:#changeMerge.interactiveThreadPoolSize[changeMerge.interactiveThreadPoolSize]
-is iset).
+Defaults to 1 if not set, or set to a negative value.
 
 [[index.batchThreads]]index.batchThreads::
 +
@@ -2431,8 +2410,7 @@
 online schema upgrades.
 +
 If not set or set to a negative value, defaults to the number of logical
-CPUs as returned by the JVM (unless
-link:#changeMerge.threadPoolSize[changeMerge.threadPoolSize] is set).
+CPUs as returned by the JVM.
 
 [[index.onlineUpgrade]]index.onlineUpgrade::
 +
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 5b1f5e3..980c612 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -126,6 +126,14 @@
   reviewer-added --change <change id> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --reviewer <reviewer>
 ====
 
+=== reviewer-deleted
+
+Called whenever a reviewer (with a vote) is removed from a change.
+
+====
+  reviewer-deleted --change <change id> --change-url <change url> --change-owner <change owner> --project <project name> --branch <branch> --reviewer <reviewer> [--<approval category id> <score> --<approval category id> <score> ...]
+====
+
 === topic-changed
 
 Called whenever a change's topic is changed from the Web UI or via the REST API.
diff --git a/Documentation/config-mail.txt b/Documentation/config-mail.txt
index a97cdf2..7aead9a 100644
--- a/Documentation/config-mail.txt
+++ b/Documentation/config-mail.txt
@@ -62,6 +62,12 @@
 to removing votes on changes.  It is a `ChangeEmail`: see `ChangeSubject.vm`
 and `ChangeFooter.vm`.
 
+=== DeleteReviewer.vm
+
+The `DeleteReiewer.vm` template will determine the contents of the email related
+to a user removing a reviewer (with a vote) from a change.  It is a
+`ChangeEmail`: see `ChangeSubject.vm` and `ChangeFooter.vm`.
+
 === Footer.vm
 
 The `Footer.vm` template will determine the contents of the footer text
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index f13ada1..e36f8a6 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -161,13 +161,13 @@
 Install {extension,plugin,gwt}-api to the local maven repository:
 
 ----
-  buck build api_install
+  sh tools/maven/api.sh install
 ----
 
 Install gerrit.war to the local maven repository:
 
 ----
-  buck build war_install
+  sh tools/maven/api.sh war_install
 ----
 
 === Plugins
@@ -613,7 +613,7 @@
 The following tests should be executed, when Buck version is upgraded:
 
 * buck build release
-* buck build api_install
+* tools/maven/api.sh install
 * buck test
 * buck build gerrit, change some sources in gerrit-server project,
   repeat buck build gerrit and verify that gerrit.war was updated
@@ -642,30 +642,6 @@
 link:#buck-daemon[Using Buck daemon] section above how to temporarily
 disable `buckd`.
 
-=== Re-triggering rule execution
-
-There is no way to re-trigger custom rules with side effects, like
-`api_{deploy|install}`. This is a `genrule()` that depends on Java sources
-and is deploying the Plugin API through custom Python script to the local or
-remote Maven repositories. When for some reasons the deployment was undone,
-there is no supported way to re-trigger the execution of `api_{deploy|install}`
-targets. That's because `--no-cache` option will ignore the `Buck` cache, but
-there is no way to ignore `buck-out` directory. To overcome this Buck's design
-limitation new `tools/maven/api.py` script was added, that always re-triggers
-installation or deployment of Plugin API to local or Central Maven repository.
-
-```
-  tools/maven/api.py {deploy|install}
-```
-
-Dry run mode is also supported:
-
-```
-  tools/maven/api.py -n {deploy|install}
-```
-
-With this script the deployment would re-trigger on every invocation.
-
 == Troubleshooting Buck
 
 In some cases problems with Buck itself need to be investigated. See for example
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 501f986..8315776 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1804,7 +1804,7 @@
 
   @Inject
   public MyMenu(@PluginName String name) {
-    menuEntries = Lists.newArrayList();
+    menuEntries = new ArrayList<>();
     menuEntries.add(new MenuEntry("My Menu", Collections.singletonList(
       new MenuItem("My Screen", "#/x/" + name + "/my-screen", ""))));
   }
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index 2a6cb1c..fc1ad10 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -154,11 +154,11 @@
 [[build-gerrit]]
 === Build Gerrit
 
-* Build the Gerrit WAR and API JARs
+* Build the Gerrit WAR, API JARs and documentation
 +
 ----
   buck clean
-  buck build --no-cache release
+  buck build --no-cache release docs
   buck build api_install
 ----
 
@@ -191,26 +191,19 @@
 * Push the WAR to Maven Central:
 +
 ----
-  buck build war_deploy
+  sh tools/maven/api.sh war_deploy
 ----
 
 * Push the plugin artifacts to Maven Central:
 +
 ----
-  buck build api_deploy
-----
-+
-For troubleshooting, the environment variable `VERBOSE` can be set. This
-prints out the commands that are executed by the Buck build process:
-+
-----
-  VERBOSE=1 buck build api_deploy
+  sh tools/maven/api.sh deploy
 ----
 +
 If no artifacts are uploaded, clean the `buck-out` folder and retry:
 +
 ----
-  rm -rf buck-out
+  buck clean ; rm -rf buck-out
 ----
 
 * Push the plugin Maven archetypes to Maven Central:
@@ -345,8 +338,8 @@
   make -C ReleaseNotes
 ----
 
-* Extract the documentation files from the zip file generated during
-the release build: `buck-out/gen/Documentation/html/html.zip`.
+* Extract the documentation files from the zip file generated from
+`buck build docs`: `buck-out/gen/Documentation/searchfree/searchfree.zip`.
 
 * Upload the files manually via web browser to the appropriate folder
 in the
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 2b7aa4c..d4836e5 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -84,5 +84,9 @@
 * link:https://gerrit.googlesource.com/gerrit[Source Code]
 * link:https://www.gerritcodereview.com/about.md[A History of Gerrit Code Review]
 
+GERRIT
+------
+Part of link:https://www.gerritcodereview.com/[Gerrit Code Review]
+
 SEARCHBOX
 ---------
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index ae42477..423d87d 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1437,6 +1437,142 @@
   }
 ----
 
+[[get-watched-projects]]
+=== Get Watched Projects
+--
+'GET /accounts/link:#account-id[\{account-id\}]/watched.projects'
+--
+
+Retrieves all projects a user is watching.
+
+.Request
+----
+  GET /a/accounts/self/watched.projects HTTP/1.0
+----
+
+As result the watched projects of the user are returned as a list of
+link:#project-watch-info[ProjectWatchInfo] entities.
+The result is sorted by project name in ascending order.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  [
+    {
+      "project": "Test Project 1",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    },
+    {
+      "project": "Test Project 2",
+      "filter": "branch:experimental",
+      "notify_all_comments": true,
+      "notify_submitted_changes": true,
+      "notify_abandoned_changes": true
+    }
+  ]
+----
+
+[[set-watched-projects]]
+=== Add/Update a List of Watched Project Entities
+--
+'POST /accounts/link:#account-id[\{account-id\}]/watched.projects'
+--
+
+Add new projects to watch or update existing watched projects.
+Projects that are already watched by a user will be updated with
+the provided configuration. All other projects in the request
+will be watched using the provided configuration. The posted body
+can contain link:#project-watch-info[ProjectWatchInfo] entities.
+Omitted boolean values will be set to false.
+
+.Request
+----
+  POST /a/accounts/self/watched.projects HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  [
+    {
+      "project": "Test Project 1",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    }
+  ]
+----
+
+As result the watched projects of the user are returned as a list of
+link:#project-watch-info[ProjectWatchInfo] entities.
+The result is sorted by project name in ascending order.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  [
+    {
+      "project": "Test Project 1",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    },
+    {
+      "project": "Test Project 2",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    }
+  ]
+----
+
+[[delete-watched-projects]]
+=== Delete Watched Projects
+--
+'POST /accounts/link:#account-id[\{account-id\}]/watched.projects:delete'
+--
+
+Projects posted to this endpoint will no longer be watched. The posted body
+can contain an array of project names as strings.
+
+.Request
+----
+  POST /a/accounts/self/watched.projects:delete HTTP/1.0
+  Content-Type: application/json;charset=UTF-8
+
+  [
+    "Test Project 1"
+  ]
+----
+
+As result the watched projects of the user are returned as a list of
+link:#project-watch-info[ProjectWatchInfo] entities.
+The result is sorted by project name in ascending order.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Disposition: attachment
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  [
+    {
+      "project": "Test Project 2",
+      "notify_new_changes": true,
+      "notify_new_patch_sets": true,
+      "notify_all_comments": true,
+    }
+  ]
+----
+
 [[get-starred-changes]]
 === Get Starred Changes
 --
@@ -2095,6 +2231,22 @@
 |`username` |The new username of the account.
 |=======================
 
+[[project-watch-info]]
+=== ProjectWatchInfo
+The `WatchedProjectsInfo` entity contains information about a project watch
+for a user.
+
+[options="header",cols="1,^1,5"]
+|=======================
+|Field Name                 |        |Description
+|`project`                  |        |The name of the project.
+|`filter`                   |optional|A filter string to be applied to the project.
+|`notify_new_changes`       |optional|Notify on new changes.
+|`notify_new_patch_sets`    |optional|Notify on new patch sets.
+|`notify_all_comments`      |optional|Notify on comments.
+|`notify_submitted_changes` |optional|Notify on submitted changes.
+|`notify_abandoned_changes` |optional|Notify on abandoned changes.
+|=======================
 
 GERRIT
 ------
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
index 34379a1..839b393 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/AcceptanceTestRequestScope.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.acceptance;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -32,6 +31,7 @@
 import com.google.inject.Scope;
 import com.google.inject.util.Providers;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /** Guice scopes for state during an Acceptance Test connection. */
@@ -44,7 +44,7 @@
 
   public static class Context implements RequestContext {
     private final RequestCleanup cleanup = new RequestCleanup();
-    private final Map<Key<?>, Object> map = Maps.newHashMap();
+    private final Map<Key<?>, Object> map = new HashMap<>();
     private final SchemaFactory<ReviewDb> schemaFactory;
     private final SshSession session;
     private final CurrentUser user;
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
index c16eed7..14188bd 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InProcessProtocol.java
@@ -259,11 +259,8 @@
         UploadPack up = new UploadPack(repo);
         up.setPackConfig(transferConfig.getPackConfig());
         up.setTimeout(transferConfig.getTimeout());
-
-        if (!ctl.allRefsAreVisible()) {
-          up.setAdvertiseRefsHook(new VisibleRefFilter(
-              tagCache, changeCache, repo, ctl, dbProvider.get(), true));
-        }
+        up.setAdvertiseRefsHook(new VisibleRefFilter(
+            tagCache, changeCache, repo, ctl, dbProvider.get(), true));
         List<PreUploadHook> hooks = Lists.newArrayList(preUploadHooks);
         hooks.add(uploadValidatorsFactory.create(
             ctl.getProject(), repo, "localhost-test"));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 6314777..631b480 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -20,12 +20,14 @@
 import static com.google.gerrit.acceptance.PushOneCommit.SUBJECT;
 import static com.google.gerrit.extensions.client.ReviewerState.CC;
 import static com.google.gerrit.extensions.client.ReviewerState.REVIEWER;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
 import static com.google.gerrit.server.group.SystemGroupBackend.CHANGE_OWNER;
 import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
 import static com.google.gerrit.server.project.Util.blockLabel;
 import static com.google.gerrit.server.project.Util.category;
 import static com.google.gerrit.server.project.Util.value;
+import static java.util.concurrent.TimeUnit.SECONDS;
 
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
@@ -64,17 +66,19 @@
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.group.SystemGroupBackend;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.Util;
 import com.google.gerrit.testutil.FakeEmailSender.Message;
 import com.google.gerrit.testutil.NoteDbMode;
+import com.google.gerrit.testutil.TestTimeUtil;
 
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.sql.Timestamp;
@@ -88,6 +92,18 @@
 
 @NoHttpd
 public class ChangeIT extends AbstractDaemonTest {
+  private String systemTimeZone;
+
+  @Before
+  public void setTimeForTesting() {
+    systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+  }
+
+  @After
+  public void resetTime() {
+    TestTimeUtil.useSystemTime();
+    System.setProperty("user.timezone", systemTimeZone);
+  }
 
   @Test
   public void get() throws Exception {
@@ -503,6 +519,7 @@
 
   @Test
   public void addReviewer() throws Exception {
+    TestTimeUtil.resetWithClockStep(1, SECONDS);
     PushOneCommit.Result r = createChange();
     ChangeResource rsrc = parseResource(r);
     String oldETag = rsrc.getETag();
@@ -538,10 +555,10 @@
     assertThat(reviewers.iterator().next()._accountId)
         .isEqualTo(user.getId().get());
 
-    // Ensure ETag is updated but lastUpdatedOn isn't.
+    // Ensure ETag and lastUpdatedOn are updated.
     rsrc = parseResource(r);
     assertThat(rsrc.getETag()).isNotEqualTo(oldETag);
-    assertThat(rsrc.getChange().getLastUpdatedOn()).isEqualTo(oldTs);
+    assertThat(rsrc.getChange().getLastUpdatedOn()).isNotEqualTo(oldTs);
   }
 
   @Test
@@ -1082,8 +1099,7 @@
     try (Repository repo = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(repo)) {
       RevCommit commitPatchSetCreation = rw.parseCommit(
-          repo.exactRef(ChangeNoteUtil.changeRefName(new Change.Id(c._number)))
-              .getObjectId());
+          repo.exactRef(changeMetaRef(new Change.Id(c._number))).getObjectId());
 
       assertThat(commitPatchSetCreation.getShortMessage())
           .isEqualTo("Create patch set 2");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
index f1aa062..6e2e761 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/group/GroupsIT.java
@@ -21,7 +21,6 @@
 import com.google.common.base.Function;
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
@@ -49,6 +48,7 @@
 
 import java.sql.Timestamp;
 import java.util.Arrays;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -125,7 +125,7 @@
     String p = createGroup("parent");
     String g1 = createGroup("newGroup1");
     String g2 = createGroup("newGroup2");
-    List<String> groups = Lists.newLinkedList();
+    List<String> groups = new LinkedList<>();
     groups.add(g1);
     groups.add(g2);
     gApi.groups().id(p).addGroups(g1, g2);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index 5f2af6e..fd7b359 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -399,9 +399,8 @@
 
   @Test
   public void testPushForMasterWithHashtags() throws Exception {
-
-    // Hashtags currently only work when noteDB is enabled
-    assume().that(notesMigration.enabled()).isTrue();
+    // Hashtags only work when reading from NoteDB is enabled
+    assume().that(notesMigration.readChanges()).isTrue();
 
     // specify a single hashtag as option
     String hashtag1 = "tag1";
@@ -427,9 +426,8 @@
 
   @Test
   public void testPushForMasterWithMultipleHashtags() throws Exception {
-
-    // Hashtags currently only work when noteDB is enabled
-    assume().that(notesMigration.enabled()).isTrue();
+    // Hashtags only work when reading from NoteDB is enabled
+    assume().that(notesMigration.readChanges()).isTrue();
 
     // specify multiple hashtags as options
     String hashtag1 = "tag1";
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 24ddbf2..bb1a656 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
@@ -127,14 +127,13 @@
     ObjectId commitId = repo.git().fetch().setRemote("origin").call()
         .getAdvertisedRef("refs/heads/" + branch).getObjectId();
 
-    try (RevWalk rw = repo.getRevWalk()) {
-      RevCommit c = rw.parseCommit(commitId);
-      rw.parseBody(c.getTree());
+    RevWalk rw = repo.getRevWalk();
+    RevCommit c = rw.parseCommit(commitId);
+    rw.parseBody(c.getTree());
 
-      RevTree tree = c.getTree();
-      RevObject actualId = repo.get(tree, submodule);
+    RevTree tree = c.getTree();
+    RevObject actualId = repo.get(tree, submodule);
 
-      assertThat(actualId).isEqualTo(expectedId);
-    }
+    assertThat(actualId).isEqualTo(expectedId);
   }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
new file mode 100644
index 0000000..879430a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/account/WatchedProjectsIT.java
@@ -0,0 +1,169 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.acceptance.rest.account;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.collect.Lists;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+public class WatchedProjectsIT extends AbstractDaemonTest {
+
+  private static final String NEW_PROJECT_NAME = "newProjectAccess";
+
+  @Test
+  public void setAndGetWatchedProjects() throws Exception {
+    String projectName1 = createProject(NEW_PROJECT_NAME).get();
+    String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
+
+    List<ProjectWatchInfo> projectsToWatch = new ArrayList<>(2);
+
+    ProjectWatchInfo pwi = new ProjectWatchInfo();
+    pwi.project = projectName1;
+    pwi.notifyAbandonedChanges = true;
+    pwi.notifyNewChanges = true;
+    pwi.notifyAllComments = true;
+    projectsToWatch.add(pwi);
+
+    pwi = new ProjectWatchInfo();
+    pwi.project = projectName2;
+    pwi.filter = "branch:master";
+    pwi.notifySubmittedChanges = true;
+    pwi.notifyNewPatchSets = true;
+    projectsToWatch.add(pwi);
+
+    List<ProjectWatchInfo> persistedWatchedProjects =
+        gApi.accounts().self().setWatchedProjects(projectsToWatch);
+    assertThat(persistedWatchedProjects)
+        .containsAllIn(projectsToWatch).inOrder();
+  }
+
+  @Test
+  public void setAndDeleteWatchedProjects() throws Exception {
+    String projectName1 = createProject(NEW_PROJECT_NAME).get();
+    String projectName2 = createProject(NEW_PROJECT_NAME + "2").get();
+
+    List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
+
+    ProjectWatchInfo pwi = new ProjectWatchInfo();
+    pwi.project = projectName1;
+    pwi.notifyAbandonedChanges = true;
+    pwi.notifyNewChanges = true;
+    pwi.notifyAllComments = true;
+    projectsToWatch.add(pwi);
+
+    pwi = new ProjectWatchInfo();
+    pwi.project = projectName2;
+    pwi.filter = "branch:master";
+    pwi.notifySubmittedChanges = true;
+    pwi.notifyNewPatchSets = true;
+    projectsToWatch.add(pwi);
+
+    // Persist watched projects
+    gApi.accounts().self().setWatchedProjects(projectsToWatch);
+
+    List<String> d = Lists.newArrayList(projectName2);
+    gApi.accounts().self().deleteWatchedProjects(d);
+    projectsToWatch.remove(pwi);
+
+    List<ProjectWatchInfo> persistedWatchedProjects =
+        gApi.accounts().self().getWatchedProjects();
+
+    assertThat(persistedWatchedProjects).doesNotContain(pwi);
+    assertThat(persistedWatchedProjects).containsAllIn(projectsToWatch);
+  }
+
+  @Test
+  public void watchNonExistingProject() throws Exception {
+    String projectName = NEW_PROJECT_NAME + "3";
+
+    List<ProjectWatchInfo> projectsToWatch = new ArrayList<>(2);
+
+    ProjectWatchInfo pwi = new ProjectWatchInfo();
+    pwi.project = projectName;
+    pwi.notifyAbandonedChanges = true;
+    pwi.notifyNewChanges = true;
+    pwi.notifyAllComments = true;
+    projectsToWatch.add(pwi);
+
+    exception.expect(UnprocessableEntityException.class);
+    gApi.accounts().self().setWatchedProjects(projectsToWatch);
+  }
+
+  @Test
+  public void deleteNonExistingProject() throws Exception {
+    String projectName = project.get();
+
+    // Let another user watch a project
+    setApiUser(admin);
+    List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
+
+    ProjectWatchInfo pwi = new ProjectWatchInfo();
+    pwi.project = projectName;
+    pwi.notifyAbandonedChanges = true;
+    pwi.notifyNewChanges = true;
+    pwi.notifyAllComments = true;
+    projectsToWatch.add(pwi);
+
+    gApi.accounts().self().setWatchedProjects(projectsToWatch);
+
+    // Try to delete a watched project using a different user
+    List<String> d = Lists.newArrayList(projectName);
+    gApi.accounts().self().deleteWatchedProjects(d);
+
+    setApiUser(user);
+    exception.expect(UnprocessableEntityException.class);
+    gApi.accounts().self().deleteWatchedProjects(d);
+  }
+
+  @Test
+  public void modifyProjectWatchUsingOmittedValues() throws Exception {
+    String projectName = project.get();
+
+    // Let another user watch a project
+    setApiUser(admin);
+    List<ProjectWatchInfo> projectsToWatch = new LinkedList<>();
+
+    ProjectWatchInfo pwi = new ProjectWatchInfo();
+    pwi.project = projectName;
+    pwi.notifyAbandonedChanges = true;
+    pwi.notifyNewChanges = true;
+    pwi.notifyAllComments = true;
+    projectsToWatch.add(pwi);
+
+    // Persist a defined state
+    gApi.accounts().self().setWatchedProjects(projectsToWatch);
+
+    // Omit previously set value - will set it to false on the server
+    // The response will not carry this field then as we omit sending
+    // false values in JSON
+    pwi.notifyNewChanges = null;
+
+    // Perform update
+    gApi.accounts().self().setWatchedProjects(projectsToWatch);
+
+    List<ProjectWatchInfo> watchedProjects =
+        gApi.accounts().self().getWatchedProjects();
+
+    assertThat(watchedProjects).containsAllIn(projectsToWatch);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 28a01fb..5403e0d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -26,7 +26,6 @@
 import com.google.common.base.Function;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
@@ -82,6 +81,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.ByteArrayOutputStream;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -127,7 +127,7 @@
 
   @Before
   public void setUp() throws Exception {
-    mergeResults = Maps.newHashMap();
+    mergeResults = new HashMap<>();
     eventListenerRegistration =
         eventListeners.add(new UserScopedEventListener() {
           @Override
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 00d9bed..924eb4d 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.eclipse.jgit.lib.Constants.SIGNED_OFF_BY_TAG;
 
@@ -31,7 +32,6 @@
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.config.AnonymousCowardNameProvider;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.testutil.ConfigSuite;
 import com.google.gerrit.testutil.TestTimeUtil;
 
@@ -122,8 +122,7 @@
     try (Repository repo = repoManager.openRepository(project);
         RevWalk rw = new RevWalk(repo)) {
       RevCommit commit = rw.parseCommit(
-          repo.exactRef(ChangeNoteUtil.changeRefName(new Change.Id(c._number)))
-              .getObjectId());
+          repo.exactRef(changeMetaRef(new Change.Id(c._number))).getObjectId());
 
       assertThat(commit.getShortMessage()).isEqualTo("Create change");
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
index cc66394..29874e1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/DeleteDraftPatchSetIT.java
@@ -191,7 +191,7 @@
   private Ref getDraftRef(TestAccount account, Change.Id changeId)
       throws Exception {
     try (Repository repo = repoManager.openRepository(allUsers)) {
-      return repo.exactRef(RefNames.refsDraftComments(account.id, changeId));
+      return repo.exactRef(RefNames.refsDraftComments(changeId, account.id));
     }
   }
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
index 1f1c80a..a044772 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/HashtagsIT.java
@@ -38,7 +38,7 @@
 public class HashtagsIT extends AbstractDaemonTest {
   @Before
   public void before() {
-    assume().that(notesMigration.enabled()).isTrue();
+    assume().that(notesMigration.readChanges()).isTrue();
   }
 
   @BeforeClass
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
index 54eca70..b0d34f0 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ListChangesOptionsIT.java
@@ -20,7 +20,6 @@
 import static com.google.gerrit.extensions.client.ListChangesOption.MESSAGES;
 
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
@@ -29,6 +28,7 @@
 import org.junit.Before;
 import org.junit.Test;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @NoHttpd
@@ -39,7 +39,7 @@
 
   @Before
   public void setUp() throws Exception {
-    results = Lists.newArrayList();
+    results = new ArrayList<>();
     results.add(push("file contents", null));
     changeId = results.get(0).getChangeId();
     results.add(push("new contents 1", changeId));
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
index 8228a277..40ea296 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/GetRelatedIT.java
@@ -692,6 +692,7 @@
           PatchSet ps = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
           psUtil.setGroups(ctx.getDb(), ctx.getUpdate(psId), ps,
               ImmutableList.<String> of());
+          ctx.bumpLastUpdatedOn(false);
           return true;
         }
       });
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 1deabe7..f4ad11e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -16,6 +16,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -30,6 +31,8 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.Patch;
+import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -41,7 +44,6 @@
 import com.google.gerrit.server.change.RevisionResource;
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.NoteDbChangeState;
 import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
@@ -283,21 +285,20 @@
     PushOneCommit.Result r = createChange();
     Change.Id id = r.getPatchSetId().getParentKey();
 
-    ObjectId changeMetaId = getMetaRef(
-        project, ChangeNoteUtil.changeRefName(id));
+    ObjectId changeMetaId = getMetaRef(project, changeMetaRef(id));
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name());
 
     putDraft(user, id, 1, "comment by user");
     ObjectId userDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(user.getId(), id));
+        allUsers, RefNames.refsDraftComments(id, user.getId()));
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
         + "," + user.getId() + "=" + userDraftsId.name());
 
     putDraft(admin, id, 2, "comment by admin");
     ObjectId adminDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(admin.getId(), id));
+        allUsers, RefNames.refsDraftComments(id, admin.getId()));
     assertThat(admin.getId().get()).isLessThan(user.getId().get());
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
@@ -306,7 +307,7 @@
 
     putDraft(admin, id, 2, "revised comment by admin");
     adminDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(admin.getId(), id));
+        allUsers, RefNames.refsDraftComments(id, admin.getId()));
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
         + "," + admin.getId() + "=" + adminDraftsId.name()
@@ -459,6 +460,78 @@
     checker.rebuildAndCheckChanges(id);
   }
 
+  @Test
+  public void noteDbUsesOriginalSubjectFromPatchSetAndIgnoresChangeField()
+      throws Exception {
+    PushOneCommit.Result r = createChange();
+    String orig = r.getChange().change().getSubject();
+    r = pushFactory.create(
+            db, admin.getIdent(), testRepo, orig + " v2",
+            PushOneCommit.FILE_NAME, "new contents", r.getChangeId())
+        .to("refs/heads/master");
+    r.assertOkStatus();
+
+    PatchSet.Id psId = r.getPatchSetId();
+    Change.Id id = psId.getParentKey();
+    Change c = db.changes().get(id);
+
+    c.setCurrentPatchSet(psId, c.getSubject(), "Bogus original subject");
+    db.changes().update(Collections.singleton(c));
+
+    checker.rebuildAndCheckChanges(id);
+
+    notesMigration.setAllEnabled(true);
+    ChangeNotes notes = notesFactory.create(db, project, id);
+    Change nc = notes.getChange();
+    assertThat(nc.getSubject()).isEqualTo(c.getSubject());
+    assertThat(nc.getSubject()).isEqualTo(orig + " v2");
+    assertThat(nc.getOriginalSubject()).isNotEqualTo(c.getOriginalSubject());
+    assertThat(nc.getOriginalSubject()).isEqualTo(orig);
+  }
+
+  @Test
+  public void deleteDraftPS1WithNoOtherEntities() throws Exception {
+    PushOneCommit push = pushFactory.create(db, admin.getIdent(), testRepo);
+    PushOneCommit.Result r = push.to("refs/drafts/master");
+    push = pushFactory.create(db, admin.getIdent(), testRepo,
+        PushOneCommit.SUBJECT, "b.txt", "4711", r.getChangeId());
+    r = push.to("refs/drafts/master");
+    PatchSet.Id psId = r.getPatchSetId();
+    Change.Id id = psId.getParentKey();
+
+    gApi.changes().id(r.getChangeId()).revision(1).delete();
+
+    checker.rebuildAndCheckChanges(id);
+
+    notesMigration.setAllEnabled(true);
+    ChangeNotes notes = notesFactory.create(db, project, id);
+    assertThat(notes.getPatchSets().keySet()).containsExactly(psId);
+  }
+
+  @Test
+  public void ignorePatchLineCommentsOnPatchSet0() throws Exception {
+    PushOneCommit.Result r = createChange();
+    Change change = r.getChange().change();
+    Change.Id id = change.getId();
+
+    PatchLineComment comment = new PatchLineComment(
+        new PatchLineComment.Key(
+            new Patch.Key(new PatchSet.Id(id, 0), PushOneCommit.FILE_NAME),
+            "uuid"),
+        0, user.getId(), null, TimeUtil.nowTs());
+    comment.setSide((short) 1);
+    comment.setMessage("message");
+    comment.setStatus(PatchLineComment.Status.PUBLISHED);
+    db.patchComments().insert(Collections.singleton(comment));
+    indexer.index(db, change.getProject(), id);
+
+    checker.rebuildAndCheckChanges(id);
+
+    notesMigration.setAllEnabled(true);
+    ChangeNotes notes = notesFactory.create(db, project, id);
+    assertThat(notes.getComments()).isEmpty();
+  }
+
   private void setInvalidNoteDbState(Change.Id id) throws Exception {
     ReviewDb db = unwrapDb();
     Change c = db.changes().get(id);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
index 8dae9ae..56a56ee 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbandonRestoreIT.java
@@ -86,4 +86,4 @@
     }
     assertThat(actual).containsExactlyElementsIn(expected);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
index 20e6277..32a0175 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/UploadArchiveIT.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.acceptance.ssh;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
 
 import com.google.common.base.Splitter;
 import com.google.common.collect.Iterables;
@@ -22,12 +23,14 @@
 import com.google.gerrit.acceptance.GerritConfig;
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.testutil.NoteDbMode;
 
 import org.apache.commons.compress.archivers.zip.ZipArchiveEntry;
 import org.apache.commons.compress.archivers.zip.ZipArchiveInputStream;
 import org.eclipse.jgit.transport.PacketLineIn;
 import org.eclipse.jgit.transport.PacketLineOut;
 import org.eclipse.jgit.util.IO;
+import org.junit.Before;
 import org.junit.Test;
 
 import java.io.ByteArrayInputStream;
@@ -39,6 +42,13 @@
 @NoHttpd
 public class UploadArchiveIT extends AbstractDaemonTest {
 
+  @Before
+  public void setUp() {
+    // There is some Guice request scoping problem preventing this test from
+    // passing in CHECK mode.
+    assume().that(NoteDbMode.get()).isNotEqualTo(NoteDbMode.CHECK);
+  }
+
   @Test
   @GerritConfig(name = "download.archive", value = "off")
   public void archiveFeatureOff() throws Exception {
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 3ffb76c..62bf3e9 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
@@ -17,7 +17,6 @@
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
-import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.ThreadFactoryBuilder;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -40,6 +39,7 @@
 import java.io.IOException;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ExecutorService;
@@ -70,7 +70,7 @@
     config = cfg;
     cacheDir = getCacheDir(site, cfg.getString("cache", null, "directory"));
     h2CacheSize = cfg.getLong("cache", null, "h2CacheSize", -1);
-    caches = Lists.newLinkedList();
+    caches = new LinkedList<>();
     this.cacheMap = cacheMap;
 
     if (cacheDir != null) {
diff --git a/gerrit-cache-h2/src/test/java/com/google/gerrit/server/cache/h2/H2CacheTest.java b/gerrit-cache-h2/src/test/java/com/google/gerrit/server/cache/h2/H2CacheTest.java
index 3b7e436..c999d71 100644
--- a/gerrit-cache-h2/src/test/java/com/google/gerrit/server/cache/h2/H2CacheTest.java
+++ b/gerrit-cache-h2/src/test/java/com/google/gerrit/server/cache/h2/H2CacheTest.java
@@ -79,4 +79,4 @@
     }));
     assertFalse("did not invoke Callable", called.get());
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
index a6e54b2..2105b8c 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/accounts/AccountApi.java
@@ -17,6 +17,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.SshKeyInfo;
@@ -43,6 +44,12 @@
   EditPreferencesInfo setEditPreferences(EditPreferencesInfo in)
       throws RestApiException;
 
+  List<ProjectWatchInfo> getWatchedProjects() throws RestApiException;
+  List<ProjectWatchInfo> setWatchedProjects(List<ProjectWatchInfo> in)
+      throws RestApiException;
+  void deleteWatchedProjects(List<String> in)
+      throws RestApiException;
+
   void starChange(String id) throws RestApiException;
   void unstarChange(String id) throws RestApiException;
   void addEmail(EmailInput input) throws RestApiException;
@@ -105,6 +112,24 @@
     }
 
     @Override
+    public List<ProjectWatchInfo> getWatchedProjects()
+        throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public List<ProjectWatchInfo> setWatchedProjects(
+        List<ProjectWatchInfo> in) throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
+    public void deleteWatchedProjects(List<String> in)
+        throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public void starChange(String id) throws RestApiException {
       throw new NotImplementedException();
     }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
index 27bdf16..92ac1d6 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectInput.java
@@ -41,4 +41,4 @@
     public String value;
     public List<String> values;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java
index 57d4849..ce6464d 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/InheritableBoolean.java
@@ -18,4 +18,4 @@
   TRUE,
   FALSE,
   INHERIT
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
index 66641b0..9c85ac7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/KeyMapType.java
@@ -19,4 +19,4 @@
   EMACS,
   SUBLIME,
   VIM
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
index 6f4190d..3114cb9 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectState.java
@@ -18,4 +18,4 @@
   ACTIVE,
   READ_ONLY,
   HIDDEN
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
new file mode 100644
index 0000000..beb869e
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/ProjectWatchInfo.java
@@ -0,0 +1,50 @@
+// 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;
+
+import java.util.Objects;
+
+public class ProjectWatchInfo {
+  public String project;
+  public String filter;
+
+  public Boolean notifyNewChanges;
+  public Boolean notifyNewPatchSets;
+  public Boolean notifyAllComments;
+  public Boolean notifySubmittedChanges;
+  public Boolean notifyAbandonedChanges;
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof ProjectWatchInfo) {
+      ProjectWatchInfo w = (ProjectWatchInfo) obj;
+      return Objects.equals(project, w.project)
+          && Objects.equals(filter, w.filter)
+          && Objects.equals(notifyNewChanges, w.notifyNewChanges)
+          && Objects.equals(notifyNewPatchSets, w.notifyNewPatchSets)
+          && Objects.equals(notifyAllComments, w.notifyAllComments)
+          && Objects.equals(notifySubmittedChanges, w.notifySubmittedChanges)
+          && Objects.equals(notifyAbandonedChanges, w.notifyAbandonedChanges);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects
+        .hash(project, filter, notifyNewChanges, notifyNewPatchSets,
+            notifyAllComments, notifySubmittedChanges, notifyAbandonedChanges);
+  }
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
index 2b916f1..fcfeb01 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/SubmitType.java
@@ -20,4 +20,4 @@
   REBASE_IF_NECESSARY,
   MERGE_ALWAYS,
   CHERRY_PICK
-}
\ No newline at end of file
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java
index fc4ae03..c03a684 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/client/Theme.java
@@ -118,4 +118,4 @@
         return false;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
index 3c067ee0..5cc7799 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/ChunkManager.java
@@ -119,4 +119,4 @@
   }
 
   abstract int getCmLine(int line, DisplaySide side);
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
index cd791aeb..d3c150d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentRange.java
@@ -52,4 +52,4 @@
 
   protected CommentRange() {
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffChunkInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffChunkInfo.java
index 4725b1e..51a7c8b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffChunkInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffChunkInfo.java
@@ -50,4 +50,4 @@
       return comp == 0 ? 1 : comp;
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NoOpKeyCommand.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NoOpKeyCommand.java
index c22769e..969b861 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NoOpKeyCommand.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/NoOpKeyCommand.java
@@ -26,4 +26,4 @@
   @Override
   public void onKeyPress(KeyPressEvent event) {
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideChunkManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideChunkManager.java
index 9b8a6aa..117fece 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideChunkManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideChunkManager.java
@@ -271,4 +271,4 @@
   int getCmLine(int line, DisplaySide side) {
     return line;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java
index 053dbd3..9801d60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/RefInfo.java
@@ -27,4 +27,4 @@
 
   protected RefInfo() {
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
index bf8fe01..063f22b 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/GitOverHttpServlet.java
@@ -256,10 +256,8 @@
           uploadValidatorsFactory.create(pc.getProject(), repo, request.getRemoteHost());
       up.setPreUploadHook(PreUploadHookChain.newChain(
           Lists.newArrayList(up.getPreUploadHook(), uploadValidators)));
-      if (!pc.allRefsAreVisible()) {
-        up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache,
-            repo, pc, db.get(), true));
-      }
+      up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache,
+          repo, pc, db.get(), true));
 
       next.doFilter(request, response);
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
index d1c617f..8ae0e5c 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpAutoRegisterModuleGenerator.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.server.plugins.AutoRegisterUtil.calculateBindAnnotation;
 
 import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.server.plugins.InvalidPluginException;
@@ -28,13 +27,14 @@
 import com.google.inject.servlet.ServletModule;
 
 import java.lang.annotation.Annotation;
+import java.util.HashMap;
 import java.util.Map;
 
 import javax.servlet.http.HttpServlet;
 
 class HttpAutoRegisterModuleGenerator extends ServletModule
     implements ModuleGenerator {
-  private final Map<String, Class<HttpServlet>> serve = Maps.newHashMap();
+  private final Map<String, Class<HttpServlet>> serve = new HashMap<>();
   private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
 
   @Override
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 a4cd623..beb0139 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
@@ -66,8 +66,10 @@
 import java.nio.charset.Charset;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentMap;
@@ -100,7 +102,7 @@
   private final int sshPort;
   private final RestApiServlet managerApi;
 
-  private List<Plugin> pending = Lists.newArrayList();
+  private List<Plugin> pending = new ArrayList<>();
   private ContextMapper wrapper;
   private final ConcurrentMap<String, PluginHolder> plugins
       = Maps.newConcurrentMap();
@@ -370,10 +372,10 @@
       String prefix, String pluginName,
       PluginResourceKey cacheKey, HttpServletResponse res,long lastModifiedTime)
       throws IOException {
-    List<PluginEntry> cmds = Lists.newArrayList();
-    List<PluginEntry> servlets = Lists.newArrayList();
-    List<PluginEntry> restApis = Lists.newArrayList();
-    List<PluginEntry> docs = Lists.newArrayList();
+    List<PluginEntry> cmds = new ArrayList<>();
+    List<PluginEntry> servlets = new ArrayList<>();
+    List<PluginEntry> restApis = new ArrayList<>();
+    List<PluginEntry> docs = new ArrayList<>();
     PluginEntry about = null;
     Enumeration<PluginEntry> entries = scanner.entries();
     while (entries.hasMoreElements()) {
@@ -443,7 +445,7 @@
   private void sendMarkdownAsHtml(String md, String pluginName,
       PluginResourceKey cacheKey, HttpServletResponse res, long lastModifiedTime)
       throws UnsupportedEncodingException, IOException {
-    Map<String, String> macros = Maps.newHashMap();
+    Map<String, String> macros = new HashMap<>();
     macros.put("PLUGIN", pluginName);
     macros.put("SSH_HOST", sshHost);
     macros.put("SSH_PORT", "" + sshPort);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
index 6a9bf9f..af4776d 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/LfsPluginServlet.java
@@ -16,7 +16,6 @@
 
 import static javax.servlet.http.HttpServletResponse.SC_NOT_IMPLEMENTED;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.httpd.resources.Resource;
 import com.google.gerrit.server.config.GerritServerConfig;
@@ -33,6 +32,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.FilterChain;
@@ -55,7 +55,7 @@
   public static final String URL_REGEX =
       "^(?:/a)?(?:/p/|/)(.+)(?:/info/lfs/objects/batch)$";
 
-  private List<Plugin> pending = Lists.newArrayList();
+  private List<Plugin> pending = new ArrayList<>();
   private final String pluginName;
   private GuiceFilter filter;
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 43c66db..32a80fe 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -18,7 +18,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
 import com.google.common.hash.Hasher;
 import com.google.common.hash.Hashing;
 import com.google.common.primitives.Bytes;
@@ -231,7 +230,7 @@
   }
 
   private void plugins(StringWriter w) {
-    List<String> urls = Lists.newArrayList();
+    List<String> urls = new ArrayList<>();
     for (WebUiPlugin u : plugins) {
       urls.add(String.format("plugins/%s/%s",
           u.getPluginName(),
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
index 46843fc..4dd21ae 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/restapi/ParameterParser.java
@@ -24,7 +24,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.Url;
@@ -40,6 +39,7 @@
 
 import java.io.IOException;
 import java.io.StringWriter;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Set;
@@ -107,7 +107,7 @@
   }
 
   private static Set<String> query(HttpServletRequest req) {
-    Set<String> params = Sets.newHashSet();
+    Set<String> params = new HashSet<>();
     if (!Strings.isNullOrEmpty(req.getQueryString())) {
       for (String kvPair : Splitter.on('&').split(req.getQueryString())) {
         params.add(Iterables.getFirst(
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 660dbbb..27f291a 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
@@ -40,9 +40,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 import com.google.common.io.BaseEncoding;
 import com.google.common.io.CountingOutputStream;
 import com.google.common.math.IntMath;
@@ -123,10 +121,14 @@
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.sql.Timestamp;
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Pattern;
 import java.util.zip.GZIPOutputStream;
@@ -712,13 +714,13 @@
 
   private static void enablePartialGetFields(GsonBuilder gb,
       Multimap<String, String> config) {
-    final Set<String> want = Sets.newHashSet();
+    final Set<String> want = new HashSet<>();
     for (String p : config.get("fields")) {
       Iterables.addAll(want, OptionUtil.splitOptionValue(p));
     }
     if (!want.isEmpty()) {
       gb.addSerializationExclusionStrategy(new ExclusionStrategy() {
-        private final Map<String, String> names = Maps.newHashMap();
+        private final Map<String, String> names = new HashMap<>();
 
         @Override
         public boolean shouldSkipField(FieldAttributes field) {
@@ -917,7 +919,7 @@
       }
     }
 
-    Map<String, RestView<RestResource>> r = Maps.newTreeMap();
+    Map<String, RestView<RestResource>> r = new TreeMap<>();
     for (String plugin : views.plugins()) {
       RestView<RestResource> action = views.get(plugin, name);
       if (action != null) {
@@ -950,7 +952,7 @@
     if (Strings.isNullOrEmpty(path)) {
       return Collections.emptyList();
     }
-    List<IdString> out = Lists.newArrayList();
+    List<IdString> out = new ArrayList<>();
     for (String p : Splitter.on('/').split(path)) {
       out.add(IdString.fromUrl(p));
     }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
index 0fff8ce..91afd97 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/account/AgreementInfoFactory.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.httpd.rpc.account;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.AgreementInfo;
 import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.common.data.PermissionRule;
@@ -29,7 +27,9 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -54,14 +54,14 @@
 
   @Override
   public AgreementInfo call() throws Exception {
-    List<String> accepted = Lists.newArrayList();
-    Map<String, ContributorAgreement> agreements = Maps.newHashMap();
+    List<String> accepted = new ArrayList<>();
+    Map<String, ContributorAgreement> agreements = new HashMap<>();
     Collection<ContributorAgreement> cas =
         projectCache.getAllProjects().getConfig().getContributorAgreements();
     for (ContributorAgreement ca : cas) {
       agreements.put(ca.getName(), ca.forUi());
 
-      List<AccountGroup.UUID> groupIds = Lists.newArrayList();
+      List<AccountGroup.UUID> groupIds = new ArrayList<>();
       for (PermissionRule rule : ca.getAccepted()) {
         if ((rule.getAction() == Action.ALLOW) && (rule.getGroup() != null)) {
           if (rule.getGroup().getUUID() == null) {
diff --git a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
index b49a9f2..94408c6 100644
--- a/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
+++ b/gerrit-lucene/src/main/java/com/google/gerrit/lucene/LuceneVersionManager.java
@@ -226,7 +226,7 @@
 
   private <K, V, I extends Index<K, V>> TreeMap<Integer, Version<V>>
       scanVersions(IndexDefinition<K, V, I> def, GerritIndexStatus cfg) {
-    TreeMap<Integer, Version<V>> versions = Maps.newTreeMap();
+    TreeMap<Integer, Version<V>> versions = new TreeMap<>();
     for (Schema<V> schema : def.getSchemas().values()) {
       // This part is Lucene-specific.
       Path p = getDir(sitePaths, def.getName(), schema);
diff --git a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
index cb441236..3a40252 100644
--- a/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
+++ b/gerrit-openid/src/main/java/com/google/gerrit/httpd/auth/openid/LoginForm.java
@@ -20,7 +20,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.auth.openid.OpenIdUrls;
@@ -46,6 +45,7 @@
 import org.w3c.dom.Element;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -102,7 +102,7 @@
       suggestProviders = ImmutableSet.of();
       ssoUrl = authConfig.getOpenIdSsoUrl();
     } else {
-      Set<String> providers = Sets.newHashSet();
+      Set<String> providers = new HashSet<>();
       for (Map.Entry<String, String> e : ALL_PROVIDERS.entrySet()) {
         if (impl.isAllowedOpenID(e.getValue())) {
           providers.add(e.getKey());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index af14ff9..da81843 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -123,7 +123,7 @@
 
   @Override
   protected void afterInit(SiteRun run) throws Exception {
-    List<Module> modules = Lists.newArrayList();
+    List<Module> modules = new ArrayList<>();
     modules.add(new AbstractModule() {
       @Override
       protected void configure() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
index bbb16e7..0e647b9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Reindex.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.gerrit.server.schema.DataSourceProvider.Context.MULTI_USER;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.lucene.LuceneIndexModule;
@@ -42,6 +41,7 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -114,7 +114,7 @@
     if (changesVersion != null) {
       versions.put(ChangeSchemaDefinitions.INSTANCE.getName(), changesVersion);
     }
-    List<Module> modules = Lists.newArrayList();
+    List<Module> modules = new ArrayList<>();
     Module indexModule;
     switch (IndexModule.getIndexType(dbInjector)) {
       case LUCENE:
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index d16c3bd..540ba0b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -20,7 +20,6 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.common.IoUtil;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
@@ -247,7 +246,7 @@
         bind(Path.class).annotatedWith(SitePath.class).toInstance(sitePath);
         List<String> plugins =
             MoreObjects.firstNonNull(
-                getInstallPlugins(), Lists.<String> newArrayList());
+                getInstallPlugins(), new ArrayList<String>());
         bind(new TypeLiteral<List<String>>() {}).annotatedWith(
             InstallPlugins.class).toInstance(plugins);
         bind(new TypeLiteral<Boolean>() {}).annotatedWith(
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
index c9b2b92..43d7d3b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/InitPlugins.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.pgm.init;
 
 import com.google.common.collect.FluentIterable;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.PluginData;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
@@ -30,6 +29,7 @@
 import java.io.InputStream;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
@@ -55,7 +55,7 @@
   private static List<PluginData> listPlugins(final SitePaths site,
       final boolean deleteTempPluginFile, PluginsDistribution pluginsDistribution)
           throws IOException {
-    final List<PluginData> result = Lists.newArrayList();
+    final List<PluginData> result = new ArrayList<>();
     pluginsDistribution.foreach(new PluginsDistribution.Processor() {
       @Override
       public void process(String pluginName, InputStream in) throws IOException {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
index ffb0017..65a66de 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/PostgreSQLInitializer.java
@@ -29,4 +29,4 @@
     databaseSection.string("Database username", "username", username());
     databaseSection.password("username", "password");
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
index 9060bf0..7f86a8a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/SitePathInitializer.java
@@ -105,6 +105,7 @@
     extractMailExample("ChangeSubject.vm");
     extractMailExample("Comment.vm");
     extractMailExample("CommentFooter.vm");
+    extractMailExample("DeleteReviewer.vm");
     extractMailExample("DeleteVote.vm");
     extractMailExample("Footer.vm");
     extractMailExample("Merged.vm");
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
index eb12937..9ef31ff 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/PerThreadReviewDbModule.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.pgm.util;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -24,6 +23,7 @@
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -43,7 +43,7 @@
   @Override
   protected void configure() {
     final List<ReviewDb> dbs = Collections.synchronizedList(
-        Lists.<ReviewDb> newArrayList());
+        new ArrayList<ReviewDb>());
     final ThreadLocal<ReviewDb> localDb = new ThreadLocal<>();
 
     bind(ReviewDb.class).toProvider(new Provider<ReviewDb>() {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
index 7991423..9e2da5c 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/SiteProgram.java
@@ -18,7 +18,6 @@
 import static com.google.inject.Scopes.SINGLETON;
 import static com.google.inject.Stage.PRODUCTION;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -224,7 +223,7 @@
       throw new RuntimeException(e);
     }
 
-    List<Module> modules = Lists.newArrayList();
+    List<Module> modules = new ArrayList<>();
     modules.add(new AbstractModule() {
       @Override
       protected void configure() {
diff --git a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
index 4a1e587..4d9d0f0 100644
--- a/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
+++ b/gerrit-pgm/src/main/resources/com/google/gerrit/pgm/init/libraries.config
@@ -16,14 +16,14 @@
 # Version should match lib/bouncycastle/BUCK
 [library "bouncyCastleProvider"]
   name = Bouncy Castle Crypto Provider v152
-  url = http://repo2.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.52/bcprov-jdk15on-1.52.jar
+  url = https://repo1.maven.org/maven2/org/bouncycastle/bcprov-jdk15on/1.52/bcprov-jdk15on-1.52.jar
   sha1 = 88a941faf9819d371e3174b5ed56a3f3f7d73269
   remove = bcprov-.*[.]jar
 
 # Version should match lib/bouncycastle/BUCK
 [library "bouncyCastleSSL"]
   name = Bouncy Castle Crypto SSL v152
-  url = http://repo2.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.52/bcpkix-jdk15on-1.52.jar
+  url = https://repo1.maven.org/maven2/org/bouncycastle/bcpkix-jdk15on/1.52/bcpkix-jdk15on-1.52.jar
   sha1 = b8ffac2bbc6626f86909589c8cc63637cc936504
   needs = bouncyCastleProvider
   remove = bcpkix-.*[.]jar
@@ -31,14 +31,14 @@
 # Version should match lib/bouncycastle/BUCK
 [library "bouncyCastlePGP"]
   name = Bouncy Castle Crypto OpenPGP v152
-  url = http://repo2.maven.org/maven2/org/bouncycastle/bcpg-jdk15on/1.52/bcpg-jdk15on-1.52.jar
+  url = https://repo1.maven.org/maven2/org/bouncycastle/bcpg-jdk15on/1.52/bcpg-jdk15on-1.52.jar
   sha1 = ff4665a4b5633ff6894209d5dd10b7e612291858
   needs = bouncyCastleProvider
   remove = bcpg-.*[.]jar
 
 [library "mysqlDriver"]
   name = MySQL Connector/J 5.1.21
-  url = http://repo2.maven.org/maven2/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar
+  url = https://repo1.maven.org/maven2/mysql/mysql-connector-java/5.1.21/mysql-connector-java-5.1.21.jar
   sha1 = 7abbd19fc2e2d5b92c0895af8520f7fa30266be9
   remove = mysql-connector-java-.*[.]jar
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
index 77b0137..9e36fc1 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Account.java
@@ -122,46 +122,23 @@
      *              We assume that the caller has trimmed any prefix.
      */
     public static Id fromRefPart(String name) {
-      if (name == null) {
-        return null;
-      }
+      Integer id = RefNames.parseShardedRefPart(name);
+      return id != null ? new Account.Id(id) : null;
+    }
 
-      String[] parts = name.split("/");
-      int n = parts.length;
-      if (n < 2) {
-        return null;
-      }
-
-      // Last 2 digits.
-      int le;
-      for (le = 0; le < parts[0].length(); le++) {
-        if (!Character.isDigit(parts[0].charAt(le))) {
-          return null;
-        }
-      }
-      if (le != 2) {
-        return null;
-      }
-
-      // Full ID.
-      int ie;
-      for (ie = 0; ie < parts[1].length(); ie++) {
-        if (!Character.isDigit(parts[1].charAt(ie))) {
-          if (ie == 0) {
-            return null;
-          } else {
-            break;
-          }
-        }
-      }
-
-      int shard = Integer.parseInt(parts[0]);
-      int id = Integer.parseInt(parts[1].substring(0, ie));
-
-      if (id % 100 != shard) {
-        return null;
-      }
-      return new Account.Id(id);
+    /**
+     * Parse an Account.Id out of the last part of a ref name.
+     * <p>
+     * The input is a ref name of the form {@code ".../1234"}, where the suffix
+     * is a non-sharded account ID. Ref names using a sharded ID should use
+     * {@link #fromRefPart(String)} instead for greater safety.
+     *
+     * @param name ref name
+     * @return account ID, or null if not numeric.
+     */
+    public static Id fromRefSuffix(String name) {
+      Integer id = RefNames.parseRefSuffix(name);
+      return id != null ? new Account.Id(id) : null;
     }
   }
 
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 863ed08..88efd50 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -163,6 +163,11 @@
       return new Change.Id(Integer.parseInt(id));
     }
 
+    public static Id fromRefPart(String ref) {
+      Integer id = RefNames.parseShardedRefPart(ref);
+      return id != null ? new Change.Id(id) : null;
+    }
+
     static int startIndex(String ref) {
       if (ref == null || !ref.startsWith(REFS_CHANGES)) {
         return -1;
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java
index b1b2615..5a98d94 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/CommentRange.java
@@ -96,4 +96,4 @@
     return "Range[startLine=" + startLine + ", startCharacter=" + startCharacter
         + ", endLine=" + endLine + ", endCharacter=" + endCharacter + "]";
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
index 85ebdb1..d7c546b 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/RefNames.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.reviewdb.client;
 
-
 /** Constants and utilities for Gerrit-specific ref names. */
 public class RefNames {
   public static final String REFS = "refs/";
@@ -79,6 +78,21 @@
     return ref;
   }
 
+  public static String changeMetaRef(Change.Id id) {
+    StringBuilder r = new StringBuilder();
+    r.append(REFS_CHANGES);
+    int n = id.get();
+    int m = n % 100;
+    if (m < 10) {
+      r.append('0');
+    }
+    r.append(m);
+    r.append('/');
+    r.append(n);
+    r.append(META_SUFFIX);
+    return r.toString();
+  }
+
   public static String refsUsers(Account.Id accountId) {
     StringBuilder r = new StringBuilder();
     r.append(REFS_USERS);
@@ -93,15 +107,15 @@
     return r.toString();
   }
 
-  public static String refsDraftComments(Account.Id accountId,
-      Change.Id changeId) {
-    StringBuilder r = buildRefsPrefix(REFS_DRAFT_COMMENTS, accountId.get());
-    r.append(changeId.get());
+  public static String refsDraftComments(Change.Id changeId,
+      Account.Id accountId) {
+    StringBuilder r = buildRefsPrefix(REFS_DRAFT_COMMENTS, changeId.get());
+    r.append(accountId.get());
     return r.toString();
   }
 
-  public static String refsDraftCommentsPrefix(Account.Id accountId) {
-    return buildRefsPrefix(REFS_DRAFT_COMMENTS, accountId.get()).toString();
+  public static String refsDraftCommentsPrefix(Change.Id changeId) {
+    return buildRefsPrefix(REFS_DRAFT_COMMENTS, changeId.get()).toString();
   }
 
   public static String refsStarredChanges(Change.Id changeId,
@@ -160,6 +174,69 @@
       .toString();
   }
 
+  static Integer parseShardedRefPart(String name) {
+    if (name == null) {
+      return null;
+    }
+
+    String[] parts = name.split("/");
+    int n = parts.length;
+    if (n < 2) {
+      return null;
+    }
+
+    // Last 2 digits.
+    int le;
+    for (le = 0; le < parts[0].length(); le++) {
+      if (!Character.isDigit(parts[0].charAt(le))) {
+        return null;
+      }
+    }
+    if (le != 2) {
+      return null;
+    }
+
+    // Full ID.
+    int ie;
+    for (ie = 0; ie < parts[1].length(); ie++) {
+      if (!Character.isDigit(parts[1].charAt(ie))) {
+        if (ie == 0) {
+          return null;
+        } else {
+          break;
+        }
+      }
+    }
+
+    int shard = Integer.parseInt(parts[0]);
+    int id = Integer.parseInt(parts[1].substring(0, ie));
+
+    if (id % 100 != shard) {
+      return null;
+    }
+    return id;
+  }
+
+  static Integer parseRefSuffix(String name) {
+    if (name == null) {
+      return null;
+    }
+    int i = name.length();
+    while (i > 0) {
+      char c = name.charAt(i - 1);
+      if (c == '/') {
+        break;
+      } else if (!Character.isDigit(c)) {
+        return null;
+      }
+      i--;
+    }
+    if (i == 0) {
+      return null;
+    }
+    return Integer.valueOf(name.substring(i, name.length()));
+  }
+
   private RefNames() {
   }
 }
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
index e8dc5e0..00bf44e 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/AccountTest.java
@@ -14,69 +14,48 @@
 
 package com.google.gerrit.reviewdb.client;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.reviewdb.client.Account.Id.fromRef;
+import static com.google.gerrit.reviewdb.client.Account.Id.fromRefPart;
+import static com.google.gerrit.reviewdb.client.Account.Id.fromRefSuffix;
 
 import org.junit.Test;
 
 public class AccountTest {
   @Test
   public void parseRefName() {
-    assertRef(1, "refs/users/01/1");
-    assertRef(1, "refs/users/01/1-drafts");
-    assertRef(1, "refs/users/01/1-drafts/2");
-    assertRef(1, "refs/users/01/1/edit/2");
+    assertThat(fromRef("refs/users/01/1")).isEqualTo(id(1));
+    assertThat(fromRef("refs/users/01/1-drafts")).isEqualTo(id(1));
+    assertThat(fromRef("refs/users/01/1-drafts/2")).isEqualTo(id(1));
+    assertThat(fromRef("refs/users/01/1/edit/2")).isEqualTo(id(1));
 
-    assertNotRef(null);
-    assertNotRef("");
+    assertThat(fromRef(null)).isNull();
+    assertThat(fromRef("")).isNull();
 
     // Invalid characters.
-    assertNotRef("refs/users/01a/1");
-    assertNotRef("refs/users/01/a1");
+    assertThat(fromRef("refs/users/01a/1")).isNull();
+    assertThat(fromRef("refs/users/01/a1")).isNull();
 
     // Mismatched shard.
-    assertNotRef("refs/users/01/23");
+    assertThat(fromRef("refs/users/01/23")).isNull();
 
     // Shard too short.
-    assertNotRef("refs/users/1/1");
+    assertThat(fromRef("refs/users/1/1")).isNull();
   }
 
   @Test
   public void parseRefNameParts() {
-    assertRefPart(1, "01/1");
-    assertRefPart(1, "01/1-drafts");
-    assertRefPart(1, "01/1-drafts/2");
-
-    assertNotRefPart(null);
-    assertNotRefPart("");
-
-    // This method assumes that the common prefix "refs/users/" will be removed.
-    assertNotRefPart("refs/users/01/1");
-
-    // Invalid characters.
-    assertNotRefPart("01a/1");
-    assertNotRefPart("01/a1");
-
-    // Mismatched shard.
-    assertNotRefPart("01/23");
-
-    // Shard too short.
-    assertNotRefPart("1/1");
+    assertThat(fromRefPart("01/1")).isEqualTo(id(1));
+    assertThat(fromRefPart("ab/cd")).isNull();
   }
 
-  private static void assertRef(int accountId, String refName) {
-    assertEquals(new Account.Id(accountId), Account.Id.fromRef(refName));
+  @Test
+  public void parseRefSuffix() {
+    assertThat(fromRefSuffix("12/34")).isEqualTo(id(34));
+    assertThat(fromRefSuffix("ab/cd")).isNull();
   }
 
-  private static void assertNotRef(String refName) {
-    assertNull(Account.Id.fromRef(refName));
-  }
-
-  private static void assertRefPart(int accountId, String refName) {
-    assertEquals(new Account.Id(accountId), Account.Id.fromRefPart(refName));
-  }
-
-  private static void assertNotRefPart(String refName) {
-    assertNull(Account.Id.fromRefPart(refName));
+  private Account.Id id(int n) {
+    return new Account.Id(n);
   }
 }
diff --git a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
index 6797ffb..0a35eea 100644
--- a/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
+++ b/gerrit-reviewdb/src/test/java/com/google/gerrit/reviewdb/client/RefNamesTest.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.reviewdb.client;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.reviewdb.client.RefNames.parseShardedRefPart;
+import static com.google.gerrit.reviewdb.client.RefNames.parseRefSuffix;
 
 import org.junit.Test;
 
@@ -38,14 +40,14 @@
 
   @Test
   public void refsDraftComments() throws Exception {
-    assertThat(RefNames.refsDraftComments(accountId, changeId))
-      .isEqualTo("refs/draft-comments/23/1011123/67473");
+    assertThat(RefNames.refsDraftComments(changeId, accountId))
+      .isEqualTo("refs/draft-comments/73/67473/1011123");
   }
 
   @Test
   public void refsDraftCommentsPrefix() throws Exception {
-    assertThat(RefNames.refsDraftCommentsPrefix(accountId))
-      .isEqualTo("refs/draft-comments/23/1011123/");
+    assertThat(RefNames.refsDraftCommentsPrefix(changeId))
+      .isEqualTo("refs/draft-comments/73/67473/");
   }
 
   @Test
@@ -65,4 +67,42 @@
     assertThat(RefNames.refsEdit(accountId, changeId, psId))
       .isEqualTo("refs/users/23/1011123/edit-67473/42");
   }
+
+  @Test
+  public void testParseShardedRefsPart() throws Exception {
+    assertThat(parseShardedRefPart("01/1")).isEqualTo(1);
+    assertThat(parseShardedRefPart("01/1-drafts")).isEqualTo(1);
+    assertThat(parseShardedRefPart("01/1-drafts/2")).isEqualTo(1);
+
+    assertThat(parseShardedRefPart(null)).isNull();
+    assertThat(parseShardedRefPart("")).isNull();
+
+    // Prefix not stripped.
+    assertThat(parseShardedRefPart("refs/users/01/1")).isNull();
+
+    // Invalid characters.
+    assertThat(parseShardedRefPart("01a/1")).isNull();
+    assertThat(parseShardedRefPart("01/a1")).isNull();
+
+    // Mismatched shard.
+    assertThat(parseShardedRefPart("01/23")).isNull();
+
+    // Shard too short.
+    assertThat(parseShardedRefPart("1/1")).isNull();
+  }
+
+  @Test
+  public void testParseRefSuffix() throws Exception {
+    assertThat(parseRefSuffix("1/2/34")).isEqualTo(34);
+    assertThat(parseRefSuffix("/34")).isEqualTo(34);
+
+    assertThat(parseRefSuffix(null)).isNull();
+    assertThat(parseRefSuffix("")).isNull();
+    assertThat(parseRefSuffix("34")).isNull();
+    assertThat(parseRefSuffix("12/ab")).isNull();
+    assertThat(parseRefSuffix("12/a4")).isNull();
+    assertThat(parseRefSuffix("12/4a")).isNull();
+    assertThat(parseRefSuffix("a4")).isNull();
+    assertThat(parseRefSuffix("4a")).isNull();
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index b8d609d..6d84599 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -54,6 +54,7 @@
 import com.google.gerrit.server.events.ProjectCreatedEvent;
 import com.google.gerrit.server.events.RefUpdatedEvent;
 import com.google.gerrit.server.events.ReviewerAddedEvent;
+import com.google.gerrit.server.events.ReviewerDeletedEvent;
 import com.google.gerrit.server.events.TopicChangedEvent;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.WorkQueue;
@@ -183,6 +184,9 @@
     /** Path of the reviewer added hook. */
     private final Optional<Path> reviewerAddedHook;
 
+    /** Path of the reviewer deleted hook. */
+    private final Optional<Path> reviewerDeletedHook;
+
     /** Path of the topic changed hook. */
     private final Optional<Path> topicChangedHook;
 
@@ -269,6 +273,7 @@
         changeRestoredHook = hook(config, hooksPath, "change-restored");
         refUpdatedHook = hook(config, hooksPath, "ref-updated");
         reviewerAddedHook = hook(config, hooksPath, "reviewer-added");
+        reviewerDeletedHook = hook(config, hooksPath, "reviewer-deleted");
         topicChangedHook = hook(config, hooksPath, "topic-changed");
         claSignedHook = hook(config, hooksPath, "cla-signed");
         refUpdateHook = hook(config, hooksPath, "ref-update");
@@ -700,6 +705,70 @@
     }
 
     @Override
+    public void doReviewerDeletedHook(final Change change, Account account,
+      PatchSet patchSet, String comment, final Map<String, Short> approvals,
+      final Map<String, Short> oldApprovals, ReviewDb db) throws OrmException {
+
+      ReviewerDeletedEvent event = new ReviewerDeletedEvent(change);
+      event.change = changeAttributeSupplier(change);
+      event.patchSet = patchSetAttributeSupplier(change, patchSet);
+      event.reviewer = accountAttributeSupplier(account);
+      event.comment = comment;
+      event.approvals = Suppliers.memoize(
+          new Supplier<ApprovalAttribute[]>() {
+            @Override
+            public ApprovalAttribute[] get() {
+              LabelTypes labelTypes = projectCache.get(
+                  change.getProject()).getLabelTypes();
+              if (!approvals.isEmpty()) {
+                ApprovalAttribute[] r = new ApprovalAttribute[approvals.size()];
+                int i = 0;
+                for (Map.Entry<String, Short> approval : approvals.entrySet()) {
+                  r[i++] = getApprovalAttribute(labelTypes, approval,
+                      oldApprovals);
+                }
+                return r;
+              }
+              return null;
+            }
+          });
+
+      dispatcher.get().postEvent(change, event, db);
+
+      if (!reviewerDeletedHook.isPresent()) {
+        return;
+      }
+
+      List<String> args = new ArrayList<>();
+      ChangeAttribute c = event.change.get();
+      AccountState owner = accountCache.get(change.getOwner());
+
+      addArg(args, "--change", c.id);
+      addArg(args, "--change-url", c.url);
+      addArg(args, "--change-owner", getDisplayName(owner.getAccount()));
+      addArg(args, "--project", c.project);
+      addArg(args, "--branch", c.branch);
+      addArg(args, "--reviewer", getDisplayName(account));
+      LabelTypes labelTypes = projectCache.get(
+          change.getProject()).getLabelTypes();
+      // append votes that were removed
+      for (Map.Entry<String, Short> approval : approvals.entrySet()) {
+        LabelType lt = labelTypes.byLabel(approval.getKey());
+        if (lt != null && approval.getValue() != null) {
+          addArg(args, "--" + lt.getName(), Short.toString(approval.getValue()));
+          if (oldApprovals != null && !oldApprovals.isEmpty()) {
+            Short oldValue = oldApprovals.get(approval.getKey());
+            if (oldValue != null) {
+              addArg(args, "--" + lt.getName() + "-oldValue",
+                  Short.toString(oldValue));
+            }
+          }
+        }
+      }
+      runHook(change.getProject(), reviewerDeletedHook, args);
+    }
+
+    @Override
     public void doTopicChangedHook(Change change, Account account,
         String oldTopic, ReviewDb db)
             throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
index ee1de65..9b02d18 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHooks.java
@@ -155,7 +155,23 @@
       PatchSet patchSet, ReviewDb db) throws OrmException;
 
   /**
-   * Fire the Topic Changed Hook.
+   * Fire the Reviewer Deleted Hook
+   *
+   * @param change The change itself.
+   * @param account The reviewer that was removed.
+   * @param patchSet The patchset that the reviewer was removed from.
+   * @param comment The comment given.
+   * @param approvals Map of label IDs to scores.
+   * @param oldApprovals Map of label IDs to old approval scores
+   * @param db The review database.
+   * @throws OrmException
+   */
+  void doReviewerDeletedHook(Change change, Account account, PatchSet patchSet,
+      String comment, Map<String, Short> approvals,
+      Map<String, Short> oldApprovals, ReviewDb db) throws OrmException;
+
+  /**
+   * Fire the Topic Changed Hook
    *
    * @param change The change itself.
    * @param account The gerrit user who changed the topic.
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
index bec0b7e..dbfa979 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/DisabledChangeHooks.java
@@ -91,6 +91,12 @@
   }
 
   @Override
+  public void doReviewerDeletedHook(Change change, Account account,
+      PatchSet patchSet, String comment, Map<String, Short> approvals,
+      Map<String, Short> oldApprovals, ReviewDb db) {
+  }
+
+  @Override
   public void doTopicChangedHook(Change change, Account account, String oldTopic,
       ReviewDb db) {
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
index f6df335a..068b70d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/RulesCache.java
@@ -18,7 +18,6 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -61,6 +60,7 @@
 import java.net.URLClassLoader;
 import java.nio.file.Files;
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.List;
@@ -283,7 +283,7 @@
         predicateProviders, cl)));
     ctl.setEnabled(EnumSet.allOf(Prolog.Feature.class), false);
 
-    List<String> packages = Lists.newArrayList();
+    List<String> packages = new ArrayList<>();
     packages.addAll(PACKAGE_LIST);
     for (PredicateProvider predicateProvider : predicateProviders) {
       packages.addAll(predicateProvider.getPackages());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
index 27f15e6..206e840 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValue.java
@@ -93,4 +93,4 @@
   protected T createValue(Prolog engine) {
     return null;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index bc301e3..9034e47 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.rules.StoredValue.create;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -45,6 +44,7 @@
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
+import java.util.HashMap;
 import java.util.Map;
 
 public final class StoredValues {
@@ -150,7 +150,7 @@
       new StoredValue<Map<Account.Id, IdentifiedUser>>() {
         @Override
         protected Map<Account.Id, IdentifiedUser> createValue(Prolog engine) {
-          return Maps.newHashMap();
+          return new HashMap<>();
         }
       };
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index dba723f..f4aa4c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -19,7 +19,6 @@
 
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.reviewdb.client.Account;
@@ -148,7 +147,7 @@
   private static TreeMap<Integer, PatchSet> getPatchSets(ChangeData cd)
       throws OrmException {
     Collection<PatchSet> patchSets = cd.patchSets();
-    TreeMap<Integer, PatchSet> result = Maps.newTreeMap();
+    TreeMap<Integer, PatchSet> result = new TreeMap<>();
     for (PatchSet ps : patchSets) {
       result.put(ps.getId().get(), ps);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
index b694cd8..11a3d81 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeUtil.java
@@ -16,7 +16,6 @@
 
 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;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.util.IdGenerator;
@@ -28,7 +27,6 @@
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.Map;
 
 @Singleton
@@ -77,15 +75,6 @@
     return u + '_' + l;
   }
 
-  public static void bumpRowVersionNotLastUpdatedOn(Change.Id id, ReviewDb db)
-      throws OrmException {
-    // Empty update of Change to bump rowVersion, changing its ETag.
-    Change c = db.changes().get(id);
-    if (c != null) {
-      db.changes().update(Collections.singleton(c));
-    }
-  }
-
   public static PatchSet.Id nextPatchSetId(Map<String, Ref> allRefs,
       PatchSet.Id id) {
     PatchSet.Id next = nextPatchSetId(id);
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 fa31ed5..d990115 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
@@ -23,7 +23,6 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.extensions.client.Side;
 import com.google.gerrit.extensions.common.CommentInfo;
@@ -52,7 +51,6 @@
 import org.eclipse.jgit.lib.NullProgressMonitor;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
@@ -62,8 +60,6 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
 
 /**
  * Utility functions to manipulate PatchLineComments.
@@ -156,7 +152,7 @@
     }
 
     notes.load();
-    List<PatchLineComment> comments = Lists.newArrayList();
+    List<PatchLineComment> comments = new ArrayList<>();
     comments.addAll(notes.getComments().values());
     return sort(comments);
   }
@@ -168,9 +164,9 @@
           db.patchComments().byChange(notes.getChangeId()), Status.DRAFT));
     }
 
-    List<PatchLineComment> comments = Lists.newArrayList();
-    for (String refSuffix : getDraftRefs(notes.getChangeId()).keySet()) {
-      Account.Id account = Account.Id.fromRefPart(refSuffix);
+    List<PatchLineComment> comments = new ArrayList<>();
+    for (Ref ref : getDraftRefs(notes.getChangeId())) {
+      Account.Id account = Account.Id.fromRefSuffix(ref.getName());
       if (account != null) {
         comments.addAll(draftByChangeAuthor(db, notes, account));
       }
@@ -196,11 +192,11 @@
     if (!migration.readChanges()) {
       return sort(db.patchComments().byPatchSet(psId).toList());
     }
-    List<PatchLineComment> comments = Lists.newArrayList();
+    List<PatchLineComment> comments = new ArrayList<>();
     comments.addAll(publishedByPatchSet(db, notes, psId));
 
-    for (String refSuffix : getDraftRefs(notes.getChangeId()).keySet()) {
-      Account.Id account = Account.Id.fromRefPart(refSuffix);
+    for (Ref ref : getDraftRefs(notes.getChangeId())) {
+      Account.Id account = Account.Id.fromRefSuffix(ref.getName());
       if (account != null) {
         comments.addAll(draftByPatchSetAuthor(db, psId, account, notes));
       }
@@ -266,7 +262,7 @@
             }
           }).toSortedList(PLC_ORDER);
     }
-    List<PatchLineComment> comments = Lists.newArrayList();
+    List<PatchLineComment> comments = new ArrayList<>();
     comments.addAll(notes.getDraftComments(author).values());
     return sort(comments);
   }
@@ -278,17 +274,24 @@
       return sort(db.patchComments().draftByAuthor(author).toList());
     }
 
-    Set<String> refNames =
-        getRefNamesAllUsers(RefNames.refsDraftCommentsPrefix(author));
-    List<PatchLineComment> comments = Lists.newArrayList();
-    for (String refName : refNames) {
-      Change.Id changeId = Change.Id.parse(refName);
-      // Avoid loading notes for all affected changes just to be able to auto-
-      // rebuild. This is only used in a corner case in the search codepath, so
-      // returning slightly stale values is ok.
-      DraftCommentNotes notes =
-          draftFactory.createWithAutoRebuildingDisabled(changeId, author);
-      comments.addAll(notes.load().getComments().values());
+    List<PatchLineComment> comments = new ArrayList<>();
+    try (Repository repo = repoManager.openRepository(allUsers)) {
+      for (String refName : repo.getRefDatabase()
+          .getRefs(RefNames.REFS_DRAFT_COMMENTS).keySet()) {
+        Account.Id accountId = Account.Id.fromRefSuffix(refName);
+        Change.Id changeId = Change.Id.fromRefPart(refName);
+        if (accountId == null || changeId == null) {
+          continue;
+        }
+        // Avoid loading notes for all affected changes just to be able to auto-
+        // rebuild. This is only used in a corner case in the search codepath,
+        // so returning slightly stale values is ok.
+        DraftCommentNotes notes =
+            draftFactory.createWithAutoRebuildingDisabled(changeId, author);
+        comments.addAll(notes.load().getComments().values());
+      }
+    } catch (IOException e) {
+      throw new OrmException(e);
     }
     return sort(comments);
   }
@@ -314,7 +317,7 @@
     try (Repository repo = repoManager.openRepository(allUsers);
         RevWalk rw = new RevWalk(repo)) {
       BatchRefUpdate bru = repo.getRefDatabase().newBatchUpdate();
-      for (Ref ref : getDraftRefs(repo, changeId).values()) {
+      for (Ref ref : getDraftRefs(repo, changeId)) {
         bru.addCommand(new ReceiveCommand(
             ref.getObjectId(), ObjectId.zeroId(), ref.getName()));
       }
@@ -372,16 +375,7 @@
     return c.getRevId();
   }
 
-  private Set<String> getRefNamesAllUsers(String prefix) throws OrmException {
-    try (Repository repo = repoManager.openRepository(allUsers)) {
-      RefDatabase refDb = repo.getRefDatabase();
-      return refDb.getRefs(prefix).keySet();
-    } catch (IOException e) {
-      throw new OrmException(e);
-    }
-  }
-
-  public Map<String, Ref> getDraftRefs(Change.Id changeId)
+  public Collection<Ref> getDraftRefs(Change.Id changeId)
       throws OrmException {
     try (Repository repo = repoManager.openRepository(allUsers)) {
       return getDraftRefs(repo, changeId);
@@ -390,17 +384,10 @@
     }
   }
 
-  private Map<String, Ref> getDraftRefs(Repository repo,
-      final Change.Id changeId) throws IOException {
-    final String suffix = "/" + changeId.get();
-    return Maps.filterKeys(
-        repo.getRefDatabase().getRefs(RefNames.REFS_DRAFT_COMMENTS),
-        new Predicate<String>() {
-          @Override
-          public boolean apply(String input) {
-            return input.endsWith(suffix);
-          }
-        });
+  private Collection<Ref> getDraftRefs(Repository repo, Change.Id changeId)
+      throws IOException {
+    return repo.getRefDatabase().getRefs(
+        RefNames.refsDraftCommentsPrefix(changeId)).values();
   }
 
   private static List<PatchLineComment> sort(List<PatchLineComment> comments) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
index e38f88c..af7affd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ReviewersUtil.java
@@ -129,7 +129,7 @@
       suggestedAccounts = suggestAccount(suggestReviewers, visibilityControl);
     }
 
-    List<SuggestedReviewerInfo> reviewer = Lists.newArrayList();
+    List<SuggestedReviewerInfo> reviewer = new ArrayList<>();
     for (AccountInfo a : suggestedAccounts) {
       SuggestedReviewerInfo info = new SuggestedReviewerInfo();
       info.account = a;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
index 9c9ee5c..aeff017 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/access/ListAccess.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.access;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
@@ -28,14 +26,16 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 public class ListAccess implements RestReadView<TopLevelResource> {
 
   @Option(name = "--project", aliases = {"-p"}, metaVar = "PROJECT",
       usage = "projects for which the access rights should be returned")
-  private List<String> projects = Lists.newArrayList();
+  private List<String> projects = new ArrayList<>();
 
   private final GetAccess getAccess;
 
@@ -47,7 +47,7 @@
   @Override
   public Map<String, ProjectAccessInfo> apply(TopLevelResource resource)
       throws ResourceNotFoundException, ResourceConflictException, IOException {
-    Map<String, ProjectAccessInfo> access = Maps.newTreeMap();
+    Map<String, ProjectAccessInfo> access = new TreeMap<>();
     for (String p : projects) {
       Project.NameKey projectName = new Project.NameKey(p);
       access.put(p, getAccess.apply(projectName));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
index 28df97a..57ffd0a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountByEmailCacheImpl.java
@@ -17,7 +17,6 @@
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -33,6 +32,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 
@@ -93,7 +93,7 @@
     @Override
     public Set<Account.Id> load(String email) throws Exception {
       try (ReviewDb db = schema.open()) {
-        Set<Account.Id> r = Sets.newHashSet();
+        Set<Account.Id> r = new HashSet<>();
         for (Account a : db.accounts().byPreferredEmail(email)) {
           r.add(a.getId());
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java
index 3e9b575..68e19e4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountLoader.java
@@ -18,8 +18,6 @@
 
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.server.account.AccountDirectory.DirectoryException;
@@ -28,9 +26,11 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -57,8 +57,8 @@
   AccountLoader(InternalAccountDirectory directory, @Assisted boolean detailed) {
     this.directory = directory;
     options = detailed ? DETAILED_OPTIONS : InternalAccountDirectory.ID_ONLY;
-    created = Maps.newHashMap();
-    provided = Lists.newArrayList();
+    created = new HashMap<>();
+    provided = new ArrayList<>();
   }
 
   public AccountInfo get(Account.Id id) {
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 558f8c0..04793c5 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,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -154,7 +153,7 @@
 
       // more than one match, try to return the best one
       String name = nameOrEmail.substring(0, lt - 1);
-      Set<Account.Id> nameMatches = Sets.newHashSet();
+      Set<Account.Id> nameMatches = new HashSet<>();
       for (Account.Id id : ids) {
         Account a = byId.get(id).getAccount();
         if (name.equals(a.getFullName())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
index bcdc071..3a511ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CreateAccount.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GlobalCapability;
@@ -51,6 +50,7 @@
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
@@ -214,7 +214,7 @@
 
   private Set<AccountGroup.Id> parseGroups(List<String> groups)
       throws UnprocessableEntityException {
-    Set<AccountGroup.Id> groupIds = Sets.newHashSet();
+    Set<AccountGroup.Id> groupIds = new HashSet<>();
     if (groups != null) {
       for (String g : groups) {
         groupIds.add(GroupDescriptions.toAccountGroup(
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
new file mode 100644
index 0000000..674cba6
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
@@ -0,0 +1,77 @@
+// Copyright (C) 2016 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.google.gerrit.server.account;
+
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.Response;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+@Singleton
+public class DeleteWatchedProjects
+    implements RestModifyView<AccountResource, List<String>> {
+
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<IdentifiedUser> self;
+
+  @Inject
+  DeleteWatchedProjects(Provider<ReviewDb> dbProvider,
+      Provider<IdentifiedUser> self) {
+    this.dbProvider = dbProvider;
+    this.self = self;
+  }
+
+  @Override
+  public Response<?> apply(
+      AccountResource rsrc, List<String> input)
+      throws UnprocessableEntityException, OrmException, AuthException {
+    if (self.get() != rsrc.getUser()) {
+      throw new AuthException("It is not allowed to edit project watches "
+          + "of other users");
+    }
+    ResultSet<AccountProjectWatch> watchedProjects =
+        dbProvider.get().accountProjectWatches()
+            .byAccount(rsrc.getUser().getAccountId());
+    HashMap<String, AccountProjectWatch> watchedProjectsMap = new HashMap<>();
+    for (AccountProjectWatch watchedProject : watchedProjects) {
+      watchedProjectsMap
+          .put(watchedProject.getProjectNameKey().get(), watchedProject);
+    }
+
+    if (input != null) {
+      List<AccountProjectWatch.Key> keysToDelete = new LinkedList<>();
+      for (String projectKeyToDelete : input) {
+        if (!watchedProjectsMap.containsKey(projectKeyToDelete))
+          throw new UnprocessableEntityException(projectKeyToDelete
+              + " is not currently watched by this user.");
+        keysToDelete.add(watchedProjectsMap.get(projectKeyToDelete).getKey());
+      }
+      dbProvider.get().accountProjectWatches().deleteKeys(keysToDelete);
+    }
+
+    return Response.none();
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
index d231767..cbd0e32 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetCapabilities.java
@@ -33,8 +33,6 @@
 import static com.google.gerrit.common.data.GlobalCapability.VIEW_QUEUE;
 
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.common.data.PermissionRange;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
@@ -54,7 +52,9 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.Map;
 import java.util.Set;
 
@@ -62,7 +62,7 @@
   @Option(name = "-q", metaVar = "CAP", usage = "Capability to inspect")
   void addQuery(String name) {
     if (query == null) {
-      query = Sets.newHashSet();
+      query = new HashSet<>();
     }
     Iterables.addAll(query, OptionUtil.splitOptionValue(name));
   }
@@ -86,7 +86,7 @@
     }
 
     CapabilityControl cc = resource.getUser().getCapabilities();
-    Map<String, Object> have = Maps.newLinkedHashMap();
+    Map<String, Object> have = new LinkedHashMap<>();
     for (String name : GlobalCapability.getAllNames()) {
       if (!name.equals(PRIORITY) && want(name) && cc.canPerform(name)) {
         if (GlobalCapability.hasRange(name)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
index cdc2044..02cfaa0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEditPreferences.java
@@ -74,4 +74,4 @@
           new EditPreferencesInfo(), EditPreferencesInfo.defaults(), in);
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
index 22ce4b2..14cc74e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetEmails.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.inject.Singleton;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -27,7 +27,7 @@
 
   @Override
   public List<EmailInfo> apply(AccountResource rsrc) {
-    List<EmailInfo> emails = Lists.newArrayList();
+    List<EmailInfo> emails = new ArrayList<>();
     for (String email : rsrc.getUser().getEmailAddresses()) {
       if (email != null) {
         EmailInfo e = new EmailInfo();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
index f777145..5b71e0b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetGroups.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.account;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -26,6 +25,7 @@
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @Singleton
@@ -43,7 +43,7 @@
   public List<GroupInfo> apply(AccountResource resource) throws OrmException {
     IdentifiedUser user = resource.getUser();
     Account.Id userId = user.getAccountId();
-    List<GroupInfo> groups = Lists.newArrayList();
+    List<GroupInfo> groups = new ArrayList<>();
     for (AccountGroup.UUID uuid : user.getEffectiveGroups().getKnownGroups()) {
       GroupControl ctl;
       try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
new file mode 100644
index 0000000..fd00b7a
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetWatchedProjects.java
@@ -0,0 +1,78 @@
+// 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.client.ProjectWatchInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.util.LinkedList;
+import java.util.List;
+
+@Singleton
+public class GetWatchedProjects implements RestReadView<AccountResource> {
+
+  private final Provider<ReviewDb> dbProvider;
+  private final Provider<IdentifiedUser> self;
+
+  @Inject
+  public GetWatchedProjects(Provider<ReviewDb> dbProvider,
+      Provider<IdentifiedUser> self) {
+    this.dbProvider = dbProvider;
+    this.self = self;
+  }
+
+  @Override
+  public List<ProjectWatchInfo> apply(AccountResource rsrc)
+      throws OrmException, AuthException {
+    if (self.get() != rsrc.getUser()) {
+      throw new AuthException("It is not allowed to list project watches "
+          + "of other users");
+    }
+    List<ProjectWatchInfo> projectWatchInfos = new LinkedList<>();
+    Iterable<AccountProjectWatch> projectWatches =
+        dbProvider.get().accountProjectWatches()
+            .byAccount(rsrc.getUser().getAccountId());
+    for (AccountProjectWatch a : projectWatches) {
+      ProjectWatchInfo pwi = new ProjectWatchInfo();
+      pwi.filter = a.getFilter();
+      pwi.project = a.getProjectNameKey().get();
+      pwi.notifyAbandonedChanges =
+          toBoolean(
+              a.isNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES));
+      pwi.notifyNewChanges =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_CHANGES));
+      pwi.notifyNewPatchSets =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS));
+      pwi.notifySubmittedChanges =
+          toBoolean(
+              a.isNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES));
+      pwi.notifyAllComments =
+          toBoolean(a.isNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS));
+      projectWatchInfos.add(pwi);
+    }
+    return projectWatchInfos;
+  }
+
+  private static Boolean toBoolean(boolean value) {
+    return value ? true : null;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
index 4b56b81..9bd6b30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupIncludeCacheImpl.java
@@ -17,7 +17,6 @@
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.AccountGroupById;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -33,6 +32,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -150,7 +150,7 @@
           return Collections.emptySet();
         }
 
-        Set<AccountGroup.UUID> ids = Sets.newHashSet();
+        Set<AccountGroup.UUID> ids = new HashSet<>();
         for (AccountGroupById agi : db.accountGroupById()
             .byGroup(group.get(0).getId())) {
           ids.add(agi.getIncludeUUID());
@@ -172,13 +172,13 @@
     @Override
     public Set<AccountGroup.UUID> load(AccountGroup.UUID key) throws Exception {
       try (ReviewDb db = schema.open()) {
-        Set<AccountGroup.Id> ids = Sets.newHashSet();
+        Set<AccountGroup.Id> ids = new HashSet<>();
         for (AccountGroupById agi : db.accountGroupById()
             .byIncludeUUID(key)) {
           ids.add(agi.getGroupId());
         }
 
-        Set<AccountGroup.UUID> groupArray = Sets.newHashSet();
+        Set<AccountGroup.UUID> groupArray = new HashSet<>();
         for (AccountGroup g : db.accountGroups().get(ids)) {
           groupArray.add(g.getGroupUUID());
         }
@@ -199,7 +199,7 @@
     @Override
     public Set<AccountGroup.UUID> load(String key) throws Exception {
       try (ReviewDb db = schema.open()) {
-        Set<AccountGroup.UUID> ids = Sets.newHashSet();
+        Set<AccountGroup.UUID> ids = new HashSet<>();
         for (AccountGroupById agi : db.accountGroupById().all()) {
           if (!AccountGroup.isInternalGroup(agi.getIncludeUUID())) {
             ids.add(agi.getIncludeUUID());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
index 8612d74..f9d5294 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GroupMembership.java
@@ -46,7 +46,7 @@
    * Implementors may implement the method as:
    *
    * <pre>
-   * Set&lt;AccountGroup.UUID&gt; r = Sets.newHashSet();
+   * Set&lt;AccountGroup.UUID&gt; r = new HashSet<>();
    * for (AccountGroup.UUID id : groupIds)
    *   if (contains(id)) r.add(id);
    * </pre>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
index b8a67ff..3eaeebe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/IncludingGroupMembership.java
@@ -22,6 +22,7 @@
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -102,7 +103,7 @@
 
   @Override
   public Set<AccountGroup.UUID> intersection(Iterable<AccountGroup.UUID> groupIds) {
-    Set<AccountGroup.UUID> r = Sets.newHashSet();
+    Set<AccountGroup.UUID> r = new HashSet<>();
     for (AccountGroup.UUID id : groupIds) {
       if (contains(id)) {
         r.add(id);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
index 54d4cc0..b11be39 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Module.java
@@ -56,6 +56,10 @@
     delete(ACCOUNT_KIND, "password.http").to(PutHttpPassword.class);
     child(ACCOUNT_KIND, "sshkeys").to(SshKeys.class);
     post(ACCOUNT_KIND, "sshkeys").to(AddSshKey.class);
+    get(ACCOUNT_KIND, "watched.projects").to(GetWatchedProjects.class);
+    post(ACCOUNT_KIND, "watched.projects").to(PostWatchedProjects.class);
+    post(ACCOUNT_KIND, "watched.projects:delete")
+        .to(DeleteWatchedProjects.class);
 
     get(SSH_KEY_KIND).to(GetSshKey.class);
     delete(SSH_KEY_KIND).to(DeleteSshKey.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/PostWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/PostWatchedProjects.java
new file mode 100644
index 0000000..cf67ad9
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/PostWatchedProjects.java
@@ -0,0 +1,102 @@
+// 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.client.ProjectWatchInfo;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.project.ProjectsCollection;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+@Singleton
+public class PostWatchedProjects
+    implements RestModifyView<AccountResource, List<ProjectWatchInfo>> {
+  private final Provider<IdentifiedUser> self;
+  private GetWatchedProjects getWatchedProjects;
+  private Provider<ReviewDb> dbProvider;
+  private ProjectsCollection projectsCollection;
+
+  @Inject
+  public PostWatchedProjects(GetWatchedProjects getWatchedProjects,
+      Provider<ReviewDb> dbProvider,
+      ProjectsCollection projectsCollection,
+      Provider<IdentifiedUser> self) {
+    this.getWatchedProjects = getWatchedProjects;
+    this.dbProvider = dbProvider;
+    this.projectsCollection = projectsCollection;
+    this.self = self;
+  }
+
+  @Override
+  public List<ProjectWatchInfo> apply(AccountResource rsrc,
+      List<ProjectWatchInfo> input)
+      throws OrmException, RestApiException, IOException {
+    if (self.get() != rsrc.getUser()) {
+      throw new AuthException("not allowed to edit project watches");
+    }
+    List<AccountProjectWatch> accountProjectWatchList =
+        getAccountProjectWatchList(input, rsrc.getUser().getAccountId());
+    dbProvider.get().accountProjectWatches().upsert(accountProjectWatchList);
+    return getWatchedProjects.apply(rsrc);
+  }
+
+  private List<AccountProjectWatch> getAccountProjectWatchList(
+      List<ProjectWatchInfo> input, Account.Id accountId)
+      throws UnprocessableEntityException, BadRequestException, IOException {
+    List<AccountProjectWatch> watchedProjects = new LinkedList<>();
+    for (ProjectWatchInfo a : input) {
+      if (a.project == null) {
+        throw new BadRequestException("project name must be specified");
+      }
+
+      Project.NameKey projectKey =
+          projectsCollection.parse(a.project).getNameKey();
+
+      AccountProjectWatch.Key key =
+          new AccountProjectWatch.Key(accountId, projectKey, a.filter);
+      AccountProjectWatch apw = new AccountProjectWatch(key);
+      apw.setNotify(AccountProjectWatch.NotifyType.ABANDONED_CHANGES,
+          toBoolean(a.notifyAbandonedChanges));
+      apw.setNotify(AccountProjectWatch.NotifyType.ALL_COMMENTS,
+          toBoolean(a.notifyAllComments));
+      apw.setNotify(AccountProjectWatch.NotifyType.NEW_CHANGES,
+          toBoolean(a.notifyNewChanges));
+      apw.setNotify(AccountProjectWatch.NotifyType.NEW_PATCHSETS,
+          toBoolean(a.notifyNewPatchSets));
+      apw.setNotify(AccountProjectWatch.NotifyType.SUBMITTED_CHANGES,
+          toBoolean(a.notifySubmittedChanges));
+      watchedProjects.add(apw);
+    }
+    return watchedProjects;
+  }
+
+  private boolean toBoolean(Boolean b) {
+    return b == null ? false : b;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
index 3450aff..3fccacce 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/UniversalGroupBackend.java
@@ -35,6 +35,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -180,7 +181,7 @@
         }
         lookups.put(m, uuid);
       }
-      Set<AccountGroup.UUID> groups = Sets.newHashSet();
+      Set<AccountGroup.UUID> groups = new HashSet<>();
       for (Map.Entry<GroupMembership, Collection<AccountGroup.UUID>> entry
           : lookups.asMap().entrySet()) {
         groups.addAll(entry.getKey().intersection(entry.getValue()));
@@ -190,7 +191,7 @@
 
     @Override
     public Set<AccountGroup.UUID> getKnownGroups() {
-      Set<AccountGroup.UUID> groups = Sets.newHashSet();
+      Set<AccountGroup.UUID> groups = new HashSet<>();
       for (GroupMembership m : memberships.values()) {
         groups.addAll(m.getKnownGroups());
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
index 709020f..5860391 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/accounts/AccountApiImpl.java
@@ -22,6 +22,7 @@
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.client.EditPreferencesInfo;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
+import com.google.gerrit.extensions.client.ProjectWatchInfo;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.GpgKeyInfo;
 import com.google.gerrit.extensions.common.SshKeyInfo;
@@ -39,10 +40,13 @@
 import com.google.gerrit.server.account.GetEditPreferences;
 import com.google.gerrit.server.account.GetPreferences;
 import com.google.gerrit.server.account.GetSshKeys;
+import com.google.gerrit.server.account.GetWatchedProjects;
+import com.google.gerrit.server.account.DeleteWatchedProjects;
 import com.google.gerrit.server.account.SetDiffPreferences;
 import com.google.gerrit.server.account.SetEditPreferences;
 import com.google.gerrit.server.account.SetPreferences;
 import com.google.gerrit.server.account.SshKeys;
+import com.google.gerrit.server.account.PostWatchedProjects;
 import com.google.gerrit.server.account.StarredChanges;
 import com.google.gerrit.server.change.ChangeResource;
 import com.google.gerrit.server.change.ChangesCollection;
@@ -71,6 +75,9 @@
   private final SetDiffPreferences setDiffPreferences;
   private final GetEditPreferences getEditPreferences;
   private final SetEditPreferences setEditPreferences;
+  private final GetWatchedProjects getWatchedProjects;
+  private final PostWatchedProjects postWatchedProjects;
+  private final DeleteWatchedProjects deleteWatchedProjects;
   private final StarredChanges.Create starredChangesCreate;
   private final StarredChanges.Delete starredChangesDelete;
   private final CreateEmail.Factory createEmailFactory;
@@ -90,6 +97,9 @@
       SetDiffPreferences setDiffPreferences,
       GetEditPreferences getEditPreferences,
       SetEditPreferences setEditPreferences,
+      GetWatchedProjects getWatchedProjects,
+      PostWatchedProjects postWatchedProjects,
+      DeleteWatchedProjects deleteWatchedProjects,
       StarredChanges.Create starredChangesCreate,
       StarredChanges.Delete starredChangesDelete,
       CreateEmail.Factory createEmailFactory,
@@ -109,6 +119,9 @@
     this.setDiffPreferences = setDiffPreferences;
     this.getEditPreferences = getEditPreferences;
     this.setEditPreferences = setEditPreferences;
+    this.getWatchedProjects = getWatchedProjects;
+    this.postWatchedProjects = postWatchedProjects;
+    this.deleteWatchedProjects = deleteWatchedProjects;
     this.starredChangesCreate = starredChangesCreate;
     this.starredChangesDelete = starredChangesDelete;
     this.createEmailFactory = createEmailFactory;
@@ -192,6 +205,35 @@
   }
 
   @Override
+  public List<ProjectWatchInfo> getWatchedProjects() throws RestApiException {
+    try {
+      return getWatchedProjects.apply(account);
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot get watched projects", e);
+    }
+  }
+
+  @Override
+  public List<ProjectWatchInfo> setWatchedProjects(
+      List<ProjectWatchInfo> in) throws RestApiException {
+    try {
+      return postWatchedProjects.apply(account, in);
+    } catch (OrmException | IOException e) {
+      throw new RestApiException("Cannot update watched projects", e);
+    }
+  }
+
+  @Override
+  public void deleteWatchedProjects(List<String> in)
+      throws RestApiException {
+    try {
+      deleteWatchedProjects.apply(account, in);
+    } catch (OrmException e) {
+      throw new RestApiException("Cannot delete watched projects", e);
+    }
+  }
+
+  @Override
   public void starChange(String id) throws RestApiException {
     try {
       ChangeResource rsrc = changes.parse(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
index 87c8af2..76a9fd6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/UniversalAuthBackend.java
@@ -16,11 +16,11 @@
 
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -38,8 +38,8 @@
 
   @Override
   public AuthUser authenticate(final AuthRequest request) throws AuthException {
-    List<AuthUser> authUsers = Lists.newArrayList();
-    List<AuthException> authExs = Lists.newArrayList();
+    List<AuthUser> authUsers = new ArrayList<>();
+    List<AuthException> authExs = new ArrayList<>();
     for (AuthBackend backend : authBackends) {
       try {
         authUsers.add(checkNotNull(backend.authenticate(request)));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
index 69d523b..96b437d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/cache/ForwardingRemovalListener.java
@@ -59,4 +59,4 @@
       l.onRemoval(pluginName, cacheName, notification);
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index d8fbd3b..288dd83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -132,7 +132,6 @@
       patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
       change.setStatus(Change.Status.ABANDONED);
       change.setLastUpdatedOn(ctx.getWhen());
-      ctx.saveChange();
 
       update.setStatus(change.getStatus());
       message = newMessage(ctx);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 6af3e79..44e55c8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -332,7 +332,6 @@
     }
     patchSet = psUtil.insert(ctx.getDb(), ctx.getRevWalk(), update, psId,
         commit, draft, newGroups, null);
-    ctx.saveChange();
 
     /* TODO: fixStatus is used here because the tests
      * (byStatusClosed() in AbstractQueryChangesTest)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index bcbcff8..9e2d5f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -125,6 +125,8 @@
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -280,7 +282,7 @@
         }));
 
     List<List<ChangeInfo>> res = Lists.newArrayListWithCapacity(in.size());
-    Map<Change.Id, ChangeInfo> out = Maps.newHashMap();
+    Map<Change.Id, ChangeInfo> out = new HashMap<>();
     for (QueryResult r : in) {
       List<ChangeInfo> infos = toChangeInfo(out, r.changes());
       if (!infos.isEmpty() && r.moreChanges()) {
@@ -610,7 +612,7 @@
     // Include a user in the output for this label if either:
     //  - They are an explicit reviewer.
     //  - They ever voted on this change.
-    Set<Account.Id> allUsers = Sets.newHashSet();
+    Set<Account.Id> allUsers = new HashSet<>();
     allUsers.addAll(cd.reviewers().values());
     for (PatchSetApproval psa : cd.approvals().values()) {
       allUsers.add(psa.getAccountId());
@@ -667,7 +669,7 @@
   private Map<String, LabelWithStatus> labelsForClosedChange(ChangeData cd,
       LabelTypes labelTypes, boolean standard, boolean detailed)
       throws OrmException {
-    Set<Account.Id> allUsers = Sets.newHashSet();
+    Set<Account.Id> allUsers = new HashSet<>();
     if (detailed) {
       // Users expect to see all reviewers on closed changes, even if they
       // didn't vote on the latest patch set. If we don't need detailed labels,
@@ -680,7 +682,7 @@
 
     // We can only approximately reconstruct what the submit rule evaluator
     // would have done. These should really come from a stored submit record.
-    Set<String> labelNames = Sets.newHashSet();
+    Set<String> labelNames = new HashSet<>();
     Multimap<Account.Id, PatchSetApproval> current = HashMultimap.create();
     for (PatchSetApproval a : cd.currentApprovals()) {
       allUsers.add(a.getAccountId());
@@ -755,7 +757,7 @@
 
   private void setLabelValues(LabelType type, LabelWithStatus l) {
     l.label().defaultValue = type.getDefaultValue();
-    l.label().values = Maps.newLinkedHashMap();
+    l.label().values = new LinkedHashMap<>();
     for (LabelValue v : type.getValues()) {
       l.label().values.put(v.formatValue(), v.getText());
     }
@@ -871,7 +873,7 @@
   private Map<String, RevisionInfo> revisions(ChangeControl ctl,
       Map<PatchSet.Id, PatchSet> map) throws PatchListNotAvailableException,
       GpgException, OrmException, IOException {
-    Map<String, RevisionInfo> res = Maps.newLinkedHashMap();
+    Map<String, RevisionInfo> res = new LinkedHashMap<>();
     for (PatchSet in : map.values()) {
       if ((has(ALL_REVISIONS)
           || in.getId().equals(ctl.getChange().currentPatchSetId()))
@@ -1003,7 +1005,7 @@
 
   private Map<String, FetchInfo> makeFetchMap(ChangeControl ctl, PatchSet in)
       throws OrmException {
-    Map<String, FetchInfo> r = Maps.newLinkedHashMap();
+    Map<String, FetchInfo> r = new LinkedHashMap<>();
 
     for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
       String schemeName = e.getExportName();
@@ -1049,7 +1051,7 @@
   private static void addCommand(FetchInfo fetchInfo, String commandName,
       String c) {
     if (fetchInfo.commands == null) {
-      fetchInfo.commands = Maps.newTreeMap();
+      fetchInfo.commands = new TreeMap<>();
     }
     fetchInfo.commands.put(commandName, c);
   }
@@ -1063,7 +1065,7 @@
 
   private static void addApproval(LabelInfo label, ApprovalInfo approval) {
     if (label.all == null) {
-      label.all = Lists.newArrayList();
+      label.all = new ArrayList<>();
     }
     label.all.add(approval);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 513a34f..cefbf8a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -286,7 +286,6 @@
       changeMessage.setMessage(sb.toString());
 
       cmUtil.addChangeMessage(ctx.getDb(), ctx.getUpdate(psId), changeMessage);
-      ctx.saveChange(); // Bump lastUpdatedOn to match message.
       return true;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
index b991303..bfbc828 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateDraftComment.java
@@ -127,6 +127,7 @@
           comment, patchListCache, ctx.getChange(), ps);
       plcUtil.putComments(
           ctx.getDb(), ctx.getUpdate(psId), Collections.singleton(comment));
+      ctx.bumpLastUpdatedOn(false);
       return true;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
index 2edff4b..c9f5aa3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteDraftPatchSet.java
@@ -160,7 +160,6 @@
       if (c.currentPatchSetId().equals(psId)) {
         c.setCurrentPatchSet(previousPatchSetInfo(ctx));
       }
-      ctx.saveChange();
     }
 
     private boolean deletedOnlyPatchSet() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index dc3b26b..ed0a6b8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -17,55 +17,84 @@
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.TimeUtil;
+import com.google.gerrit.common.data.LabelType;
+import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
 import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
+import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.PatchSetUtil;
 import com.google.gerrit.server.change.DeleteReviewer.Input;
 import com.google.gerrit.server.git.BatchUpdate;
 import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.mail.DeleteReviewerSender;
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 
 @Singleton
 public class DeleteReviewer implements RestModifyView<ReviewerResource, Input> {
+  private static final Logger log = LoggerFactory
+      .getLogger(DeleteReviewer.class);
+
   public static class Input {
   }
 
   private final Provider<ReviewDb> dbProvider;
   private final ApprovalsUtil approvalsUtil;
+  private final PatchSetUtil psUtil;
   private final ChangeMessagesUtil cmUtil;
   private final BatchUpdate.Factory batchUpdateFactory;
   private final IdentifiedUser.GenericFactory userFactory;
+  private final ChangeHooks hooks;
+  private final Provider<IdentifiedUser> user;
+  private final DeleteReviewerSender.Factory deleteReviewerSenderFactory;
 
   @Inject
   DeleteReviewer(Provider<ReviewDb> dbProvider,
       ApprovalsUtil approvalsUtil,
+      PatchSetUtil psUtil,
       ChangeMessagesUtil cmUtil,
       BatchUpdate.Factory batchUpdateFactory,
-      IdentifiedUser.GenericFactory userFactory) {
+      IdentifiedUser.GenericFactory userFactory,
+      ChangeHooks hooks,
+      Provider<IdentifiedUser> user,
+      DeleteReviewerSender.Factory deleteReviewerSenderFactory) {
     this.dbProvider = dbProvider;
     this.approvalsUtil = approvalsUtil;
+    this.psUtil = psUtil;
     this.cmUtil = cmUtil;
     this.batchUpdateFactory = batchUpdateFactory;
     this.userFactory = userFactory;
+    this.hooks = hooks;
+    this.user = user;
+    this.deleteReviewerSenderFactory = deleteReviewerSenderFactory;
   }
 
   @Override
@@ -74,7 +103,7 @@
     try (BatchUpdate bu = batchUpdateFactory.create(dbProvider.get(),
         rsrc.getChangeResource().getProject(),
         rsrc.getChangeResource().getUser(), TimeUtil.nowTs())) {
-      Op op = new Op(rsrc.getReviewerUser().getAccountId());
+      Op op = new Op(rsrc.getReviewerUser().getAccount());
       bu.addOp(rsrc.getChange().getId(), op);
       bu.execute();
     }
@@ -83,29 +112,44 @@
   }
 
   private class Op extends BatchUpdate.Op {
-    private final Account.Id reviewerId;
+    private final Account reviewer;
+    ChangeMessage changeMessage;
+    Change currChange;
+    PatchSet currPs;
+    List<PatchSetApproval> del = new ArrayList<>();
+    Map<String, Short> newApprovals = new HashMap<>();
+    Map<String, Short> oldApprovals = new HashMap<>();
 
-    Op(Account.Id reviewerId) {
-      this.reviewerId = reviewerId;
+    Op(Account reviewerAccount) {
+      this.reviewer = reviewerAccount;
     }
 
     @Override
     public boolean updateChange(ChangeContext ctx)
         throws AuthException, ResourceNotFoundException, OrmException {
-      PatchSet.Id currPs = ctx.getChange().currentPatchSetId();
+      Account.Id reviewerId = reviewer.getId();
+      currChange = ctx.getChange();
+      currPs = psUtil.current(dbProvider.get(), ctx.getNotes());
+
+      LabelTypes labelTypes = ctx.getControl().getLabelTypes();
+      // removing a reviewer will remove all her votes
+      for (LabelType lt : labelTypes.getLabelTypes()) {
+        newApprovals.put(lt.getName(), (short) 0);
+      }
+
       StringBuilder msg = new StringBuilder();
-      List<PatchSetApproval> del = Lists.newArrayList();
       for (PatchSetApproval a : approvals(ctx, reviewerId)) {
         if (ctx.getControl().canRemoveReviewer(a)) {
           del.add(a);
-          if (a.getPatchSetId().equals(currPs)
-              && a.getValue() != 0) {
+          if (a.getPatchSetId().equals(currPs.getId()) && a.getValue() != 0) {
+            oldApprovals.put(a.getLabel(), a.getValue());
             if (msg.length() == 0) {
-              msg.append("Removed the following votes:\n\n");
+              msg.append("Removed reviewer ").append(reviewer.getFullName())
+                  .append(" with the following votes:\n\n");
             }
-            msg.append("* ")
-                .append(a.getLabel()).append(formatLabelValue(a.getValue()))
-                .append(" by ").append(userFactory.create(a.getAccountId()).getNameEmail())
+            msg.append("* ").append(a.getLabel())
+                .append(formatLabelValue(a.getValue())).append(" by ")
+                .append(userFactory.create(a.getAccountId()).getNameEmail())
                 .append("\n");
           }
         } else {
@@ -116,21 +160,37 @@
         throw new ResourceNotFoundException();
       }
       ctx.getDb().patchSetApprovals().delete(del);
-      ChangeUpdate update = ctx.getUpdate(currPs);
+      ChangeUpdate update = ctx.getUpdate(currPs.getId());
       update.removeReviewer(reviewerId);
 
       if (msg.length() > 0) {
-        ChangeMessage changeMessage =
-            new ChangeMessage(new ChangeMessage.Key(ctx.getChange().getId(),
+        changeMessage = new ChangeMessage(
+            new ChangeMessage.Key(currChange.getId(),
                 ChangeUtil.messageUUID(ctx.getDb())),
-                ctx.getUser().getAccountId(),
-                ctx.getWhen(), currPs);
+            ctx.getUser().getAccountId(), ctx.getWhen(), currPs.getId());
         changeMessage.setMessage(msg.toString());
         cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
       }
+
       return true;
     }
 
+    @Override
+    public void postUpdate(Context ctx) {
+      if (changeMessage == null) {
+        return;
+      }
+
+      emailReviewers(ctx.getProject(), currChange, del, changeMessage);
+      try {
+        hooks.doReviewerDeletedHook(currChange, reviewer, currPs,
+            changeMessage.getMessage(), newApprovals, oldApprovals,
+            dbProvider.get());
+      } catch (OrmException e) {
+        log.warn("ChangeHook.doCommentAddedHook delivery failed", e);
+      }
+    }
+
     private Iterable<PatchSetApproval> approvals(ChangeContext ctx,
         final Account.Id accountId) throws OrmException {
       return Iterables.filter(
@@ -151,4 +211,29 @@
       }
     }
   }
+
+  private void emailReviewers(Project.NameKey projectName, Change change,
+      List<PatchSetApproval> dels, ChangeMessage changeMessage) {
+
+    // The user knows they removed themselves, don't bother emailing them.
+    List<Account.Id> toMail = Lists.newArrayListWithCapacity(dels.size());
+    Account.Id userId = user.get().getAccountId();
+    for (PatchSetApproval psa : dels) {
+      if (!psa.getAccountId().equals(userId)) {
+        toMail.add(psa.getAccountId());
+      }
+    }
+    if (!toMail.isEmpty()) {
+      try {
+        DeleteReviewerSender cm =
+            deleteReviewerSenderFactory.create(projectName, change.getId());
+        cm.setFrom(userId);
+        cm.addReviewers(toMail);
+        cm.setChangeMessage(changeMessage);
+        cm.send();
+      } catch (Exception err) {
+        log.error("Cannot email update for change " + change.getId(), err);
+      }
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
index 0ded3a2..8f597ac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteVote.java
@@ -168,7 +168,6 @@
       if (psa == null) {
         throw new ResourceNotFoundException();
       }
-      ctx.saveChange();
       ctx.getDb().patchSetApprovals().update(Collections.singleton(psa));
 
       if (msg.length() > 0) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
index e31b11f..45794fb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/FileInfoJson.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.client.DiffPreferencesInfo.Whitespace;
 import com.google.gerrit.extensions.common.FileInfo;
@@ -33,6 +32,7 @@
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.util.Map;
+import java.util.TreeMap;
 
 @Singleton
 public class FileInfoJson {
@@ -57,7 +57,7 @@
     PatchList list = patchListCache.get(
         new PatchListKey(a, b, Whitespace.IGNORE_NONE), change.getProject());
 
-    Map<String, FileInfo> files = Maps.newTreeMap();
+    Map<String, FileInfo> files = new TreeMap<>();
     for (PatchListEntry e : list.getPatches()) {
       FileInfo d = new FileInfo();
       d.status = e.getChangeType() != Patch.ChangeType.MODIFIED
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index 79025b4..c504da0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -231,7 +231,7 @@
 
     private List<String> scan(Account.Id userId, PatchSet.Id psId)
         throws OrmException {
-      List<String> r = Lists.newArrayList();
+      List<String> r = new ArrayList<>();
       for (AccountPatchReview w : db.get().accountPatchReviews()
           .byReviewer(userId, psId)) {
         r.add(w.getKey().getPatchKey().getFileName());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
index 2c5eda4..56857b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/IncludedInResolver.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
 import org.eclipse.jgit.errors.MissingObjectException;
@@ -35,6 +34,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Set;
 
@@ -107,8 +108,8 @@
 
   private boolean includedInOne(final Collection<Ref> refs) throws IOException {
     parseCommits(refs);
-    List<RevCommit> before = Lists.newLinkedList();
-    List<RevCommit> after = Lists.newLinkedList();
+    List<RevCommit> before = new LinkedList<>();
+    List<RevCommit> after = new LinkedList<>();
     partition(before, after);
     // It is highly likely that the target is reachable from the "after" set
     // Within the "before" set we are trying to handle cases arising from clock skew
@@ -120,7 +121,7 @@
    */
   private Set<String> includedIn(final Collection<RevCommit> tips, int limit)
       throws IOException, MissingObjectException, IncorrectObjectTypeException {
-    Set<String> result = Sets.newHashSet();
+    Set<String> result = new HashSet<>();
     for (RevCommit tip : tips) {
       boolean commitFound = false;
       rw.resetRetain(RevFlag.UNINTERESTING, containsTarget);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
index ca5a55b..1e3480f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ListReviewers.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.change;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -25,6 +24,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -48,7 +48,7 @@
 
   @Override
   public List<ReviewerInfo> apply(ChangeResource rsrc) throws OrmException {
-    Map<Account.Id, ReviewerResource> reviewers = Maps.newLinkedHashMap();
+    Map<Account.Id, ReviewerResource> reviewers = new LinkedHashMap<>();
     ReviewDb db = dbProvider.get();
     for (Account.Id accountId
         : approvalsUtil.getReviewers(db, rsrc.getNotes()).values()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
index 23d29de5..842e8bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Mergeable.java
@@ -24,7 +24,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.git.BranchOrderSection;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.MergeUtil;
@@ -33,6 +32,7 @@
 import com.google.gerrit.server.project.ProjectState;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -47,6 +47,7 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Map;
 import java.util.Objects;
 
@@ -175,10 +176,23 @@
     final boolean mergeable =
         cache.get(commit, ref, type, strategy, change.getDest(), git);
     if (!Objects.equals(mergeable, old)) {
-      // TODO(dborowitz): Include cache info in ETag somehow instead.
-      ChangeUtil.bumpRowVersionNotLastUpdatedOn(change.getId(), db.get());
+      invalidateETag(change.getId(), db.get());
       indexer.index(db.get(), change);
     }
     return mergeable;
   }
+
+  private static void invalidateETag(Change.Id id, ReviewDb db)
+      throws OrmException {
+    // Empty update of Change to bump rowVersion, changing its ETag.
+    // TODO(dborowitz): Include cache info in ETag somehow instead.
+    if (db instanceof DisabledChangesReviewDbWrapper) {
+      db = ((DisabledChangesReviewDbWrapper) db).unsafeGetDelegate();
+    }
+    Change c = db.changes().get(id);
+    if (c != null) {
+      db.changes().update(Collections.singleton(c));
+    }
+  }
+
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
index db5cc47..2139ec4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Move.java
@@ -186,7 +186,6 @@
       cmsg.setMessage(msgBuf.toString());
       cmUtil.addChangeMessage(ctx.getDb(), update, cmsg);
 
-      ctx.saveChange();
       return true;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index a70716a..3defdd7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -246,7 +246,6 @@
       change.setStatus(Change.Status.NEW);
     }
     change.setCurrentPatchSet(patchSetInfo);
-    ctx.saveChange();
     if (copyApprovals) {
       approvalCopier.copy(db, ctl, patchSet);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index 961b0bf..e78fd9e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -24,8 +24,6 @@
 import com.google.common.base.MoreObjects;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
 import com.google.common.hash.HashCode;
@@ -370,9 +368,6 @@
       dirty |= insertComments(ctx);
       dirty |= updateLabels(ctx);
       dirty |= insertMessage(ctx);
-      if (dirty) {
-        ctx.saveChange();
-      }
       return dirty;
     }
 
@@ -413,8 +408,8 @@
         }
       }
 
-      List<PatchLineComment> del = Lists.newArrayList();
-      List<PatchLineComment> ups = Lists.newArrayList();
+      List<PatchLineComment> del = new ArrayList<>();
+      List<PatchLineComment> ups = new ArrayList<>();
 
       Set<CommentSetEntry> existingIds = in.omitDuplicateComments
           ? readExistingComments(ctx)
@@ -493,7 +488,7 @@
 
     private Map<String, PatchLineComment> changeDrafts(ChangeContext ctx)
         throws OrmException {
-      Map<String, PatchLineComment> drafts = Maps.newHashMap();
+      Map<String, PatchLineComment> drafts = new HashMap<>();
       for (PatchLineComment c : plcUtil.draftByChangeAuthor(
           ctx.getDb(), ctx.getNotes(), user.getAccountId())) {
         c.setTag(in.tag);
@@ -504,7 +499,7 @@
 
     private Map<String, PatchLineComment> patchSetDrafts(ChangeContext ctx)
         throws OrmException {
-      Map<String, PatchLineComment> drafts = Maps.newHashMap();
+      Map<String, PatchLineComment> drafts = new HashMap<>();
       for (PatchLineComment c : plcUtil.draftByPatchSetAuthor(ctx.getDb(),
           psId, user.getAccountId(), ctx.getNotes())) {
         drafts.put(c.getKey().get(), c);
@@ -591,8 +586,8 @@
         return false;
       }
 
-      List<PatchSetApproval> del = Lists.newArrayList();
-      List<PatchSetApproval> ups = Lists.newArrayList();
+      List<PatchSetApproval> del = new ArrayList<>();
+      List<PatchSetApproval> ups = new ArrayList<>();
       Map<String, PatchSetApproval> current = scanLabels(ctx, del);
       LabelTypes labelTypes = ctx.getControl().getLabelTypes();
       Map<String, Short> allApprovals = getAllApprovals(labelTypes,
@@ -693,7 +688,7 @@
     private Map<String, PatchSetApproval> scanLabels(ChangeContext ctx,
         List<PatchSetApproval> del) throws OrmException {
       LabelTypes labelTypes = ctx.getControl().getLabelTypes();
-      Map<String, PatchSetApproval> current = Maps.newHashMap();
+      Map<String, PatchSetApproval> current = new HashMap<>();
 
       for (PatchSetApproval a : approvalsUtil.byPatchSetUser(
           ctx.getDb(), ctx.getControl(), psId, user.getAccountId())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
index d6e80f4..7cb410e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReviewers.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.common.data.GroupDescription;
@@ -63,6 +62,7 @@
 
 import java.io.IOException;
 import java.text.MessageFormat;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -170,7 +170,7 @@
       return result;
     }
 
-    Map<Account.Id, ChangeControl> reviewers = Maps.newHashMap();
+    Map<Account.Id, ChangeControl> reviewers = new HashMap<>();
     ChangeControl control = rsrc.getControl();
     Set<Account> members;
     try {
@@ -275,10 +275,11 @@
               rsrc.getChange(),
               reviewers.keySet());
 
-      if (!added.isEmpty()) {
-        patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
+      if (added.isEmpty()) {
+        return false;
       }
-      return !added.isEmpty();
+      patchSet = psUtil.current(dbProvider.get(), rsrc.getNotes());
+      return true;
     }
 
     @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
index 74cbd5f..ff5185e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PublishDraftPatchSet.java
@@ -192,7 +192,6 @@
       if (wasDraftChange) {
         change.setStatus(Change.Status.NEW);
         update.setStatus(change.getStatus());
-        ctx.saveChange();
       }
     }
 
@@ -202,10 +201,6 @@
         throw new ResourceConflictException("Patch set is not a draft");
       }
       psUtil.publish(ctx.getDb(), ctx.getUpdate(psId), patchSet);
-      // Force ETag invalidation if not done already
-      if (!wasDraftChange) {
-        ctx.saveChange();
-      }
     }
 
     private void addReviewers(ChangeContext ctx)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
index 082798a..1d756f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutDraftComment.java
@@ -155,6 +155,7 @@
         plcUtil.putComments(ctx.getDb(), update,
             Collections.singleton(update(comment, in, ctx.getWhen())));
       }
+      ctx.bumpLastUpdatedOn(false);
       return true;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 61718ae..f0db81d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -114,7 +114,6 @@
       }
       change.setTopic(Strings.emptyToNull(newTopicName));
       update.setTopic(change.getTopic());
-      ctx.saveChange();
 
       ChangeMessage cmsg = new ChangeMessage(
           new ChangeMessage.Key(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 8696ed6..b03194e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -117,7 +117,6 @@
       patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
       change.setStatus(Status.NEW);
       change.setLastUpdatedOn(ctx.getWhen());
-      ctx.saveChange();
       update.setStatus(change.getStatus());
 
       message = newMessage(ctx);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
index c8db0ed..a390505 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revisions.java
@@ -35,6 +35,7 @@
 import com.google.inject.Singleton;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -113,7 +114,7 @@
       // Impossibly long identifier will never match.
       return Collections.emptyList();
     } else {
-      List<RevisionResource> out = Lists.newArrayList();
+      List<RevisionResource> out = new ArrayList<>();
       for (PatchSet ps : psUtil.byChange(dbProvider.get(), change.getNotes())) {
         if (ps.getRevision() != null && ps.getRevision().get().startsWith(id)) {
           out.add(new RevisionResource(change, ps));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
index 46fbe67..8cbdf14 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SuggestChangeReviewers.java
@@ -73,4 +73,4 @@
       };
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
index 3eecea3..45d9669 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/TestSubmitRule.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.MoreObjects;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.TestSubmitRuleInput;
@@ -34,6 +33,7 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -116,31 +116,31 @@
       switch (n.status) {
         case OK:
           if (ok == null) {
-            ok = Maps.newLinkedHashMap();
+            ok = new LinkedHashMap<>();
           }
           ok.put(n.label, who);
           break;
         case REJECT:
           if (reject == null) {
-            reject = Maps.newLinkedHashMap();
+            reject = new LinkedHashMap<>();
           }
           reject.put(n.label, who);
           break;
         case NEED:
           if (need == null) {
-            need = Maps.newLinkedHashMap();
+            need = new LinkedHashMap<>();
           }
           need.put(n.label, new None());
           break;
         case MAY:
           if (may == null) {
-            may = Maps.newLinkedHashMap();
+            may = new LinkedHashMap<>();
           }
           may.put(n.label, who);
           break;
         case IMPOSSIBLE:
           if (impossible == null) {
-            impossible = Maps.newLinkedHashMap();
+            impossible = new LinkedHashMap<>();
           }
           impossible.put(n.label, new None());
           break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 1d2fc09..b5b3b6a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -109,6 +109,7 @@
 import com.google.gerrit.server.mail.AddKeySender;
 import com.google.gerrit.server.mail.AddReviewerSender;
 import com.google.gerrit.server.mail.CreateChangeSender;
+import com.google.gerrit.server.mail.DeleteReviewerSender;
 import com.google.gerrit.server.mail.EmailModule;
 import com.google.gerrit.server.mail.FromAddressGenerator;
 import com.google.gerrit.server.mail.FromAddressGeneratorProvider;
@@ -205,6 +206,7 @@
 
     factory(AccountInfoCacheFactory.Factory.class);
     factory(AddReviewerSender.Factory.class);
+    factory(DeleteReviewerSender.Factory.class);
     factory(AddKeySender.Factory.class);
     factory(BatchUpdate.Factory.class);
     factory(CapabilityControl.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
index c6ba605..3a87239 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListCapabilities.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.config;
 
 import com.google.common.base.CharMatcher;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.config.CapabilityDefinition;
 import com.google.gerrit.extensions.registration.DynamicMap;
@@ -28,6 +27,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.util.Map;
+import java.util.TreeMap;
 
 /** List capabilities visible to the calling user. */
 @Singleton
@@ -43,7 +43,7 @@
   @Override
   public Map<String, CapabilityInfo> apply(ConfigResource resource)
       throws IllegalAccessException, NoSuchFieldException {
-    Map<String, CapabilityInfo> output = Maps.newTreeMap();
+    Map<String, CapabilityInfo> output = new TreeMap<>();
     collectCoreCapabilities(output);
     collectPluginCapabilities(output);
     return output;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
index 291e48b..c7d10a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ListTopMenus.java
@@ -14,13 +14,13 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.extensions.webui.TopMenu;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
+import java.util.ArrayList;
 import java.util.List;
 
 @Singleton
@@ -34,7 +34,7 @@
 
   @Override
   public List<TopMenu.MenuEntry> apply(ConfigResource resource) {
-    List<TopMenu.MenuEntry> entries = Lists.newArrayList();
+    List<TopMenu.MenuEntry> entries = new ArrayList<>();
     for (TopMenu extension : extensions) {
       entries.addAll(extension.getEntries());
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
index a2aa937..58a6c60 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/PluginConfigFactory.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.ProjectLevelConfig;
 import com.google.gerrit.server.plugins.Plugin;
@@ -36,6 +35,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.HashMap;
 import java.util.Map;
 
 @Singleton
@@ -60,7 +60,7 @@
     this.cfgProvider = cfgProvider;
     this.projectCache = projectCache;
     this.projectStateFactory = projectStateFactory;
-    this.pluginConfigs = Maps.newHashMap();
+    this.pluginConfigs = new HashMap<>();
 
     this.cfgSnapshot = FileSnapshot.save(site.gerrit_config.toFile());
     this.cfg = cfgProvider.get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
index 738d309..be9e1b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditJson.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.edit;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.common.CommitInfo;
 import com.google.gerrit.extensions.common.EditInfo;
 import com.google.gerrit.extensions.common.FetchInfo;
@@ -31,6 +30,7 @@
 import org.eclipse.jgit.revwalk.RevCommit;
 
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.Map;
 
 @Singleton
@@ -78,7 +78,7 @@
   }
 
   private Map<String, FetchInfo> fillFetchMap(ChangeEdit edit) {
-    Map<String, FetchInfo> r = Maps.newLinkedHashMap();
+    Map<String, FetchInfo> r = new LinkedHashMap<>();
     for (DynamicMap.Entry<DownloadScheme> e : downloadSchemes) {
       String schemeName = e.getExportName();
       DownloadScheme scheme = e.getProvider().get();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
index 0cd80f2..b4c4d2b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditModifier.java
@@ -46,6 +46,7 @@
 import org.eclipse.jgit.dircache.DirCacheEditor.DeletePath;
 import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.InvalidObjectIdException;
 import org.eclipse.jgit.lib.BatchRefUpdate;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.FileMode;
@@ -68,6 +69,7 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.sql.Timestamp;
+import java.util.concurrent.atomic.AtomicReference;
 import java.util.Map;
 import java.util.TimeZone;
 
@@ -334,14 +336,15 @@
         ObjectReader reader = repo.newObjectReader()) {
       String refName = edit.getRefName();
       RevCommit prevEdit = edit.getEditCommit();
-      ObjectId newTree = writeNewTree(op,
+      ObjectId newTree = writeNewTree(
+          op,
           rw,
           inserter,
           prevEdit,
           reader,
           file,
           newFile,
-          toBlob(inserter, content));
+          content);
       if (ObjectId.equals(newTree, prevEdit.getTree())) {
         throw new InvalidChangeOperationException("no changes were made");
       }
@@ -402,10 +405,16 @@
     return res;
   }
 
-  private static ObjectId writeNewTree(TreeOperation op, RevWalk rw,
-      ObjectInserter ins, RevCommit prevEdit, ObjectReader reader,
-      String fileName, @Nullable String newFile,
-      @Nullable final ObjectId content) throws IOException {
+  private static ObjectId writeNewTree(
+      TreeOperation op,
+      RevWalk rw,
+      final ObjectInserter ins,
+      RevCommit prevEdit,
+      ObjectReader reader,
+      String fileName,
+      @Nullable String newFile,
+      @Nullable final RawInput content)
+      throws InvalidChangeOperationException, IOException {
     DirCache newTree = readTree(reader, prevEdit);
     DirCacheEditor dce = newTree.editor();
     switch (op) {
@@ -425,15 +434,41 @@
 
       case CHANGE_ENTRY:
         checkNotNull(content, "new content required");
+
+        final AtomicReference<IOException> ioe =
+            new AtomicReference<>(null);
+        final AtomicReference<InvalidChangeOperationException> icoe =
+            new AtomicReference<>(null);
         dce.add(new PathEdit(fileName) {
           @Override
           public void apply(DirCacheEntry ent) {
-            if (ent.getRawMode() == 0) {
-              ent.setFileMode(FileMode.REGULAR_FILE);
+            try {
+              if (ent.getFileMode() == FileMode.GITLINK) {
+                ent.setLength(0);
+                ent.setLastModified(0);
+                ent.setObjectId(ObjectId.fromString(
+                    ByteStreams.toByteArray(content.getInputStream()), 0));
+              } else {
+                if (ent.getRawMode() == 0) {
+                  ent.setFileMode(FileMode.REGULAR_FILE);
+                }
+                ent.setObjectId(toBlob(ins, content));
+              }
+            } catch (IOException e) {
+              ioe.set(e);
+            } catch (InvalidObjectIdException e) {
+              icoe.set(new InvalidChangeOperationException(
+                  "Invalid object id in submodule link: " + e.getMessage()));
+              icoe.get().initCause(e);
             }
-            ent.setObjectId(content);
           }
         });
+        if (ioe.get() != null) {
+          throw ioe.get();
+        }
+        if (icoe.get() != null) {
+          throw icoe.get();
+        }
         break;
 
       case RESTORE_ENTRY:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
index 8c9adda..4365c74 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/HashtagsChangedEvent.java
@@ -28,4 +28,4 @@
   public HashtagsChangedEvent (Change change) {
     super(TYPE, change);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
new file mode 100644
index 0000000..1b57906
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/ReviewerDeletedEvent.java
@@ -0,0 +1,31 @@
+// 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.events;
+
+import com.google.common.base.Supplier;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.server.data.AccountAttribute;
+import com.google.gerrit.server.data.ApprovalAttribute;
+
+public class ReviewerDeletedEvent extends PatchSetEvent {
+  static final String TYPE = "reviewer-deleted";
+  public Supplier<AccountAttribute> reviewer;
+  public Supplier<ApprovalAttribute[]> approvals;
+  public String comment;
+
+  public ReviewerDeletedEvent(Change change) {
+    super(TYPE, change);
+  }
+}
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 db1e97e..86b269b 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
@@ -179,7 +179,7 @@
     private final ReviewDbWrapper dbWrapper;
 
     private boolean deleted;
-    private boolean saved;
+    private boolean bumpLastUpdatedOn = true;
 
     private ChangeContext(ChangeControl ctl, ReviewDbWrapper dbWrapper) {
       this.ctl = ctl;
@@ -223,13 +223,11 @@
       return c;
     }
 
-    public void saveChange() {
-      checkState(!deleted, "cannot both save and delete change");
-      saved = true;
+    public void bumpLastUpdatedOn(boolean bump) {
+      bumpLastUpdatedOn = bump;
     }
 
     public void deleteChange() {
-      checkState(!saved, "cannot both save and delete change");
       deleted = true;
     }
   }
@@ -581,14 +579,14 @@
           }
 
           // Bump lastUpdatedOn or rowVersion and commit.
+          Iterable<Change> cs = changesToUpdate(ctx);
           if (newChanges.containsKey(id)) {
-            db.changes().insert(bumpLastUpdatedOn(ctx));
-          } else if (ctx.saved) {
-            db.changes().update(bumpLastUpdatedOn(ctx));
+            // Insert rather than upsert in case of a race on change IDs.
+            db.changes().insert(cs);
           } else if (ctx.deleted) {
-            db.changes().delete(bumpLastUpdatedOn(ctx));
+            db.changes().delete(cs);
           } else {
-            db.changes().update(bumpRowVersionNotLastUpdatedOn(ctx));
+            db.changes().update(cs);
           }
           db.commit();
         } finally {
@@ -618,19 +616,14 @@
     }
   }
 
-  private static Iterable<Change> bumpLastUpdatedOn(ChangeContext ctx) {
+  private static Iterable<Change> changesToUpdate(ChangeContext ctx) {
     Change c = ctx.getChange();
-    if (c.getLastUpdatedOn().before(ctx.getWhen())) {
+    if (ctx.bumpLastUpdatedOn && c.getLastUpdatedOn().before(ctx.getWhen())) {
       c.setLastUpdatedOn(ctx.getWhen());
     }
     return Collections.singleton(c);
   }
 
-  private static Iterable<Change> bumpRowVersionNotLastUpdatedOn(
-      ChangeContext ctx) {
-    return Collections.singleton(ctx.getChange());
-  }
-
   private ChangeContext newChangeContext(Change.Id id) throws Exception {
     Change c = newChanges.get(id);
     if (c == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
index 42dc505..90e2aac 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/GarbageCollectionQueue.java
@@ -19,11 +19,12 @@
 import com.google.inject.Singleton;
 
 import java.util.Collection;
+import java.util.HashSet;
 import java.util.Set;
 
 @Singleton
 public class GarbageCollectionQueue {
-  private final Set<Project.NameKey> projectsScheduledForGc = Sets.newHashSet();
+  private final Set<Project.NameKey> projectsScheduledForGc = new HashSet<>();
 
   public synchronized Set<Project.NameKey> addAll(Collection<Project.NameKey> projects) {
     Set<Project.NameKey> added =
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 2e589d3..26db045 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
@@ -776,7 +776,6 @@
               cmUtil.addChangeMessage(ctx.getDb(),
                   ctx.getUpdate(change.currentPatchSetId()), msg);
 
-              ctx.saveChange();
               return true;
             }
           });
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java
index 0482034..1f8930c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOpRepoManager.java
@@ -49,7 +49,7 @@
  * a transaction of a submission, this caches open repositories to satisfy
  * that requirement.
  */
-class MergeOpRepoManager implements AutoCloseable {
+public class MergeOpRepoManager implements AutoCloseable {
   public class OpenRepo {
     final Repository repo;
     final CodeReviewRevWalk rw;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java
index 7be7014..5ea0c02 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeTip.java
@@ -17,12 +17,12 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
 
 import org.eclipse.jgit.lib.ObjectId;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.Map;
 
 /**
@@ -50,7 +50,7 @@
     checkArgument(!toMerge.isEmpty(), "toMerge may not be empty");
     this.initialTip = initialTip;
     this.branchTip = initialTip;
-    this.mergeResults = Maps.newHashMap();
+    this.mergeResults = new HashMap<>();
     // Assume fast-forward merge until opposite is proven.
     for (CodeReviewCommit commit : toMerge) {
       mergeResults.put(commit.copy(), commit.copy());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
index f88c2ce..d8ed075 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotifyConfig.java
@@ -15,12 +15,12 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
 import com.google.gerrit.server.mail.Address;
 
 import java.util.EnumSet;
+import java.util.HashSet;
 import java.util.Set;
 
 public class NotifyConfig implements Comparable<NotifyConfig> {
@@ -33,8 +33,8 @@
   private String filter;
 
   private Header header;
-  private Set<GroupReference> groups = Sets.newHashSet();
-  private Set<Address> addresses = Sets.newHashSet();
+  private Set<GroupReference> groups = new HashSet<>();
+  private Set<Address> addresses = new HashSet<>();
 
   public String getName() {
     return name;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
index 6087432..91bc428 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PerThreadRequestScope.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.server.config.RequestScopedReviewDbProvider;
 import com.google.gerrit.server.util.RequestContext;
 import com.google.gerrit.server.util.ThreadLocalRequestContext;
@@ -25,6 +24,7 @@
 import com.google.inject.Provider;
 import com.google.inject.Scope;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
@@ -37,7 +37,7 @@
     private final Map<Key<?>, Object> map;
 
     private Context() {
-      map = Maps.newHashMap();
+      map = new HashMap<>();
     }
 
     private <T> T get(Key<T> key, Provider<T> creator) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index 1edfa16..56b7b90 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -69,6 +69,7 @@
 import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -527,7 +528,7 @@
    */
   private void loadNotifySections(
       Config rc, Map<String, GroupReference> groupsByName) {
-    notifySections = Maps.newHashMap();
+    notifySections = new HashMap<>();
     for (String sectionName : rc.getSubsections(NOTIFY)) {
       NotifyConfig n = new NotifyConfig();
       n.setName(sectionName);
@@ -683,7 +684,7 @@
 
   private void loadLabelSections(Config rc) {
     Map<String, String> lowerNames = Maps.newHashMapWithExpectedSize(2);
-    labelSections = Maps.newLinkedHashMap();
+    labelSections = new LinkedHashMap<>();
     for (String name : rc.getSubsections(LABEL)) {
       String lower = name.toLowerCase();
       if (lowerNames.containsKey(lower)) {
@@ -693,7 +694,7 @@
       }
       lowerNames.put(lower, name);
 
-      List<LabelValue> values = Lists.newArrayList();
+      List<LabelValue> values = new ArrayList<>();
       for (String value : rc.getStringList(LABEL, name, KEY_VALUE)) {
         try {
           values.add(parseLabelValue(value));
@@ -818,7 +819,7 @@
   }
 
   private void loadPluginSections(Config rc) {
-    pluginConfigs = Maps.newHashMap();
+    pluginConfigs = new HashMap<>();
     for (String plugin : rc.getSubsections(PLUGIN)) {
       Config pluginConfig = new Config();
       pluginConfigs.put(plugin, pluginConfig);
@@ -973,7 +974,7 @@
   private void saveNotifySections(
       Config rc, Set<AccountGroup.UUID> keepGroups) {
     for (NotifyConfig nc : sort(notifySections.values())) {
-      List<String> email = Lists.newArrayList();
+      List<String> email = new ArrayList<>();
       for (GroupReference gr : nc.getGroups()) {
         if (gr.getUUID() != null) {
           keepGroups.add(gr.getUUID());
@@ -982,7 +983,7 @@
       }
       Collections.sort(email);
 
-      List<String> addrs = Lists.newArrayList();
+      List<String> addrs = new ArrayList<>();
       for (Address addr : nc.getAddresses()) {
         addrs.add(addr.toString());
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectRunnable.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectRunnable.java
index 32f157d..7032878 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectRunnable.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectRunnable.java
@@ -22,4 +22,4 @@
   String getRemoteName();
 
   boolean hasCustomizedPrint();
-}
\ No newline at end of file
+}
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 0053ead..9e1165f 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
@@ -477,9 +477,11 @@
     });
 
     if (!projectControl.allRefsAreVisible()) {
-      rp.setCheckReferencedObjectsAreReachable(receiveConfig.checkReferencedObjectsAreReachable);
-      rp.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo, projectControl, db, false));
+      rp.setCheckReferencedObjectsAreReachable(
+          receiveConfig.checkReferencedObjectsAreReachable);
     }
+    rp.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo,
+        projectControl, db, false));
     List<AdvertiseRefsHook> advHooks = new ArrayList<>(3);
     advHooks.add(new AdvertiseRefsHook() {
       @Override
@@ -629,7 +631,7 @@
       rp.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
     }
 
-    Set<Branch.NameKey> branches = Sets.newHashSet();
+    Set<Branch.NameKey> branches = new HashSet<>();
     for (ReceiveCommand c : commands) {
         if (c.getResult() == OK) {
           if (c.getType() == ReceiveCommand.Type.UPDATE) { // aka fast-forward
@@ -787,7 +789,7 @@
       return;
     }
 
-    List<String> lastCreateChangeErrors = Lists.newArrayList();
+    List<String> lastCreateChangeErrors = new ArrayList<>();
     for (CreateRequest create : newChanges) {
       if (create.cmd.getResult() == OK) {
         okToInsert++;
@@ -817,7 +819,7 @@
     }
 
     try {
-      List<CheckedFuture<?, RestApiException>> futures = Lists.newArrayList();
+      List<CheckedFuture<?, RestApiException>> futures = new ArrayList<>();
       for (ReplaceRequest replace : replaceByChange.values()) {
         if (replace.inputCommand == magicBranch.cmd) {
           futures.add(replace.insertPatchSet());
@@ -1502,7 +1504,7 @@
   }
 
   private void selectNewAndReplacedChangesFromMagicBranch() {
-    newChanges = Lists.newArrayList();
+    newChanges = new ArrayList<>();
 
     SetMultimap<ObjectId, Ref> existing = changeRefsById();
     GroupCollector groupCollector = GroupCollector.create(refsById, db, psUtil,
@@ -1529,7 +1531,7 @@
             magicBranch.ctl != null ? magicBranch.ctl.getRefName() : null);
       }
 
-      List<ChangeLookup> pending = Lists.newArrayList();
+      List<ChangeLookup> pending = new ArrayList<>();
       Set<Change.Key> newChangeIds = new HashSet<>();
       int maxBatchChanges =
           receiveConfig.getEffectiveMaxBatchChangesLimit(user);
@@ -2028,7 +2030,7 @@
               reader.abbreviate(newCommit).name()));
         } else {
           StringBuilder msg = new StringBuilder();
-          msg.append("(W) ");
+          msg.append("(I) ");
           msg.append(reader.abbreviate(newCommit).name());
           msg.append(":");
           msg.append(" no files changed");
@@ -2516,7 +2518,6 @@
           if (change.getStatus().isOpen()) {
             change.setCurrentPatchSet(info);
             change.setStatus(Change.Status.MERGED);
-            ctx.saveChange();
 
             // we cannot reconstruct the submit records for when this change was
             // submitted, this is why we must fix the status
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index 9e51b3f..3469298 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -263,7 +263,6 @@
     if (mergedIntoRef == null) {
       resetChange(ctx, msg);
     }
-    ctx.saveChange();
 
     return true;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index 53389d2..ecba568 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.base.MoreObjects;
-import com.google.common.collect.Lists;
 
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
@@ -50,6 +49,7 @@
 import java.io.BufferedReader;
 import java.io.IOException;
 import java.io.StringReader;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -479,7 +479,7 @@
     TreeWalk tw = new TreeWalk(reader);
     tw.addTree(revision.getTree());
     tw.setRecursive(recursive);
-    List<PathInfo> paths = Lists.newArrayList();
+    List<PathInfo> paths = new ArrayList<>();
     while (tw.next()) {
       paths.add(new PathInfo(tw));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
index a0c9312..6095546 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/WorkQueue.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git;
 
-import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.ListenableFutureTask;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.lifecycle.LifecycleModule;
@@ -129,7 +128,7 @@
   }
 
   public <T> List<T> getTaskInfos(TaskInfoFactory<T> factory) {
-    List<T> taskInfos = Lists.newArrayList();
+    List<T> taskInfos = new ArrayList<>();
     for (Executor exe : queues) {
       for (Task<?> task : exe.getTasks()) {
         taskInfos.add(factory.getTaskInfo(task));
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 9a9e0b9..91d2cc7 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
@@ -160,7 +160,6 @@
           prevPs != null ? prevPs.getGroups() : ImmutableList.<String> of(),
           null);
       ctx.getChange().setCurrentPatchSet(patchSetInfo);
-      ctx.saveChange();
 
       // Don't copy approvals, as this is already taken care of by
       // SubmitStrategyOp.
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 f886e49..01ae0b8 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
@@ -21,7 +21,6 @@
 
 import com.google.common.base.Function;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -63,6 +62,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -327,7 +327,7 @@
   private LabelNormalizer.Result approve(ChangeContext ctx, ChangeUpdate update)
       throws OrmException {
     PatchSet.Id psId = update.getPatchSetId();
-    Map<PatchSetApproval.Key, PatchSetApproval> byKey = Maps.newHashMap();
+    Map<PatchSetApproval.Key, PatchSetApproval> byKey = new HashMap<>();
     for (PatchSetApproval psa : args.approvalsUtil.byPatchSet(
         ctx.getDb(), ctx.getControl(), psId)) {
       byKey.put(psa.getKey(), psa);
@@ -484,7 +484,6 @@
     logDebug("Setting change {} merged", c.getId());
     c.setStatus(Change.Status.MERGED);
     c.setSubmissionId(args.submissionId);
-    ctx.saveChange();
 
     // TODO(dborowitz): We need to be able to change the author of the message,
     // which is not the user from the update context. addMergedMessage was able
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
index f54e071..4bf3076 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/MergeValidators.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.git.validators;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicMap;
 import com.google.gerrit.extensions.registration.DynamicMap.Entry;
 import com.google.gerrit.extensions.registration.DynamicSet;
@@ -36,6 +35,7 @@
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
+import java.util.LinkedList;
 import java.util.List;
 
 public class MergeValidators {
@@ -60,7 +60,7 @@
       PatchSet.Id patchSetId,
       IdentifiedUser caller)
       throws MergeValidationException {
-    List<MergeValidationListener> validators = Lists.newLinkedList();
+    List<MergeValidationListener> validators = new LinkedList<>();
 
     validators.add(new PluginMergeValidationListener(mergeValidationListeners));
     validators.add(projectConfigValidatorFactory.create());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
index 6fd0f5c..769c7d2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/RefOperationValidators.java
@@ -15,7 +15,6 @@
 
 import com.google.common.base.Predicate;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.IdentifiedUser;
@@ -29,6 +28,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.List;
 
 public class RefOperationValidators {
@@ -63,7 +63,7 @@
   public List<ValidationMessage> validateForRefOperation()
     throws RefOperationValidationException {
 
-    List<ValidationMessage> messages = Lists.newArrayList();
+    List<ValidationMessage> messages = new ArrayList<>();
     boolean withException = false;
     try {
       for (RefOperationValidationListener listener : refOperationValidationListeners) {
@@ -97,4 +97,4 @@
       return input.isError();
     }
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationException.java
index 159496b..be264b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/validators/UploadValidationException.java
@@ -27,4 +27,4 @@
   public UploadValidationException(String message) {
     super(message);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
index c274a37..e98653a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddIncludedGroups.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.common.GroupInfo;
@@ -39,6 +38,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -98,8 +99,8 @@
     input = Input.init(input);
 
     GroupControl control = resource.getControl();
-    Map<AccountGroup.UUID, AccountGroupById> newIncludedGroups = Maps.newHashMap();
-    List<GroupInfo> result = Lists.newLinkedList();
+    Map<AccountGroup.UUID, AccountGroupById> newIncludedGroups = new HashMap<>();
+    List<GroupInfo> result = new LinkedList<>();
     Account.Id me = control.getUser().getAccountId();
 
     for (String includedGroup : input.groups) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index e947f2f..4cdbb5a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -47,7 +46,9 @@
 import com.google.inject.Singleton;
 
 import java.util.Collection;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -174,7 +175,7 @@
 
   public void addMembers(AccountGroup.Id groupId,
       Collection<? extends Account.Id> newMemberIds) throws OrmException {
-    Map<Account.Id, AccountGroupMember> newAccountGroupMembers = Maps.newHashMap();
+    Map<Account.Id, AccountGroupMember> newAccountGroupMembers = new HashMap<>();
     for (Account.Id accId : newMemberIds) {
       if (!newAccountGroupMembers.containsKey(accId)) {
         AccountGroupMember.Key key =
@@ -212,7 +213,7 @@
 
   private List<AccountInfo> toAccountInfoList(Set<Account.Id> accountIds)
       throws OrmException {
-    List<AccountInfo> result = Lists.newLinkedList();
+    List<AccountInfo> result = new LinkedList<>();
     AccountLoader loader = infoFactory.create(true);
     for (Account.Id accId : accountIds) {
       result.add(loader.get(accId));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
index f97dac7..30b856a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DbGroupMemberAuditListener.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.group;
 
 import com.google.common.base.Joiner;
-import com.google.common.collect.Lists;
 import com.google.gerrit.audit.GroupMemberAuditListener;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
@@ -37,6 +36,7 @@
 import java.text.MessageFormat;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.LinkedList;
 import java.util.List;
 
 class DbGroupMemberAuditListener implements GroupMemberAuditListener {
@@ -61,7 +61,7 @@
   @Override
   public void onAddAccountsToGroup(Account.Id me,
       Collection<AccountGroupMember> added) {
-    List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
+    List<AccountGroupMemberAudit> auditInserts = new LinkedList<>();
     for (AccountGroupMember m : added) {
       AccountGroupMemberAudit audit =
           new AccountGroupMemberAudit(m, me, TimeUtil.nowTs());
@@ -79,8 +79,8 @@
   @Override
   public void onDeleteAccountsFromGroup(Account.Id me,
       Collection<AccountGroupMember> removed) {
-    List<AccountGroupMemberAudit> auditInserts = Lists.newLinkedList();
-    List<AccountGroupMemberAudit> auditUpdates = Lists.newLinkedList();
+    List<AccountGroupMemberAudit> auditInserts = new LinkedList<>();
+    List<AccountGroupMemberAudit> auditUpdates = new LinkedList<>();
     try (ReviewDb db = schema.open()) {
       for (AccountGroupMember m : removed) {
         AccountGroupMemberAudit audit = null;
@@ -131,7 +131,7 @@
   @Override
   public void onDeleteGroupsFromGroup(Account.Id me,
       Collection<AccountGroupById> removed) {
-    final List<AccountGroupByIdAud> auditUpdates = Lists.newLinkedList();
+    final List<AccountGroupByIdAud> auditUpdates = new LinkedList<>();
     try (ReviewDb db = schema.open()) {
       for (final AccountGroupById g : removed) {
         AccountGroupByIdAud audit = null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
index bde8fb7..da683a3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteIncludedGroups.java
@@ -15,8 +15,6 @@
 package com.google.gerrit.server.group;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -37,6 +35,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -71,7 +71,7 @@
 
     final GroupControl control = resource.getControl();
     final Map<AccountGroup.UUID, AccountGroupById> includedGroups = getIncludedGroups(internalGroup.getId());
-    final List<AccountGroupById> toRemove = Lists.newLinkedList();
+    final List<AccountGroupById> toRemove = new LinkedList<>();
 
     for (final String includedGroup : input.groups) {
       GroupDescription.Basic d = groupsCollection.parse(includedGroup);
@@ -100,8 +100,7 @@
 
   private Map<AccountGroup.UUID, AccountGroupById> getIncludedGroups(
       final AccountGroup.Id groupId) throws OrmException {
-    final Map<AccountGroup.UUID, AccountGroupById> groups =
-        Maps.newHashMap();
+    final Map<AccountGroup.UUID, AccountGroupById> groups = new HashMap<>();
     for (AccountGroupById g : db.get().accountGroupById().byGroup(groupId)) {
       groups.put(g.getIncludeUUID(), g);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
index b14974b..5a759bc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/DeleteMembers.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.group;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.audit.AuditService;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -36,6 +34,8 @@
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import java.util.HashMap;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
@@ -71,7 +71,7 @@
 
     final GroupControl control = resource.getControl();
     final Map<Account.Id, AccountGroupMember> members = getMembers(internalGroup.getId());
-    final List<AccountGroupMember> toRemove = Lists.newLinkedList();
+    final List<AccountGroupMember> toRemove = new LinkedList<>();
 
     for (final String nameOrEmail : input.members) {
       Account a = accounts.parse(nameOrEmail).getAccount();
@@ -102,7 +102,7 @@
 
   private Map<Account.Id, AccountGroupMember> getMembers(
       final AccountGroup.Id groupId) throws OrmException {
-    final Map<Account.Id, AccountGroupMember> members = Maps.newHashMap();
+    final Map<Account.Id, AccountGroupMember> members = new HashMap<>();
     for (final AccountGroupMember m : db.get().accountGroupMembers()
         .byGroup(groupId)) {
       members.put(m.getAccountId(), m);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
index b0e238a..b7d499d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCache.java
@@ -77,4 +77,4 @@
     assert this != EMPTY;
     groups.putAll(other.groups);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java
index 0abf618..049680e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/GroupInfoCacheFactory.java
@@ -14,12 +14,12 @@
 
 package com.google.gerrit.server.group;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.account.GroupBackend;
 import com.google.inject.Inject;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /** Efficiently builds a {@link GroupInfoCache}. */
@@ -34,7 +34,7 @@
   @Inject
   GroupInfoCacheFactory(GroupBackend groupBackend) {
     this.groupBackend = groupBackend;
-    this.out = Maps.newHashMap();
+    this.out = new HashMap<>();
   }
 
   /**
@@ -59,4 +59,4 @@
     want(uuid);
     return out.get(uuid);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
index 40db33b..d23cac4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListGroups.java
@@ -18,8 +18,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupDescriptions;
 import com.google.gerrit.common.data.GroupReference;
@@ -49,11 +47,14 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Set;
 import java.util.SortedMap;
+import java.util.TreeMap;
 
 /** List groups visible to the calling user. */
 public class ListGroups implements RestReadView<TopLevelResource> {
@@ -61,7 +62,7 @@
   protected final GroupCache groupCache;
 
   private final List<ProjectControl> projects = new ArrayList<>();
-  private final Set<AccountGroup.UUID> groupsToInspect = Sets.newHashSet();
+  private final Set<AccountGroup.UUID> groupsToInspect = new HashSet<>();
   private final GroupControl.Factory groupControlFactory;
   private final GroupControl.GenericFactory genericGroupControlFactory;
   private final Provider<IdentifiedUser> identifiedUser;
@@ -177,7 +178,7 @@
   @Override
   public SortedMap<String, GroupInfo> apply(TopLevelResource resource)
       throws OrmException, BadRequestException {
-    SortedMap<String, GroupInfo> output = Maps.newTreeMap();
+    SortedMap<String, GroupInfo> output = new TreeMap<>();
     for (GroupInfo info : get()) {
       output.put(MoreObjects.firstNonNull(
           info.name,
@@ -209,7 +210,7 @@
     List<GroupInfo> groupInfos;
     List<AccountGroup> groupList;
     if (!projects.isEmpty()) {
-      Map<AccountGroup.UUID, AccountGroup> groups = Maps.newHashMap();
+      Map<AccountGroup.UUID, AccountGroup> groups = new HashMap<>();
       for (final ProjectControl projectControl : projects) {
         final Set<GroupReference> groupsRefs = projectControl.getAllGroups();
         for (final GroupReference groupRef : groupsRefs) {
@@ -290,7 +291,7 @@
 
   private List<GroupInfo> getGroupsOwnedBy(IdentifiedUser user)
       throws OrmException {
-    List<GroupInfo> groups = Lists.newArrayList();
+    List<GroupInfo> groups = new ArrayList<>();
     int found = 0;
     int foundIndex = 0;
     for (AccountGroup g : filterGroups(groupCache.all())) {
@@ -314,7 +315,7 @@
   }
 
   private List<AccountGroup> filterGroups(final Iterable<AccountGroup> groups) {
-    final List<AccountGroup> filteredGroups = Lists.newArrayList();
+    final List<AccountGroup> filteredGroups = new ArrayList<>();
     final boolean isAdmin =
         identifiedUser.get().getCapabilities().canAdministrateServer();
     for (final AccountGroup group : groups) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
index 8e22ef9..803c498 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListIncludedGroups.java
@@ -16,7 +16,6 @@
 
 import static com.google.common.base.Strings.nullToEmpty;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
@@ -31,6 +30,7 @@
 
 import org.slf4j.Logger;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -59,7 +59,7 @@
     }
 
     boolean ownerOfParent = rsrc.getControl().isOwner();
-    List<GroupInfo> included = Lists.newArrayList();
+    List<GroupInfo> included = new ArrayList<>();
     for (AccountGroupById u : dbProvider.get()
         .accountGroupById()
         .byGroup(rsrc.toAccountGroup().getId())) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
index d623b31..98d18ca 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/ListMembers.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.group;
 
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupDetail;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.common.AccountInfo;
@@ -35,6 +34,7 @@
 import org.kohsuke.args4j.Option;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
@@ -90,7 +90,7 @@
       final HashSet<AccountGroup.UUID> seenGroups) throws OrmException {
     seenGroups.add(groupUUID);
 
-    final Map<Account.Id, AccountInfo> members = Maps.newHashMap();
+    final Map<Account.Id, AccountInfo> members = new HashMap<>();
     final AccountGroup group = groupCache.get(groupUUID);
     if (group == null) {
       // the included group is an external group and can't be resolved
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 fe06c41..befd5ed 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
@@ -140,10 +140,6 @@
       threads = config.getInt("index", null, "threads", 0);
     }
     if (threads <= 0) {
-      threads =
-          config.getInt("changeMerge", null, "interactiveThreadPoolSize", 0);
-    }
-    if (threads <= 0) {
       return MoreExecutors.newDirectExecutorService();
     }
     return MoreExecutors.listeningDecorator(
@@ -161,9 +157,6 @@
     }
     int threads = config.getInt("index", null, "batchThreads", 0);
     if (threads <= 0) {
-      threads = config.getInt("changeMerge", null, "threadPoolSize", 0);
-    }
-    if (threads <= 0) {
       threads = Runtime.getRuntime().availableProcessors();
     }
     return MoreExecutors.listeningDecorator(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
index 5ead80f..ca61b00 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/SchemaUtil.java
@@ -22,8 +22,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 
 import org.eclipse.jgit.lib.PersonIdent;
 
@@ -32,6 +30,7 @@
 import java.lang.reflect.ParameterizedType;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
@@ -39,7 +38,7 @@
 public class SchemaUtil {
   public static <V> ImmutableSortedMap<Integer, Schema<V>> schemasFromClass(
       Class<?> schemasClass, Class<V> valueClass) {
-    Map<Integer, Schema<V>> schemas = Maps.newHashMap();
+    Map<Integer, Schema<V>> schemas = new HashMap<>();
     for (Field f : schemasClass.getDeclaredFields()) {
       if (Modifier.isStatic(f.getModifiers())
           && Modifier.isFinal(f.getModifiers())
@@ -100,7 +99,7 @@
       Iterable<String> emails) {
     Splitter at = Splitter.on('@');
     Splitter s = Splitter.on(CharMatcher.anyOf("@.- ")).omitEmptyStrings();
-    HashSet<String> parts = Sets.newHashSet();
+    HashSet<String> parts = new HashSet<>();
     for (String email : emails) {
       if (email == null) {
         continue;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
index 3ca7c6a..14723b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/change/AllChangesIndexer.java
@@ -66,6 +66,7 @@
 
 import java.io.IOException;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Iterator;
@@ -147,7 +148,7 @@
         totalWork >= 0 ? totalWork : MultiProgressMonitor.UNKNOWN);
     final Task failedTask = mpm.beginSubTask("failed", MultiProgressMonitor.UNKNOWN);
 
-    final List<ListenableFuture<?>> futures = Lists.newArrayList();
+    final List<ListenableFuture<?>> futures = new ArrayList<>();
     final AtomicBoolean ok = new AtomicBoolean(true);
 
     for (final Project.NameKey project : projects) {
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 e62e665..d61040c 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
@@ -222,7 +222,7 @@
       return ImmutableSet.of();
     }
     Splitter s = Splitter.on('/').omitEmptyStrings();
-    Set<String> r = Sets.newHashSet();
+    Set<String> r = new HashSet<>();
     for (String path : paths) {
       for (String part : s.split(path)) {
         r.add(part);
@@ -302,7 +302,7 @@
           if (c == null) {
             return ImmutableSet.of();
           }
-          Set<Integer> r = Sets.newHashSet();
+          Set<Integer> r = new HashSet<>();
           if (!args.allowsDrafts && c.getStatus() == Change.Status.DRAFT) {
             return r;
           }
@@ -336,7 +336,7 @@
       };
 
   private static Set<String> getRevisions(ChangeData cd) throws OrmException {
-    Set<String> revisions = Sets.newHashSet();
+    Set<String> revisions = new HashSet<>();
     for (PatchSet ps : cd.patchSets()) {
       if (ps.getRevision() != null) {
         revisions.add(ps.getRevision().get());
@@ -372,8 +372,8 @@
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          Set<String> allApprovals = Sets.newHashSet();
-          Set<String> distinctApprovals = Sets.newHashSet();
+          Set<String> allApprovals = new HashSet<>();
+          Set<String> distinctApprovals = new HashSet<>();
           for (PatchSetApproval a : input.currentApprovals()) {
             if (a.getValue() != 0 && !a.isLegacySubmit()) {
               allApprovals.add(formatLabel(a.getLabel(), a.getValue(),
@@ -505,7 +505,7 @@
         @Override
         public Iterable<String> get(ChangeData input, FillArgs args)
             throws OrmException {
-          Set<String> r = Sets.newHashSet();
+          Set<String> r = new HashSet<>();
           for (PatchLineComment c : input.publishedComments()) {
             r.add(c.getMessage());
           }
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
new file mode 100644
index 0000000..89daa1d
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
@@ -0,0 +1,81 @@
+// 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.mail;
+
+import com.google.gerrit.common.errors.EmailException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountProjectWatch.NotifyType;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/** Let users know that a reviewer and possibly her review have
+ * been removed. */
+public class DeleteReviewerSender extends ReplyToChangeSender {
+  private final Set<Account.Id> reviewers = new HashSet<>();
+
+  public static interface Factory extends
+      ReplyToChangeSender.Factory<DeleteReviewerSender> {
+    @Override
+    DeleteReviewerSender create(Project.NameKey project, Change.Id change);
+  }
+
+  @Inject
+  public DeleteReviewerSender(EmailArguments ea,
+      @Assisted Project.NameKey project,
+      @Assisted Change.Id id)
+      throws OrmException {
+    super(ea, "deleteReviewer", newChangeData(ea, project, id));
+  }
+
+  public void addReviewers(Collection<Account.Id> cc) {
+    reviewers.addAll(cc);
+  }
+
+  @Override
+  protected void init() throws EmailException {
+    super.init();
+
+    ccAllApprovals();
+    bccStarredBy();
+    ccExistingReviewers();
+    includeWatchers(NotifyType.ALL_COMMENTS);
+    add(RecipientType.TO, reviewers);
+  }
+
+  @Override
+  protected void formatChange() throws EmailException {
+    appendText(velocifyFile("DeleteReviewer.vm"));
+  }
+
+  public List<String> getReviewerNames() {
+    if (reviewers.isEmpty()) {
+      return null;
+    }
+    List<String> names = new ArrayList<>();
+    for (Account.Id id : reviewers) {
+      names.add(getNameFor(id));
+    }
+    return names;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
index a9240cb..7ceb0ae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/EmailModule.java
@@ -21,6 +21,7 @@
   protected void configure() {
     factory(AbandonedSender.Factory.class);
     factory(CommentSender.Factory.class);
+    factory(DeleteReviewerSender.Factory.class);
     factory(DeleteVoteSender.Factory.class);
     factory(RestoredSender.Factory.class);
     factory(RevertedSender.Factory.class);
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 aeb56d7..4834efd 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
@@ -18,7 +18,6 @@
 import static com.google.gerrit.extensions.client.GeneralPreferencesInfo.EmailStrategy.DISABLED;
 import static java.nio.charset.StandardCharsets.UTF_8;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.errors.EmailException;
 import com.google.gerrit.extensions.api.changes.ReviewInput.NotifyHandling;
 import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
@@ -63,7 +62,7 @@
   protected String messageClass;
   private final HashSet<Account.Id> rcptTo = new HashSet<>();
   private final Map<String, EmailHeader> headers;
-  private final Set<Address> smtpRcptTo = Sets.newHashSet();
+  private final Set<Address> smtpRcptTo = new HashSet<>();
   private Address smtpFromAddress;
   private StringBuilder body;
   protected VelocityContext velocityContext;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
index 95b0219..374b2e9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/ProjectWatch.java
@@ -15,8 +15,6 @@
 package com.google.gerrit.server.mail;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.GroupDescription;
 import com.google.gerrit.common.data.GroupDescriptions;
 import com.google.gerrit.common.data.GroupReference;
@@ -41,6 +39,7 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -100,8 +99,8 @@
 
   public static class Watchers {
     static class List {
-      protected final Set<Account.Id> accounts = Sets.newHashSet();
-      protected final Set<Address> emails = Sets.newHashSet();
+      protected final Set<Account.Id> accounts = new HashSet<>();
+      protected final Set<Address> emails = new HashSet<>();
     }
     protected final List to = new List();
     protected final List cc = new List();
@@ -141,8 +140,8 @@
       Watchers.List matching,
       AccountGroup.UUID startUUID) throws OrmException {
     ReviewDb db = args.db.get();
-    Set<AccountGroup.UUID> seen = Sets.newHashSet();
-    List<AccountGroup.UUID> q = Lists.newArrayList();
+    Set<AccountGroup.UUID> seen = new HashSet<>();
+    List<AccountGroup.UUID> q = new ArrayList<>();
 
     seen.add(startUUID);
     q.add(startUUID);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 55b8556..94dcede 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -103,8 +103,8 @@
   private boolean loaded;
 
   AbstractChangeNotes(Args args, Change.Id changeId) {
-    this.args = args;
-    this.changeId = changeId;
+    this.args = checkNotNull(args);
+    this.changeId = checkNotNull(changeId);
   }
 
   public Change.Id getChangeId() {
@@ -120,7 +120,7 @@
     if (loaded) {
       return self();
     }
-    if (!args.migration.readChanges() || changeId == null) {
+    if (!args.migration.readChanges()) {
       loadDefaults();
       return self();
     }
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 8659ae4..22255b5 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
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.notedb;
 
+import static com.google.common.base.MoreObjects.firstNonNull;
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
@@ -22,16 +23,25 @@
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.NOTE_DB;
 import static com.google.gerrit.server.notedb.ChangeBundle.Source.REVIEW_DB;
 
+import com.google.auto.value.AutoValue;
 import com.google.common.base.CharMatcher;
-import com.google.common.base.Joiner;
+import com.google.common.base.Function;
+import com.google.common.base.Predicate;
+import com.google.common.collect.Collections2;
 import com.google.common.collect.ComparisonChain;
 import com.google.common.collect.ImmutableCollection;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.collect.Multimap;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
+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.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.Patch;
@@ -47,7 +57,10 @@
 import java.sql.Timestamp;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Comparator;
+import java.util.Iterator;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -114,35 +127,38 @@
     return out;
   }
 
+  // Unlike the *Map comparators, which are intended to make key lists diffable,
+  // this comparator sorts first on timestamp, then on every other field.
+  private static final Ordering<ChangeMessage> CHANGE_MESSAGE_ORDER =
+      new Ordering<ChangeMessage>() {
+        final Ordering<Comparable<?>> nullsFirst =
+            Ordering.natural().nullsFirst();
+
+        @Override
+        public int compare(ChangeMessage a, ChangeMessage b) {
+          return ComparisonChain.start()
+              .compare(a.getWrittenOn(), b.getWrittenOn())
+              .compare(a.getKey().getParentKey().get(),
+                  b.getKey().getParentKey().get())
+              .compare(psId(a), psId(b), nullsFirst)
+              .compare(a.getAuthor(), b.getAuthor(), intKeyOrdering())
+              .compare(a.getMessage(), b.getMessage(), nullsFirst)
+              .result();
+        }
+
+        private Integer psId(ChangeMessage m) {
+          return m.getPatchSetId() != null ? m.getPatchSetId().get() : null;
+        }
+      };
+
   private static ImmutableList<ChangeMessage> changeMessageList(
       Iterable<ChangeMessage> in) {
-    // Unlike the *Map comparators, which are intended to make key lists
-    // diffable, this comparator sorts first on timestamp, then on every other
-    // field.
-    final Ordering<Comparable<?>> nullsFirst = Ordering.natural().nullsFirst();
-    return new Ordering<ChangeMessage>() {
-      @Override
-      public int compare(ChangeMessage a, ChangeMessage b) {
-        return ComparisonChain.start()
-            .compare(roundToSecond(a.getWrittenOn()),
-                roundToSecond(b.getWrittenOn()))
-            .compare(a.getKey().getParentKey().get(),
-                b.getKey().getParentKey().get())
-            .compare(psId(a), psId(b), nullsFirst)
-            .compare(a.getAuthor(), b.getAuthor(), intKeyOrdering())
-            .compare(a.getMessage(), b.getMessage(), nullsFirst)
-            .result();
-      }
-
-      private Integer psId(ChangeMessage m) {
-        return m.getPatchSetId() != null ? m.getPatchSetId().get() : null;
-      }
-    }.immutableSortedCopy(in);
+    return CHANGE_MESSAGE_ORDER.immutableSortedCopy(in);
   }
 
 
-  private static Map<PatchSet.Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
-    Map<PatchSet.Id, PatchSet> out = new TreeMap<>(
+  private static TreeMap<PatchSet.Id, PatchSet> patchSetMap(Iterable<PatchSet> in) {
+    TreeMap<PatchSet.Id, PatchSet> out = new TreeMap<>(
         new Comparator<PatchSet.Id>() {
           @Override
           public int compare(PatchSet.Id a, PatchSet.Id b) {
@@ -235,7 +251,7 @@
 
   private final Change change;
   private final ImmutableList<ChangeMessage> changeMessages;
-  private final ImmutableMap<PatchSet.Id, PatchSet> patchSets;
+  private final ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
   private final ImmutableMap<PatchSetApproval.Key, PatchSetApproval>
       patchSetApprovals;
   private final ImmutableMap<PatchLineComment.Key, PatchLineComment>
@@ -251,7 +267,7 @@
       Source source) {
     this.change = checkNotNull(change);
     this.changeMessages = changeMessageList(changeMessages);
-    this.patchSets = ImmutableMap.copyOf(patchSetMap(patchSets));
+    this.patchSets = ImmutableSortedMap.copyOfSorted(patchSetMap(patchSets));
     this.patchSetApprovals =
         ImmutableMap.copyOf(patchSetApprovalMap(patchSetApprovals));
     this.patchLineComments =
@@ -307,27 +323,144 @@
     return ImmutableList.copyOf(diffs);
   }
 
+  private Timestamp getFirstPatchSetTime() {
+    if (patchSets.isEmpty()) {
+      return change.getCreatedOn();
+    }
+    return patchSets.firstEntry().getValue().getCreatedOn();
+  }
+
+  private Timestamp getLatestTimestamp() {
+    Ordering<Timestamp> o = Ordering.natural().nullsFirst();
+    Timestamp ts = null;
+    for (ChangeMessage cm : getChangeMessages()) {
+      ts = o.max(ts, cm.getWrittenOn());
+    }
+    for (PatchSet ps : getPatchSets()) {
+      ts = o.max(ts, ps.getCreatedOn());
+    }
+    for (PatchSetApproval psa : getPatchSetApprovals()) {
+      ts = o.max(ts, psa.getGranted());
+    }
+    for (PatchLineComment plc : getPatchLineComments()) {
+      // Ignore draft comments, as they do not show up in the change meta graph.
+      if (plc.getStatus() != PatchLineComment.Status.DRAFT) {
+        ts = o.max(ts, plc.getWrittenOn());
+      }
+    }
+    return firstNonNull(ts, change.getLastUpdatedOn());
+  }
+
+  private Map<PatchSetApproval.Key, PatchSetApproval>
+      filterPatchSetApprovals() {
+    return limitToExistingPatchSets(patchSetApprovals,
+        new Function<PatchSetApproval.Key, PatchSet.Id>() {
+          @Override
+          public PatchSet.Id apply(PatchSetApproval.Key in) {
+            return in.getParentKey();
+          }
+        });
+  }
+
+  private Map<PatchLineComment.Key, PatchLineComment>
+      filterPatchLineComments() {
+    return limitToExistingPatchSets(patchLineComments,
+        new Function<PatchLineComment.Key, PatchSet.Id>() {
+          @Override
+          public PatchSet.Id apply(PatchLineComment.Key in) {
+            return in.getParentKey().getParentKey();
+          }
+        });
+  }
+
+  private <K, V> Map<K, V> limitToExistingPatchSets(Map<K, V> in,
+      final Function<K, PatchSet.Id> func) {
+    return Maps.filterKeys(
+        in, new Predicate<K>() {
+          @Override
+          public boolean apply(K in) {
+            return patchSets.containsKey(func.apply(in));
+          }
+        });
+  }
+
+  private Collection<ChangeMessage> filterChangeMessages() {
+    return Collections2.filter(changeMessages,
+        new Predicate<ChangeMessage>() {
+          @Override
+          public boolean apply(ChangeMessage in) {
+            PatchSet.Id psId = in.getPatchSetId();
+            return psId == null || patchSets.containsKey(psId);
+          }
+        });
+  }
+
   private static void diffChanges(List<String> diffs, ChangeBundle bundleA,
       ChangeBundle bundleB) {
     Change a = bundleA.change;
     Change b = bundleB.change;
     String desc = a.getId().equals(b.getId()) ? describe(a.getId()) : "Changes";
 
+    boolean excludeCreatedOn = false;
+    boolean excludeSubject = false;
     boolean excludeOrigSubj = false;
     boolean excludeTopic = false;
-    // Ignore null original subject on the ReviewDb side, as this field is
-    // always set in NoteDb.
+    Timestamp aUpdated = a.getLastUpdatedOn();
+    Timestamp bUpdated = b.getLastUpdatedOn();
+
+    // Allow created timestamp in NoteDb to be either the created timestamp of
+    // the change, or the timestamp of the first remaining patch set.
+    //
+    // Ignore subject if the NoteDb subject starts with the ReviewDb subject.
+    // The NoteDb subject is read directly from the commit, whereas the ReviewDb
+    // subject historically may have been truncated to fit in a SQL varchar
+    // column.
+    //
+    // Ignore original subject on the ReviewDb side when comparing to NoteDb.
+    // This field may have any number of values:
+    //  - It may be null, if the change has had no new patch sets pushed since
+    //    migrating to schema 103.
+    //  - It may match the first patch set subject, if the change was created
+    //    after migrating to schema 103.
+    //  - It may match the subject of the first patch set that was pushed after
+    //    the migration to schema 103, even though that is neither the subject
+    //    of the first patch set nor the subject of the last patch set. (See
+    //    Change#setCurrentPatchSet as of 43b10f86 for this behavior.) This
+    //    subject of an intermediate patch set is not available to the
+    //    ChangeBundle; we would have to get the subject from the repo, which is
+    //    inconvenient at this point.
+    //
+    // Ignore original subject on the ReviewDb side if it equals the subject of
+    // the current patch set.
     //
     // Ignore empty topic on the ReviewDb side if it is null on the NoteDb side.
+    //
+    // Use max timestamp of all ReviewDb entities when comparing with NoteDb.
     if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
-      excludeOrigSubj = a.getOriginalSubjectOrNull() == null;
+      excludeCreatedOn = !timestampsDiffer(
+          bundleA, bundleA.getFirstPatchSetTime(), bundleB, b.getCreatedOn());
+      excludeSubject = b.getSubject().startsWith(a.getSubject());
+      excludeOrigSubj = true;
       excludeTopic = "".equals(a.getTopic()) && b.getTopic() == null;
+      aUpdated = bundleA.getLatestTimestamp();
     } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
-      excludeOrigSubj = b.getOriginalSubjectOrNull() == null;
+      excludeCreatedOn = !timestampsDiffer(
+          bundleA, a.getCreatedOn(), bundleB, bundleB.getFirstPatchSetTime());
+      excludeSubject = a.getSubject().startsWith(b.getSubject());
+      excludeOrigSubj = true;
       excludeTopic = a.getTopic() == null && "".equals(b.getTopic());
+      bUpdated = bundleB.getLatestTimestamp();
     }
 
-    List<String> exclude = Lists.newArrayList("rowVersion", "noteDbState");
+    String updatedField = "lastUpdatedOn";
+    List<String> exclude =
+        Lists.newArrayList(updatedField, "noteDbState", "rowVersion");
+    if (excludeCreatedOn) {
+      exclude.add("createdOn");
+    }
+    if (excludeSubject) {
+      exclude.add("subject");
+    }
     if (excludeOrigSubj) {
       exclude.add("originalSubject");
     }
@@ -336,6 +469,40 @@
     }
     diffColumnsExcluding(diffs, Change.class, desc, bundleA, a, bundleB, b,
         exclude);
+
+    // Allow last updated timestamps to either be exactly equal (within slop),
+    // or the NoteDb timestamp to be equal to the latest entity timestamp in the
+    // whole ReviewDb bundle (within slop).
+    if (timestampsDiffer(bundleA, a.getLastUpdatedOn(),
+          bundleB, b.getLastUpdatedOn())) {
+      diffTimestamps(diffs, desc, bundleA, aUpdated, bundleB, bUpdated,
+          "effective last updated time");
+    }
+  }
+
+  /**
+   * Set of fields that must always exactly match between ReviewDb and NoteDb.
+   * <p>
+   * Used to limit the worst-case quadratic search when pairing off matching
+   * messages below.
+   */
+  @AutoValue
+  static abstract class ChangeMessageCandidate {
+    static ChangeMessageCandidate create(ChangeMessage cm) {
+      return new AutoValue_ChangeBundle_ChangeMessageCandidate(
+          cm.getAuthor(),
+          cm.getMessage(),
+          cm.getTag());
+    }
+
+    @Nullable abstract Account.Id author();
+    @Nullable abstract String message();
+    @Nullable abstract String tag();
+
+    // Exclude:
+    //  - patch set, which may be null on ReviewDb side but not NoteDb
+    //  - UUID, which is always different between ReviewDb and NoteDb
+    //  - writtenOn, which is fuzzy
   }
 
   private static void diffChangeMessages(List<String> diffs,
@@ -343,9 +510,9 @@
     if (bundleA.source == REVIEW_DB && bundleB.source == REVIEW_DB) {
       // Both came from ReviewDb: check all fields exactly.
       Map<ChangeMessage.Key, ChangeMessage> as =
-          changeMessageMap(bundleA.changeMessages);
+          changeMessageMap(bundleA.filterChangeMessages());
       Map<ChangeMessage.Key, ChangeMessage> bs =
-          changeMessageMap(bundleB.changeMessages);
+          changeMessageMap(bundleB.filterChangeMessages());
 
       for (ChangeMessage.Key k : diffKeySets(diffs, as, bs)) {
         ChangeMessage a = as.get(k);
@@ -355,43 +522,79 @@
       }
       return;
     }
-
-    // At least one is from NoteDb, so comparisons are inexact as noted below.
     Change.Id id = bundleA.getChange().getId();
     checkArgument(id.equals(bundleB.getChange().getId()));
-    List<ChangeMessage> as = bundleA.changeMessages;
-    List<ChangeMessage> bs = bundleB.changeMessages;
-    if (as.size() != bs.size()) {
-      Joiner j = Joiner.on("\n");
-      diffs.add("Differing numbers of ChangeMessages for Change.Id " + id
-          + ":\n" + j.join(as) + "\n--- vs. ---\n" + j.join(bs));
+
+    // Try to pair up matching ChangeMessages from each side, and succeed only
+    // if both collections are empty at the end. Quadratic in the worst case,
+    // but easy to reason about.
+    List<ChangeMessage> as = new LinkedList<>(bundleA.filterChangeMessages());
+
+    Multimap<ChangeMessageCandidate, ChangeMessage> bs =
+        LinkedListMultimap.create();
+    for (ChangeMessage b : bundleB.filterChangeMessages()) {
+      bs.put(ChangeMessageCandidate.create(b), b);
+    }
+
+    Iterator<ChangeMessage> ait = as.iterator();
+    A: while (ait.hasNext()) {
+      ChangeMessage a = ait.next();
+      Iterator<ChangeMessage> bit =
+          bs.get(ChangeMessageCandidate.create(a)).iterator();
+      while (bit.hasNext()) {
+        ChangeMessage b = bit.next();
+        if (changeMessagesMatch(bundleA, a, bundleB, b)) {
+          ait.remove();
+          bit.remove();
+          continue A;
+        }
+      }
+    }
+
+    if (as.isEmpty() && bs.isEmpty()) {
       return;
     }
-
-    for (int i = 0; i < as.size(); i++) {
-      ChangeMessage a = as.get(i);
-      ChangeMessage b = bs.get(i);
-      String desc = "ChangeMessage on " + id + " at index " + i;
-
-      // Ignore null PatchSet.Id on a ReviewDb change; all entities in NoteDb
-      // have a PatchSet.Id.
-      boolean checkPsId = true;
-      if (bundleA.source == REVIEW_DB) {
-        checkPsId = a.getPatchSetId() != null;
-      } else if (bundleB.source == REVIEW_DB) {
-        checkPsId = b.getPatchSetId() != null;
+    StringBuilder sb = new StringBuilder("ChangeMessages differ for Change.Id ")
+        .append(id).append('\n');
+    if (!as.isEmpty()) {
+      sb.append("Only in A:");
+      for (ChangeMessage cm : as) {
+        sb.append("\n  ").append(cm);
       }
-
-      // Ignore UUIDs for both sides.
-      List<String> exclude = Lists.newArrayList("key");
-      if (!checkPsId) {
-        exclude.add("patchset");
+      if (!bs.isEmpty()) {
+        sb.append('\n');
       }
-
-      // Normal column-wise diff also allows timestamp slop.
-      diffColumnsExcluding(diffs, ChangeMessage.class, desc, bundleA, a,
-          bundleB, b, exclude);
     }
+    if (!bs.isEmpty()) {
+      sb.append("Only in B:");
+      for (ChangeMessage cm : CHANGE_MESSAGE_ORDER.sortedCopy(bs.values())) {
+        sb.append("\n  ").append(cm);
+      }
+    }
+    diffs.add(sb.toString());
+  }
+
+  private static boolean changeMessagesMatch(
+      ChangeBundle bundleA, ChangeMessage a,
+      ChangeBundle bundleB, ChangeMessage b) {
+    List<String> tempDiffs = new ArrayList<>();
+    String temp = "temp";
+
+    boolean excludePatchSet = false;
+    if (bundleA.source == REVIEW_DB && bundleB.source == NOTE_DB) {
+      excludePatchSet = a.getPatchSetId() == null;
+    } else if (bundleA.source == NOTE_DB && bundleB.source == REVIEW_DB) {
+      excludePatchSet = b.getPatchSetId() == null;
+    }
+
+    List<String> exclude = Lists.newArrayList("key");
+    if (excludePatchSet) {
+      exclude.add("patchset");
+    }
+
+    diffColumnsExcluding(
+        tempDiffs, ChangeMessage.class, temp, bundleA, a, bundleB, b, exclude);
+    return tempDiffs.isEmpty();
   }
 
   private static void diffPatchSets(List<String> diffs, ChangeBundle bundleA,
@@ -418,8 +621,10 @@
 
   private static void diffPatchSetApprovals(List<String> diffs,
       ChangeBundle bundleA, ChangeBundle bundleB) {
-    Map<PatchSetApproval.Key, PatchSetApproval> as = bundleA.patchSetApprovals;
-    Map<PatchSetApproval.Key, PatchSetApproval> bs = bundleB.patchSetApprovals;
+    Map<PatchSetApproval.Key, PatchSetApproval> as =
+          bundleA.filterPatchSetApprovals();
+    Map<PatchSetApproval.Key, PatchSetApproval> bs =
+        bundleB.filterPatchSetApprovals();
     for (PatchSetApproval.Key k : diffKeySets(diffs, as, bs)) {
       PatchSetApproval a = as.get(k);
       PatchSetApproval b = bs.get(k);
@@ -430,8 +635,10 @@
 
   private static void diffPatchLineComments(List<String> diffs,
       ChangeBundle bundleA, ChangeBundle bundleB) {
-    Map<PatchLineComment.Key, PatchLineComment> as = bundleA.patchLineComments;
-    Map<PatchLineComment.Key, PatchLineComment> bs = bundleB.patchLineComments;
+    Map<PatchLineComment.Key, PatchLineComment> as =
+        bundleA.filterPatchLineComments();
+    Map<PatchLineComment.Key, PatchLineComment> bs =
+        bundleB.filterPatchLineComments();
     for (PatchLineComment.Key k : diffKeySets(diffs, as, bs)) {
       PatchLineComment a = as.get(k);
       PatchLineComment b = bs.get(k);
@@ -516,15 +723,28 @@
         | SecurityException e) {
       throw new IllegalArgumentException(e);
     }
+    diffTimestamps(diffs, desc, bundleA, ta, bundleB, tb, field);
+  }
+
+  private static void diffTimestamps(List<String> diffs, String desc,
+      ChangeBundle bundleA, Timestamp ta, ChangeBundle bundleB, Timestamp tb,
+      String fieldDesc) {
     if (bundleA.source == bundleB.source || ta == null || tb == null) {
-      diffValues(diffs, desc, ta, tb, field);
+      diffValues(diffs, desc, ta, tb, fieldDesc);
     } else if (bundleA.source == NOTE_DB) {
-      diffTimestamps(diffs, desc, ta, tb, field);
+      diffTimestamps(diffs, desc, ta, tb, fieldDesc);
     } else {
-      diffTimestamps(diffs, desc, tb, ta, field);
+      diffTimestamps(diffs, desc, tb, ta, fieldDesc);
     }
   }
 
+  private static boolean timestampsDiffer(ChangeBundle bundleA, Timestamp ta,
+      ChangeBundle bundleB, Timestamp tb) {
+    List<String> tempDiffs = new ArrayList<>(1);
+    diffTimestamps(tempDiffs, "temp", bundleA, ta, bundleB, tb, "temp");
+    return !tempDiffs.isEmpty();
+  }
+
   private static void diffTimestamps(List<String> diffs, String desc,
       Timestamp tsFromNoteDb, Timestamp tsFromReviewDb, String field) {
     // Because ChangeRebuilder may batch events together that are several
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
index c3457ed..84e647e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeDraftUpdate.java
@@ -221,7 +221,7 @@
 
   @Override
   protected String getRefName() {
-    return RefNames.refsDraftComments(accountId, getId());
+    return RefNames.refsDraftComments(getId(), accountId);
   }
 
   @Override
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 8b112a3..51155cd 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
@@ -21,7 +21,6 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.reviewdb.client.Account;
@@ -31,7 +30,6 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchLineComment.Status;
 import com.google.gerrit.reviewdb.client.PatchSet;
-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.GerritPersonIdent;
@@ -55,6 +53,7 @@
 import java.io.PrintWriter;
 import java.sql.Timestamp;
 import java.text.ParseException;
+import java.util.ArrayList;
 import java.util.Date;
 import java.util.HashSet;
 import java.util.List;
@@ -88,21 +87,6 @@
   private static final String UUID = "UUID";
   private static final String TAG = FOOTER_TAG.getName();
 
-  public static String changeRefName(Change.Id id) {
-    StringBuilder r = new StringBuilder();
-    r.append(RefNames.REFS_CHANGES);
-    int n = id.get();
-    int m = n % 100;
-    if (m < 10) {
-      r.append('0');
-    }
-    r.append(m);
-    r.append('/');
-    r.append(n);
-    r.append(RefNames.META_SUFFIX);
-    return r.toString();
-  }
-
   public static String formatTime(PersonIdent ident, Timestamp t) {
     GitDateFormatter dateFormatter = new GitDateFormatter(Format.DEFAULT);
     // TODO(dborowitz): Use a ThreadLocal or use Joda.
@@ -163,7 +147,7 @@
       return ImmutableList.of();
     }
     Set<PatchLineComment.Key> seen = new HashSet<>();
-    List<PatchLineComment> result = Lists.newArrayList();
+    List<PatchLineComment> result = new ArrayList<>();
     int sizeOfNote = note.length;
     byte[] psb = PATCH_SET.getBytes(UTF_8);
     byte[] bpsb = BASE_PATCH_SET.getBytes(UTF_8);
@@ -539,9 +523,12 @@
     PersonIdent ident = newIdent(
         accountCache.get(c.getAuthor()).getAccount(),
         c.getWrittenOn(), serverIdent, anonymousCowardName);
-    String nameString = ident.getName() + " <" + ident.getEmailAddress()
-        + ">";
-    appendHeaderField(writer, AUTHOR, nameString);
+    StringBuilder name = new StringBuilder();
+    PersonIdent.appendSanitized(name, ident.getName());
+    name.append(" <");
+    PersonIdent.appendSanitized(name, ident.getEmailAddress());
+    name.append('>');
+    appendHeaderField(writer, AUTHOR, name.toString());
 
     String parent = c.getParentUuid();
     if (parent != null) {
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 b685485..aad2bcd 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
@@ -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 com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
@@ -401,9 +402,9 @@
 
   private ChangeNotes(Args args, Project.NameKey project, Change change,
       boolean autoRebuild, @Nullable ChainedReceiveCommands cmds) {
-    super(args, change != null ? change.getId() : null);
+    super(args, change.getId());
     this.project = project;
-    this.change = change != null ? new Change(change) : null;
+    this.change = new Change(change);
     this.autoRebuild = autoRebuild;
     this.cmds = cmds;
   }
@@ -530,7 +531,7 @@
 
   @Override
   protected String getRefName() {
-    return ChangeNoteUtil.changeRefName(getChangeId());
+    return changeMetaRef(getChangeId());
   }
 
   public PatchSet getCurrentPatchSet() {
@@ -547,54 +548,52 @@
       loadDefaults();
       return;
     }
-    try (ChangeNotesParser parser = new ChangeNotesParser(
-         project, change.getId(), rev, handle.walk(), args.repoManager,
-         args.noteUtil, args.metrics)) {
-      parser.parseAll();
+    ChangeNotesParser parser = new ChangeNotesParser(
+        change.getId(), rev, handle.walk(), args.noteUtil, args.metrics);
+    parser.parseAll();
 
-      if (parser.status != null) {
-        change.setStatus(parser.status);
-      }
-      approvals = parser.buildApprovals();
-      changeMessagesByPatchSet = parser.buildMessagesByPatchSet();
-      allChangeMessages = parser.buildAllMessages();
-      comments = ImmutableListMultimap.copyOf(parser.comments);
-      revisionNoteMap = parser.revisionNoteMap;
-      change.setKey(new Change.Key(parser.changeId));
-      change.setDest(new Branch.NameKey(project, parser.branch));
-      change.setTopic(Strings.emptyToNull(parser.topic));
-      change.setCreatedOn(parser.createdOn);
-      change.setLastUpdatedOn(parser.lastUpdatedOn);
-      change.setOwner(parser.ownerId);
-      change.setSubmissionId(parser.submissionId);
-      patchSets = ImmutableSortedMap.copyOf(
-          parser.patchSets, ReviewDbUtil.intKeyOrdering());
-
-      if (!patchSets.isEmpty()) {
-        change.setCurrentPatchSet(
-            parser.currentPatchSetId, parser.subject, parser.originalSubject);
-      } else {
-        // TODO(dborowitz): This should be an error, but for now it's required
-        // for some tests to pass.
-        change.clearCurrentPatchSet();
-      }
-
-      if (parser.hashtags != null) {
-        hashtags = ImmutableSet.copyOf(parser.hashtags);
-      } else {
-        hashtags = ImmutableSet.of();
-      }
-      ImmutableSetMultimap.Builder<ReviewerStateInternal, Account.Id> reviewers =
-          ImmutableSetMultimap.builder();
-      for (Map.Entry<Account.Id, ReviewerStateInternal> e
-          : parser.reviewers.entrySet()) {
-        reviewers.put(e.getValue(), e.getKey());
-      }
-      this.reviewers = reviewers.build();
-      this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
-
-      submitRecords = ImmutableList.copyOf(parser.submitRecords);
+    if (parser.status != null) {
+      change.setStatus(parser.status);
     }
+    approvals = parser.buildApprovals();
+    changeMessagesByPatchSet = parser.buildMessagesByPatchSet();
+    allChangeMessages = parser.buildAllMessages();
+    comments = ImmutableListMultimap.copyOf(parser.comments);
+    revisionNoteMap = parser.revisionNoteMap;
+    change.setKey(new Change.Key(parser.changeId));
+    change.setDest(new Branch.NameKey(project, parser.branch));
+    change.setTopic(Strings.emptyToNull(parser.topic));
+    change.setCreatedOn(parser.createdOn);
+    change.setLastUpdatedOn(parser.lastUpdatedOn);
+    change.setOwner(parser.ownerId);
+    change.setSubmissionId(parser.submissionId);
+    patchSets = ImmutableSortedMap.copyOf(
+        parser.patchSets, ReviewDbUtil.intKeyOrdering());
+
+    if (!patchSets.isEmpty()) {
+      change.setCurrentPatchSet(
+          parser.currentPatchSetId, parser.subject, parser.originalSubject);
+    } else {
+      // TODO(dborowitz): This should be an error, but for now it's required for
+      // some tests to pass.
+      change.clearCurrentPatchSet();
+    }
+
+    if (parser.hashtags != null) {
+      hashtags = ImmutableSet.copyOf(parser.hashtags);
+    } else {
+      hashtags = ImmutableSet.of();
+    }
+    ImmutableSetMultimap.Builder<ReviewerStateInternal, Account.Id> reviewers =
+        ImmutableSetMultimap.builder();
+    for (Map.Entry<Account.Id, ReviewerStateInternal> e
+        : parser.reviewers.entrySet()) {
+      reviewers.put(e.getValue(), e.getKey());
+    }
+    this.reviewers = reviewers.build();
+    this.allPastReviewers = ImmutableList.copyOf(parser.allPastReviewers);
+
+    submitRecords = ImmutableList.copyOf(parser.submitRecords);
   }
 
   @Override
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 604c866..9d9e180 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
@@ -57,21 +57,17 @@
 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.client.Project;
 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.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gerrit.server.util.LabelVote;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.InvalidObjectIdException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.PersonIdent;
-import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.util.RawParseUtils;
@@ -82,7 +78,9 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Map.Entry;
@@ -90,7 +88,7 @@
 import java.util.Set;
 import java.util.TreeMap;
 
-class ChangeNotesParser implements AutoCloseable {
+class ChangeNotesParser {
   // Sentinel RevId indicating a mutable field on a patch set was parsed, but
   // the parser does not yet know its commit SHA-1.
   private static final RevId PARTIAL_PATCH_SET =
@@ -123,36 +121,27 @@
   private final Change.Id id;
   private final ObjectId tip;
   private final ChangeNotesRevWalk walk;
-  private final Repository repo;
   private final Map<PatchSet.Id,
       Table<Account.Id, Entry<String, String>, Optional<PatchSetApproval>>> approvals;
   private final List<ChangeMessage> allChangeMessages;
   private final Multimap<PatchSet.Id, ChangeMessage> changeMessagesByPatchSet;
 
-  ChangeNotesParser(Project.NameKey project, Change.Id changeId, ObjectId tip,
-      ChangeNotesRevWalk walk, GitRepositoryManager repoManager,
-      ChangeNoteUtil noteUtil, NoteDbMetrics metrics)
-      throws RepositoryNotFoundException, IOException {
+  ChangeNotesParser(Change.Id changeId, ObjectId tip, ChangeNotesRevWalk walk,
+      ChangeNoteUtil noteUtil, NoteDbMetrics metrics) {
     this.id = changeId;
     this.tip = tip;
     this.walk = walk;
-    this.repo = repoManager.openRepository(project);
     this.noteUtil = noteUtil;
     this.metrics = metrics;
-    approvals = Maps.newHashMap();
-    reviewers = Maps.newLinkedHashMap();
-    allPastReviewers = Lists.newArrayList();
+    approvals = new HashMap<>();
+    reviewers = new LinkedHashMap<>();
+    allPastReviewers = new ArrayList<>();
     submitRecords = Lists.newArrayListWithExpectedSize(1);
-    allChangeMessages = Lists.newArrayList();
+    allChangeMessages = new ArrayList<>();
     changeMessagesByPatchSet = LinkedListMultimap.create();
     comments = ArrayListMultimap.create();
     patchSets = Maps.newTreeMap(ReviewDbUtil.intKeyOrdering());
-    patchSetStates = Maps.newHashMap();
-  }
-
-  @Override
-  public void close() {
-    repo.close();
+    patchSetStates = new HashMap<>();
   }
 
   void parseAll() throws ConfigInvalidException, IOException {
@@ -207,18 +196,14 @@
     Timestamp ts =
         new Timestamp(commit.getCommitterIdent().getWhen().getTime());
 
-    boolean updateTs = commit.getParentCount() == 0;
     createdOn = ts;
     parseTag(commit);
-    updateTs |= tag != null;
 
     if (branch == null) {
       branch = parseBranch(commit);
-      updateTs |= branch != null;
     }
     if (status == null) {
       status = parseStatus(commit);
-      updateTs |= status != null;
     }
 
     PatchSet.Id psId = parsePatchSetId(commit);
@@ -238,7 +223,6 @@
 
     if (changeId == null) {
       changeId = parseChangeId(commit);
-      updateTs |= changeId != null;
     }
 
     String currSubject = parseSubject(commit);
@@ -247,28 +231,22 @@
         subject = currSubject;
       }
       originalSubject = currSubject;
-      updateTs = true;
     }
 
-    updateTs |= parseChangeMessage(psId, accountId, commit, ts) != null;
+    parseChangeMessage(psId, accountId, commit, ts);
     if (topic == null) {
       topic = parseTopic(commit);
-      updateTs |= topic != null;
     }
 
-    Set<String> oldHashtags = hashtags;
     parseHashtags(commit);
-    updateTs |= hashtags != oldHashtags;
 
     if (submissionId == null) {
       submissionId = parseSubmissionId(commit);
-      updateTs |= submissionId != null;
     }
 
     ObjectId currRev = parseRevision(commit);
     if (currRev != null) {
       parsePatchSet(psId, currRev, accountId, ts);
-      updateTs = true;
     }
     parseGroups(psId, commit);
 
@@ -276,12 +254,10 @@
       // Only parse the most recent set of submit records; any older ones are
       // still there, but not currently used.
       parseSubmitRecords(commit.getFooterLineValues(FOOTER_SUBMITTED_WITH));
-      updateTs |= !submitRecords.isEmpty();
     }
 
     for (String line : commit.getFooterLineValues(FOOTER_LABEL)) {
       parseApproval(psId, accountId, ts, line);
-      updateTs = true;
     }
 
     for (ReviewerStateInternal state : ReviewerStateInternal.values()) {
@@ -292,10 +268,8 @@
       // behavior.
     }
 
-    if (updateTs) {
-      if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
-        lastUpdatedOn = ts;
-      }
+    if (lastUpdatedOn == null || ts.after(lastUpdatedOn)) {
+      lastUpdatedOn = ts;
     }
   }
 
@@ -476,7 +450,7 @@
     throw invalidFooter(FOOTER_PATCH_SET, psIdLine);
   }
 
-  private ChangeMessage parseChangeMessage(PatchSet.Id psId,
+  private void parseChangeMessage(PatchSet.Id psId,
       Account.Id accountId, ChangeNotesCommit commit, Timestamp ts) {
     byte[] raw = commit.getRawBuffer();
     int size = raw.length;
@@ -484,12 +458,12 @@
 
     int subjectStart = RawParseUtils.commitMessage(raw, 0);
     if (subjectStart < 0 || subjectStart >= size) {
-      return null;
+      return;
     }
 
     int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
     if (subjectEnd == size) {
-      return null;
+      return;
     }
 
     int changeMessageStart;
@@ -499,7 +473,7 @@
     } else if (raw[subjectEnd] == '\r') {
       changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
     } else {
-      return null;
+      return;
     }
 
     int ptr = size - 1;
@@ -519,7 +493,7 @@
     }
 
     if (ptr <= changeMessageStart) {
-      return null;
+      return;
     }
 
     String changeMsgString = RawParseUtils.decode(enc, raw,
@@ -533,7 +507,6 @@
     changeMessage.setTag(tag);
     changeMessagesByPatchSet.put(psId, changeMessage);
     allChangeMessages.add(changeMessage);
-    return changeMessage;
   }
 
   private void parseNotes()
@@ -660,7 +633,7 @@
           new Supplier<Map<Entry<String, String>, Optional<PatchSetApproval>>>() {
             @Override
             public Map<Entry<String, String>, Optional<PatchSetApproval>> get() {
-              return Maps.newLinkedHashMap();
+              return new LinkedHashMap<>();
             }
           });
       approvals.put(psId, curr);
@@ -690,7 +663,7 @@
         checkFooter(rec != null, FOOTER_SUBMITTED_WITH, line);
         SubmitRecord.Label label = new SubmitRecord.Label();
         if (rec.labels == null) {
-          rec.labels = Lists.newArrayList();
+          rec.labels = new ArrayList<>();
         }
         rec.labels.add(label);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index 22c5fde..87155d4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -18,6 +18,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 com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
@@ -31,7 +32,6 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableMultimap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Ordering;
 import com.google.common.collect.Sets;
@@ -230,7 +230,7 @@
     Change change = new Change(bundle.getChange());
     // We will rebuild all events, except for draft comments, in buckets based
     // on author and timestamp.
-    List<Event> events = Lists.newArrayList();
+    List<Event> events = new ArrayList<>();
     Multimap<Account.Id, PatchLineCommentEvent> draftCommentEvents =
         ArrayListMultimap.create();
 
@@ -241,8 +241,11 @@
     deleteRef(change, changeMetaRepo, manager.getChangeRepo().cmds);
 
     Integer minPsNum = getMinPatchSetNum(bundle);
+    Set<PatchSet.Id> psIds =
+        Sets.newHashSetWithExpectedSize(bundle.getPatchSets().size());
 
     for (PatchSet ps : bundle.getPatchSets()) {
+      psIds.add(ps.getId());
       events.add(new PatchSetEvent(
           change, ps, manager.getChangeRepo().rw));
       for (PatchLineComment c : getPatchLineComments(bundle, ps)) {
@@ -257,13 +260,17 @@
     }
 
     for (PatchSetApproval psa : bundle.getPatchSetApprovals()) {
-      events.add(new ApprovalEvent(psa, change.getCreatedOn()));
+      if (psIds.contains(psa.getPatchSetId())) {
+        events.add(new ApprovalEvent(psa, change.getCreatedOn()));
+      }
     }
 
     Change noteDbChange = new Change(null, null, null, null, null);
     for (ChangeMessage msg : bundle.getChangeMessages()) {
-      events.add(
-          new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
+      if (msg.getPatchSetId() == null || psIds.contains(msg.getPatchSetId())) {
+        events.add(
+            new ChangeMessageEvent(msg, noteDbChange, change.getCreatedOn()));
+      }
     }
 
     sortAndFillEvents(change, noteDbChange, events, minPsNum);
@@ -401,7 +408,7 @@
 
   private List<HashtagsEvent> getHashtagsEvents(Change change,
       NoteDbUpdateManager manager) throws IOException {
-    String refName = ChangeNoteUtil.changeRefName(change.getId());
+    String refName = changeMetaRef(change.getId());
     ObjectId old = manager.getChangeRepo().getObjectId(refName);
     if (old == null) {
       return Collections.emptyList();
@@ -460,7 +467,7 @@
 
   private void deleteRef(Change change, Repository repo,
       ChainedReceiveCommands cmds) throws IOException {
-    String refName = ChangeNoteUtil.changeRefName(change.getId());
+    String refName = changeMetaRef(change.getId());
     ObjectId old = cmds.getObjectId(repo, refName);
     if (old != null) {
       cmds.add(new ReceiveCommand(old, ObjectId.zeroId(), refName));
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 a947cb4..3c7f909 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
@@ -18,6 +18,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 com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 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;
@@ -480,7 +481,7 @@
 
   @Override
   protected String getRefName() {
-    return ChangeNoteUtil.changeRefName(getId());
+    return changeMetaRef(getId());
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
index 43f58a5..e325911 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java
@@ -110,7 +110,7 @@
 
   @Override
   protected String getRefName() {
-    return RefNames.refsDraftComments(author, getChangeId());
+    return RefNames.refsDraftComments(getChangeId(), author);
   }
 
   @Override
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 6b62656..f1912a4 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
@@ -16,6 +16,7 @@
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.annotations.VisibleForTesting;
@@ -166,7 +167,7 @@
   }
 
   public boolean isChangeUpToDate(Repository changeRepo) throws IOException {
-    Ref ref = changeRepo.exactRef(ChangeNoteUtil.changeRefName(changeId));
+    Ref ref = changeRepo.exactRef(changeMetaRef(changeId));
     if (ref == null) {
       return changeMetaId.equals(ObjectId.zeroId());
     }
@@ -176,7 +177,7 @@
   public boolean areDraftsUpToDate(Repository draftsRepo, Account.Id accountId)
       throws IOException {
     Ref ref = draftsRepo.exactRef(
-        RefNames.refsDraftComments(accountId, changeId));
+        RefNames.refsDraftComments(changeId, accountId));
     if (ref == null) {
       return !draftIds.containsKey(accountId);
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
index 8344fed..5a192f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java
@@ -258,12 +258,10 @@
     for (ReceiveCommand cmd : allUsersRepo.cmds.getCommands().values()) {
       String r = cmd.getRefName();
       if (r.startsWith(REFS_DRAFT_COMMENTS)) {
-        Account.Id accountId =
-            Account.Id.fromRefPart(r.substring(REFS_DRAFT_COMMENTS.length()));
-        checkDraftRef(accountId != null, r);
-        int s = r.lastIndexOf('/');
-        checkDraftRef(s >= 0 && s < r.length() - 1, r);
-        Change.Id changeId = Change.Id.parse(r.substring(s + 1));
+        Change.Id changeId =
+            Change.Id.fromRefPart(r.substring(REFS_DRAFT_COMMENTS.length()));
+        Account.Id accountId = Account.Id.fromRefSuffix(r);
+        checkDraftRef(accountId != null && changeId != null, r);
         draftIds.put(changeId, accountId, cmd.getNewId());
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
index e239654..ee8f963 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AbstractPreloadedPluginScanner.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkState;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.server.plugins.Plugin.ApiType;
 import com.google.inject.Module;
@@ -26,6 +25,7 @@
 import java.io.IOException;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.Modifier;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.jar.Manifest;
@@ -85,7 +85,7 @@
         ImmutableMap.builder();
 
     for (Class<? extends Annotation> annotation : annotations) {
-      Set<ExtensionMetaData> classMetaDataSet = Sets.newHashSet();
+      Set<ExtensionMetaData> classMetaDataSet = new HashSet<>();
       result.put(annotation, classMetaDataSet);
 
       for (Class<?> clazz : preloadedClasses) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index f719e8d..438add6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -20,7 +20,6 @@
 
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.annotations.Listen;
@@ -40,6 +39,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.ParameterizedType;
 import java.util.Arrays;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 
@@ -78,7 +78,7 @@
   }
 
   AutoRegisterModules discover() throws InvalidPluginException {
-    sysSingletons = Sets.newHashSet();
+    sysSingletons = new HashSet<>();
     sysListen = LinkedListMultimap.create();
     initJs = null;
 
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 d94df9c..1f612a3 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
@@ -27,7 +27,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 
 import org.eclipse.jgit.util.IO;
 import org.objectweb.asm.AnnotationVisitor;
@@ -47,6 +46,8 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -77,10 +78,10 @@
   public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
       String pluginName, Iterable<Class<? extends Annotation>> annotations)
       throws InvalidPluginException {
-    Set<String> descriptors = Sets.newHashSet();
+    Set<String> descriptors = new HashSet<>();
     Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
     Map<Class<? extends Annotation>, String> classObjToClassDescr =
-        Maps.newHashMap();
+        new HashMap<>();
 
     for (Class<? extends Annotation> annotation : annotations) {
       String descriptor = Type.getType(annotation).getDescriptor();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 0146f84..ff29785 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.restapi.RestReadView;
@@ -34,6 +33,7 @@
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 /** List the installed plugins. */
 @RequiresCapability(GlobalCapability.VIEW_PLUGINS)
@@ -63,7 +63,7 @@
     });
 
     if (stdout == null) {
-      Map<String, PluginInfo> output = Maps.newTreeMap();
+      Map<String, PluginInfo> output = new TreeMap<>();
       for (Plugin p : plugins) {
         PluginInfo info = new PluginInfo(p);
         output.put(p.getName(), info);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index 3154b4e..4fe0c2a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.plugins;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.registration.RegistrationHandle;
 import com.google.gerrit.extensions.registration.ReloadableRegistrationHandle;
@@ -26,6 +25,7 @@
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
 import java.nio.file.Path;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 import java.util.jar.Attributes;
@@ -146,7 +146,7 @@
     if (manager != null) {
       if (handle instanceof ReloadableRegistrationHandle) {
         if (reloadableHandles == null) {
-          reloadableHandles = Lists.newArrayList();
+          reloadableHandles = new ArrayList<>();
         }
         reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
index bc471e4..5e7d9ee 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginGuiceEnvironment.java
@@ -22,8 +22,6 @@
 import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.RootRelative;
 import com.google.gerrit.extensions.events.LifecycleListener;
@@ -56,7 +54,9 @@
 import java.lang.reflect.ParameterizedType;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -347,7 +347,7 @@
       PrivateInternals_DynamicMapImpl<Object> map =
           (PrivateInternals_DynamicMapImpl<Object>) e.getValue();
 
-      Map<Annotation, ReloadableRegistrationHandle<?>> am = Maps.newHashMap();
+      Map<Annotation, ReloadableRegistrationHandle<?>> am = new HashMap<>();
       for (ReloadableRegistrationHandle<?> h : oldHandles.get(type)) {
         Annotation a = h.getKey().getAnnotation();
         if (a != null && !UNIQUE_ANNOTATION.isInstance(a)) {
@@ -402,7 +402,7 @@
       // Index all old handles that match this DynamicSet<T> keyed by
       // annotations. Ignore the unique annotations, thereby favoring
       // the @Named annotations or some other non-unique naming.
-      Map<Annotation, ReloadableRegistrationHandle<?>> am = Maps.newHashMap();
+      Map<Annotation, ReloadableRegistrationHandle<?>> am = new HashMap<>();
       List<ReloadableRegistrationHandle<?>> old = oldHandles.get(type);
       Iterator<ReloadableRegistrationHandle<?>> oi = old.iterator();
       while (oi.hasNext()) {
@@ -510,8 +510,8 @@
   }
 
   private Module copy(Injector src) {
-    Set<TypeLiteral<?>> dynamicTypes = Sets.newHashSet();
-    Set<TypeLiteral<?>> dynamicItemTypes = Sets.newHashSet();
+    Set<TypeLiteral<?>> dynamicTypes = new HashSet<>();
+    Set<TypeLiteral<?>> dynamicItemTypes = new HashSet<>();
     for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
       TypeLiteral<?> type = e.getKey().getTypeLiteral();
       if (type.getRawType() == DynamicItem.class) {
@@ -524,7 +524,7 @@
       }
     }
 
-    final Map<Key<?>, Binding<?>> bindings = Maps.newLinkedHashMap();
+    final Map<Key<?>, Binding<?>> bindings = new LinkedHashMap<>();
     for (Map.Entry<Key<?>, Binding<?>> e : src.getBindings().entrySet()) {
       if (dynamicTypes.contains(e.getKey().getTypeLiteral())
           && e.getKey().getAnnotation() != null) {
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 0ea7bd2..fb1dce5 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
@@ -25,7 +25,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Queues;
 import com.google.common.collect.Sets;
 import com.google.common.io.ByteStreams;
 import com.google.gerrit.extensions.annotations.PluginName;
@@ -56,10 +55,12 @@
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.AbstractMap;
+import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
+import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -114,8 +115,8 @@
     pluginUserFactory = puf;
     running = Maps.newConcurrentMap();
     disabled = Maps.newConcurrentMap();
-    broken = Maps.newHashMap();
-    toCleanup = Queues.newArrayDeque();
+    broken = new HashMap<>();
+    toCleanup = new ArrayDeque<>();
     cleanupHandles = Maps.newConcurrentMap();
     cleaner = pct;
     urlProvider = provider;
@@ -659,8 +660,8 @@
       assert winner != null;
       // Disable all loser plugins by renaming their file names to
       // "file.disabled" and replace the disabled files in the multimap.
-      Collection<Path> elementsToRemove = Lists.newArrayList();
-      Collection<Path> elementsToAdd = Lists.newArrayList();
+      Collection<Path> elementsToRemove = new ArrayList<>();
+      Collection<Path> elementsToAdd = new ArrayList<>();
       for (Path loser : Iterables.skip(enabled, 1)) {
         log.warn(String.format("Plugin <%s> was disabled, because"
              + " another plugin <%s>"
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index de1966f..115ddb5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -30,6 +30,8 @@
 
 import java.io.IOException;
 import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.LinkedList;
 import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.Manifest;
@@ -198,7 +200,7 @@
     }
 
     if (env.hasSshModule()) {
-      List<Module> modules = Lists.newLinkedList();
+      List<Module> modules = new LinkedList<>();
       if (getApiType() == ApiType.PLUGIN) {
         modules.add(env.getSshModule());
       }
@@ -214,7 +216,7 @@
     }
 
     if (env.hasHttpModule()) {
-      List<Module> modules = Lists.newLinkedList();
+      List<Module> modules = new LinkedList<>();
       if (getApiType() == ApiType.PLUGIN) {
         modules.add(env.getHttpModule());
       }
@@ -279,7 +281,7 @@
     if (serverManager != null) {
       if (handle instanceof ReloadableRegistrationHandle) {
         if (reloadableHandles == null) {
-          reloadableHandles = Lists.newArrayList();
+          reloadableHandles = new ArrayList<>();
         }
         reloadableHandles.add((ReloadableRegistrationHandle<?>) handle);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
index 0c540a7..d24a7e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfo.java
@@ -16,7 +16,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.common.ActionInfo;
@@ -34,6 +33,7 @@
 import com.google.inject.util.Providers;
 
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.TreeMap;
@@ -131,7 +131,7 @@
     this.submitType = p.getSubmitType();
     this.state = p.getState() != com.google.gerrit.extensions.client.ProjectState.ACTIVE ? p.getState() : null;
 
-    this.commentlinks = Maps.newLinkedHashMap();
+    this.commentlinks = new LinkedHashMap<>();
     for (CommentLinkInfo cl : projectState.getCommentLinks()) {
       this.commentlinks.put(cl.name, cl);
     }
@@ -140,7 +140,7 @@
         getPluginConfig(control.getProjectState(), pluginConfigEntries,
             cfgFactory, allProjects);
 
-    actions = Maps.newTreeMap();
+    actions = new TreeMap<>();
     for (UiAction.Description d : UiActions.from(
         views, new ProjectResource(control),
         Providers.of(control.getUser()))) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
index 4646e3b..459f20c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DashboardsCollection.java
@@ -49,6 +49,7 @@
 import org.eclipse.jgit.lib.Repository;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 @Singleton
@@ -207,7 +208,7 @@
     Boolean isDefault;
 
     String title;
-    List<Section> sections = Lists.newArrayList();
+    List<Section> sections = new ArrayList<>();
 
     DashboardInfo(String ref, String name) {
       this.ref = ref;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
index 00ebe2f..b8c5fd8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
@@ -17,7 +17,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
@@ -48,6 +47,7 @@
 
 import java.io.IOException;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 
 @Singleton
@@ -135,7 +135,7 @@
     }
 
     info.local = new HashMap<>();
-    info.ownerOf = Sets.newHashSet();
+    info.ownerOf = new HashSet<>();
     Map<AccountGroup.UUID, Boolean> visibleGroups = new HashMap<>();
 
     for (AccessSection section : config.getAccessSections()) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index 0c417d9..1bf763f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
@@ -25,7 +23,9 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -64,7 +64,7 @@
   }
 
   private List<ProjectInfo> getDirectChildProjects(Project.NameKey parent) {
-    List<ProjectInfo> childProjects = Lists.newArrayList();
+    List<ProjectInfo> childProjects = new ArrayList<>();
     for (Project.NameKey projectName : projectCache.all()) {
       ProjectState e = projectCache.get(projectName);
       if (e == null) {
@@ -80,7 +80,7 @@
 
   private List<ProjectInfo> getChildProjectsRecursively(Project.NameKey parent,
       CurrentUser user) {
-    Map<Project.NameKey, ProjectNode> projects = Maps.newHashMap();
+    Map<Project.NameKey, ProjectNode> projects = new HashMap<>();
     for (Project.NameKey name : projectCache.all()) {
       ProjectState p = projectCache.get(name);
       if (p == null) {
@@ -106,7 +106,7 @@
   }
 
   private List<ProjectInfo> getChildProjectsRecursively(ProjectNode p) {
-    List<ProjectInfo> allChildren = Lists.newArrayList();
+    List<ProjectInfo> allChildren = new ArrayList<>();
     for (ProjectNode c : p.getChildren()) {
       if (c.isVisible()) {
         allChildren.add(json.format(c.getProject()));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
index b4bd9a3..2546ac6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListDashboards.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.reviewdb.client.RefNames.REFS_DASHBOARDS;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
 import com.google.gerrit.reviewdb.client.Project;
@@ -37,6 +36,7 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.List;
 
 class ListDashboards implements RestReadView<ProjectResource> {
@@ -61,7 +61,7 @@
       return scan(resource.getControl(), project, true);
     }
 
-    List<List<DashboardInfo>> all = Lists.newArrayList();
+    List<List<DashboardInfo>> all = new ArrayList<>();
     boolean setDefault = true;
     for (ProjectState ps : ctl.getProjectState().tree()) {
       ctl = ps.controlFor(ctl.getUser());
@@ -85,7 +85,7 @@
     Project.NameKey projectName = ctl.getProject().getNameKey();
     try (Repository git = gitManager.openRepository(projectName);
         RevWalk rw = new RevWalk(git)) {
-      List<DashboardInfo> all = Lists.newArrayList();
+      List<DashboardInfo> all = new ArrayList<>();
       for (Ref ref : git.getRefDatabase().getRefs(REFS_DASHBOARDS).values()) {
         if (ctl.controlForRef(ref.getName()).canRead()) {
           all.addAll(scanDashboards(ctl.getProject(), git, rw, ref,
@@ -101,7 +101,7 @@
   private List<DashboardInfo> scanDashboards(Project definingProject,
       Repository git, RevWalk rw, Ref ref, String project, boolean setDefault)
       throws IOException {
-    List<DashboardInfo> list = Lists.newArrayList();
+    List<DashboardInfo> list = new ArrayList<>();
     try (TreeWalk tw = new TreeWalk(rw.getObjectReader())) {
       tw.addTree(rw.parseTree(ref.getObjectId()));
       tw.setRecursive(true);
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 907b925..bf17a37 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
@@ -21,8 +21,6 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.errors.NoSuchGroupException;
 import com.google.gerrit.extensions.common.ProjectInfo;
@@ -61,8 +59,11 @@
 import java.io.OutputStream;
 import java.io.OutputStreamWriter;
 import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
@@ -179,7 +180,7 @@
     this.groupUuid = groupUuid;
   }
 
-  private final List<String> showBranch = Lists.newArrayList();
+  private final List<String> showBranch = new ArrayList<>();
   private boolean showTree;
   private FilterType type = FilterType.ALL;
   private boolean showDescription;
@@ -256,8 +257,8 @@
 
     int foundIndex = 0;
     int found = 0;
-    TreeMap<String, ProjectInfo> output = Maps.newTreeMap();
-    Map<String, String> hiddenNames = Maps.newHashMap();
+    TreeMap<String, ProjectInfo> output = new TreeMap<>();
+    Map<String, String> hiddenNames = new HashMap<>();
     Set<String> rejected = new HashSet<>();
 
     final TreeMap<Project.NameKey, ProjectNode> treeMap = new TreeMap<>();
@@ -357,7 +358,7 @@
                   Ref ref = refs.get(i);
                   if (ref != null && ref.getObjectId() != null) {
                     if (info.branches == null) {
-                      info.branches = Maps.newLinkedHashMap();
+                      info.branches = new LinkedHashMap<>();
                     }
                     info.branches.put(showBranch.get(i), ref.getObjectId().name());
                   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
index 927d205..bbc7b77 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListTags.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.project;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.api.projects.TagInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.IdString;
@@ -43,6 +42,7 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
@@ -93,7 +93,7 @@
   @Override
   public List<TagInfo> apply(ProjectResource resource) throws IOException,
       ResourceNotFoundException, BadRequestException {
-    List<TagInfo> tags = Lists.newArrayList();
+    List<TagInfo> tags = new ArrayList<>();
 
     try (Repository repo = getRepository(resource.getNameKey());
         RevWalk rw = new RevWalk(repo)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
index a5cf5d6..9a9c5bb 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PermissionCollection.java
@@ -35,6 +35,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -81,7 +82,7 @@
 
       Collection<String> usernames = null;
       boolean perUser = false;
-      Map<AccessSection, Project.NameKey> sectionToProject = Maps.newLinkedHashMap();
+      Map<AccessSection, Project.NameKey> sectionToProject = new LinkedHashMap<>();
       for (SectionMatcher sm : matcherList) {
         // If the matcher has to expand parameters and its prefix matches the
         // reference there is a very good chance the reference is actually user
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
index ad94d64..308a258 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectCacheImpl.java
@@ -40,6 +40,7 @@
 
 import java.io.IOException;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 import java.util.Set;
@@ -210,7 +211,7 @@
 
   @Override
   public Set<AccountGroup.UUID> guessRelevantGroupUUIDs() {
-    Set<AccountGroup.UUID> groups = Sets.newHashSet();
+    Set<AccountGroup.UUID> groups = new HashSet<>();
     for (Project.NameKey n : all()) {
       ProjectState p = byName.getIfPresent(n.get());
       if (p != null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
index b610854..6fab775 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectControl.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.PageLinks;
@@ -385,7 +384,7 @@
     }
     final IdentifiedUser iUser = user.asIdentifiedUser();
 
-    List<AccountGroup.UUID> okGroupIds = Lists.newArrayList();
+    List<AccountGroup.UUID> okGroupIds = new ArrayList<>();
     for (ContributorAgreement ca : contributorAgreements) {
       List<AccountGroup.UUID> groupIds;
       groupIds = okGroupIds;
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 7094828..29f97fb 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
@@ -21,7 +21,6 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.common.data.LabelType;
@@ -61,8 +60,10 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -126,7 +127,7 @@
     this.rulesCache = rulesCache;
     this.commentLinks = commentLinks;
     this.config = config;
-    this.configs = Maps.newHashMap();
+    this.configs = new HashMap<>();
     this.capabilities = isAllProjects
       ? new CapabilityCollection(config.getAccessSection(AccessSection.GLOBAL_CAPABILITIES))
       : null;
@@ -277,7 +278,7 @@
       return getLocalAccessSections();
     }
 
-    List<SectionMatcher> all = Lists.newArrayList();
+    List<SectionMatcher> all = new ArrayList<>();
     for (ProjectState s : tree()) {
       all.addAll(s.getLocalAccessSections());
     }
@@ -423,7 +424,7 @@
   }
 
   public LabelTypes getLabelTypes() {
-    Map<String, LabelType> types = Maps.newLinkedHashMap();
+    Map<String, LabelType> types = new LinkedHashMap<>();
     for (ProjectState s : treeInOrder()) {
       for (LabelType type : s.getConfig().getLabelSections().values()) {
         String lower = type.getName().toLowerCase();
@@ -443,7 +444,7 @@
   }
 
   public List<CommentLinkInfo> getCommentLinks() {
-    Map<String, CommentLinkInfo> cls = Maps.newLinkedHashMap();
+    Map<String, CommentLinkInfo> cls = new LinkedHashMap<>();
     for (CommentLinkInfo cl : commentLinks) {
       cls.put(cl.name.toLowerCase(), cl);
     }
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 dd57ed4..cbe7fba 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
@@ -14,8 +14,6 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRange;
@@ -45,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -128,8 +127,8 @@
   public boolean isVisibleByRegisteredUsers() {
     List<PermissionRule> access = relevant.getPermission(Permission.READ);
     List<PermissionRule> overridden = relevant.getOverridden(Permission.READ);
-    Set<ProjectRef> allows = Sets.newHashSet();
-    Set<ProjectRef> blocks = Sets.newHashSet();
+    Set<ProjectRef> allows = new HashSet<>();
+    Set<ProjectRef> blocks = new HashSet<>();
     for (PermissionRule rule : access) {
       if (rule.isBlock()) {
         blocks.add(relevant.getRuleProps(rule));
@@ -506,7 +505,7 @@
 
   private PermissionRange toRange(String permissionName,
       List<PermissionRule> ruleList) {
-    Map<ProjectRef, AllowedRange> ranges = Maps.newHashMap();
+    Map<ProjectRef, AllowedRange> ranges = new HashMap<>();
     for (PermissionRule rule : ruleList) {
       ProjectRef p = relevant.getRuleProps(rule);
       AllowedRange r = ranges.get(p);
@@ -546,8 +545,8 @@
   private boolean doCanPerform(String permissionName, boolean blockOnly) {
     List<PermissionRule> access = access(permissionName);
     List<PermissionRule> overridden = relevant.getOverridden(permissionName);
-    Set<ProjectRef> allows = Sets.newHashSet();
-    Set<ProjectRef> blocks = Sets.newHashSet();
+    Set<ProjectRef> allows = new HashSet<>();
+    Set<ProjectRef> blocks = new HashSet<>();
     for (PermissionRule rule : access) {
       if (rule.isBlock() && !rule.getForce()) {
         blocks.add(relevant.getRuleProps(rule));
@@ -566,8 +565,8 @@
   private boolean canForcePerform(String permissionName) {
     List<PermissionRule> access = access(permissionName);
     List<PermissionRule> overridden = relevant.getOverridden(permissionName);
-    Set<ProjectRef> allows = Sets.newHashSet();
-    Set<ProjectRef> blocks = Sets.newHashSet();
+    Set<ProjectRef> allows = new HashSet<>();
+    Set<ProjectRef> blocks = new HashSet<>();
     for (PermissionRule rule : access) {
       if (rule.isBlock()) {
         blocks.add(relevant.getRuleProps(rule));
@@ -588,8 +587,8 @@
   private boolean isForceBlocked(String permissionName) {
     List<PermissionRule> access = access(permissionName);
     List<PermissionRule> overridden = relevant.getOverridden(permissionName);
-    Set<ProjectRef> allows = Sets.newHashSet();
-    Set<ProjectRef> blocks = Sets.newHashSet();
+    Set<ProjectRef> allows = new HashSet<>();
+    Set<ProjectRef> blocks = new HashSet<>();
     for (PermissionRule rule : access) {
       if (rule.isBlock()) {
         blocks.add(relevant.getRuleProps(rule));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
index 2292295..93236b0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SubmitRuleEvaluator.java
@@ -17,7 +17,6 @@
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.common.data.SubmitTypeRecord;
@@ -485,7 +484,7 @@
       }
       List<Term> r;
       if (resultsTerm instanceof ListTerm) {
-        r = Lists.newArrayList();
+        r = new ArrayList<>();
         for (Term t = resultsTerm; t instanceof ListTerm;) {
           ListTerm l = (ListTerm) t;
           r.add(l.car().dereference());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
index 9ed0447..9e68595 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/AndSource.java
@@ -21,7 +21,6 @@
 import com.google.common.collect.FluentIterable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.gerrit.server.query.AndPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gwtorm.server.ListResultSet;
@@ -108,7 +107,7 @@
     if (source == null) {
       throw new OrmException("No ChangeDataSource: " + this);
     }
-    List<ChangeData> r = Lists.newArrayList();
+    List<ChangeData> r = new ArrayList<>();
     ChangeData last = null;
     int nextStart = 0;
     boolean skipped = false;
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 76dd030..b32ccab 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
@@ -125,7 +125,7 @@
       return;
     }
 
-    Map<Change.Id, ChangeData> missing = Maps.newHashMap();
+    Map<Change.Id, ChangeData> missing = new HashMap<>();
     for (ChangeData cd : changes) {
       if (cd.change == null) {
         missing.put(cd.getId(), cd);
@@ -183,7 +183,7 @@
       return;
     }
 
-    Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
+    Map<PatchSet.Id, ChangeData> missing = new HashMap<>();
     for (ChangeData cd : changes) {
       if (cd.currentPatchSet == null && cd.patchSets == null) {
         missing.put(cd.change().currentPatchSetId(), cd);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
index 44e0654..bb5f8ff 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsWatchedByPredicate.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server.query.change;
 
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.AccountProjectWatch;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.query.AndPredicate;
@@ -23,6 +22,7 @@
 import com.google.gerrit.server.query.QueryBuilder;
 import com.google.gerrit.server.query.QueryParseException;
 
+import java.util.ArrayList;
 import java.util.List;
 
 class IsWatchedByPredicate extends AndPredicate<ChangeData> {
@@ -45,7 +45,7 @@
       ChangeQueryBuilder.Arguments args,
       boolean checkIsVisible) throws QueryParseException {
     CurrentUser user = args.getUser();
-    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    List<Predicate<ChangeData>> r = new ArrayList<>();
     ChangeQueryBuilder builder = new ChangeQueryBuilder(args);
     for (AccountProjectWatch w : user.getNotificationFilters()) {
       Predicate<ChangeData> f = null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
index 2e2454d..8bf569a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/LabelPredicate.java
@@ -28,6 +28,7 @@
 import com.google.gerrit.server.util.RangeUtil.Range;
 import com.google.inject.Provider;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
@@ -152,7 +153,7 @@
     if (args.accounts == null || args.accounts.isEmpty()) {
       return new EqualsLabelPredicate(args, label, expVal, null);
     } else {
-      List<Predicate<ChangeData>> r = Lists.newArrayList();
+      List<Predicate<ChangeData>> r = new ArrayList<>();
       for (Account.Id a : args.accounts) {
         r.add(new EqualsLabelPredicate(args, label, expVal, a));
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
index 2cabfc5..0cd6978 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ParentProjectPredicate.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.CurrentUser;
@@ -26,6 +25,7 @@
 import com.google.gerrit.server.query.Predicate;
 import com.google.inject.Provider;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -48,7 +48,7 @@
       return Collections.emptyList();
     }
 
-    List<Predicate<ChangeData>> r = Lists.newArrayList();
+    List<Predicate<ChangeData>> r = new ArrayList<>();
     r.add(new ProjectPredicate(projectState.getProject().getName()));
     ListChildProjects children = listChildProjects.get();
     children.setRecursive(true);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
index 23350d2..2fd0177 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/PredicateArgs.java
@@ -14,10 +14,10 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.gerrit.server.query.QueryParseException;
 
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -44,8 +44,8 @@
    * @throws QueryParseException
    */
   PredicateArgs(String args) throws QueryParseException {
-    positional = Lists.newArrayList();
-    keyValue = Maps.newHashMap();
+    positional = new ArrayList<>();
+    keyValue = new HashMap<>();
 
     String[] splitArgs = args.split(",");
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
index dfc0f75e9..88f1911 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryChanges.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.query.change;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -31,6 +30,7 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
@@ -82,7 +82,7 @@
 
   public void addQuery(String query) {
     if (queries == null) {
-      queries = Lists.newArrayList();
+      queries = new ArrayList<>();
     }
     queries.add(query);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/HANA.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/HANA.java
index 523c336..44f1f0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/HANA.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/HANA.java
@@ -52,4 +52,4 @@
     // HANA uses column tables and should not require additional indices
     return ScriptRunner.NOOP;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java
index 2c2051d..7cdf93e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/JDBC.java
@@ -34,4 +34,4 @@
   public String getUrl() {
     return ConfigUtil.getRequired(cfg, "database", "url");
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MaxDb.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MaxDb.java
index 88908bb..9a09746 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MaxDb.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MaxDb.java
@@ -49,4 +49,4 @@
   public ScriptRunner getIndexScript() throws IOException {
     return getScriptRunner("index_maxdb.sql");
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
index 20e1620..0b345e8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/MySql.java
@@ -53,4 +53,4 @@
     // a new MySQL connection is usually very fast.
     return false;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java
index e9ffae5..3e3509e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/PostgreSQL.java
@@ -51,4 +51,4 @@
   public ScriptRunner getIndexScript() throws IOException {
     return getScriptRunner("index_postgres.sql");
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
index e67fba2..ddffd36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/SchemaVersion.java
@@ -26,6 +26,7 @@
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.sql.Statement;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
 
@@ -80,7 +81,7 @@
     migrateData(pending, ui, curr, db);
 
     JdbcSchema s = (JdbcSchema) db;
-    final List<String> pruneList = Lists.newArrayList();
+    final List<String> pruneList = new ArrayList<>();
     s.pruneSchema(new StatementExecutor() {
       @Override
       public void execute(String sql) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
index 36e7e8c..f768c5e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ssh/SshAddressesModule.java
@@ -27,6 +27,7 @@
 
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -81,8 +82,8 @@
     if (want.length > 0) {
       return Arrays.asList(want);
     }
-    List<InetSocketAddress> pub = Lists.newArrayList();
-    List<InetSocketAddress> local = Lists.newArrayList();
+    List<InetSocketAddress> pub = new ArrayList<>();
+    List<InetSocketAddress> local = new ArrayList<>();
 
     for (SocketAddress addr : listen) {
       if (addr instanceof InetSocketAddress) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
index 0b2efd8..2eb8596 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/GuiceRequestScopePropagator.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.util;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.server.RemotePeer;
 import com.google.gerrit.server.config.CanonicalWebUrl;
@@ -29,6 +28,7 @@
 import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.net.SocketAddress;
+import java.util.HashMap;
 import java.util.Map;
 import java.util.concurrent.Callable;
 
@@ -54,7 +54,7 @@
    */
   @Override
   protected <T> Callable<T> wrapImpl(Callable<T> callable) {
-    Map<Key<?>, Object> seedMap = Maps.newHashMap();
+    Map<Key<?>, Object> seedMap = new HashMap<>();
 
     // Request scopes appear to use specific keys in their map, instead of only
     // providers. Add bindings for both the key to the instance directly and the
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
index ad2ab90..907ef70 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SubmoduleSectionParser.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.server.util;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
@@ -27,6 +26,7 @@
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -70,7 +70,7 @@
   }
 
   public Set<SubmoduleSubscription> parseAllSections() {
-    Set<SubmoduleSubscription> parsedSubscriptions = Sets.newHashSet();
+    Set<SubmoduleSubscription> parsedSubscriptions = new HashSet<>();
     for (final String id : bbc.getSubsections("submodule")) {
       final SubmoduleSubscription subscription = parse(id);
       if (subscription != null) {
diff --git a/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java b/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
index 32713d1..a91bead 100644
--- a/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
+++ b/gerrit-server/src/main/java/gerrit/AbstractCommitUserIdentityPredicate.java
@@ -75,4 +75,4 @@
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_author_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_author_3.java
index 59c4c18..9ef68f5 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_author_3.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_author_3.java
@@ -34,4 +34,4 @@
     UserIdentity author = psInfo.getAuthor();
     return exec(engine, author);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_committer_3.java b/gerrit-server/src/main/java/gerrit/PRED_commit_committer_3.java
index 77a668b..d73ed9b 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_committer_3.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_committer_3.java
@@ -34,4 +34,4 @@
     UserIdentity committer = psInfo.getCommitter();
     return exec(engine, committer);
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
index 893c5bc..8fcb98c 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_delta_4.java
@@ -175,4 +175,4 @@
     }
     throw new IllegalArgumentException("ChangeType not recognized");
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java b/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java
index 6e1dc91..6fc1c2f 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_commit_message_1.java
@@ -50,4 +50,4 @@
     }
     return cont;
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm
new file mode 100644
index 0000000..619ba2a
--- /dev/null
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/DeleteReviewer.vm
@@ -0,0 +1,44 @@
+## 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.
+##
+##
+## Template Type:
+## -------------
+## This is a velocity mail template, see: http://velocity.apache.org and the
+## gerrit-docs:config-mail.txt for more info on modifying gerrit mail templates.
+##
+## Template File Names and extensions:
+## ----------------------------------
+## Gerrit will use templates ending in ".vm" but will ignore templates ending
+## in ".vm.example".  If a .vm template does not exist, the default internal
+## gerrit template which is the same as the .vm.example will be used.  If you
+## want to override the default template, copy the .vm.example file to a .vm
+## file and edit it appropriately.
+##
+## This Template:
+## --------------
+## The DeleteReviewer.vm template will determine the contents of the email
+## related to removal of a reviewer (and the reviewer's votes) from reviews.
+## It is a ChangeEmail: see ChangeSubject.vm and ChangeFooter.vm.
+##
+$fromName has removed $email.joinStrings($email.reviewerNames, ', ') from this change.
+
+Change subject: $change.subject
+......................................................................
+
+
+#if ($email.coverLetter)
+$email.coverLetter
+
+#end
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java
index 2304ece..fb046fd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/DestinationListTest.java
@@ -18,7 +18,6 @@
 import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.replay;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 
@@ -27,6 +26,7 @@
 import org.junit.Test;
 
 import java.io.IOException;
+import java.util.HashSet;
 import java.util.Set;
 
 public class DestinationListTest extends TestCase {
@@ -61,7 +61,7 @@
   public static final Branch.NameKey B_BAR = dest(P_SLASH, R_BAR);
   public static final Branch.NameKey B_COMPLEX = dest(P_COMPLEX, R_FOO);
 
-  public static final Set<Branch.NameKey> D_SIMPLE = Sets.newHashSet();
+  public static final Set<Branch.NameKey> D_SIMPLE = new HashSet<>();
   static {
     D_SIMPLE.clear();
     D_SIMPLE.add(B_FOO);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
index 8afc0b7..f57fba4 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java
@@ -21,6 +21,7 @@
 import static java.util.concurrent.TimeUnit.MILLISECONDS;
 import static java.util.concurrent.TimeUnit.SECONDS;
 
+import com.google.common.collect.ImmutableList;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
@@ -117,7 +118,7 @@
         "changeId differs for Changes: {" + id1 + "} != {" + id2 + "}",
         "createdOn differs for Changes:"
             + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}",
-        "lastUpdatedOn differs for Changes:"
+        "effective last updated time differs for Changes:"
             + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}");
   }
 
@@ -155,7 +156,7 @@
     assertDiffs(b1, b2,
         "createdOn differs for Change.Id " + c1.getId() + ":"
             + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:02.0}",
-        "lastUpdatedOn differs for Change.Id " + c1.getId() + ":"
+        "effective last updated time differs for Change.Id " + c1.getId() + ":"
             + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:03.0}");
 
     // One NoteDb, slop is allowed.
@@ -174,22 +175,22 @@
         comments(), NOTE_DB);
     ChangeBundle b3 = new ChangeBundle(c3, messages(), patchSets(), approvals(),
         comments(), REVIEW_DB);
-    String msg = "lastUpdatedOn differs for Change.Id " + c1.getId()
-        + " in NoteDb vs. ReviewDb:"
+    String msg = "effective last updated time differs for Change.Id "
+        + c1.getId() + " in NoteDb vs. ReviewDb:"
         + " {2009-09-30 17:00:01.0} != {2009-09-30 17:00:10.0}";
     assertDiffs(b1, b3, msg);
     assertDiffs(b3, b1, msg);
   }
 
   @Test
-  public void diffChangesIgnoresNullOriginalSubjectInReviewDb()
+  public void diffChangesIgnoresOriginalSubjectInReviewDb()
       throws Exception {
     Change c1 = TestChanges.newChange(
         new Project.NameKey("project"), new Account.Id(100));
-    c1.setCurrentPatchSet(c1.currentPatchSetId(), "Subject", null);
+    c1.setCurrentPatchSet(c1.currentPatchSetId(), "Subject", "Original A");
     Change c2 = clone(c1);
     c2.setCurrentPatchSet(
-        c2.currentPatchSetId(), c1.getSubject(), "Original subject");
+        c2.currentPatchSetId(), c1.getSubject(), "Original B");
 
     // Both ReviewDb, exact match required.
     ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
@@ -198,7 +199,7 @@
         comments(), REVIEW_DB);
     assertDiffs(b1, b2,
         "originalSubject differs for Change.Id " + c1.getId() + ":"
-            + " {null} != {Original subject}");
+            + " {Original A} != {Original B}");
 
     // Both NoteDb, exact match required.
     b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
@@ -207,23 +208,15 @@
         NOTE_DB);
     assertDiffs(b1, b2,
         "originalSubject differs for Change.Id " + c1.getId() + ":"
-            + " {null} != {Original subject}");
+            + " {Original A} != {Original B}");
 
-    // Original subject ignored if ReviewDb has null value.
+    // One ReviewDb, one NoteDb, original subject is ignored.
     b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
         REVIEW_DB);
     b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
         NOTE_DB);
     assertNoDiffs(b1, b2);
-
-    // Exact match still required if NoteDb has null value (not realistic).
-    b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
-        NOTE_DB);
-    b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
-        REVIEW_DB);
-    assertDiffs(b1, b2,
-        "originalSubject differs for Change.Id " + c1.getId() + ":"
-            + " {null} != {Original subject}");
+    assertNoDiffs(b2, b1);
   }
 
   @Test
@@ -273,6 +266,106 @@
   }
 
   @Test
+  public void diffChangesTakesMaxEntityTimestampFromReviewDb()
+      throws Exception {
+    Change c1 = TestChanges.newChange(
+        new Project.NameKey("project"), new Account.Id(100));
+    PatchSetApproval a = new PatchSetApproval(
+        new PatchSetApproval.Key(
+            c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
+        (short) 1,
+        TimeUtil.nowTs());
+
+    Change c2 = clone(c1);
+    c2.setLastUpdatedOn(a.getGranted());
+
+    // Both ReviewDb, exact match required.
+    ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(),
+        approvals(a), comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(),
+        approvals(a), comments(), REVIEW_DB);
+    assertDiffs(b1, b2,
+        "effective last updated time differs for Change.Id " + c1.getId() + ":"
+            + " {2009-09-30 17:00:00.0} != {2009-09-30 17:00:06.0}");
+
+    // NoteDb allows latest timestamp from all entities in bundle.
+    b2 = new ChangeBundle(c2, messages(), patchSets(),
+        approvals(a), comments(), NOTE_DB);
+    assertNoDiffs(b1, b2);
+  }
+
+  @Test
+  public void diffChangesIgnoresChangeTimestampIfAnyOtherEntitiesExist() {
+    Change c1 = TestChanges.newChange(
+        new Project.NameKey("project"), new Account.Id(100));
+    PatchSetApproval a = new PatchSetApproval(
+        new PatchSetApproval.Key(
+            c1.currentPatchSetId(), accountId, new LabelId("Code-Review")),
+        (short) 1,
+        TimeUtil.nowTs());
+    c1.setLastUpdatedOn(a.getGranted());
+
+    Change c2 = clone(c1);
+    c2.setLastUpdatedOn(TimeUtil.nowTs());
+
+    // ReviewDb has later lastUpdatedOn timestamp than NoteDb, allowed since
+    // NoteDb matches the latest timestamp of a non-Change entity.
+    ChangeBundle b1 = new ChangeBundle(c2, messages(), patchSets(),
+        approvals(a), comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c1, messages(), patchSets(),
+        approvals(a), comments(), NOTE_DB);
+    assertThat(b1.getChange().getLastUpdatedOn())
+        .isGreaterThan(b2.getChange().getLastUpdatedOn());
+    assertNoDiffs(b1, b2);
+
+    // Timestamps must actually match if Change is the only entity.
+    b1 = new ChangeBundle(c2, messages(), patchSets(), approvals(), comments(),
+        REVIEW_DB);
+    b2 = new ChangeBundle(c1, messages(), patchSets(), approvals(), comments(),
+        NOTE_DB);
+    assertDiffs(b1, b2,
+        "effective last updated time differs for Change.Id " + c1.getId()
+            + " in NoteDb vs. ReviewDb:"
+            + " {2009-09-30 17:00:06.0} != {2009-09-30 17:00:12.0}");
+  }
+
+  @Test
+  public void diffChangesAllowsReviewDbSubjectToBePrefixOfNoteDbSubject()
+      throws Exception {
+    Change c1 = TestChanges.newChange(
+        new Project.NameKey("project"), new Account.Id(100));
+    Change c2 = clone(c1);
+    c2.setCurrentPatchSet(c1.currentPatchSetId(),
+        c1.getSubject().substring(0, 10), c1.getOriginalSubject());
+    assertThat(c2.getSubject()).isNotEqualTo(c1.getSubject());
+
+    // Both ReviewDb, exact match required.
+    ChangeBundle b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+        comments(), REVIEW_DB);
+    assertDiffs(b1, b2,
+        "subject differs for Change.Id " + c1.getId() + ":"
+            + " {Change subject} != {Change sub}");
+
+    // ReviewDb has shorter subject, allowed.
+    b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+        comments(), NOTE_DB);
+    b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+        comments(), REVIEW_DB);
+    assertNoDiffs(b1, b2);
+
+    // NoteDb has shorter subject, not allowed.
+    b1 = new ChangeBundle(c1, messages(), patchSets(), approvals(),
+        comments(), REVIEW_DB);
+    b2 = new ChangeBundle(c2, messages(), patchSets(), approvals(),
+        comments(), NOTE_DB);
+    assertDiffs(b1, b2,
+        "subject differs for Change.Id " + c1.getId() + ":"
+            + " {Change subject} != {Change sub}");
+  }
+
+  @Test
   public void diffChangeMessageKeySets() throws Exception {
     Change c = TestChanges.newChange(project, accountId);
     int id = c.getId().get();
@@ -282,10 +375,10 @@
     ChangeMessage cm2 = new ChangeMessage(
         new ChangeMessage.Key(c.getId(), "uuid2"),
         accountId, TimeUtil.nowTs(), c.currentPatchSetId());
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
+        comments(), REVIEW_DB);
 
     assertDiffs(b1, b2,
         "ChangeMessage.Key sets differ:"
@@ -300,10 +393,10 @@
         accountId, TimeUtil.nowTs(), c.currentPatchSetId());
     cm1.setMessage("message 1");
     ChangeMessage cm2 = clone(cm1);
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
+        comments(), REVIEW_DB);
 
     assertNoDiffs(b1, b2);
 
@@ -324,20 +417,20 @@
     ChangeMessage cm2 = clone(cm1);
     cm2.getKey().set("uuid2");
 
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
+        comments(), REVIEW_DB);
     // Both are ReviewDb, exact UUID match is required.
     assertDiffs(b1, b2,
         "ChangeMessage.Key sets differ:"
             + " [" + id + ",uuid1] only in A; [" + id + ",uuid2] only in B");
 
     // One NoteDb, UUIDs are ignored.
-    b1 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), REVIEW_DB);
-    b2 = new ChangeBundle(c, messages(cm2), patchSets(), approvals(),
-        comments(), NOTE_DB);
+    b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        REVIEW_DB);
+    b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
+        NOTE_DB);
     assertNoDiffs(b1, b2);
   }
 
@@ -355,31 +448,25 @@
     cm1.setMessage("message 2");
 
     // Both ReviewDb: Uses same keySet diff as other types.
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm2), patchSets(),
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm2), latest(c),
         approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
     assertDiffs(b1, b2,
         "ChangeMessage.Key sets differ: [" + id
         + ",uuid2] only in A; [] only in B");
 
     // One NoteDb: UUIDs in keys can't be used for comparison, just diff counts.
-    b1 = new ChangeBundle(c, messages(cm1, cm2), patchSets(), approvals(),
+    b1 = new ChangeBundle(c, messages(cm1, cm2), latest(c), approvals(),
         comments(), REVIEW_DB);
-    b2 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), NOTE_DB);
+    b2 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        NOTE_DB);
     assertDiffs(b1, b2,
-        "Differing numbers of ChangeMessages for Change.Id " + id + ":\n"
-        + "ChangeMessage{key=" + id + ",uuid1, author=100,"
-        + " writtenOn=2009-09-30 17:00:06.0, patchset=" + id + ",1, tag=null,"
-        + " message=[message 2]}\n"
-        + "ChangeMessage{key=" + id + ",uuid2, author=100,"
-        + " writtenOn=2009-09-30 17:00:12.0, patchset=" + id + ",1, tag=null,"
-        + " message=[null]}\n"
-        + "--- vs. ---\n"
-        + "ChangeMessage{key=" + id + ",uuid1, author=100,"
-        + " writtenOn=2009-09-30 17:00:06.0, patchset=" + id + ",1, tag=null,"
-        + " message=[message 2]}");
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm2);
+    assertDiffs(b2, b1,
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in B:\n  " + cm2);
   }
 
   @Test
@@ -395,13 +482,21 @@
     ChangeMessage cm3 = clone(cm1);
     cm3.getKey().set("uuid2"); // Differs only in UUID.
 
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm3), patchSets(),
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1, cm3), latest(c),
         approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2, cm3), patchSets(),
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2, cm3), latest(c),
         approvals(), comments(), NOTE_DB);
+    // Implementation happens to pair up cm1 in b1 with cm3 in b2 because it
+    // depends on iteration order and doesn't care about UUIDs. The important
+    // thing is that there's some diff.
     assertDiffs(b1, b2,
-        "message differs for ChangeMessage on " + id + " at index 1:"
-        + " {message 1} != {message 2}");
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm3 + "\n"
+            + "Only in B:\n  " + cm2);
+    assertDiffs(b2, b1,
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm2 + "\n"
+            + "Only in B:\n  " + cm3);
   }
 
   @Test
@@ -415,19 +510,19 @@
     cm2.setWrittenOn(TimeUtil.nowTs());
 
     // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
+        comments(), REVIEW_DB);
     assertDiffs(b1, b2,
         "writtenOn differs for ChangeMessage.Key " + c.getId() + ",uuid1:"
             + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
 
     // One NoteDb, slop is allowed.
-    b1 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), NOTE_DB);
-    b2 = new ChangeBundle(c, messages(cm2), patchSets(), approvals(),
-        comments(), REVIEW_DB);
+    b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        NOTE_DB);
+    b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
+        REVIEW_DB);
     assertNoDiffs(b1, b2);
     assertNoDiffs(b2, b1);
 
@@ -435,15 +530,19 @@
     superWindowResolution();
     ChangeMessage cm3 = clone(cm1);
     cm3.setWrittenOn(TimeUtil.nowTs());
-    b1 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), NOTE_DB);
-    ChangeBundle b3 = new ChangeBundle(c, messages(cm3), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    String msg = "writtenOn differs for ChangeMessage on " + c.getId() +
-        " at index 0 in NoteDb vs. ReviewDb:"
-        + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
-    assertDiffs(b1, b3, msg);
-    assertDiffs(b3, b1, msg);
+    b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        NOTE_DB);
+    ChangeBundle b3 = new ChangeBundle(c, messages(cm3), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    int id = c.getId().get();
+    assertDiffs(b1, b3,
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm1 + "\n"
+            + "Only in B:\n  " + cm3);
+    assertDiffs(b3, b1,
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm3 + "\n"
+            + "Only in B:\n  " + cm1);
   }
 
   @Test
@@ -458,10 +557,10 @@
     ChangeMessage cm2 = clone(cm1);
     cm2.setPatchSetId(null);
 
-    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), patchSets(),
-        approvals(), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), patchSets(),
-        approvals(), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(),
+        comments(), REVIEW_DB);
 
     // Both are ReviewDb, exact patch set ID match is required.
     assertDiffs(b1, b2,
@@ -469,20 +568,25 @@
             + " {" + id + ",1} != {null}");
 
     // Null patch set ID on ReviewDb is ignored.
-    b1 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), NOTE_DB);
-    b2 = new ChangeBundle(c, messages(cm2), patchSets(), approvals(),
-        comments(), REVIEW_DB);
+    b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        NOTE_DB);
+    b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
+        REVIEW_DB);
     assertNoDiffs(b1, b2);
 
     // Null patch set ID on NoteDb is not ignored (but is not realistic).
-    b1 = new ChangeBundle(c, messages(cm1), patchSets(), approvals(),
-        comments(), REVIEW_DB);
-    b2 = new ChangeBundle(c, messages(cm2), patchSets(), approvals(),
-        comments(), NOTE_DB);
+    b1 = new ChangeBundle(c, messages(cm1), latest(c), approvals(), comments(),
+        REVIEW_DB);
+    b2 = new ChangeBundle(c, messages(cm2), latest(c), approvals(), comments(),
+        NOTE_DB);
     assertDiffs(b1, b2,
-        "patchset differs for ChangeMessage on " + id + " at index 0:"
-            + " {" + id + ",1} != {null}");
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm1 + "\n"
+            + "Only in B:\n  " + cm2);
+    assertDiffs(b2, b1,
+        "ChangeMessages differ for Change.Id " + id + "\n"
+            + "Only in A:\n  " + cm2 + "\n"
+            + "Only in B:\n  " + cm1);
   }
 
   @Test
@@ -616,10 +720,10 @@
         (short) 1,
         TimeUtil.nowTs());
 
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a1), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a2), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
+        comments(), REVIEW_DB);
 
     assertDiffs(b1, b2,
         "PatchSetApproval.Key sets differ:"
@@ -636,10 +740,10 @@
         (short) 1,
         TimeUtil.nowTs());
     PatchSetApproval a2 = clone(a1);
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a1), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a2), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
+        comments(), REVIEW_DB);
 
     assertNoDiffs(b1, b2);
 
@@ -663,30 +767,30 @@
     a2.setGranted(TimeUtil.nowTs());
 
     // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a1), comments(), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a2), comments(), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1),
+        comments(), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2),
+        comments(), REVIEW_DB);
     assertDiffs(b1, b2,
         "granted differs for PatchSetApproval.Key "
             + c.getId() + "%2C1,100,Code-Review:"
             + " {2009-09-30 17:00:07.0} != {2009-09-30 17:00:08.0}");
 
     // One NoteDb, slop is allowed.
-    b1 = new ChangeBundle(c, messages(), patchSets(), approvals(a1),
-        comments(), NOTE_DB);
-    b2 = new ChangeBundle(c, messages(), patchSets(), approvals(a2),
-        comments(), REVIEW_DB);
+    b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
+        NOTE_DB);
+    b2 = new ChangeBundle(c, messages(), latest(c), approvals(a2), comments(),
+        REVIEW_DB);
     assertNoDiffs(b1, b2);
 
     // But not too much slop.
     superWindowResolution();
     PatchSetApproval a3 = clone(a1);
     a3.setGranted(TimeUtil.nowTs());
-    b1 = new ChangeBundle(c, messages(), patchSets(), approvals(a1),
-        comments(), NOTE_DB);
-    ChangeBundle b3 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(a3), comments(), REVIEW_DB);
+    b1 = new ChangeBundle(c, messages(), latest(c), approvals(a1), comments(),
+        NOTE_DB);
+    ChangeBundle b3 = new ChangeBundle(c, messages(), latest(c), approvals(a3),
+        comments(), REVIEW_DB);
     String msg = "granted differs for PatchSetApproval.Key "
         + c.getId() + "%2C1,100,Code-Review in NoteDb vs. ReviewDb:"
         + " {2009-09-30 17:00:07.0} != {2009-09-30 17:00:15.0}";
@@ -707,10 +811,10 @@
             new Patch.Key(c.currentPatchSetId(), "filename2"), "uuid2"),
         5, accountId, null, TimeUtil.nowTs());
 
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c1), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c2), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c1), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c2), REVIEW_DB);
 
     assertDiffs(b1, b2,
         "PatchLineComment.Key sets differ:"
@@ -726,10 +830,10 @@
             new Patch.Key(c.currentPatchSetId(), "filename"), "uuid"),
         5, accountId, null, TimeUtil.nowTs());
     PatchLineComment c2 = clone(c1);
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c1), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c2), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c1), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c2), REVIEW_DB);
 
     assertNoDiffs(b1, b2);
 
@@ -752,36 +856,56 @@
     c2.setWrittenOn(TimeUtil.nowTs());
 
     // Both are ReviewDb, exact timestamp match is required.
-    ChangeBundle b1 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c1), REVIEW_DB);
-    ChangeBundle b2 = new ChangeBundle(c, messages(), patchSets(),
-        approvals(), comments(c2), REVIEW_DB);
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c1), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c2), REVIEW_DB);
     assertDiffs(b1, b2,
         "writtenOn differs for PatchLineComment.Key "
             + c.getId() + ",1,filename,uuid:"
             + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:03.0}");
 
     // One NoteDb, slop is allowed.
-    b1 = new ChangeBundle(c, messages(), patchSets(), approvals(),
-        comments(c1), NOTE_DB);
-    b2 = new ChangeBundle(c, messages(), patchSets(), approvals(),
-        comments(c2), REVIEW_DB);
+    b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1),
+        NOTE_DB);
+    b2 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c2),
+        REVIEW_DB);
     assertNoDiffs(b1, b2);
 
     // But not too much slop.
     superWindowResolution();
     PatchLineComment c3 = clone(c1);
     c3.setWrittenOn(TimeUtil.nowTs());
-    b1 = new ChangeBundle(c, messages(), patchSets(), approvals(), comments(c1),
+    b1 = new ChangeBundle(c, messages(), latest(c), approvals(), comments(c1),
         NOTE_DB);
-    ChangeBundle b3 = new ChangeBundle(c, messages(), patchSets(), approvals(),
+    ChangeBundle b3 = new ChangeBundle(c, messages(), latest(c), approvals(),
         comments(c3), REVIEW_DB);
     String msg = "writtenOn differs for PatchLineComment.Key " + c.getId()
         + ",1,filename,uuid in NoteDb vs. ReviewDb:"
         + " {2009-09-30 17:00:02.0} != {2009-09-30 17:00:10.0}";
     assertDiffs(b1, b3, msg);
     assertDiffs(b3, b1, msg);
-}
+  }
+
+  @Test
+  public void diffPatchLineCommentsIgnoresCommentsOnInvalidPatchSet()
+      throws Exception {
+    Change c = TestChanges.newChange(project, accountId);
+    PatchLineComment c1 = new PatchLineComment(
+        new PatchLineComment.Key(
+            new Patch.Key(c.currentPatchSetId(), "filename1"), "uuid1"),
+        5, accountId, null, TimeUtil.nowTs());
+    PatchLineComment c2 = new PatchLineComment(
+        new PatchLineComment.Key(
+            new Patch.Key(new PatchSet.Id(c.getId(), 0), "filename2"), "uuid2"),
+        5, accountId, null, TimeUtil.nowTs());
+
+    ChangeBundle b1 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c1, c2), REVIEW_DB);
+    ChangeBundle b2 = new ChangeBundle(c, messages(), latest(c), approvals(),
+        comments(c1), REVIEW_DB);
+    assertNoDiffs(b1, b2);
+  }
 
   private static void assertNoDiffs(ChangeBundle a, ChangeBundle b) {
     assertThat(a.differencesFrom(b)).isEmpty();
@@ -811,6 +935,10 @@
     return Arrays.asList(ents);
   }
 
+  private static List<PatchSet> latest(Change c) {
+    return ImmutableList.of(new PatchSet(c.currentPatchSetId()));
+  }
+
   private static List<PatchSetApproval> approvals(PatchSetApproval... ents) {
     return Arrays.asList(ents);
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
index d4d7d19..16cc3b9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesParserTest.java
@@ -17,7 +17,6 @@
 import static org.junit.Assert.fail;
 
 import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
@@ -65,6 +64,11 @@
         + "Patch-set: 1\n",
         new PersonIdent("Change Owner", "x@gerrit",
           serverIdent.getWhen(), serverIdent.getTimeZone())));
+    assertParseFails(writeCommit("Update change\n"
+        + "\n"
+        + "Patch-set: 1\n",
+        new PersonIdent("Change\n\u1234<Owner>", "\n\nx<@>\u0002gerrit",
+          serverIdent.getWhen(), serverIdent.getTimeZone())));
   }
 
   @Test
@@ -477,9 +481,7 @@
   }
 
   private void assertParseSucceeds(RevCommit commit) throws Exception {
-    try (ChangeNotesParser parser = newParser(commit)) {
-      parser.parseAll();
-    }
+    newParser(commit).parseAll();
   }
 
   private void assertParseFails(String body) throws Exception {
@@ -487,8 +489,8 @@
   }
 
   private void assertParseFails(RevCommit commit) throws Exception {
-    try (ChangeNotesParser parser = newParser(commit)) {
-      parser.parseAll();
+    try {
+      newParser(commit).parseAll();
       fail("Expected parse to fail:\n" + commit.getFullMessage());
     } catch (ConfigInvalidException e) {
       // Expected
@@ -496,8 +498,7 @@
   }
 
   private ChangeNotesParser newParser(ObjectId tip) throws Exception {
-    Change c = newChange();
-    return new ChangeNotesParser(c.getProject(), c.getId(), tip, walk,
-        repoManager, noteUtil, args.metrics);
+    return new ChangeNotesParser(
+        newChange().getId(), tip, walk, noteUtil, args.metrics);
   }
 }
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 a796f9c..f1b5c9a 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
@@ -15,8 +15,8 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
-import static com.google.gerrit.server.notedb.ChangeNoteUtil.changeRefName;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.CC;
 import static com.google.gerrit.server.notedb.ReviewerStateInternal.REVIEWER;
 import static com.google.gerrit.testutil.TestChanges.incrementPatchSet;
@@ -45,6 +45,7 @@
 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.server.IdentifiedUser;
 import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -525,7 +526,7 @@
   @Test
   public void emptyChangeUpdate() throws Exception {
     Change c = newChange();
-    Ref initial = repo.exactRef(changeRefName(c.getId()));
+    Ref initial = repo.exactRef(changeMetaRef(c.getId()));
     assertThat(initial).isNotNull();
 
     // Empty update doesn't create a new commit.
@@ -533,7 +534,7 @@
     update.commit();
     assertThat(update.getResult()).isNull();
 
-    Ref updated = repo.exactRef(changeRefName(c.getId()));
+    Ref updated = repo.exactRef(changeMetaRef(c.getId()));
     assertThat(updated.getObjectId()).isEqualTo(initial.getObjectId());
   }
 
@@ -728,18 +729,17 @@
     Timestamp ts7 = newNotes(c).getChange().getLastUpdatedOn();
     assertThat(ts7).isGreaterThan(ts6);
 
-    // Updates that should not touch the timestamp.
     update = newUpdate(c, changeOwner);
     update.putReviewer(otherUser.getAccountId(), ReviewerStateInternal.REVIEWER);
     update.commit();
     Timestamp ts8 = newNotes(c).getChange().getLastUpdatedOn();
-    assertThat(ts8).isEqualTo(ts7);
+    assertThat(ts8).isGreaterThan(ts7);
 
     update = newUpdate(c, changeOwner);
     update.setGroups(ImmutableList.of("a", "b"));
     update.commit();
     Timestamp ts9 = newNotes(c).getChange().getLastUpdatedOn();
-    assertThat(ts9).isEqualTo(ts8);
+    assertThat(ts9).isGreaterThan(ts8);
 
     // Finish off by merging the change.
     update = newUpdate(c, changeOwner);
@@ -1018,27 +1018,23 @@
     assertThat(commitWithComments).isNotNull();
 
     try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
-      try (ChangeNotesParser notesWithComments = new ChangeNotesParser(
-          project, c.getId(), commitWithComments.copy(), rw, repoManager,
-          noteUtil, args.metrics)) {
-        notesWithComments.parseAll();
-        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
-            notesWithComments.buildApprovals();
-        assertThat(approvals1).isEmpty();
-        assertThat(notesWithComments.comments).hasSize(1);
-      }
+      ChangeNotesParser notesWithComments = new ChangeNotesParser(
+          c.getId(), commitWithComments.copy(), rw, noteUtil, args.metrics);
+      notesWithComments.parseAll();
+      ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals1 =
+          notesWithComments.buildApprovals();
+      assertThat(approvals1).isEmpty();
+      assertThat(notesWithComments.comments).hasSize(1);
     }
 
     try (ChangeNotesRevWalk rw = ChangeNotesCommit.newRevWalk(repo)) {
-      try (ChangeNotesParser notesWithApprovals = new ChangeNotesParser(project,
-          c.getId(), commitWithApprovals.copy(), rw, repoManager,
-          noteUtil, args.metrics)) {
-        notesWithApprovals.parseAll();
-        ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
-            notesWithApprovals.buildApprovals();
-        assertThat(approvals2).hasSize(1);
-        assertThat(notesWithApprovals.comments).hasSize(1);
-      }
+      ChangeNotesParser notesWithApprovals = new ChangeNotesParser(c.getId(),
+          commitWithApprovals.copy(), rw, noteUtil, args.metrics);
+      notesWithApprovals.parseAll();
+      ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals2 =
+          notesWithApprovals.buildApprovals();
+      assertThat(approvals2).hasSize(1);
+      assertThat(notesWithApprovals.comments).hasSize(1);
     }
   }
 
@@ -1490,6 +1486,58 @@
   }
 
   @Test
+  public void patchLineCommentNotesFormatWeirdUser() throws Exception {
+    Account account = new Account(new Account.Id(3), TimeUtil.nowTs());
+    account.setFullName("Weird\n\u0002<User>\n");
+    account.setPreferredEmail(" we\r\nird@ex>ample<.com");
+    accountCache.put(account);
+    IdentifiedUser user = userFactory.create(account.getId());
+
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, user);
+    String uuid = "uuid";
+    CommentRange range = new CommentRange(1, 1, 2, 1);
+    Timestamp time = TimeUtil.nowTs();
+    PatchSet.Id psId = c.currentPatchSetId();
+
+    PatchLineComment comment = newPublishedComment(psId, "file1",
+        uuid, range, range.getEndLine(), user, null, time, "comment",
+        (short) 1, "abcd1234abcd1234abcd1234abcd1234abcd1234");
+    update.setPatchSetId(psId);
+    update.putComment(comment);
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+
+    try (RevWalk walk = new RevWalk(repo)) {
+      ArrayList<Note> notesInTree =
+          Lists.newArrayList(notes.revisionNoteMap.noteMap.iterator());
+      Note note = Iterables.getOnlyElement(notesInTree);
+
+      byte[] bytes =
+          walk.getObjectReader().open(
+              note.getData(), Constants.OBJ_BLOB).getBytes();
+      String noteString = new String(bytes, UTF_8);
+      String timeStr = ChangeNoteUtil.formatTime(serverIdent, time);
+      assertThat(noteString).isEqualTo(
+          "Revision: abcd1234abcd1234abcd1234abcd1234abcd1234\n"
+          + "Patch-set: 1\n"
+          + "File: file1\n"
+          + "\n"
+          + "1:1-2:1\n"
+          + timeStr + "\n"
+          + "Author: Weird\u0002User <3@gerrit>\n"
+          + "UUID: uuid\n"
+          + "Bytes: 7\n"
+          + "comment\n"
+          + "\n");
+    }
+
+    assertThat(notes.getComments())
+        .isEqualTo(ImmutableMultimap.of(comment.getRevId(), comment));
+  }
+
+  @Test
   public void patchLineCommentMultipleOnePatchsetOneFileBothSides()
       throws Exception {
     Change c = newChange();
@@ -1878,8 +1926,8 @@
     update.putComment(comment);
     update.commit();
 
-    assertThat(repo.exactRef(changeRefName(c.getId()))).isNotNull();
-    String draftRef = refsDraftComments(otherUser.getAccountId(), c.getId());
+    assertThat(repo.exactRef(changeMetaRef(c.getId()))).isNotNull();
+    String draftRef = refsDraftComments(c.getId(), otherUser.getAccountId());
     assertThat(exactRefAllUsers(draftRef)).isNull();
   }
 
@@ -1901,7 +1949,7 @@
     update.putComment(draft);
     update.commit();
 
-    String draftRef = refsDraftComments(otherUser.getAccountId(), c.getId());
+    String draftRef = refsDraftComments(c.getId(), otherUser.getAccountId());
     ObjectId old = exactRefAllUsers(draftRef);
     assertThat(old).isNotNull();
 
@@ -2075,7 +2123,7 @@
     update.putComment(comment2);
     update.commit();
 
-    String refName = refsDraftComments(otherUserId, c.getId());
+    String refName = refsDraftComments(c.getId(), otherUserId);
     ObjectId oldDraftId = exactRefAllUsers(refName);
 
     update = newUpdate(c, otherUser);
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 97776f1..61d7590 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
@@ -703,7 +703,7 @@
   @Test
   public void start() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
-    List<Change> changes = Lists.newArrayList();
+    List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 2; i++) {
       changes.add(insert(repo, newChange(repo)));
     }
@@ -717,7 +717,7 @@
   @Test
   public void startWithLimit() throws Exception {
     TestRepository<Repo> repo = createProject("repo");
-    List<Change> changes = Lists.newArrayList();
+    List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 3; i++) {
       changes.add(insert(repo, newChange(repo)));
     }
@@ -747,8 +747,8 @@
   public void updateOrder() throws Exception {
     resetTimeWithClockStep(2, MINUTES);
     TestRepository<Repo> repo = createProject("repo");
-    List<ChangeInserter> inserters = Lists.newArrayList();
-    List<Change> changes = Lists.newArrayList();
+    List<ChangeInserter> inserters = new ArrayList<>();
+    List<Change> changes = new ArrayList<>();
     for (int i = 0; i < 5; i++) {
       inserters.add(newChange(repo));
       changes.add(insert(repo, inserters.get(i)));
@@ -1071,7 +1071,7 @@
 
   @Test
   public void byHashtagWithNoteDb() throws Exception {
-    assume().that(notesMigration.enabled()).isTrue();
+    assume().that(notesMigration.readChanges()).isTrue();
     List<Change> changes = setUpHashtagChanges();
     assertQuery("hashtag:foo", changes.get(1), changes.get(0));
     assertQuery("hashtag:bar", changes.get(1));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 87b5322..cd6e825 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -21,7 +21,6 @@
 
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.LabelValue;
@@ -44,6 +43,7 @@
 import java.io.IOException;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
@@ -111,7 +111,7 @@
 
   @Test
   public void testCreateSchema_LabelTypes() throws Exception {
-    List<String> labels = Lists.newArrayList();
+    List<String> labels = new ArrayList<>();
     for (LabelType label : getLabelTypes().getLabelTypes()) {
       labels.add(label.getName());
     }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
index 56e460d..69a8487 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/tools/hooks/HookTestCase.java
@@ -52,8 +52,6 @@
 
 import static com.google.common.truth.Truth.assert_;
 
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.io.ByteStreams;
 
 import org.eclipse.jgit.junit.LocalDiskRepositoryTestCase;
@@ -67,14 +65,16 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.net.URL;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Map;
+import java.util.TreeMap;
 
 @Ignore
 public abstract class HookTestCase extends LocalDiskRepositoryTestCase {
   protected Repository repository;
-  private final Map<String, File> hooks = Maps.newTreeMap();
-  private final List<File> cleanup = Lists.newArrayList();
+  private final Map<String, File> hooks = new TreeMap<>();
+  private final List<File> cleanup = new ArrayList<>();
 
   @Override
   @Before
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
index 0ea74a5..ba62cf7 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/util/SubmoduleSectionParserTest.java
@@ -21,7 +21,6 @@
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 
-import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
@@ -37,6 +36,7 @@
 import java.net.URI;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
@@ -88,7 +88,7 @@
         new Branch.NameKey(new Project.NameKey("super-project"),
             "refs/heads/master");
 
-    Set<SubmoduleSubscription> expectedSubscriptions = Sets.newHashSet();
+    Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
     expectedSubscriptions
         .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
             new Project.NameKey("a"), "refs/heads/master"), "a"));
@@ -135,7 +135,7 @@
         new Branch.NameKey(new Project.NameKey("super-project"),
             "refs/heads/master");
 
-    Set<SubmoduleSubscription> expectedSubscriptions = Sets.newHashSet();
+    Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
     expectedSubscriptions
         .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
             new Project.NameKey("a"), "refs/heads/master"), "a"));
@@ -204,7 +204,7 @@
         new Branch.NameKey(new Project.NameKey("super-project"),
             "refs/heads/master");
 
-    Set<SubmoduleSubscription> expectedSubscriptions = Sets.newHashSet();
+    Set<SubmoduleSubscription> expectedSubscriptions = new HashSet<>();
     expectedSubscriptions
         .add(new SubmoduleSubscription(superBranchNameKey, new Branch.NameKey(
             new Project.NameKey("a/b"), "refs/heads/master"), "a/b"));
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
index 011d69d..d9841a3 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FakeAccountCache.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.testutil;
 
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountExternalId;
@@ -23,6 +22,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.account.AccountState;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /** Fake implementation of {@link AccountCache} for testing. */
@@ -31,8 +31,8 @@
   private final Map<String, AccountState> byUsername;
 
   public FakeAccountCache() {
-    byId = Maps.newHashMap();
-    byUsername = Maps.newHashMap();
+    byId = new HashMap<>();
+    byUsername = new HashMap<>();
   }
 
   @Override
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
index eee3a8d..bbcb6a9 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/FilesystemLoggingMockingTestCase.java
@@ -15,17 +15,17 @@
 package com.google.gerrit.testutil;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Lists;
 
 import org.eclipse.jgit.util.FileUtils;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Collection;
 
 public abstract class FilesystemLoggingMockingTestCase extends LoggingMockingTestCase {
 
-  private Collection<File> toCleanup = Lists.newArrayList();
+  private Collection<File> toCleanup = new ArrayList<>();
 
   /**
    * Asserts that a given file exists.
@@ -175,4 +175,4 @@
     cleanupCreatedFiles();
     super.tearDown();
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java
index 25a6534..7edfa1a 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryH2Type.java
@@ -27,4 +27,4 @@
     // not used
     throw new UnsupportedOperationException();
   }
-}
\ No newline at end of file
+}
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index b8fa592..74fa22d 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.testutil;
 
 import com.google.common.collect.ImmutableSortedSet;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -26,6 +25,7 @@
 import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription;
 import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
 
+import java.util.HashMap;
 import java.util.Map;
 import java.util.SortedSet;
 
@@ -64,7 +64,7 @@
     }
   }
 
-  private Map<String, Repo> repos = Maps.newHashMap();
+  private Map<String, Repo> repos = new HashMap<>();
 
   @Override
   public synchronized Repo openRepository(Project.NameKey name)
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java
index 770dfcd..d7140ec 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/LoggingMockingTestCase.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.testutil;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.testutil.log.LogUtil;
 
 import org.apache.log4j.LogManager;
@@ -22,6 +21,7 @@
 import org.apache.log4j.spi.LoggingEvent;
 import org.junit.After;
 
+import java.util.ArrayList;
 import java.util.Iterator;
 
 /**
@@ -102,7 +102,7 @@
   @Override
   public void setUp() throws Exception {
     super.setUp();
-    loggedEvents = Lists.newArrayList();
+    loggedEvents = new ArrayList<>();
 
     // The logger we're interested is class name without the trailing "Test".
     // While this is not the most general approach it is sufficient for now,
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 6fc5ef9..66eed8b 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
@@ -20,12 +20,12 @@
 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;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.PatchLineCommentsUtil;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.notedb.ChangeBundle;
-import com.google.gerrit.server.notedb.ChangeNoteUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeRebuilder;
 import com.google.gerrit.server.schema.DisabledChangesReviewDbWrapper;
@@ -118,8 +118,7 @@
   public void assertNoChangeRef(Project.NameKey project, Change.Id changeId)
       throws Exception {
     try (Repository repo = repoManager.openRepository(project)) {
-      assertThat(repo.exactRef(ChangeNoteUtil.changeRefName(changeId)))
-          .isNull();
+      assertThat(repo.exactRef(RefNames.changeMetaRef(changeId))).isNull();
     }
   }
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
index c4727f5..bbad2be 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/AliasCommand.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.sshd;
 
 import com.google.common.base.Throwables;
-import com.google.common.collect.Lists;
 import com.google.common.util.concurrent.Atomics;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.server.CurrentUser;
@@ -118,7 +117,7 @@
   }
 
   private static LinkedList<String> chain(CommandName command) {
-    LinkedList<String> chain = Lists.newLinkedList();
+    LinkedList<String> chain = new LinkedList<>();
     while (command != null) {
       chain.addFirst(command.value());
       command = Commands.parentOf(command);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
index 8c43438a..8b468a7 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshAutoRegisterModuleGenerator.java
@@ -18,7 +18,6 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.LinkedListMultimap;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.server.plugins.InvalidPluginException;
@@ -30,12 +29,13 @@
 import org.apache.sshd.server.Command;
 
 import java.lang.annotation.Annotation;
+import java.util.HashMap;
 import java.util.Map;
 
 class SshAutoRegisterModuleGenerator
     extends AbstractModule
     implements ModuleGenerator {
-  private final Map<String, Class<Command>> commands = Maps.newHashMap();
+  private final Map<String, Class<Command>> commands = new HashMap<>();
   private final Multimap<TypeLiteral<?>, Class<?>> listeners = LinkedListMultimap.create();
   private CommandName command;
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
index 5fd8932..5121717 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshDaemon.java
@@ -21,7 +21,6 @@
 import com.google.common.base.Strings;
 import com.google.common.base.Supplier;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.Version;
 import com.google.gerrit.extensions.events.LifecycleListener;
 import com.google.gerrit.metrics.Counter0;
@@ -625,8 +624,7 @@
   }
 
   private void initCompression(boolean enableCompression) {
-    List<NamedFactory<Compression>> compressionFactories =
-        Lists.newArrayList();
+    List<NamedFactory<Compression>> compressionFactories = new ArrayList<>();
 
     // Always support no compression over SSHD.
     compressionFactories.add(BuiltinCompressions.none);
@@ -661,7 +659,7 @@
   private void initUserAuth(final PublickeyAuthenticator pubkey,
       final GSSAuthenticator kerberosAuthenticator,
       String kerberosKeytab, String kerberosPrincipal) {
-    List<NamedFactory<UserAuth>> authFactories = Lists.newArrayList();
+    List<NamedFactory<UserAuth>> authFactories = new ArrayList<>();
     if (kerberosKeytab != null) {
       authFactories.add(UserAuthGSSFactory.INSTANCE);
       log.info("Enabling kerberos with keytab " + kerberosKeytab);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 974b233..3429587 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.extensions.registration.PrivateInternals_DynamicTypes.registerInParentInjectors;
 import static com.google.inject.Scopes.SINGLETON;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.lifecycle.LifecycleModule;
 import com.google.gerrit.server.PeerDaemonUser;
 import com.google.gerrit.server.RemotePeer;
@@ -42,6 +41,7 @@
 import org.eclipse.jgit.lib.Config;
 
 import java.net.SocketAddress;
+import java.util.HashMap;
 import java.util.Map;
 
 /** Configures standard dependencies for {@link SshDaemon}. */
@@ -50,7 +50,7 @@
 
   @Inject
   SshModule(@GerritServerConfig Config cfg) {
-    aliases = Maps.newHashMap();
+    aliases = new HashMap<>();
     for (String name : cfg.getNames("ssh-alias", true)) {
       aliases.put(name, cfg.getString("ssh-alias", null, name));
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
index e3455e3..9616aec 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshScope.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.sshd;
 
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.TimeUtil;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
@@ -32,6 +31,7 @@
 import com.google.inject.Scope;
 import com.google.inject.util.Providers;
 
+import java.util.HashMap;
 import java.util.Map;
 
 /** Guice scopes for state during an SSH connection. */
@@ -44,7 +44,7 @@
 
   class Context implements RequestContext {
     private final RequestCleanup cleanup = new RequestCleanup();
-    private final Map<Key<?>, Object> map = Maps.newHashMap();
+    private final Map<Key<?>, Object> map = new HashMap<>();
     private final SchemaFactory<ReviewDb> schemaFactory;
     private final SshSession session;
     private final String commandLine;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
index 5f6bb05..b4594d4 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminSetParent.java
@@ -17,7 +17,6 @@
 import com.google.common.base.Function;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.gerrit.common.data.GlobalCapability;
 import com.google.gerrit.extensions.annotations.RequiresCapability;
 import com.google.gerrit.extensions.common.ProjectInfo;
@@ -115,7 +114,7 @@
       }
     }
 
-    final List<Project.NameKey> childProjects = Lists.newArrayList();
+    final List<Project.NameKey> childProjects = new ArrayList<>();
     for (final ProjectControl pc : children) {
       childProjects.add(pc.getProject().getNameKey());
     }
@@ -175,7 +174,7 @@
    * that were specified to be excluded from reparenting.
    */
   private List<Project.NameKey> getChildrenForReparenting(final ProjectControl parent) {
-    final List<Project.NameKey> childProjects = Lists.newArrayList();
+    final List<Project.NameKey> childProjects = new ArrayList<>();
     final List<Project.NameKey> excluded =
         new ArrayList<>(excludedChildren.size());
     for (final ProjectControl excludedChild : excludedChildren) {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index a9b6d3f..dad8672 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -17,7 +17,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.base.Strings;
-import com.google.common.collect.Maps;
 import com.google.common.io.CharStreams;
 import com.google.gerrit.common.data.LabelType;
 import com.google.gerrit.common.data.LabelValue;
@@ -51,10 +50,12 @@
 import java.io.IOException;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 @CommandMetaData(name = "review", description = "Apply reviews to one or more patch sets")
 public class ReviewCommand extends SshCommand {
@@ -275,7 +276,7 @@
     review.message = Strings.emptyToNull(changeComment);
     review.tag = Strings.emptyToNull(changeTag);
     review.notify = notify;
-    review.labels = Maps.newTreeMap();
+    review.labels = new TreeMap<>();
     review.drafts = ReviewInput.DraftHandling.PUBLISH;
     review.strictLabels = strictLabels;
     for (ApproveOption ao : optionList) {
@@ -335,7 +336,7 @@
   @Override
   protected void parseCommandLine() throws UnloggedFailure {
     optionList = new ArrayList<>();
-    customLabels = Maps.newHashMap();
+    customLabels = new HashMap<>();
 
     ProjectControl allProjectsControl;
     try {
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
index d1235cc..a7e01c5 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -40,25 +40,26 @@
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
+import java.util.ArrayList;
 import java.util.List;
 
 @CommandMetaData(name = "set-members", description = "Modify members of specific group or number of groups")
 public class SetMembersCommand extends SshCommand {
 
   @Option(name = "--add", aliases = {"-a"}, metaVar = "USER", usage = "users that should be added as group member")
-  private List<Account.Id> accountsToAdd = Lists.newArrayList();
+  private List<Account.Id> accountsToAdd = new ArrayList<>();
 
   @Option(name = "--remove", aliases = {"-r"}, metaVar = "USER", usage = "users that should be removed from the group")
-  private List<Account.Id> accountsToRemove = Lists.newArrayList();
+  private List<Account.Id> accountsToRemove = new ArrayList<>();
 
   @Option(name = "--include", aliases = {"-i"}, metaVar = "GROUP", usage = "group that should be included as group member")
-  private List<AccountGroup.UUID> groupsToInclude = Lists.newArrayList();
+  private List<AccountGroup.UUID> groupsToInclude = new ArrayList<>();
 
   @Option(name = "--exclude", aliases = {"-e"}, metaVar = "GROUP", usage = "group that should be excluded from the group")
-  private List<AccountGroup.UUID> groupsToRemove = Lists.newArrayList();
+  private List<AccountGroup.UUID> groupsToRemove = new ArrayList<>();
 
   @Argument(index = 0, required = true, multiValued = true, metaVar = "GROUP", usage = "groups to modify")
-  private List<AccountGroup.UUID> groups = Lists.newArrayList();
+  private List<AccountGroup.UUID> groups = new ArrayList<>();
 
   @Inject
   private AddMembers addMembers;
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
index 39efb26..b420a5f 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Upload.java
@@ -68,10 +68,8 @@
     }
 
     final UploadPack up = new UploadPack(repo);
-    if (!projectControl.allRefsAreVisible()) {
-      up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo,
-          projectControl, db, true));
-    }
+    up.setAdvertiseRefsHook(new VisibleRefFilter(tagCache, changeCache, repo,
+        projectControl, db, true));
     up.setPackConfig(config.getPackConfig());
     up.setTimeout(config.getTimeout());
     up.setPostUploadHook(uploadMetrics);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
index 8bfe4e1..5e2480e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/UploadArchive.java
@@ -17,7 +17,6 @@
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.ArchiveFormat;
 import com.google.gerrit.server.change.GetArchive;
@@ -38,6 +37,7 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
@@ -113,7 +113,7 @@
    */
   protected void readArguments() throws IOException, Failure {
     String argCmd = "argument ";
-    List<String> args = Lists.newArrayList();
+    List<String> args = new ArrayList<>();
 
     // Read arguments in Pkt-Line format
     PacketLineIn packetIn = new PacketLineIn(in);
diff --git a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
index e70ab72..b5888b50 100644
--- a/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
+++ b/gerrit-util-cli/src/main/java/com/google/gerrit/util/cli/CmdLineParser.java
@@ -37,7 +37,6 @@
 import com.google.common.base.Strings;
 import com.google.common.collect.LinkedHashMultimap;
 import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
@@ -59,6 +58,7 @@
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.ResourceBundle;
@@ -282,7 +282,7 @@
 
   @SuppressWarnings("rawtypes")
   private static Map<String, OptionHandler> index(List<OptionHandler> in) {
-    Map<String, OptionHandler> m = Maps.newHashMap();
+    Map<String, OptionHandler> m = new HashMap<>();
     for (OptionHandler handler : in) {
       if (handler.option instanceof NamedOptionDef) {
         NamedOptionDef def = (NamedOptionDef) handler.option;
@@ -352,7 +352,7 @@
     private void ensureOptionsInitialized() {
       if (optionsList == null) {
         help = new HelpOption();
-        optionsList = Lists.newArrayList();
+        optionsList = new ArrayList<>();
         addOption(help, help);
       }
     }
diff --git a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
index fe4a864..d35f31d 100644
--- a/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
+++ b/gerrit-war/src/main/java/com/google/gerrit/httpd/UnzippedDistribution.java
@@ -17,7 +17,6 @@
 import static com.google.gerrit.pgm.init.InitPlugins.JAR;
 import static com.google.gerrit.pgm.init.InitPlugins.PLUGIN_DIR;
 
-import com.google.common.collect.Lists;
 import com.google.gerrit.pgm.init.PluginsDistribution;
 import com.google.inject.Singleton;
 
@@ -26,6 +25,7 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.List;
 
 import javax.servlet.ServletContext;
@@ -57,7 +57,7 @@
 
   @Override
   public List<String> listPluginNames() throws FileNotFoundException {
-    List<String> names = Lists.newArrayList();
+    List<String> names = new ArrayList<>();
     String[] list = getPluginsDir().list();
     if (list != null) {
       for (String pluginJarName : list) {
diff --git a/lib/JGIT_VERSION b/lib/JGIT_VERSION
index 878d673..6878780 100644
--- a/lib/JGIT_VERSION
+++ b/lib/JGIT_VERSION
@@ -1,4 +1,4 @@
 include_defs('//lib/maven.defs')
 
-REPO = MAVEN_CENTRAL # Leave here even if set to MAVEN_CENTRAL.
-VERS = '4.3.0.201604071810-r'
+REPO = GERRIT # Leave here even if set to MAVEN_CENTRAL.
+VERS = '4.3.0.201604071810-r.23-gc9b0028'
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index 98ad93c..4cf692e 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -16,6 +16,7 @@
     '//lib:args4j',
     '//lib:guava',
     '//lib/log:api',
+    '//lib/log:nop',
   ],
   visibility = ['//tools/eclipse:classpath'],
 )
diff --git a/lib/jgit/org.eclipse.jgit.archive/BUCK b/lib/jgit/org.eclipse.jgit.archive/BUCK
index e5aeedd..10ab2b0 100644
--- a/lib/jgit/org.eclipse.jgit.archive/BUCK
+++ b/lib/jgit/org.eclipse.jgit.archive/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'jgit-archive',
   id = 'org.eclipse.jgit:org.eclipse.jgit.archive:' + VERS,
-  sha1 = 'c065b765aac56cdbe531634fdfd829a6ce8bbd0c',
+  sha1 = 'c612e5bd40ebf6226032cb32c14b396d7ebfe036',
   license = 'jgit',
   repository = REPO,
   deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.http.server/BUCK b/lib/jgit/org.eclipse.jgit.http.server/BUCK
index 89d0eba..8ebc18df 100644
--- a/lib/jgit/org.eclipse.jgit.http.server/BUCK
+++ b/lib/jgit/org.eclipse.jgit.http.server/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'jgit-servlet',
   id = 'org.eclipse.jgit:org.eclipse.jgit.http.server:' + VERS,
-  sha1 = '516925ff0df67705e368c905a910ed982655cc32',
+  sha1 = 'bb01841b74a48abe506c2e44f238e107188e6c8f',
   license = 'jgit',
   repository = REPO,
   deps = ['//lib/jgit/org.eclipse.jgit:jgit'],
diff --git a/lib/jgit/org.eclipse.jgit.junit/BUCK b/lib/jgit/org.eclipse.jgit.junit/BUCK
index 787a3a3..4b06573 100644
--- a/lib/jgit/org.eclipse.jgit.junit/BUCK
+++ b/lib/jgit/org.eclipse.jgit.junit/BUCK
@@ -4,7 +4,7 @@
 maven_jar(
   name = 'junit',
   id = 'org.eclipse.jgit:org.eclipse.jgit.junit:' + VERS,
-  sha1 = '060a98c260b23f64c47e3fb4d77b684ccb64c114',
+  sha1 = '62dddedccdcd67b622d0d35a4bfb15c7eab8e171',
   license = 'DO_NOT_DISTRIBUTE',
   repository = REPO,
   unsign = True,
diff --git a/lib/jgit/org.eclipse.jgit/BUCK b/lib/jgit/org.eclipse.jgit/BUCK
index 669e406..0d19343 100644
--- a/lib/jgit/org.eclipse.jgit/BUCK
+++ b/lib/jgit/org.eclipse.jgit/BUCK
@@ -4,8 +4,8 @@
 maven_jar(
   name = 'jgit',
   id = 'org.eclipse.jgit:org.eclipse.jgit:' + VERS,
-  bin_sha1 = 'e3c57967fb8df5172d62bb4bbbd355554db4c65d',
-  src_sha1 = '4d64b05a50d581a2884a5b7dc66163be18bac755',
+  bin_sha1 = 'dc4464c876cbf3815fd6cf6cb9d29d375566d6b1',
+  src_sha1 = 'ab3f9344d524f71c74307e68c82c698266e4bcec',
   license = 'jgit',
   repository = REPO,
   unsign = True,
diff --git a/lib/js/BUCK b/lib/js/BUCK
index 86d69e6..36a3d19 100644
--- a/lib/js/BUCK
+++ b/lib/js/BUCK
@@ -134,18 +134,6 @@
 )
 
 bower_component(
-  name = 'iron-ajax',
-  package = 'polymerelements/iron-ajax',
-  version = '1.2.0',
-  deps = [
-    ':polymer',
-    ':promise-polyfill',
-  ],
-  license = 'polymer',
-  sha1 = 'f195d0d0ddef73a20573b0a02ce6a505cc1d7014',
-)
-
-bower_component(
   name = 'iron-autogrow-textarea',
   package = 'polymerelements/iron-autogrow-textarea',
   version = '1.0.12',
diff --git a/plugins/replication b/plugins/replication
index 8db7117..a3f9374 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 8db7117d509498c7d88979034afbc67220d75f6c
+Subproject commit a3f93741618e2bef903f9140dd15d66b5cc4f018
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index f6df712..141898f 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit f6df7121d2704e73c2a315a660e5cc4e12ab1ab9
+Subproject commit 141898f9e160c190bdf28999e31f5c667a477f9c
diff --git a/polygerrit-ui/BUCK b/polygerrit-ui/BUCK
index fa7f52c..614e85c 100644
--- a/polygerrit-ui/BUCK
+++ b/polygerrit-ui/BUCK
@@ -4,7 +4,6 @@
   name = 'polygerrit_components',
   deps = [
     '//lib/js:fetch',
-    '//lib/js:iron-ajax',
     '//lib/js:iron-autogrow-textarea',
     '//lib/js:iron-dropdown',
     '//lib/js:iron-input',
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
index f71d0f7..1d3968f 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.html
@@ -15,8 +15,7 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-change-list/gr-change-list.html">
 
 <dom-module id="gr-change-list-view">
@@ -26,15 +25,9 @@
         background-color: var(--view-background-color);
         display: block;
       }
-      .loading,
-      .error {
-        padding: 1em var(--default-horizontal-margin);
-      }
       .loading {
         color: #666;
-      }
-      .error {
-        color: #D32F2F;
+        padding: 1em var(--default-horizontal-margin);
       }
       gr-change-list {
         width: 100%;
@@ -56,30 +49,21 @@
         }
       }
     </style>
-    <gr-ajax
-        auto
-        url="/changes/"
-        params="[[_computeQueryParams(_query, _offset, changesPerPage)]]"
-        last-response="{{_changes}}"
-        last-error="{{_lastError}}"
-        loading="{{_loading}}"></gr-ajax>
-    <div class="loading" hidden$="[[!_loading]]" hidden>Loading...</div>
-    <div class="error" hidden$="[[_computeErrorHidden(_loading, _lastError)]]" hidden>
-      [[_lastError.request.xhr.responseText]]
-    </div>
-    <div hidden$="[[_computeListHidden(_loading, _lastError)]]" hidden>
+    <div class="loading" hidden$="[[!_loading]]">Loading...</div>
+    <div hidden$="[[_loading]]">
       <gr-change-list
           changes="{{_changes}}"
           selected-index="{{viewState.selectedChangeIndex}}"
           show-star="[[loggedIn]]"></gr-change-list>
       <nav>
-        <a href$="[[_computeNavLink(_query, _offset, -1, changesPerPage)]]"
+        <a href$="[[_computeNavLink(_query, _offset, -1, _changesPerPage)]]"
            hidden$="[[_hidePrevArrow(_offset)]]">&larr; Prev</a>
-        <a href$="[[_computeNavLink(_query, _offset, 1, changesPerPage)]]"
-           hidden$="[[_hideNextArrow(_changes.length, changesPerPage)]]">
+        <a href$="[[_computeNavLink(_query, _offset, 1, _changesPerPage)]]"
+           hidden$="[[_hideNextArrow(_changes.length, _changesPerPage)]]">
           Next &rarr;</a>
       </nav>
     </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-change-list-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
index a694fc9..79531ac 100644
--- a/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-change-list-view/gr-change-list-view.js
@@ -49,7 +49,7 @@
         value: function() { return {}; },
       },
 
-      changesPerPage: Number,
+      _changesPerPage: Number,
 
       /**
        * Currently active query.
@@ -67,11 +67,6 @@
       _changes: Array,
 
       /**
-       * Contains error of last request (in case of change loading error).
-       */
-      _lastError: Object,
-
-      /**
        * For showing a "loading..." string during ajax requests.
        */
       _loading: {
@@ -80,10 +75,6 @@
       },
     },
 
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
-
     attached: function() {
       this.fire('title-change', {title: this._query});
     },
@@ -91,6 +82,7 @@
     _paramsChanged: function(value) {
       if (value.view != this.tagName.toLowerCase()) { return; }
 
+      this._loading = true;
       this._query = value.query;
       this._offset = value.offset || 0;
       if (this.viewState.query != this._query ||
@@ -101,22 +93,23 @@
       }
 
       this.fire('title-change', {title: this._query});
+
+      this._getPreferences().then(function(prefs) {
+        this._changesPerPage = prefs.changes_per_page;
+        return this._getChanges();
+      }.bind(this)).then(function(changes) {
+        this._changes = changes;
+        this._loading = false;
+      }.bind(this));
     },
 
-    _computeQueryParams: function(query, offset, changesPerPage) {
-      var options = this.listChangesOptionsToHex(
-          this.ListChangesOption.LABELS,
-          this.ListChangesOption.DETAILED_ACCOUNTS
-      );
-      var obj = {
-        n: changesPerPage,
-        O: options,
-        S: offset || 0,
-      };
-      if (query && query.length > 0) {
-        obj.q = query;
-      }
-      return obj;
+    _getChanges: function() {
+      return this.$.restAPI.getChanges(this._changesPerPage, this._query,
+          this._offset);
+    },
+
+    _getPreferences: function() {
+      return this.$.restAPI.getPreferences();
     },
 
     _computeNavLink: function(query, offset, direction, changesPerPage) {
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
index fb2de62..ce413ca 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.html
@@ -15,7 +15,7 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/rest-client-behavior.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-dashboard-view">
   <template>
@@ -37,12 +37,6 @@
         }
       }
     </style>
-    <gr-ajax
-        auto
-        url="/changes/"
-        params="[[_computeQueryParams()]]"
-        last-response="{{_results}}"
-        loading="{{_loading}}"></gr-ajax>
     <div class="loading" hidden$="[[!_loading]]">Loading...</div>
     <div hidden$="[[_loading]]" hidden>
       <gr-change-list
@@ -53,6 +47,7 @@
           groups="{{_results}}"
           group-titles="[[_groupTitles]]"></gr-change-list>
     </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-dashboard-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
index fc6a3ff..3ac6463 100644
--- a/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
+++ b/polygerrit-ui/app/elements/change-list/gr-dashboard-view/gr-dashboard-view.js
@@ -49,28 +49,21 @@
       },
     },
 
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
-
     attached: function() {
       this.fire('title-change', {title: 'My Reviews'});
+
+      this._loading = true;
+      this._getDashboardChanges().then(function(results) {
+        this._results = results;
+        this._loading = false;
+      }.bind(this)).catch(function(err) {
+        this._loading = false;
+        console.error(err.message);
+      }.bind(this));
     },
 
-    _computeQueryParams: function() {
-      var options = this.listChangesOptionsToHex(
-          this.ListChangesOption.LABELS,
-          this.ListChangesOption.DETAILED_ACCOUNTS,
-          this.ListChangesOption.REVIEWED
-      );
-      return {
-        O: options,
-        q: [
-          'is:open owner:self',
-          'is:open reviewer:self -owner:self',
-          'is:closed (owner:self OR reviewer:self) -age:4w limit:10',
-        ],
-      };
+    _getDashboardChanges: function() {
+      return this.$.restAPI.getDashboardChanges();
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 30e89bf..7ab23a1 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -266,9 +266,8 @@
           </div>
           <div class="relatedChanges">
             <gr-related-changes-list id="relatedChanges"
-              change="[[_change]]"
-              server-config="[[serverConfig]]"
-              patch-num="[[_patchNum]]"></gr-related-changes-list>
+                change="[[_change]]"
+                patch-num="[[_patchNum]]"></gr-related-changes-list>
           </div>
         </div>
       </section>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index 1066625..8134704 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -39,7 +39,8 @@
       }
       .reviewed,
       .status {
-        justify-content: center;
+        display: inline-block;
+        text-align: center;
         width: 1.5em;
       }
       .positionIndicator {
@@ -60,13 +61,20 @@
         flex: 1;
         overflow: hidden;
         padding-left: .35em;
-        text-decoration: none;
         text-overflow: ellipsis;
         white-space: nowrap;
       }
-      .row:not(.header) .path:hover {
+      .path a {
+        text-decoration: none;
+      }
+      .path a:hover {
         text-decoration: underline;
       }
+      .oldPath {
+        color: #999;
+        overflow: hidden;
+        text-overflow: ellipsis;
+      }
       .comments,
       .stats {
         text-align: right;
@@ -89,9 +97,6 @@
       .removed {
         color: #D32F2F;
       }
-      .reviewed input[type="checkbox"] {
-        display: inline-block;
-      }
       .drafts {
         color: #C62828;
         font-weight: bold;
@@ -131,9 +136,16 @@
         <div class$="[[_computeClass('status', file.__path)]]">
           [[_computeFileStatus(file.status)]]
         </div>
-        <a class="path" href$="[[_computeDiffURL(changeNum, patchNum, file.__path)]]">
-          [[_computeFileDisplayName(file.__path)]]
-        </a>
+        <div class="path">
+          <a href$="[[_computeDiffURL(changeNum, patchNum, file.__path)]]"
+              title="[[_computeFileDisplayName(file.__path)]]">
+            [[_computeFileDisplayName(file.__path)]]
+          </a>
+          <div class="oldPath" hidden$="[[!file.old_path]]" hidden
+              title="[[file.old_path]]">
+            [[file.old_path]]
+          </div>
+        </div>
         <div class="comments">
           <span class="drafts">[[_computeDraftsString(drafts, patchNum, file.__path)]]</span>
           [[_computeCommentsString(comments, patchNum, file.__path)]]
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
index 2b13dd6..213b004 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.html
@@ -15,8 +15,7 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../behaviors/rest-client-behavior.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-related-changes-list">
   <template>
@@ -68,25 +67,6 @@
         display: none;
       }
     </style>
-    <gr-ajax id="relatedXHR"
-        url="[[_computeRelatedURL(change._number, patchNum)]]"
-        last-response="{{_relatedResponse}}"></gr-ajax>
-    <gr-ajax id="submittedTogetherXHR"
-        url="[[_computeSubmittedTogetherURL(change._number)]]"
-        last-response="{{_submittedTogether}}"></gr-ajax>
-    <gr-ajax id="conflictsXHR"
-        url="/changes/"
-        params="[[_computeConflictsQueryParams(change._number)]]"
-        last-response="{{_conflicts}}"></gr-ajax>
-    <gr-ajax id="cherryPicksXHR"
-        url="/changes/"
-        params="[[_computeCherryPicksQueryParams(change.project, change.change_id, change._number)]]"
-        last-response="{{_cherryPicks}}"></gr-ajax>
-    <gr-ajax id="sameTopicXHR"
-        url="/changes/"
-        params="[[_computeSameTopicQueryParams(change.topic)]]"
-        last-response="{{_sameTopic}}"></gr-ajax>
-
     <div hidden$="[[!_loading]]">Loading...</div>
     <section class="relatedChanges" hidden$="[[!_relatedResponse.changes.length]]" hidden>
       <h4>Relation chain</h4>
@@ -138,6 +118,7 @@
         </a>
       </template>
     </section>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-related-changes-list.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
index 9f433dc..73a57d4 100644
--- a/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
+++ b/polygerrit-ui/app/elements/change/gr-related-changes-list/gr-related-changes-list.js
@@ -20,10 +20,6 @@
     properties: {
       change: Object,
       patchNum: String,
-      serverConfig: {
-        type: Object,
-        observer: '_serverConfigChanged',
-      },
       hidden: {
         type: Boolean,
         value: false,
@@ -31,15 +27,6 @@
       },
 
       _loading: Boolean,
-      _resolveServerConfigReady: Function,
-      _serverConfigReady: {
-        type: Object,
-        value: function() {
-          return new Promise(function(resolve) {
-            this._resolveServerConfigReady = resolve;
-          }.bind(this));
-        }
-      },
       _connectedRevisions: {
         type: Array,
         computed: '_computeConnectedRevisions(change, patchNum, ' +
@@ -52,10 +39,6 @@
       _sameTopic: Array,
     },
 
-    behaviors: [
-      Gerrit.RESTClientBehavior,
-    ],
-
     observers: [
       '_resultsChanged(_relatedResponse.changes, _submittedTogether, ' +
           '_conflicts, _cherryPicks, _sameTopic)',
@@ -67,72 +50,58 @@
       }
       this._loading = true;
       var promises = [
-        this.$.relatedXHR.generateRequest().completes,
-        this.$.submittedTogetherXHR.generateRequest().completes,
-        this.$.conflictsXHR.generateRequest().completes,
-        this.$.cherryPicksXHR.generateRequest().completes,
+        this._getRelatedChanges().then(function(response) {
+          this._relatedResponse = response;
+        }.bind(this)),
+        this._getSubmittedTogether().then(function(response) {
+          this._submittedTogether = response;
+        }.bind(this)),
+        this._getConflicts().then(function(response) {
+          this._conflicts = response;
+        }.bind(this)),
+        this._getCherryPicks().then(function(response) {
+          this._cherryPicks = response;
+        }.bind(this)),
       ];
 
-      return this._serverConfigReady.then(function() {
-        if (this.change.topic &&
-            !this.serverConfig.change.submit_whole_topic) {
-          return this.$.sameTopicXHR.generateRequest().completes;
+      return this._getServerConfig().then(function(config) {
+        if (this.change.topic && !config.change.submit_whole_topic) {
+          return this._getChangesWithSameTopic().then(function(response) {
+            this._sameTopic = response;
+          }.bind(this));
         } else {
-          this._sameTopic = [];
+         this._sameTopic = [];
         }
-        return Promise.resolve();
+        return this._sameTopic;
       }.bind(this)).then(Promise.all(promises)).then(function() {
         this._loading = false;
       }.bind(this));
     },
 
-    _computeRelatedURL: function(changeNum, patchNum) {
-      return this.changeBaseURL(changeNum, patchNum) + '/related';
+    _getRelatedChanges: function() {
+      return this.$.restAPI.getRelatedChanges(this.change._number,
+          this.patchNum);
     },
 
-    _computeSubmittedTogetherURL: function(changeNum) {
-      return this.changeBaseURL(changeNum) + '/submitted_together';
+    _getSubmittedTogether: function() {
+      return this.$.restAPI.getChangesSubmittedTogether(this.change._number);
     },
 
-    _computeConflictsQueryParams: function(changeNum) {
-      var options = this.listChangesOptionsToHex(
-          this.ListChangesOption.CURRENT_REVISION,
-          this.ListChangesOption.CURRENT_COMMIT
-      );
-      return {
-        O: options,
-        q: 'status:open is:mergeable conflicts:' + changeNum,
-      };
+    _getServerConfig: function() {
+      return this.$.restAPI.getConfig();
     },
 
-    _computeCherryPicksQueryParams: function(project, changeID, changeNum) {
-      var options = this.listChangesOptionsToHex(
-          this.ListChangesOption.CURRENT_REVISION,
-          this.ListChangesOption.CURRENT_COMMIT
-      );
-      var query = [
-        'project:' + project,
-        'change:' + changeID,
-        '-change:' + changeNum,
-        '-is:abandoned',
-      ].join(' ');
-      return {
-        O: options,
-        q: query
-      };
+    _getConflicts: function() {
+      return this.$.restAPI.getChangeConflicts(this.change._number);
     },
 
-    _computeSameTopicQueryParams: function(topic) {
-      var options = this.listChangesOptionsToHex(
-          this.ListChangesOption.LABELS,
-          this.ListChangesOption.CURRENT_REVISION,
-          this.ListChangesOption.CURRENT_COMMIT,
-          this.ListChangesOption.DETAILED_LABELS
-      );
-      return {
-        O: options,
-        q: 'status:open topic:' + topic,
-      };
+    _getCherryPicks: function() {
+      return this.$.restAPI.getChangeCherryPicks(this.change.project,
+          this.change.change_id, this.change._number);
+    },
+
+    _getChangesWithSameTopic: function() {
+      return this.$.restAPI.getChangesWithSameTopic(this.change.topic);
     },
 
     _computeChangeURL: function(changeNum, patchNum) {
@@ -182,10 +151,6 @@
       return '';
     },
 
-    _serverConfigChanged: function(config) {
-      this._resolveServerConfigReady(config);
-    },
-
     _resultsChanged: function(related, submittedTogether, conflicts,
         cherryPicks, sameTopic) {
       var results = [
diff --git a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
index 47324ba..be536da 100644
--- a/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
+++ b/polygerrit-ui/app/elements/change/gr-reply-dialog/gr-reply-dialog.js
@@ -141,16 +141,13 @@
       this.disabled = true;
       this._saveReview(obj).then(function(response) {
         this.disabled = false;
-        if (!response.ok) {
-          alert('Oops. Something went wrong. Check the console and bug the ' +
-              'PolyGerrit team for assistance.');
-          return response.text().then(function(text) {
-            console.error(text);
-          });
-        }
+        if (!response.ok) { return response; }
 
         this.draft = '';
         this.fire('send', null, {bubbles: false});
+      }.bind(this)).catch(function(err) {
+        this.disabled = false;
+        throw err;
       }.bind(this));
     },
 
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
index 6e081ea..3fee424 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.html
@@ -18,9 +18,8 @@
 <link rel="import" href="../../../bower_components/iron-input/iron-input.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
 <link rel="import" href="../../shared/gr-account-chip/gr-account-chip.html">
-<link rel="import" href="../../shared/gr-ajax/gr-ajax.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
-<link rel="import" href="../../shared/gr-request/gr-request.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-reviewer-list">
   <template>
@@ -80,10 +79,6 @@
         }
       }
     </style>
-    <gr-ajax id="autocompleteXHR"
-        url="[[_computeAutocompleteURL(change)]]"
-        params="[[_computeAutocompleteParams(_inputVal)]]"
-        on-response="_handleResponse"></gr-ajax>
     <template is="dom-repeat" items="[[_reviewers]]" as="reviewer">
       <gr-account-chip class="reviewer" account="[[reviewer]]"
           on-remove="_handleRemove"
@@ -119,6 +114,7 @@
       <gr-button link id="addReviewer" class="addReviewer" on-tap="_handleAddTap"
           hidden$="[[_showInput]]">Add reviewer</gr-button>
     </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-reviewer-list.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
index 275ab6f..de99039 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list.js
@@ -121,35 +121,10 @@
       return false;
     },
 
-    _computeAutocompleteURL: function(change) {
-      return '/changes/' + change._number + '/suggest_reviewers';
-    },
-
-    _computeAutocompleteParams: function(inputVal) {
-      return {
-        n: 10,  // Return max 10 results
-        q: inputVal,
-      };
-    },
-
     _computeSelected: function(index, selectedIndex) {
       return index == selectedIndex;
     },
 
-    _handleResponse: function(e) {
-      this._autocompleteData = e.detail.response.filter(function(reviewer) {
-        var account = reviewer.account;
-        if (!account) { return true; }
-        for (var i = 0; i < this._reviewers.length; i++) {
-          if (account._account_id == this.change.owner._account_id ||
-              account._account_id == this._reviewers[i]._account_id) {
-            return false;
-          }
-        }
-        return true;
-      }, this);
-    },
-
     _handleBodyClick: function(e) {
       var eventPath = Polymer.dom(e).path;
       for (var i = 0; i < eventPath.length; i++) {
@@ -165,7 +140,12 @@
       e.preventDefault();
       var target = Polymer.dom(e).rootTarget;
       var accountID = parseInt(target.getAttribute('data-account-id'), 10);
-      this._send('DELETE', this._restEndpoint(accountID)).then(function(req) {
+      this.disabled = true;
+      this._xhrPromise =
+          this._removeReviewer(accountID).then(function(response) {
+        this.disabled = false;
+        if (!response.ok) { return response; }
+
         var reviewers = this.change.reviewers;
         ['REVIEWER', 'CC'].forEach(function(type) {
           reviewers[type] = reviewers[type] || [];
@@ -177,8 +157,7 @@
           }
         }, this);
       }.bind(this)).catch(function(err) {
-        alert('Oops. Something went wrong. Check the console and bug the ' +
-            'PolyGerrit team for assistance.');
+        this.disabled = false;
         throw err;
       }.bind(this));
     },
@@ -241,7 +220,8 @@
           return;
         }
         this._lastAutocompleteRequest =
-            this.$.autocompleteXHR.generateRequest();
+            this._getSuggestedReviewers(this.change._number, val).then(
+                this._handleReviewersResponse.bind(this));
       }.bind(this);
 
       this._clearInputRequestHandle();
@@ -253,6 +233,24 @@
       }
     },
 
+    _handleReviewersResponse: function(response) {
+      this._autocompleteData = response.filter(function(reviewer) {
+        var account = reviewer.account;
+        if (!account) { return true; }
+        for (var i = 0; i < this._reviewers.length; i++) {
+          if (account._account_id == this.change.owner._account_id ||
+              account._account_id == this._reviewers[i]._account_id) {
+            return false;
+          }
+        }
+        return true;
+      }, this);
+    },
+
+    _getSuggestedReviewers: function(changeNum, inputVal) {
+      return this.$.restAPI.getChangeSuggestedReviewers(changeNum, inputVal);
+    },
+
     _handleKey: function(e) {
       if (this._hideAutocomplete) {
         if (e.keyCode == 27) {  // 'esc'
@@ -302,43 +300,32 @@
         reviewerID = reviewer.group.id;
       }
       this._autocompleteData = [];
-      this._send('POST', this._restEndpoint(), reviewerID).then(function(req) {
-        this.change.reviewers.CC = this.change.reviewers.CC || [];
-        req.response.reviewers.forEach(function(r) {
-          this.push('change.removable_reviewers', r);
-          this.push('change.reviewers.CC', r);
-        }, this);
-        this._inputVal = '';
-        this.$.input.focus();
+      this.disabled = true;
+      this._xhrPromise = this._addReviewer(reviewerID).then(function(response) {
+        this.change.reviewers['CC'] = this.change.reviewers['CC'] || [];
+        this.disabled = false;
+        if (!response.ok) { return response; }
+
+        return this.$.restAPI.getResponseObject(response).then(function(obj) {
+          obj.reviewers.forEach(function(r) {
+            this.push('change.removable_reviewers', r);
+            this.push('change.reviewers.CC', r);
+          }, this);
+          this._inputVal = '';
+          this.$.input.focus();
+        }.bind(this));
       }.bind(this)).catch(function(err) {
-        // TODO(andybons): Use the message returned by the server.
-        alert('Unable to add ' + reviewerID + ' as a reviewer.');
+        this.disabled = false;
         throw err;
       }.bind(this));
     },
 
-    _send: function(method, url, reviewerID) {
-      this.disabled = true;
-      var request = document.createElement('gr-request');
-      var opts = {
-        method: method,
-        url: url,
-      };
-      if (reviewerID) {
-        opts.body = {reviewer: reviewerID};
-      }
-      this._xhrPromise = request.send(opts);
-      var enableEl = function() { this.disabled = false; }.bind(this);
-      this._xhrPromise.then(enableEl).catch(enableEl);
-      return this._xhrPromise;
+    _addReviewer: function(id) {
+      return this.$.restAPI.addChangeReviewer(this.change._number, id);
     },
 
-    _restEndpoint: function(id) {
-      var path = '/changes/' + this.change._number + '/reviewers';
-      if (id) {
-        path += '/' + id;
-      }
-      return path;
+    _removeReviewer: function(id) {
+      return this.$.restAPI.removeChangeReviewer(this.change._number, id);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
index eccc72e..9311e91 100644
--- a/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
+++ b/polygerrit-ui/app/elements/change/gr-reviewer-list/gr-reviewer-list_test.html
@@ -34,20 +34,12 @@
 <script>
   suite('gr-reviewer-list tests', function() {
     var element;
-    var server;
 
     setup(function() {
       element = fixture('basic');
-
-      server = sinon.fakeServer.create();
-      server.respondWith(
-        'GET',
-        /\/changes\/42\/suggest_reviewers\?n=10&q=andy(.*)/,
-        [
-          200,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n' +
-          JSON.stringify([
+      stub('gr-rest-api-interface', {
+        getChangeSuggestedReviewers: function() {
+          return Promise.resolve([
             {
               account: {
                 _account_id: 1021482,
@@ -68,41 +60,32 @@
                 name: 'andy',
               }
             }
-          ]),
-        ]
-      );
-      server.respondWith(
-        'POST',
-        '/changes/42/reviewers',
-        [
-          200,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n' +
-          JSON.stringify({
-            reviewers: [{
-              _account_id: 1021482,
-              approvals: {
-                'Code-Review': ' 0'
-              },
-              email: 'andybons@chromium.org',
-              name: 'Andrew Bonventre',
-            }]
-          }),
-        ]
-      );
-      server.respondWith(
-        'DELETE',
-        '/changes/42/reviewers/1021482',
-        [
-          204,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n{}',
-        ]
-      );
-    });
-
-    teardown(function() {
-      server.restore();
+          ]);
+        },
+        addChangeReviewer: function() {
+          return Promise.resolve({
+            ok: true,
+            text: function() {
+              return Promise.resolve(
+                ')]}\'\n' +
+                JSON.stringify({
+                  reviewers: [{
+                    _account_id: 1021482,
+                    approvals: {
+                      'Code-Review': ' 0'
+                    },
+                    email: 'andybons@chromium.org',
+                    name: 'Andrew Bonventre',
+                  }]
+                })
+              );
+            },
+          });
+        },
+        removeChangeReviewer: function() {
+          return Promise.resolve({ok: true});
+        },
+      });
     });
 
     test('controls hidden on immutable element', function() {
@@ -176,7 +159,7 @@
       };
       flushAsynchronousOperations();
       var chips =
-        Polymer.dom(element.root).querySelectorAll('gr-account-chip');
+          Polymer.dom(element.root).querySelectorAll('gr-account-chip');
       assert.equal(chips.length, 3);
       Array.from(chips).forEach(function(el) {
         var accountID = parseInt(el.getAttribute('data-account-id'), 10);
@@ -195,17 +178,15 @@
     test('autocomplete starts at >= 3 chars', function() {
       element._inputRequestTimeout = 0;
       element._mutable = true;
-      var genRequestStub = sinon.stub(
-        element.$.autocompleteXHR,
-        'generateRequest',
+      var requestStub = sinon.stub(element, '_getSuggestedReviewers',
         function() {
-          assert(false, 'generateRequest should not be called for input ' +
-              'lengths of less than 3 chars');
+          assert(false, '_getSuggestedReviewers should not be called for ' +
+              'input lengths of less than 3 chars');
         }
       );
       element._inputVal = 'fo';
       flushAsynchronousOperations();
-      genRequestStub.restore();
+      requestStub.restore();
     });
 
     test('add/remove reviewer flow', function(done) {
@@ -220,9 +201,8 @@
       MockInteractions.tap(element.$$('.addReviewer'));
       flushAsynchronousOperations();
       element._inputVal = 'andy';
-      server.respond();
 
-      element._lastAutocompleteRequest.completes.then(function() {
+      element._lastAutocompleteRequest.then(function() {
         flushAsynchronousOperations();
         assert.isFalse(element.$$('.dropdown').hasAttribute('hidden'));
         var itemEls = Polymer.dom(element.root).querySelectorAll('.reviewer');
@@ -242,8 +222,7 @@
         assert.isTrue(element.$$('.dropdown').hasAttribute('hidden'));
 
         element._inputVal = 'andyb';
-        server.respond();
-        element._lastAutocompleteRequest.completes.then(function() {
+        element._lastAutocompleteRequest.then(function() {
           assert.isFalse(element.$$('.dropdown').hasAttribute('hidden'));
           var itemEls = Polymer.dom(element.root).querySelectorAll('.reviewer');
           assert.equal(itemEls.length, 3);
@@ -251,7 +230,6 @@
           assert.isFalse(itemEls[1].hasAttribute('selected'));
           MockInteractions.pressAndReleaseKeyOn(element, 13); // 'enter'
           assert.isTrue(element.disabled);
-          server.respond();
 
           element._xhrPromise.then(function() {
             assert.isFalse(element.disabled);
@@ -262,13 +240,12 @@
             MockInteractions.tap(element.$$('.reviewer').$$('gr-button'));
             flushAsynchronousOperations();
             assert.isTrue(element.disabled);
-            server.respond();
 
             element._xhrPromise.then(function() {
               flushAsynchronousOperations();
               assert.isFalse(element.disabled);
               var reviewerEls =
-                Polymer.dom(element.root).querySelectorAll('.reviewer');
+                  Polymer.dom(element.root).querySelectorAll('.reviewer');
               assert.equal(reviewerEls.length, 0);
               done();
             });
diff --git a/polygerrit-ui/app/elements/shared/gr-request/gr-request.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
similarity index 64%
rename from polygerrit-ui/app/elements/shared/gr-request/gr-request.html
rename to polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
index df9eddc..80f293d 100644
--- a/polygerrit-ui/app/elements/shared/gr-request/gr-request.html
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.html
@@ -1,5 +1,5 @@
 <!--
-Copyright (C) 2015 The Android Open Source Project
+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.
@@ -15,11 +15,13 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-ajax/iron-request.html">
+<link rel="import" href="../../shared/gr-alert/gr-alert.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
-<dom-module id="gr-request">
+<dom-module id="gr-error-manager">
   <template>
-    <iron-request id="xhr"></iron-request>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
-  <script src="gr-request.js"></script>
+  <script src="gr-error-manager.js"></script>
 </dom-module>
+
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
new file mode 100644
index 0000000..757d79f
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager.js
@@ -0,0 +1,109 @@
+// 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.
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-error-manager',
+
+    properties: {
+      _alertElement: Element,
+      _hideAlertHandle: Number,
+      _hideAlertTimeout: {
+        type: Number,
+        value: 5000,
+      },
+    },
+
+    attached: function() {
+      this.listen(document, 'server-error', '_handleServerError');
+      this.listen(document, 'network-error', '_handleNetworkError');
+    },
+
+    detached: function() {
+      this._clearHideAlertHandle();
+      this.unlisten(document, 'server-error', '_handleServerError');
+      this.unlisten(document, 'network-error', '_handleNetworkError');
+    },
+
+    _handleServerError: function(e) {
+      if (e.detail.response.status === 403) {
+        this._getLoggedIn().then(function(loggedIn) {
+          if (loggedIn) {
+            // The app was logged at one point and is now getting auth errors.
+            // This indicates the auth token is no longer valid.
+            this._showAuthErrorAlert();
+          }
+        }.bind(this));
+      } else {
+        e.detail.response.text().then(function(text) {
+          this._showAlert('Server error: ' + text);
+        }.bind(this));
+      }
+    },
+
+    _handleNetworkError: function(e) {
+      this._showAlert('Server unavailable');
+      console.error(e.detail.error.message);
+    },
+
+    _getLoggedIn: function() {
+      return this.$.restAPI.getLoggedIn();
+    },
+
+    _showAlert: function(text) {
+      if (this._alertElement) { return; }
+
+      this._clearHideAlertHandle();
+      this._hideAlertHandle =
+            this.async(this._hideAlert.bind(this), this._hideAlertTimeout);
+      var el = this._createToastAlert();
+      el.show(text);
+      this._alertElement = el;
+    },
+
+    _hideAlert: function() {
+      if (!this._alertElement) { return; }
+
+      this._alertElement.hide();
+      this._alertElement = null;
+    },
+
+    _clearHideAlertHandle: function() {
+      if (this._hideAlertHandle != null) {
+        this.cancelAsync(this._hideAlertHandle);
+        this._hideAlertHandle = null;
+      }
+    },
+
+    _showAuthErrorAlert: function() {
+      if (this._alertElement) { return; }
+
+      var el = this._createToastAlert();
+      el.addEventListener('action', this._refreshPage.bind(this));
+      el.show('Auth error', 'Refresh page');
+      this._alertElement = el;
+    },
+
+    _createToastAlert: function() {
+      var el = document.createElement('gr-alert');
+      el.toast = true;
+      return el;
+    },
+
+    _refreshPage: function() {
+      window.location.reload();
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
new file mode 100644
index 0000000..98f79b0
--- /dev/null
+++ b/polygerrit-ui/app/elements/core/gr-error-manager/gr-error-manager_test.html
@@ -0,0 +1,85 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-error-manager</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-error-manager.html">
+
+<test-fixture id="basic">
+  <template>
+    <gr-error-manager></gr-error-manager>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-error-manager tests', function() {
+    var element;
+
+    setup(function() {
+      stub('gr-rest-api-interface', {
+        getLoggedIn: function() { return Promise.resolve(true); },
+      });
+      element = fixture('basic');
+    });
+
+    test('show auth error', function(done) {
+      var showAuthErrorStub = sinon.stub(element, '_showAuthErrorAlert');
+      element.fire('server-error', {response: {status: 403}});
+      flush(function() {
+        assert.isTrue(showAuthErrorStub.calledOnce);
+        showAuthErrorStub.restore();
+        done();
+      });
+    });
+
+    test('show normal server error', function(done) {
+      var showAlertStub = sinon.stub(element, '_showAlert');
+      element.fire('server-error', {response: {
+        status: 500,
+        text: function() { return Promise.resolve('ZOMG'); },
+      }});
+      flush(function() {
+        assert.isTrue(showAlertStub.calledOnce);
+        assert.isTrue(showAlertStub.lastCall.calledWithExactly(
+            'Server error: ZOMG'));
+        showAlertStub.restore();
+        done();
+      });
+    });
+
+    test('show network error', function(done) {
+      var consoleErrorStub = sinon.stub(console, 'error');
+      var showAlertStub = sinon.stub(element, '_showAlert');
+      element.fire('network-error', {error: new Error('ZOMG')});
+      flush(function() {
+        assert.isTrue(showAlertStub.calledOnce);
+        assert.isTrue(showAlertStub.lastCall.calledWithExactly(
+            'Server unavailable'));
+        assert.isTrue(consoleErrorStub.calledOnce);
+        assert.isTrue(consoleErrorStub.lastCall.calledWithExactly('ZOMG'));
+        showAlertStub.restore();
+        consoleErrorStub.restore();
+        done();
+      });
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
index 82b0faf..9462408 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html
@@ -124,11 +124,24 @@
 
   suite('comment action tests', function() {
     var element;
-    var server;
 
     setup(function() {
       stub('gr-rest-api-interface', {
         getLoggedIn: function() { return Promise.resolve(false); },
+        saveDiffDraft: function() { return Promise.resolve({
+          ok: true,
+          text: function() { return Promise.resolve(')]}\'\n' +
+              JSON.stringify({
+                id: '7afa4931_de3d65bd',
+                path: '/path/to/file.txt',
+                line: 5,
+                in_reply_to: 'baf0414d_60047215',
+                updated: '2015-12-21 02:01:10.850000000',
+                message: 'Done'
+              }));
+          },
+        })},
+        deleteDiffDraft: function() { return Promise.resolve({ok: true}); },
       });
       element = fixture('withComment');
       element.comments = [{
@@ -142,35 +155,6 @@
         updated: '2015-12-08 19:48:33.843000000',
       }];
       flushAsynchronousOperations();
-
-      server = sinon.fakeServer.create();
-      // Eat any requests made by elements in this suite.
-      server.respondWith(
-        'PUT',
-        '/changes/41/1/drafts',
-        [
-          201,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n' + JSON.stringify({
-            id: '7afa4931_de3d65bd',
-            path: '/path/to/file.txt',
-            line: 5,
-            in_reply_to: 'baf0414d_60047215',
-            updated: '2015-12-21 02:01:10.850000000',
-            message: 'Done'
-          }),
-        ]
-      );
-
-      server.respondWith(
-        'DELETE',
-        '/changes/41/1/drafts/baf0414d_60047215',
-        [
-          204,
-          {},
-          '',
-        ]
-      );
     });
 
     test('reply', function(done) {
@@ -210,7 +194,6 @@
       var commentEl = element.$$('gr-diff-comment');
       assert.ok(commentEl);
       commentEl.addEventListener('done', function() {
-        server.respond();
         var drafts = element._orderedComments.filter(function(c) {
           return c.__draft == true;
         });
@@ -236,7 +219,6 @@
           Polymer.dom(element.root).querySelectorAll('gr-diff-comment')[1];
       assert.ok(draftEl);
       draftEl.addEventListener('comment-discard', function() {
-        server.respond();
         var drafts = element.comments.filter(function(c) {
           return c.__draft == true;
         });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index a6a8c4d..fd35ad7 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -19,7 +19,7 @@
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
-<link rel="import" href="../../shared/gr-request/gr-request.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-diff-comment">
   <template>
@@ -147,6 +147,7 @@
         </div>
       </div>
     </div>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-diff-comment.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index 81449b8..ded5108 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -72,22 +72,26 @@
     save: function() {
       this.comment.message = this._editDraft;
       this.disabled = true;
-      var endpoint = this._restEndpoint(this.comment.id);
-      this._send('PUT', endpoint).then(function(req) {
+      this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
         this.disabled = false;
-        var comment = req.response;
-        comment.__draft = true;
-        // Maintain the ephemeral draft ID for identification by other
-        // elements.
-        if (this.comment.__draftID) {
-          comment.__draftID = this.comment.__draftID;
-        }
-        this.comment = comment;
-        this.editing = false;
+        if (!response.ok) { return response; }
+
+        return this.$.restAPI.getResponseObject(response).then(function(obj) {
+          var comment = obj;
+          comment.__draft = true;
+          // Maintain the ephemeral draft ID for identification by other
+          // elements.
+          if (this.comment.__draftID) {
+            comment.__draftID = this.comment.__draftID;
+          }
+          this.comment = comment;
+          this.editing = false;
+
+          return obj;
+        }.bind(this));
       }.bind(this)).catch(function(err) {
-        alert('Your draft couldn’t be saved. Check the console and contact ' +
-            'the PolyGerrit team for assistance.');
         this.disabled = false;
+        throw err;
       }.bind(this));
     },
 
@@ -179,18 +183,21 @@
         throw Error('Cannot discard a non-draft comment.');
       }
       this.disabled = true;
-      var commentID = this.comment.id;
-      if (!commentID) {
+      if (!this.comment.id) {
         this.fire('comment-discard');
         return;
       }
-      this._send('DELETE', this._restEndpoint(commentID)).then(function(req) {
+
+      this._xhrPromise =
+          this._deleteDraft(this.comment).then(function(response) {
+        this.disabled = false;
+        if (!response.ok) { return response; }
+
         this.fire('comment-discard');
       }.bind(this)).catch(function(err) {
-        alert('Your draft couldn’t be deleted. Check the console and ' +
-            'contact the PolyGerrit team for assistance.');
         this.disabled = false;
-      }.bind(this));
+        throw err;
+      }.bind(this));;
     },
 
     _preventDefaultAndBlur: function(e) {
@@ -198,26 +205,13 @@
       Polymer.dom(e).rootTarget.blur();
     },
 
-    _send: function(method, url) {
-      var xhr = document.createElement('gr-request');
-      var opts = {
-        method: method,
-        url: url,
-      };
-      if (method == 'PUT' || method == 'POST') {
-        opts.body = this.comment;
-      }
-      this._xhrPromise = xhr.send(opts);
-      return this._xhrPromise;
+    _saveDraft: function(draft) {
+      return this.$.restAPI.saveDiffDraft(this.changeNum, this.patchNum, draft);
     },
 
-    _restEndpoint: function(id) {
-      var path = '/changes/' + this.changeNum + '/revisions/' +
-          this.patchNum + '/drafts';
-      if (id) {
-        path += '/' + id;
-      }
-      return path;
+    _deleteDraft: function(draft) {
+      return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
+          draft);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index c95a9a4..a333e14 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -42,6 +42,9 @@
   suite('gr-diff-comment tests', function() {
     var element;
     setup(function() {
+      stub('gr-rest-api-interface', {
+        getAccount: function() { return Promise.resolve(null); },
+      });
       element = fixture('basic');
       element.comment = {
         author: {
@@ -93,9 +96,30 @@
 
   suite('gr-diff-comment draft tests', function() {
     var element;
-    var server;
 
     setup(function() {
+      stub('gr-rest-api-interface', {
+        getAccount: function() { return Promise.resolve(null); },
+        saveDiffDraft: function() {
+          return Promise.resolve({
+            ok: true,
+            text: function() {
+              return Promise.resolve(
+                ')]}\'\n{' +
+                  '"id": "baf0414d_40572e03",' +
+                  '"path": "/path/to/file",' +
+                  '"line": 5,' +
+                  '"updated": "2015-12-08 21:52:36.177000000",' +
+                  '"message": "saved!"' +
+                '}'
+              );
+            },
+          });
+        },
+        removeChangeReviewer: function() {
+          return Promise.resolve({ok: true});
+        },
+      });
       element = fixture('draft');
       element.changeNum = 42;
       element.patchNum = 1;
@@ -106,43 +130,6 @@
         path: '/path/to/file',
         line: 5,
       };
-
-      server = sinon.fakeServer.create();
-      server.respondWith(
-        'PUT',
-        '/changes/42/revisions/1/drafts',
-        [
-          201,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n{' +
-            '"id": "baf0414d_40572e03",' +
-            '"path": "/path/to/file",' +
-            '"line": 5,' +
-            '"updated": "2015-12-08 21:52:36.177000000",' +
-            '"message": "created!"' +
-          '}'
-        ]
-      );
-
-      server.respondWith(
-        'PUT',
-        /\/changes\/42\/revisions\/1\/drafts\/.+/,
-        [
-          200,
-          {'Content-Type': 'application/json'},
-          ')]}\'\n{' +
-            '"id": "baf0414d_40572e03",' +
-            '"path": "/path/to/file",' +
-            '"line": 5,' +
-            '"updated": "2015-12-08 21:52:36.177000000",' +
-            '"message": "saved!"' +
-          '}'
-        ]
-      );
-    });
-
-    teardown(function() {
-      server.restore();
     });
 
     function isVisible(el) {
@@ -224,14 +211,10 @@
       assert.isTrue(element.disabled,
           'Element should be disabled when creating draft.');
 
-      server.respond();
-
-      element._xhrPromise.then(function(req) {
+      element._xhrPromise.then(function(draft) {
         assert.isFalse(element.disabled,
             'Element should be enabled when done creating draft.');
-        assert.equal(req.status, 201);
-        assert.equal(req.url, '/changes/42/revisions/1/drafts');
-        assert.equal(req.response.message, 'created!');
+        assert.equal(draft.message, 'saved!');
         assert.isFalse(element.editing);
       }).then(function() {
         MockInteractions.tap(element.$$('.edit'));
@@ -240,15 +223,11 @@
         MockInteractions.tap(element.$$('.save'));
         assert.isTrue(element.disabled,
             'Element should be disabled when updating draft.');
-        server.respond();
 
-        element._xhrPromise.then(function(req) {
+        element._xhrPromise.then(function(draft) {
           assert.isFalse(element.disabled,
               'Element should be enabled when done updating draft.');
-          assert.equal(req.status, 200);
-          assert.equal(req.url,
-              '/changes/42/revisions/1/drafts/baf0414d_40572e03');
-          assert.equal(req.response.message, 'saved!');
+          assert.equal(draft.message, 'saved!');
           assert.isFalse(element.editing);
           done();
         });
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index e18d329..b2adcf4 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -121,29 +121,7 @@
     },
 
     _getDiffPreferences: function() {
-      return this._getLoggedIn().then(function(loggedIn) {
-        if (!loggedIn) {
-          // These defaults should match the defaults in
-          // gerrit-extension-api/src/main/jcg/gerrit/extensions/client/DiffPreferencesInfo.java
-          // NOTE: There are some settings that don't apply to PolyGerrit
-          // (Render mode being at least one of them).
-          return Promise.resolve({
-            auto_hide_diff_table_header: true,
-            context: 10,
-            cursor_blink_rate: 0,
-            ignore_whitespace: 'IGNORE_NONE',
-            intraline_difference: true,
-            line_length: 100,
-            show_line_endings: true,
-            show_tabs: true,
-            show_whitespace_errors: true,
-            syntax_highlighting: true,
-            tab_size: 8,
-            theme: 'DEFAULT',
-          });
-        }
-        return this.$.restAPI.getDiffPreferences();
-      }.bind(this));
+      return this.$.restAPI.getDiffPreferences();
     },
 
     _handleReviewedChange: function(e) {
@@ -366,14 +344,11 @@
       el.disabled = true;
       this._saveDiffPreferences().then(function(response) {
         el.disabled = false;
-        if (!response.ok) {
-          alert('Oops. Something went wrong. Check the console and bug the ' +
-              'PolyGerrit team for assistance.');
-          return response.text().then(function(text) {
-            console.error(text);
-          });
-        }
+        if (!response.ok) { return response; }
+
         this.$.prefsOverlay.close();
+      }.bind(this)).catch(function(err) {
+        el.disabled = false;
       }.bind(this));
     },
 
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 1272a45..c7ff59f 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -18,6 +18,7 @@
 <link rel="import" href="../behaviors/keyboard-shortcut-behavior.html">
 <link rel="import" href="../styles/app-theme.html">
 
+<link rel="import" href="./core/gr-error-manager/gr-error-manager.html">
 <link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html">
 <link rel="import" href="./core/gr-main-header/gr-main-header.html">
 <link rel="import" href="./core/gr-router/gr-router.html">
@@ -89,7 +90,6 @@
         <gr-change-list-view
             params="[[params]]"
             view-state="{{_viewState.changeListView}}"
-            changes-per-page="[[_preferences.changes_per_page]]"
             logged-in="[[_computeLoggedIn(_account)]]"></gr-change-list-view>
       </template>
       <template is="dom-if" if="[[_showDashboardView]]" restamp="true">
@@ -140,6 +140,7 @@
     <template is="dom-repeat" items="[[_serverConfig.plugin.js_resource_paths]]" as="path">
       <script src$="/[[path]]" defer></script>
     </template>
+    <gr-error-manager></gr-error-manager>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-app.js"></script>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 3495218..be61428 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -30,7 +30,6 @@
       },
       _serverConfig: Object,
       _version: String,
-      _preferences: Object,
       _showChangeListView: Boolean,
       _showDashboardView: Boolean,
       _showChangeView: Boolean,
@@ -52,10 +51,6 @@
       Gerrit.KeyboardShortcutBehavior,
     ],
 
-    get loggedIn() {
-      return !!(this._account && Object.keys(this._account).length > 0);
-    },
-
     attached: function() {
       this.$.restAPI.getAccount().then(function(account) {
         this._account = account;
@@ -88,17 +83,9 @@
     },
 
     _accountChanged: function(account) {
-      if (this.loggedIn) {
-        this.$.restAPI.getPreferences().then(function(preferences) {
-          this._preferences = preferences;
-        }.bind(this));
-        // Diff preferences are cached; warm it before a diff is rendered.
-        this.$.restAPI.getDiffPreferences();
-      } else {
-        this._preferences = {
-          changes_per_page: 25,
-        };
-      }
+      // Preferences are cached when a user is logged in; warm them.
+      this.$.restAPI.getPreferences();
+      this.$.restAPI.getDiffPreferences();
     },
 
     _viewChanged: function(view) {
@@ -117,7 +104,7 @@
 
     // Argument used for binding update only.
     _computeLoggedIn: function(account) {
-      return this.loggedIn;
+      return !!(account && Object.keys(account).length > 0);
     },
 
     _handlePageError: function(e) {
diff --git a/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.html b/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.html
deleted file mode 100644
index 9a93426..0000000
--- a/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.html
+++ /dev/null
@@ -1,35 +0,0 @@
-<!--
-Copyright (C) 2015 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.
--->
-
-<link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../../../bower_components/iron-ajax/iron-ajax.html">
-
-<dom-module id="gr-ajax">
-  <template>
-    <iron-ajax id="xhr"
-        auto="[[auto]]"
-        url="[[url]]"
-        params="[[params]]"
-        json-prefix=")]}'"
-        last-error="{{lastError}}"
-        last-response="{{lastResponse}}"
-        loading="{{loading}}"
-        on-response="_handleResponse"
-        on-error="_handleError"
-        debounce-duration="300"></iron-ajax>
-  </template>
-  <script src="gr-ajax.js"></script>
-</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.js b/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.js
deleted file mode 100644
index 7fec507..0000000
--- a/polygerrit-ui/app/elements/shared/gr-ajax/gr-ajax.js
+++ /dev/null
@@ -1,81 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  'use strict';
-
-  Polymer({
-    is: 'gr-ajax',
-
-    /**
-     * Fired when a response is received.
-     *
-     * @event response
-     */
-
-    /**
-     * Fired when an error is received.
-     *
-     * @event error
-     */
-
-    hostAttributes: {
-      hidden: true
-    },
-
-    properties: {
-      auto: {
-        type: Boolean,
-        value: false,
-      },
-      url: String,
-      params: {
-        type: Object,
-        value: function() {
-          return {};
-        },
-      },
-      lastError: {
-        type: Object,
-        notify: true,
-      },
-      lastResponse: {
-        type: Object,
-        notify: true,
-      },
-      loading: {
-        type: Boolean,
-        notify: true,
-      },
-    },
-
-    ready: function() {
-      // Used for debugging which element a request came from.
-      var headers = this.$.xhr.headers;
-      headers['x-requesting-element-id'] = this.id || 'gr-ajax (no id)';
-      this.$.xhr.headers = headers;
-    },
-
-    generateRequest: function() {
-      return this.$.xhr.generateRequest();
-    },
-
-    _handleResponse: function(e, req) {
-      this.fire('response', req, {bubbles: false});
-    },
-
-    _handleError: function(e, req) {
-      this.fire('error', req, {bubbles: false});
-    },
-  });
-})();
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
index 28e45a4..140fbaa 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.html
@@ -39,6 +39,14 @@
       :host([shown]) {
         transform: translateY(0);
       }
+      .text {
+        display: inline-block;
+        max-width: 25em;
+        overflow: hidden;
+        text-overflow: ellipsis;
+        vertical-align: bottom;
+        white-space: nowrap;
+      }
       .action {
         color: #a1c2fa;
         font-weight: bold;
@@ -46,11 +54,11 @@
         text-decoration: none;
       }
     </style>
-    [[text]]
+    <span class="text">[[text]]</span>
     <gr-button
         link
         class="action"
-        hidden$="[[!actionText]]"
+        hidden$="[[_hideActionButton]]"
         on-tap="_handleActionTap">[[actionText]]</gr-button>
   </template>
   <script src="gr-alert.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
index ba481ec..a3e933f 100644
--- a/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
+++ b/polygerrit-ui/app/elements/shared/gr-alert/gr-alert.js
@@ -38,6 +38,7 @@
         reflectToAttribute: true,
       },
 
+      _hideActionButton: Boolean,
       _boundTransitionEndHandler: {
         type: Function,
         value: function() { return this._handleTransitionEnd.bind(this); },
@@ -56,6 +57,7 @@
     show: function(text, opt_actionText) {
       this.text = text;
       this.actionText = opt_actionText;
+      this._hideActionButton = !opt_actionText
       document.body.appendChild(this);
       this._setShown(true);
     },
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
index 62a9d2d..05fe10b 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.html
@@ -15,7 +15,7 @@
 -->
 
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
-<link rel="import" href="../gr-request/gr-request.html">
+<link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-change-star">
   <template>
@@ -46,6 +46,7 @@
       <!-- Public Domain image from the Noun Project: https://thenounproject.com/search/?q=star&i=25969 -->
       <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0px" y="0px" viewBox="0 0 100 100" enable-background="new 0 0 100 100" xml:space="preserve"><path d="M26.439,95.601c-5.608,2.949-9.286,0.276-8.216-5.968l4.5-26.237L3.662,44.816c-4.537-4.423-3.132-8.746,3.137-9.657  l26.343-3.829L44.923,7.46c2.804-5.682,7.35-5.682,10.154,0l11.78,23.87l26.343,3.829c6.27,0.911,7.674,5.234,3.138,9.657  L77.277,63.397l4.501,26.237c1.07,6.244-2.608,8.916-8.216,5.968L50,83.215L26.439,95.601z"></path></svg>
     </button>
+    <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-change-star.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
index 26680b6..23c56b4 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star.js
@@ -35,27 +35,10 @@
     },
 
     _handleStarTap: function() {
-      var method = this.change.starred ? 'DELETE' : 'PUT';
-      this.set('change.starred', !this.change.starred);
-      this._send(method, this._restEndpoint()).catch(function(err) {
-        this.set('change.starred', !this.change.starred);
-        alert('Change couldn’t be starred. Check the console and contact ' +
-            'the PolyGerrit team for assistance.');
-        throw err;
-      }.bind(this));
-    },
-
-    _send: function(method, url) {
-      var xhr = document.createElement('gr-request');
-      this._xhrPromise = xhr.send({
-        method: method,
-        url: url,
-      });
-      return this._xhrPromise;
-    },
-
-    _restEndpoint: function() {
-      return '/accounts/self/starred.changes/' + this.change._number;
+      var newVal = !this.change.starred;
+      this.set('change.starred', newVal);
+      this._xhrPromise = this.$.restAPI.saveChangeStarred(this.change._number,
+          newVal);
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
index 8b13a86..969f7dd 100644
--- a/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-change-star/gr-change-star_test.html
@@ -20,8 +20,6 @@
 
 <script src="../../../bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
 <script src="../../../bower_components/web-component-tester/browser.js"></script>
-<script src="../../../bower_components/page/page.js"></script>
-<script src="../../../scripts/util.js"></script>
 
 <link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
 <link rel="import" href="gr-change-star.html">
@@ -35,39 +33,16 @@
 <script>
   suite('gr-change-star tests', function() {
     var element;
-    var server;
 
     setup(function() {
+      stub('gr-rest-api-interface', {
+        saveChangeStarred: function() { return Promise.resolve({ok: true}); },
+      });
       element = fixture('basic');
       element.change = {
         _number: 2,
         starred: true,
       };
-
-      server = sinon.fakeServer.create();
-      server.respondWith(
-        'PUT',
-        '/accounts/self/starred.changes/2',
-        [
-          204,
-          {'Content-Type': 'application/json'},
-          ''
-        ]
-      );
-
-      server.respondWith(
-        'DELETE',
-        '/accounts/self/starred.changes/2',
-        [
-          204,
-          {'Content-Type': 'application/json'},
-          ''
-        ]
-      );
-    });
-
-    teardown(function() {
-      server.restore();
     });
 
     test('star visibility states', function() {
@@ -86,11 +61,7 @@
       element.set('change.starred', false);
       MockInteractions.tap(element.$$('button'));
 
-      server.respond();
-
       element._xhrPromise.then(function(req) {
-        assert.equal(req.status, 204);
-        assert.equal(req.url, '/accounts/self/starred.changes/2');
         assert.equal(element.change.starred, true);
         done();
       });
@@ -100,11 +71,7 @@
       element.set('change.starred', true);
       MockInteractions.tap(element.$$('button'));
 
-      server.respond();
-
       element._xhrPromise.then(function(req) {
-        assert.equal(req.status, 204);
-        assert.equal(req.url, '/accounts/self/starred.changes/2');
         assert.equal(element.change.starred, false);
         done();
       });
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index a77d4e7..d1886e7 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -69,11 +69,12 @@
     }
 
     suite('24 hours time format preference', function() {
-      setup(function() {
+      setup(function(done) {
         return stubRestAPI(
           {time_format: 'HHMM_24', relative_date_in_change_table: false}
         ).then(function() {
           element = fixture('basic');
+          element._loadPreferences().then(function() { done(); });
         });
       });
 
@@ -108,12 +109,13 @@
     });
 
     suite('12 hours time format preference', function() {
-      setup(function() {
+      setup(function(done) {
         // relative_date_in_change_table is not set when false.
         return stubRestAPI(
           {time_format: 'HHMM_12'}
         ).then(function() {
           element = fixture('basic');
+          element._loadPreferences().then(function() { done(); });
         });
       });
 
@@ -130,7 +132,7 @@
           {time_format: 'HHMM_12', relative_date_in_change_table: true}
         ).then(function() {
           element = fixture('basic');
-          done();
+          element._loadPreferences().then(function() { done(); });
         });
       });
 
@@ -153,16 +155,13 @@
           {time_format: 'HHMM_12', relative_date_in_change_table: true}
         ).then(function() {
           element = fixture('basic');
-          done();
+          element._loadPreferences().then(function() { done(); });
         });
       });
 
-      test('Preferences are respected', function(done) {
-        element._loadPreferences().then(function() {
-          assert.equal(element._timeFormat, 'h:mm A');
-          assert.isTrue(element._relative);
-          done();
-        });
+      test('Preferences are respected', function() {
+        assert.equal(element._timeFormat, 'h:mm A');
+        assert.isTrue(element._relative);
       });
     });
 
@@ -170,16 +169,13 @@
       setup(function(done) {
         return stubRestAPI(null).then(function() {
           element = fixture('basic');
-          done();
+          element._loadPreferences().then(function() { done(); });
         });
       });
 
-      test('Default preferences are respected', function(done) {
-        element._loadPreferences().then(function() {
-          assert.equal(element._timeFormat, 'H:mm');
-          assert.isFalse(element._relative);
-          done();
-        });
+      test('Default preferences are respected', function() {
+        assert.equal(element._timeFormat, 'H:mm');
+        assert.isFalse(element._relative);
       });
     });
   });
diff --git a/polygerrit-ui/app/elements/shared/gr-request/gr-request.js b/polygerrit-ui/app/elements/shared/gr-request/gr-request.js
deleted file mode 100644
index be24344..0000000
--- a/polygerrit-ui/app/elements/shared/gr-request/gr-request.js
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright (C) 2016 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-(function() {
-  'use strict';
-
-  Polymer({
-    is: 'gr-request',
-
-    hostAttributes: {
-      hidden: true
-    },
-
-    send: function(options) {
-      options.headers = options.headers || {};
-      if (options.body != null) {
-        options.headers['content-type'] =
-            options.headers['content-type'] || 'application/json';
-      }
-      options.headers['x-gerrit-auth'] = options.headers['x-gerrit-auth'] ||
-          util.getCookie('XSRF_TOKEN');
-      options.jsonPrefix = options.jsonPrefix || ')]}\'';
-      return this.$.xhr.send(options);
-    },
-  });
-})();
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 54ce15c..1ada7e9 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
@@ -68,6 +68,18 @@
   Polymer({
     is: 'gr-rest-api-interface',
 
+    /**
+     * Fired when an server error occurs.
+     *
+     * @event server-error
+     */
+
+    /**
+     * Fired when a network error occurs.
+     *
+     * @event network-error
+     */
+
     properties: {
       _cache: {
         type: Object,
@@ -84,49 +96,58 @@
       opt_opts = opt_opts || {};
 
       var fetchOptions = {
-        credentials: (opt_opts.noCredentials ? undefined : 'same-origin'),
+        credentials: 'same-origin',
         headers: opt_opts.headers,
       };
 
-      var urlWithParams = url;
-      if (opt_params) {
-        var params = [];
-        for (var p in opt_params) {
-          if (opt_params[p] == null) {
-            params.push(encodeURIComponent(p));
-            continue;
-          }
-          params.push(
-            encodeURIComponent(p) + '=' +
-            encodeURIComponent(opt_params[p]));
-        }
-        // Sorting the params leaves the order deterministic which is easier
-        // to test.
-        urlWithParams += '?' + params.sort().join('&');
-      }
-
+      var urlWithParams = this._urlWithParams(url, opt_params);
       return fetch(urlWithParams, fetchOptions).then(function(response) {
         if (opt_cancelCondition && opt_cancelCondition()) {
           response.body.cancel();
           return;
         }
 
-        if (!response.ok && opt_errFn) {
-          opt_errFn.call(null, response);
-          return undefined;
+        if (!response.ok) {
+          if (opt_errFn) {
+            opt_errFn.call(null, response);
+            return undefined;
+          }
+          this.fire('server-error', {response: response});
         }
+
         return this.getResponseObject(response);
       }.bind(this)).catch(function(err) {
-        if (opt_opts.noCredentials) {
-          throw err;
+        if (opt_errFn) {
+          opt_errFn.call(null, null, err);
         } else {
-          // This could be because of a 302 auth redirect. Retry the request.
-          return this.fetchJSON(url, opt_errFn, opt_cancelCondition, opt_params,
-              Object.assign(opt_opts, {noCredentials: true}));
+          this.fire('network-error', {error: err});
+          throw err;
         }
+        throw err;
       }.bind(this));
     },
 
+    _urlWithParams: function(url, opt_params) {
+      if (!opt_params) { return url; }
+
+      var params = [];
+      for (var p in opt_params) {
+        if (opt_params[p] == null) {
+          params.push(encodeURIComponent(p));
+          continue;
+        }
+        var values = [].concat(opt_params[p]);
+        for (var i = 0; i < values.length; i++) {
+          params.push(
+            encodeURIComponent(p) + '=' +
+            encodeURIComponent(values[i]));
+        }
+      }
+      // Sorting the params leaves the order deterministic which is easier
+      // to test.
+      return url + '?' + params.sort().join('&');
+    },
+
     getResponseObject: function(response) {
       return response.text().then(function(text) {
         var result;
@@ -153,7 +174,29 @@
     },
 
     getDiffPreferences: function() {
-      return this._fetchSharedCacheURL('/accounts/self/preferences.diff');
+      return this.getLoggedIn().then(function(loggedIn) {
+        if (loggedIn) {
+          return this._fetchSharedCacheURL('/accounts/self/preferences.diff');
+        }
+        // These defaults should match the defaults in
+        // gerrit-extension-api/src/main/jcg/gerrit/extensions/client/DiffPreferencesInfo.java
+        // NOTE: There are some settings that don't apply to PolyGerrit
+        // (Render mode being at least one of them).
+        return Promise.resolve({
+          auto_hide_diff_table_header: true,
+          context: 10,
+          cursor_blink_rate: 0,
+          ignore_whitespace: 'IGNORE_NONE',
+          intraline_difference: true,
+          line_length: 100,
+          show_line_endings: true,
+          show_tabs: true,
+          show_whitespace_errors: true,
+          syntax_highlighting: true,
+          tab_size: 8,
+          theme: 'DEFAULT',
+        });
+      }.bind(this));
     },
 
     saveDiffPreferences: function(prefs, opt_errFn, opt_ctx) {
@@ -172,7 +215,15 @@
     },
 
     getPreferences: function() {
-      return this._fetchSharedCacheURL('/accounts/self/preferences');
+      return this.getLoggedIn().then(function(loggedIn) {
+        if (loggedIn) {
+          return this._fetchSharedCacheURL('/accounts/self/preferences');
+        }
+
+        return Promise.resolve({
+          changes_per_page: 25,
+        });
+      }.bind(this));
     },
 
     _fetchSharedCacheURL: function(url) {
@@ -197,6 +248,39 @@
       return this._sharedFetchPromises[url];
     },
 
+    getChanges: function(changesPerPage, opt_query, opt_offset) {
+      var options = this._listChangesOptionsToHex(
+          ListChangesOption.LABELS,
+          ListChangesOption.DETAILED_ACCOUNTS
+      );
+      var params = {
+        n: changesPerPage,
+        O: options,
+        S: opt_offset || 0,
+      };
+      if (opt_query && opt_query.length > 0) {
+        params.q = opt_query;
+      }
+      return this.fetchJSON('/changes/', null, null, params);
+    },
+
+    getDashboardChanges: function() {
+      var options = this._listChangesOptionsToHex(
+          ListChangesOption.LABELS,
+          ListChangesOption.DETAILED_ACCOUNTS,
+          ListChangesOption.REVIEWED
+      );
+      var params = {
+        O: options,
+        q: [
+          'is:open owner:self',
+          'is:open reviewer:self -owner:self',
+          'is:closed (owner:self OR reviewer:self) -age:4w limit:10',
+        ],
+      };
+      return this.fetchJSON('/changes/', null, null, params);
+    },
+
     getChangeActionURL: function(changeNum, opt_patchNum, endpoint) {
       return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
     },
@@ -311,6 +395,94 @@
               });
     },
 
+    getChangeSuggestedReviewers: function(changeNum, inputVal, opt_errFn,
+        opt_ctx) {
+      var url = this.getChangeActionURL(changeNum, null, '/suggest_reviewers');
+      return this.fetchJSON(url, opt_errFn, opt_ctx, {
+        n: 10,  // Return max 10 results
+        q: inputVal,
+      });
+    },
+
+    addChangeReviewer: function(changeNum, reviewerID) {
+      return this._sendChangeReviewerRequest('POST', changeNum, reviewerID);
+    },
+
+    removeChangeReviewer: function(changeNum, reviewerID) {
+      return this._sendChangeReviewerRequest('DELETE', changeNum, reviewerID);
+    },
+
+    _sendChangeReviewerRequest: function(method, changeNum, reviewerID) {
+      var url = this.getChangeActionURL(changeNum, null, '/reviewers');
+      var body;
+      switch(method) {
+        case 'POST':
+          body = {reviewer: reviewerID};
+          break;
+        case 'DELETE':
+          url += '/' + reviewerID;
+          break;
+        default:
+          throw Error('Unsupported HTTP method: ' + method);
+      }
+
+      return this.send(method, url, body);
+    },
+
+    getRelatedChanges: function(changeNum, patchNum) {
+      return this.fetchJSON(
+          this.getChangeActionURL(changeNum, patchNum, '/related'));
+    },
+
+    getChangesSubmittedTogether: function(changeNum) {
+      return this.fetchJSON(
+          this.getChangeActionURL(changeNum, null, '/submitted_together'));
+    },
+
+    getChangeConflicts: function(changeNum) {
+      var options = this._listChangesOptionsToHex(
+          ListChangesOption.CURRENT_REVISION,
+          ListChangesOption.CURRENT_COMMIT
+      );
+      var params = {
+        O: options,
+        q: 'status:open is:mergeable conflicts:' + changeNum,
+      };
+      return this.fetchJSON('/changes/', null, null, params);
+    },
+
+    getChangeCherryPicks: function(project, changeID, changeNum) {
+      var options = this._listChangesOptionsToHex(
+          ListChangesOption.CURRENT_REVISION,
+          ListChangesOption.CURRENT_COMMIT
+      );
+      var query = [
+        'project:' + project,
+        'change:' + changeID,
+        '-change:' + changeNum,
+        '-is:abandoned',
+      ].join(' ');
+      var params = {
+        O: options,
+        q: query
+      };
+      return this.fetchJSON('/changes/', null, null, params);
+    },
+
+    getChangesWithSameTopic: function(topic) {
+      var options = this._listChangesOptionsToHex(
+          ListChangesOption.LABELS,
+          ListChangesOption.CURRENT_REVISION,
+          ListChangesOption.CURRENT_COMMIT,
+          ListChangesOption.DETAILED_LABELS
+      );
+      var params = {
+        O: options,
+        q: 'status:open topic:' + topic,
+      };
+      return this.fetchJSON('/changes/', null, null, params);
+    },
+
     getReviewedFiles: function(changeNum, patchNum) {
       return this.fetchJSON(
           this.getChangeActionURL(changeNum, patchNum, '/files?reviewed'));
@@ -331,6 +503,12 @@
       return this.send('POST', url, review, opt_errFn, opt_ctx);
     },
 
+    saveChangeStarred: function(changeNum, starred) {
+      var url = '/accounts/self/starred.changes/' + changeNum;
+      var method = starred ? 'PUT' : 'DELETE';
+      return this.send(method, url);
+    },
+
     send: function(method, url, opt_body, opt_errFn, opt_ctx) {
       var headers = new Headers({
         'X-Gerrit-Auth': this._getCookie('XSRF_TOKEN'),
@@ -347,13 +525,24 @@
         }
         options.body = opt_body;
       }
-      return fetch(url, options).catch(function(err) {
+      return fetch(url, options).then(function(response) {
+        if (!response.ok) {
+          if (opt_errFn) {
+            opt_errFn.call(null, response);
+            return undefined;
+          }
+          this.fire('server-error', {response: response});
+        }
+
+        return response;
+      }.bind(this)).catch(function(err) {
+        this.fire('network-error', {error: err});
         if (opt_errFn) {
-          opt_errFn.call(opt_ctx || this);
+          opt_errFn.call(opt_ctx, null, err);
         } else {
           throw err;
         }
-      });
+      }.bind(this));
     },
 
     getDiff: function(changeNum, basePatchNum, patchNum, path,
@@ -431,6 +620,31 @@
       return this._changeBaseURL(changeNum, opt_patchNum) + endpoint;
     },
 
+    saveDiffDraft: function(changeNum, patchNum, draft) {
+      return this._sendDiffDraftRequest('PUT', changeNum, patchNum, draft);
+    },
+
+    deleteDiffDraft: function(changeNum, patchNum, draft) {
+      return this._sendDiffDraftRequest('DELETE', changeNum, patchNum, draft);
+    },
+
+    _sendDiffDraftRequest: function(method, changeNum, patchNum, draft) {
+      var url = this.getChangeActionURL(changeNum, patchNum, '/drafts');
+      var body;
+      switch(method) {
+        case 'PUT':
+          body = draft;
+          break;
+        case 'DELETE':
+          url += '/' + draft.id;
+          break;
+        default:
+          throw Error('Unsupported HTTP method: ' + method);
+      }
+
+      return this.send(method, url, body);
+    },
+
     _changeBaseURL: function(changeNum, opt_patchNum) {
       var v = '/changes/' + changeNum;
       if (opt_patchNum) {
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index 5fb1906..0004fa9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -83,19 +83,18 @@
     });
 
     test('params are properly encoded', function() {
-      var fetchStub = sinon.stub(window, 'fetch', function() {
-        return Promise.resolve({text: function() {
-          return Promise.resolve(')]}\'\n{}');
-        }});
-      });
-      var params = {
+      var url = element._urlWithParams('/path/', {
         sp: 'hola',
         gr: 'guten tag',
         noval: null,
-      };
-      element.fetchJSON('/path/', null, null, params);
-      assert.equal(fetchStub.args[0][0], '/path/?gr=guten%20tag&noval&sp=hola');
-      fetchStub.restore();
+      });
+      assert.equal(url, '/path/?gr=guten%20tag&noval&sp=hola');
+
+      url = element._urlWithParams('/path/', {
+        sp: 'hola',
+        en: ['hey', 'hi'],
+      });
+      assert.equal(url, '/path/?en=hey&en=hi&sp=hola');
     });
 
     test('request callbacks can be canceled', function(done) {
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index dc12bf9..9d16b84 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -38,6 +38,7 @@
     '../elements/change-list/gr-change-list/gr-change-list_test.html',
     '../elements/change-list/gr-change-list-item/gr-change-list-item_test.html',
     '../elements/core/gr-account-dropdown/gr-account-dropdown_test.html',
+    '../elements/core/gr-error-manager/gr-error-manager_test.html',
     '../elements/core/gr-main-header/gr-main-header_test.html',
     '../elements/core/gr-search-bar/gr-search-bar_test.html',
     '../elements/diff/gr-diff/gr-diff-builder_test.html',
@@ -48,9 +49,9 @@
     '../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
     '../elements/diff/gr-diff-view/gr-diff-view_test.html',
     '../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
-    '../elements/shared/gr-alert/gr-alert_test.html',
     '../elements/shared/gr-account-label/gr-account-label_test.html',
     '../elements/shared/gr-account-link/gr-account-link_test.html',
+    '../elements/shared/gr-alert/gr-alert_test.html',
     '../elements/shared/gr-avatar/gr-avatar_test.html',
     '../elements/shared/gr-change-star/gr-change-star_test.html',
     '../elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html',
diff --git a/tools/maven/api.py b/tools/maven/api.py
deleted file mode 100755
index 600de6a..0000000
--- a/tools/maven/api.py
+++ /dev/null
@@ -1,78 +0,0 @@
-#!/usr/bin/env python
-# Copyright (C) 2015 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.
-
-from __future__ import print_function
-from argparse import ArgumentParser
-from json import loads
-from os import environ, path, remove
-from subprocess import check_call, check_output, Popen, PIPE
-from sys import stderr
-from tempfile import mkstemp
-
-
-def locations():
-  d = Popen('buck audit dependencies api'.split(),
-            stdin=None, stdout=PIPE, stderr=PIPE)
-  t = Popen('xargs buck targets --show_output'.split(),
-            stdin=d.stdout, stdout=PIPE, stderr=PIPE)
-  out = t.communicate()[0]
-  d.wait()
-  targets = []
-  outs = []
-  for e in out.strip().split('\n'):
-    t, o = e.split()
-    targets.append(t)
-    outs.append(o)
-  return dict(zip(targets, outs))
-
-parser = ArgumentParser()
-parser.add_argument('-n', '--dryrun', action='store_true')
-parser.add_argument('-v', '--verbose', action='store_true')
-
-subparsers = parser.add_subparsers(help='action', dest='action')
-subparsers.add_parser('deploy', help='Deploy to Maven (remote)')
-subparsers.add_parser('install', help='Install to Maven (local)')
-
-args = parser.parse_args()
-
-root = path.abspath(__file__)
-while not path.exists(path.join(root, '.buckconfig')):
-  root = path.dirname(root)
-
-if not args.dryrun:
-  check_call('buck build api'.split())
-target = check_output(('buck targets --json api_%s' % args.action).split())
-
-s = loads(target)[0]['cmd']
-
-fd, tempfile = mkstemp()
-s = s.replace('$(exe //tools/maven:mvn)', path.join(root, 'tools/maven/mvn.py'))
-s = s.replace('-o $OUT', '-o %s' % tempfile)
-
-locations = locations()
-
-while '$(location' in s:
-  start = s.index('$(location')
-  end = s.index(')', start)
-  target = s[start+11:end]
-  s = s.replace(s[start:end+1], locations[target])
-
-try:
-  if args.verbose or args.dryrun or environ.get('VERBOSE'):
-    print(s, file=stderr)
-  if not args.dryrun:
-    check_call(s.split())
-finally:
-  remove(tempfile)
diff --git a/tools/maven/api.sh b/tools/maven/api.sh
new file mode 100755
index 0000000..54f6994
--- /dev/null
+++ b/tools/maven/api.sh
@@ -0,0 +1,69 @@
+#!/bin/bash
+
+# Copyright (C) 2015 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.
+
+if [[ "$#" == "0" ]] ; then
+  cat <<EOF
+Usage: run "$0 COMMAND" from the top of your workspace, where
+COMMAND is one of
+
+  install
+  deploy
+  war_install
+  war_deploy
+
+Set VERBOSE in the environment to get more information.
+
+EOF
+
+  exit 1
+fi
+
+set -o errexit
+set -o nounset
+
+
+case "$1" in
+install)
+    command="api_install"
+    ;;
+deploy)
+    command="api_deploy"
+    ;;
+war_install)
+    command="war_install"
+    ;;
+war_deploy)
+    command="war_deploy"
+    ;;
+*)
+    echo "unknown command $1"
+    exit 1
+    ;;
+esac
+
+if [[ "${VERBOSE:-x}" != "x" ]]; then
+  set -o xtrace
+fi
+
+buck build //tools/maven:gen_${command} || \
+  { echo "buck failed to build gen_${command}. Use VERBOSE=1 for more info" ; exit 1 ; }
+
+script="./buck-out/gen/tools/maven/gen_${command}/${command}.sh"
+
+# The PEX wrapper does some funky exit handling, so even if the script
+# does "exit(0)", the return status is '1'. So we can't tell if the
+# following invocation was successful.
+${script}
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 83a33e8..4011d71 100755
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -69,7 +69,12 @@
       file=stderr)
     exit(1)
 
-with open(args.o, 'w') as fd:
+
+out = stderr
+if args.o:
+  out = open(args.o, 'w')
+
+with out as fd:
   if args.repository:
     print('Repository: %s' % args.repository, file=fd)
   if args.url:
diff --git a/tools/maven/package.defs b/tools/maven/package.defs
index 8fe9a13..c3e5595 100644
--- a/tools/maven/package.defs
+++ b/tools/maven/package.defs
@@ -12,6 +12,19 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+sh_bang_template = (' && '.join([
+  "echo '#!/bin/sh -eu' > $OUT",
+  'echo "# this script should run from the root of your workspace." >> $OUT',
+  'echo "" >> $OUT',
+  "echo 'if [[ -n \"$${VERBOSE:-}\" ]]; then set -x ; fi' >> $OUT", 
+  'echo "" >> $OUT',
+  'echo %s >> $OUT',
+  'echo "" >> $OUT',
+  'echo %s >> $OUT',
+  # This is supposed to be handled by executable=True, but it doesn't
+  # work. Bug?
+  'chmod +x $OUT' ]))
+
 def maven_package(
     version,
     repository = None,
@@ -20,44 +33,61 @@
     src = {},
     doc = {},
     war = {}):
-  cmd = ['$(exe //tools/maven:mvn)', '-v', version, '-o', '$OUT']
-  api_cmd = []
+
+  build_cmd = ['buck', 'build']
+
+  mvn_cmd = ['$(exe //tools/maven:mvn)', '-v', version]
+  api_cmd = mvn_cmd[:]
+  api_targets = [ '//tools/maven:mvn' ]
   for type,d in [('jar', jar), ('java-source', src), ('javadoc', doc)]:
-    for a,t in d.iteritems():
+    for a,t in sorted(d.iteritems()):
       api_cmd.append('-s %s:%s:$(location %s)' % (a,type,t))
+      api_targets.append(t)
 
   genrule(
-    name = 'api_install',
-    cmd = ' '.join(cmd + api_cmd + ['-a', 'install']),
-    out = 'api_install.info',
+    name = 'gen_api_install',
+    cmd = sh_bang_template % (
+      ' '.join(build_cmd + api_targets),
+      ' '.join(api_cmd + ['-a', 'install'])),
+    out = 'api_install.sh',
+    executable = True,
   )
 
   if repository and url:
     genrule(
-      name = 'api_deploy',
-      cmd = ' '.join(cmd + api_cmd + [
-        '-a', 'deploy',
-        '--repository', repository,
-        '--url', url]),
-      out = 'api_deploy.info',
+      name = 'gen_api_deploy',
+      cmd = sh_bang_template % (
+        ' '.join(build_cmd + api_targets),
+        ' '.join(api_cmd + ['-a', 'deploy',
+                            '--repository', repository,
+                            '--url', url])),
+      out = 'api_deploy.sh',
+      executable = True,
     )
 
-  war_cmd = []
-  for a,t in war.iteritems():
+  war_cmd = mvn_cmd[:]
+  war_targets = [ '//tools/maven:mvn' ]
+  for a,t in sorted(war.iteritems()):
     war_cmd.append('-s %s:war:$(location %s)' % (a,t))
+    war_targets.append(t)
 
   genrule(
-    name = 'war_install',
-    cmd = ' '.join(cmd + war_cmd + ['-a', 'install']),
-    out = 'war_install.info',
+    name = 'gen_war_install',
+    cmd = sh_bang_template % (' '.join(build_cmd + war_targets),
+                              ' '.join(war_cmd + ['-a', 'install'])),
+    out = 'war_install.sh',
+    executable = True,
   )
 
   if repository and url:
     genrule(
-      name = 'war_deploy',
-      cmd = ' '.join(cmd + war_cmd + [
+      name = 'gen_war_deploy',
+      cmd = sh_bang_template % (
+          ' '.join(build_cmd + war_targets),
+          ' '.join(war_cmd + [
         '-a', 'deploy',
         '--repository', repository,
-        '--url', url]),
-      out = 'war_deploy.info',
+        '--url', url])),
+      out = 'war_deploy.sh',
+      executable = True,
     )