Merge changes from topic 'abstract-notes-migration'

* changes:
  Use different NotesMigration implementation for testing
  Ensure NotesMigration method calls aren't cached
  Extract an abstract base class for NotesMigration
diff --git a/.buckconfig b/.buckconfig
index 7814eec..61c28e4 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -22,7 +22,7 @@
 
 [java]
   jar_spool_mode = direct_to_jar
-  src_roots = java, resources
+  src_roots = java, resources, src
 
 [project]
   ignore = .git, eclipse-out
@@ -34,3 +34,7 @@
 
 [test]
   excluded_labels = manual
+
+[repositories]
+  jgit = lib/jgit
+
diff --git a/.mailmap b/.mailmap
index c8e2f82..598d52d 100644
--- a/.mailmap
+++ b/.mailmap
@@ -9,7 +9,8 @@
 David Ostrovsky <david@ostrovsky.org>                                                       <d.ostrovsky@gmx.de>
 Deniz Türkoglu <deniz@spotify.com>                                                          Deniz Türkoglu <deniz@spotify.com>
 Deniz Türkoglu <deniz@spotify.com>                                                          Deniz Turkoglu <deniz@spotify.com>
-Edwin Kempin <edwin.kempin@sap.com>                                                         Edwin Kempin <edwin.kempin@gmail.com>
+Edwin Kempin <ekempin@google.com>                                                           Edwin Kempin <edwin.kempin@gmail.com>
+Edwin Kempin <ekempin@google.com>                                                           Edwin Kempin <edwin.kempin@sap.com>
 Eryk Szymanski <eryksz@gmail.com>                                                           <eryksz@google.com>
 Fredrik Luthander <fredrik.luthander@sonymobile.com>                                        <fredrik@gandaraj.com>
 Fredrik Luthander <fredrik.luthander@sonymobile.com>                                        <fredrik.luthander@sonyericsson.com>
diff --git a/Documentation/images/link.png b/Documentation/images/link.png
index 621443e..25eacb7 100644
--- a/Documentation/images/link.png
+++ b/Documentation/images/link.png
Binary files differ
diff --git a/Documentation/pgm-init.txt b/Documentation/pgm-init.txt
index 6aa3a74..9fe813b 100644
--- a/Documentation/pgm-init.txt
+++ b/Documentation/pgm-init.txt
@@ -12,6 +12,8 @@
 	[--list-plugins]
 	[--install-plugin=<PLUGIN_NAME>]
         [--dev]
+	[--skip-all-downloads]
+	[--skip-download=<LIBRARY_NAME>]
 --
 
 == DESCRIPTION
@@ -56,6 +58,14 @@
 	Install in developer mode. Default configuration settings are
 	chosen to run the Gerrit server as a developer.
 
+--skip-all-downloads::
+	Do not automatically download and install required libraries. The
+	administrator must provision the required libraries in the lib/ folder.
+
+--skip-download::
+	Do not automatically download and install the library with the given name.
+	The administrator must provision the required library in the lib/ folder.
+
 == CONTEXT
 This command can only be run on a server which has direct
 connectivity to the metadata database, and local access to the
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index a5dffd1..7a907ba 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -13,10 +13,8 @@
 'POST /changes'
 --
 
-The change info link:#change-info[ChangeInfo] entity must be provided in the
-request body. Only the following attributes are honored: `project`,
-`branch`, `subject`, `status` and `topic`. The first three attributes are
-mandatory. Valid values for status are: `DRAFT` and `NEW`.
+The change input link:#change-input[ChangeInput] entity must be provided in the
+request body.
 
 .Request
 ----
@@ -4016,9 +4014,29 @@
 |`problems`           |optional|
 A list of link:#problem-info[ProblemInfo] entities describing potential
 problems with this change. Only set if link:#check[CHECK] is set.
+|==================================
+
+[[change-input]]
+=== ChangeInput
+The `ChangeInput` entity contains information about creating a new change.
+
+[options="header",cols="1,^1,5"]
+|==================================
+|Field Name           ||Description
+|`project`            ||The name of the project.
+|`branch`             ||
+The name of the target branch. +
+The `refs/heads/` prefix is omitted.
+|`subject`            ||
+The subject of the change (header line of the commit message).
+|`topic`              |optional|The topic to which this change belongs.
+|`status`             |optional, default to `NEW`|
+The status of the change (only `NEW` and `DRAFT` accepted here).
 |`base_change`        |optional|
 A link:#change-id[\{change-id\}] that identifies the base change for a create
-change operation. Only used for the link:#create-change[CreateChange] endpoint.
+change operation.
+|`new_branch`         |optional, default to `false`|
+Allow creating a new branch when set to `true`.
 |==================================
 
 [[change-message-info]]
diff --git a/contrib/abandon_stale.py b/contrib/abandon_stale.py
index 958077d..5f5b9ef 100755
--- a/contrib/abandon_stale.py
+++ b/contrib/abandon_stale.py
@@ -135,7 +135,7 @@
         query_terms = ["status:new", "age:%s" % options.age]
         if options.branches:
             query_terms += ["branch:%s" % b for b in options.branches]
-        elif options.exclude_branch:
+        elif options.exclude_branches:
             query_terms += ["-branch:%s" % b for b in options.exclude_branches]
         if options.projects:
             query_terms += ["project:%s" % p for p in options.projects]
diff --git a/gerrit-acceptance-framework/BUCK b/gerrit-acceptance-framework/BUCK
index 9ee1572..2e4218e 100644
--- a/gerrit-acceptance-framework/BUCK
+++ b/gerrit-acceptance-framework/BUCK
@@ -14,9 +14,9 @@
   '//lib/httpcomponents:httpclient',
   '//lib/httpcomponents:httpcore',
   '//lib/jetty:servlet',
-  '//lib/jgit:junit',
   '//lib/log:impl_log4j',
   '//lib/log:log4j',
