Merge "Add missing documentation about hook configuration"
diff --git a/Documentation/cmd-stream-events.txt b/Documentation/cmd-stream-events.txt
index bf78051..b3c6037 100644
--- a/Documentation/cmd-stream-events.txt
+++ b/Documentation/cmd-stream-events.txt
@@ -41,9 +41,10 @@
 SCHEMA
 ------
 The JSON messages consist of nested objects referencing the *change*,
-*patchset*, *account* involved, and other attributes as appropriate.
+*patchSet*, *account* involved, and other attributes as appropriate.
 The currently supported message types are *patchset-created*,
-*comment-added*, *change-merged*, and *change-abandoned*.
+*change-abandoned*, *change-restored*, *change-merged*,
+*comment-added* and *ref-updated*.
 
 Note that any field may be missing in the JSON messages, so consumers of
 this JSON stream should deal with that appropriately.
@@ -56,7 +57,7 @@
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 uploader:: link:json.html#account[account attribute]
 
@@ -66,27 +67,31 @@
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 abandoner:: link:json.html#account[account attribute]
 
+reason:: Reason for abandoning the change.
+
 Change Restored
-^^^^^^^^^^^^^^^^
+^^^^^^^^^^^^^^^
 type:: "change-restored"
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 restorer:: link:json.html#account[account attribute]
 
+reason:: Reason for restoring the change.
+
 Change Merged
 ^^^^^^^^^^^^^
 type:: "change-merged"
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 submitter:: link:json.html#account[account attribute]
 
@@ -96,10 +101,12 @@
 
 change:: link:json.html#change[change attribute]
 
-patchset:: link:json.html#patchset[patchset attribute]
+patchSet:: link:json.html#patchSet[patchSet attribute]
 
 author:: link:json.html#account[account attribute]
 
+approvals:: All link:json.html#approval[approval attributes] granted.
+
 comment:: Comment text author had written
 
 Ref Updated
@@ -108,7 +115,7 @@
 
 submitter:: link:json.html#account[account attribute]
 
-refUpdate:: link:json.html#refupdate[refupdate attribute]
+refUpdate:: link:json.html#refUpdate[refUpdate attribute]
 
 
 SEE ALSO
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 06d2f72..0435f4e 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -1953,6 +1953,28 @@
 and text results for changes. If false, the URL is disabled and
 returns 404 to clients. Default is true, enabling `/query`.
 
+[[site.upgradeSchemaOnStartup]]site.upgradeSchemaOnStartup::
++
+Control whether schema upgrade should be done on Gerrit startup. The following
+values are supported:
++
+* `OFF`
++
+No automatic schema upgrade on startup.
++
+* `AUTO`
++
+Perform schema migration on startup, if necessary.  If, as a result of
+schema migration, there would be any unused database objects they will
+be dropped automatically.
++
+* `AUTO_NO_PRUNE`
++
+Like `AUTO` but unused database objects will not be pruned.
+
++
+The default is `OFF`.
+
 [[ssh-alias]] Section ssh-alias
 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
diff --git a/Documentation/dev-eclipse.txt b/Documentation/dev-eclipse.txt
index ca56da3..a19d85e 100644
--- a/Documentation/dev-eclipse.txt
+++ b/Documentation/dev-eclipse.txt
@@ -73,9 +73,15 @@
 Running Hosted Mode
 ~~~~~~~~~~~~~~~~~~~
 
-Import the gerrit-gwtdebug project:
+To debug the GWT code executing in the web browser, three additional Git
+repositories need to be cloned.
 
-* Import gerrit-gwtdebug/pom.xml using General -> Maven Projects
+* https://gerrit.googlesource.com/gwtexpui
+* https://gerrit.googlesource.com/gwtjsonrpc
+* https://gerrit.googlesource.com/gwtorm
+
+In Eclipse, import the pom.xml file in the root directory of each of
+these cloned gits via General -> Maven Projects.
 
 Duplicate the existing `gwtui_dbg` launch configuration:
 
@@ -97,12 +103,17 @@
 Known problems
 --------------
 
-When running Gerrit under the Eclipse debugger, code that attempts
+* When running Gerrit under the Eclipse debugger, code that attempts
 to load Prolog code may erroneously raise ClassNotFoundException,
 claiming that classes in the `Gerrit` package can't be found. The
 error can often be resolved by rebuilding Gerrit with `mvn package`
 and restarting the debug session.
 
