Merge branch 'stable-2.8'

* stable-2.8:
  Fix jdbc code: Both PreparedStatements must be closed in any case
  SideBySide2: Let CodeMirror handle "contextmenu" event
  SideBySide2: Restore some old keybindings and fix help popup
  Add --project and --branch parameters on the topic-changed hook
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index 8fb6e82..f9a498b 100644
--- a/Documentation/config-hooks.txt
+++ b/Documentation/config-hooks.txt
@@ -126,7 +126,7 @@
 Called whenever a change's topic is changed from the Web UI or via the REST API.
 
 ====
-  topic-changed --change <change id> --changer <changer> --old-topic <old topic> --new-topic <new topic>
+  topic-changed --change <change id> --project <project name> --branch <branch> --changer <changer> --old-topic <old topic> --new-topic <new topic>
 ====
 
 cla-signed
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
index ba4f626..032db65 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyCommand.java
@@ -24,6 +24,7 @@
   public static final int M_CTRL = 1 << 16;
   public static final int M_ALT = 2 << 16;
   public static final int M_META = 4 << 16;
+  public static final int M_SHIFT = 8 << 16;
 
   public static boolean same(final KeyCommand a, final KeyCommand b) {
     return a.getClass() == b.getClass() && a.helpText.equals(b.helpText);
@@ -58,6 +59,9 @@
     if ((keyMask & M_META) == M_META) {
       modifier(b, KeyConstants.I.keyMeta());
     }
+    if ((keyMask & M_SHIFT) == M_SHIFT) {
+      modifier(b, KeyConstants.I.keyShift());
+    }
 
     final char c = (char) (keyMask & 0xffff);
     switch (c) {
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
index 56fb85c..b4cb41e 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.java
@@ -32,6 +32,7 @@
   String keyCtrl();
   String keyAlt();
   String keyMeta();
+  String keyShift();
   String keyEnter();
   String keyEsc();
 }
diff --git a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
index e21daf5..2e12b07 100644
--- a/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
+++ b/gerrit-gwtexpui/src/main/java/com/google/gwtexpui/globalkey/client/KeyConstants.properties
@@ -10,5 +10,6 @@
 keyCtrl = Ctrl
 keyAlt = Alt
 keyMeta = Meta
+keyShift = Shift
 keyEnter = Enter
 keyEsc = Esc
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
index 0a01adc..e0c0cd4 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySide2.java
@@ -69,7 +69,6 @@
 import com.google.gwtexpui.globalkey.client.ShowHelpCommand;
 
 import net.codemirror.lib.CodeMirror;
-import net.codemirror.lib.CodeMirror.EventHandler;
 import net.codemirror.lib.CodeMirror.GutterClickHandler;
 import net.codemirror.lib.CodeMirror.LineClassWhere;
 import net.codemirror.lib.CodeMirror.LineHandle;
@@ -111,7 +110,6 @@
 
   private CodeMirror cmA;
   private CodeMirror cmB;
-  private CodeMirror lastFocused;
   private ScrollSynchronizer scrollingGlue;
   private HandlerRegistration resizeHandler;
   private JsArray<CommentInfo> publishedBase;
@@ -279,17 +277,9 @@
     cm.on("focus", new Runnable() {
       @Override
       public void run() {
-        lastFocused = cm;
         updateActiveLine(cm).run();
       }
     });
-    cm.on("contextmenu", new EventHandler() {
-      @Override
-      public void handle(CodeMirror instance, NativeEvent event) {
-        CodeMirror.setObjectProperty(event, "codemirrorIgnore", true);
-        lastFocused.focus();
-      }
-    });
     cm.addKeyMap(KeyMap.create()
         .on("'a'", upToChange(true))
         .on("'u'", upToChange(false))
@@ -316,12 +306,30 @@
             (header.hasNext() ? header.next : header.up).go();
           }
         })
-        .on("Shift-Alt-/", new Runnable() {
+        .on("Shift-/", new Runnable() {
           @Override
           public void run() {
             new ShowHelpCommand().onKeyPress(null);
           }
         })
+        .on("Ctrl-F", new Runnable() {
+          @Override
+          public void run() {
+            CodeMirror.handleVimKey(cm, "/");
+          }
+        })
+        .on("Space", new Runnable() {
+          @Override
+          public void run() {
+            CodeMirror.handleVimKey(cm, "<PageDown>");
+          }
+        })
+        .on("Ctrl-A", new Runnable() {
+          @Override
+          public void run() {
+            cm.execCommand("selectAll");
+          }
+        })
         .on("N", maybeNextVimSearch(cm))
         .on("P", diffChunkNav(cm, true))
         .on("Shift-O", openClosePublished(cm))
@@ -336,9 +344,13 @@
     keysNavigation.add(new UpToChangeCommand2(revision, 0, 'u'));
     keysNavigation.add(new NoOpKeyCommand(0, 'j', PatchUtil.C.lineNext()));
     keysNavigation.add(new NoOpKeyCommand(0, 'k', PatchUtil.C.linePrev()));
+    keysNavigation.add(new NoOpKeyCommand(0, 'n', PatchUtil.C.chunkNext2()));
+    keysNavigation.add(new NoOpKeyCommand(0, 'p', PatchUtil.C.chunkPrev2()));
 
     keysAction = new KeyCommandSet(Gerrit.C.sectionActions());
     keysAction.add(new NoOpKeyCommand(0, 'o', PatchUtil.C.expandComment()));
+    keysAction.add(new NoOpKeyCommand(
+        KeyCommand.M_SHIFT, 'o', PatchUtil.C.expandAllCommentsOnCurrentLine()));
     keysAction.add(new KeyCommand(0, 'r', PatchUtil.C.toggleReviewed()) {
       @Override
       public void onKeyPress(KeyPressEvent event) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
index c6793d6..908801b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.java
@@ -45,10 +45,13 @@
   String lineNext();
   String chunkPrev();
   String chunkNext();
+  String chunkPrev2();
+  String chunkNext2();
   String commentPrev();
   String commentNext();
   String fileList();
   String expandComment();
+  String expandAllCommentsOnCurrentLine();
 
   String toggleReviewed();
   String markAsReviewedAndGoToNext();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
index 5259a4c..a1b6192 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/patches/PatchConstants.properties
@@ -27,10 +27,13 @@
 lineNext = Next line
 chunkPrev = Previous diff chunk or comment
 chunkNext = Next diff chunk or comment
+chunkPrev2 = Previous diff chunk
+chunkNext2 = Next diff chunk or search result
 commentPrev = Previous comment
 commentNext = Next comment
 fileList = Browse files in patch set
 expandComment = Expand or collapse comment
+expandAllCommentsOnCurrentLine = Expand or collapse all comments on current line
 
 toggleReviewed = Toggle the reviewed flag
 markAsReviewedAndGoToNext = Mark patch as reviewed and go to next unreviewed patch
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
index 96daa49..c5a047c 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/CodeMirror.java
@@ -277,11 +277,6 @@
     return this.display.scrollbarV;
   }-*/;
 
-  public static final native void setObjectProperty(JavaScriptObject obj,
-      String name, boolean value) /*-{
-    obj[name] = value;
-  }-*/;
-
   public static final native KeyMap cloneKeyMap(String name) /*-{
     var i = $wnd.CodeMirror.keyMap[name];
     var o = {};
@@ -291,6 +286,10 @@
     return o;
   }-*/;
 
+  public final native void execCommand(String cmd) /*-{
+    this.execCommand(cmd);
+  }-*/;
+
   public static final native void addKeyMap(String name, KeyMap km) /*-{
     $wnd.CodeMirror.keyMap[name] = km;
   }-*/;
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
index 46e7a71..d2954ba 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/lib/Loader.java
@@ -90,7 +90,8 @@
   private static void initVimKeys() {
     // TODO: Better custom keybindings, remove temporary navigation hacks.
     KeyMap km = CodeMirror.cloneKeyMap("vim");
-    for (String s : new String[] {"A", "C", "O", "R", "U", "Ctrl-C"}) {
+    for (String s : new String[] {
+        "A", "C", "O", "R", "U", "Ctrl-C", "Ctrl-O"}) {
       km.remove(s);
     }
     CodeMirror.addKeyMap("vim_ro", km);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
index 96a7b7e..1798f5a 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/common/ChangeHookRunner.java
@@ -582,6 +582,8 @@
 
       final List<String> args = new ArrayList<String>();
       addArg(args, "--change", event.change.id);
+      addArg(args, "--project", event.change.project);
+      addArg(args, "--branch", event.change.branch);
       addArg(args, "--changer", getDisplayName(account));
       addArg(args, "--old-topic", oldTopic);
       addArg(args, "--new-topic", event.change.topic);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_74.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_74.java
index f884c73..4a2c477 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_74.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_74.java
@@ -45,7 +45,7 @@
     // Grab all the groups since we don't have the cache available
     HashMap<AccountGroup.Id, AccountGroup.UUID> allGroups =
         new HashMap<AccountGroup.Id, AccountGroup.UUID>();
-    for( AccountGroup ag : db.accountGroups().all() ) {
+    for (AccountGroup ag : db.accountGroups().all()) {
       allGroups.put(ag.getId(), ag.getGroupUUID());
     }
 
@@ -58,54 +58,58 @@
 
     // Iterate over all entries in account_group_includes
     Statement oldGroupIncludesStmt = conn.createStatement();
-    ResultSet oldGroupIncludes = oldGroupIncludesStmt.
-        executeQuery("SELECT * FROM account_group_includes");
-    while (oldGroupIncludes.next()) {
-      AccountGroup.Id oldGroupId =
-          new AccountGroup.Id(oldGroupIncludes.getInt("group_id"));
-      AccountGroup.Id oldIncludeId =
-          new AccountGroup.Id(oldGroupIncludes.getInt("include_id"));
-      AccountGroup.UUID uuidFromIncludeId = allGroups.get(oldIncludeId);
+    try {
+      ResultSet oldGroupIncludes = oldGroupIncludesStmt.
+          executeQuery("SELECT * FROM account_group_includes");
+      while (oldGroupIncludes.next()) {
+        AccountGroup.Id oldGroupId =
+            new AccountGroup.Id(oldGroupIncludes.getInt("group_id"));
+        AccountGroup.Id oldIncludeId =
+            new AccountGroup.Id(oldGroupIncludes.getInt("include_id"));
+        AccountGroup.UUID uuidFromIncludeId = allGroups.get(oldIncludeId);
 
-      // If we've got an include, but the group no longer exists, don't bother converting
-      if (uuidFromIncludeId == null) {
-        ui.message("Skipping group_id = \"" + oldIncludeId.get() +
-            "\", not a current group");
-        continue;
-      }
-
-      // Create the new include entry
-      AccountGroupById destIncludeEntry = new AccountGroupById(
-          new AccountGroupById.Key(oldGroupId, uuidFromIncludeId));
-
-      // Iterate over all the audits (for this group)
-      PreparedStatement oldAuditsQuery = conn.prepareStatement(
-          "SELECT * FROM account_group_includes_audit WHERE group_id=? AND include_id=?");
-      oldAuditsQuery.setInt(1, oldGroupId.get());
-      oldAuditsQuery.setInt(2, oldIncludeId.get());
-      ResultSet oldGroupIncludeAudits = oldAuditsQuery.executeQuery();
-      while (oldGroupIncludeAudits.next()) {
-        Account.Id addedBy = new Account.Id(oldGroupIncludeAudits.getInt("added_by"));
-        int removedBy = oldGroupIncludeAudits.getInt("removed_by");
-
-        // Create the new audit entry
-        AccountGroupByIdAud destAuditEntry =
-            new AccountGroupByIdAud(destIncludeEntry, addedBy,
-                oldGroupIncludeAudits.getTimestamp("added_on"));
-
-        // If this was a "removed on" entry, note that
-        if (removedBy > 0) {
-          destAuditEntry.removed(new Account.Id(removedBy),
-              oldGroupIncludeAudits.getTimestamp("removed_on"));
+        // If we've got an include, but the group no longer exists, don't bother converting
+        if (uuidFromIncludeId == null) {
+          ui.message("Skipping group_id = \"" + oldIncludeId.get() +
+              "\", not a current group");
+          continue;
         }
-        newIncludeAudits.add(destAuditEntry);
+
+        // Create the new include entry
+        AccountGroupById destIncludeEntry = new AccountGroupById(
+            new AccountGroupById.Key(oldGroupId, uuidFromIncludeId));
+
+        // Iterate over all the audits (for this group)
+        PreparedStatement oldAuditsQueryStmt = conn.prepareStatement(
+            "SELECT * FROM account_group_includes_audit WHERE group_id=? AND include_id=?");
+        try {
+          oldAuditsQueryStmt.setInt(1, oldGroupId.get());
+          oldAuditsQueryStmt.setInt(2, oldIncludeId.get());
+          ResultSet oldGroupIncludeAudits = oldAuditsQueryStmt.executeQuery();
+          while (oldGroupIncludeAudits.next()) {
+            Account.Id addedBy = new Account.Id(oldGroupIncludeAudits.getInt("added_by"));
+            int removedBy = oldGroupIncludeAudits.getInt("removed_by");
+
+            // Create the new audit entry
+            AccountGroupByIdAud destAuditEntry =
+                new AccountGroupByIdAud(destIncludeEntry, addedBy,
+                    oldGroupIncludeAudits.getTimestamp("added_on"));
+
+            // If this was a "removed on" entry, note that
+            if (removedBy > 0) {
+              destAuditEntry.removed(new Account.Id(removedBy),
+                  oldGroupIncludeAudits.getTimestamp("removed_on"));
+            }
+            newIncludeAudits.add(destAuditEntry);
+          }
+          newIncludes.add(destIncludeEntry);
+        } finally {
+          oldAuditsQueryStmt.close();
+        }
       }
-      newIncludes.add(destIncludeEntry);
-      oldAuditsQuery.close();
-      oldGroupIncludeAudits.close();
+    } finally {
+      oldGroupIncludesStmt.close();
     }
-    oldGroupIncludes.close();
-    oldGroupIncludesStmt.close();
 
     // Now insert all of the new entries to the database
     db.accountGroupById().insert(newIncludes);