Merge "Also mention that MySQL can support replication, not just Postgres"
diff --git a/.gitignore b/.gitignore
index f318b65..465893d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,5 @@
 /.settings/org.eclipse.jdt.core.prefs
 /.settings/org.maven.ide.eclipse.prefs
 /test_site
+/.idea
+/gerrit-parent.iml
diff --git a/Documentation/cmd-ban-commit.txt b/Documentation/cmd-ban-commit.txt
new file mode 100644
index 0000000..fb4a2ac9
--- /dev/null
+++ b/Documentation/cmd-ban-commit.txt
@@ -0,0 +1,60 @@
+gerrit ban-commit
+=================
+
+NAME
+----
+gerrit ban-commit - Bans a commit from a project's repository.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit ban-commit'
+  [--reason <REASON>]
+  <PROJECT>
+  <COMMIT> ...
+
+DESCRIPTION
+-----------
+Marks a commit as banned for the specified repository.  If a commit is
+banned Gerrit rejects every push that includes this commit with
+link:error-contains-banned-commit.html[contains banned commit ...].
+
+[NOTE]
+This command just marks the commit as banned, but it does not remove
+the commit from the history of any central branch.  This needs to be
+done manually.
+
+ACCESS
+------
+Caller must be owner of the project or be a member of the privileged
+'Administrators' group.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+OPTIONS
+-------
+<PROJECT>::
+	Required; name of the project for which the commit should be
+	banned.
+
+<COMMIT>::
+	Required; commit(s) that should be banned.
+
+--reason::
+	Reason for banning the commit.
+
+EXAMPLES
+--------
+Ban commit `421919d015c062fd28901fe144a78a555d0b5984` from project
+`myproject`:
+
+====
+	$ ssh -p 29418 review.example.com gerrit ban-commit myproject \
+	421919d015c062fd28901fe144a78a555d0b5984
+====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index b09c3b3..e7d59fb 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -54,6 +54,9 @@
 'gerrit approve'::
 	'Deprecated alias for `gerrit review`.'
 
+link:cmd-ban-commit.html[gerrit ban-commit]::
+	Bans a commit from a project's repository.
+
 link:cmd-ls-groups.html[gerrit ls-groups]::
 	List groups visible to the caller.
 
diff --git a/gerrit-antlr/.gitignore b/gerrit-antlr/.gitignore
index 194bedc..fb047af 100644
--- a/gerrit-antlr/.gitignore
+++ b/gerrit-antlr/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-antlr.iml
\ No newline at end of file
diff --git a/gerrit-antlr/pom.xml b/gerrit-antlr/pom.xml
index aa0d7fd..34cb46f 100644
--- a/gerrit-antlr/pom.xml
+++ b/gerrit-antlr/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-antlr</artifactId>
diff --git a/gerrit-common/.gitignore b/gerrit-common/.gitignore
index 194bedc..759f12c 100644
--- a/gerrit-common/.gitignore
+++ b/gerrit-common/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-common.iml
\ No newline at end of file
diff --git a/gerrit-common/pom.xml b/gerrit-common/pom.xml
index e7933ea..9b3fe5f 100644
--- a/gerrit-common/pom.xml
+++ b/gerrit-common/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-common</artifactId>
diff --git a/gerrit-ehcache/.gitignore b/gerrit-ehcache/.gitignore
index 20251d4..fe190c9 100644
--- a/gerrit-ehcache/.gitignore
+++ b/gerrit-ehcache/.gitignore
@@ -3,3 +3,4 @@
 /.project
 /.settings/org.eclipse.m2e.core.prefs
 /.settings/org.maven.ide.eclipse.prefs
+/gerrit-ehcache.iml
\ No newline at end of file
diff --git a/gerrit-ehcache/pom.xml b/gerrit-ehcache/pom.xml
index 839c52b..f9117b9 100644
--- a/gerrit-ehcache/pom.xml
+++ b/gerrit-ehcache/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-ehcache</artifactId>
diff --git a/gerrit-gwtdebug/.gitignore b/gerrit-gwtdebug/.gitignore
index 194bedc..4207862 100644
--- a/gerrit-gwtdebug/.gitignore
+++ b/gerrit-gwtdebug/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-gwtdebug.iml
\ No newline at end of file
diff --git a/gerrit-gwtdebug/pom.xml b/gerrit-gwtdebug/pom.xml
index 734f645..01b93a6 100644
--- a/gerrit-gwtdebug/pom.xml
+++ b/gerrit-gwtdebug/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtdebug</artifactId>
diff --git a/gerrit-gwtui/.gitignore b/gerrit-gwtui/.gitignore
index 194bedc..53d46b3 100644
--- a/gerrit-gwtui/.gitignore
+++ b/gerrit-gwtui/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-gwtui.iml
\ No newline at end of file
diff --git a/gerrit-gwtui/pom.xml b/gerrit-gwtui/pom.xml
index d6ac743..b3291d1 100644
--- a/gerrit-gwtui/pom.xml
+++ b/gerrit-gwtui/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-gwtui</artifactId>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
index 74a2678..13bba12 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/ErrorDialog.java
@@ -25,6 +25,7 @@
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.user.client.PluginSafePopupPanel;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
@@ -94,6 +95,11 @@
     body.add(message.toBlockWidget());
   }
 
+  public ErrorDialog(final Widget w) {
+    this();
+    body.add(w);
+  }
+
   /** Create a dialog box to nicely format an exception. */
   public ErrorDialog(final Throwable what) {
     this();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
index a8315d8..574f58e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritCss.java
@@ -104,6 +104,7 @@
   String errorDialogTitle();
   String errorDialogButtons();
   String errorDialogErrorType();
+  String errorDialogText();
   String fileColumnHeader();
   String fileLine();
   String fileLineCONTEXT();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index 9250ca3..d049ff6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -89,6 +89,7 @@
   String initialRevision();
   String buttonAddBranch();
   String buttonDeleteBranch();
+  String branchDeletionOpenChanges();
 
   String groupListPrev();
   String groupListNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 389ed2c..7e0edec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -68,6 +68,8 @@
 initialRevision = Initial Revision
 buttonAddBranch = Create Branch
 buttonDeleteBranch = Delete
+branchDeletionOpenChanges = The following branches were not deleted \
+because they have open changes:
 
 groupListPrev = Previous group
 groupListNext = Next group
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
index f3ecbb3..56d9417 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectBranchesScreen.java
@@ -16,16 +16,19 @@
 
 import com.google.gerrit.client.ConfirmationCallback;
 import com.google.gerrit.client.ConfirmationDialog;
+import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.GitwebLink;
 import com.google.gerrit.client.rpc.GerritCallback;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
+import com.google.gerrit.client.ui.BranchLink;
 import com.google.gerrit.client.ui.FancyFlexTable;
 import com.google.gerrit.client.ui.HintTextBox;
 import com.google.gerrit.common.data.ListBranchesResult;
 import com.google.gerrit.common.errors.InvalidNameException;
 import com.google.gerrit.common.errors.InvalidRevisionException;
 import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.Scheduler;
 import com.google.gwt.core.client.Scheduler.ScheduledCommand;
@@ -41,6 +44,7 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.Grid;
 import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.VerticalPanel;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import com.google.gwtjsonrpc.client.RemoteJsonException;
 
@@ -260,24 +264,52 @@
               b.toSafeHtml(), new ConfirmationCallback() {
         @Override
         public void onOk() {
-          Util.PROJECT_SVC.deleteBranch(getProjectKey(), ids,
-              new GerritCallback<Set<Branch.NameKey>>() {
-                public void onSuccess(final Set<Branch.NameKey> deleted) {
-                  for (int row = 1; row < table.getRowCount();) {
-                    final Branch k = getRowItem(row);
-                    if (k != null && deleted.contains(k.getNameKey())) {
-                      table.removeRow(row);
-                    } else {
-                      row++;
-                    }
-                  }
-                }
-              });
+          deleteBranches(ids);
         }
       });
       confirmationDialog.center();
     }
 
+    private void deleteBranches(final Set<Branch.NameKey> branchIds) {
+      Util.PROJECT_SVC.deleteBranch(getProjectKey(), branchIds,
+          new GerritCallback<Set<Branch.NameKey>>() {
+            public void onSuccess(final Set<Branch.NameKey> deleted) {
+              if (!deleted.isEmpty()) {
+                for (int row = 1; row < table.getRowCount();) {
+                  final Branch k = getRowItem(row);
+                  if (k != null && deleted.contains(k.getNameKey())) {
+                    table.removeRow(row);
+                  } else {
+                    row++;
+                  }
+                }
+              }
+
+              branchIds.removeAll(deleted);
+              if (!branchIds.isEmpty()) {
+                final VerticalPanel p = new VerticalPanel();
+                final ErrorDialog errorDialog = new ErrorDialog(p);
+                final Label l = new Label(Util.C.branchDeletionOpenChanges());
+                l.setStyleName(Gerrit.RESOURCES.css().errorDialogText());
+                p.add(l);
+                for (final Branch.NameKey branch : branchIds) {
+                  final BranchLink link =
+                      new BranchLink(branch.getParentKey(), Change.Status.NEW,
+                          branch.get(), null) {
+                    @Override
+                    public void go() {
+                      errorDialog.hide();
+                      super.go();
+                    };
+                  };
+                  p.add(link);
+                }
+                errorDialog.center();
+              }
+            }
+          });
+    }
+
     void display(final List<Branch> result) {
       canDelete = false;
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
index 87208c6..52c37c3 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/gerrit.css
@@ -375,6 +375,18 @@
   width: 100%;
   margin-top: 15px;
 }
+.errorDialogText {
+  font-size: 15px;
+  font-family: verdana;
+}
+.errorDialog a,
+.errorDialog a:visited,
+.errorDialog a:hover {
+  color: white;
+  font-weight: bold;
+  font-size: 15px;
+  font-family: verdana;
+}
 
 
 /** Screen **/
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
index ef3faf8..964ba4b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/SideBySideTable.java
@@ -34,6 +34,7 @@
 import com.google.gwt.user.client.ui.Anchor;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
+import com.google.gwt.user.client.ui.HasVerticalAlignment;
 import com.google.gwt.user.client.ui.InlineLabel;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