+  '@jgit//:junit',
 ]
 
 PROVIDED = [
@@ -29,10 +29,10 @@
   '//gerrit-reviewdb:server',
   '//gerrit-server:server',
   '//lib:gson',
-  '//lib/jgit:jgit',
   '//lib:jsch',
   '//lib/mina:sshd',
   '//lib:servlet-api-3_1',
+  '@jgit//:jgit',
 ]
 
 java_binary(
@@ -85,8 +85,8 @@
     '//lib/guice:guice-servlet',
     '//lib/guice:javax-inject',
     '//lib:gwtorm_client',
-    '//lib:junit__jar',
-    '//lib:truth__jar',
+    '//lib:junit',
+    '//lib:truth',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
index af3e5eb..5788ec9 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/PushOneCommit.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
@@ -263,7 +264,7 @@
 
     public void assertChange(Change.Status expectedStatus,
         String expectedTopic, TestAccount... expectedReviewers)
-        throws OrmException {
+        throws OrmException, NoSuchChangeException {
       Change c = getChange().change();
       assertThat(c.getSubject()).isEqualTo(resSubj);
       assertThat(c.getStatus()).isEqualTo(expectedStatus);
@@ -272,9 +273,9 @@
     }
 
     private void assertReviewers(Change c, TestAccount... expectedReviewers)
-        throws OrmException {
+        throws OrmException, NoSuchChangeException {
       Iterable<Account.Id> actualIds = approvalsUtil
-          .getReviewers(db, notesFactory.create(db, c.getProject(), c.getId()))
+          .getReviewers(db, notesFactory.createChecked(db, c))
           .values();
       assertThat(actualIds).containsExactlyElementsIn(
           Sets.newHashSet(TestAccount.ids(expectedReviewers)));
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 7f9ffa6..ebc5d00 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -32,9 +32,9 @@
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
     '//lib/log:api',
     '//lib/mina:sshd',
+    '@jgit//:jgit',
   ],
   visibility = [
     '//gerrit-plugin-api/...',
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 6ac86a5..0baf527 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
@@ -48,6 +48,7 @@
 import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.common.ApprovalInfo;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.common.GitPerson;
 import com.google.gerrit.extensions.common.LabelInfo;
@@ -585,7 +586,7 @@
 
   @Test
   public void createEmptyChange() throws Exception {
-    ChangeInfo in = new ChangeInfo();
+    ChangeInput in = new ChangeInput();
     in.branch = Constants.MASTER;
     in.subject = "Create a change from the API";
     in.project = project.get();
@@ -981,6 +982,38 @@
     }
   }
 
+  @Test
+  public void createEmptyChangeOnNonExistingBranch() throws Exception {
+    ChangeInput in = new ChangeInput();
+    in.branch = "foo";
+    in.subject = "Create a change on new branch from the API";
+    in.project = project.get();
+    in.newBranch = true;
+    ChangeInfo info = gApi
+        .changes()
+        .create(in)
+        .get();
+    assertThat(info.project).isEqualTo(in.project);
+    assertThat(info.branch).isEqualTo(in.branch);
+    assertThat(info.subject).isEqualTo(in.subject);
+    assertThat(Iterables.getOnlyElement(info.messages).message)
+        .isEqualTo("Uploaded patch set 1.");
+  }
+
+  @Test
+  public void createEmptyChangeOnExistingBranchWithNewBranch() throws Exception {
+    ChangeInput in = new ChangeInput();
+    in.branch = Constants.MASTER;
+    in.subject = "Create a change on new branch from the API";
+    in.project = project.get();
+    in.newBranch = true;
+
+    exception.expect(ResourceConflictException.class);
+    gApi.changes()
+        .create(in)
+        .get();
+  }
+
   private static Iterable<Account.Id> getReviewers(
       Collection<AccountInfo> r) {
     return Iterables.transform(r, new Function<AccountInfo, Account.Id>() {
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
index 813c9c9..4dbd6ad 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmitOnPushIT.java
@@ -29,6 +29,7 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.ApprovalsUtil;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -219,13 +220,15 @@
   }
 
   private PatchSetApproval getSubmitter(PatchSet.Id patchSetId)
-      throws OrmException {
+      throws OrmException, NoSuchChangeException {
     ChangeNotes notes =
-        notesFactory.create(db, project, patchSetId.getParentKey()).load();
+        notesFactory.createChecked(db, project, patchSetId.getParentKey())
+            .load();
     return approvalsUtil.getSubmitter(db, notes, patchSetId);
   }
 
-  private void assertSubmitApproval(PatchSet.Id patchSetId) throws OrmException {
+  private void assertSubmitApproval(PatchSet.Id patchSetId)
+      throws OrmException, NoSuchChangeException {
     PatchSetApproval a = getSubmitter(patchSetId);
     assertThat(a.isSubmit()).isTrue();
     assertThat(a.getValue()).isEqualTo((short) 1);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
index 0e47123..35ae221 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/VisibleRefFilterIT.java
@@ -34,7 +34,6 @@
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.server.edit.ChangeEditModifier;
 import com.google.gerrit.server.git.ProjectConfig;
-import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.Util;
 import com.google.inject.Inject;
 
@@ -187,16 +186,16 @@
     allow(Permission.READ, REGISTERED_USERS, "refs/heads/master");
     deny(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
 
-    ChangeNotes notes = notesFactory.create(db, project, c1);
+    Change c = notesFactory.createChecked(db, project, c1).getChange();
     PatchSet ps1 = getPatchSet(new PatchSet.Id(c1, 1));
 
     // Admin's edit is not visible.
     setApiUser(admin);
-    editModifier.createEdit(notes.getChange(), ps1);
+    editModifier.createEdit(c, ps1);
 
     // User's edit is visible.
     setApiUser(user);
-    editModifier.createEdit(notes.getChange(), ps1);
+    editModifier.createEdit(c, ps1);
 
     assertRefs(
         "HEAD",
@@ -214,12 +213,12 @@
       deny(Permission.READ, REGISTERED_USERS, "refs/heads/master");
       allow(Permission.READ, REGISTERED_USERS, "refs/heads/branch");
 
-      ChangeNotes notes = notesFactory.create(db, project, c1);
+      Change c = notesFactory.createChecked(db, project, c1).getChange();
       PatchSet ps1 = getPatchSet(new PatchSet.Id(c1, 1));
       setApiUser(admin);
-      editModifier.createEdit(notes.getChange(), ps1);
+      editModifier.createEdit(c, ps1);
       setApiUser(user);
-      editModifier.createEdit(notes.getChange(), ps1);
+      editModifier.createEdit(c, ps1);
 
       assertRefs(
           // Change 1 is visible due to accessDatabase capability, even though
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 394eb5f..1d99bfd 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
@@ -57,6 +57,7 @@
 import com.google.gerrit.server.events.ChangeMergedEvent;
 import com.google.gerrit.server.events.Event;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.testutil.ConfigSuite;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -248,8 +249,10 @@
       if (!expectedExceptionType.isAssignableFrom(e.getClass())
           || !e.getMessage().equals(expectedExceptionMsg)) {
         throw new AssertionError("Expected exception of type "
-            + expectedExceptionType.getSimpleName() + " with message: "
-            + expectedExceptionMsg, e);
+            + expectedExceptionType.getSimpleName() + " with message: \""
+            + expectedExceptionMsg + "\" but got exception of type "
+            + e.getClass().getSimpleName() + " with message \""
+            + e.getMessage() + "\"", e);
       }
       return;
     }
@@ -309,10 +312,10 @@
   }
 
   protected void assertSubmitter(String changeId, int psId)
-      throws OrmException {
+      throws OrmException, NoSuchChangeException {
     Change c =
         getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
-    ChangeNotes cn = notesFactory.create(db, c.getProject(), c.getId());
+    ChangeNotes cn = notesFactory.createChecked(db, c);
     PatchSetApproval submitter = approvalsUtil.getSubmitter(
         db, cn, new PatchSet.Id(cn.getChangeId(), psId));
     assertThat(submitter).isNotNull();
@@ -321,10 +324,10 @@
   }
 
   protected void assertNoSubmitter(String changeId, int psId)
-      throws OrmException {
+      throws OrmException, NoSuchChangeException {
     Change c =
         getOnlyElement(queryProvider.get().byKeyPrefix(changeId)).change();
-    ChangeNotes cn = notesFactory.create(db, c.getProject(), c.getId());
+    ChangeNotes cn = notesFactory.createChecked(db, c);
     PatchSetApproval submitter = approvalsUtil.getSubmitter(
         db, cn, new PatchSet.Id(cn.getChangeId(), psId));
     assertThat(submitter).isNull();
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 1e0dc12..063a795 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
@@ -23,6 +23,7 @@
 import com.google.gerrit.acceptance.NoHttpd;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -61,7 +62,7 @@
 
   @Test
   public void createEmptyChange_MissingBranch() throws Exception {
-    ChangeInfo ci = new ChangeInfo();
+    ChangeInput ci = new ChangeInput();
     ci.project = project.get();
     assertCreateFails(ci, BadRequestException.class,
         "branch must be non-empty");
@@ -69,7 +70,7 @@
 
   @Test
   public void createEmptyChange_MissingMessage() throws Exception {
-    ChangeInfo ci = new ChangeInfo();
+    ChangeInput ci = new ChangeInput();
     ci.project = project.get();
     ci.branch = "master";
     assertCreateFails(ci, BadRequestException.class,
@@ -78,26 +79,26 @@
 
   @Test
   public void createEmptyChange_InvalidStatus() throws Exception {
-    ChangeInfo ci = newChangeInfo(ChangeStatus.MERGED);
+    ChangeInput ci = newChangeInput(ChangeStatus.MERGED);
     assertCreateFails(ci, BadRequestException.class,
         "unsupported change status");
   }
 
   @Test
   public void createNewChange() throws Exception {
-    assertCreateSucceeds(newChangeInfo(ChangeStatus.NEW));
+    assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
   }
 
   @Test
   public void createDraftChange() throws Exception {
     assume().that(isAllowDrafts()).isTrue();
-    assertCreateSucceeds(newChangeInfo(ChangeStatus.DRAFT));
+    assertCreateSucceeds(newChangeInput(ChangeStatus.DRAFT));
   }
 
   @Test
   public void createDraftChangeNotAllowed() throws Exception {
     assume().that(isAllowDrafts()).isFalse();
-    ChangeInfo ci = newChangeInfo(ChangeStatus.DRAFT);
+    ChangeInput ci = newChangeInput(ChangeStatus.DRAFT);
     assertCreateFails(ci, MethodNotAllowedException.class,
         "draft workflow is disabled");
   }
@@ -106,7 +107,7 @@
   public void notedbCommit() throws Exception {
     assume().that(notesMigration.enabled()).isTrue();
 
-    ChangeInfo c = assertCreateSucceeds(newChangeInfo(ChangeStatus.NEW));
+    ChangeInfo c = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
     try (Repository repo = repoManager.openMetadataRepository(project);
         RevWalk rw = new RevWalk(repo)) {
       RevCommit commit = rw.parseCommit(
@@ -126,8 +127,8 @@
     }
   }
 
-  private ChangeInfo newChangeInfo(ChangeStatus status) {
-    ChangeInfo in = new ChangeInfo();
+  private ChangeInput newChangeInput(ChangeStatus status) {
+    ChangeInput in = new ChangeInput();
     in.project = project.get();
     in.branch = "master";
     in.subject = "Empty change";
@@ -136,7 +137,7 @@
     return in;
   }
 
-  private ChangeInfo assertCreateSucceeds(ChangeInfo in) throws Exception {
+  private ChangeInfo assertCreateSucceeds(ChangeInput in) throws Exception {
     ChangeInfo out = gApi.changes().create(in).get();
     assertThat(out.branch).isEqualTo(in.branch);
     assertThat(out.subject).isEqualTo(in.subject);
@@ -149,7 +150,7 @@
     return out;
   }
 
-  private void assertCreateFails(ChangeInfo in,
+  private void assertCreateFails(ChangeInput in,
       Class<? extends RestApiException> errType, String errSubstring)
       throws Exception {
     exception.expect(errType);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
index f32b69b..13be15c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/change/ConsistencyCheckerIT.java
@@ -223,7 +223,7 @@
     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
     assertThat(p.outcome).isEqualTo("Deleted patch set");
 
-    c = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     assertThat(c.currentPatchSetId().get()).isEqualTo(1);
     assertThat(getPatchSet(ps1.getId())).isNotNull();
     assertThat(getPatchSet(ps2.getId())).isNull();
@@ -271,7 +271,7 @@
     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
     assertThat(p.outcome).isEqualTo("Deleted patch set");
 
-    c = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     assertThat(c.currentPatchSetId().get()).isEqualTo(3);
     assertThat(getPatchSet(ps1.getId())).isNotNull();
     assertThat(getPatchSet(ps2.getId())).isNull();
@@ -299,7 +299,7 @@
     assertThat(p.outcome)
         .isEqualTo("Cannot delete patch set; no patch sets would remain");
 
-    c = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     assertThat(c.currentPatchSetId().get()).isEqualTo(1);
     assertThat(getPatchSet(ps1.getId())).isNotNull();
   }
@@ -387,7 +387,7 @@
     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
     assertThat(p.outcome).isEqualTo("Marked change as merged");
 
-    c = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     assertThat(c.getStatus()).isEqualTo(Change.Status.MERGED);
     assertProblems(c);
   }
@@ -476,7 +476,7 @@
     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
     assertThat(p.outcome).isEqualTo("Inserted as patch set 2");
 
-    c = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     PatchSet.Id psId2 = new PatchSet.Id(c.getId(), 2);
     assertThat(c.currentPatchSetId()).isEqualTo(psId2);
     assertThat(getPatchSet(psId2).getRevision().get())
@@ -518,7 +518,7 @@
     assertThat(p.status).isEqualTo(ProblemInfo.Status.FIXED);
     assertThat(p.outcome).isEqualTo("Inserted as patch set 2");
 
-    c  = notesFactory.create(db, project, c.getId()).getChange();
+    c = notesFactory.createChecked(db, c).getChange();
     PatchSet.Id psId2 = new PatchSet.Id(c.getId(), 2);
     assertThat(c.currentPatchSetId()).isEqualTo(psId2);
     assertThat(getPatchSet(psId2).getRevision().get())
diff --git a/gerrit-cache-h2/BUCK b/gerrit-cache-h2/BUCK
index 37c8b96..7790a23 100644
--- a/gerrit-cache-h2/BUCK
+++ b/gerrit-cache-h2/BUCK
@@ -8,8 +8,8 @@
     '//lib:guava',
     '//lib:h2',
     '//lib/guice:guice',
-    '//lib/jgit:jgit',
     '//lib/log:api',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-common/BUCK b/gerrit-common/BUCK
index a36a9b7..10bb754 100644
--- a/gerrit-common/BUCK
+++ b/gerrit-common/BUCK
@@ -44,9 +44,9 @@
     '//lib:gwtjsonrpc',
     '//lib:gwtorm',
     '//lib:guava',
-    '//lib/jgit:jgit',
     '//lib/joda:joda-time',
     '//lib/log:api',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
index da8aeb2..b1381e7 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/changes/Changes.java
@@ -16,6 +16,7 @@
 
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
 
@@ -58,7 +59,7 @@
   ChangeApi id(String project, String branch, String id)
       throws RestApiException;
 
-  ChangeApi create(ChangeInfo in) throws RestApiException;
+  ChangeApi create(ChangeInput in) throws RestApiException;
 
   QueryRequest query();
   QueryRequest query(String query);
@@ -156,7 +157,7 @@
     }
 
     @Override
-    public ChangeApi create(ChangeInfo in) throws RestApiException {
+    public ChangeApi create(ChangeInput in) throws RestApiException {
       throw new NotImplementedException();
     }
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
index d697502..34b19c2 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInfo.java
@@ -43,7 +43,6 @@
   public Integer insertions;
   public Integer deletions;
 
-  public String baseChange;
   public int _number;
 
   public AccountInfo owner;
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInput.java
new file mode 100644
index 0000000..9f0df93
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/common/ChangeInput.java
@@ -0,0 +1,28 @@
+// 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.common;
+
+import com.google.gerrit.extensions.client.ChangeStatus;
+
+public class ChangeInput {
+  public String project;
+  public String branch;
+  public String subject;
+
+  public String topic;
+  public ChangeStatus status;
+  public String baseChange;
+  public Boolean newBranch;
+}
diff --git a/gerrit-gpg/BUCK b/gerrit-gpg/BUCK
index 2b258a9..6d6579c 100644
--- a/gerrit-gpg/BUCK
+++ b/gerrit-gpg/BUCK
@@ -8,8 +8,8 @@
   '//lib/guice:guice',
   '//lib/guice:guice-assistedinject',
   '//lib/guice:guice-servlet',
-  '//lib/jgit:jgit',
   '//lib/log:api',
+  '@jgit//:jgit',
 ]
 
 java_library(
@@ -45,12 +45,12 @@
     ':gpg',
     ':testutil',
     '//gerrit-cache-h2:cache-h2',
-    '//gerrit-lucene:lucene',  
+    '//gerrit-lucene:lucene',
     '//gerrit-server:testutil',
     '//lib:truth',
     '//lib/bouncycastle:bcpg',
     '//lib/bouncycastle:bcprov',
-    '//lib/jgit:junit',
+    '@jgit//:junit',
   ],
   source_under_test = [':gpg'],
   visibility = ['//tools/eclipse:classpath'],
diff --git a/gerrit-gwtui-common/BUCK b/gerrit-gwtui-common/BUCK
index 436714a..6aa2d98 100644
--- a/gerrit-gwtui-common/BUCK
+++ b/gerrit-gwtui-common/BUCK
@@ -64,7 +64,7 @@
     ':client',
     '//lib:junit',
     '//lib/gwt:user',
-    '//lib/jgit:jgit',
+    '@jgit//:jgit',
   ],
   source_under_test = [':client'],
   vm_args = ['-Xmx512m'],
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 879b8fa..0f0bde4 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -25,7 +25,6 @@
   gwt_xml = 'src/main/java/%s.gwt.xml' % MODULE.replace('.', '/'),
   resources = glob(['src/main/java/**/*']),
   deps = [
-    ':freebie_application_icon_set',
     ':silk_icons',
     '//gerrit-gwtui-common:diffy_logo',
     '//gerrit-gwtui-common:client',
@@ -41,14 +40,6 @@
 )
 
 java_library(
-  name = 'freebie_application_icon_set',
-  deps = [
-    '//lib:LICENSE-freebie_application_icon_set',
-    '//lib:LICENSE-CC-BY3.0',
-  ],
-)
-
-java_library(
   name = 'silk_icons',
   deps = [
     '//lib:LICENSE-silk_icons',
@@ -69,7 +60,6 @@
     '//lib:junit',
     '//lib/gwt:dev',
     '//lib/gwt:user',
-    '//lib/gwt:gwt-test-utils',
   ],
   source_under_test = [':ui_module'],
   vm_args = ['-Xmx512m'],
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
index a546c62..670b5c8 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/editor/EditScreen.java
@@ -257,20 +257,7 @@
     return new Runnable() {
       @Override
       public void run() {
-        String n = Window.prompt(EditConstants.I.gotoLineNumber(), "");
-        if (n != null) {
-          try {
-            int line = Integer.parseInt(n);
-            line--;
-            if (line >= 0) {
-              cm.scrollToLine(line);
-            }
-          } catch (NumberFormatException e) {
-            // ignore non valid numbers
-            // We don't want to popup another ugly dialog just to say
-            // "The number you've provided is invalid, try again"
-          }
-        }
+        cm.execCommand("jumpToLine");
       }
     };
   }
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java
deleted file mode 100644
index 04dccdb..0000000
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/FormatUtilTest.java
+++ /dev/null
@@ -1,62 +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.
-
-package com.google.gerrit.client;
-
-import static com.google.gerrit.client.FormatUtil.formatBytes;
-import static com.google.gerrit.client.FormatUtil.formatPercentage;
-import static org.junit.Assert.assertEquals;
-
-import com.googlecode.gwt.test.GwtModule;
-import com.googlecode.gwt.test.GwtTest;
-
-import org.junit.Ignore;
-import org.junit.Test;
-
-@GwtModule("com.google.gerrit.GerritGwtUI")
-@Ignore
-public class FormatUtilTest extends GwtTest {
-  @Test
-  public void testFormatBytes() {
-    assertEquals("+/- 0 B", formatBytes(0));
-    assertEquals("+27 B", formatBytes(27));
-    assertEquals("+999 B", formatBytes(999));
-    assertEquals("+1000 B", formatBytes(1000));
-    assertEquals("+1023 B", formatBytes(1023));
-    assertEquals("+1.0 KiB", formatBytes(1024));
-    assertEquals("+1.7 KiB", formatBytes(1728));
-    assertEquals("+108.0 KiB", formatBytes(110592));
-    assertEquals("+6.8 MiB", formatBytes(7077888));
-    assertEquals("+432.0 MiB", formatBytes(452984832));
-    assertEquals("+27.0 GiB", formatBytes(28991029248L));
-    assertEquals("+1.7 TiB", formatBytes(1855425871872L));
-    assertEquals("+8.0 EiB", formatBytes(9223372036854775807L));
-
-    assertEquals("-27 B", formatBytes(-27));
-    assertEquals("-1.7 MiB", formatBytes(-1728));
-  }
-
-  @Test
-  public void testFormatPercentage() {
-    assertEquals("N/A", formatPercentage(0, 10));
-    assertEquals("0%", formatPercentage(100, 0));
-    assertEquals("+25%", formatPercentage(100, 25));
-    assertEquals("-25%", formatPercentage(100, -25));
-    assertEquals("+50%", formatPercentage(100, 50));
-    assertEquals("-50%", formatPercentage(100, -50));
-    assertEquals("+100%", formatPercentage(100, 100));
-    assertEquals("-100%", formatPercentage(100, -100));
-    assertEquals("+500%", formatPercentage(100, 500));
-  }
-}
diff --git a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java b/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
deleted file mode 100644
index d751f34..0000000
--- a/gerrit-gwtui/src/test/java/com/google/gerrit/client/diff/EditIteratorTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.client.diff;
-
-import static org.junit.Assert.assertEquals;
-
-import com.google.gwt.core.client.JavaScriptObject;
-import com.google.gwt.core.client.JsArrayString;
-
-import com.googlecode.gwt.test.GwtModule;
-import com.googlecode.gwt.test.GwtTest;
-
-import net.codemirror.lib.Pos;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/** Unit tests for EditIterator */
-@GwtModule("com.google.gerrit.GerritGwtUI")
-@Ignore
-// TODO(davido): Enable this again when gwt-test-utils lib is fixed.
-public class EditIteratorTest extends GwtTest {
-  private JsArrayString lines;
-
-  private void assertLineChsEqual(Pos a, Pos b) {
-    assertEquals(a.line() + "," + a.ch(), b.line() + "," + b.ch());
-  }
-
-  @Before
-  public void initialize() {
-    lines = (JsArrayString) JavaScriptObject.createArray();
-    lines.push("1st");
-    lines.push("2nd");
-    lines.push("3rd");
-  }
-
-  @Test
-  public void testNegativeAdvance() {
-    EditIterator i = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(1, 1), i.advance(5));
-    assertLineChsEqual(Pos.create(0, 3), i.advance(-2));
-  }
-
-  @Test
-  public void testNoAdvance() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(0), iter.advance(0));
-  }
-
-  @Test
-  public void testSimpleAdvance() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(0, 1), iter.advance(1));
-  }
-
-  @Test
-  public void testEndsBeforeNewline() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(0, 3), iter.advance(3));
-  }
-
-  @Test
-  public void testEndsOnNewline() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(1), iter.advance(4));
-  }
-
-  @Test
-  public void testAcrossNewline() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(1, 1), iter.advance(5));
-  }
-
-  @Test
-  public void testContinueFromBeforeNewline() {
-    EditIterator iter = new EditIterator(lines, 0);
-    iter.advance(3);
-    assertLineChsEqual(Pos.create(2, 2), iter.advance(7));
-  }
-
-  @Test
-  public void testContinueFromAfterNewline() {
-    EditIterator iter = new EditIterator(lines, 0);
-    iter.advance(4);
-    assertLineChsEqual(Pos.create(2, 2), iter.advance(6));
-  }
-
-  @Test
-  public void testAcrossMultipleLines() {
-    EditIterator iter = new EditIterator(lines, 0);
-    assertLineChsEqual(Pos.create(2, 2), iter.advance(10));
-  }
-}
diff --git a/gerrit-httpd/BUCK b/gerrit-httpd/BUCK
index 7988cf6..3eff7d9 100644
--- a/gerrit-httpd/BUCK
+++ b/gerrit-httpd/BUCK
@@ -33,10 +33,10 @@
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
-    '//lib/jgit:jgit-servlet',
     '//lib/log:api',
     '//lib/lucene:lucene-core-and-backward-codecs',
+    '@jgit//:jgit',
+    '@jgit//:jgit-servlet',
   ],
   provided_deps = ['//lib:servlet-api-3_1'],
   visibility = ['PUBLIC'],
@@ -69,9 +69,9 @@
     '//lib/easymock:easymock',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
-    '//lib/jgit:junit',
     '//lib/joda:joda-time',
+    '@jgit//:jgit',
+    '@jgit//:junit',
   ],
   source_under_test = [':httpd'],
   # TODO(sop) Remove after Buck supports Eclipse
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
index 5a6df97..7b524df 100644
--- a/gerrit-lucene/BUCK
+++ b/gerrit-lucene/BUCK
@@ -31,11 +31,11 @@
     '//lib:gwtorm',
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
-    '//lib/jgit:jgit',
     '//lib/log:api',
     '//lib/lucene:lucene-analyzers-common',
     '//lib/lucene:lucene-core-and-backward-codecs',
     '//lib/lucene:lucene-misc',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-openid/BUCK b/gerrit-openid/BUCK
index 0a6363b..9e99399 100644
--- a/gerrit-openid/BUCK
+++ b/gerrit-openid/BUCK
@@ -19,8 +19,8 @@
     '//lib/commons:codec',
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
     '//lib/log:api',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-patch-jgit/BUCK b/gerrit-patch-jgit/BUCK
index b54499f..434be37 100644
--- a/gerrit-patch-jgit/BUCK
+++ b/gerrit-patch-jgit/BUCK
@@ -9,12 +9,33 @@
   gwt_xml = SRC + 'JGit.gwt.xml',
   deps = [
     '//lib:gwtjsonrpc',
-    '//lib/jgit:Edit',
+    ':Edit',
   ],
   provided_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
 )
 
+gwt_module(
+  name = 'Edit',
+  srcs = [':jgit_edit_src'],
+  deps = [':edit_src'],
+  visibility = ['PUBLIC'],
+)
+
+prebuilt_jar(
+  name = 'edit_src',
+  binary_jar = ':jgit_edit_src',
+)
+
+genrule(
+  name = 'jgit_edit_src',
+  cmd = 'unzip -qd $TMP $(location @jgit//:jgit_src) ' +
+    'org/eclipse/jgit/diff/Edit.java;' +
+    'cd $TMP;' +
+    'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java',
+  out = 'edit.src.zip',
+)
+
 java_library(
   name = 'server',
   srcs = [
@@ -25,7 +46,7 @@
   ],
   deps = [
     '//lib:gson',
-    '//lib/jgit:jgit',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
@@ -35,8 +56,8 @@
   srcs = glob(['src/test/java/**/*.java']),
   deps = [
     ':server',
-    '//lib/jgit:jgit',
     '//lib:junit',
+    '@jgit//:jgit',
   ],
   source_under_test = [':server'],
   visibility = ['//tools/eclipse:classpath'],
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index 3a14ffd..8ea8eab 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -15,9 +15,9 @@
   '//lib/guice:guice',
   '//lib/guice:guice-assistedinject',
   '//lib/guice:guice-servlet',
-  '//lib/jgit:jgit',
   '//lib/log:api',
   '//lib/log:log4j',
+  '@jgit//:jgit',
 ]
 
 DEPS = BASE_JETTY_DEPS + [
@@ -176,8 +176,8 @@
     '//lib:junit',
     '//lib/easymock:easymock',
     '//lib/guice:guice',
-    '//lib/jgit:jgit',
-    '//lib/jgit:junit',
+    '@jgit//:jgit',
+    '@jgit//:junit',
   ],
   source_under_test = [':pgm'],
 )
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 e08b7ac..f57f95f 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
@@ -40,6 +40,7 @@
 import java.io.IOException;
 import java.nio.file.Path;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.List;
 
 /** Initialize a new Gerrit installation. */
@@ -67,6 +68,12 @@
       usage = "Setup site with default options suitable for developers")
   private boolean dev;
 
