Merge "Add REST API to toggle starred change state" into stable-2.8
diff --git a/Documentation/access-control.txt b/Documentation/access-control.txt
index db806cc..189316c 100644
--- a/Documentation/access-control.txt
+++ b/Documentation/access-control.txt
@@ -822,6 +822,10 @@
 can always edit the topic name (even without having the `Edit Topic Name`
 access right assigned).
 
+Whether the topic can be edited on closed changes can be controlled
+by the 'Force Edit' flag. If this flag is not set the topic can only be
+edited on open changes.
+
 
 Examples of typical roles in a project
 --------------------------------------
diff --git a/Documentation/config-hooks.txt b/Documentation/config-hooks.txt
index e232c0c..5875837 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/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index d0e361a..717547b 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -650,16 +650,17 @@
 UI Extension
 ------------
 
-Plugins can contribute their own UI actions on core Gerrit pages.
-This is useful for workflow customization or exposing plugin functionality
-through the UI in addition to SSH commands and the REST API.
+Plugins can contribute UI actions on core Gerrit pages. This is useful
+for workflow customization or exposing plugin functionality through the
+UI in addition to SSH commands and the REST API.
 
-For instance a plugin to integrate Jira with Gerrit changes may contribute its
-own "File bug" button to allow filing a bug from the change page or plugins to
-integrate continuous integration systems may contribute a "Schedule" button to
-allow a CI build to be scheduled manually from the patch set panel.
+For instance a plugin to integrate Jira with Gerrit changes may
+contribute a "File bug" button to allow filing a bug from the change
+page or plugins to integrate continuous integration systems may
+contribute a "Schedule" button to allow a CI build to be scheduled
+manually from the patch set panel.
 
-Two different places on core Gerrit pages are currently supported:
+Two different places on core Gerrit pages are supported:
 
 * Change screen
 * Project info screen
@@ -768,7 +769,8 @@
 }
 ----
 
-The module above must be declared in pom.xml for Maven driven plugins:
+The module above must be declared in the `pom.xml` for Maven driven
+plugins:
 
 [source,xml]
 ----
@@ -777,7 +779,7 @@
 </manifestEntries>
 ----
 
-or in the BUCK configuration file for Buck driven plugins:
+or in the `BUCK` configuration file for Buck driven plugins:
 
 [source,python]
 ----
@@ -832,7 +834,8 @@
 }
 ----
 
-The HTTP module above must be declared in pom.xml for Maven driven plugins:
+The HTTP module above must be declared in the `pom.xml` for Maven
+driven plugins:
 
 [source,xml]
 ----
@@ -841,7 +844,7 @@
 </manifestEntries>
 ----
 
-or in the BUCK configuration file for Buck driven plugins
+or in the `BUCK` configuration file for Buck driven plugins
 
 [source,python]
 ----
@@ -858,7 +861,7 @@
 The following prerequisities must be met, to satisfy the capability check:
 
 * user is authenticated
-* user is a member of the Administrators group, or
+* user is a member of a group which has the `Administrate Server` capability, or
 * user is a member of a group which has the required capability
 
 The `apply` method is called when the button is clicked. If `UiAction` is
@@ -878,7 +881,7 @@
 ====
 
 A special case is to bind an endpoint without a view name.  This is
-particularly useful for DELETE requests:
+particularly useful for `DELETE` requests:
 
 [source,java]
 ----
diff --git a/Documentation/index.txt b/Documentation/index.txt
index 9b8acc4..58dae6e 100644
--- a/Documentation/index.txt
+++ b/Documentation/index.txt
@@ -60,7 +60,7 @@
 ... How to read stats from the JVM
 .. High availability
 .. Replication
-.. Plugins
+.. link:https://gerrit-review.googlesource.com/#/admin/projects/?filter=plugins%252F[Plugins]
 .. link:dev-design.html[System Design]
 .. link:config-contact.html[User Contact Information]
 .. link:config-reverseproxy.html[Reverse Proxy]
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 4f25aa8..5bd5e0c 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -181,7 +181,7 @@
 ~~~~~~~~~~~~~~
 The `CapabilityInfo` entity contains information about a capability.
 