@@ -382,7 +383,11 @@
 
     CellFormatter fmt = table.getCellFormatter();
     for (int i = 0 + offset; i < loopTo + offset; i++) {
-      insertRow(row + i);
+      // The overridden version of insertRow adds some css classes we don't
+      // want.
+      super.insertRow(row + i);
+      table.getRowFormatter().setVerticalAlign(row + i,
+          HasVerticalAlignment.ALIGN_TOP);
       int lineA = line.getStartA() + i;
       int lineB = line.getStartB() + i;
       if (numRows < 0) {
diff --git a/gerrit-httpd/.gitignore b/gerrit-httpd/.gitignore
index 194bedc..5bbeafd 100644
--- a/gerrit-httpd/.gitignore
+++ b/gerrit-httpd/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-httpd.iml
\ No newline at end of file
diff --git a/gerrit-httpd/pom.xml b/gerrit-httpd/pom.xml
index a6374da..ceacb66 100644
--- a/gerrit-httpd/pom.xml
+++ b/gerrit-httpd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-httpd</artifactId>
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
index 47a9395..ab266f3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/ChangeDetailFactory.java
@@ -134,7 +134,7 @@
     detail.setCanEdit(control.getRefControl().canWrite());
 
     if (detail.getChange().getStatus().isOpen()) {
-      List<SubmitRecord> submitRecords = control.canSubmit(db, patch.getId());
+      List<SubmitRecord> submitRecords = control.canSubmit(db, patch);
       for (SubmitRecord rec : submitRecords) {
         if (rec.labels != null) {
           for (SubmitRecord.Label lbl : rec.labels) {
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
index 638bfe3..183b5f6 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/changedetail/PatchSetPublishDetailFactory.java
@@ -83,7 +83,8 @@
     final Change.Id changeId = patchSetId.getParentKey();
     final ChangeControl control = changeControlFactory.validateFor(changeId);
     change = control.getChange();
-    patchSetInfo = infoFactory.get(db, patchSetId);
+    PatchSet patchSet = db.patchSets().get(patchSetId);
+    patchSetInfo = infoFactory.get(change, patchSet);
     drafts = db.patchComments().draftByPatchSetAuthor(patchSetId, user.getAccountId()).toList();
 
     aic.want(change.getOwner());
@@ -119,7 +120,7 @@
           .toList();
 
       boolean couldSubmit = false;
-      List<SubmitRecord> submitRecords = control.canSubmit(db, patchSetId);
+      List<SubmitRecord> submitRecords = control.canSubmit(db, patchSet);
       for (SubmitRecord rec : submitRecords) {
         if (rec.status == SubmitRecord.Status.OK) {
           couldSubmit = true;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
index 073e2d7..a3a1c25 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/DeleteBranches.java
@@ -18,11 +18,13 @@
 import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.ReplicationQueue;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
+import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 
@@ -34,6 +36,7 @@
 
 import java.io.IOException;
 import java.util.HashSet;
+import java.util.Iterator;
 import java.util.Set;
 
 class DeleteBranches extends Handler<Set<Branch.NameKey>> {
@@ -50,6 +53,7 @@
   private final ReplicationQueue replication;
   private final IdentifiedUser identifiedUser;
   private final ChangeHooks hooks;
+  private final ReviewDb db;
 
   private final Project.NameKey projectName;
   private final Set<Branch.NameKey> toRemove;
@@ -60,6 +64,7 @@
       final ReplicationQueue replication,
       final IdentifiedUser identifiedUser,
       final ChangeHooks hooks,
+      final ReviewDb db,
 
       @Assisted Project.NameKey name, @Assisted Set<Branch.NameKey> toRemove) {
     this.projectControlFactory = projectControlFactory;
@@ -67,6 +72,7 @@
     this.replication = replication;
     this.identifiedUser = identifiedUser;
     this.hooks = hooks;
+    this.db = db;
 
     this.projectName = name;
     this.toRemove = toRemove;
@@ -74,17 +80,23 @@
 
   @Override
   public Set<Branch.NameKey> call() throws NoSuchProjectException,
-      RepositoryNotFoundException {
+      RepositoryNotFoundException, OrmException {
     final ProjectControl projectControl =
         projectControlFactory.controlFor(projectName);
 
-    for (Branch.NameKey k : toRemove) {
+    final Iterator<Branch.NameKey> branchIt = toRemove.iterator();
+    while (branchIt.hasNext()) {
+      final Branch.NameKey k = branchIt.next();
       if (!projectName.equals(k.getParentKey())) {
         throw new IllegalArgumentException("All keys must be from same project");
       }
       if (!projectControl.controlForRef(k).canDelete()) {
         throw new IllegalStateException("Cannot delete " + k.getShortName());
       }
+
+      if (db.changes().byBranchOpenAll(k).iterator().hasNext()) {
+        branchIt.remove();
+      }
     }
 
     final Set<Branch.NameKey> deleted = new HashSet<Branch.NameKey>();
diff --git a/gerrit-launcher/.gitignore b/gerrit-launcher/.gitignore
index 194bedc..980a6b1 100644
--- a/gerrit-launcher/.gitignore
+++ b/gerrit-launcher/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-launcher.iml
\ No newline at end of file
diff --git a/gerrit-launcher/pom.xml b/gerrit-launcher/pom.xml
index 7d07652..e700351 100644
--- a/gerrit-launcher/pom.xml
+++ b/gerrit-launcher/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-launcher</artifactId>
diff --git a/gerrit-main/.gitignore b/gerrit-main/.gitignore
index 194bedc..c847710 100644
--- a/gerrit-main/.gitignore
+++ b/gerrit-main/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-main.iml
\ No newline at end of file
diff --git a/gerrit-main/pom.xml b/gerrit-main/pom.xml
index 9d8320c..bb2d763 100644
--- a/gerrit-main/pom.xml
+++ b/gerrit-main/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-main</artifactId>
diff --git a/gerrit-openid/.gitignore b/gerrit-openid/.gitignore
index 194bedc..158faf1 100644
--- a/gerrit-openid/.gitignore
+++ b/gerrit-openid/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-openid.iml
\ No newline at end of file
diff --git a/gerrit-openid/pom.xml b/gerrit-openid/pom.xml
index ed2625e..fa4ab95 100644
--- a/gerrit-openid/pom.xml
+++ b/gerrit-openid/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-openid</artifactId>
diff --git a/gerrit-patch-commonsnet/.gitignore b/gerrit-patch-commonsnet/.gitignore
index 194bedc..121f8e90 100644
--- a/gerrit-patch-commonsnet/.gitignore
+++ b/gerrit-patch-commonsnet/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-patch-commonsnet.iml
\ No newline at end of file
diff --git a/gerrit-patch-commonsnet/pom.xml b/gerrit-patch-commonsnet/pom.xml
index 75ee12e..f1a8b3e 100644
--- a/gerrit-patch-commonsnet/pom.xml
+++ b/gerrit-patch-commonsnet/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-commonsnet</artifactId>
diff --git a/gerrit-patch-jgit/.gitignore b/gerrit-patch-jgit/.gitignore
index 194bedc..7c4c433 100644
--- a/gerrit-patch-jgit/.gitignore
+++ b/gerrit-patch-jgit/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-patch-jgit.iml
\ No newline at end of file
diff --git a/gerrit-patch-jgit/pom.xml b/gerrit-patch-jgit/pom.xml
index f8190f5..65223fb 100644
--- a/gerrit-patch-jgit/pom.xml
+++ b/gerrit-patch-jgit/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-patch-jgit</artifactId>
diff --git a/gerrit-pgm/.gitignore b/gerrit-pgm/.gitignore
index 194bedc..dafe355 100644
--- a/gerrit-pgm/.gitignore
+++ b/gerrit-pgm/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-pgm.iml
\ No newline at end of file
diff --git a/gerrit-pgm/pom.xml b/gerrit-pgm/pom.xml
index 1463d15..a015219 100644
--- a/gerrit-pgm/pom.xml
+++ b/gerrit-pgm/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-pgm</artifactId>
diff --git a/gerrit-prettify/.gitignore b/gerrit-prettify/.gitignore
index 194bedc..8cf95ef 100644
--- a/gerrit-prettify/.gitignore
+++ b/gerrit-prettify/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-prettify.iml
\ No newline at end of file
diff --git a/gerrit-prettify/pom.xml b/gerrit-prettify/pom.xml
index f5bd3d6..9354274 100644
--- a/gerrit-prettify/pom.xml
+++ b/gerrit-prettify/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-prettify</artifactId>
diff --git a/gerrit-reviewdb/.gitignore b/gerrit-reviewdb/.gitignore
index 194bedc..812ddd0 100644
--- a/gerrit-reviewdb/.gitignore
+++ b/gerrit-reviewdb/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-reviewdb.iml
\ No newline at end of file
diff --git a/gerrit-reviewdb/pom.xml b/gerrit-reviewdb/pom.xml
index 24d6a1b..f9fb49e 100644
--- a/gerrit-reviewdb/pom.xml
+++ b/gerrit-reviewdb/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-reviewdb</artifactId>
diff --git a/gerrit-server/.gitignore b/gerrit-server/.gitignore
index 194bedc..9324efe 100644
--- a/gerrit-server/.gitignore
+++ b/gerrit-server/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-server.iml
\ No newline at end of file
diff --git a/gerrit-server/pom.xml b/gerrit-server/pom.xml
index 58e43cf..f35608c 100644
--- a/gerrit-server/pom.xml
+++ b/gerrit-server/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-server</artifactId>
diff --git a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
index 8ab9471..b9f476b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/rules/StoredValues.java
@@ -16,12 +16,16 @@
 
 import static com.google.gerrit.rules.StoredValue.create;
 
+import com.google.common.collect.Maps;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
@@ -29,6 +33,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.query.change.ChangeData;
 
 import com.googlecode.prolog_cafe.lang.Prolog;
 import com.googlecode.prolog_cafe.lang.SystemException;
@@ -37,21 +42,25 @@
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
 
+import java.util.Map;
+
 public final class StoredValues {
   public static final StoredValue<ReviewDb> REVIEW_DB = create(ReviewDb.class);
   public static final StoredValue<Change> CHANGE = create(Change.class);
-  public static final StoredValue<PatchSet.Id> PATCH_SET_ID = create(PatchSet.Id.class);
+  public static final StoredValue<ChangeData> CHANGE_DATA = create(ChangeData.class);
+  public static final StoredValue<PatchSet> PATCH_SET = create(PatchSet.class);
   public static final StoredValue<ChangeControl> CHANGE_CONTROL = create(ChangeControl.class);
 
   public static final StoredValue<PatchSetInfo> PATCH_SET_INFO = new StoredValue<PatchSetInfo>() {
     @Override
     public PatchSetInfo createValue(Prolog engine) {
-      PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
+      Change change = StoredValues.CHANGE.get(engine);
+      PatchSet ps = StoredValues.PATCH_SET.get(engine);
       PrologEnvironment env = (PrologEnvironment) engine.control;
       PatchSetInfoFactory patchInfoFactory =
           env.getInjector().getInstance(PatchSetInfoFactory.class);
       try {
-        return patchInfoFactory.get(REVIEW_DB.get(engine), patchSetId);
+        return patchInfoFactory.get(change, ps);
       } catch (PatchSetInfoNotAvailableException e) {
         throw new SystemException(e.getMessage());
       }
@@ -102,6 +111,23 @@
     }
   };
 
+  public static final StoredValue<AnonymousUser> ANONYMOUS_USER =
+      new StoredValue<AnonymousUser>() {
+        @Override
+        protected AnonymousUser createValue(Prolog engine) {
+          PrologEnvironment env = (PrologEnvironment) engine.control;
+          return env.getInjector().getInstance(AnonymousUser.class);
+        }
+      };
+
+  public static final StoredValue<Map<Account.Id, IdentifiedUser>> USERS =
+      new StoredValue<Map<Account.Id, IdentifiedUser>>() {
+        @Override
+        protected Map<Account.Id, IdentifiedUser> createValue(Prolog engine) {
+          return Maps.newHashMap();
+        }
+      };
+
   private StoredValues() {
   }
 }
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
index 6c216a8..e469c34 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/AccountManager.java
@@ -426,6 +426,56 @@
     }
   }
 
+  /**
+   * Unlink an authentication identity from an existing account.
+   *
+   * @param from account to unlink the identity from.
+   * @param who the identity to delete
+   * @return the result of unlinking the identity from the user.
+   * @throws AccountException the identity belongs to a different account, or it
+   *         cannot be unlinked at this time.
+   */
+  public AuthResult unlink(final Account.Id from, AuthRequest who)
+      throws AccountException {
+    try {
+      final ReviewDb db = schema.open();
+      try {
+        who = realm.unlink(db, from, who);
+
+        final AccountExternalId.Key key = id(who);
+        AccountExternalId extId = db.accountExternalIds().get(key);
+        if (extId != null) {
+          if (!extId.getAccountId().equals(from)) {
+            throw new AccountException("Identity in use by another account");
+          }
+          db.accountExternalIds().delete(Collections.singleton(extId));
+
+          if (who.getEmailAddress() != null) {
+            final Account a = db.accounts().get(from);
+            if (a.getPreferredEmail() != null
+                && a.getPreferredEmail().equals(who.getEmailAddress())) {
+              a.setPreferredEmail(null);
+              db.accounts().update(Collections.singleton(a));
+            }
+            byEmailCache.evict(who.getEmailAddress());
+            byIdCache.evict(from);
+          }
+
+        } else {
+          throw new AccountException("Identity not found");
+        }
+
+        return new AuthResult(from, key, false);
+
+      } finally {
+        db.close();
+      }
+    } catch (OrmException e) {
+      throw new AccountException("Cannot unlink identity", e);
+    }
+  }
+
+
   private static AccountExternalId.Key id(final AuthRequest who) {
     return new AccountExternalId.Key(who.getExternalId());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
index 4f3392c..844e604 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DefaultRealm.java
@@ -56,6 +56,11 @@
   }
 
   @Override
+  public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
+    return who;
+  }
+
+  @Override
   public void onCreateAccount(final AuthRequest who, final Account account) {
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
index fc7c0be..2ebd0e5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/Realm.java
@@ -29,6 +29,9 @@
   public AuthRequest link(ReviewDb db, Account.Id to, AuthRequest who)
       throws AccountException;
 
+  public AuthRequest unlink(ReviewDb db, Account.Id to, AuthRequest who)
+      throws AccountException;
+
   public void onCreateAccount(AuthRequest who, Account account);
 
   public GroupMembership groups(AccountState who);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
index e085d1e..910bf06 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapRealm.java
@@ -255,6 +255,11 @@
   }
 
   @Override
+  public AuthRequest unlink(ReviewDb db, Account.Id from, AuthRequest who) {
+    return who;
+  }
+
+  @Override
   public void onCreateAccount(final AuthRequest who, final Account account) {
     usernameCache.put(who.getLocalUser(), account.getId());
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
index abd3582..6648c7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/Submit.java
@@ -80,7 +80,7 @@
       throw new NoSuchChangeException(changeId);
     }
 
-    List<SubmitRecord> submitResult = control.canSubmit(db, patchSetId);
+    List<SubmitRecord> submitResult = control.canSubmit(db, patch);
     if (submitResult.isEmpty()) {
       throw new IllegalStateException(
           "ChangeControl.canSubmit returned empty list");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index e76249a..6068c50 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -16,27 +16,10 @@
 
 import static org.eclipse.jgit.util.StringUtils.equalsIgnoreCase;
 
-import com.google.common.base.Function;
-import com.google.common.base.Predicates;
-import com.google.common.collect.Iterables;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.AccountGroupName;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.OrmRuntimeException;
-import com.google.gwtorm.server.SchemaFactory;
-
 import org.eclipse.jgit.lib.Config;
-import org.slf4j.Logger;
-
 import java.lang.reflect.InvocationTargetException;
-import java.text.MessageFormat;
 import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Set;
 import java.util.concurrent.TimeUnit;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -303,83 +286,6 @@
     }
   }
 
-  /**
-   * Resolve groups from group names, via the database. Group names not found in
-   * the database will be skipped.
-   *
-   * @param dbfactory database to resolve from.
-   * @param groupNames group names to resolve.
-   * @param log log for any warnings and errors.
-   * @param groupNotFoundWarning formatted message to output to the log for each
-   *        group name which is not found in the database. <code>{0}</code> will
-   *        be replaced with the group name.
-   * @return the actual groups resolved from the database. If no groups are
-   *         found, returns an empty {@code Set}, never {@code null}.
-   */
-  public static Set<AccountGroup.UUID> groupsFor(
-      SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log,
-      String groupNotFoundWarning) {
-    final Set<AccountGroup.UUID> result = new HashSet<AccountGroup.UUID>();
-    try {
-      final ReviewDb db = dbfactory.open();
-      try {
-        List<AccountGroupName> groups = db.accountGroupNames().get(
-            Iterables.transform(Arrays.asList(groupNames),
-                new Function<String, AccountGroup.NameKey>() {
-                  @Override
-                  public AccountGroup.NameKey apply(String name) {
-                    return new AccountGroup.NameKey(name);
-                  }
-            })).toList();
-
-        Iterator<AccountGroup> ags = db.accountGroups().get(
-            Iterables.transform(Iterables.filter(groups, Predicates.notNull()),
-                new Function<AccountGroupName, AccountGroup.Id>() {
-                  @Override
-                  public AccountGroup.Id apply(AccountGroupName group) {
-                    return group.getId();
-                  }
-            })).iterator();
-
-        for (int i = 0; i < groupNames.length; i++) {
-          if (groups.get(i) == null) {
-            log.warn(MessageFormat.format(groupNotFoundWarning, groupNames[i]));
-            continue;
-          }
-          AccountGroup ag = ags.next();
-          if (ag == null) {
-            log.warn(MessageFormat.format(groupNotFoundWarning, groupNames[i]));
-          } else {
-            result.add(ag.getGroupUUID());
-          }
-        }
-      } finally {
-        db.close();
-      }
-    } catch (OrmRuntimeException e) {
-      log.error("Database error, cannot load groups", e);
-    } catch (OrmException e) {
-      log.error("Database error, cannot load groups", e);
-    }
-    return result;
-  }
-
-  /**
-   * Resolve groups from group names, via the database. Group names not found in
-   * the database will be skipped.
-   *
-   * @param dbfactory database to resolve from.
-   * @param groupNames group names to resolve.
-   * @param log log for any warnings and errors.
-   * @return the actual groups resolved from the database. If no groups are
-   *         found, returns an empty {@code Set}, never {@code null}.
-   */
-  public static Set<AccountGroup.UUID> groupsFor(
-      SchemaFactory<ReviewDb> dbfactory, String[] groupNames, Logger log) {
-    return groupsFor(dbfactory, groupNames, log,
-        "Group \"{0}\" not in database, skipping.");
-  }
-
   private static boolean match(final String a, final String... cases) {
     for (final String b : cases) {
       if (equalsIgnoreCase(a, b)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
index 944bbeb..f581adc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritRequestModule.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.changedetail.RestoreChange;
 import com.google.gerrit.server.changedetail.Submit;
 import com.google.gerrit.server.git.AsyncReceiveCommits;
+import com.google.gerrit.server.git.BanCommit;
 import com.google.gerrit.server.git.CreateCodeReviewNotes;
 import com.google.gerrit.server.git.MergeOp;
 import com.google.gerrit.server.git.MetaDataUpdate;
@@ -117,5 +118,6 @@
     factory(CreateProject.Factory.class);
     factory(Submit.Factory.class);
     factory(SuggestParentCandidates.Factory.class);
+    factory(BanCommit.Factory.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
index 9992f18..c89f025 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitReceivePackGroupsProvider.java
@@ -15,8 +15,7 @@
 package com.google.gerrit.server.config;
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -25,9 +24,9 @@
 
 public class GitReceivePackGroupsProvider extends GroupSetProvider {
   @Inject
-  public GitReceivePackGroupsProvider(@GerritServerConfig Config config,
-      SchemaFactory<ReviewDb> db) {
-    super(config, db, "receive", null, "allowGroup");
+  public GitReceivePackGroupsProvider(GroupCache gc,
+      @GerritServerConfig Config config) {
+    super(gc, config, "receive", null, "allowGroup");
 
     // If no group was set, default to "registered users"
     //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
index 76d8844..b5de742 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GitUploadPackGroupsProvider.java
@@ -15,8 +15,7 @@
 package com.google.gerrit.server.config;
 
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -26,9 +25,9 @@
 
 public class GitUploadPackGroupsProvider extends GroupSetProvider {
   @Inject
-  public GitUploadPackGroupsProvider(@GerritServerConfig Config config,
-      SchemaFactory<ReviewDb> db) {
-    super(config, db, "upload", null, "allowGroup");
+  public GitUploadPackGroupsProvider(GroupCache gc,
+      @GerritServerConfig Config config) {
+    super(gc, config, "upload", null, "allowGroup");
 
     // If no group was set, default to "registered users" and "anonymous"
     //
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
index 15711af..3619cda 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GroupSetProvider.java
@@ -14,12 +14,9 @@
 
 package com.google.gerrit.server.config;
 
-import static com.google.gerrit.server.config.ConfigUtil.groupsFor;
-import static java.util.Collections.unmodifiableSet;
-
+import com.google.common.collect.ImmutableSet;
 import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -37,10 +34,20 @@
   protected Set<AccountGroup.UUID> groupIds;
 
   @Inject
-  protected GroupSetProvider(@GerritServerConfig Config config,
-      SchemaFactory<ReviewDb> db, String section, String subsection, String name) {
+  protected GroupSetProvider(GroupCache groupCache,
+      @GerritServerConfig Config config, String section,
+      String subsection, String name) {
     String[] groupNames = config.getStringList(section, subsection, name);
-    groupIds = unmodifiableSet(groupsFor(db, groupNames, log));
+    ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
+    for (String n : groupNames) {
+      AccountGroup g = groupCache.get(new AccountGroup.NameKey(n));
+      if (g != null) {
+        builder.add(g.getGroupUUID());
+      } else {
+        log.warn("Group \"{0}\" not in database, skipping.", n);
+      }
+    }
+    groupIds = builder.build();
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
index b279086..7172b6f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ProjectOwnerGroupsProvider.java
@@ -14,8 +14,7 @@
 
 package com.google.gerrit.server.config;
 
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gwtorm.server.SchemaFactory;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.lib.Config;
@@ -33,8 +32,8 @@
  */
 public class ProjectOwnerGroupsProvider extends GroupSetProvider {
   @Inject
-  public ProjectOwnerGroupsProvider(
-      @GerritServerConfig final Config config, final SchemaFactory<ReviewDb> db) {
-    super(config, db, "repository", "*", "ownerGroup");
+  public ProjectOwnerGroupsProvider(GroupCache gc,
+      @GerritServerConfig final Config config) {
+    super(gc, config, "repository", "*", "ownerGroup");
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
new file mode 100644
index 0000000..c9c9753
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommit.java
@@ -0,0 +1,273 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import static com.google.gerrit.server.git.GitRepositoryManager.REF_REJECT_COMMITS;
+
+import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.GerritPersonIdent;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.CommitBuilder;
+import org.eclipse.jgit.lib.Constants;
+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.RefUpdate;
+import org.eclipse.jgit.lib.RefUpdate.Result;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.notes.Note;
+import org.eclipse.jgit.notes.NoteMap;
+import org.eclipse.jgit.notes.NoteMapMerger;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+
+import java.io.IOException;
+import java.util.List;
+
+public class BanCommit {
+
+  private static final int MAX_LOCK_FAILURE_CALLS = 10;
+  private static final int SLEEP_ON_LOCK_FAILURE_MS = 25;
+
+  public interface Factory {
+    BanCommit create();
+  }
+
+  private final Provider<CurrentUser> currentUser;
+  private final GitRepositoryManager repoManager;
+  private final AccountCache accountCache;
+  private final PersonIdent gerritIdent;
+
+  @Inject
+  BanCommit(final Provider<CurrentUser> currentUser,
+      final GitRepositoryManager repoManager, final AccountCache accountCache,
+      @GerritPersonIdent final PersonIdent gerritIdent) {
+    this.currentUser = currentUser;
+    this.repoManager = repoManager;
+    this.accountCache = accountCache;
+    this.gerritIdent = gerritIdent;
+  }
+
+  public BanCommitResult ban(final ProjectControl projectControl,
+      final List<ObjectId> commitsToBan, final String reason)
+      throws PermissionDeniedException, IOException,
+      IncompleteUserInfoException, InterruptedException, MergeException {
+    if (!projectControl.isOwner()) {
+      throw new PermissionDeniedException(
+          "No project owner: not permitted to ban commits");
+    }
+
+    final BanCommitResult result = new BanCommitResult();
+
+    final PersonIdent currentUserIdent = createPersonIdent();
+    final Repository repo =
+        repoManager.openRepository(projectControl.getProject().getNameKey());
+    try {
+      final RevWalk revWalk = new RevWalk(repo);
+      final ObjectInserter inserter = repo.newObjectInserter();
+      try {
+        NoteMap baseNoteMap = null;
+        RevCommit baseCommit = null;
+        final Ref notesBranch = repo.getRef(REF_REJECT_COMMITS);
+        if (notesBranch != null) {
+          baseCommit = revWalk.parseCommit(notesBranch.getObjectId());
+          baseNoteMap = NoteMap.read(revWalk.getObjectReader(), baseCommit);
+        }
+
+        final NoteMap ourNoteMap;
+        if (baseCommit != null) {
+          ourNoteMap = NoteMap.read(repo.newObjectReader(), baseCommit);
+        } else {
+          ourNoteMap = NoteMap.newEmptyMap();
+        }
+
+        for (final ObjectId commitToBan : commitsToBan) {
+          try {
+            revWalk.parseCommit(commitToBan);
+          } catch (MissingObjectException e) {
+            // ignore exception, also not existing commits can be banned
+          } catch (IncorrectObjectTypeException e) {
+            result.notACommit(commitToBan, e.getMessage());
+            continue;
+          }
+
+          final Note note = ourNoteMap.getNote(commitToBan);
+          if (note != null) {
+            result.commitAlreadyBanned(commitToBan);
+            continue;
+          }
+
+          final String noteContent = reason != null ? reason : "";
+          final ObjectId noteContentId =
+              inserter
+                  .insert(Constants.OBJ_BLOB, noteContent.getBytes("UTF-8"));
+          ourNoteMap.set(commitToBan, noteContentId);
+          result.commitBanned(commitToBan);
+        }
+
+        if (result.getNewlyBannedCommits().isEmpty()) {
+          return result;
+        }
+
+        final ObjectId ourCommit =
+            commit(ourNoteMap, inserter, currentUserIdent, baseCommit, result,
+                reason);
+
+        updateRef(repo, revWalk, inserter, ourNoteMap, ourCommit, baseNoteMap,
+            baseCommit);
+      } finally {
+        revWalk.release();
+        inserter.release();
+      }
+    } finally {
+      repo.close();
+    }
+
+    return result;
+  }
+
+  private PersonIdent createPersonIdent() throws IncompleteUserInfoException {
+    final String userName = currentUser.get().getUserName();
+    final Account account = accountCache.getByUsername(userName).getAccount();
+    if (account.getFullName() == null) {
+      throw new IncompleteUserInfoException(userName, "full name");
+    }
+    if (account.getPreferredEmail() == null) {
+      throw new IncompleteUserInfoException(userName, "preferred email");
+    }
+    return new PersonIdent(account.getFullName(), account.getPreferredEmail());
+  }
+
+  private static ObjectId commit(final NoteMap noteMap,
+      final ObjectInserter inserter, final PersonIdent personIdent,
+      final ObjectId baseCommit, final BanCommitResult result,
+      final String reason) throws IOException {
+    final String commitMsg =
+        buildCommitMessage(result.getNewlyBannedCommits(), reason);
+    if (baseCommit != null) {
+      return createCommit(noteMap, inserter, personIdent, commitMsg, baseCommit);
+    } else {
+      return createCommit(noteMap, inserter, personIdent, commitMsg);
+    }
+  }
+
+  private static ObjectId createCommit(final NoteMap noteMap,
+      final ObjectInserter inserter, final PersonIdent personIdent,
+      final String message, final ObjectId... parents) throws IOException {
+    final CommitBuilder b = new CommitBuilder();
+    b.setTreeId(noteMap.writeTree(inserter));
+    b.setAuthor(personIdent);
+    b.setCommitter(personIdent);
+    if (parents.length > 0) {
+      b.setParentIds(parents);
+    }
+    b.setMessage(message);
+    final ObjectId commitId = inserter.insert(b);
+    inserter.flush();
+    return commitId;
+  }
+
+  private static String buildCommitMessage(final List<ObjectId> bannedCommits,
+      final String reason) {
+    final StringBuilder commitMsg = new StringBuilder();
+    commitMsg.append("Banning ");
+    commitMsg.append(bannedCommits.size());
+    commitMsg.append(" ");
+    commitMsg.append(bannedCommits.size() == 1 ? "commit" : "commits");
+    commitMsg.append("\n\n");
+    if (reason != null) {
+      commitMsg.append("Reason: ");
+      commitMsg.append(reason);
+      commitMsg.append("\n\n");
+    }
+    commitMsg.append("The following commits are banned:\n");
+    final StringBuilder commitList = new StringBuilder();
+    for (final ObjectId c : bannedCommits) {
+      if (commitList.length() > 0) {
+        commitList.append(",\n");
+      }
+      commitList.append(c.getName());
+    }
+    commitMsg.append(commitList);
+    return commitMsg.toString();
+  }
+
+  public void updateRef(final Repository repo, final RevWalk revWalk,
+      final ObjectInserter inserter, final NoteMap ourNoteMap,
+      final ObjectId oursCommit, final NoteMap baseNoteMap,
+      final ObjectId baseCommit) throws IOException, InterruptedException,
+      MissingObjectException, IncorrectObjectTypeException,
+      CorruptObjectException, MergeException {
+
+    int remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+    RefUpdate refUpdate = createRefUpdate(repo, oursCommit, baseCommit);
+
+    for (;;) {
+      final Result result = refUpdate.update();
+
+      if (result == Result.LOCK_FAILURE) {
+        if (--remainingLockFailureCalls > 0) {
+          Thread.sleep(SLEEP_ON_LOCK_FAILURE_MS);
+        } else {
+          throw new MergeException("Failed to lock the ref: "
+              + REF_REJECT_COMMITS);
+        }
+
+      } else if (result == Result.REJECTED) {
+        final RevCommit theirsCommit =
+            revWalk.parseCommit(refUpdate.getOldObjectId());
+        final NoteMap theirNoteMap =
+            NoteMap.read(revWalk.getObjectReader(), theirsCommit);
+        final NoteMapMerger merger = new NoteMapMerger(repo);
+        final NoteMap merged =
+            merger.merge(baseNoteMap, ourNoteMap, theirNoteMap);
+        final ObjectId mergeCommit =
+            createCommit(merged, inserter, gerritIdent,
+                "Merged note commits\n", oursCommit, theirsCommit);
+        refUpdate = createRefUpdate(repo, mergeCommit, theirsCommit);
+        remainingLockFailureCalls = MAX_LOCK_FAILURE_CALLS;
+
+      } else if (result == Result.IO_FAILURE) {
+        throw new IOException(
+            "Couldn't create commit reject notes because of IO_FAILURE");
+      } else {
+        break;
+      }
+    }
+  }
+
+  private static RefUpdate createRefUpdate(final Repository repo,
+      final ObjectId newObjectId, final ObjectId expectedOldObjectId)
+      throws IOException {
+    RefUpdate refUpdate = repo.updateRef(REF_REJECT_COMMITS);
+    refUpdate.setNewObjectId(newObjectId);
+    if (expectedOldObjectId == null) {
+      refUpdate.setExpectedOldObjectId(ObjectId.zeroId());
+    } else {
+      refUpdate.setExpectedOldObjectId(expectedOldObjectId);
+    }
+    return refUpdate;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
new file mode 100644
index 0000000..1b48455
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BanCommitResult.java
@@ -0,0 +1,54 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+import org.eclipse.jgit.lib.ObjectId;
+
+import java.util.LinkedList;
+import java.util.List;
+
+public class BanCommitResult {
+
+  private final List<ObjectId> newlyBannedCommits = new LinkedList<ObjectId>();
+  private final List<ObjectId> alreadyBannedCommits = new LinkedList<ObjectId>();
+  private final List<ObjectId> ignoredObjectIds = new LinkedList<ObjectId>();
+
+  public BanCommitResult() {
+  }
+
+  public void commitBanned(final ObjectId commitId) {
+    newlyBannedCommits.add(commitId);
+  }
+
+  public void commitAlreadyBanned(final ObjectId commitId) {
+    alreadyBannedCommits.add(commitId);
+  }
+
+  public void notACommit(final ObjectId id, final String message) {
+    ignoredObjectIds.add(id);
+  }
+
+  public List<ObjectId> getNewlyBannedCommits() {
+    return newlyBannedCommits;
+  }
+
+  public List<ObjectId> getAlreadyBannedCommits() {
+    return alreadyBannedCommits;
+  }
+
+  public List<ObjectId> getIgnoredObjectIds() {
+    return ignoredObjectIds;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
new file mode 100644
index 0000000..204d777
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/IncompleteUserInfoException.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.server.git;
+
+public class IncompleteUserInfoException extends Exception {
+  private static final long serialVersionUID = 1L;
+
+  public IncompleteUserInfoException(final String userName, final String missingInfo) {
+    super("For the user \"" + userName + "\" " + missingInfo + " is not set.");
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
index 44becb5..1997c13 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeException.java
@@ -15,7 +15,7 @@
 package com.google.gerrit.server.git;
 
 /** Indicates the current branch's queue cannot be processed at this time. */
-class MergeException extends Exception {
+public class MergeException extends Exception {
   private static final long serialVersionUID = 1L;
 
   MergeException(final String msg) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
index 5c6cd25..0868749 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushOp.java
@@ -343,10 +343,6 @@
 
       if (config.isMirror()) {
         for (final Ref ref : remote.values()) {
-          if (noPerms && GitRepositoryManager.REF_CONFIG.equals(ref.getName())) {
-            continue;
-          }
-
           if (!Constants.HEAD.equals(ref.getName())) {
             final RefSpec spec = matchDst(ref.getName());
             if (spec != null && !local.containsKey(spec.getSource())) {
@@ -360,16 +356,13 @@
 
     } else {
       for (final String src : delta) {
-        if (noPerms && GitRepositoryManager.REF_CONFIG.equals(src)) {
-          continue;
-        }
-
         final RefSpec spec = matchSrc(src);
         if (spec != null) {
           // If the ref still exists locally, send it, otherwise delete it.
           //
           Ref srcRef = local.get(src);
-          if (srcRef != null) {
+          if (srcRef != null &&
+              !(noPerms && GitRepositoryManager.REF_CONFIG.equals(src))) {
             send(cmds, spec, srcRef);
           } else if (config.isMirror()) {
             delete(cmds, spec);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
index 5cf5f7a..5bff0ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/PushReplication.java
@@ -14,13 +14,15 @@
 
 package com.google.gerrit.server.git;
 
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.ReplicationUser;
+import com.google.gerrit.server.account.GroupCache;
 import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.account.ListGroupMembership;
-import com.google.gerrit.server.config.ConfigUtil;
 import com.google.gerrit.server.config.FactoryModule;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.project.NoSuchProjectException;
@@ -99,11 +101,12 @@
   private final SchemaFactory<ReviewDb> database;
   private final ReplicationUser.Factory replicationUserFactory;
   private final GitRepositoryManager gitRepositoryManager;
+  private final GroupCache groupCache;
 
   @Inject
   PushReplication(final Injector i, final WorkQueue wq, final SitePaths site,
       final ReplicationUser.Factory ruf, final SchemaFactory<ReviewDb> db,
-      final GitRepositoryManager grm)
+      final GitRepositoryManager grm, GroupCache gc)
       throws ConfigInvalidException, IOException {
     injector = i;
     workQueue = wq;
@@ -111,6 +114,7 @@
     replicationUserFactory = ruf;
     gitRepositoryManager = grm;
     configs = allConfigs(site);
+    groupCache = gc;
   }
 
   @Override
@@ -195,7 +199,6 @@
         }
       }
 
-
       if (c.getPushRefSpecs().isEmpty()) {
         RefSpec spec = new RefSpec();
         spec = spec.setSourceDestination("refs/*", "refs/*");
@@ -204,7 +207,7 @@
       }
 
       r.add(new ReplicationConfig(injector, workQueue, c, cfg, database,
-          replicationUserFactory, gitRepositoryManager));
+          replicationUserFactory, gitRepositoryManager, groupCache));
     }
     return Collections.unmodifiableList(r);
   }
@@ -392,7 +395,8 @@
     ReplicationConfig(final Injector injector, final WorkQueue workQueue,
         final RemoteConfig rc, final Config cfg, SchemaFactory<ReviewDb> db,
         final ReplicationUser.Factory replicationUserFactory,
-        final GitRepositoryManager gitRepositoryManager) {
+        final GitRepositoryManager gitRepositoryManager,
+        GroupCache groupCache) {
 
       remote = rc;
       delay = Math.max(0, getInt(rc, cfg, "replicationdelay", 15));
@@ -406,8 +410,16 @@
           cfg.getStringList("remote", rc.getName(), "authGroup");
       final GroupMembership authGroups;
       if (authGroupNames.length > 0) {
-        authGroups = new ListGroupMembership(ConfigUtil.groupsFor(db, authGroupNames, //
-            log, "Group \"{0}\" not in database, removing from authGroup"));
+        ImmutableSet.Builder<AccountGroup.UUID> builder = ImmutableSet.builder();
+        for (String name : authGroupNames) {
+          AccountGroup g = groupCache.get(new AccountGroup.NameKey(name));
+          if (g != null) {
+            builder.add(g.getGroupUUID());
+          } else {
+            log.warn("Group \"{0}\" not in database, removing from authGroup", name);
+          }
+        }
+        authGroups = new ListGroupMembership(builder.build());
       } else {
         authGroups = ReplicationUser.EVERYTHING_VISIBLE;
       }
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 290b162..83877d0 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
@@ -16,6 +16,8 @@
 
 import static com.google.gerrit.server.git.MultiProgressMonitor.UNKNOWN;
 
+import com.google.common.collect.LinkedListMultimap;
+import com.google.common.collect.ListMultimap;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.ApprovalType;
@@ -112,6 +114,34 @@
   private static final FooterKey TESTED_BY = new FooterKey("Tested-by");
   private static final FooterKey CHANGE_ID = new FooterKey("Change-Id");
 
+  private static final String COMMAND_REJECTION_MESSAGE_FOOTER =
+      "Please read the documentation and contact an administrator\n"
+          + "if you feel the configuration is incorrect";
+
+  private enum Error {
+        CONFIG_UPDATE("You are not allowed to perform this operation.\n"
+        + "Configuration changes can only be pushed by project owners\n"
+        + "who also have 'Push' rights on " + GitRepositoryManager.REF_CONFIG),
+        UPDATE("You are not allowed to perform this operation.\n"
+        + "To push into this reference you need 'Push' rights."),
+        DELETE("You need 'Push' rights with the 'Force Push'\n"
+            + "flag set to delete references."),
+        CODE_REVIEW("You need 'Push' rights to upload code review requests.\n"
+            + "Verify that you are pushing to the right branch."),
+        CREATE("You are not allowed to perform this operation.\n"
+            + "To create new references you need 'Create Reference' rights.");
+
+    private final String value;
+
+    Error(String value) {
+      this.value = value;
+    }
+
+    public String get() {
+      return value;
+    }
+  }
+
   interface Factory {
     ReceiveCommits create(ProjectControl projectControl, Repository repository);
   }
@@ -215,6 +245,7 @@
   private final SubmoduleOp.Factory subOpFactory;
 
   private final List<Message> messages = new ArrayList<Message>();
+  private ListMultimap<Error, String> errors = LinkedListMultimap.create();
   private Task newProgress;
   private Task replaceProgress;
   private Task closeProgress;
@@ -438,6 +469,14 @@
     doReplaces();
     replaceProgress.end();
 
+    if (!errors.isEmpty()) {
+      for (Error error : errors.keySet()) {
+        rp.sendMessage(buildError(error, errors.get(error)));
+      }
+      rp.sendMessage(String.format("User: %s", displayName(currentUser)));
+      rp.sendMessage(COMMAND_REJECTION_MESSAGE_FOOTER);
+    }
+
     for (final ReceiveCommand c : commands) {
       if (c.getResult() == Result.OK) {
         switch (c.getType()) {
@@ -502,6 +541,30 @@
     }
   }
 
+  private String buildError(Error error, List<String> branches) {
+    StringBuilder sb = new StringBuilder();
+    if (branches.size() == 1) {
+      sb.append("Branch ").append(branches.get(0)).append(":\n");
+      sb.append(error.get());
+      return sb.toString();
+    }
+    sb.append("Branches");
+    String delim = " ";
+    for (String branch : branches) {
+      sb.append(delim).append(branch);
+      delim = ", ";
+    }
+    return sb.append(":\n").append(error.get()).toString();
+  }
+
+  private static String displayName(IdentifiedUser user) {
+    String displayName = user.getUserName();
+    if (displayName == null) {
+      displayName = user.getAccount().getPreferredEmail();
+    }
+    return displayName;
+  }
+
   private Account.Id toAccountId(final String nameOrEmail) throws OrmException,
       NoSuchAccountException {
     final Account a = accountResolver.findByNameOrEmail(nameOrEmail);
@@ -630,6 +693,7 @@
       validateNewCommits(ctl, cmd);
       cmd.execute(rp);
     } else {
+      errors.put(Error.CREATE, ctl.getRefName());
       reject(cmd, "can not create new references");
     }
   }
@@ -644,6 +708,11 @@
       validateNewCommits(ctl, cmd);
       cmd.execute(rp);
     } else {
+      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+        errors.put(Error.CONFIG_UPDATE, GitRepositoryManager.REF_CONFIG);
+      } else {
+        errors.put(Error.UPDATE, ctl.getRefName());
+      }
       reject(cmd, "can not update the reference as a fast forward");
     }
   }
@@ -672,7 +741,12 @@
     if (ctl.canDelete()) {
       cmd.execute(rp);
     } else {
-      reject(cmd, "can not delete references");
+      if (GitRepositoryManager.REF_CONFIG.equals(ctl.getRefName())) {
+        reject(cmd, "cannot delete project configuration");
+      } else {
+        errors.put(Error.DELETE, ctl.getRefName());
+        reject(cmd, "can not delete references");
+      }
     }
   }
 
@@ -769,7 +843,8 @@
         destBranchName.substring(0, split));
     destBranchCtl = projectControl.controlForRef(destBranch);
     if (!destBranchCtl.canUpload()) {
-      reject(cmd, "can not upload a change to this reference");
+      errors.put(Error.CODE_REVIEW, cmd.getRefName());
+      reject(cmd, "can not upload review");
       return;
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
index f59dcc3..2619e00 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchSetInfoFactory.java
@@ -18,7 +18,6 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetInfo;
-import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.reviewdb.server.ReviewDb;
@@ -29,6 +28,7 @@
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.Repository;
@@ -68,31 +68,39 @@
   }
 
   public PatchSetInfo get(ReviewDb db, PatchSet.Id patchSetId)
-    throws PatchSetInfoNotAvailableException {
-    Repository repo = null;
+      throws PatchSetInfoNotAvailableException {
     try {
       final PatchSet patchSet = db.patchSets().get(patchSetId);
       final Change change = db.changes().get(patchSet.getId().getParentKey());
-      final Project.NameKey projectKey = change.getProject();
-      repo = repoManager.openRepository(projectKey);
+      return get(change, patchSet);
+    } catch (OrmException e) {
+      throw new PatchSetInfoNotAvailableException(e);
+    }
+  }
+
+  public PatchSetInfo get(Change change, PatchSet patchSet)
+      throws PatchSetInfoNotAvailableException {
+    Repository repo;
+    try {
+      repo = repoManager.openRepository(change.getProject());
+    } catch (RepositoryNotFoundException e) {
+      throw new PatchSetInfoNotAvailableException(e);
+    }
+    try {
       final RevWalk rw = new RevWalk(repo);
       try {
         final RevCommit src =
             rw.parseCommit(ObjectId.fromString(patchSet.getRevision().get()));
-        PatchSetInfo info = get(src, patchSetId);
+        PatchSetInfo info = get(src, patchSet.getId());
         info.setParents(toParentInfos(src.getParents(), rw));
         return info;
       } finally {
         rw.release();
       }
-    } catch (OrmException e) {
-      throw new PatchSetInfoNotAvailableException(e);
     } catch (IOException e) {
       throw new PatchSetInfoNotAvailableException(e);
     } finally {
-      if (repo != null) {
-        repo.close();
-      }
+      repo.close();
     }
   }
 
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 53301b9..7652bed 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
@@ -26,10 +26,11 @@
 import com.google.gerrit.rules.StoredValues;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
+import com.google.inject.util.Providers;
 
 import com.googlecode.prolog_cafe.compiler.CompileException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
@@ -49,6 +50,8 @@
 import java.util.List;
 import java.util.Set;
 
+import javax.annotation.Nullable;
+
 
 /** Access control management for a user accessing a single change. */
 public class ChangeControl {
@@ -161,7 +164,7 @@
 
   /** Can this user see this change? */
   public boolean isVisible(ReviewDb db) throws OrmException {
-    if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db)) {
+    if (change.getStatus() == Change.Status.DRAFT && !isDraftVisible(db, null)) {
       return false;
     }
     return isRefVisible();
@@ -174,7 +177,7 @@
 
   /** Can this user see the given patchset? */
   public boolean isPatchVisible(PatchSet ps, ReviewDb db) throws OrmException {
-    if (ps.isDraft() && !isDraftVisible(db)) {
+    if (ps.isDraft() && !isDraftVisible(db, null)) {
       return false;
     }
     return isVisible(db);
@@ -235,10 +238,20 @@
 
   /** Is this user a reviewer for the change? */
   public boolean isReviewer(ReviewDb db) throws OrmException {
+    return isReviewer(db, null);
+  }
+
+  /** Is this user a reviewer for the change? */
+  public boolean isReviewer(ReviewDb db, @Nullable ChangeData cd)
+      throws OrmException {
     if (getCurrentUser() instanceof IdentifiedUser) {
       final IdentifiedUser user = (IdentifiedUser) getCurrentUser();
-      ResultSet<PatchSetApproval> results =
-        db.patchSetApprovals().byChange(change.getId());
+      Iterable<PatchSetApproval> results;
+      if (cd != null) {
+        results = cd.currentApprovals(Providers.of(db));
+      } else {
+        results = db.patchSetApprovals().byChange(change.getId());
+      }
       for (PatchSetApproval approval : results) {
         if (user.getAccountId().equals(approval.getAccountId())) {
           return true;
@@ -278,34 +291,39 @@
     return false;
   }
 
-  public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet.Id patchSetId) {
+  public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet) {
+    return canSubmit(db, patchSet, null, false);
+  }
+
+  public List<SubmitRecord> canSubmit(ReviewDb db, PatchSet patchSet,
+      @Nullable ChangeData cd, boolean fastEvalLabels) {
     if (change.getStatus().isClosed()) {
       SubmitRecord rec = new SubmitRecord();
       rec.status = SubmitRecord.Status.CLOSED;
       return Collections.singletonList(rec);
     }
 
-    if (!patchSetId.equals(change.currentPatchSetId())) {
-      return ruleError("Patch set " + patchSetId + " is not current");
+    if (!patchSet.getId().equals(change.currentPatchSetId())) {
+      return ruleError("Patch set " + patchSet.getPatchSetId() + " is not current");
     }
 
     try {
-      if (change.getStatus() == Change.Status.DRAFT){
-        if (!isVisible(db)) {
-          return ruleError("Patch set " + patchSetId + " not found");
+      if (change.getStatus() == Change.Status.DRAFT) {
+        if (!isDraftVisible(db, cd)) {
+          return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
         } else {
           return ruleError("Cannot submit draft changes");
         }
       }
-      if (isDraftPatchSet(patchSetId, db)) {
-        if (!isVisible(db)) {
-          return ruleError("Patch set " + patchSetId + " not found");
+      if (patchSet.isDraft()) {
+        if (!isDraftVisible(db, cd)) {
+          return ruleError("Patch set " + patchSet.getPatchSetId() + " not found");
         } else {
           return ruleError("Cannot submit draft patch sets");
         }
       }
     } catch (OrmException err) {
-      return logRuleError("Cannot read patch set " + patchSetId, err);
+      return logRuleError("Cannot read patch set " + patchSet.getId(), err);
     }
 
     List<Term> results = new ArrayList<Term>();
@@ -323,7 +341,8 @@
     try {
       env.set(StoredValues.REVIEW_DB, db);
       env.set(StoredValues.CHANGE, change);
-      env.set(StoredValues.PATCH_SET_ID, patchSetId);
+      env.set(StoredValues.CHANGE_DATA, cd);
+      env.set(StoredValues.PATCH_SET, patchSet);
       env.set(StoredValues.CHANGE_CONTROL, this);
 
       submitRule = env.once(
@@ -334,6 +353,10 @@
             + getProject().getName());
       }
 
+      if (fastEvalLabels) {
+        env.once("gerrit", "assume_range_from_label");
+      }
+
       try {
         for (Term[] template : env.all(
             "gerrit", "can_submit",
@@ -372,6 +395,10 @@
             parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
         if (filterRule != null) {
           try {
+            if (fastEvalLabels) {
+              env.once("gerrit", "assume_range_from_label");
+            }
+
             Term resultsTerm = toListTerm(results);
             results.clear();
             Term[] template = parentEnv.once(
@@ -515,16 +542,9 @@
     }
   }
 
-  private boolean isDraftVisible(ReviewDb db) throws OrmException {
-    return isOwner() || isReviewer(db);
-  }
-
-  private boolean isDraftPatchSet(PatchSet.Id id, ReviewDb db) throws OrmException {
-    PatchSet ps = db.patchSets().get(id);
-    if (ps == null) {
-      throw new OrmException("Patch set " + id + " not found");
-    }
-    return ps.isDraft();
+  private boolean isDraftVisible(ReviewDb db, ChangeData cd)
+      throws OrmException {
+    return isOwner() || isReviewer(db, cd);
   }
 
   private static boolean isUser(Term who) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index cb96cff..db3470e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -14,11 +14,14 @@
 
 package com.google.gerrit.server.query.change;
 
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.PatchSet.Id;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.TrackingId;
@@ -30,6 +33,7 @@
 import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.ObjectId;
@@ -47,9 +51,61 @@
 import java.util.Map;
 
 public class ChangeData {
+  public static void ensureChangeLoaded(
+      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+    Map<Change.Id, ChangeData> missing = Maps.newHashMap();
+    for (ChangeData cd : changes) {
+      if (cd.change == null) {
+        missing.put(cd.getId(), cd);
+      }
+    }
+    if (!missing.isEmpty()) {
+      for (Change change : db.get().changes().get(missing.keySet())) {
+        missing.get(change.getId()).change = change;
+      }
+    }
+  }
+
+  public static void ensureCurrentPatchSetLoaded(
+      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+    Map<PatchSet.Id, ChangeData> missing = Maps.newHashMap();
+    for (ChangeData cd : changes) {
+      if (cd.currentPatchSet == null && cd.patches == null) {
+        missing.put(cd.change(db).currentPatchSetId(), cd);
+      }
+    }
+    if (!missing.isEmpty()) {
+      for (PatchSet ps : db.get().patchSets().get(missing.keySet())) {
+        ChangeData cd = missing.get(ps.getId());
+        cd.currentPatchSet = ps;
+        cd.patches = Lists.newArrayList(ps);
+      }
+    }
+  }
+
+  public static void ensureCurrentApprovalsLoaded(
+      Provider<ReviewDb> db, List<ChangeData> changes) throws OrmException {
+    List<ResultSet<PatchSetApproval>> pending = Lists.newArrayList();
+    for (ChangeData cd : changes) {
+      if (cd.currentApprovals == null && cd.approvals == null) {
+        pending.add(db.get().patchSetApprovals()
+            .byPatchSet(cd.change(db).currentPatchSetId()));
+      }
+    }
+    if (!pending.isEmpty()) {
+      int idx = 0;
+      for (ChangeData cd : changes) {
+        if (cd.currentApprovals == null && cd.approvals == null) {
+          cd.currentApprovals = pending.get(idx++).toList();
+        }
+      }
+    }
+  }
+
   private final Change.Id legacyId;
   private Change change;
   private String commitMessage;
+  private PatchSet currentPatchSet;
   private Collection<PatchSet> patches;
   private Collection<PatchSetApproval> approvals;
   private Map<PatchSet.Id,Collection<PatchSetApproval>> approvalsMap;
@@ -144,16 +200,19 @@
   }
 
   public PatchSet currentPatchSet(Provider<ReviewDb> db) throws OrmException {
-    Change c = change(db);
-    if (c == null) {
-      return null;
-    }
-    for (PatchSet p : patches(db)) {
-      if (p.getId().equals(c.currentPatchSetId())) {
-        return p;
+    if (currentPatchSet == null) {
+      Change c = change(db);
+      if (c == null) {
+        return null;
+      }
+      for (PatchSet p : patches(db)) {
+        if (p.getId().equals(c.currentPatchSetId())) {
+          currentPatchSet = p;
+          return p;
+        }
       }
     }
-    return null;
+    return currentPatchSet;
   }
 
   public Collection<PatchSetApproval> currentApprovals(Provider<ReviewDb> db)
@@ -162,24 +221,21 @@
       Change c = change(db);
       if (c == null) {
         currentApprovals = Collections.emptyList();
+      } else if (approvals != null) {
+        Map<Id, Collection<PatchSetApproval>> map = approvalsMap(db);
+        currentApprovals = map.get(c.currentPatchSetId());
+        if (currentApprovals == null) {
+          currentApprovals = Collections.emptyList();
+          map.put(c.currentPatchSetId(), currentApprovals);
+        }
       } else {
-        currentApprovals = approvalsFor(db, c.currentPatchSetId());
+        currentApprovals = db.get().patchSetApprovals()
+            .byPatchSet(c.currentPatchSetId()).toList();
       }
     }
     return currentApprovals;
   }
 
-  public Collection<PatchSetApproval> approvalsFor(Provider<ReviewDb> db,
-      PatchSet.Id psId) throws OrmException {
-    List<PatchSetApproval> r = new ArrayList<PatchSetApproval>();
-    for (PatchSetApproval p : approvals(db)) {
-      if (p.getPatchSetId().equals(psId)) {
-        r.add(p);
-      }
-    }
-    return r;
-  }
-
   public String commitMessage(GitRepositoryManager repoManager,
       Provider<ReviewDb> db) throws IOException, OrmException {
     if (commitMessage == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
index ecfec96..6f9094a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -27,8 +27,6 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.OutputFormat;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountState;
 import com.google.gerrit.server.events.AccountAttribute;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
@@ -43,6 +41,7 @@
 import java.io.IOException;
 import java.io.Writer;
 import java.sql.Timestamp;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -50,7 +49,6 @@
 public class ListChanges {
   private final QueryProcessor imp;
   private final Provider<ReviewDb> db;
-  private final AccountCache accountCache;
   private final ApprovalTypes approvalTypes;
   private final CurrentUser user;
   private final ChangeControl.Factory changeControlFactory;
@@ -88,13 +86,11 @@
   @Inject
   ListChanges(QueryProcessor qp,
       Provider<ReviewDb> db,
-      AccountCache ac,
       ApprovalTypes at,
       CurrentUser u,
       ChangeControl.Factory cf) {
     this.imp = qp;
     this.db = db;
-    this.accountCache = ac;
     this.approvalTypes = at;
     this.user = u;
     this.changeControlFactory = cf;
@@ -135,6 +131,9 @@
           changes = changes.subList(0, imp.getLimit());
         }
       }
+      ChangeData.ensureChangeLoaded(db, changes);
+      ChangeData.ensureCurrentPatchSetLoaded(db, changes);
+      ChangeData.ensureCurrentApprovalsLoaded(db, changes);
 
       List<ChangeInfo> info = Lists.newArrayListWithCapacity(changes.size());
       for (ChangeData cd : changes) {
@@ -150,6 +149,13 @@
       res.add(info);
     }
 
+    if (!accounts.isEmpty()) {
+      for (Account account : db.get().accounts().get(accounts.keySet())) {
+        AccountAttribute a = accounts.get(account.getId());
+        a.name = Strings.emptyToNull(account.getFullName());
+      }
+    }
+
     if (format.isJson()) {
       format.newGson().toJson(
           res.size() == 1 ? res.get(0) : res,
@@ -199,22 +205,11 @@
   }
 
   private AccountAttribute asAccountAttribute(Account.Id user) {
-    if (user == null) {
-      return null;
-    } else if (accounts.containsKey(user)) {
-      return accounts.get(user);
+    AccountAttribute a = accounts.get(user);
+    if (a == null) {
+      a = new AccountAttribute();
+      accounts.put(user, a);
     }
-
-    AccountState state = accountCache.get(user);
-    String name = state.getAccount().getFullName();
-    if (Strings.isNullOrEmpty(name)) {
-      accounts.put(user, null);
-      return null;
-    }
-
-    AccountAttribute a = new AccountAttribute();
-    a.name = name;
-    accounts.put(user, a);
     return a;
   }
 
@@ -229,9 +224,9 @@
       }
     }
 
-    PatchSet.Id ps = in.currentPatchSetId();
+    PatchSet ps = cd.currentPatchSet(db);
     Map<String, LabelInfo> labels = Maps.newLinkedHashMap();
-    for (SubmitRecord rec : ctl.canSubmit(db.get(), ps)) {
+    for (SubmitRecord rec : ctl.canSubmit(db.get(), ps, cd, true)) {
       if (rec.labels == null) {
         continue;
       }
@@ -253,7 +248,7 @@
       }
     }
 
-    List<PatchSetApproval> approvals = null;
+    Collection<PatchSetApproval> approvals = null;
     for (Map.Entry<String, LabelInfo> e : labels.entrySet()) {
       if (e.getValue().approved != null || e.getValue().rejected != null) {
         continue;
@@ -273,7 +268,7 @@
       }
 
       if (approvals == null) {
-        approvals = db.get().patchSetApprovals().byPatchSet(ps).toList();
+        approvals = cd.currentApprovals(db);
       }
       for (PatchSetApproval psa : approvals) {
         short val = psa.getValue();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
index a5ac255..76945f4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/QueryProcessor.java
@@ -279,7 +279,7 @@
             if (current != null) {
               c.currentPatchSet = eventFactory.asPatchSetAttribute(current);
               eventFactory.addApprovals(c.currentPatchSet, //
-                  d.approvalsFor(db, current.getId()));
+                  d.currentApprovals(db));
 
               if (includeFiles) {
                 eventFactory.addPatchSetFileNames(c.currentPatchSet,
diff --git a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
index c760426..a0bb820 100644
--- a/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
+++ b/gerrit-server/src/main/java/gerrit/PRED__load_commit_labels_1.java
@@ -9,7 +9,9 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gwtorm.server.OrmException;
+import com.google.inject.util.Providers;
 
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
 import com.googlecode.prolog_cafe.lang.JavaException;
@@ -44,10 +46,18 @@
     try {
       PrologEnvironment env = (PrologEnvironment) engine.control;
       ReviewDb db = StoredValues.REVIEW_DB.get(engine);
-      PatchSet.Id patchSetId = StoredValues.PATCH_SET_ID.get(engine);
+      PatchSet patchSet = StoredValues.PATCH_SET.get(engine);
+      ChangeData cd = StoredValues.CHANGE_DATA.getOrNull(engine);
       ApprovalTypes types = env.getInjector().getInstance(ApprovalTypes.class);
 
-      for (PatchSetApproval a : db.patchSetApprovals().byPatchSet(patchSetId)) {
+      Iterable<PatchSetApproval> approvals;
+      if (cd != null) {
+        approvals = cd.currentApprovals(Providers.of(db));
+      } else {
+        approvals = db.patchSetApprovals().byPatchSet(patchSet.getId());
+      }
+
+      for (PatchSetApproval a : approvals) {
         if (a.getValue() == 0) {
           continue;
         }
diff --git a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
index 0a15608..1359de1 100644
--- a/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
+++ b/gerrit-server/src/main/java/gerrit/PRED_current_user_2.java
@@ -20,15 +20,12 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.rules.PrologEnvironment;
 import com.google.gerrit.rules.StoredValues;
-import com.google.gerrit.server.AnonymousUser;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
-import com.google.inject.Provider;
+import com.google.inject.util.Providers;
 
-import com.googlecode.prolog_cafe.lang.HashtableOfTerm;
 import com.googlecode.prolog_cafe.lang.IllegalTypeException;
 import com.googlecode.prolog_cafe.lang.IntegerTerm;
-import com.googlecode.prolog_cafe.lang.InternalException;
 import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
 import com.googlecode.prolog_cafe.lang.Operation;
 import com.googlecode.prolog_cafe.lang.PInstantiationException;
@@ -39,6 +36,8 @@
 import com.googlecode.prolog_cafe.lang.SymbolTerm;
 import com.googlecode.prolog_cafe.lang.Term;
 
+import java.util.Map;
+
 /**
  * Loads a CurrentUser object for a user identity.
  * <p>
@@ -53,7 +52,6 @@
   private static final long serialVersionUID = 1L;
   private static final SymbolTerm user = intern("user", 1);
   private static final SymbolTerm anonymous = intern("anonymous");
-  private static final SymbolTerm current_user = intern("current_user");
 
   PRED_current_user_2(Term a1, Term a2, Operation n) {
     arg1 = a1;
@@ -71,24 +69,14 @@
       throw new PInstantiationException(this, 1);
     }
 
-    HashtableOfTerm userHash = userHash(engine);
-    Term userTerm = userHash.get(a1);
-    if (userTerm != null && userTerm.isJavaObject()) {
-      if (!(((JavaObjectTerm) userTerm).object() instanceof CurrentUser)) {
-        userTerm = createUser(engine, a1, userHash);
-      }
-    } else {
-      userTerm = createUser(engine, a1, userHash);
-    }
-
-    if (!a2.unify(userTerm, engine.trail)) {
+    if (!a2.unify(createUser(engine, a1), engine.trail)) {
       return engine.fail();
     }
 
     return cont;
   }
 
-  public Term createUser(Prolog engine, Term key, HashtableOfTerm userHash) {
+  public Term createUser(Prolog engine, Term key) {
     if (!key.isStructure()
         || key.arity() != 1
         || !((StructureTerm) key).functor().equals(user)) {
@@ -98,54 +86,30 @@
     Term idTerm = key.arg(0);
     CurrentUser user;
     if (idTerm.isInteger()) {
+      Map<Account.Id, IdentifiedUser> cache = StoredValues.USERS.get(engine);
       Account.Id accountId = new Account.Id(((IntegerTerm) idTerm).intValue());
-
-      final ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
-      IdentifiedUser.GenericFactory userFactory = userFactory(engine);
-      if (db != null) {
-        user = userFactory.create(new Provider<ReviewDb>() {
-          public ReviewDb get() {
-            return db;
-          }
-        }, accountId);
-      } else {
-        user = userFactory.create(accountId);
+      user = cache.get(accountId);
+      if (user == null) {
+        ReviewDb db = StoredValues.REVIEW_DB.getOrNull(engine);
+        IdentifiedUser.GenericFactory userFactory = userFactory(engine);
+        IdentifiedUser who;
+        if (db != null) {
+          who = userFactory.create(Providers.of(db), accountId);
+        } else {
+          who = userFactory.create(accountId);
+        }
+        cache.put(accountId, who);
+        user = who;
       }
 
-
     } else if (idTerm.equals(anonymous)) {
-      user = anonymousUser(engine);
+      user = StoredValues.ANONYMOUS_USER.get(engine);
 
     } else {
       throw new IllegalTypeException(this, 1, "user(int)", key);
     }
 
-    Term userTerm = new JavaObjectTerm(user);
-    userHash.put(key, userTerm);
-    return userTerm;
-  }
-
-  private static HashtableOfTerm userHash(Prolog engine) {
-    Term userHash = engine.getHashManager().get(current_user);
-    if (userHash == null) {
-      HashtableOfTerm users = new HashtableOfTerm();
-      engine.getHashManager().put(current_user, new JavaObjectTerm(userHash));
-      return users;
-    }
-
-    if (userHash.isJavaObject()) {
-      Object obj = ((JavaObjectTerm) userHash).object();
-      if (obj instanceof HashtableOfTerm) {
-        return (HashtableOfTerm) obj;
-      }
-    }
-
-    throw new InternalException(current_user + " is not HashtableOfTerm");
-  }
-
-  private static AnonymousUser anonymousUser(Prolog engine) {
-    PrologEnvironment env = (PrologEnvironment) engine.control;
-    return env.getInjector().getInstance(AnonymousUser.class);
+    return new JavaObjectTerm(user);
   }
 
   private static IdentifiedUser.GenericFactory userFactory(Prolog engine) {
diff --git a/gerrit-server/src/main/prolog/gerrit_common.pl b/gerrit-server/src/main/prolog/gerrit_common.pl
index 3313162..5acc831 100644
--- a/gerrit-server/src/main/prolog/gerrit_common.pl
+++ b/gerrit-server/src/main/prolog/gerrit_common.pl
@@ -25,8 +25,7 @@
 %%   predicate that needs to obtain it.
 %%
 init :-
-  define_hash(commit_labels),
-  define_hash(current_user).
+  define_hash(commit_labels).
 
 define_hash(A) :- hash_exists(A), !, hash_clear(A).
 define_hash(A) :- atom(A), !, new_hash(_, [alias(A)]).
@@ -98,6 +97,10 @@
 %%   Lookup the range allowed to be used.
 %%
 user_label_range(Label, Who, Min, Max) :-
+  hash_get(commit_labels, '$fast_range', true), !,
+  atom(Label),
+  assume_range_from_label(Label, Who, Min, Max).
+user_label_range(Label, Who, Min, Max) :-
   Who = user(_), !,
   atom(Label),
   current_user(Who, User),
@@ -106,6 +109,14 @@
   clause(user:test_grant(Label, test_user(Name), range(Min, Max)), _)
   .
 
+assume_range_from_label :-
+  hash_put(commit_labels, '$fast_range', true).
+
+assume_range_from_label(Label, Who, Min, Max) :-
+  commit_label(label(Label, Value), Who), !,
+  Min = Value, Max = Value.
+assume_range_from_label(_, _, 0, 0).
+
 
 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
 %%
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
index f2f0fc76..1eb6842 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mail/ChangeFooter.vm
@@ -31,7 +31,8 @@
 ## The ChangeFooter.vm template will determine the contents of the footer
 ## text that will be appended to ALL emails related to changes.
 ##
---
+#set ($SPACE = " ")
+--$SPACE
 #if ($email.changeUrl)
 To view, visit $email.changeUrl
 #set ($notblank = 1)
diff --git a/gerrit-sshd/.gitignore b/gerrit-sshd/.gitignore
index 194bedc..8deb9bd 100644
--- a/gerrit-sshd/.gitignore
+++ b/gerrit-sshd/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-sshd.iml
\ No newline at end of file
diff --git a/gerrit-sshd/pom.xml b/gerrit-sshd/pom.xml
index 55e8725..1c197a0 100644
--- a/gerrit-sshd/pom.xml
+++ b/gerrit-sshd/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-sshd</artifactId>
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
index 54b0bb5..558707b 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/SshModule.java
@@ -36,6 +36,7 @@
 import com.google.gerrit.sshd.args4j.AccountGroupIdHandler;
 import com.google.gerrit.sshd.args4j.AccountGroupUUIDHandler;
 import com.google.gerrit.sshd.args4j.AccountIdHandler;
+import com.google.gerrit.sshd.args4j.ObjectIdHandler;
 import com.google.gerrit.sshd.args4j.PatchSetIdHandler;
 import com.google.gerrit.sshd.args4j.ProjectControlHandler;
 import com.google.gerrit.sshd.args4j.SocketAddressHandler;
@@ -48,6 +49,7 @@
 import org.apache.sshd.common.KeyPairProvider;
 import org.apache.sshd.server.CommandFactory;
 import org.apache.sshd.server.PublickeyAuthenticator;
+import org.eclipse.jgit.lib.ObjectId;
 import org.kohsuke.args4j.spi.OptionHandler;
 
 import java.net.SocketAddress;
@@ -118,6 +120,7 @@
     registerOptionHandler(Account.Id.class, AccountIdHandler.class);
     registerOptionHandler(AccountGroup.Id.class, AccountGroupIdHandler.class);
     registerOptionHandler(AccountGroup.UUID.class, AccountGroupUUIDHandler.class);
+    registerOptionHandler(ObjectId.class, ObjectIdHandler.class);
     registerOptionHandler(PatchSet.Id.class, PatchSetIdHandler.class);
     registerOptionHandler(ProjectControl.class, ProjectControlHandler.class);
     registerOptionHandler(SocketAddress.class, SocketAddressHandler.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
new file mode 100644
index 0000000..adb5ad6
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/args4j/ObjectIdHandler.java
@@ -0,0 +1,47 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.args4j;
+
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+public class ObjectIdHandler extends OptionHandler<ObjectId> {
+
+  @Inject
+  public ObjectIdHandler(@Assisted final CmdLineParser parser,
+      @Assisted final OptionDef option, @Assisted final Setter<ObjectId> setter) {
+    super(parser, option, setter);
+  }
+
+  @Override
+  public int parseArguments(Parameters params) throws CmdLineException {
+    final String n = params.getParameter(0);
+    setter.addValue(ObjectId.fromString(n));
+    return 1;
+  }
+
+  @Override
+  public String getDefaultMetaVariable() {
+    return "COMMIT";
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
new file mode 100644
index 0000000..fd58221
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/BanCommitCommand.java
@@ -0,0 +1,118 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.sshd.commands;
+
+import com.google.gerrit.common.errors.PermissionDeniedException;
+import com.google.gerrit.server.git.BanCommit;
+import com.google.gerrit.server.git.BanCommitResult;
+import com.google.gerrit.server.git.IncompleteUserInfoException;
+import com.google.gerrit.server.git.MergeException;
+import com.google.gerrit.server.project.ProjectControl;
+import com.google.gerrit.sshd.BaseCommand;
+import com.google.inject.Inject;
+
+import org.apache.sshd.server.Environment;
+import org.eclipse.jgit.lib.ObjectId;
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class BanCommitCommand extends BaseCommand {
+
+  @Option(name = "--reason", aliases = {"-r"}, metaVar = "REASON", usage = "reason for banning the commit")
+  private String reason;
+
+  @Argument(index = 0, required = true, metaVar = "PROJECT",
+      usage = "name of the project for which the commit should be banned")
+  private ProjectControl projectControl;
+
+  @Argument(index = 1, required = true, multiValued = true, metaVar = "COMMIT",
+      usage = "commit(s) that should be banned")
+  private List<ObjectId> commitsToBan = new ArrayList<ObjectId>();
+
+  @Inject
+  private BanCommit.Factory banCommitFactory;
+
+  @Override
+  public void start(final Environment env) throws IOException {
+    startThread(new CommandRunnable() {
+      @Override
+      public void run() throws Exception {
+        parseCommandLine();
+        BanCommitCommand.this.display();
+      }
+    });
+  }
+
+  private void display() throws Failure {
+    try {
+      final BanCommitResult result =
+          banCommitFactory.create().ban(projectControl, commitsToBan, reason);
+
+      final PrintWriter stdout = toPrintWriter(out);
+      try {
+        final List<ObjectId> newlyBannedCommits =
+            result.getNewlyBannedCommits();
+        if (!newlyBannedCommits.isEmpty()) {
+          stdout.print("The following commits were banned:\n");
+          printCommits(stdout, newlyBannedCommits);
+        }
+
+        final List<ObjectId> alreadyBannedCommits =
+            result.getAlreadyBannedCommits();
+        if (!alreadyBannedCommits.isEmpty()) {
+          stdout.print("The following commits were already banned:\n");
+          printCommits(stdout, alreadyBannedCommits);
+        }
+
+        final List<ObjectId> ignoredIds = result.getIgnoredObjectIds();
+        if (!ignoredIds.isEmpty()) {
+          stdout.print("The following ids do not represent commits"
+              + " and were ignored:\n");
+          printCommits(stdout, ignoredIds);
+        }
+      } finally {
+        stdout.flush();
+      }
+    } catch (PermissionDeniedException e) {
+      throw die(e);
+    } catch (IOException e) {
+      throw die(e);
+    } catch (IncompleteUserInfoException e) {
+      throw die(e);
+    } catch (MergeException e) {
+      throw die(e);
+    } catch (InterruptedException e) {
+      throw die(e);
+    }
+  }
+
+  private static void printCommits(final PrintWriter stdout,
+      final List<ObjectId> commits) {
+    boolean first = true;
+    for (final ObjectId c : commits) {
+      if (!first) {
+        stdout.print(",\n");
+      }
+      stdout.print(c.getName());
+      first = false;
+    }
+    stdout.print("\n\n");
+  }
+}
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
index 16461b6..4d7c93e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/DefaultCommandModule.java
@@ -35,6 +35,7 @@
     // SlaveCommandModule.
 
     command(gerrit).toProvider(new DispatchCommandProvider(gerrit));
+    command(gerrit, "ban-commit").to(BanCommitCommand.class);
     command(gerrit, "flush-caches").to(FlushCaches.class);
     command(gerrit, "ls-projects").to(ListProjectsCommand.class);
     command(gerrit, "ls-groups").to(ListGroupsCommand.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
index bc4e0bb..d56d1cd 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/Replicate.java
@@ -73,7 +73,7 @@
 
   private void schedule() throws Failure {
     if (all && projectNames.size() > 0) {
-      throw new Failure(1, "error: cannot combine --all and PROJECT");
+      throw new UnloggedFailure(1, "error: cannot combine --all and PROJECT");
     }
 
     if (!replication.isEnabled()) {
@@ -89,7 +89,7 @@
         if (projectCache.get(key) != null) {
           replication.scheduleFullSync(key, urlMatch);
         } else {
-          throw new Failure(1, "error: '" + name + "': not a Gerrit project");
+          throw new UnloggedFailure(1, "error: '" + name + "': not a Gerrit project");
         }
       }
     }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
index 32ab2db..0e1a1fe 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SlaveCommandModule.java
@@ -27,11 +27,14 @@
 
     command(gerrit, "approve").to(ErrorSlaveMode.class);
     command(gerrit, "create-account").to(ErrorSlaveMode.class);
+    command(gerrit, "create-group").to(ErrorSlaveMode.class);
     command(gerrit, "create-project").to(ErrorSlaveMode.class);
     command(gerrit, "gsql").to(ErrorSlaveMode.class);
     command(gerrit, "receive-pack").to(ErrorSlaveMode.class);
+    command(gerrit, "rename-group").to(ErrorSlaveMode.class);
     command(gerrit, "replicate").to(ErrorSlaveMode.class);
     command(gerrit, "review").to(ErrorSlaveMode.class);
     command(gerrit, "set-project-parent").to(ErrorSlaveMode.class);
+    command(gerrit, "set-reviewers").to(ErrorSlaveMode.class);
   }
 }
diff --git a/gerrit-util-cli/.gitignore b/gerrit-util-cli/.gitignore
index 194bedc..35069e7 100644
--- a/gerrit-util-cli/.gitignore
+++ b/gerrit-util-cli/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-util-cli.iml
\ No newline at end of file
diff --git a/gerrit-util-cli/pom.xml b/gerrit-util-cli/pom.xml
index 4ecbda4..4886d09 100644
--- a/gerrit-util-cli/pom.xml
+++ b/gerrit-util-cli/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-cli</artifactId>
diff --git a/gerrit-util-ssl/.gitignore b/gerrit-util-ssl/.gitignore
index 194bedc..e552ad5 100644
--- a/gerrit-util-ssl/.gitignore
+++ b/gerrit-util-ssl/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-util-ssl.iml
\ No newline at end of file
diff --git a/gerrit-util-ssl/pom.xml b/gerrit-util-ssl/pom.xml
index 2e49d47..beedb8f 100644
--- a/gerrit-util-ssl/pom.xml
+++ b/gerrit-util-ssl/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-util-ssl</artifactId>
diff --git a/gerrit-war/.gitignore b/gerrit-war/.gitignore
index 194bedc..dc8c7ad 100644
--- a/gerrit-war/.gitignore
+++ b/gerrit-war/.gitignore
@@ -2,4 +2,5 @@
 /.classpath
 /.project
 /.settings/org.maven.ide.eclipse.prefs
-/.settings/org.eclipse.m2e.core.prefs
\ No newline at end of file
+/.settings/org.eclipse.m2e.core.prefs
+/gerrit-war.iml
\ No newline at end of file
diff --git a/gerrit-war/pom.xml b/gerrit-war/pom.xml
index 733d976a..1f3750e 100644
--- a/gerrit-war/pom.xml
+++ b/gerrit-war/pom.xml
@@ -22,7 +22,7 @@
   <parent>
     <groupId>com.google.gerrit</groupId>
     <artifactId>gerrit-parent</artifactId>
-    <version>2.4-SNAPSHOT</version>
+    <version>2.5-SNAPSHOT</version>
   </parent>
 
   <artifactId>gerrit-war</artifactId>
diff --git a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
index 117bf61..3ae9440 100644
--- a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
+++ b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/gerrit.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 <!--
 
   Jetty configuration to place "gerrit.war" into the root context,
diff --git a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
index 652acad..59cc040 100644
--- a/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
+++ b/gerrit-war/src/main/webapp/WEB-INF/extra/jetty7/jetty_sslproxy.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://jetty.eclipse.org/configure.dtd">
+<!DOCTYPE Configure PUBLIC "-//Mort Bay Consulting//DTD Configure//EN" "http://www.eclipse.org/jetty/configure.dtd">
 <!--
 
   Jetty configuration to correctly handle SSL/HTTPS traffic when
diff --git a/pom.xml b/pom.xml
index 377b212..8c14a87 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
   <groupId>com.google.gerrit</groupId>
   <artifactId>gerrit-parent</artifactId>
   <packaging>pom</packaging>
-  <version>2.4-SNAPSHOT</version>
+  <version>2.5-SNAPSHOT</version>
 
   <name>Gerrit Code Review - Parent</name>
   <url>http://code.google.com/p/gerrit/</url>