+  @Option(name = "--skip-all-downloads", usage = "Don't download libraries")
+  private boolean skipAllDownloads;
+
+  @Option(name = "--skip-download", usage = "Don't download given library")
+  private List<String> skippedDownloads;
+
   @Inject
   Browser browser;
 
@@ -147,6 +154,18 @@
   }
 
   @Override
+  protected boolean skipAllDownloads() {
+    return skipAllDownloads;
+  }
+
+  @Override
+  protected List<String> getSkippedDownloads() {
+    return skippedDownloads != null
+        ? skippedDownloads
+        : Collections.<String> emptyList();
+  }
+
+  @Override
   protected String getSecureStoreLib() {
     return secureStoreLib;
   }
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 b200ed5..2136723 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
@@ -26,6 +26,7 @@
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.InstallPlugins;
+import com.google.gerrit.pgm.init.api.LibraryDownload;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.config.GerritServerConfigModule;
@@ -147,6 +148,14 @@
     return null;
   }
 
+  protected boolean skipAllDownloads() {
+    return false;
+  }
+
+  protected List<String> getSkippedDownloads() {
+    return Collections.emptyList();
+  }
+
   /**
    * Invoked before site init is called.
    *
@@ -252,6 +261,10 @@
         bind(String.class).annotatedWith(SecureStoreClassName.class)
             .toProvider(Providers.of(secureStoreClassName));
         bind(SecureStore.class).toProvider(SecureStoreProvider.class).in(SINGLETON);
+        bind(new TypeLiteral<List<String>>() {}).annotatedWith(
+            LibraryDownload.class).toInstance(getSkippedDownloads());
+        bind(Boolean.class).annotatedWith(
+            LibraryDownload.class).toInstance(skipAllDownloads());
       }
     });
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
index 988d7ef..282e75a 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/Libraries.java
@@ -16,6 +16,7 @@
 
 import static java.nio.charset.StandardCharsets.UTF_8;
 
+import com.google.gerrit.pgm.init.api.LibraryDownload;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 import com.google.inject.Singleton;
@@ -30,6 +31,7 @@
 import java.io.Reader;
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.List;
 
 /** Standard {@link LibraryDownloader} instances derived from configuration. */
 @Singleton
@@ -38,6 +40,8 @@
       "com/google/gerrit/pgm/init/libraries.config";
 
   private final Provider<LibraryDownloader> downloadProvider;
+  private final List<String> skippedDownloads;
+  private final boolean skipAllDownloads;
 
   /* final */LibraryDownloader bouncyCastlePGP;
   /* final */LibraryDownloader bouncyCastleProvider;
@@ -49,9 +53,12 @@
   /* final */LibraryDownloader oracleDriver;
 
   @Inject
-  Libraries(final Provider<LibraryDownloader> downloadProvider) {
+  Libraries(final Provider<LibraryDownloader> downloadProvider,
+      @LibraryDownload List<String> skippedDownloads,
+      @LibraryDownload Boolean skipAllDownloads) {
     this.downloadProvider = downloadProvider;
-
+    this.skippedDownloads = skippedDownloads;
+    this.skipAllDownloads = skipAllDownloads;
     init();
   }
 
@@ -98,6 +105,7 @@
     for (String d : cfg.getStringList("library", n, "needs")) {
       dl.addNeeds((LibraryDownloader) getClass().getDeclaredField(d).get(this));
     }
+    dl.setSkipDownload(skipAllDownloads || skippedDownloads.contains(n));
   }
 
   private static String getOptional(Config cfg, String name, String key) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
index e4cc305..a354376 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/LibraryDownloader.java
@@ -58,6 +58,7 @@
   private Path dst;
   private boolean download; // download or copy
   private boolean exists;
+  private boolean skipDownload;
 
   @Inject
   LibraryDownloader(ConsoleUI ui, SitePaths site) {
@@ -87,6 +88,10 @@
     needs.add(lib);
   }
 
+  void setSkipDownload(boolean skipDownload) {
+    this.skipDownload = skipDownload;
+  }
+
   void downloadRequired() {
     setRequired(true);
     download();
@@ -105,6 +110,10 @@
   }
 
   private void download() {
+    if (skipDownload) {
+      return;
+    }
+
     if (jarUrl == null || !jarUrl.contains("/")) {
       throw new IllegalStateException("Invalid JarUrl for " + name);
     }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/LibraryDownload.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/LibraryDownload.java
new file mode 100644
index 0000000..7e46b21
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/api/LibraryDownload.java
@@ -0,0 +1,25 @@
+// 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.pgm.init.api;
+
+import com.google.inject.BindingAnnotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@BindingAnnotation
+@Retention(RetentionPolicy.RUNTIME)
+public @interface LibraryDownload {
+}
diff --git a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
index 2198788..48754f1 100644
--- a/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
+++ b/gerrit-pgm/src/test/java/com/google/gerrit/pgm/init/LibrariesTest.java
@@ -26,6 +26,7 @@
 import org.junit.Test;
 
 import java.nio.file.Paths;
+import java.util.Collections;
 
 public class LibrariesTest {
   @Test
@@ -40,7 +41,7 @@
       public LibraryDownloader get() {
         return new LibraryDownloader(ui, site);
       }
-    });
+    }, Collections.<String> emptyList(), false);
 
     assertNotNull(lib.bouncyCastleProvider);
     assertNotNull(lib.mysqlDriver);
diff --git a/gerrit-plugin-api/BUCK b/gerrit-plugin-api/BUCK
index b58c10a..fa90b88 100644
--- a/gerrit-plugin-api/BUCK
+++ b/gerrit-plugin-api/BUCK
@@ -39,10 +39,10 @@
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
     '//lib/joda:joda-time',
     '//lib/log:api',
     '//lib/mina:sshd',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
@@ -65,6 +65,7 @@
     ':plugin-api',
     '//lib/bouncycastle:bcprov',
     '//lib/bouncycastle:bcpg',
+    '//lib/bouncycastle:bcpkix',
   ],
   visibility = ['PUBLIC'],
   do_it_wrong = True,
diff --git a/gerrit-prettify/BUCK b/gerrit-prettify/BUCK
index 9a0136e..b4d0844 100644
--- a/gerrit-prettify/BUCK
+++ b/gerrit-prettify/BUCK
@@ -17,10 +17,10 @@
   exported_deps = [
     '//gerrit-extension-api:client',
     '//gerrit-patch-jgit:client',
+    '//gerrit-patch-jgit:Edit',
     '//gerrit-reviewdb:client',
     '//lib:gwtjsonrpc',
     '//lib:gwtjsonrpc_src',
-    '//lib/jgit:Edit',
   ],
   provided_deps = ['//lib/gwt:user'],
   visibility = ['PUBLIC'],
@@ -44,7 +44,7 @@
     '//gerrit-reviewdb:server',
     '//lib:guava',
     '//lib:gwtjsonrpc',
-    '//lib/jgit:jgit',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 567754f..3fc4580 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -45,6 +45,7 @@
     '//lib:mime-util',
     '//lib:pegdown',
     '//lib:protobuf',
+    '//lib:tukaani-xz',
     '//lib:velocity',
     '//lib/antlr:java_runtime',
     '//lib/auto:auto-value',
@@ -58,8 +59,6 @@
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
     '//lib/guice:guice-servlet',
-    '//lib/jgit:jgit',
-    '//lib/jgit:jgit-archive',
     '//lib/joda:joda-time',
     '//lib/log:api',
     '//lib/log:jsonevent-layout',
@@ -71,6 +70,8 @@
     '//lib/ow2:ow2-asm-tree',
     '//lib/ow2:ow2-asm-util',
     '//lib/prolog:runtime',