-[options="header",width="50%",cols="1,5"]
+[options="header",width="50%",cols="1,6"]
 |=================================
 |Field Name           |Description
 |`kind`               |`gerritcodereview#capability`
@@ -195,7 +195,7 @@
 The `TopMenuEntryInfo` entity contains information about a top menu
 entry.
 
-[options="header",width="50%",cols="1,5"]
+[options="header",width="50%",cols="1,6"]
 |=================================
 |Field Name           |Description
 |`name`               |Name of the top menu entry.
@@ -208,13 +208,14 @@
 The `TopMenuItemInfo` entity contains information about a menu item in
 a top menu entry.
 
-[options="header",width="50%",cols="1,5"]
-|=================================
-|Field Name           |Description
-|`url`                |The URL of the menu item link.
-|`name`               |The name of the menu item.
-|`target`             |Target attribute of the menu item link.
-|=================================
+[options="header",width="50%",cols="1,^1,5"]
+|========================
+|Field Name ||Description
+|`url`      ||The URL of the menu item link.
+|`name`     ||The name of the menu item.
+|`target`   ||Target attribute of the menu item link.
+|`id`       |optional|The `id` attribute of the menu item link.
+|========================
 
 GERRIT
 ------
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 6b6a18e..cb946d6 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -7,6 +7,7 @@
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
     '//gerrit-launcher:launcher',
+    '//gerrit-lucene:lucene',
     '//gerrit-httpd:httpd',
     '//gerrit-pgm:init-base',
     '//gerrit-pgm:pgm',
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
index 7389519fc..e5a1f7e 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/webui/TopMenu.java
@@ -38,15 +38,21 @@
     public final String url;
     public final String name;
     public final String target;
+    public final String id;
 
     public MenuItem(String name, String url) {
       this(name, url, "_blank");
     }
 
     public MenuItem(String name, String url, String target) {
+      this(name, url, target, null);
+    }
+
+    public MenuItem(String name, String url, String target, String id) {
       this.url = url;
       this.name = name;
       this.target = target;
+      this.id = id;
     }
   }
 
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/Gerrit.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/Gerrit.java
index e73670f..568a15c 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
@@ -978,6 +978,9 @@
   private static void addExtensionLink(final LinkMenuBar m, final TopMenuItem item) {
     final Anchor atag = anchor(item.getName(), item.getUrl());
     atag.setTarget(item.getTarget());
+    if (item.getId() != null) {
+      atag.getElement().setAttribute("id", item.getId());
+    }
     m.add(atag);
   }
 }
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/extensions/TopMenuItem.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
index 7906fd0..22bb981 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/extensions/TopMenuItem.java
@@ -20,6 +20,7 @@
   public final native String getName() /*-{ return this.name; }-*/;
   public final native String getUrl() /*-{ return this.url; }-*/;
   public final native String getTarget() /*-{ return this.target; }-*/;
+  public final native String getId() /*-{ return this.id; }-*/;
 
   protected TopMenuItem() {
   }
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-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index 53abc4c..b2972d9 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -81,6 +81,8 @@
   private static final long serialVersionUID = 1L;
   private static final Logger log
       = LoggerFactory.getLogger(HttpPluginServlet.class);
+  private static final String PLUGINS_PREFIX = "/plugins/";
+  private static final String AUTHORIZED_PREFIX = "/a" + PLUGINS_PREFIX;
 
   private final MimeUtilFileTypeRegistry mimeUtil;
   private final Provider<String> webUrl;
@@ -91,6 +93,7 @@
 
   private List<Plugin> pending = Lists.newArrayList();
   private String base;
+  private String authorizedBase;
   private final ConcurrentMap<String, PluginHolder> plugins
       = Maps.newConcurrentMap();
 
@@ -129,7 +132,8 @@
     super.init(config);
 
     String path = config.getServletContext().getContextPath();
-    base = Strings.nullToEmpty(path) + "/plugins/";
+    base = Strings.nullToEmpty(path) + PLUGINS_PREFIX;
+    authorizedBase = Strings.nullToEmpty(path) + AUTHORIZED_PREFIX;
     for (Plugin plugin : pending) {
       install(plugin);
     }
@@ -213,7 +217,8 @@
       return;
     }
 
-    WrappedRequest wr = new WrappedRequest(req, base + name);
+    WrappedRequest wr = new WrappedRequest(req,
+        (isAuthorizedCall(req) ? authorizedBase : base) + name);
     FilterChain chain = new FilterChain() {
       @Override
       public void doFilter(ServletRequest req, ServletResponse res)
@@ -228,6 +233,11 @@
     }
   }
 
+  private boolean isAuthorizedCall(HttpServletRequest req) {
+    return !Strings.isNullOrEmpty(req.getServletPath())
+        && req.getServletPath().startsWith(AUTHORIZED_PREFIX);
+  }
+
   private static boolean isApiCall(HttpServletRequest req, List<String> parts) {
     String method = req.getMethod();
     int cnt = parts.size();
diff --git a/gerrit-plugin-gwtui/pom.xml b/gerrit-plugin-gwtui/pom.xml
new file mode 100644
index 0000000..3c9e2ca
--- /dev/null
+++ b/gerrit-plugin-gwtui/pom.xml
@@ -0,0 +1,161 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>com.google.gerrit</groupId>
+  <artifactId>gerrit-plugin-gwtui</artifactId>
+  <version>2.8-SNAPSHOT</version>
+  <name>Gerrit Code Review - Plugin GWT UI</name>
+
+  <description>
+    API for UI plugins to build with GWT and integrate with Gerrit
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-user</artifactId>
+      <version>2.5.1</version>
+    </dependency>
+
+    <dependency>
+      <groupId>com.google.gwt</groupId>
+      <artifactId>gwt-dev</artifactId>
+      <version>2.5.1</version>
+    </dependency>
+  </dependencies>
+
+   <build>
+     <pluginManagement>
+       <plugins>
+         <plugin>
+           <groupId>org.eclipse.m2e</groupId>
+           <artifactId>lifecycle-mapping</artifactId>
+           <version>1.0.0</version>
+           <configuration>
+             <lifecycleMappingMetadata>
+               <pluginExecutions>
+                 <pluginExecution>
+                   <pluginExecutionFilter>
+                     <groupId>org.codehaus.mojo</groupId>
+                     <artifactId>gwt-maven-plugin</artifactId>
+                     <versionRange>[2.5.0,)</versionRange>
+                     <goals>
+                       <goal>resources</goal>
+                       <goal>compile</goal>
+                       <goal>i18n</goal>
+                       <goal>generateAsync</goal>
+                     </goals>
+                   </pluginExecutionFilter>
+                   <action>
+                     <execute />
+                   </action>
+                 </pluginExecution>
+                 <pluginExecution>
+                   <pluginExecutionFilter>
+                     <groupId>org.apache.maven.plugins</groupId>
+                     <artifactId>maven-war-plugin</artifactId>
+                     <versionRange>[2.1.1,)</versionRange>
+                     <goals>
+                       <goal>exploded</goal>
+                     </goals>
+                   </pluginExecutionFilter>
+                   <action>
+                     <execute />
+                   </action>
+                 </pluginExecution>
+               </pluginExecutions>
+             </lifecycleMappingMetadata>
+          </configuration>
+        </plugin>
+      </plugins>
+    </pluginManagement>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-source-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.codehaus.mojo</groupId>
+        <artifactId>gwt-maven-plugin</artifactId>
+        <configuration>
+          <module>com.google.gerrit.Plugin</module>
+          <disableClassMetadata>true</disableClassMetadata>
+          <disableCastChecking>true</disableCastChecking>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>resources</goal>
+              <goal>compile</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-antrun-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>unpack-sources</id>
+            <phase>package</phase>
+            <configuration>
+              <tasks>
+                <unzip src="${project.build.directory}/${project.artifactId}-${project.version}-sources.jar" dest="${project.build.directory}/unpack_sources" />
+              </tasks>
+            </configuration>
+            <goals>
+              <goal>run</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-javadoc-plugin</artifactId>
+        <configuration>
+          <sourcepath>${project.build.directory}/unpack_sources</sourcepath>
+          <encoding>ISO-8859-1</encoding>
+          <quiet>true</quiet>
+          <detectOfflineLinks>false</detectOfflineLinks>
+        </configuration>
+        <executions>
+          <execution>
+            <goals>
+              <goal>jar</goal>
+            </goals>
+            <phase>package</phase>
+          </execution>
+        </executions>
+      </plugin>
+
+    </plugins>
+  </build>
+</project>
+
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/change/Files.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
index ec8234f..98e2ee9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Files.java
@@ -37,7 +37,6 @@
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.patch.PatchList;
 import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
@@ -45,9 +44,9 @@
 
 import org.eclipse.jgit.lib.ObjectReader;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevTree;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -219,23 +218,49 @@
           List<String> pathList = Lists.newArrayListWithCapacity(sz);
 
           RevWalk rw = new RevWalk(reader);
-          RevTree o = rw.parseCommit(oldList.getNewId()).getTree();
-          RevTree c = rw.parseCommit(curList.getNewId()).getTree();
-          for (PatchListEntry p : curList.getPatches()) {
-            String path = p.getNewName();
-            if (!Patch.COMMIT_MSG.equals(path) && paths.contains(path)) {
-              TreeWalk tw = TreeWalk.forPath(reader, path, o, c);
-              if (tw != null
-                  && tw.getRawMode(0) != 0
-                  && tw.getRawMode(1) != 0
-                  && tw.idEqual(0, 1)) {
-                inserts.add(new AccountPatchReview(
-                    new Patch.Key(
-                        resource.getPatchSet().getId(),
-                        path),
-                      userId));
-                pathList.add(path);
-              }
+          TreeWalk tw = new TreeWalk(reader);
+          tw.setFilter(PathFilterGroup.createFromStrings(paths));
+          tw.setRecursive(true);
+          int o = tw.addTree(rw.parseCommit(oldList.getNewId()).getTree());
+          int c = tw.addTree(rw.parseCommit(curList.getNewId()).getTree());
+
+          int op = -1;
+          if (oldList.getOldId() != null) {
+            op = tw.addTree(rw.parseCommit(oldList.getOldId()).getTree());
+          }
+
+          int cp = -1;
+          if (curList.getOldId() != null) {
+            cp = tw.addTree(rw.parseCommit(curList.getOldId()).getTree());
+          }
+
+          while (tw.next()) {
+            String path = tw.getPathString();
+            if (tw.getRawMode(o) != 0 && tw.getRawMode(c) != 0
+                && tw.idEqual(o, c)
+                && paths.contains(path)) {
+              // File exists in previously reviewed oldList and in curList.
+              // File content is identical.
+              inserts.add(new AccountPatchReview(
+                  new Patch.Key(
+                      resource.getPatchSet().getId(),
+                      path),
+                    userId));
+              pathList.add(path);
+            } else if (op >= 0 && cp >= 0
+                && tw.getRawMode(o) == 0 && tw.getRawMode(c) == 0
+                && tw.getRawMode(op) != 0 && tw.getRawMode(cp) != 0
+                && tw.idEqual(op, cp)
+                && paths.contains(path)) {
+              // File was deleted in previously reviewed oldList and curList.
+              // File exists in ancestor of oldList and curList.
+              // File content is identical in ancestors.
+              inserts.add(new AccountPatchReview(
+                  new Patch.Key(
+                      resource.getPatchSet().getId(),
+                      path),
+                    userId));
+              pathList.add(path);
             }
           }
           db.get().accountPatchReviews().insert(inserts);
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);