Merge "Remove zero space char from linkify"
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 5894ae0..c1cd0e8 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -1,14 +1,5 @@
 = Gerrit Code Review - Building with Bazel
 
-Bazel is the tool for building Gerrit.
-It has been recently introduced and, apart from the custom non-core plugins,
-all the components have been adapted to use Bazel.
-
-Nice to have:
-
-* JGit build from local tree.
-* coverage
-
 [[installation]]
 == Installation
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index d3b85df..b63b280 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -2277,6 +2277,9 @@
 +
 Metric recording statistical distribution (rate) of values.
 
+Note that metrics cannot be recorded from plugin init steps that
+are run during site initialization.
+
 Plugin metrics are recorded under `plugins/${plugin-name}/${metric-name}`.
 
 See the replication metrics in the
diff --git a/WORKSPACE b/WORKSPACE
index 1d1c1a3..6c7663e 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -536,38 +536,39 @@
     sha1 = "0c9cfae15c74f62491d4f28def0dff1dabe52a47",
 )
 
-PROLOG_VERS = "1.4.2"
+PROLOG_VERS = "1.4.3"
+PROLOG_REPO = GERRIT
 
 maven_jar(
     name = "prolog_runtime",
     artifact = "com.googlecode.prolog-cafe:prolog-runtime:" + PROLOG_VERS,
     attach_source = False,
-    repository = GERRIT,
-    sha1 = "4421b4806b6e3a318680f6ab1d57569e857169c6",
+    repository = PROLOG_REPO,
+    sha1 = "d5206556cbc76ffeab21313ffc47b586a1efbcbb",
 )
 
 maven_jar(
     name = "prolog_compiler",
     artifact = "com.googlecode.prolog-cafe:prolog-compiler:" + PROLOG_VERS,
     attach_source = False,
-    repository = GERRIT,
-    sha1 = "7e5a7ca5efe7db7f69e015cf492f8f04665244d8",
+    repository = PROLOG_REPO,
+    sha1 = "f37032cf1dec3e064427745bc59da5a12757a3b2",
 )
 
 maven_jar(
     name = "prolog_io",
     artifact = "com.googlecode.prolog-cafe:prolog-io:" + PROLOG_VERS,
     attach_source = False,
-    repository = GERRIT,
-    sha1 = "d177f6211d1013e0f31a507127f5c87a7f6941f3",
+    repository = PROLOG_REPO,
+    sha1 = "d02b2640b26f64036b6ba2b45e4acc79281cea17",
 )
 
 maven_jar(
     name = "cafeteria",
     artifact = "com.googlecode.prolog-cafe:prolog-cafeteria:" + PROLOG_VERS,
     attach_source = False,
-    repository = GERRIT,
-    sha1 = "11f396cb2588b65e6a78070488aaa58d12bf000e",
+    repository = PROLOG_REPO,
+    sha1 = "e3b1860c63e57265e5435f890263ad82dafa724f",
 )
 
 maven_jar(
diff --git a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
index 0783688..bdc1bb3 100644
--- a/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
+++ b/gerrit-acceptance-framework/src/test/java/com/google/gerrit/acceptance/InMemoryTestingDatabaseModule.java
@@ -79,7 +79,8 @@
     makeSiteDirs(p);
 
     bind(GitRepositoryManager.class)
-      .toInstance(new InMemoryRepositoryManager());
+      .to(InMemoryRepositoryManager.class);
+    bind(InMemoryRepositoryManager.class).in(SINGLETON);
 
     bind(MetricMaker.class).to(DisabledMetricMaker.class);
     bind(DataSourceType.class).to(InMemoryH2Type.class);
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
index d9a054c..ad1f844 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/BaseInit.java
@@ -23,6 +23,8 @@
 import com.google.common.base.Strings;
 import com.google.gerrit.common.Die;
 import com.google.gerrit.common.IoUtil;
+import com.google.gerrit.metrics.DisabledMetricMaker;
+import com.google.gerrit.metrics.MetricMaker;
 import com.google.gerrit.pgm.init.api.ConsoleUI;
 import com.google.gerrit.pgm.init.api.InitFlags;
 import com.google.gerrit.pgm.init.api.InstallAllPlugins;
@@ -286,6 +288,8 @@
             LibraryDownload.class).toInstance(getSkippedDownloads());
         bind(Boolean.class).annotatedWith(
             LibraryDownload.class).toInstance(skipAllDownloads());
+
+        bind(MetricMaker.class).to(DisabledMetricMaker.class);
       }
     });
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index a31d1fc..7e47d1e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -79,7 +79,10 @@
    */
     public MetaDataUpdate create(Project.NameKey name, IdentifiedUser user,
         BatchRefUpdate batch) throws RepositoryNotFoundException, IOException {
-      return create(name, mgr.openRepository(name), user, batch);
+      Repository repo = mgr.openRepository(name);
+      MetaDataUpdate md = create(name, repo, user, batch);
+      md.setCloseRepository(true);
+      return md;
     }
 
     /**
@@ -117,7 +120,8 @@
      * </pre>
      *
      * @param name project name.
-     * @param repository GIT respository
+     * @param repository the repository to update; the caller is responsible for
+     *     closing the repository.
      * @param user user for the update.
      * @param batch batch update to use; the caller is responsible for committing
      *     the update.
@@ -157,7 +161,9 @@
     /** @see User#create(Project.NameKey, IdentifiedUser, BatchRefUpdate) */
     public MetaDataUpdate create(Project.NameKey name, BatchRefUpdate batch)
         throws RepositoryNotFoundException, IOException {
-      MetaDataUpdate md = factory.create(name, mgr.openRepository(name), batch);
+      Repository repo = mgr.openRepository(name);
+      MetaDataUpdate md = factory.create(name, repo, batch);
+      md.setCloseRepository(true);
       md.getCommitBuilder().setAuthor(serverIdent);
       md.getCommitBuilder().setCommitter(serverIdent);
       return md;
@@ -166,32 +172,34 @@
 
   interface InternalFactory {
     MetaDataUpdate create(@Assisted Project.NameKey projectName,
-        @Assisted Repository db, @Assisted @Nullable BatchRefUpdate batch);
+        @Assisted Repository repository,
+        @Assisted @Nullable BatchRefUpdate batch);
   }
 
   private final GitReferenceUpdated gitRefUpdated;
   private final Project.NameKey projectName;
-  private final Repository db;
+  private final Repository repository;
   private final BatchRefUpdate batch;
   private final CommitBuilder commit;
   private boolean allowEmpty;
   private boolean insertChangeId;
+  private boolean closeRepository;
   private IdentifiedUser author;
 
   @AssistedInject
   public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
-      @Assisted Project.NameKey projectName, @Assisted Repository db,
+      @Assisted Project.NameKey projectName, @Assisted Repository repository,
       @Assisted @Nullable BatchRefUpdate batch) {
     this.gitRefUpdated = gitRefUpdated;
     this.projectName = projectName;
-    this.db = db;
+    this.repository = repository;
     this.batch = batch;
     this.commit = new CommitBuilder();
   }
 
   public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
-      Project.NameKey projectName, Repository db) {
-    this(gitRefUpdated, projectName, db, null);
+      Project.NameKey projectName, Repository repository) {
+    this(gitRefUpdated, projectName, repository, null);
   }
 
   /** Set the commit message used when committing the update. */
@@ -214,6 +222,10 @@
     this.insertChangeId = insertChangeId;
   }
 
+  public void setCloseRepository(boolean closeRepository) {
+    this.closeRepository = closeRepository;
+  }
+
   /** @return batch in which to run the update, or {@code null} for no batch. */
   BatchRefUpdate getBatch() {
     return batch;
@@ -222,7 +234,9 @@
   /** Close the cached Repository handle. */
   @Override
   public void close() {
-    getRepository().close();
+    if (closeRepository) {
+      getRepository().close();
+    }
   }
 
   Project.NameKey getProjectName() {
@@ -230,7 +244,7 @@
   }
 
   public Repository getRepository() {
-    return db;
+    return repository;
   }
 
   boolean allowEmpty() {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
index c1608b3..e867caf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/send/ChangeEmail.java
@@ -174,6 +174,7 @@
     }
     setChangeSubjectHeader();
     setHeader("X-Gerrit-Change-Id", "" + change.getKey().get());
+    setHeader("X-Gerrit-Change-Number", "" + change.getChangeId());
     setChangeUrlHeader();
     setCommitIdHeader();
   }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