+    '@jgit//:jgit',
+    '@jgit//:jgit-archive',
   ],
   provided_deps = [
     '//lib:servlet-api-3_1',
@@ -97,12 +98,12 @@
   '//lib:truth',
   '//lib/guice:guice',
   '//lib/guice:guice-servlet',
-  '//lib/jgit:jgit',
-  '//lib/jgit:junit',
   '//lib/joda:joda-time',
   '//lib/log:api',
   '//lib/log:impl_log4j',
   '//lib/log:log4j',
+  '@jgit//:jgit',
+  '@jgit//:junit',
 ]
 
 TESTUTIL = glob([
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
index f460240..bc6f732 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeFinder.java
@@ -15,7 +15,6 @@
 package com.google.gerrit.server;
 
 import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.change.ChangeTriplet;
@@ -55,8 +54,7 @@
       throws OrmException {
     // Use the index to search for changes, but don't return any stored fields,
     // to force rereading in case the index is stale.
-    InternalChangeQuery query = queryProvider.get()
-        .setRequestedFields(ImmutableSet.<String> of());
+    InternalChangeQuery query = queryProvider.get().noFields();
 
     // Try legacy id
     if (!id.isEmpty() && id.charAt(0) != '0') {
@@ -96,8 +94,7 @@
       throws OrmException {
     // Use the index to search for changes, but don't return any stored fields,
     // to force rereading in case the index is stale.
-    InternalChangeQuery query = queryProvider.get()
-        .setRequestedFields(ImmutableSet.<String> of());
+    InternalChangeQuery query = queryProvider.get().noFields();
     return asChangeControls(query.byLegacyChangeId(id), user);
   }
 
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 c49498d..bf93329 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
@@ -17,48 +17,18 @@
 import com.google.common.base.Function;
 import com.google.common.collect.Ordering;
 import com.google.gerrit.common.TimeUtil;
-import com.google.gerrit.extensions.restapi.ResourceConflictException;
-import com.google.gerrit.extensions.restapi.RestApiException;
 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.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.change.ChangeInserter;
-import com.google.gerrit.server.change.ChangeMessages;
-import com.google.gerrit.server.git.BatchUpdate;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.git.UpdateException;
-import com.google.gerrit.server.git.validators.CommitValidators;
-import com.google.gerrit.server.mail.RevertedSender;
-import com.google.gerrit.server.notedb.ChangeNotes;
-import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.project.ChangeControl;
-import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.util.IdGenerator;
 import com.google.gwtorm.server.OrmException;
-import com.google.inject.Inject;
-import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.errors.RepositoryNotFoundException;
-import org.eclipse.jgit.lib.CommitBuilder;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.ObjectInserter;
-import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.RefDatabase;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
-import org.eclipse.jgit.revwalk.RevWalk;
-import org.eclipse.jgit.util.ChangeIdUtil;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.text.MessageFormat;
 import java.util.Collections;
 import java.util.Map;
 
@@ -73,9 +43,6 @@
   private static final String SUBJECT_CROP_APPENDIX = "...";
   private static final int SUBJECT_CROP_RANGE = 10;
 
-  private static final Logger log =
-      LoggerFactory.getLogger(ChangeUtil.class);
-
   public static final Function<PatchSet, Integer> TO_PS_ID =
       new Function<PatchSet, Integer>() {
         @Override
@@ -133,6 +100,10 @@
     return next;
   }
 
+  public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
+    return new PatchSet.Id(id.getParentKey(), id.get() + 1);
+  }
+
   public static PatchSet.Id nextPatchSetId(Repository git, PatchSet.Id id)
       throws IOException {
     return nextPatchSetId(git.getRefDatabase().getRefs(RefDatabase.ALL), id);
@@ -152,159 +123,6 @@
     return subject;
   }
 
-  private final Provider<CurrentUser> user;
-  private final Provider<ReviewDb> db;
-  private final Sequences seq;
-  private final PatchSetUtil psUtil;
-  private final RevertedSender.Factory revertedSenderFactory;
-  private final ChangeInserter.Factory changeInserterFactory;
-  private final GitRepositoryManager gitManager;
-  private final BatchUpdate.Factory updateFactory;
-  private final ChangeMessagesUtil changeMessagesUtil;
-  private final ChangeUpdate.Factory changeUpdateFactory;
-
-  @Inject
-  ChangeUtil(Provider<CurrentUser> user,
-      Provider<ReviewDb> db,
-      Sequences seq,
-      PatchSetUtil psUtil,
-      RevertedSender.Factory revertedSenderFactory,
-      ChangeInserter.Factory changeInserterFactory,
-      GitRepositoryManager gitManager,
-      BatchUpdate.Factory updateFactory,
-      ChangeMessagesUtil changeMessagesUtil,
-      ChangeUpdate.Factory changeUpdateFactory) {
-    this.user = user;
-    this.db = db;
-    this.seq = seq;
-    this.psUtil = psUtil;
-    this.revertedSenderFactory = revertedSenderFactory;
-    this.changeInserterFactory = changeInserterFactory;
-    this.gitManager = gitManager;
-    this.updateFactory = updateFactory;
-    this.changeMessagesUtil = changeMessagesUtil;
-    this.changeUpdateFactory = changeUpdateFactory;
-  }
-
-  public Change.Id revert(ChangeControl ctl, PatchSet.Id patchSetId,
-      String message, PersonIdent myIdent)
-      throws NoSuchChangeException, OrmException,
-      MissingObjectException, IncorrectObjectTypeException, IOException,
-      RestApiException, UpdateException {
-    Change.Id changeIdToRevert = patchSetId.getParentKey();
-    PatchSet patch = psUtil.get(db.get(), ctl.getNotes(), patchSetId);
-    if (patch == null) {
-      throw new NoSuchChangeException(changeIdToRevert);
-    }
-    Change changeToRevert = db.get().changes().get(changeIdToRevert);
-
-    Project.NameKey project = ctl.getProject().getNameKey();
-    try (Repository git = gitManager.openRepository(project);
-        RevWalk revWalk = new RevWalk(git)) {
-      RevCommit commitToRevert =
-          revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
-
-      PersonIdent authorIdent = user.get().asIdentifiedUser()
-          .newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
-
-      if (commitToRevert.getParentCount() == 0) {
-        throw new ResourceConflictException("Cannot revert initial commit");
-      }
-
-      RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
-      revWalk.parseHeaders(parentToCommitToRevert);
-
-      CommitBuilder revertCommitBuilder = new CommitBuilder();
-      revertCommitBuilder.addParentId(commitToRevert);
-      revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
-      revertCommitBuilder.setAuthor(authorIdent);
-      revertCommitBuilder.setCommitter(authorIdent);
-
-      if (message == null) {
-        message = MessageFormat.format(
-            ChangeMessages.get().revertChangeDefaultMessage,
-            changeToRevert.getSubject(), patch.getRevision().get());
-      }
-
-      ObjectId computedChangeId =
-          ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
-              commitToRevert, authorIdent, myIdent, message);
-      revertCommitBuilder.setMessage(
-          ChangeIdUtil.insertId(message, computedChangeId, true));
-
-      RevCommit revertCommit;
-      ChangeInserter ins;
-      Change.Id changeId = new Change.Id(seq.nextChangeId());
-      try (ObjectInserter oi = git.newObjectInserter()) {
-        ObjectId id = oi.insert(revertCommitBuilder);
-        oi.flush();
-        revertCommit = revWalk.parseCommit(id);
-
-        ins = changeInserterFactory.create(
-            changeId, revertCommit, ctl.getChange().getDest().get())
-            .setValidatePolicy(CommitValidators.Policy.GERRIT)
-            .setTopic(changeToRevert.getTopic());
-
-        ChangeMessage changeMessage = new ChangeMessage(
-            new ChangeMessage.Key(
-                patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
-                user.get().getAccountId(), TimeUtil.nowTs(), patchSetId);
-        StringBuilder msgBuf = new StringBuilder();
-        msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
-        msgBuf.append("\n\n");
-        msgBuf.append("This patchset was reverted in change: ")
-              .append("I").append(computedChangeId.name());
-        changeMessage.setMessage(msgBuf.toString());
-        ChangeUpdate update = changeUpdateFactory.create(ctl, TimeUtil.nowTs());
-        changeMessagesUtil.addChangeMessage(db.get(), update, changeMessage);
-        update.commit();
-
-        ins.setMessage("Uploaded patch set 1.");
-        try (BatchUpdate bu = updateFactory.create(
-            db.get(), project, ctl.getUser(),
-            TimeUtil.nowTs())) {
-          bu.setRepository(git, revWalk, oi);
-          bu.insertChange(ins);
-          bu.execute();
-        }
-      }
-
-      try {
-        RevertedSender cm = revertedSenderFactory.create(project, changeId);
-        cm.setFrom(user.get().getAccountId());
-        cm.setChangeMessage(ins.getChangeMessage());
-        cm.send();
-      } catch (Exception err) {
-        log.error("Cannot send email for revert change " + changeId, err);
-      }
-
-      return changeId;
-    } catch (RepositoryNotFoundException e) {
-      throw new NoSuchChangeException(changeIdToRevert, e);
-    }
-  }
-
-  public String getMessage(ChangeNotes notes)
-      throws NoSuchChangeException, OrmException,
-      MissingObjectException, IncorrectObjectTypeException, IOException {
-    Change.Id changeId = notes.getChangeId();
-    PatchSet ps = psUtil.current(db.get(), notes);
-    if (ps == null) {
-      throw new NoSuchChangeException(changeId);
-    }
-
-    try (Repository git =
-          gitManager.openRepository(notes.getProjectName());
-        RevWalk revWalk = new RevWalk(git)) {
-      RevCommit commit = revWalk.parseCommit(
-          ObjectId.fromString(ps.getRevision().get()));
-      return commit.getFullMessage();
-    } catch (RepositoryNotFoundException e) {
-      throw new NoSuchChangeException(changeId, e);
-    }
-  }
-
-  public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
-    return new PatchSet.Id(id.getParentKey(), id.get() + 1);
+  private ChangeUtil() {
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
index 32db173..446c8ed 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangeApiImpl.java
@@ -54,6 +54,7 @@
 import com.google.gerrit.server.change.SubmittedTogether;
 import com.google.gerrit.server.change.SuggestReviewers;
 import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -219,7 +220,8 @@
   public ChangeApi revert(RevertInput in) throws RestApiException {
     try {
       return changeApi.id(revert.apply(change, in)._number);
-    } catch (OrmException | IOException | UpdateException e) {
+    } catch (OrmException | IOException | UpdateException
+        | NoSuchChangeException e) {
       throw new RestApiException("Cannot revert change", e);
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
index bb01dea..5485052 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/ChangesImpl.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.extensions.api.changes.Changes;
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.IdString;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -90,7 +91,7 @@
   }
 
   @Override
-  public ChangeApi create(ChangeInfo in) throws RestApiException {
+  public ChangeApi create(ChangeInput in) throws RestApiException {
     try {
       ChangeInfo out = createChange.apply(
           TopLevelResource.INSTANCE, in).value();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
index fe4721a..cb81d83 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/changes/RevisionApiImpl.java
@@ -59,6 +59,7 @@
 import com.google.gerrit.server.change.TestSubmitType;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -214,7 +215,8 @@
   public ChangeApi rebase(RebaseInput in) throws RestApiException {
     try {
       return changes.id(rebase.apply(revision, in)._number);
-    } catch (OrmException | EmailException | UpdateException | IOException e) {
+    } catch (OrmException | EmailException | UpdateException | IOException
+        | NoSuchChangeException e) {
       throw new RestApiException("Cannot rebase ps", e);
     }
   }
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 def4591..9876dd7 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
@@ -123,7 +123,7 @@
       change = ctx.getChange();
       PatchSet.Id psId = change.currentPatchSetId();
       ChangeUpdate update = ctx.getUpdate(psId);
-      if (change == null || !change.getStatus().isOpen()) {
+      if (!change.getStatus().isOpen()) {
         throw new ResourceConflictException("change is " + status(change));
       } else if (change.getStatus() == Change.Status.DRAFT) {
         throw new ResourceConflictException(
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 61a55d4..3fdca9d 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
@@ -100,6 +100,7 @@
 import com.google.gerrit.server.notedb.ReviewerStateInternal;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.SubmitRuleEvaluator;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -157,7 +158,7 @@
   private final Provider<ConsistencyChecker> checkerProvider;
   private final ActionJson actionJson;
   private final GpgApiAdapter gpgApi;
-  private final ChangeNotes.Factory changeNotesFactory;
+  private final ChangeNotes.Factory notesFactory;
 
   private AccountLoader accountLoader;
   private Map<Change.Id, List<SubmitRecord>> submitRecords;
@@ -183,7 +184,7 @@
       Provider<ConsistencyChecker> checkerProvider,
       ActionJson actionJson,
       GpgApiAdapter gpgApi,
-      ChangeNotes.Factory changeNotesFactory,
+      ChangeNotes.Factory notesFactory,
       @Assisted Set<ListChangesOption> options) {
     this.db = db;
     this.labelNormalizer = ln;
@@ -203,7 +204,7 @@
     this.checkerProvider = checkerProvider;
     this.actionJson = actionJson;
     this.gpgApi = gpgApi;
-    this.changeNotesFactory = changeNotesFactory;
+    this.notesFactory = notesFactory;
     this.options = options.isEmpty()
         ? EnumSet.noneOf(ListChangesOption.class)
         : EnumSet.copyOf(options);
@@ -223,17 +224,17 @@
   }
 
   public ChangeInfo format(Project.NameKey project, Change.Id id)
-      throws OrmException {
-    ChangeNotes notes;
+      throws OrmException, NoSuchChangeException {
+    Change c;
     try {
-      notes = changeNotesFactory.create(db.get(), project, id);
-    } catch (OrmException e) {
+      c = notesFactory.createChecked(db.get(), project, id).getChange();
+    } catch (OrmException | NoSuchChangeException e) {
       if (!has(CHECK)) {
         throw e;
       }
       return checkOnly(changeDataFactory.create(db.get(), project, id));
     }
-    return format(changeDataFactory.create(db.get(), notes.getChange()));
+    return format(changeDataFactory.create(db.get(), c));
   }
 
   public ChangeInfo format(ChangeData cd) throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
index 4ee6d33..b3e7c6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeResource.java
@@ -51,10 +51,6 @@
     this.control = control;
   }
 
-  protected ChangeResource(ChangeResource copy) {
-    this.control = copy.control;
-  }
-
   public ChangeControl getControl() {
     return control;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
index d7fe43b..20165f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Check.java
@@ -18,9 +18,12 @@
 import com.google.gerrit.extensions.client.ListChangesOption;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.NotImplementedException;
 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.extensions.restapi.RestReadView;
+import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -29,21 +32,27 @@
 
 public class Check implements RestReadView<ChangeResource>,
     RestModifyView<ChangeResource, FixInput> {
+  private final NotesMigration notesMigration;
   private final ChangeJson.Factory jsonFactory;
 
   @Inject
-  Check(ChangeJson.Factory json) {
+  Check(NotesMigration notesMigration,
+      ChangeJson.Factory json) {
+    this.notesMigration = notesMigration;
     this.jsonFactory = json;
   }
 
   @Override
-  public Response<ChangeInfo> apply(ChangeResource rsrc) throws OrmException {
+  public Response<ChangeInfo> apply(ChangeResource rsrc)
+      throws RestApiException, OrmException {
+    checkEnabled();
     return Response.withMustRevalidate(newChangeJson().format(rsrc));
   }
 
   @Override
   public Response<ChangeInfo> apply(ChangeResource rsrc, FixInput input)
-      throws AuthException, OrmException {
+      throws RestApiException, OrmException {
+    checkEnabled();
     ChangeControl ctl = rsrc.getControl();
     if (!ctl.isOwner()
         && !ctl.getProjectControl().isOwner()
@@ -56,4 +65,10 @@
   private ChangeJson newChangeJson() {
     return jsonFactory.create(EnumSet.of(ListChangesOption.CHECK));
   }
+
+  private void checkEnabled() throws NotImplementedException {
+    if (notesMigration.readChanges()) {
+      throw new NotImplementedException("check not implemented for notedb");
+    }
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
index b5c8ddb..f995760 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ConsistencyChecker.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.common.base.Preconditions.checkState;
 import static com.google.gerrit.reviewdb.client.RefNames.REFS_CHANGES;
 import static com.google.gerrit.reviewdb.server.ReviewDbUtil.intKeyOrdering;
 import static com.google.gerrit.server.ChangeUtil.PS_ID_ORDER;
@@ -47,6 +48,7 @@
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
@@ -112,6 +114,7 @@
 
   private final Provider<ReviewDb> db;
   private final GitRepositoryManager repoManager;
+  private final NotesMigration notesMigration;
   private final Provider<CurrentUser> user;
   private final Provider<PersonIdent> serverIdent;
   private final ProjectControl.GenericFactory projectControlFactory;
@@ -138,6 +141,7 @@
   @Inject
   ConsistencyChecker(Provider<ReviewDb> db,
       GitRepositoryManager repoManager,
+      NotesMigration notesMigration,
       Provider<CurrentUser> user,
       @GerritPersonIdent Provider<PersonIdent> serverIdent,
       ProjectControl.GenericFactory projectControlFactory,
@@ -149,6 +153,7 @@
       ChangeNotes.Factory notesFactory,
       ChangeUpdate.Factory changeUpdateFactory) {
     this.db = db;
+    this.notesMigration = notesMigration;
     this.repoManager = repoManager;
     this.user = user;
     this.serverIdent = serverIdent;
@@ -206,6 +211,8 @@
   }
 
   private void checkImpl() {
+    checkState(!notesMigration.readChanges(),
+        "ConsistencyChecker for notedb not yet implemented");
     checkOwner();
     checkCurrentPatchSetEntity();
 
@@ -419,13 +426,12 @@
           continue;
         }
         try {
-          Change c = notesFactory
-              .create(db.get(), change.getProject(), psId.getParentKey())
-              .getChange();
-          if (c == null || !c.getDest().equals(change.getDest())) {
+          Change c = notesFactory.createChecked(
+              db.get(), change.getProject(), psId.getParentKey()).getChange();
+          if (!c.getDest().equals(change.getDest())) {
             continue;
           }
-        } catch (OrmException e) {
+        } catch (OrmException | NoSuchChangeException e) {
           warn(e);
           // Include this patch set; should cause an error below, which is good.
         }
@@ -586,12 +592,8 @@
     try {
       db.changes().beginTransaction(cid);
       try {
-        ChangeNotes notes = notesFactory.create(db, project, cid);
+        ChangeNotes notes = notesFactory.createChecked(db, project, cid);
         Change c = notes.getChange();
-        if (c == null) {
-          throw new OrmException("Change missing: " + cid);
-        }
-
         if (psId.equals(c.currentPatchSetId())) {
           List<PatchSet> all = Lists.newArrayList(db.patchSets().byChange(cid));
           if (all.size() == 1 && all.get(0).getId().equals(psId)) {
@@ -631,7 +633,8 @@
       } finally {
         db.rollback();
       }
-    } catch (PatchSetInfoNotAvailableException | OrmException e) {
+    } catch (PatchSetInfoNotAvailableException | OrmException
+        | NoSuchChangeException e) {
       String msg = "Error deleting patch set";
       log.warn(msg + ' ' + psId, e);
       p.status = Status.FIX_FAILED;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
index d47eb8b..a29885e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CreateChange.java
@@ -20,9 +20,11 @@
 import com.google.gerrit.common.data.Capable;
 import com.google.gerrit.extensions.client.ChangeStatus;
 import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.ChangeInput;
 import com.google.gerrit.extensions.restapi.AuthException;
 import com.google.gerrit.extensions.restapi.BadRequestException;
 import com.google.gerrit.extensions.restapi.MethodNotAllowedException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.extensions.restapi.Response;
 import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.extensions.restapi.RestModifyView;
@@ -61,6 +63,7 @@
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.TreeFormatter;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.util.ChangeIdUtil;
@@ -74,7 +77,7 @@
 
 @Singleton
 public class CreateChange implements
-    RestModifyView<TopLevelResource, ChangeInfo> {
+    RestModifyView<TopLevelResource, ChangeInput> {
 
   private final Provider<ReviewDb> db;
   private final GitRepositoryManager gitManager;
@@ -117,7 +120,7 @@
   }
 
   @Override
-  public Response<ChangeInfo> apply(TopLevelResource parent, ChangeInfo input)
+  public Response<ChangeInfo> apply(TopLevelResource parent, ChangeInput input)
       throws OrmException, IOException, InvalidChangeOperationException,
       RestApiException, UpdateException {
     if (Strings.isNullOrEmpty(input.project)) {
@@ -178,24 +181,37 @@
         groups = ps.getGroups();
       } else {
         Ref destRef = git.getRefDatabase().exactRef(refName);
-        if (destRef == null) {
-          throw new UnprocessableEntityException(String.format(
-              "Branch %s does not exist.", refName));
+        if (destRef != null) {
+          if (Boolean.TRUE.equals(input.newBranch)) {
+            throw new ResourceConflictException(String.format(
+                "Branch %s already exists.", refName));
+          } else {
+            parentCommit = destRef.getObjectId();
+          }
+        } else {
+          if (Boolean.TRUE.equals(input.newBranch)) {
+            parentCommit = null;
+          } else {
+            throw new UnprocessableEntityException(String.format(
+                "Branch %s does not exist.", refName));
+          }
         }
-        parentCommit = destRef.getObjectId();
         groups = Collections.emptyList();
       }
-      RevCommit mergeTip = rw.parseCommit(parentCommit);
+      RevCommit mergeTip =
+          parentCommit == null ? null : rw.parseCommit(parentCommit);
 
       Timestamp now = TimeUtil.nowTs();
       IdentifiedUser me = user.get().asIdentifiedUser();
       PersonIdent author = me.newCommitterIdent(now, serverTimeZone);
 
-      ObjectId id = ChangeIdUtil.computeChangeId(mergeTip.getTree(),
-          mergeTip, author, author, input.subject);
-      String commitMessage = ChangeIdUtil.insertId(input.subject, id);
-
       try (ObjectInserter oi = git.newObjectInserter()) {
+        ObjectId treeId =
+            mergeTip == null ? emptyTreeId(oi) : mergeTip.getTree();
+        ObjectId id = ChangeIdUtil.computeChangeId(treeId,
+            mergeTip, author, author, input.subject);
+        String commitMessage = ChangeIdUtil.insertId(input.subject, id);
+
         RevCommit c = newCommit(oi, rw, author, mergeTip, commitMessage);
 
         Change.Id changeId = new Change.Id(seq.nextChangeId());
@@ -217,7 +233,7 @@
           bu.execute();
         }
         ChangeJson json = jsonFactory.create(ChangeJson.NO_OPTIONS);
-        return Response.created(json.format(project, changeId));
+        return Response.created(json.format(ins.getChange()));
       }
 
     }
@@ -227,8 +243,12 @@
       PersonIdent authorIdent, RevCommit mergeTip, String commitMessage)
       throws IOException {
     CommitBuilder commit = new CommitBuilder();
-    commit.setTreeId(mergeTip.getTree().getId());
-    commit.setParentId(mergeTip);
+    if (mergeTip == null) {
+      commit.setTreeId(emptyTreeId(oi));
+    } else {
+      commit.setTreeId(mergeTip.getTree().getId());
+      commit.setParentId(mergeTip);
+    }
     commit.setAuthor(authorIdent);
     commit.setCommitter(authorIdent);
     commit.setMessage(commitMessage);
@@ -242,4 +262,11 @@
     inserter.flush();
     return id;
   }
+
+  private static ObjectId emptyTreeId(ObjectInserter inserter)
+      throws IOException {
+    ObjectId id = inserter.insert(new TreeFormatter());
+    inserter.flush();
+    return id;
+  }
 }
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 b1eb630..37ca9cf 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
@@ -21,10 +21,11 @@
 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.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
@@ -32,15 +33,15 @@
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.DeleteReviewer.Input;
-import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.BatchUpdate.ChangeContext;
+import com.google.gerrit.server.git.UpdateException;
 import com.google.gerrit.server.notedb.ChangeUpdate;
-import com.google.gerrit.server.project.ChangeControl;
 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.List;
 
 @Singleton
@@ -49,44 +50,55 @@
   }
 
   private final Provider<ReviewDb> dbProvider;
-  private final ChangeUpdate.Factory updateFactory;
   private final ApprovalsUtil approvalsUtil;
   private final ChangeMessagesUtil cmUtil;
-  private final ChangeIndexer indexer;
+  private final BatchUpdate.Factory batchUpdateFactory;
   private final IdentifiedUser.GenericFactory userFactory;
 
   @Inject
   DeleteReviewer(Provider<ReviewDb> dbProvider,
-      ChangeUpdate.Factory updateFactory,
       ApprovalsUtil approvalsUtil,
       ChangeMessagesUtil cmUtil,
-      ChangeIndexer indexer,
+      BatchUpdate.Factory batchUpdateFactory,
       IdentifiedUser.GenericFactory userFactory) {
     this.dbProvider = dbProvider;
-    this.updateFactory = updateFactory;
     this.approvalsUtil = approvalsUtil;
     this.cmUtil = cmUtil;
-    this.indexer = indexer;
+    this.batchUpdateFactory = batchUpdateFactory;
     this.userFactory = userFactory;
   }
 
   @Override
   public Response<?> apply(ReviewerResource rsrc, Input input)
-      throws AuthException, ResourceNotFoundException, OrmException,
-      IOException {
-    ChangeControl control = rsrc.getControl();
-    Change.Id changeId = rsrc.getChangeId();
-    ReviewDb db = dbProvider.get();
-    ChangeUpdate update = updateFactory.create(rsrc.getControl());
+      throws RestApiException, UpdateException {
+    try (BatchUpdate bu = batchUpdateFactory.create(dbProvider.get(),
+        rsrc.getChangeResource().getProject(),
+        rsrc.getChangeResource().getUser(), TimeUtil.nowTs())) {
+      Op op = new Op(rsrc.getReviewerUser().getAccountId());
+      bu.addOp(rsrc.getChange().getId(), op);
+      bu.execute();
+    }
 
-    StringBuilder msg = new StringBuilder();
-    db.changes().beginTransaction(changeId);
-    try {
+    return Response.none();
+  }
+
+  private class Op extends BatchUpdate.Op {
+    private final Account.Id reviewerId;
+
+    Op(Account.Id reviewerId) {
+      this.reviewerId = reviewerId;
+    }
+
+    @Override
+    public boolean updateChange(ChangeContext ctx)
+        throws AuthException, ResourceNotFoundException, OrmException {
+      PatchSet.Id currPs = ctx.getChange().currentPatchSetId();
+      StringBuilder msg = new StringBuilder();
       List<PatchSetApproval> del = Lists.newArrayList();
-      for (PatchSetApproval a : approvals(db, rsrc)) {
-        if (control.canRemoveReviewer(a)) {
+      for (PatchSetApproval a : approvals(ctx, reviewerId)) {
+        if (ctx.getControl().canRemoveReviewer(a)) {
           del.add(a);
-          if (a.getPatchSetId().equals(control.getChange().currentPatchSetId())
+          if (a.getPatchSetId().equals(currPs)
               && a.getValue() != 0) {
             if (msg.length() == 0) {
               msg.append("Removed the following votes:\n\n");
@@ -103,48 +115,40 @@
       if (del.isEmpty()) {
         throw new ResourceNotFoundException();
       }
-      ChangeUtil.bumpRowVersionNotLastUpdatedOn(rsrc.getChangeId(), db);
-      db.patchSetApprovals().delete(del);
-      update.removeReviewer(rsrc.getReviewerUser().getAccountId());
+      ctx.getDb().patchSetApprovals().delete(del);
+      ChangeUpdate update = ctx.getUpdate(currPs);
+      update.removeReviewer(reviewerId);
 
       if (msg.length() > 0) {
         ChangeMessage changeMessage =
-            new ChangeMessage(new ChangeMessage.Key(rsrc.getChangeId(),
-                ChangeUtil.messageUUID(db)),
-                control.getUser().getAccountId(),
-                TimeUtil.nowTs(), rsrc.getChange().currentPatchSetId());
+            new ChangeMessage(new ChangeMessage.Key(ctx.getChange().getId(),
+                ChangeUtil.messageUUID(ctx.getDb())),
+                ctx.getUser().getAccountId(),
+                TimeUtil.nowTs(), currPs);
         changeMessage.setMessage(msg.toString());
-        cmUtil.addChangeMessage(db, update, changeMessage);
+        cmUtil.addChangeMessage(ctx.getDb(), update, changeMessage);
       }
-
-      db.commit();
-    } finally {
-      db.rollback();
+      return true;
     }
-    update.commit();
-    indexer.index(db, rsrc.getChange());
-    return Response.none();
-  }
 
-  private static String formatLabelValue(short value) {
-    if (value > 0) {
-      return "+" + value;
-    } else {
-      return Short.toString(value);
+    private Iterable<PatchSetApproval> approvals(ChangeContext ctx,
+        final Account.Id accountId) throws OrmException {
+      return Iterables.filter(
+          approvalsUtil.byChange(ctx.getDb(), ctx.getNotes()).values(),
+          new Predicate<PatchSetApproval>() {
+            @Override
+            public boolean apply(PatchSetApproval input) {
+              return accountId.equals(input.getAccountId());
+            }
+          });
     }
-  }
 
-  private Iterable<PatchSetApproval> approvals(ReviewDb db,
-      ReviewerResource rsrc) throws OrmException {
-    final Account.Id user = rsrc.getReviewerUser().getAccountId();
-    return Iterables.filter(
-        approvalsUtil.byChange(db, rsrc.getChangeResource().getNotes())
-            .values(),
-        new Predicate<PatchSetApproval>() {
-          @Override
-          public boolean apply(PatchSetApproval input) {
-            return user.equals(input.getAccountId());
-          }
-        });
+    private String formatLabelValue(short value) {
+      if (value > 0) {
+        return "+" + value;
+      } else {
+        return Short.toString(value);
+      }
+    }
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
index 867d660..5a546f3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetContent.java
@@ -17,27 +17,44 @@
 import com.google.gerrit.extensions.restapi.BinaryResult;
 import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Patch;
-import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 
 import java.io.IOException;
 
 @Singleton
 public class GetContent implements RestReadView<FileResource> {
+  private final Provider<ReviewDb> db;
+  private final GitRepositoryManager gitManager;
+  private final PatchSetUtil psUtil;
   private final FileContentUtil fileContentUtil;
-  private final ChangeUtil changeUtil;
 
   @Inject
-  GetContent(FileContentUtil fileContentUtil,
-      ChangeUtil changeUtil) {
+  GetContent(
+      Provider<ReviewDb> db,
+      GitRepositoryManager gitManager,
+      PatchSetUtil psUtil,
+      FileContentUtil fileContentUtil) {
+    this.db = db;
+    this.gitManager = gitManager;
+    this.psUtil = psUtil;
     this.fileContentUtil = fileContentUtil;
-    this.changeUtil = changeUtil;
   }
 
   @Override
@@ -46,7 +63,7 @@
       OrmException {
     String path = rsrc.getPatchKey().get();
     if (Patch.COMMIT_MSG.equals(path)) {
-      String msg = changeUtil.getMessage(
+      String msg = getMessage(
           rsrc.getRevision().getChangeResource().getNotes());
       return BinaryResult.create(msg)
           .setContentType(FileContentUtil.TEXT_X_GERRIT_COMMIT_MESSAGE)
@@ -57,4 +74,22 @@
         ObjectId.fromString(rsrc.getRevision().getPatchSet().getRevision().get()),
         path);
   }
+
+  private String getMessage(ChangeNotes notes)
+      throws NoSuchChangeException, OrmException, IOException {
+    Change.Id changeId = notes.getChangeId();
+    PatchSet ps = psUtil.current(db.get(), notes);
+    if (ps == null) {
+      throw new NoSuchChangeException(changeId);
+    }
+
+    try (Repository git = gitManager.openRepository(notes.getProjectName());
+        RevWalk revWalk = new RevWalk(git)) {
+      RevCommit commit = revWalk.parseCommit(
+          ObjectId.fromString(ps.getRevision().get()));
+      return commit.getFullMessage();
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeId, e);
+    }
+  }
 }
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 39c27cd..1853438 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
@@ -223,7 +223,9 @@
     List<String> newGroups = groups;
     if (newGroups.isEmpty()) {
       PatchSet prevPs = psUtil.current(ctx.getDb(), ctx.getNotes());
-      newGroups = prevPs != null ? prevPs.getGroups() : null;
+      if (prevPs != null) {
+        newGroups = prevPs.getGroups();
+      }
     }
     patchSet = psUtil.insert(ctx.getDb(), ctx.getRevWalk(), ctx.getUpdate(psId),
         psId, commit, draft, newGroups, null);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index 6e55d39..940d928 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -38,6 +38,7 @@
 import com.google.gerrit.server.git.validators.CommitValidators;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
@@ -98,7 +99,7 @@
   @Override
   public ChangeInfo apply(RevisionResource rsrc, RebaseInput input)
       throws EmailException, OrmException, UpdateException, RestApiException,
-      IOException {
+      IOException, NoSuchChangeException {
     ChangeControl control = rsrc.getControl();
     Change change = rsrc.getChange();
     try (Repository repo = repoManager.openRepository(change.getProject());
@@ -297,7 +298,7 @@
     @Override
     public ChangeInfo apply(ChangeResource rsrc, RebaseInput input)
         throws EmailException, OrmException, UpdateException, RestApiException,
-        IOException {
+        IOException, NoSuchChangeException {
       PatchSet ps =
           rebase.dbProvider.get().patchSets()
               .get(rsrc.getChange().currentPatchSetId());
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 54b66b6..5c176a0 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
@@ -112,11 +112,11 @@
         ResourceConflictException {
       caller = ctx.getUser().asIdentifiedUser();
       change = ctx.getChange();
-      PatchSet.Id psId = change.currentPatchSetId();
-      ChangeUpdate update = ctx.getUpdate(psId);
       if (change == null || change.getStatus() != Status.ABANDONED) {
         throw new ResourceConflictException("change is " + status(change));
       }
+      PatchSet.Id psId = change.currentPatchSetId();
+      ChangeUpdate update = ctx.getUpdate(psId);
       patchSet = psUtil.get(ctx.getDb(), ctx.getNotes(), psId);
       change.setStatus(Status.NEW);
       change.setLastUpdatedOn(ctx.getWhen());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
index d702fc6..391b706 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Revert.java
@@ -26,39 +26,96 @@
 import com.google.gerrit.extensions.webui.UiAction;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Change.Status;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.PatchSetUtil;
+import com.google.gerrit.server.Sequences;
+import com.google.gerrit.server.git.BatchUpdate;
+import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.UpdateException;
+import com.google.gerrit.server.git.validators.CommitValidators;
+import com.google.gerrit.server.mail.RevertedSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.RefControl;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 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.eclipse.jgit.util.ChangeIdUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.text.MessageFormat;
 
 @Singleton
 public class Revert implements RestModifyView<ChangeResource, RevertInput>,
     UiAction<ChangeResource> {
+  private static final Logger log = LoggerFactory.getLogger(Revert.class);
+
+  private final Provider<ReviewDb> db;
+  private final Provider<CurrentUser> user;
+  private final GitRepositoryManager repoManager;
+  private final ChangeInserter.Factory changeInserterFactory;
+  private final ChangeMessagesUtil cmUtil;
+  private final ChangeUpdate.Factory changeUpdateFactory;
+  private final BatchUpdate.Factory updateFactory;
+  private final Sequences seq;
+  private final PatchSetUtil psUtil;
+  private final RevertedSender.Factory revertedSenderFactory;
   private final ChangeJson.Factory json;
-  private final ChangeUtil changeUtil;
   private final PersonIdent myIdent;
 
   @Inject
-  Revert(ChangeJson.Factory json,
-      ChangeUtil changeUtil,
+  Revert(Provider<ReviewDb> db,
+      Provider<CurrentUser> user,
+      GitRepositoryManager repoManager,
+      ChangeInserter.Factory changeInserterFactory,
+      ChangeMessagesUtil cmUtil,
+      ChangeUpdate.Factory changeUpdateFactory,
+      BatchUpdate.Factory updateFactory,
+      Sequences seq,
+      PatchSetUtil psUtil,
+      RevertedSender.Factory revertedSenderFactory,
+      ChangeJson.Factory json,
       @GerritPersonIdent PersonIdent myIdent) {
+    this.db = db;
+    this.user = user;
+    this.repoManager = repoManager;
+    this.changeInserterFactory = changeInserterFactory;
+    this.cmUtil = cmUtil;
+    this.changeUpdateFactory = changeUpdateFactory;
+    this.updateFactory = updateFactory;
+    this.seq = seq;
+    this.psUtil = psUtil;
+    this.revertedSenderFactory = revertedSenderFactory;
     this.json = json;
-    this.changeUtil = changeUtil;
     this.myIdent = myIdent;
   }
 
   @Override
   public ChangeInfo apply(ChangeResource req, RevertInput input)
       throws IOException, OrmException, RestApiException,
-      UpdateException {
+      UpdateException, NoSuchChangeException {
     RefControl refControl = req.getControl().getRefControl();
     Change change = req.getChange();
     if (!refControl.canUpload()) {
@@ -69,7 +126,7 @@
 
     Change.Id revertedChangeId;
     try {
-      revertedChangeId = changeUtil.revert(req.getControl(),
+      revertedChangeId = revert(req.getControl(),
             change.currentPatchSetId(),
             Strings.emptyToNull(input.message),
             new PersonIdent(myIdent, TimeUtil.nowTs()));
@@ -80,6 +137,104 @@
         revertedChangeId);
   }
 
+  private Change.Id revert(ChangeControl ctl, PatchSet.Id patchSetId,
+      String message, PersonIdent myIdent)
+      throws NoSuchChangeException, OrmException,
+      MissingObjectException, IncorrectObjectTypeException, IOException,
+      RestApiException, UpdateException {
+    Change.Id changeIdToRevert = patchSetId.getParentKey();
+    PatchSet patch = psUtil.get(db.get(), ctl.getNotes(), patchSetId);
+    if (patch == null) {
+      throw new NoSuchChangeException(changeIdToRevert);
+    }
+    Change changeToRevert = db.get().changes().get(changeIdToRevert);
+
+    Project.NameKey project = ctl.getProject().getNameKey();
+    try (Repository git = repoManager.openRepository(project);
+        RevWalk revWalk = new RevWalk(git)) {
+      RevCommit commitToRevert =
+          revWalk.parseCommit(ObjectId.fromString(patch.getRevision().get()));
+
+      PersonIdent authorIdent = user.get().asIdentifiedUser()
+          .newCommitterIdent(myIdent.getWhen(), myIdent.getTimeZone());
+
+      if (commitToRevert.getParentCount() == 0) {
+        throw new ResourceConflictException("Cannot revert initial commit");
+      }
+
+      RevCommit parentToCommitToRevert = commitToRevert.getParent(0);
+      revWalk.parseHeaders(parentToCommitToRevert);
+
+      CommitBuilder revertCommitBuilder = new CommitBuilder();
+      revertCommitBuilder.addParentId(commitToRevert);
+      revertCommitBuilder.setTreeId(parentToCommitToRevert.getTree());
+      revertCommitBuilder.setAuthor(authorIdent);
+      revertCommitBuilder.setCommitter(authorIdent);
+
+      if (message == null) {
+        message = MessageFormat.format(
+            ChangeMessages.get().revertChangeDefaultMessage,
+            changeToRevert.getSubject(), patch.getRevision().get());
+      }
+
+      ObjectId computedChangeId =
+          ChangeIdUtil.computeChangeId(parentToCommitToRevert.getTree(),
+              commitToRevert, authorIdent, myIdent, message);
+      revertCommitBuilder.setMessage(
+          ChangeIdUtil.insertId(message, computedChangeId, true));
+
+      RevCommit revertCommit;
+      ChangeInserter ins;
+      Change.Id changeId = new Change.Id(seq.nextChangeId());
+      try (ObjectInserter oi = git.newObjectInserter()) {
+        ObjectId id = oi.insert(revertCommitBuilder);
+        oi.flush();
+        revertCommit = revWalk.parseCommit(id);
+
+        ins = changeInserterFactory.create(
+            changeId, revertCommit, ctl.getChange().getDest().get())
+            .setValidatePolicy(CommitValidators.Policy.GERRIT)
+            .setTopic(changeToRevert.getTopic());
+
+        ChangeMessage changeMessage = new ChangeMessage(
+            new ChangeMessage.Key(
+                patchSetId.getParentKey(), ChangeUtil.messageUUID(db.get())),
+                user.get().getAccountId(), TimeUtil.nowTs(), patchSetId);
+        StringBuilder msgBuf = new StringBuilder();
+        msgBuf.append("Patch Set ").append(patchSetId.get()).append(": Reverted");
+        msgBuf.append("\n\n");
+        msgBuf.append("This patchset was reverted in change: ")
+              .append("I").append(computedChangeId.name());
+        changeMessage.setMessage(msgBuf.toString());
+        ChangeUpdate update = changeUpdateFactory.create(ctl, TimeUtil.nowTs());
+        cmUtil.addChangeMessage(db.get(), update, changeMessage);
+        update.commit();
+
+        ins.setMessage("Uploaded patch set 1.");
+        try (BatchUpdate bu = updateFactory.create(
+            db.get(), project, ctl.getUser(),
+            TimeUtil.nowTs())) {
+          bu.setRepository(git, revWalk, oi);
+          bu.insertChange(ins);
+          bu.execute();
+        }
+      }
+
+      try {
+        RevertedSender cm = revertedSenderFactory.create(project, changeId);
+        cm.setFrom(user.get().getAccountId());
+        cm.setChangeMessage(ins.getChangeMessage());
+        cm.send();
+      } catch (Exception err) {
+        log.error("Cannot send email for revert change " + changeId, err);
+      }
+
+      return changeId;
+    } catch (RepositoryNotFoundException e) {
+      throw new NoSuchChangeException(changeIdToRevert, e);
+    }
+  }
+
   @Override
   public UiAction.Description getDescription(ChangeResource resource) {
     return new UiAction.Description()
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 01f7f82..18e4927 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
@@ -277,8 +277,8 @@
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class).to(ReindexAfterUpdate.class);
     DynamicSet.bind(binder(), GitReferenceUpdatedListener.class)
         .to(ProjectConfigEntry.UpdateChecker.class);
-    DynamicSet.bind(binder(), EventListener.class).to(EventsMetrics.class);
     DynamicSet.setOf(binder(), EventListener.class);
+    DynamicSet.bind(binder(), EventListener.class).to(EventsMetrics.class);
     DynamicSet.setOf(binder(), CommitValidationListener.class);
     DynamicSet.setOf(binder(), RefOperationValidationListener.class);
     DynamicSet.setOf(binder(), MergeValidationListener.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 09a8452..50d35c0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -384,6 +384,7 @@
       Map<PatchSet.Id, Collection<PatchSetApproval>> approvals,
       boolean includeFiles, ChangeNotes notes, LabelTypes labelTypes) {
     if (!ps.isEmpty()) {
+      checkNotNull(notes, "notes may not be null");
       ca.patchSets = new ArrayList<>(ps.size());
       for (PatchSet p : ps) {
         PatchSetAttribute psa = asPatchSetAttribute(db, revWalk, notes, p);
@@ -391,7 +392,7 @@
           addApprovals(psa, p.getId(), approvals, labelTypes);
         }
         ca.patchSets.add(psa);
-        if (includeFiles && notes != null) {
+        if (includeFiles) {
           addPatchSetFileNames(psa, notes.getChange(), p);
         }
       }
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 764dd39..9ec6016 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
@@ -188,6 +188,7 @@
 
     @Override
     public ReviewDb getDb() {
+      checkNotNull(dbWrapper);
       return dbWrapper;
     }
 
@@ -202,15 +203,20 @@
     }
 
     public ChangeNotes getNotes() {
-      return ctl.getNotes();
+      ChangeNotes n = ctl.getNotes();
+      checkNotNull(n);
+      return n;
     }
 
     public ChangeControl getControl() {
+      checkNotNull(ctl);
       return ctl;
     }
 
     public Change getChange() {
-      return ctl.getChange();
+      Change c = ctl.getChange();
+      checkNotNull(c);
+      return c;
     }
 
     public void saveChange() {
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 af0ef9e..d61b138 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
@@ -1782,10 +1782,9 @@
       final List<FooterLine> footerLines = commit.getFooterLines();
       final MailRecipients recipients = new MailRecipients();
       Map<String, Short> approvals = new HashMap<>();
-      if (magicBranch != null) {
-        recipients.add(magicBranch.getMailRecipients());
-        approvals = magicBranch.labels;
-      }
+      checkNotNull(magicBranch);
+      recipients.add(magicBranch.getMailRecipients());
+      approvals = magicBranch.labels;
       recipients.add(getRecipientsFromFooters(
           accountResolver, magicBranch.draft, footerLines));
       recipients.remove(me);
@@ -1802,28 +1801,26 @@
             .setRequestScopePropagator(requestScopePropagator)
             .setSendMail(true)
             .setUpdateRef(true));
-        if (magicBranch != null) {
+        bu.addOp(
+            changeId,
+            hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
+              .setRunHooks(false));
+        if (!Strings.isNullOrEmpty(magicBranch.topic)) {
           bu.addOp(
               changeId,
-              hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
-                .setRunHooks(false));
-          if (!Strings.isNullOrEmpty(magicBranch.topic)) {
-            bu.addOp(
-                changeId,
-                new BatchUpdate.Op() {
-                  @Override
-                  public boolean updateChange(ChangeContext ctx) {
-                    ctx.getUpdate(psId).setTopic(magicBranch.topic);
-                    return true;
-                  }
-                });
-          }
+              new BatchUpdate.Op() {
+                @Override
+                public boolean updateChange(ChangeContext ctx) {
+                  ctx.getUpdate(psId).setTopic(magicBranch.topic);
+                  return true;
+                }
+              });
         }
         bu.execute();
       }
       change = ins.getChange();
 
-      if (magicBranch != null && magicBranch.submit) {
+      if (magicBranch.submit) {
         submit(projectControl.controlFor(state.db, change), ins.getPatchSet());
       }
     }
@@ -2730,10 +2727,14 @@
 
     Change change =
         notesFactory.create(db, project.getNameKey(), cid).getChange();
+    if (change == null) {
+      log.warn(project.getName() + " change " + cid + " is missing");
+      return null;
+    }
     ChangeControl ctl = projectControl.controlFor(db, change);
     PatchSet ps = psUtil.get(db, ctl.getNotes(), psi);
-    if (change == null || ps == null) {
-      log.warn(project.getName() + " " + psi + " is missing");
+    if (ps == null) {
+      log.warn(project.getName() + " patch set " + psi + " is missing");
       return null;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java
index bf04e21..a1d312d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ScanningChangeCacheImpl.java
@@ -16,9 +16,11 @@
 
 import static com.google.gerrit.server.git.SearchingChangeCacheImpl.ID_CACHE;
 
+import com.google.common.base.Function;
 import com.google.common.cache.CacheLoader;
 import com.google.common.cache.LoadingCache;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
@@ -120,7 +122,7 @@
       return scanDb(repo, db);
     }
 
-    return scanNotedb(notesFactory, repo, db, project);
+    return scanChangesFromNotedb(notesFactory, repo, db, project);
   }
 
   public static List<Change> scanDb(Repository repo, ReviewDb db)
@@ -143,18 +145,30 @@
     return changes;
   }
 
-  public static List<Change> scanNotedb(ChangeNotes.Factory notesFactory,
+  private static List<Change> scanChangesFromNotedb(
+      ChangeNotes.Factory notesFactory, Repository repo, ReviewDb db,
+      Project.NameKey project) throws OrmException, IOException {
+    List<ChangeNotes> changeNotes = scanNotedb(notesFactory, repo, db, project);
+    return Lists.transform(changeNotes, new Function<ChangeNotes, Change>() {
+      @Override
+      public Change apply(ChangeNotes notes) {
+        return notes.getChange();
+      }
+    });
+  }
+
+  public static List<ChangeNotes> scanNotedb(ChangeNotes.Factory notesFactory,
       Repository repo, ReviewDb db, Project.NameKey project)
           throws OrmException, IOException {
     Map<String, Ref> refs =
         repo.getRefDatabase().getRefs(RefNames.REFS_CHANGES);
-    List<Change> changes = new ArrayList<>(refs.size());
+    List<ChangeNotes> changeNotes = new ArrayList<>(refs.size());
     for (Ref r : refs.values()) {
       Change.Id id = Change.Id.fromRef(r.getName());
       if (id != null) {
-        changes.add(notesFactory.create(db, project, id).getChange());
+        changeNotes.add(notesFactory.create(db, project, id));
       }
     }
-    return changes;
+    return changeNotes;
   }
 }
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 04625ed..9a9e0b9 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
@@ -14,7 +14,7 @@
 
 package com.google.gerrit.server.git.strategy;
 
-import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.gerrit.server.git.strategy.CommitMergeStatus.SKIPPED_IDENTICAL_TREE;
 
 import com.google.common.collect.ImmutableList;
@@ -151,7 +151,7 @@
           && toMerge.getStatusCode() == SKIPPED_IDENTICAL_TREE) {
         return null;
       }
-      checkState(newCommit != null,
+      checkNotNull(newCommit,
           "no new commit produced by CherryPick of %s, expected to fail fast",
           toMerge.change().getId());
       PatchSet prevPs = args.psUtil.current(ctx.getDb(), ctx.getNotes());
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 801644d..3cbd55d 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
@@ -249,22 +249,23 @@
 
     Change c = ctx.getChange();
     Change.Id id = c.getId();
-    try {
-      CodeReviewCommit commit = args.commits.get(id);
-      CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
-      logDebug("Status of change {} ({}) on {}: {}", id, commit.name(),
-          c.getDest(), s);
-      checkState(s != null,
-          "status not set for change %s; expected to previously fail fast",
-          id);
-      setApproval(ctx, args.caller);
+    CodeReviewCommit commit = args.commits.get(id);
+    checkNotNull(commit, "missing commit for change " + id);
+    CommitMergeStatus s = commit.getStatusCode();
+    checkNotNull(s,
+        "status not set for change " + id
+        + " expected to previously fail fast");
+    logDebug("Status of change {} ({}) on {}: {}", id, commit.name(),
+        c.getDest(), s);
+    setApproval(ctx, args.caller);
 
-      mergeResultRev = alreadyMerged == null
-          ? args.mergeTip.getMergeResults().get(commit)
-          // Our fixup code is not smart enough to find a merge commit
-          // corresponding to the merge result. This results in a different
-          // ChangeMergedEvent in the fixup case, but we'll just live with that.
-          : alreadyMerged;
+    mergeResultRev = alreadyMerged == null
+        ? args.mergeTip.getMergeResults().get(commit)
+        // Our fixup code is not smart enough to find a merge commit
+        // corresponding to the merge result. This results in a different
+        // ChangeMergedEvent in the fixup case, but we'll just live with that.
+        : alreadyMerged;
+    try {
       setMerged(ctx, message(ctx, commit, s));
     } catch (OrmException err) {
       String msg = "Error updating change status for " + id;
@@ -412,6 +413,7 @@
 
   private ChangeMessage message(ChangeContext ctx, CodeReviewCommit commit,
       CommitMergeStatus s) {
+    checkNotNull(s, "CommitMergeStatus may not be null");
     String txt = s.getMessage();
     if (s == CommitMergeStatus.CLEAN_MERGE) {
       return message(ctx, commit.getPatchsetId(), txt + getByAccountName());
@@ -434,7 +436,10 @@
         case REBASE_IF_NECESSARY:
           return message(ctx, commit, CommitMergeStatus.CLEAN_REBASE);
         default:
-          return message(ctx, commit, null);
+          throw new IllegalStateException("unexpected submit type "
+              + args.submitType.toString()
+              + " for change "
+              + commit.change().getId());
       }
     } else {
       throw new IllegalStateException("unexpected status " + s
@@ -454,7 +459,7 @@
     }
     ChangeMessage m = new ChangeMessage(
         new ChangeMessage.Key(psId.getParentKey(), uuid),
-        null, ctx.getWhen(), psId);
+        ctx.getUser().getAccountId(), ctx.getWhen(), psId);
     m.setMessage(body);
     return m;
   }
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 fe46888..bc9b2a1 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
@@ -99,7 +99,6 @@
     private final AllProjectsName allProjectsName;
     private final ReviewDb db;
     private final ProjectCache projectCache;
-    private final IdentifiedUser.GenericFactory identifiedUserFactory;
     private final ApprovalsUtil approvalsUtil;
     private final DynamicMap<ProjectConfigEntry> pluginConfigEntries;
 
@@ -110,13 +109,11 @@
     @Inject
     public ProjectConfigValidator(AllProjectsName allProjectsName,
         ReviewDb db, ProjectCache projectCache,
-        IdentifiedUser.GenericFactory iuf,
         ApprovalsUtil approvalsUtil,
         DynamicMap<ProjectConfigEntry> pluginConfigEntries) {
       this.allProjectsName = allProjectsName;
       this.db = db;
       this.projectCache = projectCache;
-      this.identifiedUserFactory = iuf;
       this.approvalsUtil = approvalsUtil;
       this.pluginConfigEntries = pluginConfigEntries;
     }
@@ -150,9 +147,7 @@
               if (psa == null) {
                 throw new MergeValidationException(SET_BY_ADMIN);
               }
-              final IdentifiedUser submitter =
-                  identifiedUserFactory.create(psa.getAccountId());
-              if (!submitter.getCapabilities().canAdministrateServer()) {
+              if (!caller.getCapabilities().canAdministrateServer()) {
                 throw new MergeValidationException(SET_BY_ADMIN);
               }
 
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 77f65a1..8dd018e 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
@@ -20,7 +20,9 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
+import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
+import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableMap;
@@ -28,6 +30,7 @@
 import com.google.common.collect.ImmutableSetMultimap;
 import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.ImmutableSortedSet;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Ordering;
 import com.google.common.primitives.Ints;
 import com.google.gerrit.common.data.SubmitRecord;
@@ -45,19 +48,29 @@
 import com.google.gerrit.server.config.AllUsersName;
 import com.google.gerrit.server.config.AllUsersNameProvider;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.ScanningChangeCacheImpl;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gerrit.server.query.change.InternalChangeQuery;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.lib.CommitBuilder;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
+import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.notes.NoteMap;
 import org.eclipse.jgit.revwalk.RevWalk;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.sql.Timestamp;
+import java.util.List;
 import java.util.Map;
 
 /** View of a single {@link Change} based on the log of its notes branch. */
@@ -103,18 +116,59 @@
 
   @Singleton
   public static class Factory {
+    private static final Logger log = LoggerFactory.getLogger(Factory.class);
+
     private final GitRepositoryManager repoManager;
     private final NotesMigration migration;
     private final AllUsersNameProvider allUsersProvider;
+    private final Provider<InternalChangeQuery> queryProvider;
+    private final ProjectCache projectCache;
 
     @VisibleForTesting
     @Inject
     public Factory(GitRepositoryManager repoManager,
         NotesMigration migration,
-        AllUsersNameProvider allUsersProvider) {
+        AllUsersNameProvider allUsersProvider,
+        Provider<InternalChangeQuery> queryProvider,
+        ProjectCache projectCache) {
       this.repoManager = repoManager;
       this.migration = migration;
       this.allUsersProvider = allUsersProvider;
+      this.queryProvider = queryProvider;
+      this.projectCache = projectCache;
+    }
+
+    public ChangeNotes createChecked(ReviewDb db, Change c)
+        throws OrmException, NoSuchChangeException {
+      ChangeNotes notes = create(db, c.getProject(), c.getId());
+      if (notes.getChange() == null) {
+        throw new NoSuchChangeException(c.getId());
+      }
+      return notes;
+    }
+
+    public ChangeNotes createChecked(ReviewDb db, Project.NameKey project,
+        Change.Id changeId) throws OrmException, NoSuchChangeException {
+      ChangeNotes notes = create(db, project, changeId);
+      if (notes.getChange() == null) {
+        throw new NoSuchChangeException(changeId);
+      }
+      return notes;
+    }
+
+    public ChangeNotes createChecked(Change.Id changeId)
+        throws OrmException, NoSuchChangeException {
+      InternalChangeQuery query = queryProvider.get().noFields();
+      List<ChangeData> changes = query.byLegacyChangeId(changeId);
+      if (changes.isEmpty()) {
+        throw new NoSuchChangeException(changeId);
+      }
+      if (changes.size() != 1) {
+        log.error(
+            String.format("Multiple changes found for %d", changeId.get()));
+        throw new NoSuchChangeException(changeId);
+      }
+      return changes.get(0).notes();
     }
 
     public ChangeNotes create(ReviewDb db, Project.NameKey project,
@@ -145,6 +199,45 @@
       return new ChangeNotes(repoManager, migration, allUsersProvider,
           change.getProject(), change).load();
     }
+
+    // TODO(ekempin): Remove when database backend is deleted
+    /**
+     * Instantiate ChangeNotes for a change that has been loaded by a batch read
+     * from the database.
+     */
+    public ChangeNotes createFromChangeOnlyWhenNotedbDisabled(Change change)
+        throws OrmException {
+      checkState(!migration.readChanges(), "do not call"
+          + " createFromChangeWhenNotedbDisabled when notedb is enabled");
+      return new ChangeNotes(repoManager, migration, allUsersProvider,
+          change.getProject(), change).load();
+    }
+
+    public ListMultimap<Project.NameKey, ChangeNotes> create(ReviewDb db,
+        Predicate<ChangeNotes> predicate) throws IOException, OrmException {
+      ListMultimap<Project.NameKey, ChangeNotes> m = ArrayListMultimap.create();
+      if (migration.readChanges()) {
+        for (Project.NameKey project : projectCache.all()) {
+          try (Repository repo = repoManager.openRepository(project)) {
+            List<ChangeNotes> changes =
+                ScanningChangeCacheImpl.scanNotedb(this, repo, db, project);
+            for (ChangeNotes cn : changes) {
+              if (predicate.apply(cn)) {
+                m.put(project, cn);
+              }
+            }
+          }
+        }
+      } else {
+        for (Change change : db.changes().all()) {
+          ChangeNotes notes = createFromChangeOnlyWhenNotedbDisabled(change);
+          if (predicate.apply(notes)) {
+            m.put(change.getProject(), notes);
+          }
+        }
+      }
+      return ImmutableListMultimap.copyOf(m);
+    }
   }
 
   private final Project.NameKey project;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
index f66a3e2..300d5fa 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ChangeControl.java
@@ -29,7 +29,6 @@
 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.ChangeFinder;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.query.change.ChangeData;
@@ -48,16 +47,13 @@
   public static class GenericFactory {
     private final ProjectControl.GenericFactory projectControl;
     private final ChangeNotes.Factory notesFactory;
-    private final ChangeFinder changeFinder;
 
     @Inject
     GenericFactory(
         ProjectControl.GenericFactory p,
-        ChangeNotes.Factory n,
-        ChangeFinder f) {
+        ChangeNotes.Factory n) {
       projectControl = p;
       notesFactory = n;
-      changeFinder = f;
     }
 
     public ChangeControl controlFor(ReviewDb db, Project.NameKey project,
@@ -92,13 +88,12 @@
 
     public ChangeControl validateFor(ReviewDb db, Change.Id changeId,
         CurrentUser user) throws NoSuchChangeException, OrmException {
-      ChangeControl ctl = changeFinder.findOne(changeId, user);
-      return validateFor(db, ctl.getChange(), user);
+      return validateFor(db, notesFactory.createChecked(changeId), user);
     }
 
-    public ChangeControl validateFor(ReviewDb db, Change change,
+    public ChangeControl validateFor(ReviewDb db, ChangeNotes notes,
         CurrentUser user) throws NoSuchChangeException, OrmException {
-      ChangeControl c = controlFor(db, change, user);
+      ChangeControl c = controlFor(notes, user);
       if (!c.isVisible(db)) {
         throw new NoSuchChangeException(c.getId());
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
deleted file mode 100644
index a2b3e23..0000000
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeDataResultSet.java
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (C) 2010 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.query.change;
-
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Provider;
-
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-import java.util.Set;
-
-abstract class ChangeDataResultSet<T> extends AbstractResultSet<ChangeData> {
-  static ResultSet<ChangeData> change(final ChangeData.Factory factory,
-      final Provider<ReviewDb> db, final ResultSet<Change> rs) {
-    return new ChangeDataResultSet<Change>(rs, true) {
-      @Override
-      ChangeData convert(Change t) {
-        return factory.create(db.get(), t);
-      }
-    };
-  }
-
-  private final ResultSet<T> source;
-  private final boolean unique;
-
-  ChangeDataResultSet(ResultSet<T> source, boolean unique) {
-    this.source = source;
-    this.unique = unique;
-  }
-
-  @Override
-  public Iterator<ChangeData> iterator() {
-    if (unique) {
-      return new Iterator<ChangeData>() {
-        private final Iterator<T> itr = source.iterator();
-
-        @Override
-        public boolean hasNext() {
-          return itr.hasNext();
-        }
-
-        @Override
-        public ChangeData next() {
-          return convert(itr.next());
-        }
-
-        @Override
-        public void remove() {
-          throw new UnsupportedOperationException();
-        }
-      };
-
-    } else {
-      return new Iterator<ChangeData>() {
-        private final Iterator<T> itr = source.iterator();
-        private final Set<Change.Id> seen = new HashSet<>();
-        private ChangeData next;
-
-        @Override
-        public boolean hasNext() {
-          if (next != null) {
-            return true;
-          }
-          while (itr.hasNext()) {
-            ChangeData d = convert(itr.next());
-            if (seen.add(d.getId())) {
-              next = d;
-              return true;
-            }
-          }
-          return false;
-        }
-
-        @Override
-        public ChangeData next() {
-          if (hasNext()) {
-            ChangeData r = next;
-            next = null;
-            return r;
-          }
-          throw new NoSuchElementException();
-        }
-
-        @Override
-        public void remove() {
-          throw new UnsupportedOperationException();
-        }
-      };
-    }
-  }
-
-  @Override
-  public void close() {
-    source.close();
-  }
-
-  abstract ChangeData convert(T t);
-}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
index e905eeb..dba2649 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/InternalChangeQuery.java
@@ -23,6 +23,7 @@
 
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -34,6 +35,8 @@
 import com.google.gerrit.server.index.IndexCollection;
 import com.google.gerrit.server.index.IndexConfig;
 import com.google.gerrit.server.index.Schema;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gwtorm.server.OrmException;
@@ -83,16 +86,22 @@
   private final QueryProcessor qp;
   private final IndexCollection indexes;
   private final ChangeData.Factory changeDataFactory;
+  private final NotesMigration notesMigration;
+  private final ChangeNotes.Factory notesFactory;
 
   @Inject
   InternalChangeQuery(IndexConfig indexConfig,
       QueryProcessor queryProcessor,
       IndexCollection indexes,
-      ChangeData.Factory changeDataFactory) {
+      ChangeData.Factory changeDataFactory,
+      NotesMigration notesMigration,
+      ChangeNotes.Factory notesFactory) {
     this.indexConfig = indexConfig;
     qp = queryProcessor.enforceVisibility(false);
     this.indexes = indexes;
     this.changeDataFactory = changeDataFactory;
+    this.notesMigration = notesMigration;
+    this.notesFactory = notesFactory;
   }
 
   public InternalChangeQuery setLimit(int n) {
@@ -110,6 +119,11 @@
     return this;
   }
 
+  public InternalChangeQuery noFields() {
+    qp.setRequestedFields(ImmutableSet.<String> of());
+    return this;
+  }
+
   public List<ChangeData> byKey(Change.Key key) throws OrmException {
     return byKeyPrefix(key.get());
   }
@@ -193,6 +207,18 @@
     }
 
     List<ChangeData> cds = new ArrayList<>(hashes.size());
+    if (notesMigration.enabled()) {
+      for (Change.Id cid : changeIds) {
+        Change c =
+            notesFactory.create(db, branch.getParentKey(), cid).getChange();
+        if (c != null && c.getDest().equals(branch)
+            && c.getStatus() != Change.Status.MERGED) {
+          cds.add(changeDataFactory.create(db, c));
+        }
+      }
+      return cds;
+    }
+
     for (Change c : db.changes().get(changeIds)) {
       if (c.getDest().equals(branch) && c.getStatus() != Change.Status.MERGED) {
         cds.add(changeDataFactory.create(db, c));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
index a3194a1..5ff859a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/IsStarredByPredicate.java
@@ -17,19 +17,15 @@
 import com.google.common.collect.Lists;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.query.OrPredicate;
 import com.google.gerrit.server.query.Predicate;
 import com.google.gerrit.server.query.QueryParseException;
 import com.google.gerrit.server.query.change.ChangeQueryBuilder.Arguments;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 
 import java.util.List;
 import java.util.Set;
 
-class IsStarredByPredicate extends OrPredicate<ChangeData> implements
-    ChangeDataSource {
+class IsStarredByPredicate extends OrPredicate<ChangeData> {
   private static String describe(CurrentUser user) {
     if (user.isIdentifiedUser()) {
       return user.getAccountId().toString();
@@ -45,17 +41,11 @@
     return r;
   }
 
-  private final Arguments args;
   private final CurrentUser user;
 
   IsStarredByPredicate(Arguments args) throws QueryParseException {
-    this(args, args.getIdentifiedUser());
-  }
-
-  private IsStarredByPredicate(Arguments args, IdentifiedUser user) {
-    super(predicates(user.getStarredChanges()));
-    this.args = args;
-    this.user = user;
+    super(predicates(args.getIdentifiedUser().getStarredChanges()));
+    this.user = args.getIdentifiedUser();
   }
 
   @Override
@@ -64,22 +54,6 @@
   }
 
   @Override
-  public ResultSet<ChangeData> read() throws OrmException {
-    return ChangeDataResultSet.change(args.changeDataFactory, args.db,
-        args.db.get().changes().get(user.getStarredChanges()));
-  }
-
-  @Override
-  public boolean hasChange() {
-    return true;
-  }
-
-  @Override
-  public int getCardinality() {
-    return 10;
-  }
-
-  @Override
   public int getCost() {
     return 0;
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
index 12e6c35..6282415 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/config/ConfigUtilTest.java
@@ -67,9 +67,7 @@
       i.bd = true;
       i.s = "foo";
       i.sd = "bar";
-      // This line is not needed, as it's null per default.
-      // Put it here to be explicit.
-      i.nd = null;
+      // i.nd = null; // Don't need to explicitly set it; it's null by default
       i.t = Theme.DEFAULT;
       i.td = Theme.DEFAULT;
       return i;
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 da83bae..65d7312 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
@@ -29,6 +29,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.TimeUtil;
@@ -40,6 +41,7 @@
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestApiException;
 import com.google.gerrit.lifecycle.LifecycleManager;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
@@ -89,6 +91,7 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Iterator;
 import java.util.List;
 
 @Ignore
@@ -1461,11 +1464,50 @@
       throws Exception {
     List<ChangeInfo> result = query.get();
     Iterable<Integer> ids = ids(result);
-    assertThat(ids).named(query.getQuery())
+    assertThat(ids).named(format(query, ids, changes))
         .containsExactlyElementsIn(ids(changes)).inOrder();
     return result;
   }
 
+  private String format(QueryRequest query, Iterable<Integer> actualIds,
+      Change... expectedChanges) throws RestApiException {
+    StringBuilder b = new StringBuilder();
+    b.append("query '").append(query.getQuery())
+     .append("' with expected changes ");
+    b.append(format(Iterables.transform(Arrays.asList(expectedChanges),
+        new Function<Change, Integer>() {
+          @Override
+          public Integer apply(Change change) {
+            return change.getChangeId();
+          }
+        })));
+    b.append(" and result ");
+    b.append(format(actualIds));
+    return b.toString();
+  }
+
+  private String format(Iterable<Integer> changeIds) throws RestApiException {
+    StringBuilder b = new StringBuilder();
+    b.append("[");
+    Iterator<Integer> it = changeIds.iterator();
+    while (it.hasNext()) {
+      int id = it.next();
+      ChangeInfo c = gApi.changes().id(id).get();
+      b.append("{").append(id).append(" (").append(c.changeId)
+          .append("), ").append("dest=").append(
+              new Branch.NameKey(
+                  new Project.NameKey(c.project), c.branch)).append(", ")
+          .append("status=").append(c.status).append(", ")
+          .append("lastUpdated=").append(c.updated.getTime())
+          .append("}");
+      if (it.hasNext()) {
+        b.append(", ");
+      }
+    }
+    b.append("]");
+    return b.toString();
+  }
+
   protected static Iterable<Integer> ids(Change... changes) {
     return FluentIterable.from(Arrays.asList(changes)).transform(
         new Function<Change, Integer>() {
diff --git a/gerrit-sshd/BUCK b/gerrit-sshd/BUCK
index 457f12e..fa45746 100644
--- a/gerrit-sshd/BUCK
+++ b/gerrit-sshd/BUCK
@@ -28,8 +28,8 @@
     '//lib/log:log4j',
     '//lib/mina:core',
     '//lib/mina:sshd',
-    '//lib/jgit:jgit',
-    '//lib/jgit:jgit-archive',
+    '@jgit//:jgit',
+    '@jgit//:jgit-archive',
   ],
   provided_deps = [
     '//lib/bouncycastle:bcpkix',
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
index 0ff3a01..850026b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/AdminQueryShell.java
@@ -46,17 +46,17 @@
   protected void run() throws Failure {
     try {
       checkPermission();
-
-      final QueryShell shell = factory.create(in, out);
-      shell.setOutputFormat(format);
-      if (query != null) {
-        shell.execute(query);
-      } else {
-        shell.run();
-      }
     } catch (PermissionDeniedException err) {
       throw new UnloggedFailure("fatal: " + err.getMessage());
     }
+
+    QueryShell shell = factory.create(in, out);
+    shell.setOutputFormat(format);
+    if (query != null) {
+      shell.execute(query);
+    } else {
+      shell.run();
+    }
   }
 
   /**
@@ -65,7 +65,8 @@
    * As the @RequireCapability guards at various entry points of internal
    * commands implicitly add administrators (which we want to avoid), we also
    * check permissions within QueryShell and grant access only to those who
-   * canPerformRawQuery, regardless of whether they are administrators or not.
+   * can access the database, regardless of whether they are administrators or
+   * not.
    *
    * @throws PermissionDeniedException
    */
diff --git a/gerrit-util-http/BUCK b/gerrit-util-http/BUCK
index e9b2a3d..328e7a4 100644
--- a/gerrit-util-http/BUCK
+++ b/gerrit-util-http/BUCK
@@ -15,7 +15,7 @@
     '//lib:guava',
     '//lib:servlet-api-3_1',
     '//lib/httpcomponents:httpclient',
-    '//lib/jgit:jgit',
+    '@jgit//:jgit',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index d5c85ad..71ce2c6 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -24,7 +24,7 @@
     '//lib/guice:guice',
     '//lib/guice:guice-servlet',
     '//lib/log:api',
-    '//lib/jgit:jgit',
+    '@jgit//:jgit',
   ],
   provided_deps = ['//lib:servlet-api-3_1'],
   visibility = [
diff --git a/lib/BUCK b/lib/BUCK
index cb6e65d..97c26f3 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -10,7 +10,6 @@
 define_license(name = 'clippy')
 define_license(name = 'codemirror')
 define_license(name = 'diffy')
-define_license(name = 'freebie_application_icon_set')
 define_license(name = 'h2')
 define_license(name = 'jgit')
 define_license(name = 'jsch')
@@ -249,14 +248,13 @@
   sha1 = '18a9a2ce6abf32ea1b5fd31dae5210ad93f4e5e3',
   license = 'xz',
   attach_source = False,
-  visibility = ['//lib/jgit:jgit-archive'],
+  visibility = ['//gerrit-server:server'],
 )
 
 maven_jar(
-  name = 'javassist-3.17.1-GA',
-  # The GWT version is still at 3.16.1-GA, so those do not match
-  id = 'org.javassist:javassist:3.17.1-GA',
-  sha1 = '30c30512115866b6e0123f1913bc7735b9f76d08',
+  name = 'javassist',
+  id = 'org.javassist:javassist:3.18.1-GA',
+  sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
   license = 'DO_NOT_DISTRIBUTE',
 )
 
diff --git a/lib/LICENSE-freebie_application_icon_set b/lib/LICENSE-freebie_application_icon_set
deleted file mode 100644
index 753ab1f..0000000
--- a/lib/LICENSE-freebie_application_icon_set
+++ /dev/null
@@ -1 +0,0 @@
-link:http://creativecommons.org/licenses/by/3.0/us/[CC-BY 3.0] (c) Matt Gentile, link:http://tympanus.net/codrops/2012/10/02/freebie-application-icon-set-png-psd-csh/[Freebie: Application Icon Set]
diff --git a/lib/codemirror/cm.defs b/lib/codemirror/cm.defs
index 9677058..39c4f09 100644
--- a/lib/codemirror/cm.defs
+++ b/lib/codemirror/cm.defs
@@ -19,6 +19,7 @@
   'edit/trailingspace.js',
   'scroll/annotatescrollbar.js',
   'scroll/simplescrollbars.js',
+  'search/jump-to-line.js',
   'search/matchesonscrollbar.js',
   'search/searchcursor.js',
   'search/search.js',
diff --git a/lib/gwt/BUCK b/lib/gwt/BUCK
index 6876dfe..44a9341 100644
--- a/lib/gwt/BUCK
+++ b/lib/gwt/BUCK
@@ -28,22 +28,3 @@
   visibility = ['PUBLIC'],
 )
 
-maven_jar(
-  name = 'gwt-test-utils',
-  id = 'com.googlecode.gwt-test-utils:gwt-test-utils:0.47',
-  sha1 = '284749ed37d8034bac05e374070c09cce88db540',
-  license = 'Apache2.0',
-  deps = [
-    ':javassist',
-    '//lib/log:api',
-  ],
-  visibility = ['PUBLIC'],
-)
-
-maven_jar(
-  name = 'javassist',
-  id = 'org.javassist:javassist:3.18.1-GA',
-  sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
-  license = 'Apache2.0',
-  visibility = ['PUBLIC'],
-)
diff --git a/lib/jgit/.buckconfig b/lib/jgit/.buckconfig
new file mode 100644
index 0000000..267958f
--- /dev/null
+++ b/lib/jgit/.buckconfig
@@ -0,0 +1,3 @@
+[cache]
+  mode = dir
+  dir = ~/.gerritcodereview/buck-cache/locally-built-artifacts
diff --git a/lib/jgit/.gitignore b/lib/jgit/.gitignore
new file mode 100644
index 0000000..0a0ddb2
--- /dev/null
+++ b/lib/jgit/.gitignore
@@ -0,0 +1 @@
+/buck-out
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index 87d3b2c..bf3e2a8 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -39,16 +39,13 @@
   sha1 = 'e599670a9b163182868e2e928e44eb93c5d2b1ab',
   license = 'jgit',
   repository = REPO,
-  deps = [':jgit',
-    '//lib/commons:compress',
-    '//lib:tukaani-xz',
-  ],
+  deps = [':jgit'],
   unsign = True,
   exclude = [
     'about.html',
     'plugin.properties',
   ],
-)
+ )
 
 maven_jar(
   name = 'junit',
@@ -66,24 +63,3 @@
   sha1 = 'eceaf316a8faf0e794296ebe158ae110c7d72a5a',
   license = 'Apache2.0',
 )
-
-gwt_module(
-  name = 'Edit',
-  srcs = [':jgit_edit_src'],
-  deps = [':edit_src'],
-  visibility = ['PUBLIC'],
-)
-
-prebuilt_jar(
-  name = 'edit_src',
-  binary_jar = ':jgit_edit_src',
-)
-
-genrule(
-  name = 'jgit_edit_src',
-  cmd = 'unzip -qd $TMP $(location :jgit_src) ' +
-    'org/eclipse/jgit/diff/Edit.java;' +
-    'cd $TMP;' +
-    'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java',
-  out = 'edit.src.zip',
-)
diff --git a/lib/jgit/lib/BUCK b/lib/jgit/lib/BUCK
new file mode 100644
index 0000000..fe0bbb1
--- /dev/null
+++ b/lib/jgit/lib/BUCK
@@ -0,0 +1,6 @@
+include_defs('//lib/maven.defs')
+
+define_license(name = 'Apache2.0')
+define_license(name = 'jgit')
+
+define_license(name = 'DO_NOT_DISTRIBUTE')
diff --git a/lib/jgit/lib/LICENSE-Apache2.0 b/lib/jgit/lib/LICENSE-Apache2.0
new file mode 120000
index 0000000..de5a79a
--- /dev/null
+++ b/lib/jgit/lib/LICENSE-Apache2.0
@@ -0,0 +1 @@
+../../LICENSE-Apache2.0
\ No newline at end of file
diff --git a/lib/jgit/lib/LICENSE-DO_NOT_DISTRIBUTE b/lib/jgit/lib/LICENSE-DO_NOT_DISTRIBUTE
new file mode 120000
index 0000000..6b0859b
--- /dev/null
+++ b/lib/jgit/lib/LICENSE-DO_NOT_DISTRIBUTE
@@ -0,0 +1 @@
+../../LICENSE-DO_NOT_DISTRIBUTE
\ No newline at end of file
diff --git a/lib/jgit/lib/LICENSE-jgit b/lib/jgit/lib/LICENSE-jgit
new file mode 120000
index 0000000..bfa3da5
--- /dev/null
+++ b/lib/jgit/lib/LICENSE-jgit
@@ -0,0 +1 @@
+../../LICENSE-jgit
\ No newline at end of file
diff --git a/lib/jgit/lib/maven.defs b/lib/jgit/lib/maven.defs
new file mode 120000
index 0000000..7f9292f
--- /dev/null
+++ b/lib/jgit/lib/maven.defs
@@ -0,0 +1 @@
+../../maven.defs
\ No newline at end of file
diff --git a/lib/jgit/tools/BUCK b/lib/jgit/tools/BUCK
new file mode 120000
index 0000000..7e536d7
--- /dev/null
+++ b/lib/jgit/tools/BUCK
@@ -0,0 +1 @@
+../../../tools/BUCK
\ No newline at end of file
diff --git a/lib/jgit/tools/__init__.py b/lib/jgit/tools/__init__.py
new file mode 120000
index 0000000..737f585
--- /dev/null
+++ b/lib/jgit/tools/__init__.py
@@ -0,0 +1 @@
+../../../tools/__init__.py
\ No newline at end of file
diff --git a/lib/jgit/tools/download_file.py b/lib/jgit/tools/download_file.py
new file mode 120000
index 0000000..e49dc4c
--- /dev/null
+++ b/lib/jgit/tools/download_file.py
@@ -0,0 +1 @@
+../../../tools/download_file.py
\ No newline at end of file
diff --git a/lib/jgit/tools/merge_jars.py b/lib/jgit/tools/merge_jars.py
new file mode 120000
index 0000000..399e418
--- /dev/null
+++ b/lib/jgit/tools/merge_jars.py
@@ -0,0 +1 @@
+../../../tools/merge_jars.py
\ No newline at end of file
diff --git a/lib/jgit/tools/pack_war.py b/lib/jgit/tools/pack_war.py
new file mode 120000
index 0000000..8621a6c
--- /dev/null
+++ b/lib/jgit/tools/pack_war.py
@@ -0,0 +1 @@
+../../../tools/pack_war.py
\ No newline at end of file
diff --git a/lib/jgit/tools/util.py b/lib/jgit/tools/util.py
new file mode 120000
index 0000000..8041ac5
--- /dev/null
+++ b/lib/jgit/tools/util.py
@@ -0,0 +1 @@
+../../../tools/util.py
\ No newline at end of file
diff --git a/lib/jgit/tools/util_test.py b/lib/jgit/tools/util_test.py
new file mode 120000
index 0000000..b85ecbd
--- /dev/null
+++ b/lib/jgit/tools/util_test.py
@@ -0,0 +1 @@
+../../../tools/util_test.py
\ No newline at end of file
diff --git a/lib/maven.defs b/lib/maven.defs
index 5c29bed..4107770 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -132,7 +132,6 @@
       deps = deps + license,
       binary_jar = ':%s__download_bin' % name,
       source_jar = ':%s__download_src' % name if srcjar else None,
-      visibility = visibility,
     )
     java_library(
       name = name,
diff --git a/lib/powermock/BUCK b/lib/powermock/BUCK
index 5ac97c4..221a640 100644
--- a/lib/powermock/BUCK
+++ b/lib/powermock/BUCK
@@ -66,7 +66,7 @@
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-reflect',
-    '//lib:javassist-3.17.1-GA',
+    '//lib:javassist',
     '//lib:junit',
   ],
 )
diff --git a/plugins/replication b/plugins/replication
index 7d8ba76..f0d0185 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 7d8ba7628ff1addf409ec170801202c8f8f63e7e
+Subproject commit f0d0185aec4fd90321626a050ed807e40048128e
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 552001e..47c74e2 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 552001eeaf6ffbfdb1f1ecd08cd9b34c94ca7ed8
+Subproject commit 47c74e2da9443e028d8d52d95791e0cd15add822
diff --git a/polygerrit-ui/README.md b/polygerrit-ui/README.md
index cbc2660..dc0a731 100644
--- a/polygerrit-ui/README.md
+++ b/polygerrit-ui/README.md
@@ -65,3 +65,9 @@
 ```sh
 WCT_ARGS='-p --some-flag="foo bar"' buck test --no-results-cache --include web
 ```
+
+## Style guide
+
+We follow the [Google JavaScript Style Guide](https://google.github.io/styleguide/javascriptguide.xml)
+with a few exceptions. When in doubt, remain consistent with the code around you.
+
diff --git a/polygerrit-ui/app/elements/gr-diff-preferences.html b/polygerrit-ui/app/elements/gr-diff-preferences.html
index 221d5c3..7656403 100644
--- a/polygerrit-ui/app/elements/gr-diff-preferences.html
+++ b/polygerrit-ui/app/elements/gr-diff-preferences.html
@@ -177,11 +177,11 @@
       },
 
       _handleSave: function() {
-        this.fire('save', null, {bubble: false});
+        this.fire('save', null, {bubbles: false});
       },
 
       _handleCancel: function() {
-        this.fire('cancel', null, {bubble: false});
+        this.fire('cancel', null, {bubbles: false});
       },
     });
   })();
diff --git a/tools/download_all.py b/tools/download_all.py
index 58316ca..e46f48c 100755
--- a/tools/download_all.py
+++ b/tools/download_all.py
@@ -19,6 +19,10 @@
 
 MAIN = ['//tools/eclipse:classpath']
 PAT = re.compile(r'"(//.*?)" -> "//tools:download_file"')
+# TODO(davido): Remove this hack when Buck bug is fixed:
+# https://github.com/facebook/buck/issues/656
+JGIT = re.compile(r'//:ewah|//:jgit|//:junit')
+CELL = '//lib/jgit'
 
 opts = OptionParser()
 opts.add_option('--src', action='store_true')
@@ -31,6 +35,8 @@
   m = PAT.search(line)
   if m:
     n = m.group(1)
+    if JGIT.match(n):
+      n = CELL + n[2:]
     if args.src and n.endswith('__download_bin'):
       n = n[:-13] + 'src'
     targets.add(n)
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index d36e503..a156cd0 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -128,7 +128,8 @@
       continue
 
     m = java_library.match(p)
-    if m:
+    # Don't grab the cross-cell JGit libraries as source
+    if m and not m.group(1).startswith('org.eclipse.jgit'):
       src.add(m.group(1))
     else:
       lib.add(p)
diff --git a/tools/java_doc.defs b/tools/java_doc.defs
index 514a730..65865bb 100644
--- a/tools/java_doc.defs
+++ b/tools/java_doc.defs
@@ -29,7 +29,7 @@
       '-sourcepath ',
       ':'.join(sourcepath),
       ' -classpath ',
-      ':'.join(['$(location %s)' % n for n in deps]),
+      ':'.join(['$(classpath %s)' % n for n in deps]),
       '-d $TMP',
     ]) + ';jar cf $OUT -C $TMP .',
     srcs = srcs,
diff --git a/tools/pack_war.py b/tools/pack_war.py
index ca21790..cd836a8 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -28,6 +28,7 @@
 
 war = args.tmp
 jars = set()
+basenames = set()
 
 def prune(l):
   return [j for e in l for j in e.split(':')]
@@ -36,10 +37,19 @@
   makedirs(directory)
   for j in libs:
     if j not in jars:
+      # When jgit is consumed from its own cell,
+      # potential duplicates should be filtered.
+      # e.g. jsch.jar will be reached through:
+      # 1. /home/username/projects/gerrit/buck-out/gen/lib/jsch.jar
+      # 2. /home/username/projects/jgit/buck-out/gen/lib/jsch.jar
+      if (j.find('jgit/buck-out/gen/lib') > 0
+          and path.basename(j) in basenames):
+          continue
       jars.add(j)
       n = path.basename(j)
       if j.find('buck-out/gen/gerrit-') > 0:
         n = j[j.find('buck-out'):].split('/')[2] + '-' + n
+      basenames.add(n)
       symlink(j, path.join(directory, n))
 
 if args.lib: