Merge "Merge branch 'stable-2.14'"
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 75ef9e6..6c9b601 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -1209,6 +1209,7 @@
   @Override
   public void onPluginLoad() {
     Plugin.get().panel(GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        "my_panel_name",
         new Panel.EntryPoint() {
           @Override
           public void onLoad(Panel panel) {
@@ -1220,6 +1221,23 @@
 }
 ----
 
+Change Screen panel ordering may be specified in the
+project config. Values may be either "plugin name" or
+"plugin name"."panel name".
+Panels not specified in the config will be added
+to the end in load order. Panels specified in the config that
+are not found will be ignored.
+
+Example config:
+----
+[extension-panels "CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK"]
+        panel = helloworld.change_id
+        panel = myotherplugin
+        panel = myplugin.my_panel_name
+----
+
+
+
 [[actions]]
 === Actions
 
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
index f6f9811..e30a730 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ConfigInfo.java
@@ -41,6 +41,8 @@
   public Map<String, CommentLinkInfo> commentlinks;
   public ThemeInfo theme;
 
+  public Map<String, List<String>> extensionPanelNames;
+
   public static class InheritedBooleanInfo {
     public Boolean value;
     public InheritableBoolean configuredValue;
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 410ba3e..17ad4f6 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
@@ -558,7 +558,10 @@
       builder.setPort(Integer.parseInt(port));
     }
     String[] tokens = token.split("@", 2);
-    builder.setPath(tokens[0]);
+    if (Location.getPath().endsWith("/") && tokens[0].startsWith("/")) {
+      tokens[0] = tokens[0].substring(1);
+    }
+    builder.setPath(Location.getPath() + tokens[0]);
     if (tokens.length == 2) {
       builder.setHash(tokens[1]);
     }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
index 1555f56..294fa9b 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ApiGlue.java
@@ -100,9 +100,9 @@
         var s = new SettingsScreenDefinition(p,m,c);
         (this.settingsScreens[n] || (this.settingsScreens[n]=[])).push(s);
       },
-      panel: function(i,c){this._panel(this.getPluginName(),i,c)},
-      _panel: function(n,i,c){
-        var p = new PanelDefinition(n,c);
+      panel: function(i,c,n){this._panel(this.getPluginName(),i,c,n)},
+      _panel: function(n,i,c,x){
+        var p = new PanelDefinition(n,c,x);
         (this.panels[i] || (this.panels[i]=[])).push(p);
       },
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
index 0873363..6d3dd60 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/ExtensionPanel.java
@@ -22,7 +22,10 @@
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.SimplePanel;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
@@ -32,13 +35,17 @@
   private final List<Context> contexts;
 
   public ExtensionPanel(GerritUiExtensionPoint extensionPoint) {
-    this.extensionPoint = extensionPoint;
-    this.contexts = create();
+    this(extensionPoint, new ArrayList<String>());
   }
 
-  private List<Context> create() {
+  public ExtensionPanel(GerritUiExtensionPoint extensionPoint, List<String> panelNames) {
+    this.extensionPoint = extensionPoint;
+    this.contexts = create(panelNames);
+  }
+
+  private List<Context> create(List<String> panelNames) {
     List<Context> contexts = new ArrayList<>();
-    for (Definition def : Natives.asList(Definition.get(extensionPoint.name()))) {
+    for (Definition def : getOrderedDefs(panelNames)) {
       SimplePanel p = new SimplePanel();
       add(p);
       contexts.add(Context.create(def, p));
@@ -46,6 +53,42 @@
     return contexts;
   }
 
+  private List<Definition> getOrderedDefs(List<String> panelNames) {
+    if (panelNames == null) {
+      panelNames = Collections.emptyList();
+    }
+    Map<String, List<Definition>> defsOrderedByName = new LinkedHashMap<>();
+    for (String name : panelNames) {
+      defsOrderedByName.put(name, new ArrayList<Definition>());
+    }
+    for (Definition def : Natives.asList(Definition.get(extensionPoint.name()))) {
+      addDef(def, defsOrderedByName);
+    }
+    List<Definition> orderedDefs = new ArrayList<>();
+    for (List<Definition> defList : defsOrderedByName.values()) {
+      orderedDefs.addAll(defList);
+    }
+    return orderedDefs;
+  }
+
+  private static void addDef(Definition def, Map<String, List<Definition>> defsOrderedByName) {
+    String panelName = def.getPanelName();
+    if (panelName.equals(def.getPluginName() + ".undefined")) {
+      /* Handle a partially undefined panel name from the
+      javascript layer by generating a random panel name.
+      This maintains support for panels that do not provide a name. */
+      panelName =
+          def.getPluginName() + "." + Long.toHexString(Double.doubleToLongBits(Math.random()));
+    }
+    if (defsOrderedByName.containsKey(panelName)) {
+      defsOrderedByName.get(panelName).add(def);
+    } else if (defsOrderedByName.containsKey(def.getPluginName())) {
+      defsOrderedByName.get(def.getPluginName()).add(def);
+    } else {
+      defsOrderedByName.put(panelName, Collections.singletonList(def));
+    }
+  }
+
   public void put(GerritUiExtensionPoint.Key key, String value) {
     for (Context ctx : contexts) {
       ctx.put(key.name(), value);
@@ -103,9 +146,10 @@
     static final JavaScriptObject TYPE = init();
 
     private static native JavaScriptObject init() /*-{
-      function PanelDefinition(n, c) {
+      function PanelDefinition(n, c, x) {
         this.pluginName = n;
         this.onLoad = c;
+        this.name = x;
       };
       return PanelDefinition;
     }-*/;
@@ -113,6 +157,10 @@
     static native JsArray<Definition> get(String i) /*-{ return $wnd.Gerrit.panels[i] || [] }-*/;
 
     protected Definition() {}
+
+    public final native String getPanelName() /*-{ return this.pluginName + "." + this.name; }-*/;
+
+    public final native String getPluginName() /*-{ return this.pluginName; }-*/;
   }
 
   static class Context extends JavaScriptObject {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
index 29787b8..48a812c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/api/Plugin.java
@@ -68,7 +68,7 @@
       onAction: function(t,n,c){G._onAction(this.name,t,n,c)},
       screen: function(p,c){G._screen(this.name,p,c)},
       settingsScreen: function(p,m,c){G._settingsScreen(this.name,p,m,c)},
-      panel: function(i,c){G._panel(this.name,i,c)},
+      panel: function(i,c,n){G._panel(this.name,i,c,n)},
 
       url: function (u){return G.url(this._url(u))},
       get: function(u,b){@com.google.gerrit.client.api.ActionContext::get(
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
index bb94ee3..461864a 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/ChangeScreen.java
@@ -309,8 +309,7 @@
               @Override
               public void onSuccess(final ChangeInfo info) {
                 info.init();
-                addExtensionPoints(info, initCurrentRevision(info));
-
+                initCurrentRevision(info);
                 final RevisionInfo rev = info.revision(revision);
                 CallbackGroup group = new CallbackGroup();
                 loadCommit(rev, group);
@@ -379,7 +378,8 @@
     return resolveRevisionToDisplay(info);
   }
 
-  private void addExtensionPoints(ChangeInfo change, RevisionInfo rev) {
+  private void addExtensionPoints(ChangeInfo change, RevisionInfo rev,
+      Entry result) {
     addExtensionPoint(GerritUiExtensionPoint.CHANGE_SCREEN_HEADER, headerExtension, change, rev);
     addExtensionPoint(
         GerritUiExtensionPoint.CHANGE_SCREEN_HEADER_RIGHT_OF_BUTTONS,
@@ -392,7 +392,10 @@
         change,
         rev);
     addExtensionPoint(
-        GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK, changeExtension, change, rev);
+        GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK,
+        changeExtension, change, rev,
+        result.getExtensionPanelNames(
+            GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_CHANGE_INFO_BLOCK.toString()));
     addExtensionPoint(
         GerritUiExtensionPoint.CHANGE_SCREEN_BELOW_RELATED_INFO_BLOCK,
         relatedExtension,
@@ -408,13 +411,19 @@
   }
 
   private void addExtensionPoint(
-      GerritUiExtensionPoint extensionPoint, Panel p, ChangeInfo change, RevisionInfo rev) {
-    ExtensionPanel extensionPanel = new ExtensionPanel(extensionPoint);
+      GerritUiExtensionPoint extensionPoint, Panel p, ChangeInfo change,
+      RevisionInfo rev, List<String> panelNames) {
+    ExtensionPanel extensionPanel = new ExtensionPanel(extensionPoint, panelNames);
     extensionPanel.putObject(GerritUiExtensionPoint.Key.CHANGE_INFO, change);
     extensionPanel.putObject(GerritUiExtensionPoint.Key.REVISION_INFO, rev);
     p.add(extensionPanel);
   }
 
+  private void addExtensionPoint(
+      GerritUiExtensionPoint extensionPoint, Panel p, ChangeInfo change, RevisionInfo rev) {
+    addExtensionPoint(extensionPoint, p, change, rev, Collections.emptyList());
+  }
+
   private boolean enableSignedPush() {
     return Gerrit.info().receive().enableSignedPush();
   }
@@ -1031,6 +1040,14 @@
             loadRevisionInfo();
           }
         });
+    ConfigInfoCache.get(
+        info.projectNameKey(),
+        new GerritCallback<Entry>() {
+          @Override
+          public void onSuccess(Entry entry) {
+            addExtensionPoints(info, rev, entry);
+          }
+        });
   }
 
   private void updateToken(ChangeInfo info, DiffObject base, RevisionInfo rev) {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
index 4eda46b..32f70a7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfo.java
@@ -116,6 +116,8 @@
 
   final native ThemeInfo theme() /*-{ return this.theme; }-*/;
 
+  final native NativeMap<JsArrayString> extensionPanelNames() /*-{ return this.extension_panel_names; }-*/;
+
   protected ConfigInfo() {}
 
   static class CommentLinkInfo extends JavaScriptObject {
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
index e41cf120..7182b78 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ConfigInfoCache.java
@@ -16,12 +16,14 @@
 
 import com.google.gerrit.client.changes.ChangeApi;
 import com.google.gerrit.client.info.ChangeInfo;
+import com.google.gerrit.client.rpc.Natives;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gwt.core.client.GWT;
 import com.google.gwt.user.client.rpc.AsyncCallback;
 import java.util.LinkedHashMap;
+import java.util.List;
 import java.util.Map;
 
 /** Cache of {@link ConfigInfo} objects by project name. */
@@ -48,6 +50,10 @@
     public ThemeInfo getTheme() {
       return info.theme();
     }
+
+    public List<String> getExtensionPanelNames(String extensionPoint) {
+      return Natives.asList(info.extensionPanelNames().get(extensionPoint));
+    }
   }
 
   public static void get(Project.NameKey name, AsyncCallback<Entry> cb) {
diff --git a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
index 7c478c1..a91687a 100644
--- a/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
+++ b/gerrit-plugin-gwtui/src/main/java/com/google/gerrit/plugin/client/Plugin.java
@@ -121,12 +121,16 @@
    *
    * @param extensionPoint the UI extension point for which the panel should be registered.
    * @param entry callback function invoked to create the panel widgets.
+   * @param name the name of the panel which can be used to specify panel
+   *        ordering via project config
    */
-  public void panel(GerritUiExtensionPoint extensionPoint, Panel.EntryPoint entry) {
-    panel(extensionPoint.name(), wrap(entry));
+  public final void panel(GerritUiExtensionPoint extensionPoint,
+      Panel.EntryPoint entry, String name) {
+    panel(extensionPoint.name(), wrap(entry), name);
   }
 
-  private native void panel(String i, JavaScriptObject e) /*-{ this.panel(i, e) }-*/;
+  private native void panel(String i, JavaScriptObject e, String n)
+  /*-{ this.panel(i, e, n) }-*/;
 
   protected Plugin() {}
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
index cb65ed3..032d7bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalCopier.java
@@ -43,6 +43,7 @@
 import java.util.TreeMap;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
 
 /**
  * Copies approvals between patch sets.
@@ -140,7 +141,8 @@
 
       TreeMap<Integer, PatchSet> patchSets = getPatchSets(cd);
 
-      try (Repository repo = repoManager.openRepository(project.getProject().getNameKey())) {
+      try (Repository repo = repoManager.openRepository(project.getProject().getNameKey());
+          RevWalk rw = new RevWalk(repo)) {
         // Walk patch sets strictly less than current in descending order.
         Collection<PatchSet> allPrior =
             patchSets.descendingMap().tailMap(ps.getId().get(), false).values();
@@ -154,6 +156,7 @@
               changeKindCache.getChangeKind(
                   project.getProject().getNameKey(),
                   repo,
+                  rw,
                   ObjectId.fromString(priorPs.getRevision().get()),
                   ObjectId.fromString(ps.getRevision().get()));
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
index 8321c4e..50a4167 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeInserter.java
@@ -75,6 +75,7 @@
 import java.util.concurrent.Future;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.eclipse.jgit.util.ChangeIdUtil;
 import org.slf4j.Logger;
@@ -82,7 +83,7 @@
 
 public class ChangeInserter implements InsertChangeOp {
   public interface Factory {
-    ChangeInserter create(Change.Id cid, RevCommit rc, String refName);
+    ChangeInserter create(Change.Id cid, ObjectId commitId, String refName);
   }
 
   private static final Logger log = LoggerFactory.getLogger(ChangeInserter.class);
@@ -102,7 +103,7 @@
 
   private final Change.Id changeId;
   private final PatchSet.Id psId;
-  private final RevCommit commit;
+  private final ObjectId commitId;
   private final String refName;
 
   // Fields exposed as setters.
@@ -146,7 +147,7 @@
       CommentAdded commentAdded,
       RevisionCreated revisionCreated,
       @Assisted Change.Id changeId,
-      @Assisted RevCommit commit,
+      @Assisted ObjectId commitId,
       @Assisted String refName) {
     this.projectControlFactory = projectControlFactory;
     this.userFactory = userFactory;
@@ -163,7 +164,7 @@
 
     this.changeId = changeId;
     this.psId = new PatchSet.Id(changeId, INITIAL_PATCH_SET_ID);
-    this.commit = commit;
+    this.commitId = commitId.copy();
     this.refName = refName;
     this.reviewers = Collections.emptySet();
     this.extraCC = Collections.emptySet();
@@ -175,10 +176,10 @@
   }
 
   @Override
-  public Change createChange(Context ctx) {
+  public Change createChange(Context ctx) throws IOException {
     change =
         new Change(
-            getChangeKey(commit),
+            getChangeKey(ctx.getRevWalk(), commitId),
             changeId,
             ctx.getAccountId(),
             new Branch.NameKey(ctx.getProject(), refName),
@@ -189,29 +190,31 @@
     return change;
   }
 
-  private static Change.Key getChangeKey(RevCommit commit) {
+  private static Change.Key getChangeKey(RevWalk rw, ObjectId id) throws IOException {
+    RevCommit commit = rw.parseCommit(id);
+    rw.parseBody(commit);
     List<String> idList = commit.getFooterLines(FooterConstants.CHANGE_ID);
     if (!idList.isEmpty()) {
       return new Change.Key(idList.get(idList.size() - 1).trim());
     }
-    ObjectId id =
+    ObjectId changeId =
         ChangeIdUtil.computeChangeId(
             commit.getTree(),
             commit,
             commit.getAuthorIdent(),
             commit.getCommitterIdent(),
             commit.getShortMessage());
-    StringBuilder changeId = new StringBuilder();
-    changeId.append("I").append(ObjectId.toString(id));
-    return new Change.Key(changeId.toString());
+    StringBuilder changeIdStr = new StringBuilder();
+    changeIdStr.append("I").append(ObjectId.toString(changeId));
+    return new Change.Key(changeIdStr.toString());
   }
 
   public PatchSet.Id getPatchSetId() {
     return psId;
   }
 
-  public RevCommit getCommit() {
-    return commit;
+  public ObjectId getCommitId() {
+    return commitId;
   }
 
   public Change getChange() {
@@ -338,7 +341,7 @@
       return;
     }
     if (updateRefCommand == null) {
-      ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), commit, psId.toRefName()));
+      ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), commitId, psId.toRefName()));
     } else {
       ctx.addRefUpdate(updateRefCommand);
     }
@@ -350,7 +353,8 @@
     change = ctx.getChange(); // Use defensive copy created by ChangeControl.
     ReviewDb db = ctx.getDb();
     ChangeControl ctl = ctx.getControl();
-    patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
+    patchSetInfo =
+        patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
     ctx.getChange().setCurrentPatchSet(patchSetInfo);
 
     ChangeUpdate update = ctx.getUpdate(psId);
@@ -366,7 +370,7 @@
     boolean draft = status == Change.Status.DRAFT;
     List<String> newGroups = groups;
     if (newGroups.isEmpty()) {
-      newGroups = GroupCollector.getDefaultGroups(commit);
+      newGroups = GroupCollector.getDefaultGroups(commitId);
     }
     patchSet =
         psUtil.insert(
@@ -374,7 +378,7 @@
             ctx.getRevWalk(),
             update,
             psId,
-            commit,
+            commitId,
             draft,
             newGroups,
             pushCert,
@@ -518,11 +522,11 @@
       String refName = psId.toRefName();
       try (CommitReceivedEvent event =
           new CommitReceivedEvent(
-              new ReceiveCommand(ObjectId.zeroId(), commit.getId(), refName),
+              new ReceiveCommand(ObjectId.zeroId(), commitId, refName),
               refControl.getProjectControl().getProject(),
               change.getDest().get(),
               ctx.getRevWalk().getObjectReader(),
-              commit,
+              commitId,
               ctx.getIdentifiedUser())) {
         commitValidatorsFactory
             .create(validatePolicy, refControl, new NoSshInfo(), ctx.getRepository())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
index 066c1a2..2d561c5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeJson.java
@@ -1134,15 +1134,21 @@
     return null;
   }
 
+  @Nullable
+  private RevWalk newRevWalk(@Nullable Repository repo) {
+    return repo != null ? new RevWalk(repo) : null;
+  }
+
   private Map<String, RevisionInfo> revisions(
       ChangeControl ctl, ChangeData cd, Map<PatchSet.Id, PatchSet> map, ChangeInfo changeInfo)
       throws PatchListNotAvailableException, GpgException, OrmException, IOException {
     Map<String, RevisionInfo> res = new LinkedHashMap<>();
-    try (Repository repo = openRepoIfNecessary(ctl)) {
+    try (Repository repo = openRepoIfNecessary(ctl);
+        RevWalk rw = newRevWalk(repo)) {
       for (PatchSet in : map.values()) {
         if ((has(ALL_REVISIONS) || in.getId().equals(ctl.getChange().currentPatchSetId()))
             && ctl.isPatchVisible(in, db.get())) {
-          res.put(in.getRevision().get(), toRevisionInfo(ctl, cd, in, repo, false, changeInfo));
+          res.put(in.getRevision().get(), toRevisionInfo(ctl, cd, in, repo, rw, false, changeInfo));
         }
       }
       return res;
@@ -1179,9 +1185,10 @@
   public RevisionInfo getRevisionInfo(ChangeControl ctl, PatchSet in)
       throws PatchListNotAvailableException, GpgException, OrmException, IOException {
     accountLoader = accountLoaderFactory.create(has(DETAILED_ACCOUNTS));
-    try (Repository repo = openRepoIfNecessary(ctl)) {
+    try (Repository repo = openRepoIfNecessary(ctl);
+        RevWalk rw = newRevWalk(repo)) {
       RevisionInfo rev =
-          toRevisionInfo(ctl, changeDataFactory.create(db.get(), ctl), in, repo, true, null);
+          toRevisionInfo(ctl, changeDataFactory.create(db.get(), ctl), in, repo, rw, true, null);
       accountLoader.fill();
       return rev;
     }
@@ -1192,6 +1199,7 @@
       ChangeData cd,
       PatchSet in,
       @Nullable Repository repo,
+      @Nullable RevWalk rw,
       boolean fillCommit,
       @Nullable ChangeInfo changeInfo)
       throws PatchListNotAvailableException, GpgException, OrmException, IOException {
@@ -1204,32 +1212,32 @@
     out.uploader = accountLoader.get(in.getUploader());
     out.draft = in.isDraft() ? true : null;
     out.fetch = makeFetchMap(ctl, in);
-    out.kind = changeKindCache.getChangeKind(repo, cd, in);
+    out.kind = changeKindCache.getChangeKind(repo, rw, cd, in);
     out.description = in.getDescription();
 
     boolean setCommit = has(ALL_COMMITS) || (out.isCurrent && has(CURRENT_COMMIT));
     boolean addFooters = out.isCurrent && has(COMMIT_FOOTERS);
     if (setCommit || addFooters) {
+      checkState(rw != null);
+      checkState(repo != null);
       Project.NameKey project = c.getProject();
-      try (RevWalk rw = new RevWalk(repo)) {
-        String rev = in.getRevision().get();
-        RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
-        rw.parseBody(commit);
-        if (setCommit) {
-          out.commit = toCommit(ctl, rw, commit, has(WEB_LINKS), fillCommit);
+      String rev = in.getRevision().get();
+      RevCommit commit = rw.parseCommit(ObjectId.fromString(rev));
+      rw.parseBody(commit);
+      if (setCommit) {
+        out.commit = toCommit(ctl, rw, commit, has(WEB_LINKS), fillCommit);
+      }
+      if (addFooters) {
+        Ref ref = repo.exactRef(ctl.getChange().getDest().get());
+        RevCommit mergeTip = null;
+        if (ref != null) {
+          mergeTip = rw.parseCommit(ref.getObjectId());
+          rw.parseBody(mergeTip);
         }
-        if (addFooters) {
-          Ref ref = repo.exactRef(ctl.getChange().getDest().get());
-          RevCommit mergeTip = null;
-          if (ref != null) {
-            mergeTip = rw.parseCommit(ref.getObjectId());
-            rw.parseBody(mergeTip);
-          }
-          out.commitWithFooters =
-              mergeUtilFactory
-                  .create(projectCache.get(project))
-                  .createCommitMessageOnSubmit(commit, mergeTip, ctl, in.getId());
-        }
+        out.commitWithFooters =
+            mergeUtilFactory
+                .create(projectCache.get(project))
+                .createCommitMessageOnSubmit(commit, mergeTip, ctl, in.getId());
       }
     }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
index aa47827..e1edd62 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCache.java
@@ -23,6 +23,7 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevWalk;
 
 /**
  * Cache of {@link ChangeKind} per commit.
@@ -32,9 +33,14 @@
  */
 public interface ChangeKindCache {
   ChangeKind getChangeKind(
-      Project.NameKey project, @Nullable Repository repo, ObjectId prior, ObjectId next);
+      Project.NameKey project,
+      @Nullable Repository repo,
+      @Nullable RevWalk rw,
+      ObjectId prior,
+      ObjectId next);
 
   ChangeKind getChangeKind(ReviewDb db, Change change, PatchSet patch);
 
-  ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, PatchSet patch);
+  ChangeKind getChangeKind(
+      @Nullable Repository repo, @Nullable RevWalk rw, ChangeData cd, PatchSet patch);
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
index 030ddd2..0e3c139 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/ChangeKindCacheImpl.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.change;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static org.eclipse.jgit.lib.ObjectIdSerialization.readNotNull;
 import static org.eclipse.jgit.lib.ObjectIdSerialization.writeNotNull;
@@ -94,10 +95,14 @@
 
     @Override
     public ChangeKind getChangeKind(
-        Project.NameKey project, @Nullable Repository repo, ObjectId prior, ObjectId next) {
+        Project.NameKey project,
+        @Nullable Repository repo,
+        @Nullable RevWalk rw,
+        ObjectId prior,
+        ObjectId next) {
       try {
         Key key = new Key(prior, next, useRecursiveMerge);
-        return new Loader(key, repoManager, project, repo).call();
+        return new Loader(key, repoManager, project, repo, rw).call();
       } catch (IOException e) {
         log.warn(
             "Cannot check trivial rebase of new patch set " + next.name() + " in " + project, e);
@@ -111,8 +116,9 @@
     }
 
     @Override
-    public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, PatchSet patch) {
-      return getChangeKindInternal(this, repo, cd, patch);
+    public ChangeKind getChangeKind(
+        @Nullable Repository repo, @Nullable RevWalk rw, ChangeData cd, PatchSet patch) {
+      return getChangeKindInternal(this, repo, rw, cd, patch);
     }
   }
 
@@ -183,16 +189,25 @@
     private final GitRepositoryManager repoManager;
     private final Project.NameKey projectName;
     private final Repository alreadyOpenRepo;
+    private final RevWalk alreadyOpenRw;
 
     private Loader(
         Key key,
         GitRepositoryManager repoManager,
         Project.NameKey projectName,
-        @Nullable Repository alreadyOpenRepo) {
+        @Nullable Repository alreadyOpenRepo,
+        @Nullable RevWalk alreadyOpenRw) {
+      checkArgument(
+          (alreadyOpenRepo == null && alreadyOpenRw == null)
+              || (alreadyOpenRepo != null && alreadyOpenRw != null),
+          "must either provide both repo/revwalk, or neither; got %s/%s",
+          alreadyOpenRepo,
+          alreadyOpenRw);
       this.key = key;
       this.repoManager = repoManager;
       this.projectName = projectName;
       this.alreadyOpenRepo = alreadyOpenRepo;
+      this.alreadyOpenRw = alreadyOpenRw;
     }
 
     @Override
@@ -201,17 +216,19 @@
         return ChangeKind.NO_CODE_CHANGE;
       }
 
+      RevWalk rw = alreadyOpenRw;
       Repository repo = alreadyOpenRepo;
       boolean close = false;
       if (repo == null) {
         repo = repoManager.openRepository(projectName);
+        rw = new RevWalk(repo);
         close = true;
       }
-      try (RevWalk walk = new RevWalk(repo)) {
-        RevCommit prior = walk.parseCommit(key.prior);
-        walk.parseBody(prior);
-        RevCommit next = walk.parseCommit(key.next);
-        walk.parseBody(next);
+      try {
+        RevCommit prior = rw.parseCommit(key.prior);
+        rw.parseBody(prior);
+        RevCommit next = rw.parseCommit(key.next);
+        rw.parseBody(next);
 
         if (!next.getFullMessage().equals(prior.getFullMessage())) {
           if (isSameDeltaAndTree(prior, next)) {
@@ -233,7 +250,7 @@
         // A trivial rebase can be detected by looking for the next commit
         // having the same tree as would exist when the prior commit is
         // cherry-picked onto the next commit's new first parent.
-        try (ObjectInserter ins = new InMemoryInserter(repo)) {
+        try (ObjectInserter ins = new InMemoryInserter(rw.getObjectReader())) {
           ThreeWayMerger merger = MergeUtil.newThreeWayMerger(repo, ins, key.strategyName);
           merger.setBase(prior.getParent(0));
           if (merger.merge(next.getParent(0), prior)
@@ -250,6 +267,7 @@
         return ChangeKind.REWORK;
       } finally {
         if (close) {
+          rw.close();
           repo.close();
         }
       }
@@ -327,10 +345,14 @@
 
   @Override
   public ChangeKind getChangeKind(
-      Project.NameKey project, @Nullable Repository repo, ObjectId prior, ObjectId next) {
+      Project.NameKey project,
+      @Nullable Repository repo,
+      @Nullable RevWalk rw,
+      ObjectId prior,
+      ObjectId next) {
     try {
       Key key = new Key(prior, next, useRecursiveMerge);
-      return cache.get(key, new Loader(key, repoManager, project, repo));
+      return cache.get(key, new Loader(key, repoManager, project, repo, rw));
     } catch (ExecutionException e) {
       log.warn("Cannot check trivial rebase of new patch set " + next.name() + " in " + project, e);
       return ChangeKind.REWORK;
@@ -343,12 +365,17 @@
   }
 
   @Override
-  public ChangeKind getChangeKind(@Nullable Repository repo, ChangeData cd, PatchSet patch) {
-    return getChangeKindInternal(this, repo, cd, patch);
+  public ChangeKind getChangeKind(
+      @Nullable Repository repo, @Nullable RevWalk rw, ChangeData cd, PatchSet patch) {
+    return getChangeKindInternal(this, repo, rw, cd, patch);
   }
 
   private static ChangeKind getChangeKindInternal(
-      ChangeKindCache cache, @Nullable Repository repo, ChangeData change, PatchSet patch) {
+      ChangeKindCache cache,
+      @Nullable Repository repo,
+      @Nullable RevWalk rw,
+      ChangeData change,
+      PatchSet patch) {
     ChangeKind kind = ChangeKind.REWORK;
     // Trivial case: if we're on the first patch, we don't need to use
     // the repository.
@@ -373,6 +400,7 @@
               cache.getChangeKind(
                   change.project(),
                   repo,
+                  rw,
                   ObjectId.fromString(priorPs.getRevision().get()),
                   ObjectId.fromString(patch.getRevision().get()));
         }
@@ -401,8 +429,9 @@
     // Trivial case: if we're on the first patch, we don't need to open
     // the repository.
     if (patch.getId().get() > 1) {
-      try (Repository repo = repoManager.openRepository(change.getProject())) {
-        kind = getChangeKindInternal(cache, repo, changeDataFactory.create(db, change), patch);
+      try (Repository repo = repoManager.openRepository(change.getProject());
+          RevWalk rw = new RevWalk(repo)) {
+        kind = getChangeKindInternal(cache, repo, rw, changeDataFactory.create(db, change), patch);
       } catch (IOException e) {
         // Do nothing; assume we have a complex change
         log.warn(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
index 8f0d45f..8d0da36 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/CherryPickChange.java
@@ -288,7 +288,7 @@
       String topic,
       Branch.NameKey sourceBranch,
       ObjectId sourceCommit)
-      throws OrmException {
+      throws OrmException, IOException {
     Change.Id changeId = new Change.Id(seq.nextChangeId());
     ChangeInserter ins =
         changeInserterFactory
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java
index 9d819b1..aee9afc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteChangeOp.java
@@ -38,12 +38,10 @@
 import com.google.inject.Inject;
 import java.io.IOException;
 import java.util.Collection;
-import java.util.Collections;
 import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevWalk;
 import org.eclipse.jgit.transport.ReceiveCommand;
 
@@ -147,9 +145,8 @@
 
     RevWalk revWalk = ctx.getRevWalk();
     ObjectId objectId = ObjectId.fromString(patchSet.getRevision().get());
-    RevCommit revCommit = revWalk.parseCommit(objectId);
-    return IncludedInResolver.includedInOne(
-        repository, revWalk, revCommit, Collections.singletonList(destinationRef));
+    return revWalk.isMergedInto(
+        revWalk.parseCommit(objectId), revWalk.parseCommit(destinationRef.getObjectId()));
   }
 
   private void deleteChangeElementsFromDb(ChangeContext ctx, Change.Id id) throws OrmException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
index b5089f6..88e9d42 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PatchSetInserter.java
@@ -56,7 +56,6 @@
 import java.util.Collections;
 import java.util.List;
 import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.transport.ReceiveCommand;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -65,7 +64,7 @@
   private static final Logger log = LoggerFactory.getLogger(PatchSetInserter.class);
 
   public interface Factory {
-    PatchSetInserter create(ChangeControl ctl, PatchSet.Id psId, RevCommit commit);
+    PatchSetInserter create(ChangeControl ctl, PatchSet.Id psId, ObjectId commitId);
   }
 
   // Injected fields.
@@ -80,7 +79,7 @@
 
   // Assisted-injected fields.
   private final PatchSet.Id psId;
-  private final RevCommit commit;
+  private final ObjectId commitId;
   // Read prior to running the batch update, so must only be used during
   // updateRepo; updateChange and later must use the control from the
   // ChangeContext.
@@ -118,7 +117,7 @@
       RevisionCreated revisionCreated,
       @Assisted ChangeControl ctl,
       @Assisted PatchSet.Id psId,
-      @Assisted RevCommit commit) {
+      @Assisted ObjectId commitId) {
     this.approvalsUtil = approvalsUtil;
     this.approvalCopier = approvalCopier;
     this.cmUtil = cmUtil;
@@ -130,7 +129,7 @@
 
     this.origCtl = ctl;
     this.psId = psId;
-    this.commit = commit;
+    this.commitId = commitId.copy();
   }
 
   public PatchSet.Id getPatchSetId() {
@@ -210,7 +209,7 @@
     validate(ctx);
     ctx.addRefUpdate(
         new ReceiveCommand(
-            ObjectId.zeroId(), commit, getPatchSetId().toRefName(), ReceiveCommand.Type.CREATE));
+            ObjectId.zeroId(), commitId, getPatchSetId().toRefName(), ReceiveCommand.Type.CREATE));
   }
 
   @Override
@@ -243,7 +242,7 @@
             ctx.getRevWalk(),
             ctx.getUpdate(psId),
             psId,
-            commit,
+            commitId,
             draft,
             newGroups,
             null,
@@ -264,7 +263,8 @@
       changeMessage.setMessage(message);
     }
 
-    patchSetInfo = patchSetInfoFactory.get(ctx.getRevWalk(), commit, psId);
+    patchSetInfo =
+        patchSetInfoFactory.get(ctx.getRevWalk(), ctx.getRevWalk().parseCommit(commitId), psId);
     if (change.getStatus() != Change.Status.DRAFT && !allowClosed) {
       change.setStatus(Change.Status.NEW);
     }
@@ -315,12 +315,12 @@
         new CommitReceivedEvent(
             new ReceiveCommand(
                 ObjectId.zeroId(),
-                commit.getId(),
+                commitId,
                 refName.substring(0, refName.lastIndexOf('/') + 1) + "new"),
             origCtl.getProjectControl().getProject(),
             origCtl.getRefControl().getRefName(),
             ctx.getRevWalk().getObjectReader(),
-            commit,
+            commitId,
             ctx.getIdentifiedUser())) {
       commitValidatorsFactory
           .create(validatePolicy, origCtl.getRefControl(), new NoSshInfo(), ctx.getRepository())
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
index 78352dc..24faeef 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/edit/ChangeEditUtil.java
@@ -195,7 +195,7 @@
 
       // Previously checked that the base patch set is the current patch set.
       ObjectId prior = ObjectId.fromString(basePatchSet.getRevision().get());
-      ChangeKind kind = changeKindCache.getChangeKind(change.getProject(), repo, prior, squashed);
+      ChangeKind kind = changeKindCache.getChangeKind(change.getProject(), repo, rw, prior, squashed);
       if (kind == ChangeKind.NO_CODE_CHANGE) {
         message.append("Commit message was updated.");
         inserter.setDescription("Edit commit message");
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index e3d45ac..8cc2fcf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -166,6 +166,9 @@
   private static final SubmitType DEFAULT_SUBMIT_ACTION = SubmitType.MERGE_IF_NECESSARY;
   private static final ProjectState DEFAULT_STATE_VALUE = ProjectState.ACTIVE;
 
+  private static final String EXTENSION_PANELS = "extension-panels";
+  private static final String KEY_PANEL = "panel";
+
   private Project.NameKey projectName;
   private Project project;
   private AccountsSection accountsSection;
@@ -186,6 +189,7 @@
   private Set<String> sectionsWithUnknownPermissions;
   private boolean hasLegacyPermissions;
   private boolean enableReviewerByEmail;
+  private Map<String, List<String>> extensionPanelSections;
 
   public static ProjectConfig read(MetaDataUpdate update)
       throws IOException, ConfigInvalidException {
@@ -201,6 +205,10 @@
     return r;
   }
 
+  public Map<String, List<String>> getExtensionPanelSections() {
+    return extensionPanelSections;
+  }
+
   public static CommentLinkInfoImpl buildCommentLink(Config cfg, String name, boolean allowRaw)
       throws IllegalArgumentException {
     String match = cfg.getString(COMMENTLINK, name, KEY_MATCH);
@@ -541,6 +549,7 @@
     loadPluginSections(rc);
     loadReceiveSection(rc);
     loadReviewerSection(rc);
+    loadExtensionPanelSections(rc);
   }
 
   private void loadAccountsSection(Config rc, Map<String, GroupReference> groupsByName) {
@@ -549,6 +558,25 @@
         loadPermissionRules(rc, ACCOUNTS, null, KEY_SAME_GROUP_VISIBILITY, groupsByName, false));
   }
 
+  private void loadExtensionPanelSections(Config rc) {
+    Map<String, String> lowerNames = Maps.newHashMapWithExpectedSize(2);
+    extensionPanelSections = Maps.newLinkedHashMap();
+    for (String name : rc.getSubsections(EXTENSION_PANELS)) {
+      String lower = name.toLowerCase();
+      if (lowerNames.containsKey(lower)) {
+        error(
+            new ValidationError(
+                PROJECT_CONFIG,
+                String.format(
+                    "Extension Panels \"%s\" conflicts with \"%s\"", name, lowerNames.get(lower))));
+      }
+      lowerNames.put(lower, name);
+      extensionPanelSections.put(
+          name,
+          new ArrayList<>(Arrays.asList(rc.getStringList(EXTENSION_PANELS, name, KEY_PANEL))));
+    }
+  }
+
   private void loadContributorAgreements(Config rc, Map<String, GroupReference> groupsByName) {
     contributorAgreements = new HashMap<>();
     for (String name : rc.getSubsections(CONTRIBUTOR_AGREEMENT)) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
index 27dea6b..b2c4ae9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReplaceOp.java
@@ -83,9 +83,9 @@
         Branch.NameKey dest,
         boolean checkMergedInto,
         @Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
-        @Assisted("priorCommit") RevCommit priorCommit,
+        @Assisted("priorCommitId") ObjectId priorCommit,
         @Assisted("patchSetId") PatchSet.Id patchSetId,
-        @Assisted("commit") RevCommit commit,
+        @Assisted("commitId") ObjectId commitId,
         PatchSetInfo info,
         List<String> groups,
         @Nullable MagicBranchInput magicBranch,
@@ -115,9 +115,9 @@
   private final Branch.NameKey dest;
   private final boolean checkMergedInto;
   private final PatchSet.Id priorPatchSetId;
-  private final RevCommit priorCommit;
+  private final ObjectId priorCommitId;
   private final PatchSet.Id patchSetId;
-  private final RevCommit commit;
+  private final ObjectId commitId;
   private final PatchSetInfo info;
   private final MagicBranchInput magicBranch;
   private final PushCertificate pushCertificate;
@@ -125,6 +125,7 @@
 
   private final Map<String, Short> approvals = new HashMap<>();
   private final MailRecipients recipients = new MailRecipients();
+  private RevCommit commit;
   private Change change;
   private PatchSet newPatchSet;
   private ChangeKind changeKind;
@@ -154,9 +155,9 @@
       @Assisted Branch.NameKey dest,
       @Assisted boolean checkMergedInto,
       @Assisted("priorPatchSetId") PatchSet.Id priorPatchSetId,
-      @Assisted("priorCommit") RevCommit priorCommit,
+      @Assisted("priorCommitId") ObjectId priorCommitId,
       @Assisted("patchSetId") PatchSet.Id patchSetId,
-      @Assisted("commit") RevCommit commit,
+      @Assisted("commitId") ObjectId commitId,
       @Assisted PatchSetInfo info,
       @Assisted List<String> groups,
       @Assisted @Nullable MagicBranchInput magicBranch,
@@ -180,9 +181,9 @@
     this.dest = dest;
     this.checkMergedInto = checkMergedInto;
     this.priorPatchSetId = priorPatchSetId;
-    this.priorCommit = priorCommit;
+    this.priorCommitId = priorCommitId.copy();
     this.patchSetId = patchSetId;
-    this.commit = commit;
+    this.commitId = commitId.copy();
     this.info = info;
     this.groups = groups;
     this.magicBranch = magicBranch;
@@ -192,9 +193,15 @@
 
   @Override
   public void updateRepo(RepoContext ctx) throws Exception {
+    commit = ctx.getRevWalk().parseCommit(commitId);
+    ctx.getRevWalk().parseBody(commit);
     changeKind =
         changeKindCache.getChangeKind(
-            projectControl.getProject().getNameKey(), ctx.getRepository(), priorCommit, commit);
+            projectControl.getProject().getNameKey(),
+            ctx.getRepository(),
+            ctx.getRevWalk(),
+            priorCommitId,
+            commitId);
 
     if (checkMergedInto) {
       Ref mergedInto = findMergedInto(ctx, dest.get(), commit);
@@ -205,7 +212,7 @@
     }
 
     if (updateRef) {
-      ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), commit, patchSetId.toRefName()));
+      ctx.addRefUpdate(new ReceiveCommand(ObjectId.zeroId(), commitId, patchSetId.toRefName()));
     }
   }
 
@@ -259,7 +266,7 @@
             ctx.getRevWalk(),
             update,
             patchSetId,
-            commit,
+            commitId,
             draft,
             groups,
             pushCertificate != null ? pushCertificate.toTextWithSignature() : null,
@@ -383,7 +390,7 @@
 
     List<String> idList = commit.getFooterLines(CHANGE_ID);
     if (idList.isEmpty()) {
-      change.setKey(new Change.Key("I" + commit.name()));
+      change.setKey(new Change.Key("I" + commitId.name()));
     } else {
       change.setKey(new Change.Key(idList.get(idList.size() - 1).trim()));
     }
@@ -398,7 +405,7 @@
     final Account account = ctx.getAccount();
     if (!updateRef) {
       gitRefUpdated.fire(
-          ctx.getProject(), newPatchSet.getRefName(), ObjectId.zeroId(), commit, account);
+          ctx.getProject(), newPatchSet.getRefName(), ObjectId.zeroId(), commitId, account);
     }
 
     if (changeKind != ChangeKind.TRIVIAL_REBASE) {
@@ -505,7 +512,7 @@
     return this;
   }
 
-  private Ref findMergedInto(Context ctx, String first, RevCommit commit) {
+  private static Ref findMergedInto(Context ctx, String first, RevCommit commit) {
     try {
       RefDatabase refDatabase = ctx.getRepository().getRefDatabase();
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
index feeea95..0dcb5f8 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ConfigInfoImpl.java
@@ -131,6 +131,8 @@
       actions.put(d.getId(), new ActionInfo(d));
     }
     this.theme = projectState.getTheme();
+
+    this.extensionPanelNames = projectState.getConfig().getExtensionPanelSections();
   }
 
   private Map<String, Map<String, ConfigParameterInfo>> getPluginConfig(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
index 4d0939b6..8227684 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/BatchUpdate.java
@@ -337,7 +337,7 @@
     return this;
   }
 
-  public BatchUpdate insertChange(InsertChangeOp op) {
+  public BatchUpdate insertChange(InsertChangeOp op) throws IOException {
     Context ctx = newContext();
     Change c = op.createChange(ctx);
     checkArgument(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/update/InsertChangeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/update/InsertChangeOp.java
index 1a947e6..7060059 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/update/InsertChangeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/update/InsertChangeOp.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.update;
 
 import com.google.gerrit.reviewdb.client.Change;
+import java.io.IOException;
 
 /**
  * Specialization of {@link BatchUpdateOp} for creating changes.
@@ -27,5 +28,5 @@
  * first.
  */
 public interface InsertChangeOp extends BatchUpdateOp {
-  Change createChange(Context ctx);
+  Change createChange(Context ctx) throws IOException;
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index bcd5d39..a921916 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -414,7 +414,7 @@
     TestRepository<Repo> repo = createProject("repo");
     ChangeInserter ins = newChange(repo);
     insert(repo, ins);
-    String sha = ins.getCommit().name();
+    String sha = ins.getCommitId().name();
 
     assertQuery("0000000000000000000000000000000000000000");
     for (int i = 0; i <= 36; i++) {
@@ -1741,7 +1741,7 @@
       if (dest == null) {
         dest = ins.getChange().getDest();
       }
-      shas.add(ins.getCommit().name());
+      shas.add(ins.getCommitId().name());
       expectedIds.add(ins.getChange().getId().get());
     }
 
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
index 859779d..39c6a1f 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.html
@@ -186,7 +186,9 @@
     </section>
     <section>
       <span class="title">Branch</span>
-      <span class="value">[[change.branch]]</span>
+      <span class="value">
+        <a href$="[[_computeBranchURL(change.project, change.branch)]]">[[change.branch]]</a>
+      </span>
     </section>
     <section>
       <span class="title">Topic</span>
diff --git a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
index df45f1d..0edaa41 100644
--- a/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
+++ b/polygerrit-ui/app/elements/change/gr-change-metadata/gr-change-metadata.js
@@ -230,6 +230,18 @@
       return '/q/status:open+project:' + this.encodeURL(project, false);
     },
 
+    _computeBranchURL: function(project, branch) {
+      var status;
+      if (this.change.status == this.ChangeStatus.NEW) {
+        status = this.change.status.toLowerCase().replace('new', 'open');
+      } else {
+        status = this.change.status.toLowerCase();
+      }
+      return '/q/project:' + this.encodeURL(project, false) +
+        ' branch:' +  this.encodeURL(branch, false) +
+          ' status:' + this.encodeURL(status, false);
+    },
+
     _computeTopicHref: function(topic) {
       var encodedTopic = encodeURIComponent('\"' + topic + '\"');
       return '/q/topic:' + encodeURIComponent(encodedTopic) +
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
index 0914846..808f689 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer.js
@@ -78,7 +78,7 @@
   };
 
   var CPP_DIRECTIVE_WITH_LT_PATTERN = /^\s*#(if|define).*</;
-  var CPP_WCHAR_PATTERN = /L\'.\'/g;
+  var CPP_WCHAR_PATTERN = /L\'(\\)?.\'/g;
   var JAVA_PARAM_ANNOT_PATTERN = /(@[^\s]+)\(([^)]+)\)/g;
   var GO_BACKSLASH_LITERAL = '\'\\\\\'';
   var GLOBAL_LT_PATTERN = /</g;
@@ -360,7 +360,7 @@
          * {#see https://github.com/isagalaev/highlight.js/issues/1412}
          */
         if (CPP_WCHAR_PATTERN.test(line)) {
-          line = line.replace(CPP_WCHAR_PATTERN, 'L"."');
+          line = line.replace(CPP_WCHAR_PATTERN, 'L"$1."');
         }
 
         return line;
diff --git a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
index eaa8d29..e618d3a 100644
--- a/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-syntax-layer/gr-syntax-layer_test.html
@@ -456,6 +456,11 @@
       line = 'wchar_t myChar = L\'#\'';
       var expected = 'wchar_t myChar = L"."';
       assert.equal(element._workaround('cpp', line), expected);
+
+      // Converts wchar_t character literal with escape sequence to string.
+      line = 'wchar_t myChar = L\'\\"\'';
+      expected = 'wchar_t myChar = L"\\."';
+      assert.equal(element._workaround('cpp', line), expected);
     });
 
     test('workaround go backslash character literals', function() {