index ddc4196..b6e59dd 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/InMemoryRepositoryManager.java
@@ -115,6 +115,7 @@
       throws RepositoryNotFoundException {
     Repo repo = repos.get(normalize(name));
     if (repo != null) {
+      repo.incrementOpen();
       return repo;
     }
     throw new RepositoryNotFoundException(name.get());
diff --git a/plugins/hooks b/plugins/hooks
index c0be9e0..66741e2 160000
--- a/plugins/hooks
+++ b/plugins/hooks
@@ -1 +1 @@
-Subproject commit c0be9e0e6931eee98a1d9dd69b8abd5c26d4be51
+Subproject commit 66741e2c77b92574f7dd7012e5326029eca783f5
diff --git a/plugins/replication b/plugins/replication
index 43bcff7..e5099ce 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 43bcff79216014f822d26491345dd3137e887bc1
+Subproject commit e5099cec8dbce52054640a9d0710071b1aa18e2b
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
new file mode 100644
index 0000000..0dee091
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.html
@@ -0,0 +1,52 @@
+<!--
+Copyright (C) 2017 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="gr-admin-view">
+  <template>
+    <style>
+      main {
+        margin: 2em auto;
+        max-width: 46em;
+      }
+      h1 {
+        margin-bottom: .1em;
+      }
+      @media only screen and (max-width: 67em) {
+        main {
+          margin: 2em 0 2em 15em;
+        }
+      }
+      @media only screen and (max-width: 53em) {
+        .loading {
+          padding: 0 var(--default-horizontal-margin);
+        }
+        main {
+          margin: 2em 1em;
+        }
+    </style>
+    <main>
+      <h1>Admin</h1>
+      <section>
+        This page is not yet implemented in PolyGerrit. View it in the
+        <a id="gwtLink" href$="/?polygerrit=0#[[path]]" rel="external">
+        Old UI</a>
+      </section>
+    </main>
+  </template>
+  <script src="gr-admin-view.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
new file mode 100644
index 0000000..cb248e1
--- /dev/null
+++ b/polygerrit-ui/app/elements/admin/gr-admin-view/gr-admin-view.js
@@ -0,0 +1,24 @@
+// Copyright (C) 2017 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.
+(function() {
+  'use strict';
+
+  Polymer({
+    is: 'gr-admin-view',
+
+    properties: {
+      path: String,
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/core/gr-router/gr-router.js b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
index 3819bfe..b439fc8 100644
--- a/polygerrit-ui/app/elements/core/gr-router/gr-router.js
+++ b/polygerrit-ui/app/elements/core/gr-router/gr-router.js
@@ -91,6 +91,17 @@
       });
     });
 
+    page('/admin/(.*)', loadUser, function(data) {
+      restAPI.getLoggedIn().then(function(loggedIn) {
+        if (loggedIn) {
+          data.params.view = 'gr-admin-view';
+          app.params = data.params;
+        } else {
+          page.redirect('/login/' + encodeURIComponent(data.canonicalPath));
+        }
+      });
+    });
+
     function queryHandler(data) {
       data.params.view = 'gr-change-list-view';
       app.params = data.params;
diff --git a/polygerrit-ui/app/elements/gr-app.html b/polygerrit-ui/app/elements/gr-app.html
index 2b910e6..6499f08 100644
--- a/polygerrit-ui/app/elements/gr-app.html
+++ b/polygerrit-ui/app/elements/gr-app.html
@@ -18,6 +18,8 @@
 <link rel="import" href="../behaviors/keyboard-shortcut-behavior/keyboard-shortcut-behavior.html">
 <link rel="import" href="../styles/app-theme.html">
 
+<link rel="import" href="./admin/gr-admin-view/gr-admin-view.html">
+
 <link rel="import" href="./core/gr-error-manager/gr-error-manager.html">
 <link rel="import" href="./core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html">
 <link rel="import" href="./core/gr-main-header/gr-main-header.html">
@@ -123,6 +125,9 @@
             on-account-detail-update="_handleAccountDetailUpdate">
         </gr-settings-view>
       </template>
+      <template is="dom-if" if="[[_showAdminView]]" restamp="true">
+        <gr-admin-view path="[[_path]]"></gr-admin-view>
+      </template>
       <div id="errorView" class="errorView" hidden>
         <div class="errorEmoji">[[_lastError.emoji]]</div>
         <div class="errorText">[[_lastError.text]]</div>
diff --git a/polygerrit-ui/app/elements/gr-app.js b/polygerrit-ui/app/elements/gr-app.js
index 72f5254..f38bb91 100644
--- a/polygerrit-ui/app/elements/gr-app.js
+++ b/polygerrit-ui/app/elements/gr-app.js
@@ -112,6 +112,7 @@
       this.set('_showChangeView', view === 'gr-change-view');
       this.set('_showDiffView', view === 'gr-diff-view');
       this.set('_showSettingsView', view === 'gr-settings-view');
+      this.set('_showAdminView', view === 'gr-admin-view');
       if (this.params.justRegistered) {
         this.$.registration.open();
       }
diff --git a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
index a817068..afa5163 100644
--- a/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
+++ b/polygerrit-ui/app/elements/settings/gr-settings-view/gr-settings-view.html
@@ -157,8 +157,8 @@
               <select
                   is="gr-select"
                   bind-value="{{_localPrefs.time_format}}">
-                <option value="HHMM_12">4:10 PM</option>
-                <option value="HHMM_24">16:10</option>
+                <option value="HHMM_12">4:10 PM (PST)</option>
+                <option value="HHMM_24">16:10 (PST)</option>
               </select>
             </span>
           </section>
diff --git a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
index b69d369..2ec32d0 100644
--- a/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
+++ b/polygerrit-ui/app/elements/shared/gr-button/gr-button.html
@@ -127,7 +127,9 @@
         color: #fff;
       }
       :host([primary][disabled]) {
-        background-color: #888;
+        background-color: #4d90fe;
+        color: #fff;
+        opacity: .5;
       }
     </style>
     <content></content>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
index 3d0cf5a..ae6bb75 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.html
@@ -26,8 +26,9 @@
         display: inline;
       }
     </style>
-    <span title$="[[_computeFullDateStr(dateStr, _timeFormat)]]"
-        >[[_computeDateStr(dateStr, _timeFormat, _relative)]]</span>
+    <span title$="[[_computeFullDateStr(dateStr, _timeFormat)]]">
+      [[_computeDateStr(dateStr, _timeFormat, _relative)]]
+    </span>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
   </template>
   <script src="gr-date-formatter.js"></script>
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
index f6117e4..a8c9010 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter.js
@@ -21,7 +21,9 @@
 
   var TimeFormats = {
     TIME_12: 'h:mm A', // 2:14 PM
+    TIME_12_WITH_SEC: 'h:mm:ss A', // 2:14:00 PM
     TIME_24: 'HH:mm', // 14:14
+    TIME_24_WITH_SEC: 'HH:mm:ss', // 14:14:00
     MONTH_DAY: 'MMM DD', // Aug 29
     MONTH_DAY_YEAR: 'MMM DD, YYYY', // Aug 29, 1997
   };
@@ -44,6 +46,14 @@
       this._loadPreferences();
     },
 
+    _getTzString: function() {
+      return ' ' + new Date().toString().split(' ').pop();
+    },
+
+    _getUtcOffsetString: function() {
+      return ' UTC' + moment().format('Z');
+    },
+
     _loadPreferences: function() {
       return this._getLoggedIn().then(function(loggedIn) {
         if (!loggedIn) {
@@ -121,18 +131,26 @@
       var now = new Date();
       var format = TimeFormats.MONTH_DAY_YEAR;
       if (this._isWithinDay(now, date)) {
-        format = timeFormat;
+        return date.format(timeFormat) + this._getTzString();
       } else if (this._isWithinHalfYear(now, date)) {
         format = TimeFormats.MONTH_DAY;
       }
       return date.format(format);
     },
 
+    _timeToSecondsFormat: function(timeFormat) {
+      return timeFormat === TimeFormats.TIME_12 ?
+          TimeFormats.TIME_12_WITH_SEC :
+          TimeFormats.TIME_24_WITH_SEC;
+    },
+
     _computeFullDateStr: function(dateStr, timeFormat) {
       if (!dateStr) { return ''; }
       var date = moment(util.parseDate(dateStr));
       if (!date.isValid()) { return ''; }
-      return date.format(TimeFormats.MONTH_DAY_YEAR + ', ' + timeFormat);
+      var format = TimeFormats.MONTH_DAY_YEAR + ', ';
+      format += this._timeToSecondsFormat(timeFormat);
+      return date.format(format) + this._getUtcOffsetString();
     },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
index 8d65bc3..0faf102 100644
--- a/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-date-formatter/gr-date-formatter_test.html
@@ -33,6 +33,15 @@
 <script>
   suite('gr-date-formatter tests', function() {
     var element;
+    var sandbox;
+
+    setup(function() {
+      sandbox = sinon.sandbox.create();
+    });
+
+    teardown(function() {
+      sandbox.restore();
+    });
 
     /**
      * Parse server-formatter date and normalize into current timezone.
@@ -47,13 +56,12 @@
       // Normalize and convert the date to mimic server response.
       dateStr = normalizedDate(dateStr)
           .toJSON().replace('T', ' ').slice(0, -1);
-      var clock = sinon.useFakeTimers(normalizedDate(nowStr).getTime());
+      sandbox.useFakeTimers(normalizedDate(nowStr).getTime());
       element.dateStr = dateStr;
       flush(function() {
         var span = element.$$('span');
-        assert.equal(span.textContent, expected);
+        assert.equal(span.textContent.trim(), expected);
         assert.equal(span.title, expectedTooltip);
-        clock.restore();
         done();
       });
     }
@@ -74,6 +82,8 @@
           {time_format: 'HHMM_24', relative_date_in_change_table: false}
         ).then(function() {
           element = fixture('basic');
+          sandbox.stub(element, '_getTzString').returns('');
+          sandbox.stub(element, '_getUtcOffsetString').returns('');
           element._loadPreferences().then(function() { done(); });
         });
       });
@@ -85,26 +95,26 @@
 
       test('Within 24 hours on same day', function(done) {
         testDates('2015-07-29 20:34:14.985000000',
-                  '2015-07-29 15:34:14.985000000',
-                  '15:34', 'Jul 29, 2015, 15:34', done);
+            '2015-07-29 15:34:14.985000000',
+            '15:34', 'Jul 29, 2015, 15:34:14', done);
       });
 
       test('Within 24 hours on different days', function(done) {
         testDates('2015-07-29 03:34:14.985000000',
-                  '2015-07-28 20:25:14.985000000',
-                  'Jul 28', 'Jul 28, 2015, 20:25', done);
+            '2015-07-28 20:25:14.985000000',
+            'Jul 28', 'Jul 28, 2015, 20:25:14', done);
       });
 
       test('More than 24 hours but less than six months', function(done) {
         testDates('2015-07-29 20:34:14.985000000',
-                  '2015-06-15 03:25:14.985000000',
-                  'Jun 15', 'Jun 15, 2015, 03:25', done);
+            '2015-06-15 03:25:14.985000000',
+            'Jun 15', 'Jun 15, 2015, 03:25:14', done);
       });
 
       test('More than six months', function(done) {
         testDates('2015-09-15 20:34:00.000000000',
-                  '2015-01-15 03:25:00.000000000',
-                  'Jan 15, 2015', 'Jan 15, 2015, 03:25', done);
+            '2015-01-15 03:25:00.000000000',
+            'Jan 15, 2015', 'Jan 15, 2015, 03:25:00', done);
       });
     });
 
@@ -115,14 +125,16 @@
           {time_format: 'HHMM_12'}
         ).then(function() {
           element = fixture('basic');
+          sandbox.stub(element, '_getTzString').returns('');
+          sandbox.stub(element, '_getUtcOffsetString').returns('');
           element._loadPreferences().then(function() { done(); });
         });
       });
 
       test('Within 24 hours on same day', function(done) {
         testDates('2015-07-29 20:34:14.985000000',
-                  '2015-07-29 15:34:14.985000000',
-                  '3:34 PM', 'Jul 29, 2015, 3:34 PM', done);
+            '2015-07-29 15:34:14.985000000',
+            '3:34 PM', 'Jul 29, 2015, 3:34:14 PM', done);
       });
     });
 
@@ -132,20 +144,22 @@
           {time_format: 'HHMM_12', relative_date_in_change_table: true}
         ).then(function() {
           element = fixture('basic');
+          sandbox.stub(element, '_getTzString').returns('');
+          sandbox.stub(element, '_getUtcOffsetString').returns('');
           element._loadPreferences().then(function() { done(); });
         });
       });
 
       test('Within 24 hours on same day', function(done) {
         testDates('2015-07-29 20:34:14.985000000',
-                  '2015-07-29 15:34:14.985000000',
-                  '5 hours ago', 'Jul 29, 2015, 3:34 PM', done);
+            '2015-07-29 15:34:14.985000000',
+            '5 hours ago', 'Jul 29, 2015, 3:34:14 PM', done);
       });
 
       test('More than six months', function(done) {
         testDates('2015-09-15 20:34:00.000000000',
-                  '2015-01-15 03:25:00.000000000',
-                  '8 months ago', 'Jan 15, 2015, 3:25 AM', done);
+            '2015-01-15 03:25:00.000000000',
+            '8 months ago', 'Jan 15, 2015, 3:25:00 AM', done);
       });
     });
 
@@ -155,6 +169,7 @@
           {time_format: 'HHMM_12', relative_date_in_change_table: true}
         ).then(function() {
           element = fixture('basic');
+          sandbox.stub(element, '_getTzString').returns('');
           element._loadPreferences().then(function() { done(); });
         });
       });
@@ -169,6 +184,7 @@
       setup(function(done) {
         return stubRestAPI(null).then(function() {
           element = fixture('basic');
+          sandbox.stub(element, '_getTzString').returns('');
           element._loadPreferences().then(function() { done(); });
         });
       });
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
index f61cd1b..21a6bc6 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser.js
@@ -24,7 +24,10 @@
   };
 
   GrReviewerUpdatesParser.parse = function(change) {
-    if (!change.messages || !change.reviewer_updates) {
+    if (!change ||
+        !change.messages ||
+        !change.reviewer_updates ||
+        !change.reviewer_updates.length) {
       return change;
     }
     var parser = new GrReviewerUpdatesParser(change);
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
index 86b5549..7da5f74 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-reviewer-updates-parser_test.html
@@ -39,6 +39,62 @@
       sandbox.restore();
     });
 
+    test('ignores changes without messages', function() {
+      var change = {};
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_groupUpdates');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_formatUpdates');
+      assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._groupUpdates.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._formatUpdates.called);
+    });
+
+    test('ignores changes without reviewer updates', function() {
+      var change = {
+        messages: [],
+      };
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_groupUpdates');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_formatUpdates');
+      assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._groupUpdates.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._formatUpdates.called);
+    });
+
+    test('ignores changes with empty reviewer updates', function() {
+      var change = {
+        messages: [],
+        reviewer_updates: [],
+      };
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_filterRemovedMessages');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_groupUpdates');
+      sandbox.stub(
+          GrReviewerUpdatesParser.prototype, '_formatUpdates');
+      assert.strictEqual(GrReviewerUpdatesParser.parse(change), change);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._filterRemovedMessages.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._groupUpdates.called);
+      assert.isFalse(
+          GrReviewerUpdatesParser.prototype._formatUpdates.called);
+    });
+
     test('filter removed messages', function() {
       var change = {
           messages: [