Merge "Allow to schedule a task to run gc for all projects"
diff --git a/.buckconfig b/.buckconfig
index f0422c9..43bfa5e 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -24,4 +24,4 @@
 
 [cache]
   mode = dir
-  dir = buck-out/cache
+  dir = ~/.gerritcodereview/buck-cache/cache
diff --git a/.buckversion b/.buckversion
index 4462205..7a0d3ff 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-75000f4273a399d2cd2768512639da80748877b7
+0c2056b8151692e12b9cb5e03242d1a1af2bd1a1
diff --git a/Documentation/BUCK b/Documentation/BUCK
index 9c2aea8..f070d7e 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -10,33 +10,23 @@
   name = 'html',
   cmd = 'cd $TMP;' +
     'mkdir -p %s/images;' % DOC_DIR +
-    'unzip -q $SRCDIR/only_html.zip -d %s/;' % DOC_DIR +
+    'unzip -q $(location %s) -d %s/;'
+    % (':generate_html', DOC_DIR) +
     'for s in $SRCS;do ln -s $s %s;done;' % DOC_DIR +
     'mv %s/*.{jpg,png} %s/images;' % (DOC_DIR, DOC_DIR) +
-    'rm %s/only_html.zip;' % DOC_DIR +
-    'rm %s/licenses.txt;' % DOC_DIR +
-    'cp $SRCDIR/licenses.txt LICENSES.txt;' +
+    'cp $(location %s) LICENSES.txt;' % ':licenses.txt' +
     'zip -qr $OUT *',
   srcs = glob([
       'images/*.jpg',
       'images/*.png',
-    ]) + [
-      'doc.css',
-      genfile('licenses.txt'),
-      genfile('only_html.zip'),
-    ],
-  deps = [
-    ':generate_html',
-    ':licenses.txt',
-  ],
+    ]) + ['doc.css'],
   out = 'html.zip',
   visibility = ['PUBLIC'],
 )
 
 genasciidoc(
   name = 'generate_html',
-  srcs = SRCS + [genfile('licenses.txt')],
-  deps = [':licenses.txt'],
+  srcs = SRCS + [':licenses.txt'],
   attributes = documentation_attributes(git_describe()),
   backend = 'html5',
   out = 'only_html.zip',
@@ -66,18 +56,14 @@
       '--prefix "%s/" ' % DOC_DIR +
       '--in-ext ".txt" ' +
       '--out-ext ".html" ' +
-      '$SRCS',
-  srcs = SRCS + [genfile('licenses.txt')],
-  deps = [
-    ':licenses.txt',
-    '//lib/asciidoctor:doc_indexer',
-  ],
+      '$SRCS ' +
+      '$(location :licenses.txt)',
+  srcs = SRCS,
   out = 'index.jar',
 )
 
 prebuilt_jar(
   name = 'index_lib',
-  binary_jar = genfile('index.jar'),
-  deps = [':index'],
+  binary_jar = ':index',
   visibility = ['PUBLIC'],
 )
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index b361d48..7adf265 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -16,7 +16,6 @@
     name,
     out,
     srcs = [],
-    deps = [],
     attributes = [],
     backend = None,
     visibility = []):
@@ -35,33 +34,35 @@
     asciidoc.extend(['-a', attribute])
   asciidoc.append('$SRCS')
   newsrcs = ["doc.css"]
-  newdeps = deps + ['//lib/asciidoctor:asciidoc']
-
   for src in srcs:
-    tx = []
     fn = src
-    if fn.startswith('BUCKGEN:') :
-      fn = src[8:]
-      tx = [':' + fn]
+    # We have two cases: regular source files and generated files.
+    # Generated files are passed as targets ':foo', and ':' is removed.
+    # 1. regular files: cmd = '-s foo', srcs = ['foo']
+    # 2. generated files: cmd = '-s $(location :foo)', srcs = []
+    srcs = [src]
+    passed_src = fn
+    if fn.startswith(':') :
+      fn = src[1:]
+      srcs = []
+      passed_src = '$(location :%s)' % fn
     ex = fn + EXPN
 
     genrule(
       name = ex,
       cmd = '$(exe :replace_macros) --suffix=' + EXPN +
-            ' -s %s' % fn +
+            ' -s ' + passed_src +
             ' -o $OUT',
-      srcs = [src],
-      deps = tx + [':replace_macros'],
+      srcs = srcs,
       out = ex,
     )
-    newdeps.append(':' + ex)
-    newsrcs.append(genfile(ex))
+
+    asciidoc.append('$(location :%s)' % ex)
 
   genrule(
     name = name,
     cmd = ' '.join(asciidoc),
     srcs = newsrcs,
-    deps = newdeps,
     out = out,
     visibility = visibility,
   )
diff --git a/Documentation/config-labels.txt b/Documentation/config-labels.txt
index 0fe12ca..aaeb834 100644
--- a/Documentation/config-labels.txt
+++ b/Documentation/config-labels.txt
@@ -183,14 +183,6 @@
 box will be either the lowest or highest score in the permissible range.
 
 
-[[label_abbreviation]]
-=== `label.Label-Name.abbreviation`
-
-An abbreviated name for a label shown as a compact column header, for
-example on project dashboards. Defaults to all the uppercase characters
-in the label name, e.g. `Label-Name` is abbreviated by default as `LN`.
-
-
 [[label_function]]
 === `label.Label-Name.function`
 
diff --git a/Documentation/config-reverseproxy.txt b/Documentation/config-reverseproxy.txt
index 0c52be6..dde2661 100644
--- a/Documentation/config-reverseproxy.txt
+++ b/Documentation/config-reverseproxy.txt
@@ -135,6 +135,14 @@
 Make sure to use a 'proxy_pass' URL without any path (esp. no trailing
 '/' after the 'host:port').
 
+If you are using Apache httpd server with mod_jk and AJP connector, add
+the following option to your httpd.conf directly or included from another
+file:
+
+----
+JkOptions +ForwardURICompatUnparsed
+----
+
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 2674398..492bfb3 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -5,7 +5,7 @@
 
 There is currently no binary distribution of Buck, so it has to be manually
 built and installed.  Apache Ant is required.  Currently only Linux and Mac
-OS are supported.  Gerrit's buck wrappers require Python version 2.6 or higher.
+OS are supported.  Buck requires Python version 2.7 to be installed.
 
 Clone the git and build it:
 
diff --git a/Documentation/dev-contributing.txt b/Documentation/dev-contributing.txt
index 5e307f9..95a5554 100644
--- a/Documentation/dev-contributing.txt
+++ b/Documentation/dev-contributing.txt
@@ -171,11 +171,13 @@
   * Define any static interfaces next in your class.
   * Define non static interfaces after static interfaces in your
     class.
-  * Next you should define static types and members.
+  * Next you should define static types, members, and methods, in
+    decreasing order of visibility (public to private).
   * Finally instance members, then constructors, and then instance
     methods.
-  * Some common exceptions are private helper static methods which
-    might appear near the instance methods which they help.
+  * Some common exceptions are private helper static methods, which
+    might appear near the instance methods which they help (but may
+    also appear at the top).
   * Getters and setters for the same instance field should usually
     be near each other baring a good reason not to.
   * If you are using assisted injection, the factory for your class
diff --git a/Documentation/images/user-review-ui-side-by-side-diff-screen-rename.png b/Documentation/images/user-review-ui-side-by-side-diff-screen-rename.png
new file mode 100644
index 0000000..b4d83ba
--- /dev/null
+++ b/Documentation/images/user-review-ui-side-by-side-diff-screen-rename.png
Binary files differ
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 5d7fe95..49ecc44 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -490,7 +490,10 @@
 ----
 
 As response a link:#change-info[ChangeInfo] entity is returned that
-describes the change.
+describes the change.  This response will contain all votes for each
+label and include one combined vote.  The combined label vote is
+calculated in the following order (from highest to lowest):
+REJECTED > APPROVED > DISLIKED > RECOMMENDED.
 
 .Response
 ----
@@ -544,12 +547,6 @@
         }
       },
       "Code-Review": {
-        "recommended": {
-          "_account_id": 1000097,
-          "name": "Jane Roe",
-          "email": "jane.roe@example.com",
-          "username": "jroe"
-        },
         "disliked": {
           "_account_id": 1000096,
           "name": "John Doe",
@@ -2923,7 +2920,7 @@
 
 [[comment-input]]
 === CommentInput
-The `CommitInput` entity contains information for creating an inline
+The `CommentInput` entity contains information for creating an inline
 comment.
 
 [options="header",width="50%",cols="1,^1,5"]
diff --git a/Documentation/user-review-ui.txt b/Documentation/user-review-ui.txt
index 00b957e..81ceb06 100644
--- a/Documentation/user-review-ui.txt
+++ b/Documentation/user-review-ui.txt
@@ -756,14 +756,20 @@
 
 image::images/user-review-ui-side-by-side-diff-screen-patch-sets.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-patch-sets.png"]
 
+The download icon next to the patch set list allows to download the
+patch. Unless the mime type of the file is configured as safe, the
+download file is a zip archive that contains the patch file.
+
 If the compared patches are identical, this is highlighted by a red
 `No Differences` label in the screen header.
 
 image::images/user-review-ui-side-by-side-diff-screen-no-differences.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-no-differences.png"]
 
-The download icon next to the patch set list allows to download the
-patch. Unless the mime type of the file is configured as safe, the
-download file is a zip archive that contains the patch file.
+If a file was renamed, the old and new file paths are shown in the
+header together with a similarity index that shows how much of the file
+content is unmodified.
+
+image::images/user-review-ui-side-by-side-diff-screen-rename.png[width=800, link="images/user-review-ui-side-by-side-diff-screen-rename.png"]
 
 For navigating between the patches in a patch set there are navigation
 buttons on the right side of the screen header. The left arrow button
diff --git a/bucklets/gerrit_plugin.bucklet b/bucklets/gerrit_plugin.bucklet
new file mode 100644
index 0000000..eb10456
--- /dev/null
+++ b/bucklets/gerrit_plugin.bucklet
@@ -0,0 +1,15 @@
+#
+# Dummy to make the co-existence of core and standalone plugins possible.
+# Intentionaly left empty as this doesn't suppose to have any side effects
+# in tree build, i. e.:
+#
+# cookbook-plugin/BUCK include this line:
+# include_defs('//bucklets/gerrit_plugin.bucklet')
+#
+# When executing from the Gerrit tree:
+# buck build plugins/cookbook-plugin
+#
+# this line has no effect.
+#
+# When compiling from standalone cookbook-plugin, bucklets directory points
+# to cloned bucklets library that includes real gerrit_plugin.bucklet code.
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
index 5c5746b..fc55411 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/AbstractDaemonTest.java
@@ -21,6 +21,7 @@
 
 import com.google.common.base.Joiner;
 import com.google.common.primitives.Chars;
+import com.google.gerrit.acceptance.AcceptanceTestRequestScope.Context;
 import com.google.gerrit.extensions.api.GerritApi;
 import com.google.gerrit.extensions.api.changes.RevisionApi;
 import com.google.gerrit.extensions.common.ChangeInfo;
@@ -128,9 +129,9 @@
     userSession = new RestSession(server, user);
     initSsh(admin);
     db = reviewDbProvider.open();
-    atrScope.set(atrScope.newContext(reviewDbProvider, sshSession,
-        identifiedUserFactory.create(Providers.of(db), admin.getId())));
-    sshSession = new SshSession(server, admin);
+    Context ctx = newRequestContext(admin);
+    atrScope.set(ctx);
+    sshSession = ctx.getSession();
     project = new Project.NameKey("p");
     createProject(sshSession, project.get());
     git = cloneProject(sshSession.getUrl() + "/" + project.get());
@@ -194,6 +195,19 @@
     return gApi.changes().id(id).get(s);
   }
 
+  protected List<ChangeInfo> query(String q) throws RestApiException {
+    return gApi.changes().query(q).get();
+  }
+
+  private Context newRequestContext(TestAccount account) {
+    return atrScope.newContext(reviewDbProvider, new SshSession(server, admin),
+        identifiedUserFactory.create(Providers.of(db), account.getId()));
+  }
+
+  protected Context setApiUser(TestAccount account) {
+    return atrScope.set(newRequestContext(account));
+  }
+
   protected static Gson newGson() {
     return OutputFormat.JSON_COMPACT.newGson();
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index a01525a..c79b198 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -186,9 +186,9 @@
   @Test
   public void queryChangesNoResults() throws Exception {
     createChange();
-    List<ChangeInfo> results = gApi.changes().query("status:open").get();
+    List<ChangeInfo> results = query("status:open");
     assertEquals(1, results.size());
-    results = gApi.changes().query("status:closed").get();
+    results = query("status:closed");
     assertTrue(results.isEmpty());
   }
 
@@ -196,7 +196,7 @@
   public void queryChangesOneTerm() throws Exception {
     PushOneCommit.Result r1 = createChange();
     PushOneCommit.Result r2 = createChange();
-    List<ChangeInfo> results = gApi.changes().query("status:open").get();
+    List<ChangeInfo> results = query("status:open");
     assertEquals(2, results.size());
     assertEquals(r2.getChangeId(), results.get(0).changeId);
     assertEquals(r1.getChangeId(), results.get(1).changeId);
@@ -206,9 +206,7 @@
   public void queryChangesMultipleTerms() throws Exception {
     PushOneCommit.Result r1 = createChange();
     createChange();
-    List<ChangeInfo> results = gApi.changes()
-        .query("status:open " + r1.getChangeId())
-        .get();
+    List<ChangeInfo> results = query("status:open " + r1.getChangeId());
     assertEquals(r1.getChangeId(), Iterables.getOnlyElement(results).changeId);
   }
 
@@ -232,8 +230,7 @@
   @Test
   public void queryChangesNoOptions() throws Exception {
     PushOneCommit.Result r = createChange();
-    ChangeInfo result = Iterables.getOnlyElement(
-        gApi.changes().query(r.getChangeId()).get());
+    ChangeInfo result = Iterables.getOnlyElement(query(r.getChangeId()));
     assertNull(result.labels);
     assertNull(result.messages);
     assertNull(result.revisions);
@@ -256,4 +253,29 @@
     assertEquals(r.getPatchSetId().get(), rev._number);
     assertFalse(rev.actions.isEmpty());
   }
+
+  @Test
+  public void queryChangesOwnerWithDifferentUsers() throws Exception {
+    PushOneCommit.Result r = createChange();
+    assertEquals(r.getChangeId(),
+        Iterables.getOnlyElement(query("owner:self")).changeId);
+    setApiUser(user);
+    assertTrue(query("owner:self").isEmpty());
+  }
+
+  @Test
+  public void checkReviewedFlagBeforeAndAfterReview() throws Exception {
+    PushOneCommit.Result r = createChange();
+    AddReviewerInput in = new AddReviewerInput();
+    in.reviewer = user.email;
+    gApi.changes()
+        .id(r.getChangeId())
+        .addReviewer(in);
+
+    setApiUser(user);
+    assertNull(get(r.getChangeId()).reviewed);
+
+    revision(r).review(ReviewInput.recommend());
+    assertTrue(get(r.getChangeId()).reviewed);
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
index d63d195..f42a134 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/BUCK
@@ -10,6 +10,10 @@
 
 acceptance_tests(
   srcs = OTHER_TESTS,
+  deps = [
+    ':submit_util',
+    '//lib/joda:joda-time',
+  ],
   labels = ['rest'],
 )
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
index 6352b50..acf50db 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -14,6 +14,8 @@
 
 package com.google.gerrit.acceptance.rest.change;
 
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
@@ -23,14 +25,55 @@
 import com.google.gerrit.extensions.common.ChangeInfo;
 import com.google.gerrit.extensions.common.ChangeMessageInfo;
 import com.google.gerrit.extensions.restapi.RestApiException;
+import com.google.gerrit.testutil.ConfigSuite;
 
 import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.lib.Config;
+import org.joda.time.DateTime;
+import org.joda.time.DateTimeUtils;
+import org.joda.time.DateTimeUtils.MillisProvider;
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
 
 import java.io.IOException;
 import java.util.Iterator;
+import java.util.concurrent.atomic.AtomicLong;
 
+@RunWith(ConfigSuite.class)
 public class ChangeMessagesIT extends AbstractDaemonTest {
+  private String systemTimeZone;
+  private volatile long clockStepMs;
+
+  @ConfigSuite.Config
+  public static Config noteDbEnabled() {
+    Config cfg = new Config();
+    cfg.setBoolean("notedb", null, "write", true);
+    cfg.setBoolean("notedb", "changeMessages", "read", true);
+    return cfg;
+  }
+
+  @Before
+  public void setTimeForTesting() {
+    systemTimeZone = System.setProperty("user.timezone", "US/Eastern");
+    clockStepMs = MILLISECONDS.convert(1, SECONDS);
+    final AtomicLong clockMs = new AtomicLong(
+        new DateTime(2009, 9, 30, 17, 0, 0).getMillis());
+
+    DateTimeUtils.setCurrentMillisProvider(new MillisProvider() {
+      @Override
+      public long getMillis() {
+        return clockMs.getAndAdd(clockStepMs);
+      }
+    });
+  }
+
+  @After
+  public void resetTime() {
+    DateTimeUtils.setCurrentMillisSystem();
+    System.setProperty("user.timezone", systemTimeZone);
+  }
 
   @Test
   public void messagesNotReturnedByDefault() throws Exception {
diff --git a/gerrit-antlr/BUCK b/gerrit-antlr/BUCK
index 57e7e1d..03c3c1e 100644
--- a/gerrit-antlr/BUCK
+++ b/gerrit-antlr/BUCK
@@ -17,8 +17,8 @@
 
 java_library(
   name = 'lib',
-  srcs = [genfile('query_antlr.src.zip')],
-  deps = PARSER_DEPS + [':query_antlr'],
+  srcs = [':query_antlr'],
+  deps = PARSER_DEPS,
 )
 
 # Hack necessary to expose ANTLR generated code as JAR to Eclipse.
@@ -28,9 +28,10 @@
   deps = [':lib'],
   out = 'query_parser.jar',
 )
+
 prebuilt_jar(
   name = 'query_parser',
-  binary_jar = genfile('query_parser.jar'),
-  deps = PARSER_DEPS + [':query_link'],
+  binary_jar = ':query_link',
+  deps = PARSER_DEPS,
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
index 28629b9..337aefd 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/LabelType.java
@@ -60,7 +60,7 @@
     return name;
   }
 
-  public static String defaultAbbreviation(String name) {
+  private static String getAbbreviation(String name) {
     StringBuilder abbr = new StringBuilder();
     for (int i = 0; i < name.length(); i++) {
       char c = name.charAt(i);
@@ -128,7 +128,7 @@
     values = sortValues(valueList);
     defaultValue = 0;
 
-    abbreviation = defaultAbbreviation(name);
+    abbreviation = getAbbreviation(name);
     functionName = "MaxWithBlock";
 
     maxNegative = Short.MIN_VALUE;
@@ -151,14 +151,6 @@
     return psa.getLabelId().get().equalsIgnoreCase(name);
   }
 
-  public String getAbbreviation() {
-    return abbreviation;
-  }
-
-  public void setAbbreviation(String abbreviation) {
-    this.abbreviation = abbreviation;
-  }
-
   public String getFunctionName() {
     return functionName;
   }
diff --git a/gerrit-gwtui/BUCK b/gerrit-gwtui/BUCK
index 8dc277a..f1c9e6b 100644
--- a/gerrit-gwtui/BUCK
+++ b/gerrit-gwtui/BUCK
@@ -95,11 +95,10 @@
 
 prebuilt_jar(
   name = 'diffy_logo',
-  binary_jar = genfile('diffy_images.jar'),
+  binary_jar = ':diffy_image_files_ln',
   deps = [
     '//lib:LICENSE-diffy',
     '//lib:LICENSE-CC-BY3.0',
-    ':diffy_image_files_ln',
   ],
 )
 
diff --git a/gerrit-gwtui/gwt.defs b/gerrit-gwtui/gwt.defs
index b903977..0e10116 100644
--- a/gerrit-gwtui/gwt.defs
+++ b/gerrit-gwtui/gwt.defs
@@ -63,9 +63,8 @@
     )
     prebuilt_jar(
       name = '%s_gwtxml_lib' % gwt_name,
-      binary_jar = genfile(jar),
-      gwt_jar = genfile(jar),
-      deps = [':%s_gwtxml_gen' % gwt_name],
+      binary_jar = ':%s_gwtxml_gen' % gwt_name,
+      gwt_jar = ':%s_gwtxml_gen' % gwt_name,
     )
     gwt_binary(
       name = gwt_name,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
index b472bbd..d315fa3 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/CacheBasedWebSession.java
@@ -68,7 +68,8 @@
     this.anonymousProvider = anonymousProvider;
     this.identified = identified;
 
-    if (!GitSmartHttpTools.isGitClient(request)) {
+    if (request.getRequestURI() == null
+        || !GitSmartHttpTools.isGitClient(request)) {
       String cookie = readCookie();
       if (cookie != null) {
         key = new Key(cookie);
@@ -184,6 +185,10 @@
   }
 
   private void saveCookie() {
+    if (response == null) {
+      return;
+    }
+
     final String token;
     final int ageSeconds;
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/H2CacheBasedWebSession.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/H2CacheBasedWebSession.java
index 3dfb9fb..4b68f02 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/H2CacheBasedWebSession.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/H2CacheBasedWebSession.java
@@ -17,6 +17,7 @@
 import static java.util.concurrent.TimeUnit.MINUTES;
 
 import com.google.common.cache.Cache;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.registration.DynamicItem;
 import com.google.gerrit.httpd.WebSessionManager.Val;
 import com.google.gerrit.server.AnonymousUser;
@@ -56,7 +57,7 @@
   @Inject
   H2CacheBasedWebSession(
       HttpServletRequest request,
-      HttpServletResponse response,
+      @Nullable HttpServletResponse response,
       WebSessionManagerFactory managerFactory,
       @Named(WebSessionManager.CACHE_NAME) Cache<String, Val> cache,
       AuthConfig authConfig,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
index f831ab5..2902335 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/gitweb/GitWebServlet.java
@@ -34,6 +34,7 @@
 import com.google.gerrit.httpd.GitWebConfig;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.config.SitePaths;
 import com.google.gerrit.server.git.LocalDiskRepositoryManager;
@@ -85,18 +86,21 @@
   private final LocalDiskRepositoryManager repoManager;
   private final ProjectControl.Factory projectControl;
   private final Provider<AnonymousUser> anonymousUserProvider;
+  private final Provider<CurrentUser> userProvider;
   private final EnvList _env;
 
   @Inject
   GitWebServlet(final LocalDiskRepositoryManager repoManager,
       final ProjectControl.Factory projectControl,
       final Provider<AnonymousUser> anonymousUserProvider,
+      final Provider<CurrentUser> userProvider,
       final SitePaths site,
       final GerritConfig gerritConfig, final GitWebConfig gitWebConfig)
       throws IOException {
     this.repoManager = repoManager;
     this.projectControl = projectControl;
     this.anonymousUserProvider = anonymousUserProvider;
+    this.userProvider = userProvider;
     this.gitwebCgi = gitWebConfig.getGitwebCGI();
     this.deniedActions = new HashSet<>();
 
@@ -377,7 +381,14 @@
         throw new NoSuchProjectException(nameKey);
       }
     } catch (NoSuchProjectException e) {
-      rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+      if (userProvider.get().isIdentifiedUser()) {
+        rsp.sendError(HttpServletResponse.SC_NOT_FOUND);
+      } else {
+        // Allow anonymous users a chance to login.
+        // Avoid leaking information by not distinguishing between
+        // project not existing and no access rights.
+        rsp.sendError(HttpServletResponse.SC_UNAUTHORIZED);
+      }
       return;
     }
 
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 45b55c1..907760b 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
@@ -29,6 +29,7 @@
 import com.google.gerrit.server.plugins.Plugin;
 import com.google.gerrit.server.plugins.PluginsCollection;
 import com.google.gerrit.server.plugins.ReloadPluginListener;
+import com.google.gerrit.server.plugins.ServerPlugin;
 import com.google.gerrit.server.plugins.StartPluginListener;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gwtexpui.server.CacheHeaders;
@@ -82,6 +83,13 @@
   private static final long serialVersionUID = 1L;
   private static final Logger log
       = LoggerFactory.getLogger(HttpPluginServlet.class);
+  private static final Comparator<JarEntry> JAR_ENTRY_COMPARATOR_BY_NAME =
+      new Comparator<JarEntry>() {
+        @Override
+        public int compare(JarEntry a, JarEntry b) {
+          return a.getName().compareTo(b.getName());
+        }
+      };
 
   private final MimeUtilFileTypeRegistry mimeUtil;
   private final Provider<String> webUrl;
@@ -268,7 +276,7 @@
     }
 
     if (file.startsWith(holder.staticPrefix)) {
-      JarFile jar = holder.plugin.getJarFile();
+      JarFile jar = jarFileOf(holder.plugin);
       if (jar != null) {
         JarEntry entry = jar.getJarEntry(file);
         if (exists(entry)) {
@@ -286,7 +294,7 @@
     } else if (file.startsWith(holder.docPrefix) && file.endsWith("/")) {
       res.sendRedirect(uri + "index.html");
     } else if (file.startsWith(holder.docPrefix)) {
-      JarFile jar = holder.plugin.getJarFile();
+      JarFile jar = jarFileOf(holder.plugin);
       JarEntry entry = jar.getJarEntry(file);
       if (!exists(entry)) {
         entry = findSource(jar, file);
@@ -368,18 +376,9 @@
         }
       }
     }
-    Collections.sort(cmds, new Comparator<JarEntry>() {
-      @Override
-      public int compare(JarEntry a, JarEntry b) {
-        return a.getName().compareTo(b.getName());
-      }
-    });
-    Collections.sort(docs, new Comparator<JarEntry>() {
-      @Override
-      public int compare(JarEntry a, JarEntry b) {
-        return a.getName().compareTo(b.getName());
-      }
-    });
+
+    Collections.sort(cmds, JAR_ENTRY_COMPARATOR_BY_NAME);
+    Collections.sort(docs, JAR_ENTRY_COMPARATOR_BY_NAME);
 
     StringBuilder md = new StringBuilder();
     md.append(String.format("# Plugin %s #\n", pluginName));
@@ -632,6 +631,14 @@
     return data;
   }
 
+  private static JarFile jarFileOf(Plugin plugin) {
+    if(plugin instanceof ServerPlugin) {
+      return ((ServerPlugin) plugin).getJarFile();
+    } else {
+      return null;
+    }
+  }
+
   private static class PluginHolder {
     final Plugin plugin;
     final GuiceFilter filter;
@@ -648,7 +655,7 @@
     }
 
     private static String getPrefix(Plugin plugin, String attr, String def) {
-      JarFile jarFile = plugin.getJarFile();
+      JarFile jarFile = jarFileOf(plugin);
       if (jarFile == null) {
         return def;
       }
diff --git a/gerrit-plugin-gwtui/BUCK b/gerrit-plugin-gwtui/BUCK
index 911016c..419176d 100644
--- a/gerrit-plugin-gwtui/BUCK
+++ b/gerrit-plugin-gwtui/BUCK
@@ -26,7 +26,7 @@
   name = 'gwtui-api-lib2',
   srcs = SRCS,
   resources = glob(['src/main/**/*']),
-  deps = ['//gerrit-gwtui-common:client-lib2'],
+  exported_deps = ['//gerrit-gwtui-common:client-lib2'],
   provided_deps = DEPS,
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 86f77db..dbce048 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -415,6 +415,7 @@
     subject = other.subject;
     topic = other.topic;
     mergeable = other.mergeable;
+    lastSha1MergeTested = other.lastSha1MergeTested;
   }
 
   /** Legacy 32 bit integer identity for a change. */
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
new file mode 100644
index 0000000..6ef04fb
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/ChangeMessagesUtil.java
@@ -0,0 +1,76 @@
+// Copyright (C) 2014 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.server;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.gerrit.reviewdb.client.ChangeMessage;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility functions to manipulate ChangeMessages.
+ * <p>
+ * These methods either query for and update ChangeMessages in the NoteDb or
+ * ReviewDb, depending on the state of the NotesMigration.
+ */
+@Singleton
+public class ChangeMessagesUtil {
+  private static List<ChangeMessage> sortChangeMessages(
+      Iterable<ChangeMessage> changeMessage) {
+    return ChangeNotes.MESSAGE_BY_TIME.sortedCopy(changeMessage);
+  }
+
+  private final NotesMigration migration;
+
+  @VisibleForTesting
+  @Inject
+  public ChangeMessagesUtil(NotesMigration migration) {
+    this.migration = migration;
+  }
+
+  public List<ChangeMessage> byChange(ReviewDb db, ChangeNotes notes) throws OrmException {
+    if (!migration.readChangeMessages()) {
+      return
+          sortChangeMessages(db.changeMessages().byChange(notes.getChangeId()));
+    } else {
+      return sortChangeMessages(notes.load().getChangeMessages().values());
+    }
+  }
+
+  public List<ChangeMessage> byPatchSet(ReviewDb db, ChangeNotes notes,
+      PatchSet.Id psId) throws OrmException {
+    if (!migration.readChangeMessages()) {
+      return sortChangeMessages(db.changeMessages().byPatchSet(psId));
+    }
+    return notes.load().getChangeMessages().get(psId);
+  }
+
+  public void addChangeMessage(ReviewDb db, ChangeUpdate update,
+      ChangeMessage changeMessage) throws OrmException {
+    if (changeMessage != null) {
+      update.setChangeMessage(changeMessage.getMessage());
+      db.changeMessages().insert(Collections.singleton(changeMessage));
+    }
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
index b083850..388e1cf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Abandon.java
@@ -25,12 +25,14 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.mail.AbandonedSender;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
@@ -42,7 +44,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
 
 @Singleton
 public class Abandon implements RestModifyView<ChangeResource, AbandonInput>,
@@ -54,18 +55,24 @@
   private final Provider<ReviewDb> dbProvider;
   private final ChangeJson json;
   private final ChangeIndexer indexer;
+  private final ChangeUpdate.Factory updateFactory;
+  private final ChangeMessagesUtil cmUtil;
 
   @Inject
   Abandon(ChangeHooks hooks,
       AbandonedSender.Factory abandonedSenderFactory,
       Provider<ReviewDb> dbProvider,
       ChangeJson json,
-      ChangeIndexer indexer) {
+      ChangeIndexer indexer,
+      ChangeUpdate.Factory updateFactory,
+      ChangeMessagesUtil cmUtil) {
     this.hooks = hooks;
     this.abandonedSenderFactory = abandonedSenderFactory;
     this.dbProvider = dbProvider;
     this.json = json;
     this.indexer = indexer;
+    this.updateFactory = updateFactory;
+    this.cmUtil = cmUtil;
   }
 
   @Override
@@ -84,6 +91,7 @@
     }
 
     ChangeMessage message;
+    ChangeUpdate update;
     ReviewDb db = dbProvider.get();
     db.changes().beginTransaction(change.getId());
     try {
@@ -104,12 +112,16 @@
         throw new ResourceConflictException("change is "
             + status(db.changes().get(req.getChange().getId())));
       }
+
+      //TODO(yyonas): atomic update was not propagated
+      update = updateFactory.create(control, change.getLastUpdatedOn());
       message = newMessage(input, caller, change);
-      db.changeMessages().insert(Collections.singleton(message));
+      cmUtil.addChangeMessage(db, update, message);
       db.commit();
     } finally {
       db.rollback();
     }
+    update.commit();
 
     CheckedFuture<?, IOException> indexFuture =
         indexer.indexAsync(change.getId());
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 d8344f8..3c210d4 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
@@ -27,6 +27,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.mail.CreateChangeSender;
@@ -62,6 +63,7 @@
   private final GitReferenceUpdated gitRefUpdated;
   private final ChangeHooks hooks;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
   private final MergeabilityChecker mergeabilityChecker;
   private final CreateChangeSender.Factory createChangeSenderFactory;
 
@@ -85,6 +87,7 @@
       GitReferenceUpdated gitRefUpdated,
       ChangeHooks hooks,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       MergeabilityChecker mergeabilityChecker,
       CreateChangeSender.Factory createChangeSenderFactory,
       @Assisted RefControl refControl,
@@ -95,6 +98,7 @@
     this.gitRefUpdated = gitRefUpdated;
     this.hooks = hooks;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.mergeabilityChecker = mergeabilityChecker;
     this.createChangeSenderFactory = createChangeSenderFactory;
     this.refControl = refControl;
@@ -181,20 +185,22 @@
       approvalsUtil.addApprovals(db, update, labelTypes, patchSet, patchSetInfo,
           change, ctl, approvals);
       if (messageIsForChange()) {
-        insertMessage(db);
+        cmUtil.addChangeMessage(db, update, changeMessage);
       }
       db.commit();
     } finally {
       db.rollback();
     }
-
-    update.commit();
+    if (messageIsForChange()) {
+      update.commit();
+    }
     CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
         .addChange(change)
         .reindex()
         .runAsync();
-    if (!messageIsForChange()) {
-      insertMessage(db);
+
+    if(!messageIsForChange()) {
+      commitMessageNotForChange();
     }
     gitRefUpdated.fire(change.getProject(), patchSet.getRefName(),
         ObjectId.zeroId(), commit);
@@ -220,14 +226,23 @@
     return change;
   }
 
+  private void commitMessageNotForChange() throws OrmException,
+      IOException {
+    ReviewDb db = dbProvider.get();
+    if (changeMessage != null) {
+      Change otherChange =
+          db.changes().get(changeMessage.getPatchSetId().getParentKey());
+      ChangeControl otherControl =
+          refControl.getProjectControl().controlFor(otherChange);
+      ChangeUpdate updateForOtherChange =
+          updateFactory.create(otherControl, change.getLastUpdatedOn());
+      cmUtil.addChangeMessage(db, updateForOtherChange, changeMessage);
+      updateForOtherChange.commit();
+    }
+  }
+
   private boolean messageIsForChange() {
     return changeMessage != null
         && changeMessage.getKey().getParentKey().equals(change.getKey());
   }
-
-  private void insertMessage(ReviewDb db) throws OrmException {
-    if (changeMessage != null) {
-      db.changeMessages().insert(Collections.singleton(changeMessage));
-    }
-  }
 }
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 067affa..653310d 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
@@ -74,12 +74,14 @@
 import com.google.gerrit.reviewdb.client.UserIdentity;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.AnonymousUser;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.WebLinks;
 import com.google.gerrit.server.account.AccountInfo;
 import com.google.gerrit.server.extensions.webui.UiActions;
 import com.google.gerrit.server.git.LabelNormalizer;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
@@ -88,7 +90,6 @@
 import com.google.gerrit.server.query.change.ChangeData;
 import com.google.gerrit.server.query.change.ChangeData.ChangedLines;
 import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
 import com.google.inject.Inject;
 import com.google.inject.Provider;
 
@@ -100,7 +101,6 @@
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.EnumSet;
-import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -108,22 +108,8 @@
 
 public class ChangeJson {
   private static final Logger log = LoggerFactory.getLogger(ChangeJson.class);
-  private static final ResultSet<ChangeMessage> NO_MESSAGES =
-      new ResultSet<ChangeMessage>() {
-        @Override
-        public Iterator<ChangeMessage> iterator() {
-          return toList().iterator();
-        }
-
-        @Override
-        public List<ChangeMessage> toList() {
-          return Collections.emptyList();
-        }
-
-        @Override
-        public void close() {
-        }
-      };
+  private static final List<ChangeMessage> NO_MESSAGES =
+      ImmutableList.of();
 
   private final Provider<ReviewDb> db;
   private final LabelNormalizer labelNormalizer;
@@ -140,6 +126,7 @@
   private final Revisions revisions;
   private final Provider<WebLinks> webLinks;
   private final EnumSet<ListChangesOption> options;
+  private final ChangeMessagesUtil cmUtil;
 
   private AccountInfo.Loader accountLoader;
 
@@ -159,7 +146,8 @@
       DynamicMap<DownloadCommand> downloadCommands,
       DynamicMap<RestView<ChangeResource>> changeViews,
       Revisions revisions,
-      Provider<WebLinks> webLinks) {
+      Provider<WebLinks> webLinks,
+      ChangeMessagesUtil cmUtil) {
     this.db = db;
     this.labelNormalizer = ln;
     this.userProvider = user;
@@ -174,6 +162,7 @@
     this.changeViews = changeViews;
     this.revisions = revisions;
     this.webLinks = webLinks;
+    this.cmUtil = cmUtil;
     options = EnumSet.noneOf(ListChangesOption.class);
   }
 
@@ -642,8 +631,7 @@
   private Collection<ChangeMessageInfo> messages(ChangeControl ctl, ChangeData cd,
       Map<PatchSet.Id, PatchSet> map)
       throws OrmException {
-    List<ChangeMessage> messages =
-        db.get().changeMessages().byChange(cd.getId()).toList();
+    List<ChangeMessage> messages = cmUtil.byChange(db.get(), cd.notes());
     if (messages.isEmpty()) {
       return Collections.emptyList();
     }
@@ -705,18 +693,18 @@
     if (userProvider.get().isIdentifiedUser()) {
       Account.Id self = ((IdentifiedUser) userProvider.get()).getAccountId();
       for (List<ChangeData> batch : Iterables.partition(all, 50)) {
-        List<ResultSet<ChangeMessage>> m =
+        List<List<ChangeMessage>> m =
             Lists.newArrayListWithCapacity(batch.size());
         for (ChangeData cd : batch) {
           PatchSet.Id ps = cd.change().currentPatchSetId();
           if (ps != null && cd.change().getStatus().isOpen()) {
-            m.add(db.get().changeMessages().byPatchSet(ps));
+            m.add(cmUtil.byPatchSet(db.get(), cd.notes(), ps));
           } else {
             m.add(NO_MESSAGES);
           }
         }
         for (int i = 0; i < m.size(); i++) {
-          if (isChangeReviewed(self, batch.get(i), m.get(i).toList())) {
+          if (isChangeReviewed(self, batch.get(i), m.get(i))) {
             reviewed.add(batch.get(i).getId());
           }
         }
@@ -728,12 +716,8 @@
   private boolean isChangeReviewed(Account.Id self, ChangeData cd,
       List<ChangeMessage> msgs) throws OrmException {
     // Sort messages to keep the most recent ones at the beginning.
-    Collections.sort(msgs, new Comparator<ChangeMessage>() {
-      @Override
-      public int compare(ChangeMessage a, ChangeMessage b) {
-        return b.getWrittenOn().compareTo(a.getWrittenOn());
-      }
-    });
+    msgs = ChangeNotes.MESSAGE_BY_TIME.sortedCopy(msgs);
+    Collections.reverse(msgs);
 
     Account.Id changeOwnerId = cd.change().getOwner();
     for (ChangeMessage cm : msgs) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
index a1245fb..3c53f7b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/DeleteReviewer.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.DeleteReviewer.Input;
@@ -40,7 +41,6 @@
 import com.google.inject.Singleton;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 
 @Singleton
@@ -51,6 +51,7 @@
   private final Provider<ReviewDb> dbProvider;
   private final ChangeUpdate.Factory updateFactory;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
   private final ChangeIndexer indexer;
   private final IdentifiedUser.GenericFactory userFactory;
 
@@ -58,11 +59,13 @@
   DeleteReviewer(Provider<ReviewDb> dbProvider,
       ChangeUpdate.Factory updateFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       ChangeIndexer indexer,
       IdentifiedUser.GenericFactory userFactory) {
     this.dbProvider = dbProvider;
     this.updateFactory = updateFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.indexer = indexer;
     this.userFactory = userFactory;
   }
@@ -111,7 +114,7 @@
                 ((IdentifiedUser) control.getCurrentUser()).getAccountId(),
                 TimeUtil.nowTs(), rsrc.getChange().currentPatchSetId());
         changeMessage.setMessage(msg.toString());
-        db.changeMessages().insert(Collections.singleton(changeMessage));
+        cmUtil.addChangeMessage(db, update, changeMessage);
       }
 
       db.commit();
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 c059bf4..044e899 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
@@ -29,6 +29,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalCopier;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.events.CommitReceivedEvent;
@@ -41,6 +42,7 @@
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.InvalidChangeOperationException;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.ssh.NoSshInfo;
 import com.google.gerrit.server.ssh.SshInfo;
 import com.google.gerrit.server.util.TimeUtil;
@@ -83,12 +85,14 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final ReviewDb db;
   private final ChangeUpdate.Factory updateFactory;
+  private final ChangeControl.GenericFactory ctlFactory;
   private final GitReferenceUpdated gitRefUpdated;
   private final CommitValidators.Factory commitValidatorsFactory;
   private final MergeabilityChecker mergeabilityChecker;
   private final ReplacePatchSetSender.Factory replacePatchSetFactory;
   private final ApprovalsUtil approvalsUtil;
   private final ApprovalCopier approvalCopier;
+  private final ChangeMessagesUtil cmUtil;
 
   private final Repository git;
   private final RevWalk revWalk;
@@ -110,8 +114,10 @@
   public PatchSetInserter(ChangeHooks hooks,
       ReviewDb db,
       ChangeUpdate.Factory updateFactory,
+      ChangeControl.GenericFactory ctlFactory,
       ApprovalsUtil approvalsUtil,
       ApprovalCopier approvalCopier,
+      ChangeMessagesUtil cmUtil,
       PatchSetInfoFactory patchSetInfoFactory,
       GitReferenceUpdated gitRefUpdated,
       CommitValidators.Factory commitValidatorsFactory,
@@ -127,8 +133,10 @@
     this.hooks = hooks;
     this.db = db;
     this.updateFactory = updateFactory;
+    this.ctlFactory = ctlFactory;
     this.approvalsUtil = approvalsUtil;
     this.approvalCopier = approvalCopier;
+    this.cmUtil = cmUtil;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.gitRefUpdated = gitRefUpdated;
     this.commitValidatorsFactory = commitValidatorsFactory;
@@ -212,7 +220,7 @@
   }
 
   public Change insert() throws InvalidChangeOperationException, OrmException,
-      IOException {
+      IOException, NoSuchChangeException {
     init();
     validate();
 
@@ -273,17 +281,19 @@
       }
 
       if (messageIsForChange()) {
-        insertMessage(db);
+        cmUtil.addChangeMessage(db, update, changeMessage);
       }
 
       if (copyLabels) {
         approvalCopier.copy(db, ctl, patchSet);
       }
       db.commit();
-      update.commit();
+      if (messageIsForChange()) {
+        update.commit();
+      }
 
       if (!messageIsForChange()) {
-        insertMessage(db);
+        commitMessageNotForChange(updatedChange);
       }
 
       if (sendMail) {
@@ -317,6 +327,20 @@
     return updatedChange;
   }
 
+  private void commitMessageNotForChange(Change updatedChange)
+      throws OrmException, NoSuchChangeException, IOException {
+    if (changeMessage != null) {
+      Change otherChange =
+          db.changes().get(changeMessage.getPatchSetId().getParentKey());
+      ChangeControl otherControl =
+          ctlFactory.controlFor(otherChange, user);
+      ChangeUpdate updateForOtherChange =
+          updateFactory.create(otherControl, updatedChange.getLastUpdatedOn());
+      cmUtil.addChangeMessage(db, updateForOtherChange, changeMessage);
+      updateForOtherChange.commit();
+    }
+  }
+
   private void init() throws IOException {
     if (sshInfo == null) {
       sshInfo = new NoSshInfo();
@@ -368,12 +392,6 @@
         .equals(patchSet.getId().getParentKey());
   }
 
-  private void insertMessage(ReviewDb db) throws OrmException {
-    if (changeMessage != null) {
-      db.changeMessages().insert(Collections.singleton(changeMessage));
-    }
-  }
-
   public class ChangeModifiedException extends InvalidChangeOperationException {
     private static final long serialVersionUID = 1L;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index d504743..288172e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -46,6 +46,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountsCollection;
@@ -82,6 +83,7 @@
   private final ChangeData.Factory changeDataFactory;
   private final ChangeUpdate.Factory updateFactory;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
   private final ChangeIndexer indexer;
   private final AccountsCollection accounts;
   private final EmailReviewComments.Factory email;
@@ -100,6 +102,7 @@
       ChangeData.Factory changeDataFactory,
       ChangeUpdate.Factory updateFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       ChangeIndexer indexer,
       AccountsCollection accounts,
       EmailReviewComments.Factory email,
@@ -109,6 +112,7 @@
     this.changeDataFactory = changeDataFactory;
     this.updateFactory = updateFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.indexer = indexer;
     this.accounts = accounts;
     this.email = email;
@@ -144,7 +148,7 @@
       update = updateFactory.create(revision.getControl(), timestamp);
       dirty |= insertComments(revision, input.comments, input.drafts);
       dirty |= updateLabels(revision, update, input.labels);
-      dirty |= insertMessage(revision, input.message);
+      dirty |= insertMessage(revision, input.message, update);
       if (dirty) {
         db.get().changes().update(Collections.singleton(change));
         db.get().commit();
@@ -505,8 +509,8 @@
     labelDelta.add(new LabelVote(name, value).format());
   }
 
-  private boolean insertMessage(RevisionResource rsrc, String msg)
-      throws OrmException {
+  private boolean insertMessage(RevisionResource rsrc, String msg,
+      ChangeUpdate update) throws OrmException {
     msg = Strings.nullToEmpty(msg).trim();
 
     StringBuilder buf = new StringBuilder();
@@ -534,7 +538,7 @@
         "Patch Set %d:%s",
         rsrc.getPatchSet().getPatchSetId(),
         buf.toString()));
-    db.get().changeMessages().insert(Collections.singleton(message));
+    cmUtil.addChangeMessage(db.get(), update, message);
     return true;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
index 8d96f2d..9676762 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PutTopic.java
@@ -25,10 +25,12 @@
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.PutTopic.Input;
 import com.google.gerrit.server.index.ChangeIndexer;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.util.TimeUtil;
 import com.google.gwtorm.server.AtomicUpdate;
@@ -38,7 +40,6 @@
 import com.google.inject.Singleton;
 
 import java.io.IOException;
-import java.util.Collections;
 
 @Singleton
 class PutTopic implements RestModifyView<ChangeResource, Input>,
@@ -46,6 +47,8 @@
   private final Provider<ReviewDb> dbProvider;
   private final ChangeIndexer indexer;
   private final ChangeHooks hooks;
+  private final ChangeUpdate.Factory updateFactory;
+  private final ChangeMessagesUtil cmUtil;
 
   static class Input {
     @DefaultInput
@@ -54,10 +57,13 @@
 
   @Inject
   PutTopic(Provider<ReviewDb> dbProvider, ChangeIndexer indexer,
-      ChangeHooks hooks) {
+      ChangeHooks hooks, ChangeUpdate.Factory updateFactory,
+      ChangeMessagesUtil cmUtil) {
     this.dbProvider = dbProvider;
     this.indexer = indexer;
     this.hooks = hooks;
+    this.updateFactory = updateFactory;
+    this.cmUtil = cmUtil;
   }
 
   @Override
@@ -94,6 +100,7 @@
           currentUser.getAccountId(), TimeUtil.nowTs(),
           change.currentPatchSetId());
       cmsg.setMessage(summary);
+      ChangeUpdate update;
 
       db.changes().beginTransaction(change.getId());
       try {
@@ -106,11 +113,16 @@
               return change;
             }
           });
-        db.changeMessages().insert(Collections.singleton(cmsg));
+
+        //TODO(yyonas): atomic update was not propagated
+        update = updateFactory.create(control);
+        cmUtil.addChangeMessage(db, update, cmsg);
+
         db.commit();
       } finally {
         db.rollback();
       }
+      update.commit();
       CheckedFuture<?, IOException> indexFuture =
           indexer.indexAsync(change.getId());
       hooks.doTopicChangedHook(change, currentUser.getAccount(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
index 083de08..7a31ebe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Restore.java
@@ -26,11 +26,13 @@
 import com.google.gerrit.reviewdb.client.Change.Status;
 import com.google.gerrit.reviewdb.client.ChangeMessage;
 import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
 import com.google.gerrit.server.mail.ReplyToChangeSender;
 import com.google.gerrit.server.mail.RestoredSender;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gwtorm.server.AtomicUpdate;
 import com.google.gwtorm.server.OrmException;
@@ -42,7 +44,6 @@
 import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
-import java.util.Collections;
 
 @Singleton
 public class Restore implements RestModifyView<ChangeResource, RestoreInput>,
@@ -54,18 +55,24 @@
   private final Provider<ReviewDb> dbProvider;
   private final ChangeJson json;
   private final MergeabilityChecker mergeabilityChecker;
+  private final ChangeMessagesUtil cmUtil;
+  private final ChangeUpdate.Factory updateFactory;
 
   @Inject
   Restore(ChangeHooks hooks,
       RestoredSender.Factory restoredSenderFactory,
       Provider<ReviewDb> dbProvider,
       ChangeJson json,
-      MergeabilityChecker mergeabilityChecker) {
+      MergeabilityChecker mergeabilityChecker,
+      ChangeMessagesUtil cmUtil,
+      ChangeUpdate.Factory updateFactory) {
     this.hooks = hooks;
     this.restoredSenderFactory = restoredSenderFactory;
     this.dbProvider = dbProvider;
     this.json = json;
     this.mergeabilityChecker = mergeabilityChecker;
+    this.cmUtil = cmUtil;
+    this.updateFactory = updateFactory;
   }
 
   @Override
@@ -82,6 +89,7 @@
     }
 
     ChangeMessage message;
+    ChangeUpdate update;
     ReviewDb db = dbProvider.get();
     db.changes().beginTransaction(change.getId());
     try {
@@ -102,12 +110,16 @@
         throw new ResourceConflictException("change is "
             + status(db.changes().get(req.getChange().getId())));
       }
+
+      //TODO(yyonas): atomic update was not propagated
+      update = updateFactory.create(control);
       message = newMessage(input, caller, change);
-      db.changeMessages().insert(Collections.singleton(message));
+      cmUtil.addChangeMessage(db, update, message);
       db.commit();
     } finally {
       db.rollback();
     }
+    update.commit();
 
     CheckedFuture<?, IOException> f = mergeabilityChecker.newCheck()
         .addChange(change)
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
index be2788a..3719028 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Submit.java
@@ -20,11 +20,11 @@
 import com.google.common.base.Optional;
 import com.google.common.base.Predicate;
 import com.google.common.base.Strings;
+import com.google.common.collect.FluentIterable;
 import com.google.common.collect.HashBasedTable;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Table;
 import com.google.gerrit.common.data.ParameterizedString;
@@ -44,6 +44,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -103,6 +104,7 @@
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeUpdate.Factory updateFactory;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
   private final MergeQueue mergeQueue;
   private final ChangeIndexer indexer;
   private final LabelNormalizer labelNormalizer;
@@ -118,6 +120,7 @@
       IdentifiedUser.GenericFactory userFactory,
       ChangeUpdate.Factory updateFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       MergeQueue mergeQueue,
       AccountsCollection accounts,
       ChangesCollection changes,
@@ -130,6 +133,7 @@
     this.userFactory = userFactory;
     this.updateFactory = updateFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.mergeQueue = mergeQueue;
     this.accounts = accounts;
     this.changes = changes;
@@ -226,16 +230,16 @@
    */
   public ChangeMessage getConflictMessage(RevisionResource rsrc)
       throws OrmException {
-    return Iterables.getFirst(Iterables.filter(
-      Lists.reverse(dbProvider.get().changeMessages()
-          .byChange(rsrc.getChange().getId())
-          .toList()),
-      new Predicate<ChangeMessage>() {
-        @Override
-        public boolean apply(ChangeMessage input) {
-          return input.getAuthor() == null;
-        }
-      }), null);
+    return FluentIterable.from(cmUtil.byPatchSet(dbProvider.get(), rsrc.getNotes(),
+        rsrc.getPatchSet().getId()))
+        .filter(new Predicate<ChangeMessage>() {
+          @Override
+          public boolean apply(ChangeMessage input) {
+            return input.getAuthor() == null;
+          }
+        })
+        .last()
+        .orNull();
   }
 
   public Change submit(RevisionResource rsrc, IdentifiedUser caller,
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
index aaf0273..85eba42 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/AsyncReceiveCommits.java
@@ -23,7 +23,6 @@
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.FactoryModuleBuilder;
 import com.google.inject.Inject;
-
 import com.google.inject.name.Named;
 import com.google.inject.PrivateModule;
 import com.google.inject.Provides;
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 74056ab..06e17ce 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
@@ -42,6 +42,7 @@
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.account.AccountCache;
@@ -54,6 +55,7 @@
 import com.google.gerrit.server.mail.MergeFailSender;
 import com.google.gerrit.server.mail.MergedSender;
 import com.google.gerrit.server.notedb.ChangeNotes;
+import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
@@ -88,7 +90,6 @@
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -137,9 +138,11 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
   private final ChangeControl.GenericFactory changeControlFactory;
+  private final ChangeUpdate.Factory updateFactory;
   private final MergeQueue mergeQueue;
   private final MergeValidators.Factory mergeValidatorsFactory;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
 
   private final Branch.NameKey destBranch;
   private ProjectState destProject;
@@ -173,6 +176,7 @@
       final MergeFailSender.Factory mfsf,
       final PatchSetInfoFactory psif, final IdentifiedUser.GenericFactory iuf,
       final ChangeControl.GenericFactory changeControlFactory,
+      final ChangeUpdate.Factory updateFactory,
       final MergeQueue mergeQueue, @Assisted final Branch.NameKey branch,
       final ChangeHooks hooks, final AccountCache accountCache,
       final TagCache tagCache,
@@ -182,7 +186,8 @@
       final RequestScopePropagator requestScopePropagator,
       final ChangeIndexer indexer,
       final MergeValidators.Factory mergeValidatorsFactory,
-      final ApprovalsUtil approvalsUtil) {
+      final ApprovalsUtil approvalsUtil,
+      final ChangeMessagesUtil cmUtil) {
     repoManager = grm;
     schemaFactory = sf;
     notesFactory = nf;
@@ -193,6 +198,7 @@
     patchSetInfoFactory = psif;
     identifiedUserFactory = iuf;
     this.changeControlFactory = changeControlFactory;
+    this.updateFactory = updateFactory;
     this.mergeQueue = mergeQueue;
     this.hooks = hooks;
     this.accountCache = accountCache;
@@ -204,6 +210,7 @@
     this.indexer = indexer;
     this.mergeValidatorsFactory = mergeValidatorsFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     destBranch = branch;
     toMerge = ArrayListMultimap.create();
     potentiallyStillSubmittable = new ArrayList<>();
@@ -224,7 +231,7 @@
     }
   }
 
-  public void merge() throws MergeException {
+  public void merge() throws MergeException, NoSuchChangeException, IOException {
     setDestProject();
     try {
       openSchema();
@@ -395,7 +402,7 @@
     inserter = repo.newObjectInserter();
   }
 
-  private RefUpdate openBranch() throws MergeException, OrmException {
+  private RefUpdate openBranch() throws MergeException, OrmException, NoSuchChangeException {
     try {
       final RefUpdate branchUpdate = repo.updateRef(destBranch.get());
       if (branchUpdate.getOldObjectId() != null) {
@@ -442,7 +449,7 @@
   }
 
   private ListMultimap<SubmitType, Change> validateChangeList(
-      final List<Change> submitted) throws MergeException {
+      final List<Change> submitted) throws MergeException, NoSuchChangeException {
     final ListMultimap<SubmitType, Change> toSubmit =
         ArrayListMultimap.create();
 
@@ -658,7 +665,7 @@
     hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
   }
 
-  private void updateChangeStatus(final List<Change> submitted) {
+  private void updateChangeStatus(final List<Change> submitted) throws NoSuchChangeException {
     for (final Change c : submitted) {
       final CodeReviewCommit commit = commits.get(c.getId());
       final CommitMergeStatus s = commit != null ? commit.getStatusCode() : null;
@@ -824,7 +831,8 @@
   }
 
   private void setMerged(Change c, ChangeMessage msg)
-      throws OrmException, IOException {
+      throws OrmException, IOException, NoSuchChangeException {
+    ChangeUpdate update = null;
     try {
       db.changes().beginTransaction(c.getId());
 
@@ -835,8 +843,13 @@
       c = setMergedPatchSet(c.getId(), merged);
       PatchSetApproval submitter =
           approvalsUtil.getSubmitter(db, commit.notes(), merged);
-      addMergedMessage(submitter, msg);
+      ChangeControl control = commit.getControl();
+      update = updateFactory.create(control, c.getLastUpdatedOn());
 
+      // TODO(yyonas): we need to be able to change the author of the message
+      // is not the person for whom the change was made. addMergedMessage
+      // did this in the past.
+      cmUtil.addChangeMessage(db, update, msg);
       db.commit();
 
       sendMergedEmail(c, submitter);
@@ -853,6 +866,7 @@
       db.rollback();
     }
     indexer.index(db, c);
+    update.commit();
   }
 
   private Change setMergedPatchSet(Change.Id changeId, final PatchSet.Id merged)
@@ -881,16 +895,6 @@
     });
   }
 
-  private void addMergedMessage(PatchSetApproval submitter, ChangeMessage msg)
-      throws OrmException {
-    if (msg != null) {
-      if (submitter != null && msg.getAuthor() == null) {
-        msg.setAuthor(submitter.getAccountId());
-      }
-      db.changeMessages().insert(Collections.singleton(msg));
-    }
-  }
-
   private void sendMergedEmail(final Change c, final PatchSetApproval from) {
     workQueue.getDefaultQueue()
         .submit(requestScopePropagator.wrap(new Runnable() {
@@ -933,11 +937,11 @@
         c, identifiedUserFactory.create(c.getOwner()));
   }
 
-  private void setNew(CodeReviewCommit c, ChangeMessage msg) {
+  private void setNew(CodeReviewCommit c, ChangeMessage msg) throws NoSuchChangeException, IOException {
     sendMergeFail(c.notes(), msg, true);
   }
 
-  private void setNew(Change c, ChangeMessage msg) throws OrmException {
+  private void setNew(Change c, ChangeMessage msg) throws OrmException, NoSuchChangeException, IOException {
     sendMergeFail(notesFactory.create(c), msg, true);
   }
 
@@ -947,7 +951,8 @@
 
   private RetryStatus getRetryStatus(
       @Nullable PatchSetApproval submitter,
-      ChangeMessage msg) {
+      ChangeMessage msg,
+      ChangeNotes notes) {
     if (submitter != null
         && TimeUtil.nowMs() - submitter.getGranted().getTime()
           > MAX_SUBMIT_WINDOW) {
@@ -955,8 +960,7 @@
     }
 
     try {
-      ChangeMessage last = Iterables.getLast(db.changeMessages().byChange(
-          msg.getPatchSetId().getParentKey()), null);
+      ChangeMessage last = Iterables.getLast(cmUtil.byChange(db, notes));
       if (last != null) {
         if (Objects.equal(last.getAuthor(), msg.getAuthor())
             && Objects.equal(last.getMessage(), msg.getMessage())) {
@@ -975,7 +979,7 @@
   }
 
   private void sendMergeFail(ChangeNotes notes, final ChangeMessage msg,
-      boolean makeNew) {
+      boolean makeNew) throws NoSuchChangeException, IOException {
     PatchSetApproval submitter = null;
     try {
       submitter = approvalsUtil.getSubmitter(
@@ -985,7 +989,7 @@
     }
 
     if (!makeNew) {
-      RetryStatus retryStatus = getRetryStatus(submitter, msg);
+      RetryStatus retryStatus = getRetryStatus(submitter, msg, notes);
       if (retryStatus == RetryStatus.RETRY_NO_MESSAGE) {
         return;
       } else if (retryStatus == RetryStatus.UNSUBMIT) {
@@ -996,6 +1000,7 @@
     final boolean setStatusNew = makeNew;
     final Change c = notes.getChange();
     Change change = null;
+    ChangeUpdate update = null;
     try {
       db.changes().beginTransaction(c.getId());
       try {
@@ -1013,7 +1018,11 @@
             return c;
           }
         });
-        db.changeMessages().insert(Collections.singleton(msg));
+        ChangeControl control = changeControl(change);
+
+        //TODO(yyonas): atomic change is not propagated.
+        update = updateFactory.create(control, c.getLastUpdatedOn());
+        cmUtil.addChangeMessage(db, update, msg);
         db.commit();
       } finally {
         db.rollback();
@@ -1021,6 +1030,9 @@
     } catch (OrmException err) {
       log.warn("Cannot record merge failure message", err);
     }
+    if (update != null) {
+      update.commit();
+    }
 
     CheckedFuture<?, IOException> indexFuture;
     if (change != null) {
@@ -1083,7 +1095,7 @@
     }
   }
 
-  private void abandonAllOpenChanges() {
+  private void abandonAllOpenChanges() throws NoSuchChangeException {
     Exception err = null;
     try {
       openSchema();
@@ -1105,8 +1117,13 @@
   }
 
   private void abandonOneChange(Change change) throws OrmException,
-      IOException {
+    NoSuchChangeException,  IOException {
     db.changes().beginTransaction(change.getId());
+
+    //TODO(dborowitz): support InternalUser in ChangeUpdate
+    ChangeControl control = changeControlFactory.controlFor(change,
+        identifiedUserFactory.create(change.getOwner()));
+    ChangeUpdate update = updateFactory.create(control);
     try {
       change = db.changes().atomicUpdate(
         change.getId(),
@@ -1120,6 +1137,7 @@
             return null;
           }
         });
+
       if (change != null) {
         ChangeMessage msg = new ChangeMessage(
             new ChangeMessage.Key(
@@ -1129,12 +1147,15 @@
             change.getLastUpdatedOn(),
             change.currentPatchSetId());
         msg.setMessage("Project was deleted.");
-        db.changeMessages().insert(Collections.singleton(msg));
+
+        //TODO(yyonas): atomic change is not propagated.
+        cmUtil.addChangeMessage(db, update, msg);
         db.commit();
         indexer.index(db, change);
       }
     } finally {
       db.rollback();
     }
+    update.commit();
   }
-}
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index b9b5621..8902af9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -38,6 +38,7 @@
 import com.google.inject.assistedinject.AssistedInject;
 
 import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.LargeObjectException;
 import org.eclipse.jgit.errors.MissingObjectException;
 import org.eclipse.jgit.errors.NoMergeBaseException;
 import org.eclipse.jgit.errors.NoMergeBaseException.MergeBaseFailureReason;
@@ -395,6 +396,9 @@
     final ThreeWayMerger m = newThreeWayMerger(repo, createDryRunInserter());
     try {
       return m.merge(new AnyObjectId[] {mergeTip, toMerge});
+    } catch (LargeObjectException e) {
+      log.warn("Cannot merge due to LargeObjectException: " + toMerge.name());
+      return false;
     } catch (NoMergeBaseException e) {
       return false;
     } catch (IOException e) {
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 c4c1b7f..a905385 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
@@ -130,7 +130,6 @@
   private static final String KEY_LOCAL_DEFAULT = "local-default";
 
   private static final String LABEL = "label";
-  private static final String KEY_ABBREVIATION = "abbreviation";
   private static final String KEY_FUNCTION = "function";
   private static final String KEY_DEFAULT_VALUE = "defaultValue";
   private static final String KEY_COPY_MIN_SCORE = "copyMinScore";
@@ -673,10 +672,6 @@
             "Invalid label \"%s\"", name)));
         continue;
       }
-      String abbr = rc.getString(LABEL, name, KEY_ABBREVIATION);
-      if (abbr != null) {
-        label.setAbbreviation(abbr);
-      }
 
       String functionName = Objects.firstNonNull(
           rc.getString(LABEL, name, KEY_FUNCTION), "MaxWithBlock");
@@ -1050,15 +1045,6 @@
       LabelType label = e.getValue();
       toUnset.remove(name);
       rc.setString(LABEL, name, KEY_FUNCTION, label.getFunctionName());
-
-      if (!LabelType.defaultAbbreviation(name)
-          .equals(label.getAbbreviation())) {
-        rc.setString(
-            LABEL, name, KEY_ABBREVIATION, label.getAbbreviation());
-      } else {
-        rc.unset(LABEL, name, KEY_ABBREVIATION);
-      }
-
       rc.setInt(LABEL, name, KEY_DEFAULT_VALUE, label.getDefaultValue());
       if (label.isCopyMinScore()) {
         rc.setBoolean(LABEL, name, KEY_COPY_MIN_SCORE, true);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index ac0a95e..3267e5f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -69,6 +69,7 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalCopier;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.ChangeUtil;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.IdentifiedUser;
@@ -97,6 +98,7 @@
 import com.google.gerrit.server.notedb.ChangeUpdate;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.ProjectState;
@@ -279,6 +281,7 @@
   private final ChangeHooks hooks;
   private final ApprovalsUtil approvalsUtil;
   private final ApprovalCopier approvalCopier;
+  private final ChangeMessagesUtil cmUtil;
   private final GitRepositoryManager repoManager;
   private final ProjectCache projectCache;
   private final String canonicalWebUrl;
@@ -344,6 +347,7 @@
       final ChangeHooks hooks,
       final ApprovalsUtil approvalsUtil,
       final ApprovalCopier approvalCopier,
+      final ChangeMessagesUtil cmUtil,
       final ProjectCache projectCache,
       final GitRepositoryManager repoManager,
       final TagCache tagCache,
@@ -383,6 +387,7 @@
     this.hooks = hooks;
     this.approvalsUtil = approvalsUtil;
     this.approvalCopier = approvalCopier;
+    this.cmUtil = cmUtil;
     this.projectCache = projectCache;
     this.repoManager = repoManager;
     this.canonicalWebUrl = canonicalWebUrl;
@@ -576,54 +581,59 @@
     }
 
     for (final ReceiveCommand c : commands) {
-      if (c.getResult() == OK) {
-        switch (c.getType()) {
-          case CREATE:
-            if (isHead(c) || isConfig(c)) {
-              autoCloseChanges(c);
+        if (c.getResult() == OK) {
+          try {
+            switch (c.getType()) {
+              case CREATE:
+                if (isHead(c) || isConfig(c)) {
+                  autoCloseChanges(c);
+                }
+                break;
+
+              case UPDATE: // otherwise known as a fast-forward
+                tagCache.updateFastForward(project.getNameKey(),
+                    c.getRefName(),
+                    c.getOldId(),
+                    c.getNewId());
+                if (isHead(c) || isConfig(c)) {
+                  autoCloseChanges(c);
+                }
+                break;
+
+              case UPDATE_NONFASTFORWARD:
+                if (isHead(c) || isConfig(c)) {
+                  autoCloseChanges(c);
+                }
+                break;
+
+              case DELETE:
+                break;
             }
-            break;
 
-          case UPDATE: // otherwise known as a fast-forward
-            tagCache.updateFastForward(project.getNameKey(),
-                c.getRefName(),
-                c.getOldId(),
-                c.getNewId());
-            if (isHead(c) || isConfig(c)) {
-              autoCloseChanges(c);
+            if (isConfig(c)) {
+              projectCache.evict(project);
+              ProjectState ps = projectCache.get(project.getNameKey());
+              repoManager.setProjectDescription(project.getNameKey(), //
+                  ps.getProject().getDescription());
             }
-            break;
 
-          case UPDATE_NONFASTFORWARD:
-            if (isHead(c) || isConfig(c)) {
-              autoCloseChanges(c);
+            if (!MagicBranch.isMagicBranch(c.getRefName())) {
+              // We only fire gitRefUpdated for direct refs updates.
+              // Events for change refs are fired when they are created.
+              //
+              gitRefUpdated.fire(project.getNameKey(), c.getRefName(),
+                  c.getOldId(), c.getNewId());
+              hooks.doRefUpdatedHook(
+                  new Branch.NameKey(project.getNameKey(), c.getRefName()),
+                  c.getOldId(),
+                  c.getNewId(),
+                  currentUser.getAccount());
             }
-            break;
-
-          case DELETE:
-            break;
+          } catch (NoSuchChangeException e) {
+            c.setResult(REJECTED_OTHER_REASON,
+                "No such change: " + e.getMessage());
+          }
         }
-
-        if (isConfig(c)) {
-          projectCache.evict(project);
-          ProjectState ps = projectCache.get(project.getNameKey());
-          repoManager.setProjectDescription(project.getNameKey(), //
-              ps.getProject().getDescription());
-        }
-
-        if (!MagicBranch.isMagicBranch(c.getRefName())) {
-          // We only fire gitRefUpdated for direct refs updates.
-          // Events for change refs are fired when they are created.
-          //
-          gitRefUpdated.fire(project.getNameKey(), c.getRefName(),
-              c.getOldId(), c.getNewId());
-          hooks.doRefUpdatedHook(
-              new Branch.NameKey(project.getNameKey(), c.getRefName()),
-              c.getOldId(),
-              c.getNewId(),
-              currentUser.getAccount());
-        }
-      }
     }
     closeProgress.end();
     commandProgress.end();
@@ -1928,7 +1938,7 @@
       ListenableFuture<PatchSet.Id> future = changeUpdateExector.submit(
           requestScopePropagator.wrap(new Callable<PatchSet.Id>() {
         @Override
-        public PatchSet.Id call() throws OrmException, IOException {
+        public PatchSet.Id call() throws OrmException, IOException, NoSuchChangeException {
           try {
             if (caller == Thread.currentThread()) {
               return insertPatchSet(db);
@@ -1993,7 +2003,7 @@
             new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
                 .messageUUID(db)), me, newPatchSet.getCreatedOn(), newPatchSet.getId());
         msg.setMessage("Uploaded patch set " + newPatchSet.getPatchSetId() + ".");
-        db.changeMessages().insert(Collections.singleton(msg));
+        cmUtil.addChangeMessage(db, update, msg);
 
         if (mergedIntoRef == null) {
           // Change should be new, so it can go through review again.
@@ -2049,7 +2059,7 @@
       if (mergedIntoRef != null) {
         // Change was already submitted to a branch, close it.
         //
-        markChangeMergedByPush(db, this);
+        markChangeMergedByPush(db, this, changeCtl);
       }
 
       if (cmd.getResult() == NOT_ATTEMPTED) {
@@ -2256,7 +2266,7 @@
     return true;
   }
 
-  private void autoCloseChanges(final ReceiveCommand cmd) {
+  private void autoCloseChanges(final ReceiveCommand cmd) throws NoSuchChangeException {
     final RevWalk rw = rp.getRevWalk();
     try {
       rw.reset();
@@ -2355,7 +2365,7 @@
     result.newPatchSet = ps;
     result.info = patchSetInfoFactory.get(commit, psi);
     result.mergedIntoRef = refName;
-    markChangeMergedByPush(db, result);
+    markChangeMergedByPush(db, result, result.changeCtl);
     hooks.doChangeMergedHook(
         change, currentUser.getAccount(), result.newPatchSet, db);
     sendMergedEmail(result);
@@ -2383,11 +2393,13 @@
     return r;
   }
 
-  private void markChangeMergedByPush(ReviewDb db, final ReplaceRequest result)
-      throws OrmException, IOException {
+  private void markChangeMergedByPush(ReviewDb db, final ReplaceRequest result,
+      ChangeControl control) throws OrmException, IOException {
     Change.Id id = result.change.getId();
     db.changes().beginTransaction(id);
     Change change;
+
+    ChangeUpdate update;
     try {
       change = db.changes().atomicUpdate(id, new AtomicUpdate<Change>() {
           @Override
@@ -2420,12 +2432,15 @@
           result.info.getKey());
       msg.setMessage(msgBuf.toString());
 
-      db.changeMessages().insert(Collections.singleton(msg));
+      update = updateFactory.create(control, change.getLastUpdatedOn());
+
+      cmUtil.addChangeMessage(db, update, msg);
       db.commit();
     } finally {
       db.rollback();
     }
     indexer.index(db, change);
+    update.commit();
   }
 
   private void sendMergedEmail(final ReplaceRequest result) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
index 37be365..f8bfd94 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotes.java
@@ -29,6 +29,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableSetMultimap;
+import com.google.common.collect.LinkedListMultimap;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
@@ -39,6 +40,7 @@
 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.client.PatchSetApproval.LabelId;
@@ -60,6 +62,7 @@
 import org.eclipse.jgit.util.RawParseUtils;
 
 import java.io.IOException;
+import java.nio.charset.Charset;
 import java.sql.Timestamp;
 import java.util.Collection;
 import java.util.Collections;
@@ -78,6 +81,15 @@
           }
         });
 
+  public static final Ordering<ChangeMessage> MESSAGE_BY_TIME =
+      Ordering.natural().onResultOf(
+        new Function<ChangeMessage, Timestamp>() {
+          @Override
+          public Timestamp apply(ChangeMessage input) {
+            return input.getWrittenOn();
+          }
+        });
+
   @Singleton
   public static class Factory {
     private final GitRepositoryManager repoManager;
@@ -101,6 +113,7 @@
         Table<Account.Id, String, Optional<PatchSetApproval>>> approvals;
     private final Map<Account.Id, ReviewerState> reviewers;
     private final List<SubmitRecord> submitRecords;
+    private final Multimap<PatchSet.Id, ChangeMessage> changeMessages;
     private Change.Status status;
 
     private Parser(Change.Id changeId, ObjectId tip, RevWalk walk) {
@@ -110,6 +123,7 @@
       approvals = Maps.newHashMap();
       reviewers = Maps.newLinkedHashMap();
       submitRecords = Lists.newArrayListWithExpectedSize(1);
+      changeMessages = LinkedListMultimap.create();
     }
 
     private void parseAll() throws ConfigInvalidException, IOException {
@@ -136,12 +150,21 @@
       return ImmutableListMultimap.copyOf(result);
     }
 
+    private ImmutableListMultimap<PatchSet.Id, ChangeMessage> buildMessages() {
+      for (Collection<ChangeMessage> v : changeMessages.asMap().values()) {
+        Collections.sort((List<ChangeMessage>) v, MESSAGE_BY_TIME);
+      }
+      return ImmutableListMultimap.copyOf(changeMessages);
+    }
+
     private void parse(RevCommit commit) throws ConfigInvalidException {
       if (status == null) {
         status = parseStatus(commit);
       }
       PatchSet.Id psId = parsePatchSetId(commit);
       Account.Id accountId = parseIdent(commit);
+      parseChangeMessage(psId, accountId, commit);
+
 
       if (submitRecords.isEmpty()) {
         // Only parse the most recent set of submit records; any older ones are
@@ -189,6 +212,63 @@
       return new PatchSet.Id(changeId, psId);
     }
 
+    private void parseChangeMessage(PatchSet.Id psId, Account.Id accountId,
+        RevCommit commit) {
+      byte[] raw = commit.getRawBuffer();
+      int size = raw.length;
+      Charset enc = RawParseUtils.parseEncoding(raw);
+
+      int subjectStart = RawParseUtils.commitMessage(raw, 0);
+      if (subjectStart < 0 || subjectStart >= size) {
+        return;
+      }
+
+      int subjectEnd = RawParseUtils.endOfParagraph(raw, subjectStart);
+      if (subjectEnd == size) {
+        return;
+      }
+
+      int changeMessageStart;
+
+      if (raw[subjectEnd] == '\n') {
+        changeMessageStart = subjectEnd + 2; //\n\n ends paragraph
+      } else if (raw[subjectEnd] == '\r') {
+        changeMessageStart = subjectEnd + 4; //\r\n\r\n ends paragraph
+      } else {
+        return;
+      }
+
+      int ptr = size - 1;
+      int changeMessageEnd = -1;
+      while(ptr > changeMessageStart) {
+        ptr = RawParseUtils.prevLF(raw, ptr, '\r');
+        if (ptr == -1) {
+          break;
+        }
+        if (raw[ptr] == '\n') {
+          changeMessageEnd = ptr - 1;
+          break;
+        } else if (raw[ptr] == '\r') {
+          changeMessageEnd = ptr - 3;
+          break;
+        }
+      }
+
+      if (ptr <= changeMessageStart) {
+        return;
+      }
+
+      String changeMsgString = RawParseUtils.decode(enc, raw,
+          changeMessageStart, changeMessageEnd + 1);
+      ChangeMessage changeMessage = new ChangeMessage(
+          new ChangeMessage.Key(psId.getParentKey(), commit.name()),
+          accountId,
+          new Timestamp(commit.getCommitterIdent().getWhen().getTime()),
+          psId);
+      changeMessage.setMessage(changeMsgString);
+      changeMessages.put(psId, changeMessage);
+    }
+
     private void parseApproval(PatchSet.Id psId, Account.Id accountId,
         RevCommit commit, String line) throws ConfigInvalidException {
       Table<Account.Id, String, Optional<PatchSetApproval>> curr =
@@ -353,6 +433,7 @@
   private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
   private ImmutableSetMultimap<ReviewerState, Account.Id> reviewers;
   private ImmutableList<SubmitRecord> submitRecords;
+  private ImmutableListMultimap<PatchSet.Id, ChangeMessage> changeMessages;
 
   @VisibleForTesting
   ChangeNotes(GitRepositoryManager repoManager, Change change) {
@@ -405,6 +486,11 @@
     return submitRecords;
   }
 
+  /** @return change messages by patch set, in chronological order. */
+  public ImmutableListMultimap<PatchSet.Id, ChangeMessage> getChangeMessages() {
+    return changeMessages;
+  }
+
   @Override
   protected String getRefName() {
     return ChangeNoteUtil.changeRefName(change.getId());
@@ -426,6 +512,7 @@
         change.setStatus(parser.status);
       }
       approvals = parser.buildApprovals();
+      changeMessages = parser.buildMessages();
 
       ImmutableSetMultimap.Builder<ReviewerState, Account.Id> reviewers =
           ImmutableSetMultimap.builder();
@@ -434,6 +521,7 @@
         reviewers.put(e.getValue(), e.getKey());
       }
       this.reviewers = reviewers.build();
+
       submitRecords = ImmutableList.copyOf(parser.submitRecords);
     } finally {
       walk.release();
@@ -444,6 +532,7 @@
     approvals = ImmutableListMultimap.of();
     reviewers = ImmutableSetMultimap.of();
     submitRecords = ImmutableList.of();
+    changeMessages = ImmutableListMultimap.of();
   }
 
   @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
index 74ca198..12b276f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java
@@ -88,6 +88,7 @@
   private String subject;
   private PatchSet.Id psId;
   private List<SubmitRecord> submitRecords;
+  private String changeMessage;
 
   @AssistedInject
   private ChangeUpdate(
@@ -186,6 +187,10 @@
     this.psId = psId;
   }
 
+  public void setChangeMessage(String changeMessage) {
+    this.changeMessage = changeMessage;
+  }
+
   public void putReviewer(Account.Id reviewer, ReviewerState type) {
     checkArgument(type != ReviewerState.REMOVED, "invalid ReviewerType");
     reviewers.put(reviewer, type);
@@ -297,6 +302,13 @@
       msg.append("Update patch set ").append(ps);
     }
     msg.append("\n\n");
+
+    if (changeMessage != null) {
+      msg.append(changeMessage);
+      msg.append("\n\n");
+    }
+
+
     addFooter(msg, FOOTER_PATCH_SET, ps);
     if (status != null) {
       addFooter(msg, FOOTER_STATUS, status.name().toLowerCase());
@@ -352,7 +364,8 @@
     return approvals.isEmpty()
         && reviewers.isEmpty()
         && status == null
-        && submitRecords == null;
+        && submitRecords == null
+        && changeMessage == null;
   }
 
   private static StringBuilder addFooter(StringBuilder sb, FooterKey footer) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
index 5321046..db72def 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NotesMigration.java
@@ -35,17 +35,21 @@
     Config cfg = new Config();
     cfg.setBoolean("notedb", null, "write", true);
     cfg.setBoolean("notedb", "patchSetApprovals", "read", true);
+    cfg.setBoolean("notedb", "changeMessages", "read", true);
     return new NotesMigration(cfg);
   }
 
   private final boolean write;
   private final boolean readPatchSetApprovals;
+  private final boolean readChangeMessages;
 
   @Inject
   NotesMigration(@GerritServerConfig Config cfg) {
     write = cfg.getBoolean("notedb", null, "write", false);
     readPatchSetApprovals =
         cfg.getBoolean("notedb", "patchSetApprovals", "read", false);
+    readChangeMessages =
+        cfg.getBoolean("notedb", "changeMessages", "read", false);
   }
 
   public boolean write() {
@@ -55,4 +59,8 @@
   public boolean readPatchSetApprovals() {
     return readPatchSetApprovals;
   }
+
+  public boolean readChangeMessages() {
+    return readChangeMessages;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
index bd8a524..6f1204b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/AutoRegisterModules.java
@@ -23,7 +23,7 @@
 import com.google.gerrit.extensions.annotations.Export;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
 import com.google.gerrit.extensions.annotations.Listen;
-import com.google.gerrit.server.plugins.JarScanner.ExtensionMetaData;
+import com.google.gerrit.server.plugins.PluginContentScanner.ExtensionMetaData;
 import com.google.inject.AbstractModule;
 import com.google.inject.Module;
 import com.google.inject.Scopes;
@@ -34,12 +34,11 @@
 import java.util.Arrays;
 import java.util.Map;
 import java.util.Set;
-import java.util.jar.JarFile;
 
 class AutoRegisterModules {
   private final String pluginName;
   private final PluginGuiceEnvironment env;
-  private final JarFile jarFile;
+  private final PluginContentScanner scanner;
   private final ClassLoader classLoader;
   private final ModuleGenerator sshGen;
   private final ModuleGenerator httpGen;
@@ -53,11 +52,11 @@
 
   AutoRegisterModules(String pluginName,
       PluginGuiceEnvironment env,
-      JarFile jarFile,
+      PluginContentScanner scanner,
       ClassLoader classLoader) {
     this.pluginName = pluginName;
     this.env = env;
-    this.jarFile = jarFile;
+    this.scanner = scanner;
     this.classLoader = classLoader;
     this.sshGen = env.hasSshModule() ? env.newSshModuleGenerator() : null;
     this.httpGen = env.hasHttpModule() ? env.newHttpModuleGenerator() : null;
@@ -111,7 +110,7 @@
 
   private void scan() throws InvalidPluginException {
     Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> extensions =
-        JarScanner.scan(jarFile, pluginName, Arrays.asList(Export.class, Listen.class));
+        scanner.scan(pluginName, Arrays.asList(Export.class, Listen.class));
     for (ExtensionMetaData export : extensions.get(Export.class)) {
       export(export);
     }
@@ -123,18 +122,18 @@
   private void export(ExtensionMetaData def) throws InvalidPluginException {
     Class<?> clazz;
     try {
-      clazz = Class.forName(def.getClassName(), false, classLoader);
+      clazz = Class.forName(def.className, false, classLoader);
     } catch (ClassNotFoundException err) {
       throw new InvalidPluginException(String.format(
           "Cannot load %s with @Export(\"%s\")",
-          def.getClassName(), def.getAnnotationValue()), err);
+          def.className, def.annotationValue), err);
     }
 
     Export export = clazz.getAnnotation(Export.class);
     if (export == null) {
       PluginLoader.log.warn(String.format(
           "In plugin %s asm incorrectly parsed %s with @Export(\"%s\")",
-          pluginName, clazz.getName(), def.getAnnotationValue()));
+          pluginName, clazz.getName(), def.annotationValue));
       return;
     }
 
@@ -162,11 +161,11 @@
   private void listen(ExtensionMetaData def) throws InvalidPluginException {
     Class<?> clazz;
     try {
-      clazz = Class.forName(def.getClassName(), false, classLoader);
+      clazz = Class.forName(def.className, false, classLoader);
     } catch (ClassNotFoundException err) {
       throw new InvalidPluginException(String.format(
           "Cannot load %s with @Listen",
-          def.getClassName()), err);
+          def.className), err);
     }
 
     Listen listen = clazz.getAnnotation(Listen.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
index c4fe1ec..6ce3464 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JarScanner.java
@@ -24,6 +24,7 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Sets;
@@ -38,6 +39,7 @@
 import org.objectweb.asm.Opcodes;
 import org.objectweb.asm.Type;
 
+import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
 import java.lang.annotation.Annotation;
@@ -46,10 +48,12 @@
 import java.util.Enumeration;
 import java.util.Map;
 import java.util.Set;
+import java.util.jar.Attributes;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
+import java.util.jar.Manifest;
 
-public class JarScanner {
+public class JarScanner implements PluginContentScanner {
   private static final int SKIP_ALL = ClassReader.SKIP_CODE
       | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES;
   private static final Function<ClassData, ExtensionMetaData> CLASS_DATA_TO_EXTENSION_META_DATA =
@@ -61,27 +65,19 @@
         }
       };
 
-  public static class ExtensionMetaData {
-    private final String className;
-    private final String annotationValue;
+  private final JarFile jarFile;
 
-    private ExtensionMetaData(String className, String annotationValue) {
-      this.className = className;
-      this.annotationValue = annotationValue;
-    }
-
-    public String getAnnotationValue() {
-      return annotationValue;
-    }
-
-    public String getClassName() {
-      return className;
+  public JarScanner(File srcFile) throws InvalidPluginException {
+    try {
+      this.jarFile = new JarFile(srcFile);
+    } catch (IOException e) {
+      throw new InvalidPluginException("Cannot scan plugin file " + srcFile, e);
     }
   }
 
-  public static Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
-      JarFile jarFile, String pluginName,
-      Iterable<Class<? extends Annotation>> annotations)
+  @Override
+  public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
+      String pluginName, Iterable<Class<? extends Annotation>> annotations)
       throws InvalidPluginException {
     Set<String> descriptors = Sets.newHashSet();
     Multimap<String, JarScanner.ClassData> rawMap = ArrayListMultimap.create();
@@ -262,4 +258,62 @@
     public void visitEnd() {
     }
   }
+
+  @Override
+  public Optional<PluginEntry> getEntry(String resourcePath) throws IOException {
+    JarEntry jarEntry = jarFile.getJarEntry(resourcePath);
+    if (jarEntry == null || jarEntry.getSize() == 0) {
+      return Optional.absent();
+    }
+
+    return Optional.of(resourceOf(jarEntry));
+  }
+
+  @Override
+  public Enumeration<PluginEntry> entries() {
+    return Collections.enumeration(Lists.transform(
+        Collections.list(jarFile.entries()),
+        new Function<JarEntry, PluginEntry>() {
+          public PluginEntry apply(JarEntry jarEntry) {
+            try {
+              return resourceOf(jarEntry);
+            } catch (IOException e) {
+              throw new IllegalArgumentException("Cannot convert jar entry "
+                  + jarEntry + " to a resource", e);
+            }
+          }
+        }));
+  }
+
+  @Override
+  public InputStream getInputStream(PluginEntry entry)
+      throws IOException {
+    return jarFile.getInputStream(jarFile
+        .getEntry(entry.getName()));
+  }
+
+  @Override
+  public Manifest getManifest() throws IOException {
+    return jarFile.getManifest();
+  }
+
+  private PluginEntry resourceOf(JarEntry jarEntry) throws IOException {
+    return new PluginEntry(jarEntry.getName(), jarEntry.getTime(),
+        jarEntry.getSize(), attributesOf(jarEntry));
+  }
+
+  private Map<Object, String> attributesOf(JarEntry jarEntry)
+      throws IOException {
+    Attributes attributes = jarEntry.getAttributes();
+    if (attributes == null) {
+      return Collections.emptyMap();
+    }
+    return Maps.transformEntries(attributes,
+        new Maps.EntryTransformer<Object, Object, String>() {
+          @Override
+          public String transformEntry(Object key, Object value) {
+            return (String) value;
+          }
+        });
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
index ff72576..63f69b5 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/JsPlugin.java
@@ -28,7 +28,6 @@
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
 import java.io.File;
-import java.util.jar.JarFile;
 
 class JsPlugin extends Plugin {
   private Injector httpInjector;
@@ -67,11 +66,6 @@
   }
 
   @Override
-  public JarFile getJarFile() {
-    return null;
-  }
-
-  @Override
   public Injector getSysInjector() {
     return null;
   }
@@ -109,4 +103,9 @@
           new JavaScriptPlugin(fileName));
     }
   }
+
+  @Override
+  public PluginContentScanner getContentScanner() {
+    return PluginContentScanner.EMPTY;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
index 577ee0c..c5611dd 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ListPlugins.java
@@ -137,7 +137,7 @@
       version = p.getVersion();
       disabled = p.isDisabled() ? true : null;
 
-      if (p.getJarFile() != null) {
+      if (p.getSrcFile() != null) {
         indexUrl = String.format("plugins/%s/", p.getName());
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
index 988669a..ff6f0ae 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/Plugin.java
@@ -29,7 +29,6 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.jar.Attributes;
-import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 
 public abstract class Plugin {
@@ -124,7 +123,7 @@
 
   abstract void stop(PluginGuiceEnvironment env);
 
-  public abstract JarFile getJarFile();
+  public abstract PluginContentScanner getContentScanner();
 
   public abstract Injector getSysInjector();
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java
new file mode 100644
index 0000000..0228509
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginContentScanner.java
@@ -0,0 +1,130 @@
+// Copyright (C) 2014 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.server.plugins;
+
+import com.google.common.base.Optional;
+
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.jar.Manifest;
+
+/**
+ * Scans the plugin returning classes and resources.
+ *
+ * Gerrit uses the scanner to automatically discover the classes
+ * and resources exported by the plugin for auto discovery
+ * of exported SSH commands, Servlets and listeners.
+ */
+public interface PluginContentScanner {
+
+  /**
+   * Scanner without resources.
+   */
+  PluginContentScanner EMPTY = new PluginContentScanner() {
+    @Override
+    public Manifest getManifest() throws IOException {
+      return new Manifest();
+    }
+
+    @Override
+    public Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
+        String pluginName, Iterable<Class<? extends Annotation>> annotations)
+        throws InvalidPluginException {
+     return Collections.emptyMap();
+    }
+
+    @Override
+    public Optional<PluginEntry> getEntry(String resourcePath)
+        throws IOException {
+      return Optional.absent();
+    }
+
+    @Override
+    public InputStream getInputStream(PluginEntry entry) throws IOException {
+      throw new FileNotFoundException("Empty plugin");
+    }
+
+    @Override
+    public Enumeration<PluginEntry> entries() {
+      return Collections.emptyEnumeration();
+    }
+  };
+
+  /**
+   * Plugin class extension meta-data
+   *
+   * Class name and annotation value of the class
+   * provided by a plugin to extend an existing
+   * extension point in Gerrit.
+   */
+  public static class ExtensionMetaData {
+    public final String className;
+    public final String annotationValue;
+
+    public ExtensionMetaData(String className, String annotationValue) {
+      this.className = className;
+      this.annotationValue = annotationValue;
+    }
+  }
+
+  /**
+   * Return the plugin meta-data manifest
+   *
+   * @return Manifest of the plugin or null if plugin has no meta-data
+   * @throws IOException if an I/O problem occurred whilst accessing the Manifest
+   */
+  Manifest getManifest() throws IOException;
+
+  /**
+   * Scans the plugin for declared public annotated classes
+   *
+   * @param pluginName the plugin name
+   * @param annotations annotations declared by the plugin classes
+   * @return map of annotations and associated plugin classes found
+   * @throws InvalidPluginException if the plugin is not valid or corrupted
+   */
+  Map<Class<? extends Annotation>, Iterable<ExtensionMetaData>> scan(
+      String pluginName, Iterable<Class<? extends Annotation>> annotations)
+      throws InvalidPluginException;
+
+  /**
+   * Return the plugin resource associated to a path
+   *
+   * @param resourcePath full path of the resource inside the plugin package
+   * @return the resource object or Optional.absent() if the resource was not found
+   * @throws IOException if there was a problem retrieving the resource
+   */
+  Optional<PluginEntry> getEntry(String resourcePath) throws IOException;
+
+  /**
+   * Return the InputStream of the resource entry
+   *
+   * @param entry resource entry inside the plugin package
+   * @return the resource input stream
+   * @throws IOException if there was an I/O problem accessing the resource
+   */
+  InputStream getInputStream(PluginEntry entry) throws IOException;
+
+  /**
+   * Return all the resources inside a plugin
+   *
+   * @return the enumeration of all resources found
+   */
+  Enumeration<PluginEntry> entries();
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginEntry.java
new file mode 100644
index 0000000..6022f02
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginEntry.java
@@ -0,0 +1,64 @@
+// Copyright (C) 2014 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.server.plugins;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * Plugin static resource entry
+ *
+ * Bean representing a static resource inside a plugin.
+ * All static resources are available at <plugin web url>/static
+ * and served by the HttpPluginServlet.
+ */
+public class PluginEntry {
+  public static final String ATTR_CHARACTER_ENCODING = "Character-Encoding";
+  public static final String ATTR_CONTENT_TYPE = "Content-Type";
+
+  private static final Map<Object,String> EMPTY_ATTRS = Collections.emptyMap();
+
+  private final String name;
+  private final long time;
+  private final long size;
+  private final Map<Object, String> attrs;
+
+  public PluginEntry(String name, long time, long size,
+      Map<Object, String> attrs) {
+    this.name = name;
+    this.time = time;
+    this.size = size;
+    this.attrs = attrs;
+  }
+
+  public PluginEntry(String name, long time, long size) {
+    this(name, time, size, EMPTY_ATTRS);
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public long getTime() {
+    return time;
+  }
+
+  public long getSize() {
+    return size;
+  }
+
+  public Map<Object, String> getAttrs() {
+    return attrs;
+  }
+}
\ No newline at end of file
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
index c2c8d03..b56a10c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/PluginLoader.java
@@ -589,8 +589,8 @@
 
       Plugin plugin = new ServerPlugin(name, url,
           pluginUserFactory.create(name),
-          srcJar, snapshot,
-          jarFile, manifest,
+          srcJar, snapshot, new JarFile(srcJar),
+          new JarScanner(srcJar),
           new File(dataDir, name), type, pluginLoader,
           sysModule, sshModule, httpModule);
       cleanupHandles.put(plugin, new CleanupHandle(tmp, jarFile));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
index 8dae5ae..052cdc7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/plugins/ServerPlugin.java
@@ -35,12 +35,13 @@
 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.List;
 import java.util.jar.Attributes;
 import java.util.jar.JarFile;
 import java.util.jar.Manifest;
 
-class ServerPlugin extends Plugin {
+public class ServerPlugin extends Plugin {
 
   /** Unique key that changes whenever a plugin reloads. */
   public static final class CacheKey {
@@ -59,6 +60,7 @@
 
   private final JarFile jarFile;
   private final Manifest manifest;
+  private final PluginContentScanner scanner;
   private final File dataDir;
   private final String pluginCanonicalWebUrl;
   private final ClassLoader classLoader;
@@ -78,28 +80,39 @@
       File srcJar,
       FileSnapshot snapshot,
       JarFile jarFile,
-      Manifest manifest,
+      PluginContentScanner scanner,
       File dataDir,
       ApiType apiType,
       ClassLoader classLoader,
       @Nullable Class<? extends Module> sysModule,
       @Nullable Class<? extends Module> sshModule,
-      @Nullable Class<? extends Module> httpModule) {
+      @Nullable Class<? extends Module> httpModule)
+      throws InvalidPluginException {
     super(name, srcJar, pluginUser, snapshot, apiType);
     this.pluginCanonicalWebUrl = pluginCanonicalWebUrl;
     this.jarFile = jarFile;
-    this.manifest = manifest;
+    this.scanner = scanner;
     this.dataDir = dataDir;
     this.classLoader = classLoader;
     this.sysModule = sysModule;
     this.sshModule = sshModule;
     this.httpModule = httpModule;
+    this.manifest = getPluginManifest(scanner);
   }
 
   File getSrcJar() {
     return getSrcFile();
   }
 
+  private Manifest getPluginManifest(PluginContentScanner scanner)
+      throws InvalidPluginException {
+    try {
+       return scanner.getManifest();
+    } catch (IOException e) {
+      throw new InvalidPluginException("Cannot get plugin manifest", e);
+    }
+  }
+
   @Nullable
   public String getVersion() {
     Attributes main = manifest.getMainAttributes();
@@ -136,7 +149,7 @@
 
     AutoRegisterModules auto = null;
     if (sysModule == null && sshModule == null && httpModule == null) {
-      auto = new AutoRegisterModules(getName(), env, jarFile, classLoader);
+      auto = new AutoRegisterModules(getName(), env, scanner, classLoader);
       auto.discover();
     }
 
@@ -270,4 +283,9 @@
       manager.add(handle);
     }
   }
+
+  @Override
+  public PluginContentScanner getContentScanner() {
+    return scanner;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index da19d19..ed64015 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ApprovalsUtil;
+import com.google.gerrit.server.ChangeMessagesUtil;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.IdentifiedUser;
 import com.google.gerrit.server.git.GitRepositoryManager;
@@ -152,7 +153,8 @@
    * @return instance for testing.
    */
   static ChangeData createForTest(Change.Id id, int currentPatchSetId) {
-    ChangeData cd = new ChangeData(null, null, null, null, null, null, null, null, id);
+    ChangeData cd = new ChangeData(null, null, null, null, null,
+        null, null, null, null, id);
     cd.currentPatchSet = new PatchSet(new PatchSet.Id(id, currentPatchSetId));
     return cd;
   }
@@ -163,6 +165,7 @@
   private final IdentifiedUser.GenericFactory userFactory;
   private final ChangeNotes.Factory notesFactory;
   private final ApprovalsUtil approvalsUtil;
+  private final ChangeMessagesUtil cmUtil;
   private final PatchListCache patchListCache;
   private final NotesMigration notesMigration;
   private final Change.Id legacyId;
@@ -190,6 +193,7 @@
       IdentifiedUser.GenericFactory userFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
       @Assisted ReviewDb db,
@@ -200,6 +204,7 @@
     this.userFactory = userFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
     legacyId = id;
@@ -212,6 +217,7 @@
       IdentifiedUser.GenericFactory userFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
       @Assisted ReviewDb db,
@@ -222,6 +228,7 @@
     this.userFactory = userFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
     legacyId = c.getId();
@@ -235,6 +242,7 @@
       IdentifiedUser.GenericFactory userFactory,
       ChangeNotes.Factory notesFactory,
       ApprovalsUtil approvalsUtil,
+      ChangeMessagesUtil cmUtil,
       PatchListCache patchListCache,
       NotesMigration notesMigration,
       @Assisted ReviewDb db,
@@ -245,6 +253,7 @@
     this.userFactory = userFactory;
     this.notesFactory = notesFactory;
     this.approvalsUtil = approvalsUtil;
+    this.cmUtil = cmUtil;
     this.patchListCache = patchListCache;
     this.notesMigration = notesMigration;
     legacyId = c.getChange().getId();
@@ -521,7 +530,7 @@
   public List<ChangeMessage> messages()
       throws OrmException {
     if (messages == null) {
-      messages = db.changeMessages().byChange(legacyId).toList();
+      messages = cmUtil.byChange(db, notes);
     }
     return messages;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
index db9b29d..173060b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/EqualsLabelPredicate.java
@@ -103,12 +103,6 @@
         return lt;
       }
     }
-
-    for (LabelType lt : types.getLabelTypes()) {
-      if (toFind.equalsIgnoreCase(lt.getAbbreviation())) {
-        return lt;
-      }
-    }
     return null;
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
index 7712650..1d8c126 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/AllProjectsCreator.java
@@ -215,7 +215,6 @@
         new LabelValue((short) 0, "No score"),
         new LabelValue((short) -1, "I would prefer this is not merged as is"),
         new LabelValue((short) -2, "This shall not be merged")));
-    type.setAbbreviation("CR");
     type.setCopyMinScore(true);
     c.getLabelSections().put(type.getName(), type);
     return type;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_77.java b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_77.java
index facfb75..a5166b6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_77.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/schema/Schema_77.java
@@ -203,8 +203,7 @@
     Statement catStmt = ((JdbcSchema) db).getConnection().createStatement();
     try {
       ResultSet catRs = catStmt.executeQuery(
-          "SELECT category_id, name, abbreviated_name, function_name, "
-          + " copy_min_score"
+          "SELECT category_id, name, function_name, copy_min_score"
           + " FROM approval_categories"
           + " ORDER BY position, name");
       PreparedStatement valStmt = ((JdbcSchema) db).getConnection().prepareStatement(
@@ -224,7 +223,6 @@
           LegacyLabelType type =
               new LegacyLabelType(getLabelName(catRs.getString("name")), values);
           type.setId(id);
-          type.setAbbreviation(catRs.getString("abbreviated_name"));
           type.setFunctionName(catRs.getString("function_name"));
           type.setCopyMinScore("Y".equals(catRs.getString("copy_min_score")));
           types.add(type);
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
index f2ad057..f912099 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeNotesTest.java
@@ -32,6 +32,7 @@
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 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.client.PatchSetInfo;
@@ -213,6 +214,31 @@
   }
 
   @Test
+  public void changeMessageCommitFormatSimple() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setChangeMessage("Just a little code change.\n"
+        + "How about a new line");
+    update.commit();
+    assertEquals("refs/changes/01/1/meta", update.getRefName());
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Just a little code change.\n"
+          + "How about a new line\n"
+          + "\n"
+          + "Patch-set: 1\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+  }
+
+  @Test
   public void approvalTombstoneCommitFormat() throws Exception {
     Change c = newChange();
     ChangeUpdate update = newUpdate(c, changeOwner);
@@ -608,6 +634,204 @@
     assertEquals((short) 2, psas.get(1).getValue());
   }
 
+  @Test
+  public void changeMessageOnePatchSet() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.setChangeMessage("Just a little code change.\n");
+    update.commit();
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(1, changeMessages.keySet().size());
+
+    ChangeMessage cm = Iterables.getOnlyElement(changeMessages.get(ps1));
+    assertEquals("Just a little code change.\n",
+        cm.getMessage());
+    assertEquals(changeOwner.getAccount().getId(),
+        cm.getAuthor());
+    assertEquals(ps1, cm.getPatchSetId());
+  }
+
+  @Test
+  public void changeMessagesMultiplePatchSets() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.setChangeMessage("This is the change message for the first PS.");
+    update.commit();
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    incrementPatchSet(c);
+    update = newUpdate(c, changeOwner);
+
+    update.setChangeMessage("This is the change message for the second PS.");
+    update.commit();
+    PatchSet.Id ps2 = c.currentPatchSetId();
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(2, changeMessages.keySet().size());
+
+    ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+    assertEquals("This is the change message for the first PS.",
+        cm1.getMessage());
+    assertEquals(changeOwner.getAccount().getId(),
+        cm1.getAuthor());
+
+    ChangeMessage cm2 = Iterables.getOnlyElement(changeMessages.get(ps2));
+    assertEquals(ps1, cm1.getPatchSetId());
+    assertEquals("This is the change message for the second PS.",
+        cm2.getMessage());
+    assertEquals(changeOwner.getAccount().getId(), cm2.getAuthor());
+    assertEquals(ps2, cm2.getPatchSetId());
+  }
+
+  @Test
+  public void noChangeMessage() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.commit();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Patch-set: 1\n"
+          + "Reviewer: Change Owner <1@gerrit>\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(0, changeMessages.keySet().size());
+  }
+
+  @Test
+  public void changeMessageWithTrailingDoubleNewline() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setChangeMessage("Testing trailing double newline\n"
+        + "\n");
+    update.commit();
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Testing trailing double newline\n"
+          + "\n"
+          + "\n"
+          + "\n"
+          + "Patch-set: 1\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(1, changeMessages.keySet().size());
+
+    ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+    assertEquals("Testing trailing double newline\n" + "\n", cm1.getMessage());
+    assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
+
+  }
+
+  @Test
+  public void changeMessageWithMultipleParagraphs() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.setChangeMessage("Testing paragraph 1\n"
+        + "\n"
+        + "Testing paragraph 2\n"
+        + "\n"
+        + "Testing paragraph 3");
+    update.commit();
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    RevWalk walk = new RevWalk(repo);
+    try {
+      RevCommit commit = walk.parseCommit(update.getRevision());
+      walk.parseBody(commit);
+      assertEquals("Update patch set 1\n"
+          + "\n"
+          + "Testing paragraph 1\n"
+          + "\n"
+          + "Testing paragraph 2\n"
+          + "\n"
+          + "Testing paragraph 3\n"
+          + "\n"
+          + "Patch-set: 1\n",
+          commit.getFullMessage());
+    } finally {
+      walk.release();
+    }
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(1, changeMessages.keySet().size());
+
+    ChangeMessage cm1 = Iterables.getOnlyElement(changeMessages.get(ps1));
+    assertEquals("Testing paragraph 1\n"
+        + "\n"
+        + "Testing paragraph 2\n"
+        + "\n"
+        + "Testing paragraph 3", cm1.getMessage());
+    assertEquals(changeOwner.getAccount().getId(), cm1.getAuthor());
+  }
+
+  @Test
+  public void changeMessageMultipleInOnePatchSet() throws Exception {
+    Change c = newChange();
+    ChangeUpdate update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.setChangeMessage("First change message.\n");
+    update.commit();
+
+    PatchSet.Id ps1 = c.currentPatchSetId();
+
+    update = newUpdate(c, changeOwner);
+    update.putReviewer(changeOwner.getAccount().getId(), REVIEWER);
+    update.setChangeMessage("Second change message.\n");
+    update.commit();
+
+    ChangeNotes notes = newNotes(c);
+    ListMultimap<PatchSet.Id, ChangeMessage> changeMessages =
+        notes.getChangeMessages();
+    assertEquals(1, changeMessages.keySet().size());
+
+    List<ChangeMessage> cm = changeMessages.get(ps1);
+    assertEquals(2, cm.size());
+    assertEquals("First change message.\n",
+        cm.get(0).getMessage());
+    assertEquals(changeOwner.getAccount().getId(),
+        cm.get(0).getAuthor());
+    assertEquals(ps1, cm.get(0).getPatchSetId());
+    assertEquals("Second change message.\n",
+        cm.get(1).getMessage());
+    assertEquals(changeOwner.getAccount().getId(),
+        cm.get(1).getAuthor());
+    assertEquals(ps1, cm.get(1).getPatchSetId());
+  }
+
+
   private Change newChange() {
     Change.Id changeId = new Change.Id(1);
     Change c = new Change(
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
index 15a66ab..996aafa 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/schema/SchemaCreatorTest.java
@@ -128,7 +128,6 @@
     LabelType codeReview = getLabelTypes().byLabel("Code-Review");
     assertNotNull(codeReview);
     assertEquals("Code-Review", codeReview.getName());
-    assertEquals("CR", codeReview.getAbbreviation());
     assertEquals(0, codeReview.getDefaultValue());
     assertEquals("MaxWithBlock", codeReview.getFunctionName());
     assertTrue(codeReview.isCopyMinScore());
diff --git a/gerrit-war/BUCK b/gerrit-war/BUCK
index 7e11aea..fc73973 100644
--- a/gerrit-war/BUCK
+++ b/gerrit-war/BUCK
@@ -49,8 +49,7 @@
 
 prebuilt_jar(
   name = 'log4j-config',
-  binary_jar = genfile('log4j-config.jar'),
-  deps = [':log4j-config__jar'],
+  binary_jar = ':log4j-config__jar',
   visibility = [
     '//:',
     '//tools/eclipse:classpath',
@@ -59,8 +58,7 @@
 
 prebuilt_jar(
   name = 'version',
-  binary_jar = genfile('version.jar'),
-  deps = [':gen_version'],
+  binary_jar = ':gen_version',
   visibility = ['//:'],
 )
 
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index ea6e2a1..e8539c6 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -1,5 +1,6 @@
 include_defs('//lib/maven.defs')
 include_defs('//lib/codemirror/cm3.defs')
+include_defs('//lib/codemirror/closure.defs')
 
 VERSION = '28a638a984'
 SHA1 = '68f8f136092a5965778186fb401a33be34cf73ed'
@@ -8,46 +9,52 @@
 ZIP = 'codemirror-%s.zip' % VERSION
 TOP = 'codemirror-%s' % VERSION
 
+CLOSURE_COMPILER_ARGS = [
+  '--compilation_level SIMPLE_OPTIMIZATIONS',
+  '--warning_level QUIET'
+]
+
 genrule(
   name = 'css',
   cmd = ';'.join([
       ':>$OUT',
       "echo '/** @license' >>$OUT",
-      'unzip -p %s %s/LICENSE >>$OUT' % (ZIP, TOP),
+      'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
       "echo '*/' >>$OUT",
     ] +
-    ['unzip -p %s %s/%s >>$OUT' % (ZIP, TOP, n)
+    ['unzip -p $(location :zip) %s/%s >>$OUT' % (TOP, n)
      for n in CM3_CSS + CM3_THEMES]
   ),
-  srcs = [genfile(ZIP)],
-  deps = [':download'],
+  deps = [':zip'],
   out = 'cm3.css',
 )
 
-# TODO(sop) Minify with Closure JavaScript compiler.
 genrule(
-  name = 'js',
+  name = 'cm3-verbose',
   cmd = ';'.join([
       ':>$OUT',
       "echo '/** @license' >>$OUT",
-      'unzip -p %s %s/LICENSE >>$OUT' % (ZIP, TOP),
+      'unzip -p $(location :zip) %s/LICENSE >>$OUT' % TOP,
       "echo '*/' >>$OUT",
     ] +
-    ['unzip -p %s %s/%s >>$OUT' % (ZIP, TOP, n)
+    ['unzip -p $(location :zip) %s/%s >>$OUT' % (TOP, n)
      for n in CM3_JS]
   ),
-  srcs = [genfile(ZIP)],
-  deps = [':download'],
-  out = 'cm3.js',
+  deps = [':zip'],
+  out = 'cm3-verbose.js',
+)
+
+js_minify(
+  name = 'js',
+  generated = [':cm3-verbose'],
+  compiler_args = CLOSURE_COMPILER_ARGS,
+  out = 'cm3.js'
 )
 
 prebuilt_jar(
   name = 'codemirror',
-  binary_jar = genfile('codemirror.jar'),
-  deps = [
-    ':jar',
-    '//lib:LICENSE-codemirror',
-  ],
+  binary_jar = ':jar',
+  deps = ['//lib:LICENSE-codemirror'],
   visibility = ['PUBLIC'],
 )
 
@@ -55,35 +62,28 @@
   name = 'jar',
   cmd = ';'.join([
     'cd $TMP',
-    'unzip -q $SRCDIR/%s %s' % (
-      ZIP,
-      ' '.join(['%s/mode/%s' % (TOP, n) for n in CM3_MODES])),
+    'unzip -q $(location :zip) %s' %
+    ' '.join(['%s/mode/%s' % (TOP, n) for n in CM3_MODES]),
+    ';'.join(['$(exe :js_minifier) ' +
+    ' '.join(CLOSURE_COMPILER_ARGS) +
+    ' --js_output_file %s/mode/%s.min --js %s/mode/%s'
+    % (TOP, n, TOP, n) for n in CM3_MODES]),
+    ';'.join(['mv %s/mode/%s.min %s/mode/%s' % (TOP, n, TOP, n) for n in CM3_MODES]),
     'mkdir net',
     'mv %s net/codemirror' % TOP,
     'mkdir net/codemirror/lib',
-    'mv $SRCDIR/cm3.css net/codemirror/lib',
-    'mv $SRCDIR/cm3.js net/codemirror/lib',
+    'cp $(location :css) net/codemirror/lib',
+    'cp $(location :js) net/codemirror/lib',
     'zip -qr $OUT *'
   ]),
-  srcs = [
-    genfile(ZIP),
-    genfile('cm3.css'),
-    genfile('cm3.js'),
-  ],
-  deps = [
-    ':download',
-    ':css',
-    ':js',
-  ],
   out = 'codemirror.jar',
 )
 
 genrule(
-  name = 'download',
+  name = 'zip',
   cmd = '$(exe //tools:download_file)' +
     ' -o $OUT' +
     ' -u ' + URL +
     ' -v ' + SHA1,
-  deps = ['//tools:download_file'],
-  out = 'codemirror-' + VERSION + '.zip',
+  out = ZIP,
 )
diff --git a/lib/codemirror/closure.defs b/lib/codemirror/closure.defs
new file mode 100644
index 0000000..e602b9f
--- /dev/null
+++ b/lib/codemirror/closure.defs
@@ -0,0 +1,50 @@
+# https://code.google.com/p/closure-compiler/wiki/BinaryDownloads?tm=2
+CLOSURE_VERSION = '20140407'
+CLOSURE_COMPILER_URL = 'http://dl.google.com/closure-compiler/compiler-%s.zip' % CLOSURE_VERSION
+COMPILER = 'compiler.jar'
+CLOSURE_COMPILER_SHA1 = 'eeb02bfd45eb4a080b66dd423eaee4bdd1d674e9'
+
+def js_minify(
+    name,
+    out,
+    compiler_args = [],
+    srcs = [],
+    generated = []):
+  cmd = ['$(exe :js_minifier) --js_output_file $OUT'] + compiler_args
+  if srcs:
+    cmd.append('$SRCS')
+  if generated:
+    cmd.extend(['$(location %s)' % n for n in generated])
+
+  genrule(
+    name = name,
+    cmd = ' '.join(cmd),
+    srcs = srcs,
+    out = out,
+)
+
+java_binary(
+  name = 'js_minifier',
+  main_class = 'com.google.javascript.jscomp.CommandLineRunner',
+  deps = [':compiler-jar']
+)
+
+prebuilt_jar(
+  name = 'compiler-jar',
+  binary_jar = ':compiler',
+)
+
+genrule(
+  name = 'compiler',
+  cmd = 'unzip -p $(location :closure-compiler-zip) %s >$OUT' % COMPILER,
+  out = COMPILER,
+)
+
+genrule(
+  name = 'closure-compiler-zip',
+  cmd = '$(exe //tools:download_file)' +
+    ' -o $OUT' +
+    ' -u ' + CLOSURE_COMPILER_URL +
+    ' -v ' + CLOSURE_COMPILER_SHA1,
+  out = 'closure-compiler.zip',
+)
diff --git a/lib/guice/BUCK b/lib/guice/BUCK
index 946bf9d..162ad07 100644
--- a/lib/guice/BUCK
+++ b/lib/guice/BUCK
@@ -1,6 +1,7 @@
 include_defs('//lib/maven.defs')
 
 VERSION = '4.0-beta'
+COOKIE_PATCH = '4.0-beta-98-g8d88344'
 EXCLUDE = [
   'META-INF/DEPENDENCIES',
   'META-INF/LICENSE',
@@ -41,8 +42,9 @@
 
 maven_jar(
   name = 'guice-servlet',
-  id = 'com.google.inject.extensions:guice-servlet:' + VERSION,
-  sha1 = '46b44984f254c0bf92d0c972fad1a70292ada28e',
+  id = 'com.google.inject.extensions:guice-servlet:' + COOKIE_PATCH,
+  repository = GERRIT,
+  sha1 = 'fa17d57a083fe9fc86b93f2dc37069573a2e65cd',
   license = 'Apache2.0',
   deps = [':guice'],
   exclude = EXCLUDE,
diff --git a/lib/jgit/BUCK b/lib/jgit/BUCK
index 07671a5..65d6c62 100644
--- a/lib/jgit/BUCK
+++ b/lib/jgit/BUCK
@@ -69,17 +69,15 @@
 
 prebuilt_jar(
   name = 'Edit',
-  binary_jar = genfile('edit-src.jar'),
-  deps = [':jgit_edit_src'],
+  binary_jar = ':jgit_edit_src',
   visibility = ['PUBLIC'],
 )
 
 genrule(
   name = 'jgit_edit_src',
-  cmd = 'unzip -qd $TMP $SRCS org/eclipse/jgit/diff/Edit.java;' +
+  cmd = 'unzip -qd $TMP $(location :jgit_src) ' +
+    'org/eclipse/jgit/diff/Edit.java;' +
     'cd $TMP;' +
     'zip -Dq $OUT org/eclipse/jgit/diff/Edit.java',
-  srcs = [genfile('jgit/org.eclipse.jgit-%s-src.jar' % VERS)],
   out = 'edit-src.jar',
-  deps = [':jgit_src']
 )
diff --git a/lib/maven.defs b/lib/maven.defs
index 0ac5005..67dad4e 100644
--- a/lib/maven.defs
+++ b/lib/maven.defs
@@ -79,9 +79,8 @@
     cmd.append('--unsign')
 
   genrule(
-    name = name + '__download_bin',
+    name = '%s__download_bin' % name,
     cmd = ' '.join(cmd),
-    deps = ['//tools:download_file'],
     out = binjar,
   )
   license = ['//lib:LICENSE-' + license]
@@ -91,32 +90,30 @@
     if src_sha1:
       cmd.extend(['-v', src_sha1])
     genrule(
-      name = name + '__download_src',
+      name = '%s__download_src' % name,
       cmd = ' '.join(cmd),
-      deps = ['//tools:download_file'],
       out = srcjar,
     )
     prebuilt_jar(
-      name = name + '_src',
-      binary_jar = genfile(srcjar),
-      deps = license + [':' + name + '__download_src'],
+      name = '%s_src' % name,
+      binary_jar = ':%s__download_src' % name,
+      deps = license,
       visibility = visibility,
     )
   else:
     srcjar = None
     genrule(
-      name = name + '__download_src',
+      name = '%s__download_src' % name,
       cmd = ':>$OUT',
-      out = '__' + name + '__no_src',
+      out = '__%s__no_src' % name,
     )
 
-  srcdep = [':' + name + '__download_src'] if srcjar else []
   if exported_deps:
     prebuilt_jar(
-      name = name + '__jar',
-      deps = deps + srcdep + license + [':' + name + '__download_bin'],
-      binary_jar = genfile(binjar),
-      source_jar = genfile(srcjar) if srcjar else None,
+      name = '%s__jar' % name,
+      deps = deps,
+      binary_jar = ':%s__download_bin' % name,
+      source_jar = ':%s__download_src' % name if srcjar else None,
     )
     java_library(
       name = name,
@@ -126,9 +123,9 @@
   else:
     prebuilt_jar(
       name = name,
-      deps = deps + srcdep + license + [':' + name + '__download_bin'],
-      binary_jar = genfile(binjar),
-      source_jar = genfile(srcjar) if srcjar else None,
+      deps = deps,
+      binary_jar = ':%s__download_bin' % name,
+      source_jar = ':%s__download_src' % name if srcjar else None,
       visibility = visibility,
     )
 
@@ -141,18 +138,17 @@
   binjar = name + '.jar'
   srcjar = name + '-src.jar'
   genrule(
-    name = name + '__local_bin',
+    name = '%s__local_bin' % name,
     cmd = 'ln -s %s $OUT' % jar,
     out = binjar)
   if src:
     genrule(
-      name = name + '__local_src',
+      name = '%s__local_src' % name,
       cmd = 'ln -s %s $OUT' % src,
       out = srcjar)
     prebuilt_jar(
-      name = name + '_src',
-      deps = [':' + name + '__local_src'],
-      binary_jar = genfile(srcjar),
+      name = '%s_src' % name,
+      binary_jar = ':%s__local_src' % name,
       visibility = visibility,
     )
   else:
@@ -160,8 +156,8 @@
 
   prebuilt_jar(
     name = name,
-    deps = deps + [':' + name + '__local_bin'],
-    binary_jar = genfile(binjar),
-    source_jar = genfile(srcjar) if srcjar else None,
+    deps = deps,
+    binary_jar = ':%s__local_bin' % name,
+    source_jar = ':%s__local_src' % name if srcjar else None,
     visibility = visibility,
   )
diff --git a/lib/prolog/prolog.defs b/lib/prolog/prolog.defs
index 62e4c6d..d0636f5 100644
--- a/lib/prolog/prolog.defs
+++ b/lib/prolog/prolog.defs
@@ -23,16 +23,12 @@
       ' $TMP $OUT ' +
       ' '.join(srcs),
     srcs = srcs,
-    deps = ['//lib/prolog:compiler'],
     out = name + '.src.zip',
   )
   java_library(
     name = name + '__lib',
-    srcs = [genfile(name + '.src.zip')],
-    deps = [
-      ':' + name + '__pl2j',
-      '//lib/prolog:prolog-cafe',
-    ] + deps,
+    srcs = [':' + name + '__pl2j'],
+    deps = ['//lib/prolog:prolog-cafe'] + deps,
   )
   genrule(
     name = name + '__ln',
@@ -42,7 +38,6 @@
   )
   prebuilt_jar(
     name = name,
-    binary_jar = genfile(name + '.jar'),
-    deps = [':%s__ln' % name],
+    binary_jar = ':%s__ln' % name,
     visibility = visibility,
   )
diff --git a/plugins/BUCK b/plugins/BUCK
index 480cd4c..134f275 100644
--- a/plugins/BUCK
+++ b/plugins/BUCK
@@ -28,11 +28,11 @@
     ';'.join(['echo >&2 plugins/'+n+' is required.' for n in NEED]) +
     (';echo >&2;exit 1;' if NEED else '') +
     'mkdir -p $TMP/WEB-INF/plugins;' +
-    'for s in $SRCS;do ln -s $s $TMP/WEB-INF/plugins;done;' +
+    'for s in ' +
+    ' '.join(['$(location //%s/%s:%s)' % (BASE, n, n) for n in HAVE]) +
+    ';do ln -s $s $TMP/WEB-INF/plugins;done;' +
     'cd $TMP;' +
     'zip -qr $OUT .',
-  srcs = [genfile('%s/%s.jar' % (n, n)) for n in HAVE],
-  deps = ['//%s/%s:%s' % (BASE, n, n) for n in HAVE],
   out = 'core.zip',
   visibility = ['//:release'],
 )
diff --git a/plugins/replication b/plugins/replication
index d92b6f2..3f67e32 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit d92b6f2f92b06f0f189439029a2abb7107d23838
+Subproject commit 3f67e32dc01955cdec9bac64b19fc7cfcb20b816
diff --git a/tools/build.defs b/tools/build.defs
index 9ce28f2..8b858cd 100644
--- a/tools/build.defs
+++ b/tools/build.defs
@@ -14,12 +14,8 @@
 
 # These definitions support building a runnable version of Gerrit.
 
-DOCS_SRC = genfile('Documentation/html.zip')
+DOCS_HTML = '//Documentation:html'
 DOCS_LIB = '//Documentation:index_lib'
-DOCS_DEP = [
-  '//Documentation:html',
-  '//Documentation:index_lib',
-]
 LIBS = [
   '//gerrit-war:log4j-config',
   '//gerrit-war:init',
@@ -50,24 +46,19 @@
   for l in pgmlibs:
     cmd.extend(['--pgmlib', l])
 
-  src = []
   dep = []
   if docs:
-    src.append(DOCS_SRC)
-    dep.extend(DOCS_DEP)
+    cmd.append('$(location %s)' % DOCS_HTML)
+    dep.append(DOCS_LIB)
     cmd.extend(['--lib', DOCS_LIB])
   if context:
     for t in context:
-      dep.append(t)
       cmd.append('$(location %s)' % t)
-  if src:
-    cmd.append('$SRCS')
 
   genrule(
     name = name,
     cmd = ' '.join(cmd),
-    srcs = src,
-    deps = libs + pgmlibs + dep + ['//tools:pack_war'],
+    deps = libs + pgmlibs + dep,
     out = name + '.war',
     visibility = visibility,
   )
diff --git a/tools/default.defs b/tools/default.defs
index 4c6e898..16f5d10 100644
--- a/tools/default.defs
+++ b/tools/default.defs
@@ -28,7 +28,6 @@
     cmd = '$(exe //lib/antlr:antlr-tool) -o $TMP $SRCS;' +
       'cd $TMP;' +
       'zip -qr $OUT .',
-    deps = ['//lib/antlr:antlr-tool'],
     out = out,
   )
 
@@ -110,8 +109,7 @@
   if gwt_module:
     prebuilt_jar(
       name = '%s-static-jar' % name,
-      binary_jar = genfile('%s-static.zip' % name),
-      deps = [':%s-static' % name],
+      binary_jar = ':%s-static' % name,
     )
     genrule(
       name = '%s-static' % name,
@@ -121,7 +119,6 @@
         ';cd $TMP' +
         ';zip -qr $OUT .',
       out = '%s-static.zip' % name,
-      deps = [':%s__gwt_application' % name]
     )
     gwt_binary(
       name = name + '__gwt_application',
@@ -136,10 +133,10 @@
 
   java_binary(
     name = name,
-    manifest_file = genfile('MANIFEST.MF'),
+    manifest_file = ':%s__manifest' % name,
+    merge_manifests = False,
     deps = [
       ':%s__plugin' % name,
-      ':%s__manifest' % name,
     ] + static_jars,
     visibility = visibility,
   )
@@ -189,7 +186,6 @@
       '-d $TMP',
     ]) + ';jar cf $OUT -C $TMP .',
     srcs = srcs,
-    deps = deps,
     out = name + '.jar',
     visibility = visibility,
 )
diff --git a/tools/download_file.py b/tools/download_file.py
index d4ddf1c..d5d8b66 100755
--- a/tools/download_file.py
+++ b/tools/download_file.py
@@ -28,6 +28,7 @@
 CACHE_DIR = path.join(GERRIT_HOME, 'buck-cache')
 LOCAL_PROPERTIES = 'local.properties'
 
+
 def hashfile(p):
   d = sha1()
   with open(p, 'rb') as f:
@@ -38,6 +39,7 @@
       d.update(b)
   return d.hexdigest()
 
+
 def safe_mkdirs(d):
   if path.isdir(d):
     return
@@ -47,6 +49,7 @@
     if not path.isdir(d):
       raise err
 
+
 def download_properties(root_dir):
   """ Get the download properties.
 
@@ -73,6 +76,7 @@
       pass
   return p
 
+
 def cache_entry(args):
   if args.v:
     h = args.v
@@ -81,6 +85,7 @@
   name = '%s-%s' % (path.basename(args.o), h)
   return path.join(CACHE_DIR, name)
 
+
 opts = OptionParser()
 opts.add_option('-o', help='local output file')
 opts.add_option('-u', help='URL to download')
@@ -137,30 +142,24 @@
   exclude += args.x
 if args.exclude_java_sources:
   try:
-    zf = ZipFile(cache_ent, 'r')
-    try:
+    with ZipFile(cache_ent, 'r') as zf:
       for n in zf.namelist():
         if n.endswith('.java'):
           exclude.append(n)
-    finally:
-      zf.close()
   except (BadZipfile, LargeZipFile) as err:
-    print('error opening %s: %s'  % (cache_ent, err), file=stderr)
+    print('error opening %s: %s' % (cache_ent, err), file=stderr)
     exit(1)
 
 if args.unsign:
   try:
-    zf = ZipFile(cache_ent, 'r')
-    try:
+    with ZipFile(cache_ent, 'r') as zf:
       for n in zf.namelist():
         if (n.endswith('.RSA')
             or n.endswith('.SF')
             or n.endswith('.LIST')):
           exclude.append(n)
-    finally:
-      zf.close()
   except (BadZipfile, LargeZipFile) as err:
-    print('error opening %s: %s'  % (cache_ent, err), file=stderr)
+    print('error opening %s: %s' % (cache_ent, err), file=stderr)
     exit(1)
 
 safe_mkdirs(path.dirname(args.o))
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py
index 50644eb..2008316 100755
--- a/tools/eclipse/project.py
+++ b/tools/eclipse/project.py
@@ -32,7 +32,7 @@
 ])
 
 ROOT = path.abspath(__file__)
-for _ in range(0, 3):
+while not path.exists(path.join(ROOT, '.buckconfig')):
   ROOT = path.dirname(ROOT)
 
 opts = OptionParser()
diff --git a/tools/gwt-constants.defs b/tools/gwt-constants.defs
index 44d2a62..cc09d3e 100644
--- a/tools/gwt-constants.defs
+++ b/tools/gwt-constants.defs
@@ -6,7 +6,6 @@
 ]
 
 GWT_PLUGIN_DEPS = [
-  '//gerrit-gwtui-common:client',
   '//gerrit-plugin-gwtui:gwtui-api-lib',
   '//lib/gwt:user',
 ]
diff --git a/tools/maven/mvn.py b/tools/maven/mvn.py
index 9e36b48..cc10816 100644
--- a/tools/maven/mvn.py
+++ b/tools/maven/mvn.py
@@ -16,9 +16,8 @@
 from __future__ import print_function
 from optparse import OptionParser
 from os import path, environ
-
+from subprocess import check_output
 from sys import stderr
-from tools.util import check_output
 
 opts = OptionParser()
 opts.add_option('--repository', help='maven repository id')
@@ -34,7 +33,7 @@
   exit(1)
 
 root = path.abspath(__file__)
-for _ in range(0, 3):
+while not path.exists(path.join(root, '.buckconfig')):
   root = path.dirname(root)
 
 if 'install' == args.a:
diff --git a/tools/pack_war.py b/tools/pack_war.py
index 09ff054..ee87b51 100755
--- a/tools/pack_war.py
+++ b/tools/pack_war.py
@@ -16,9 +16,8 @@
 from __future__ import print_function
 from optparse import OptionParser
 from os import getcwd, chdir, makedirs, path, symlink
-from subprocess import check_call
+from subprocess import check_call, check_output
 import sys
-from util import check_output
 
 opts = OptionParser()
 opts.add_option('-o', help='path to write WAR to')
@@ -31,6 +30,7 @@
 root = war[:war.index('buck-out')]
 jars = set()
 
+
 def link_jars(libs, directory):
   makedirs(directory)
   while not path.isfile('.buckconfig'):
@@ -51,7 +51,7 @@
 try:
   for s in ctx:
     check_call(['unzip', '-q', '-d', war, s])
-  check_call(['zip', '-9qr', args.o, '.'], cwd = war)
+  check_call(['zip', '-9qr', args.o, '.'], cwd=war)
 except KeyboardInterrupt:
   print('Interrupted by user', file=sys.stderr)
   exit(1)
diff --git a/tools/util.py b/tools/util.py
index 9115ac7..6dfe719 100644
--- a/tools/util.py
+++ b/tools/util.py
@@ -14,13 +14,6 @@
 
 from os import path
 
-try:
-  from subprocess import check_output
-except ImportError:
-  from subprocess import Popen, PIPE
-  def check_output(*cmd):
-    return Popen(*cmd, stdout=PIPE).communicate()[0]
-
 REPO_ROOTS = {
   'GERRIT': 'http://gerrit-maven.storage.googleapis.com',
   'GERRIT_API': 'https://gerrit-api.commondatastorage.googleapis.com/release',
@@ -29,6 +22,7 @@
   'MAVEN_LOCAL': 'file://' + path.expanduser('~/.m2/repository'),
 }
 
+
 def resolve_url(url, redirects):
   """ Resolve URL of a Maven artifact.