Merge "Access control documentation: Tidying up format mistake"
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index 55cb456..e446f71 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -150,6 +150,9 @@
 link:cmd-plugin-remove.html[gerrit plugin rm]::
     Alias for 'gerrit plugin remove'.
 
+link:cmd-test-submit-rule.html[gerrit test-submit-rule]::
+	Test prolog submit rules.
+
 link:cmd-kill.html[kill]::
 	Kills a scheduled or running task.
 
diff --git a/Documentation/cmd-test-submit-rule.txt b/Documentation/cmd-test-submit-rule.txt
new file mode 100644
index 0000000..5b70bd1
--- /dev/null
+++ b/Documentation/cmd-test-submit-rule.txt
@@ -0,0 +1,93 @@
+gerrit test-submit-rule
+=======================
+
+NAME
+----
+gerrit test-submit-rule - Test prolog submit rules with a chosen changeset.
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit test-submit-rule'
+  [-s]
+  [--no-filters]
+  [--format {TEXT | JSON}]
+  CHANGE
+
+DESCRIPTION
+-----------
+Provides a way to test prolog link:prolog-cookbook.html[submit rules].
+
+OPTIONS
+-------
+-s::
+	Reads a rules.pl file from stdin instead of rules.pl in refs/meta/config.
+
+--no-filters::
+	Don't run the submit_filter/2 from the parent projects of the specified change.
+
+--format::
+  What output format to display the results in.
++
+--
+`text`:: Simple text based format.
+`json`:: A JSON object described in link:json.html#submitRecord[submit record].
+`json_compact`:: Minimized JSON output.
+--
+
+ACCESS
+------
+Can be used by anyone that has permission to read the specified changeset.
+
+EXAMPLES
+--------
+
+
+Test submit_rule from stdin.
+====
+ $ cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+ Non-Author-Code-Review: NOT_READY
+ Verified: NOT_READY
+ Code-Review: NOT_READY by Anonymous Coward <test@email.com>
+
+ NOT_READY
+====
+
+Test submit_rule from stdin and return the results as JSON.
+====
+ cat non-author-codereview.pl | ssh -p 29418 review.example.com gerrit test-submit-rule --format=JSON -s I78f2c6673db24e4e92ed32f604c960dc952437d9
+ {
+  "approvals": [
+    {
+      "type": "Verified",
+      "value": "NEED"
+    },
+    {
+      "type": "Code-Review",
+      "value": "OK",
+      "by": {
+        "email": "test@email.com",
+        "username": "test"
+      }
+    }
+  ],
+  "value": "NOT_READY"
+ }
+====
+
+Test the active submit_rule from the refs/meta/config branch, ignoring filters in the project parents.
+====
+ $ ssh -p 29418 review.example.com gerrit test-submit-rule I78f2c6673db24e4e92ed32f604c960dc952437d9 --no-filters
+ Verified: NOT_READY
+ Code-Review: NOT_READY by Anonymous Coward <test@email.com>
+
+ NOT_READY
+====
+
+SCRIPTING
+---------
+Can be used either interactively for testing new prolog submit rules, or from a script to check the submit status of a change.
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index c328b41..ea45d804e 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1707,6 +1707,18 @@
 Default is `(memberUid=${username})` for RFC 2307,
 and unset (disabled) for Active Directory.
 
+[[ldap.groupName]]ldap.groupName::
++
+_(Optional)_ Name of an attribute on the group object which contains
+the value for the group name in Gerrit.
+Typically this is the `cn` property in LDAP, but could also be
+`apple-group-realname`.
++
+Attribute values may be concatenated with literal strings. For example
+to join group name and group id, use the pattern `${cn} (${gidNumber})`.
++
+Default is `cn` for RFC 2307 servers and Active Directory.
+
 [[ldap.localUsernameToLowerCase]]ldap.localUsernameToLowerCase::
 +
 Converts the local username, that is used to login into the Gerrit
diff --git a/Documentation/json.txt b/Documentation/json.txt
index f0588ce..aea9bac 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -30,6 +30,9 @@
 
 commitMessage:: The full commit message for the change.
 
+createdOn:: Time in seconds since the UNIX epoch when this change
+was created.
+
 lastUpdated:: Time in seconds since the UNIX epoch when this change
 was last updated.
 
@@ -48,14 +51,20 @@
 
   ABANDONED;; Change was abandoned by its owner or administrator.
 
+comments:: All comments for this change in <<message,message attributes>>.
+
 trackingIds:: Issue tracking system links in
-<<trackingid,trackingid attribute>>, scraped out of the commit
+<<trackingid,trackingid attributes>>, scraped out of the commit
 message based on the server's
 link:config-gerrit.html#trackingid[trackingid] sections.
 
 currentPatchSet:: Current <<patchSet,patchSet attribute>>.
 
-patchSets:: All <<patchSet,patchSet attribute>> for this change.
+patchSets:: All <<patchSet,patchSet attributes>> for this change.
+
+dependsOn:: List of changes that this change depends on in <<dependency,dependency attributes>>.
+
+neededBy:: List of changes that depend on this change in <<dependency,dependency attributes>>.
 
 submitRecords:: The <<submitRecord,submitRecord attribute>> contains
 information about whether this change has been or can be submitted.
@@ -90,14 +99,23 @@
 
 revision:: Git commit for this patchset.
 
+parents:: List of parent revisions.
+
 ref:: Git reference pointing at the revision.  This reference is
 available through the Gerrit Code Review server's Git interface
 for the containing change.
 
 uploader:: Uploader of the patch set in <<account,account attribute>>.
 
+createdOn:: Time in seconds since the UNIX epoch when this patchset
+was created.
+
 approvals:: The <<approval,approval attribute>> granted.
 
+comments:: All inline comments for this patchset in <<patchsetcomment,patchsetComment attributes>>.
+
+files:: All changed files in this patchset in <<patch,patch attributes>>.
+
 [[approval]]
 approval
 --------
@@ -123,10 +141,10 @@
 
 newRev:: The new value the ref was updated to.
 
-project:: Project path in Gerrit.
-
 refName:: Ref name within project.
 
+project:: Project path in Gerrit.
+
 [[queryLimit]]
 queryLimit
 ----------
@@ -178,6 +196,68 @@
 
 by:: The <<account,account>> that applied the label.
 
+[[dependency]]
+dependency
+----------
+Information about a change or patchset dependency.
+
+id:: Change identifier.
+
+number:: Change number.
+
+revision:: Patchset revision.
+
+ref:: Ref name.
+
+isCurrentPatchSet:: If the revision is the current patchset of the change.
+
+[[message]]
+message
+-------
+Comment added on a change by a reviewer.
+
+timestamp:: Time in seconds since the UNIX epoch when this comment
+was added.
+
+reviewer:: The <<account,account>> that added the comment.
+
+message:: The comment text.
+
+[[patchsetcomment]]
+patchsetComment
+---------------
+Comment added inline on a patchset by a reviewer.
+
+file:: The name of the file on which the comment was added.
+
+line:: The line number at which the comment was added.
+
+reviewer:: The <<account,account>> that added the comment.
+
+message:: The comment text.
+
+[[patch]]
+patch
+-----
+Information about a patch on a file.
+
+file:: The name of the file.
+
+type:: The type of change.
+
+  ADDED;; The file is being created/introduced by this patch.
+
+  MODIFIED;; The file already exists, and has updated content.
+
+  DELETED;; The file existed, but is being removed by this patch.
+
+  RENAMED;; The file is renamed.
+
+  COPIED;; The file is copied from another file.
+
+  REWRITE;; Sufficient amount of content changed to claim the file was rewritten.
+
+
 SEE ALSO
 --------
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index 5fd0d39..f334b36 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
@@ -16,6 +16,7 @@
 
 import static com.google.gerrit.common.data.GlobalCapability.ADMINISTRATE_SERVER;
 import static com.google.gerrit.common.data.GlobalCapability.CREATE_PROJECT;
+import static com.google.gerrit.common.data.GlobalCapability.CREATE_GROUP;
 
 import com.google.gerrit.client.account.AccountCapabilities;
 import com.google.gerrit.client.auth.openid.OpenIdSignInDialog;
@@ -610,17 +611,28 @@
     menuLeft.add(projectsBar, C.menuProjects());
 
     if (signedIn) {
-      final LinkMenuBar menuBar = new LinkMenuBar();
-      addLink(menuBar, C.menuGroups(), PageLinks.ADMIN_GROUPS);
+      final LinkMenuBar groupsBar = new LinkMenuBar();
+      addLink(groupsBar, C.menuGroupsList(), PageLinks.ADMIN_GROUPS);
+      AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
+        @Override
+        public void onSuccess(AccountCapabilities result) {
+          if (result.canPerform(CREATE_GROUP)) {
+            addLink(groupsBar, C.menuGroupsCreate(), PageLinks.ADMIN_CREATE_GROUP);
+          }
+        }
+      }, CREATE_GROUP);
+      menuLeft.add(groupsBar, C.menuGroups());
+
+      final LinkMenuBar pluginsBar = new LinkMenuBar();
       AccountCapabilities.all(new GerritCallback<AccountCapabilities>() {
         @Override
         public void onSuccess(AccountCapabilities result) {
           if (result.canPerform(ADMINISTRATE_SERVER)) {
-            addLink(menuBar, C.menuPlugins(), PageLinks.ADMIN_PLUGINS);
+            addLink(pluginsBar, C.menuPluginsInstalled(), PageLinks.ADMIN_PLUGINS);
+            menuLeft.add(pluginsBar, C.menuPlugins());
           }
         }
       }, ADMINISTRATE_SERVER);
-      menuLeft.add(menuBar, C.menuAdmin());
     }
 
     if (getConfig().isDocumentationAvailable()) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index dbf9973..331ceb6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -73,10 +73,12 @@
   String menuProjectsList();
   String menuProjectsCreate();
 
-  String menuAdmin();
   String menuPeople();
   String menuGroups();
+  String menuGroupsList();
+  String menuGroupsCreate();
   String menuPlugins();
+  String menuPluginsInstalled();
 
   String menuDocumentation();
   String menuDocumentationIndex();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index 41dc06b..ef23c8d 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -56,10 +56,13 @@
 menuProjectsList = List
 menuProjectsCreate = Create New Project
 
-menuAdmin = Admin
 menuPeople = People
 menuGroups = Groups
+menuGroupsList = List
+menuGroupsCreate = Create New Group
+
 menuPlugins = Plugins
+menuPluginsInstalled = Installed
 
 menuDocumentation = Documentation
 menuDocumentationIndex = Index
@@ -90,4 +93,4 @@
 projectAccessError = You don't have permissions to modify the access rights for the following refs:
 projectAccessProposeForReviewHint = You may propose these modifications to the project owners by clicking on 'Save for Review'.
 
-userCannotVoteToolTip = User cannot vote in this category
\ No newline at end of file
+userCannotVoteToolTip = User cannot vote in this category
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 9e65836..0c18169 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
@@ -108,7 +108,6 @@
   String projectAdminTabAccess();
 
   String plugins();
-  String pluginTabInstalled();
   String pluginDisabled();
 
   String columnPluginName();
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 47bcea6..91202c5 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
@@ -88,7 +88,6 @@
 projectAdminTabAccess = Access
 
 plugins = Plugins
-pluginTabInstalled = Installed
 pluginDisabled = Disabled
 columnPluginName = Plugin Name
 columnPluginVersion = Version
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
index dc58e22..3157d97 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/GroupListScreen.java
@@ -14,16 +14,12 @@
 
 package com.google.gerrit.client.admin;
 
-import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.rpc.ScreenLoadCallback;
 import com.google.gerrit.client.ui.AccountScreen;
-import com.google.gerrit.client.ui.Hyperlink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.common.data.GroupList;
-import com.google.gwt.user.client.ui.VerticalPanel;
 
 public class GroupListScreen extends AccountScreen {
-  private VerticalPanel createGroupLinkPanel;
   private GroupTable groups;
 
   @Override
@@ -33,7 +29,6 @@
         .visibleGroups(new ScreenLoadCallback<GroupList>(this) {
           @Override
           protected void preDisplay(GroupList result) {
-            createGroupLinkPanel.setVisible(result.isCanCreateGroup());
             groups.display(result.getGroups());
             groups.finishDisplay();
           }
@@ -45,12 +40,6 @@
     super.onInitUI();
     setPageTitle(Util.C.groupListTitle());
 
-    createGroupLinkPanel = new VerticalPanel();
-    createGroupLinkPanel.setStyleName(Gerrit.RESOURCES.css().createGroupLink());
-    createGroupLinkPanel.add(new Hyperlink(Util.C.headingCreateGroup(),
-        PageLinks.ADMIN_CREATE_GROUP));
-    add(createGroupLinkPanel);
-
     groups = new GroupTable(true /* hyperlink to admin */, PageLinks.ADMIN_GROUPS);
     add(groups);
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
index 72cd7f9..dabcb45 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/PluginScreen.java
@@ -14,16 +14,12 @@
 
 package com.google.gerrit.client.admin;
 
-import static com.google.gerrit.common.PageLinks.ADMIN_PLUGINS;
+import com.google.gerrit.client.ui.Screen;
 
-import com.google.gerrit.client.ui.MenuScreen;
-
-public abstract class PluginScreen extends MenuScreen {
+public abstract class PluginScreen extends Screen {
 
   public PluginScreen() {
     setRequiresSignIn(true);
-
-    link(Util.C.pluginTabInstalled(), ADMIN_PLUGINS);
   }
 
   @Override
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
index 95b8487f..c0f0c4b 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Init.java
@@ -140,8 +140,9 @@
         throw (Die) why;
       }
 
-      final StringBuilder buf = new StringBuilder();
+      final StringBuilder buf = new StringBuilder(ce.getMessage());
       while (why != null) {
+        buf.append("\n");
         buf.append(why.getMessage());
         why = why.getCause();
         if (why != null) {
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
index 00e282b..6ddd6d2 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Patch.java
@@ -84,7 +84,7 @@
     /** Path was copied from {@link Patch#getSourceFileName()}. */
     COPIED('C'),
 
-    /** Sufficient amount of content changed to claim the file was written. */
+    /** Sufficient amount of content changed to claim the file was rewritten. */
     REWRITE('W');
 
     private final char code;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
index 2265bc2..7cf6268 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/Helper.java
@@ -242,6 +242,7 @@
     final List<String> groupBases;
     final SearchScope groupScope;
     final ParameterizedString groupPattern;
+    final ParameterizedString groupName;
     final List<LdapQuery> groupMemberQueryList;
 
     LdapSchema(final DirContext ctx) {
@@ -257,6 +258,7 @@
       groupBases = LdapRealm.optionalList(config, "groupBase");
       groupScope = LdapRealm.scope(config, "groupScope");
       groupPattern = LdapRealm.paramString(config, "groupPattern", type.groupPattern());
+      groupName = LdapRealm.paramString(config, "groupName", type.groupName());
       final String groupMemberPattern =
           LdapRealm.optdef(config, "groupMemberPattern", type.groupMemberPattern());
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
index 5c30e5c..6b54f6b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapGroupBackend.java
@@ -41,6 +41,7 @@
 
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
@@ -81,11 +82,11 @@
     return uuid.get().startsWith(LDAP_UUID);
   }
 
-  private static GroupReference groupReference(LdapQuery.Result res)
-      throws NamingException {
+  private static GroupReference groupReference(ParameterizedString p,
+      LdapQuery.Result res) throws NamingException {
     return new GroupReference(
         new AccountGroup.UUID(LDAP_UUID + res.getDN()),
-        LDAP_NAME + cnFor(res.getDN()));
+        LDAP_NAME + LdapRealm.apply(p, res));
   }
 
   private static String cnFor(String dn) {
@@ -203,13 +204,14 @@
         LdapSchema schema = helper.getSchema(ctx);
         ParameterizedString filter = ParameterizedString.asis(
             schema.groupPattern.replace(GROUPNAME, name).toString());
-        Set<String> returnAttrs = Collections.<String>emptySet();
+        Set<String> returnAttrs =
+            new HashSet<String>(schema.groupName.getParameterNames());
         Map<String, String> params = Collections.emptyMap();
         for (String groupBase : schema.groupBases) {
           LdapQuery query = new LdapQuery(
               groupBase, schema.groupScope, filter, returnAttrs);
           for (LdapQuery.Result res : query.query(ctx, params)) {
-            out.add(groupReference(res));
+            out.add(groupReference(schema.groupName, res));
           }
         }
       } finally {
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 72eb7ec..4ddbc67 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
@@ -167,7 +167,7 @@
     return !readOnlyAccountFields.contains(field);
   }
 
-  private static String apply(ParameterizedString p, LdapQuery.Result m)
+  static String apply(ParameterizedString p, LdapQuery.Result m)
       throws NamingException {
     if (p == null) {
       return null;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
index 3c4a54b..fd5bb30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/auth/ldap/LdapType.java
@@ -36,6 +36,8 @@
 
   abstract String groupMemberPattern();
 
+  abstract String groupName();
+
   abstract String accountFullName();
 
   abstract String accountEmailAddress();
@@ -58,6 +60,11 @@
     }
 
     @Override
+    String groupName() {
+      return "cn";
+    }
+
+    @Override
     String accountFullName() {
       return "displayName";
     }
@@ -101,6 +108,11 @@
     }
 
     @Override
+    String groupName() {
+      return "cn";
+    }
+
+    @Override
     String groupMemberPattern() {
       return null; // Active Directory uses memberOf in the account
     }
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
index c0e00aa..79e35be 100644
--- 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
@@ -92,7 +92,8 @@
           banCommitNotes.set(commitToBan, createNoteContent(reason, inserter));
         }
         inserter.flush();
-        NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo);
+        NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(repo,
+            inserter);
         NoteMap newlyCreated =
             notesBranchUtil.commitNewNotes(banCommitNotes, REF_REJECT_COMMITS,
                 createPersonIdent(), buildCommitMessage(commitsToBan, reason));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
index 6d1b155..eab27f0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ChangeMergeQueue.java
@@ -89,6 +89,7 @@
         });
       }
 
+      @SuppressWarnings("unused")
       @Provides
       public PerThreadRequestScope.Scoper provideScoper(
           final PerThreadRequestScope.Propagator propagator) {
@@ -243,6 +244,7 @@
       dest = d;
     }
 
+    @Override
     public void run() {
       unschedule(this);
       mergeImpl(dest);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
index b067a49..50412e3 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/CreateCodeReviewNotes.java
@@ -114,9 +114,11 @@
         message.append("* ").append(c.getShortMessage()).append("\n");
       }
 
-      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db,
+          inserter);
       notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
           message.toString());
+      inserter.flush();
     } catch (IOException e) {
       throw new CodeReviewNoteCreationException(e);
     } catch (ConcurrentRefUpdateException e) {
@@ -148,9 +150,11 @@
         notes.set(commitId, createNoteContent(c, commitId));
       }
 
-      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db);
+      NotesBranchUtil notesBranchUtil = notesBranchUtilFactory.create(db,
+          inserter);
       notesBranchUtil.commitAllNotes(notes, REFS_NOTES_REVIEW, author,
           commitMessage);
+      inserter.flush();
     } catch (ConcurrentRefUpdateException e) {
       throw new CodeReviewNoteCreationException(e);
     } finally {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 26fd2ea..8123c64 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -487,6 +487,7 @@
     toMerge.clear();
     toMerge.addAll(heads);
     Collections.sort(toMerge, new Comparator<CodeReviewCommit>() {
+      @Override
       public int compare(final CodeReviewCommit a, final CodeReviewCommit b) {
         return a.originalOrder - b.originalOrder;
       }
@@ -854,6 +855,7 @@
       approvalList =
           db.patchSetApprovals().byPatchSet(n.patchsetId).toList();
       Collections.sort(approvalList, new Comparator<PatchSetApproval>() {
+        @Override
         public int compare(final PatchSetApproval a, final PatchSetApproval b) {
           return a.getGranted().compareTo(b.getGranted());
         }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
index 17cfea8..7887edd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -47,7 +47,7 @@
  */
 public class NotesBranchUtil {
   public interface Factory {
-    NotesBranchUtil create(Repository db);
+    NotesBranchUtil create(Repository db, ObjectInserter inserter);
   }
 
   private static final int MAX_LOCK_FAILURE_CALLS = 10;
@@ -55,6 +55,7 @@
 
   private PersonIdent gerritIdent;
   private final Repository db;
+  private final ObjectInserter inserter;
 
   private RevCommit baseCommit;
   private NoteMap base;
@@ -63,7 +64,6 @@
   private NoteMap ours;
 
   private RevWalk revWalk;
-  private ObjectInserter inserter;
   private ObjectReader reader;
   private boolean overwrite;
 
@@ -71,9 +71,11 @@
 
   @Inject
   public NotesBranchUtil(@GerritPersonIdent final PersonIdent gerritIdent,
-      @Assisted Repository db) {
+      @Assisted Repository db,
+      @Assisted ObjectInserter inserter) {
     this.gerritIdent = gerritIdent;
     this.db = db;
+    this.inserter = inserter;
   }
 
   /**
@@ -128,7 +130,6 @@
       ConcurrentRefUpdateException {
     try {
       revWalk = new RevWalk(db);
-      inserter = db.newObjectInserter();
       reader = db.newObjectReader();
       loadBase(notesBranch);
       if (overwrite) {
@@ -144,7 +145,6 @@
       updateRef(notesBranch);
     } finally {
       revWalk.release();
-      inserter.release();
       reader.release();
     }
   }
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 9832a79..033faaa 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
@@ -443,12 +443,18 @@
       return ruleError("Project submit rule has no solution");
     }
 
-    // Convert the results from Prolog Cafe's format to Gerrit's common format.
-    // can_submit/1 terminates when an ok(P) record is found. Therefore walk
-    // the results backwards, using only that ok(P) record if it exists. This
-    // skips partial results that occur early in the output. Later after the loop
-    // the out collection is reversed to restore it to the original ordering.
-    //
+    return resultsToSubmitRecord(submitRule, results);
+  }
+
+  /**
+   * Convert the results from Prolog Cafe's format to Gerrit's common format.
+   *
+   * can_submit/1 terminates when an ok(P) record is found. Therefore walk
+   * the results backwards, using only that ok(P) record if it exists. This
+   * skips partial results that occur early in the output. Later after the loop
+   * the out collection is reversed to restore it to the original ordering.
+   */
+  public List<SubmitRecord> resultsToSubmitRecord(Term submitRule, List<Term> results) {
     List<SubmitRecord> out = new ArrayList<SubmitRecord>(results.size());
     for (int resultIdx = results.size() - 1; 0 <= resultIdx; resultIdx--) {
       Term submitRecord = results.get(resultIdx);
@@ -565,7 +571,7 @@
         && who.arg(0).isInteger();
   }
 
-  private static Term toListTerm(List<Term> terms) {
+  public static Term toListTerm(List<Term> terms) {
     Term list = Prolog.Nil;
     for (int i = terms.size() - 1; i >= 0; i--) {
       list = new ListTerm(terms.get(i), list);
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
index 212ffb1..06926df 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/tools/root/hooks/commit-msg
@@ -17,6 +17,8 @@
 # limitations under the License.
 #
 
+unset GREP_OPTIONS
+
 CHANGE_ID_AFTER="Bug|Issue"
 MSG="$1"
 
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index db4e985..90bc07e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -31,6 +31,7 @@
     command(gerrit, "rename-group").to(RenameGroupCommand.class);
     command(gerrit, "create-project").to(CreateProjectCommand.class);
     command(gerrit, "gsql").to(AdminQueryShell.class);
+    command(gerrit, "test-submit-rule").to(TestSubmitRule.class);
     command(gerrit, "set-reviewers").to(SetReviewersCommand.class);
     command(gerrit, "receive-pack").to(Receive.class);
     command(gerrit, "set-project-parent").to(AdminSetParent.class);
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java
new file mode 100644
index 0000000..9a81140
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/TestSubmitRule.java
@@ -0,0 +1,240 @@
+// 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.data.AccountInfo;
+import com.google.gerrit.common.data.SubmitRecord;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.rules.PrologEnvironment;
+import com.google.gerrit.rules.StoredValues;
+import com.google.gerrit.server.OutputFormat;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.config.AnonymousCowardName;
+import com.google.gerrit.server.events.AccountAttribute;
+import com.google.gerrit.server.events.SubmitLabelAttribute;
+import com.google.gerrit.server.events.SubmitRecordAttribute;
+import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.lang.BufferingPrologControl;
+import com.googlecode.prolog_cafe.lang.JavaObjectTerm;
+import com.googlecode.prolog_cafe.lang.ListTerm;
+import com.googlecode.prolog_cafe.lang.Prolog;
+import com.googlecode.prolog_cafe.lang.PrologClassLoader;
+import com.googlecode.prolog_cafe.lang.PrologException;
+import com.googlecode.prolog_cafe.lang.PrologMachineCopy;
+import com.googlecode.prolog_cafe.lang.SymbolTerm;
+import com.googlecode.prolog_cafe.lang.Term;
+import com.googlecode.prolog_cafe.lang.VariableTerm;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+
+/** Command that allows testing of prolog submit-rules in a live instance. */
+final class TestSubmitRule extends SshCommand {
+  @Inject
+  private ReviewDb db;
+
+  @Inject
+  private PrologEnvironment.Factory envFactory;
+
+  @Inject
+  private ChangeControl.Factory ccFactory;
+
+  @Inject
+  private AccountCache accountCache;
+
+  final @AnonymousCowardName String anonymousCowardName;
+
+  @Argument(index = 0, required = true, usage = "ChangeId to load in prolog environment")
+  private String changeId;
+
+  @Option(name = "-s",
+      usage = "Read prolog script from stdin instead of reading rules.pl from the refs/meta/config branch")
+  private boolean useStdin;
+
+  @Option(name = "--format", metaVar = "FMT", usage = "Output display format")
+  private OutputFormat format = OutputFormat.TEXT;
+
+  @Option(name = "--no-filters", aliases = {"-n"},
+      usage = "Don't run the submit_filter/2 from the parent projects")
+  private boolean skipSubmitFilters;
+
+  private static final String[] PACKAGE_LIST = {Prolog.BUILTIN, "gerrit"};
+
+  @Inject
+  public TestSubmitRule(@AnonymousCowardName String anonymous) {
+    anonymousCowardName = anonymous;
+  }
+  private PrologMachineCopy newMachine() {
+    BufferingPrologControl ctl = new BufferingPrologControl();
+    ctl.setMaxDatabaseSize(16 * 1024);
+    ctl.setPrologClassLoader(new PrologClassLoader(getClass().getClassLoader()));
+    return PrologMachineCopy.save(ctl);
+  }
+
+  @Override
+  protected void run() throws UnloggedFailure {
+    InputStreamReader inReader = new InputStreamReader(in);
+
+    try {
+      PrologEnvironment pcl;
+
+      List<Change> changeList =
+          db.changes().byKey(new Change.Key(changeId)).toList();
+      if (changeList.size() != 1)
+        throw new UnloggedFailure(1, "Invalid ChangeId");
+
+      Change c = changeList.get(0);
+      PatchSet ps = db.patchSets().get(c.currentPatchSetId());
+      // Will throw exception if current user can not access this change, and
+      // thus will leak information that a change-id is valid even though the
+      // user are not allowed to see the change.
+      // See http://code.google.com/p/gerrit/issues/detail?id=1586
+      ChangeControl cc = ccFactory.controlFor(c);
+      ProjectState projectState = cc.getProjectControl().getProjectState();
+
+      if (useStdin) {
+        pcl = envFactory.create(newMachine());
+      } else {
+        pcl = projectState.newPrologEnvironment();
+      }
+
+      pcl.set(StoredValues.REVIEW_DB, db);
+      pcl.set(StoredValues.CHANGE, c);
+      pcl.set(StoredValues.PATCH_SET, ps);
+      pcl.set(StoredValues.CHANGE_CONTROL, cc);
+      if (useStdin) {
+        pcl.initialize(PACKAGE_LIST);
+        pcl.execute(Prolog.BUILTIN, "consult_stream",
+            SymbolTerm.intern("stdin"), new JavaObjectTerm(inReader));
+      }
+
+      List<Term> results = new ArrayList<Term>();
+      Term submitRule =
+          pcl.once("gerrit", "locate_submit_rule", new VariableTerm());
+
+      for (Term[] template : pcl.all("gerrit", "can_submit", submitRule,
+          new VariableTerm())) {
+        results.add(template[1]);
+      }
+
+      if (!skipSubmitFilters) {
+        runSubmitFilters(projectState, results, pcl);
+      }
+
+      List<SubmitRecord> res = cc.resultsToSubmitRecord(submitRule, results);
+      for (SubmitRecord r : res) {
+        if (format.isJson()) {
+          SubmitRecordAttribute submitRecord = new SubmitRecordAttribute();
+          submitRecord.status = r.status.name();
+
+          List<SubmitLabelAttribute> submitLabels = new LinkedList<SubmitLabelAttribute>();
+          for(SubmitRecord.Label l : r.labels) {
+            SubmitLabelAttribute label = new SubmitLabelAttribute();
+            label.label = l.label;
+            label.status= l.status.name();
+            if(l.appliedBy != null) {
+              Account a = accountCache.get(l.appliedBy).getAccount();
+              label.by = new AccountAttribute();
+              label.by.email = a.getPreferredEmail();
+              label.by.name = a.getFullName();
+              label.by.username = a.getUserName();
+            }
+            submitLabels.add(label);
+          }
+          submitRecord.labels = submitLabels;
+          format.newGson().toJson(submitRecord, new TypeToken<SubmitRecordAttribute>() {}.getType(), stdout);
+          stdout.print('\n');
+        } else {
+          for(SubmitRecord.Label l : r.labels) {
+            stdout.print(l.label + ": " + l.status);
+            if(l.appliedBy != null) {
+              AccountInfo a = new AccountInfo(accountCache.get(l.appliedBy).getAccount());
+              stdout.print(" by " + a.getNameEmail(anonymousCowardName));
+            }
+            stdout.print('\n');
+          }
+          stdout.print("\n" + r.status.name() + "\n");
+        }
+      }
+    } catch (Exception e) {
+      throw new UnloggedFailure("Processing of prolog script failed: " + e);
+    }
+  }
+
+  private void runSubmitFilters(ProjectState projectState, List<Term> results,
+      PrologEnvironment pcl) throws UnloggedFailure {
+    ProjectState parentState = projectState.getParentState();
+    PrologEnvironment childEnv = pcl;
+    Set<Project.NameKey> projectsSeen = new HashSet<Project.NameKey>();
+    projectsSeen.add(projectState.getProject().getNameKey());
+
+    while (parentState != null) {
+      if (!projectsSeen.add(parentState.getProject().getNameKey())) {
+        // parent has been seen before, stop walk up inheritance tree
+        break;
+      }
+      PrologEnvironment parentEnv;
+      try {
+        parentEnv = parentState.newPrologEnvironment();
+      } catch (CompileException err) {
+        throw new UnloggedFailure("Cannot consult rules.pl for "
+            + parentState.getProject().getName() + err);
+      }
+
+      parentEnv.copyStoredValues(childEnv);
+      Term filterRule =
+          parentEnv.once("gerrit", "locate_submit_filter", new VariableTerm());
+      if (filterRule != null) {
+        try {
+          Term resultsTerm = ChangeControl.toListTerm(results);
+          results.clear();
+          Term[] template =
+              parentEnv.once("gerrit", "filter_submit_results", filterRule,
+                  resultsTerm, new VariableTerm());
+          @SuppressWarnings("unchecked")
+          final List<? extends Term> termList =
+              ((ListTerm) template[2]).toJava();
+          results.addAll(termList);
+        } catch (PrologException err) {
+          throw new UnloggedFailure("Exception calling " + filterRule + " of "
+              + parentState.getProject().getName() + err);
+        } catch (RuntimeException err) {
+          throw new UnloggedFailure("Exception calling " + filterRule + " of "
+              + parentState.getProject().getName() + err);
+        }
+      }
+
+      parentState = parentState.getParentState();
+      childEnv = parentEnv;
+    }
+  }
+}