+* OpenID authentication won't work in hosted mode, so you need to change
+the link:config-gerrit.html#auth.type[auth.type] configuration parameter
+to `DEVELOPMENT_BECOME_ANY_ACCOUNT` to disable OpenID and allow you to
+impersonate whatever account you otherwise would've used.
+
 
 GERRIT
 ------
diff --git a/Documentation/json.txt b/Documentation/json.txt
index ae7ec63..fac1424 100644
--- a/Documentation/json.txt
+++ b/Documentation/json.txt
@@ -53,9 +53,9 @@
 message based on the server's
 link:config-gerrit.html#trackingid[trackingid] sections.
 
-currentPatchSet:: Current <<patchset,patchset attribute>>.
+currentPatchSet:: Current <<patchSet,patchSet attribute>>.
 
-patchSets:: All <<patchset,patchset attribute>> for this change.
+patchSets:: All <<patchSet,patchSet attribute>> for this change.
 
 [[trackingid]]
 trackingid
@@ -76,8 +76,8 @@
 
 email:: User's preferred email address.
 
-[[patchset]]
-patchset
+[[patchSet]]
+patchSet
 --------
 Refers to a specific patchset within a <<change,change>>.
 
@@ -109,8 +109,8 @@
 
 by:: Reviewer of the patch set in <<account,account attribute>>.
 
-[[refupdate]]
-refupdate
+[[refUpdate]]
+refUpdate
 --------
 Information about a ref that was updated.
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
index 9efea22..05dd5d9 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/AccountDashboardScreen.java
@@ -49,6 +49,7 @@
 
     outgoing.setTitleText(Util.C.outgoingReviews());
     incoming.setTitleText(Util.C.incomingReviews());
+    incoming.setHighlightUnreviewed(true);
     closed.setTitleText(Util.C.recentlyClosed());
 
     table.addSection(outgoing);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
index 52e3bd2..0c8e03e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeInfo.java
@@ -71,6 +71,7 @@
   private final native String createdRaw() /*-{ return this.created; }-*/;
   private final native String updatedRaw() /*-{ return this.updated; }-*/;
   public final native boolean starred() /*-{ return this.starred ? true : false; }-*/;
+  public final native boolean reviewed() /*-{ return this.reviewed ? true : false; }-*/;
   public final native String _sortkey() /*-{ return this._sortkey; }-*/;
   private final native JavaScriptObject labels0() /*-{ return this.labels; }-*/;
   public final native LabelInfo label(String n) /*-{ return this.labels[n]; }-*/;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
index 1b9db39..fc2b418 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeTable2.java
@@ -26,16 +26,19 @@
 import com.google.gerrit.client.ui.ProjectLink;
 import com.google.gerrit.common.PageLinks;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ClickEvent;
 import com.google.gwt.event.dom.client.ClickHandler;
 import com.google.gwt.event.dom.client.KeyCodes;
 import com.google.gwt.event.dom.client.KeyPressEvent;
+import com.google.gwt.user.client.DOM;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
 import com.google.gwt.user.client.ui.FlowPanel;
 import com.google.gwt.user.client.ui.HTMLTable.Cell;
 import com.google.gwt.user.client.ui.HTMLTable.CellFormatter;
 import com.google.gwt.user.client.ui.Image;
 import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.UIObject;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -188,7 +191,8 @@
     }
   }
 
-  private void populateChangeRow(final int row, final ChangeInfo c) {
+  private void populateChangeRow(final int row, final ChangeInfo c,
+      boolean highlightUnreviewed) {
     if (Gerrit.isSignedIn()) {
       table.setWidget(row, C_STAR, StarredChanges.createIcon(
           c.legacy_id(),
@@ -284,10 +288,13 @@
       }
     }
 
-    // TODO(sop): Highlight changes I haven't reviewed on my dashboard.
-    // final Element tr = DOM.getParent(fmt.getElement(row, 0));
-    // UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
-    // !haveReview && highlightUnreviewed);
+    boolean needHighlight = false;
+    if (highlightUnreviewed && !c.reviewed()) {
+      needHighlight = true;
+    }
+    final Element tr = DOM.getParent(fmt.getElement(row, 0));
+    UIObject.setStyleName(tr, Gerrit.RESOURCES.css().needsReview(),
+        needHighlight);
 
     setRowItem(row, c);
   }
@@ -368,6 +375,11 @@
     int titleRow = -1;
     int dataBegin;
     int rows;
+    private boolean highlightUnreviewed;
+
+    public void setHighlightUnreviewed(boolean value) {
+      this.highlightUnreviewed = value;
+    }
 
     public void setTitleText(final String text) {
       titleText = text;
@@ -399,7 +411,8 @@
         rows++;
       }
       for (int i = 0; i < sz; i++) {
-        parent.populateChangeRow(dataBegin + i, changeList.get(i));
+        parent.populateChangeRow(dataBegin + i, changeList.get(i),
+            highlightUnreviewed);
       }
     }
   }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
index 2b87a28..1a6ea3e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.java
@@ -14,57 +14,29 @@
 
 package com.google.gerrit.client.changes;
 
+import com.google.gerrit.client.Gerrit;
 import com.google.gerrit.client.ui.CommentLinkProcessor;
 import com.google.gwt.user.client.ui.Composite;
-
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.PreElement;
-import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.ui.HTMLPanel;
+import com.google.gwt.user.client.ui.HTML;
 import com.google.gwtexpui.safehtml.client.SafeHtml;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 
 public class CommitMessageBlock extends Composite {
-  interface Binder extends UiBinder<HTMLPanel, CommitMessageBlock> {
-  }
-
-  private static Binder uiBinder = GWT.create(Binder.class);
-
-  @UiField
-  PreElement commitSummaryPre;
-  @UiField
-  PreElement commitBodyPre;
+  private final HTML description;
 
   public CommitMessageBlock() {
-    initWidget(uiBinder.createAndBindUi(this));
+    description = new HTML();
+    description.setStyleName(Gerrit.RESOURCES.css().changeScreenDescription());
+    initWidget(description);
   }
 
   public void display(final String commitMessage) {
-    String commitSummary = "";
-    String commitBody = "";
-
-    String[] splitCommitMessage = commitMessage.split("\n", 2);
-    commitSummary = splitCommitMessage[0];
-    commitBody = splitCommitMessage[1];
-
-    // Hide commit body if there is no body
-    if (commitBody.trim().isEmpty()) {
-      commitBodyPre.getStyle().setDisplay(Display.NONE);
-    }
-
-    // Linkify commit summary
-    SafeHtml commitSummaryLinkified = new SafeHtmlBuilder().append(commitSummary);
-    commitSummaryLinkified = commitSummaryLinkified.linkify();
-    commitSummaryLinkified = CommentLinkProcessor.apply(commitSummaryLinkified);
-
-    // Linkify commit body
-    SafeHtml commitBodyLinkified = new SafeHtmlBuilder().append(commitBody);
-    commitBodyLinkified = commitBodyLinkified.linkify();
-    commitBodyLinkified = CommentLinkProcessor.apply(commitBodyLinkified);
-
-    commitSummaryPre.setInnerHTML(commitSummaryLinkified.asString());
-    commitBodyPre.setInnerHTML(commitBodyLinkified.asString());
+    SafeHtml msg = new SafeHtmlBuilder().append(commitMessage);
+    msg = msg.linkify();
+    msg = CommentLinkProcessor.apply(msg);
+    msg = new SafeHtmlBuilder().openElement("p").append(msg).closeElement("p");
+    msg = msg.replaceAll("\n\n", "</p><p>");
+    msg = msg.replaceAll("\n", "<br />");
+    SafeHtml.set(description, msg);
   }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
deleted file mode 100644
index 41d6f83..0000000
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/CommitMessageBlock.ui.xml
+++ /dev/null
@@ -1,76 +0,0 @@
-<?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.
--->
-
-<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
-    xmlns:g='urn:import:com.google.gwt.user.client.ui'>
-
-
-  <ui:style>
-    @eval selectionColor com.google.gerrit.client.Gerrit.getTheme().selectionColor;
-    @eval trimColor com.google.gerrit.client.Gerrit.getTheme().trimColor;
-    @eval backgroundColor com.google.gerrit.client.Gerrit.getTheme().backgroundColor;
-
-    .commitMessageTable {
-      border-collapse: separate;
-      border-spacing: 0;
-      margin-bottom: 10px;
-    }
-
-    .header {
-      border: 1px solid trimColor;
-      padding: 2px 6px 1px;
-      background-color: trimColor;
-      white-space: nowrap;
-      color: textColor;
-      font-size: 10pt;
-    }
-
-    .contents {
-      border-bottom: 1px solid trimColor;
-      border-left: 1px solid trimColor;
-      border-right: 1px solid trimColor;
-      padding: 5px;
-    }
-
-    .contents span {
-      font-weight: bold;
-    }
-
-    .contents pre {
-      margin: 0;
-    }
-
-    .commitSummary {
-      font-weight: bold;
-    }
-
-    .commitBody {
-      margin-top: 10px;
-    }
-  </ui:style>
-
-  <g:HTMLPanel>
-    <table class='{style.commitMessageTable}'>
-      <tr><th class='{style.header}'>Commit Message</th></tr>
-      <tr><td class='{style.contents}'>
-        <pre class='{style.commitSummary}' ui:field='commitSummaryPre'/>
-        <pre class='{style.commitBody}' ui:field='commitBodyPre'/>
-      </td></tr>
-    </table>
-  </g:HTMLPanel>
-</ui:UiBinder>
-
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 79f9011..47eae99 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
@@ -218,8 +218,12 @@
 
     String uri = req.getRequestURI();
     String ctx = req.getContextPath();
-    String file = uri.substring(ctx.length() + 1);
+    if (uri.length() <= ctx.length()) {
+      Resource.NOT_FOUND.send(req, res);
+      return;
+    }
 
+    String file = uri.substring(ctx.length() + 1);
     ResourceKey key = new ResourceKey(holder.plugin, file);
     Resource rsc = resourceCache.getIfPresent(key);
     if (rsc != null) {
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
index ff5ee17..282bbc9 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/Daemon.java
@@ -35,12 +35,14 @@
 import com.google.gerrit.pgm.util.RuntimeShutdown;
 import com.google.gerrit.pgm.util.SiteProgram;
 import com.google.gerrit.reviewdb.client.AuthType;
+import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
 import com.google.gerrit.server.config.AuthConfig;
 import com.google.gerrit.server.config.AuthConfigModule;
 import com.google.gerrit.server.config.CanonicalWebUrlModule;
 import com.google.gerrit.server.config.CanonicalWebUrlProvider;
 import com.google.gerrit.server.config.GerritGlobalModule;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.config.MasterNodeStartup;
 import com.google.gerrit.server.contact.HttpContactStoreConnection;
 import com.google.gerrit.server.git.ReceiveCommitsExecutorModule;
@@ -49,15 +51,24 @@
 import com.google.gerrit.server.mail.SmtpEmailSender;
 import com.google.gerrit.server.plugins.PluginGuiceEnvironment;
 import com.google.gerrit.server.plugins.PluginModule;
+import com.google.gerrit.server.schema.SchemaUpdater;
 import com.google.gerrit.server.schema.SchemaVersionCheck;
+import com.google.gerrit.server.schema.UpdateUI;
 import com.google.gerrit.server.ssh.NoSshModule;
 import com.google.gerrit.sshd.SshModule;
 import com.google.gerrit.sshd.commands.MasterCommandModule;
 import com.google.gerrit.sshd.commands.SlaveCommandModule;
+import com.google.gwtorm.jdbc.JdbcExecutor;
+import com.google.gwtorm.jdbc.JdbcSchema;
+import com.google.gwtorm.server.OrmException;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.gwtorm.server.StatementExecutor;
+import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 import com.google.inject.Provider;
 
+import org.eclipse.jgit.lib.Config;
 import org.kohsuke.args4j.Option;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -145,6 +156,7 @@
       sysInjector = createSysInjector();
       sysInjector.getInstance(PluginGuiceEnvironment.class)
         .setCfgInjector(cfgInjector);
+      sysInjector.getInstance(SchemaUpgrade.class).upgradeSchema();
       manager.add(dbInjector, cfgInjector, sysInjector);
 
       if (sshd) {
@@ -192,6 +204,74 @@
     }
   }
 
+  static class SchemaUpgrade {
+
+    private final Config config;
+    private final SchemaUpdater updater;
+    private final SchemaFactory<ReviewDb> schema;
+
+    @Inject
+    SchemaUpgrade(@GerritServerConfig Config config, SchemaUpdater updater,
+        SchemaFactory<ReviewDb> schema) {
+      this.config = config;
+      this.updater = updater;
+      this.schema = schema;
+    }
+
+    void upgradeSchema() throws OrmException {
+      SchemaUpgradePolicy policy =
+          config.getEnum("site", null, "upgradeSchemaOnStartup",
+              SchemaUpgradePolicy.OFF);
+      if (policy == SchemaUpgradePolicy.AUTO
+          || policy == SchemaUpgradePolicy.AUTO_NO_PRUNE) {
+        final List<String> pruneList = new ArrayList<String>();
+        updater.update(new UpdateUI() {
+          @Override
+          public void message(String msg) {
+            log.info(msg);
+          }
+
+          @Override
+          public boolean yesno(boolean def, String msg) {
+            return true;
+          }
+
+          @Override
+          public boolean isBatch() {
+            return true;
+          }
+
+          @Override
+          public void pruneSchema(StatementExecutor e, List<String> prune) {
+            for (String p : prune) {
+              if (!pruneList.contains(p)) {
+                pruneList.add(p);
+              }
+            }
+          }
+        });
+
+        if (!pruneList.isEmpty() && policy == SchemaUpgradePolicy.AUTO) {
+          log.info("Pruning: " + pruneList.toString());
+          final JdbcSchema db = (JdbcSchema) schema.open();
+          try {
+            final JdbcExecutor e = new JdbcExecutor(db);
+            try {
+              for (String sql : pruneList) {
+                e.execute(sql);
+              }
+            } finally {
+              e.close();
+            }
+          } finally {
+            db.close();
+          }
+        }
+      }
+    }
+  }
+
+
   private String myVersion() {
     return com.google.gerrit.common.Version.getVersion();
   }
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
new file mode 100644
index 0000000..67f5c91
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/SchemaUpgradePolicy.java
@@ -0,0 +1,28 @@
+// Copyright (C) 2012 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.pgm;
+
+/** Policy for auto upgrading schema on server startup */
+public enum SchemaUpgradePolicy {
+
+  /** Perform schema migration if necessary and prune unused objects */
+  AUTO,
+
+  /** Like AUTO but don't prune unused objects */
+  AUTO_NO_PRUNE,
+
+  /** No automatic schema upgrade */
+  OFF
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
index 556ae82..a295c49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ApprovalsUtil.java
@@ -124,14 +124,14 @@
     Account.Id authorId = info.getAuthor() != null
         ? info.getAuthor().getAccount()
         : null;
-    if (authorId != null) {
+    if (authorId != null && !ps.isDraft()) {
       need.add(authorId);
     }
 
     Account.Id committerId = info.getCommitter() != null
         ? info.getCommitter().getAccount()
         : null;
-    if (committerId != null) {
+    if (committerId != null && !ps.isDraft()) {
       need.add(committerId);
     }
     need.remove(change.getOwner());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
index eb42921..1524185 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/CapabilityControl.java
@@ -83,7 +83,7 @@
       || canAdministrateServer();
   }
 
-  /** @return true if the user can create a group. */
+  /** @return true if the user can create a project. */
   public boolean canCreateProject() {
     return canPerform(GlobalCapability.CREATE_PROJECT)
       || canAdministrateServer();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 4db9409..ecf98c2 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -159,6 +159,7 @@
   private CodeReviewCommit mergeTip;
   private Set<RevCommit> alreadyAccepted;
   private RefUpdate branchUpdate;
+  private ObjectInserter inserter;
 
   private final ChangeHooks hooks;
   private final AccountCache accountCache;
@@ -282,6 +283,9 @@
     } catch (OrmException e) {
       throw new MergeException("Cannot query the database", e);
     } finally {
+      if (inserter != null) {
+        inserter.release();
+      }
       if (rw != null) {
         rw.release();
       }
@@ -334,6 +338,8 @@
     rw.sort(RevSort.TOPO);
     rw.sort(RevSort.COMMIT_TIME_DESC, true);
     CAN_MERGE = rw.newFlag("CAN_MERGE");
+
+    inserter = repo.newObjectInserter();
   }
 
   private void openBranch() throws MergeException {
@@ -505,21 +511,10 @@
   }
 
   private void mergeOneCommit(final CodeReviewCommit n) throws MergeException {
-    final ThreeWayMerger m;
-    if (destProject.isUseContentMerge()) {
-      // Settings for this project allow us to try and
-      // automatically resolve conflicts within files if needed.
-      // Use ResolveMerge and instruct to operate in core.
-      m = MergeStrategy.RESOLVE.newMerger(repo, true);
-    } else {
-      // No auto conflict resolving allowed. If any of the
-      // affected files was modified, merge will fail.
-      m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
-    }
-
+    final ThreeWayMerger m = newThreeWayMerger();
     try {
       if (m.merge(new AnyObjectId[] {mergeTip, n})) {
-        writeMergeCommit(m, n);
+        writeMergeCommit(m.getResultTreeId(), n);
 
       } else {
         failed(n, CommitMergeStatus.PATH_CONFLICT);
@@ -537,6 +532,35 @@
     }
   }
 
+  private ThreeWayMerger newThreeWayMerger() {
+    ThreeWayMerger m;
+    if (destProject.isUseContentMerge()) {
+      // Settings for this project allow us to try and
+      // automatically resolve conflicts within files if needed.
+      // Use ResolveMerge and instruct to operate in core.
+      m = MergeStrategy.RESOLVE.newMerger(repo, true);
+    } else {
+      // No auto conflict resolving allowed. If any of the
+      // affected files was modified, merge will fail.
+      m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
+    }
+    m.setObjectInserter(new ObjectInserter.Filter() {
+      @Override
+      protected ObjectInserter delegate() {
+        return inserter;
+      }
+
+      @Override
+      public void flush() {
+      }
+
+      @Override
+      public void release() {
+      }
+    });
+    return m;
+  }
+
   private CodeReviewCommit failed(final CodeReviewCommit n,
       final CommitMergeStatus failure) throws MissingObjectException,
       IncorrectObjectTypeException, IOException {
@@ -550,7 +574,7 @@
     return failed;
   }
 
-  private void writeMergeCommit(final Merger m, final CodeReviewCommit n)
+  private void writeMergeCommit(ObjectId treeId, CodeReviewCommit n)
       throws IOException, MissingObjectException, IncorrectObjectTypeException {
     final List<CodeReviewCommit> merged = new ArrayList<CodeReviewCommit>();
     rw.reset();
@@ -599,13 +623,13 @@
     PersonIdent authorIdent = computeAuthor(merged);
 
     final CommitBuilder mergeCommit = new CommitBuilder();
-    mergeCommit.setTreeId(m.getResultTreeId());
+    mergeCommit.setTreeId(treeId);
     mergeCommit.setParentIds(mergeTip, n);
     mergeCommit.setAuthor(authorIdent);
     mergeCommit.setCommitter(myIdent);
     mergeCommit.setMessage(msgbuf.toString());
 
-    mergeTip = (CodeReviewCommit) rw.parseCommit(commit(m, mergeCommit));
+    mergeTip = (CodeReviewCommit) rw.parseCommit(commit(mergeCommit));
   }
 
   private PersonIdent computeAuthor(
@@ -691,19 +715,7 @@
   private void cherryPickChanges() throws MergeException, OrmException {
     while (!toMerge.isEmpty()) {
       final CodeReviewCommit n = toMerge.remove(0);
-      final ThreeWayMerger m;
-
-      if (destProject.isUseContentMerge()) {
-        // Settings for this project allow us to try and
-        // automatically resolve conflicts within files if needed.
-        // Use ResolveMerge and instruct to operate in core.
-        m = MergeStrategy.RESOLVE.newMerger(repo, true);
-      } else {
-        // No auto conflict resolving allowed. If any of the
-        // affected files was modified, merge will fail.
-        m = MergeStrategy.SIMPLE_TWO_WAY_IN_CORE.newMerger(repo);
-      }
-
+      final ThreeWayMerger m = newThreeWayMerger();
       try {
         if (mergeTip == null) {
           // The branch is unborn. Take a fast-forward resolution to
@@ -898,7 +910,7 @@
     mergeCommit.setCommitter(toCommitterIdent(submitAudit));
     mergeCommit.setMessage(msgbuf.toString());
 
-    final ObjectId id = commit(m, mergeCommit);
+    final ObjectId id = commit(mergeCommit);
     final CodeReviewCommit newCommit = (CodeReviewCommit) rw.parseCommit(id);
     final Change oldChange = n.change;
 
@@ -961,16 +973,11 @@
     db.patchSetAncestors().insert(toInsert);
   }
 
-  private ObjectId commit(final Merger m, final CommitBuilder mergeCommit)
+  private ObjectId commit(CommitBuilder mergeCommit)
       throws IOException, UnsupportedEncodingException {
-    ObjectInserter oi = m.getObjectInserter();
-    try {
-      ObjectId id = oi.insert(mergeCommit);
-      oi.flush();
-      return id;
-    } finally {
-      oi.release();
-    }
+    ObjectId id = inserter.insert(mergeCommit);
+    inserter.flush();
+    return id;
   }
 
   private boolean contains(List<FooterLine> footers, FooterKey key, String val) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index d6e84bb..d27c205 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -255,10 +255,24 @@
 
     ObjectId treeId;
     ResolveMerger m = (ResolveMerger) MergeStrategy.RESOLVE.newMerger(repo, true);
-    ObjectInserter ins = m.getObjectInserter();
+    final ObjectInserter ins = repo.newObjectInserter();
     try {
       DirCache dc = DirCache.newInCore();
       m.setDirCache(dc);
+      m.setObjectInserter(new ObjectInserter.Filter() {
+        @Override
+        protected ObjectInserter delegate() {
+          return ins;
+        }
+
+        @Override
+        public void flush() {
+        }
+
+        @Override
+        public void release() {
+        }
+      });
 
       boolean couldMerge = false;
       try {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
index adf4f19..ba4a74e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ListChanges.java
@@ -22,10 +22,12 @@
 import com.google.gerrit.common.data.SubmitRecord;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Change;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.OutputFormat;
 import com.google.gerrit.server.events.AccountAttribute;
 import com.google.gerrit.server.project.ChangeControl;
@@ -43,6 +45,7 @@
 import java.sql.Timestamp;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -200,6 +203,7 @@
     out._number = in.getId().get();
     out._sortkey = in.getSortKey();
     out.starred = user.getStarredChanges().contains(in.getId()) ? true : null;
+    out.reviewed = isChangeReviewed(cd) ? true : null;
     out.labels = labelsFor(cd);
     return out;
   }
@@ -291,6 +295,37 @@
     return labels;
   }
 
+  private boolean isChangeReviewed(ChangeData cd) throws OrmException {
+    if (user instanceof IdentifiedUser) {
+      PatchSet.Id patchSetId = cd.currentPatchSet(db).getId();
+      List<ChangeMessage> messages =
+          db.get().changeMessages().byPatchSet(patchSetId).toList();
+
+      if (messages.isEmpty()) {
+        return false;
+      }
+
+      // Sort messages to let the most recent ones at the beginning.
+      Collections.sort(messages, new Comparator<ChangeMessage>() {
+        @Override
+        public int compare(ChangeMessage a, ChangeMessage b) {
+          return b.getWrittenOn().compareTo(a.getWrittenOn());
+        }
+      });
+
+      Account.Id currentUserId = ((IdentifiedUser) user).getAccountId();
+      Account.Id changeOwnerId = cd.change(db).getOwner();
+      for (ChangeMessage cm : messages) {
+        if (currentUserId.equals(cm.getAuthor())) {
+          return true;
+        } else if (changeOwnerId.equals(cm.getAuthor())) {
+          return false;
+        }
+      }
+    }
+    return false;
+  }
+
   static class ChangeInfo {
     String project;
     String branch;
@@ -301,6 +336,7 @@
     Timestamp created;
     Timestamp updated;
     Boolean starred;
+    Boolean reviewed;
 
     String _sortkey;
     int _number;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
index 7c7b880..bec2f3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_67.java
@@ -18,7 +18,6 @@
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Sets;
-import com.google.gerrit.common.data.ContributorAgreement;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gwtorm.jdbc.JdbcSchema;
@@ -54,7 +53,6 @@
           "SELECT group_id, owner_group_id FROM account_groups"
           + " WHERE owner_group_uuid is NULL or owner_group_uuid =''");
       try {
-        Map<Integer, ContributorAgreement> agreements = Maps.newHashMap();
         while (rs.next()) {
           AccountGroup.Id groupId = new AccountGroup.Id(rs.getInt(1));
           AccountGroup.Id ownerId = new AccountGroup.Id(rs.getInt(2));
diff --git a/pom.xml b/pom.xml
index 2f5a296..440bd1c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -46,7 +46,7 @@
   </issueManagement>
 
   <properties>
-    <jgitVersion>1.3.0.201202151440-r.190-g65f6e06</jgitVersion>
+    <jgitVersion>2.0.0.201206130900-r.23-gb3dbf19</jgitVersion>
     <gwtormVersion>1.4</gwtormVersion>
     <gwtjsonrpcVersion>1.3</gwtjsonrpcVersion>
     <gwtexpuiVersion>1.2.5</gwtexpuiVersion>