Merge "Move ChangeRebuilder#rebuildProject to RebuildNoteDb"
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 edb5b4e..4967b0f 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
@@ -25,12 +25,11 @@
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.ImmutableSortedSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.ListMultimap;
-import com.google.common.collect.Maps;
 import com.google.common.collect.Multimap;
 import com.google.common.collect.Multimaps;
 import com.google.common.collect.Ordering;
@@ -349,7 +348,7 @@
 
   // Lazy defensive copies of mutable ReviewDb types, to avoid polluting the
   // ChangeNotesCache from handlers.
-  private ImmutableMap<PatchSet.Id, PatchSet> patchSets;
+  private ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets;
   private ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals;
 
   @VisibleForTesting
@@ -368,18 +367,26 @@
     return change;
   }
 
-  public ImmutableMap<PatchSet.Id, PatchSet> getPatchSets() {
+  public ImmutableSortedMap<PatchSet.Id, PatchSet> getPatchSets() {
     if (patchSets == null) {
-      patchSets = ImmutableMap.copyOf(
-          Maps.transformValues(state.patchSets(), PatchSet::new));
+      ImmutableSortedMap.Builder<PatchSet.Id, PatchSet> b =
+          ImmutableSortedMap.orderedBy(comparing(PatchSet.Id::get));
+      for (Map.Entry<PatchSet.Id, PatchSet> e : state.patchSets()) {
+        b.put(e.getKey(), new PatchSet(e.getValue()));
+      }
+      patchSets = b.build();
     }
     return patchSets;
   }
 
   public ImmutableListMultimap<PatchSet.Id, PatchSetApproval> getApprovals() {
     if (approvals == null) {
-      approvals = ImmutableListMultimap.copyOf(
-          Multimaps.transformValues(state.approvals(), PatchSetApproval::new));
+      ImmutableListMultimap.Builder<PatchSet.Id, PatchSetApproval> b =
+          ImmutableListMultimap.builder();
+      for (Map.Entry<PatchSet.Id, PatchSetApproval> e : state.approvals()) {
+        b.put(e.getKey(), new PatchSetApproval(e.getValue()));
+      }
+      approvals = b.build();
     }
     return approvals;
   }
@@ -526,7 +533,7 @@
 
   public PatchSet getCurrentPatchSet() {
     PatchSet.Id psId = change.currentPatchSetId();
-    return checkNotNull(state.patchSets().get(psId),
+    return checkNotNull(getPatchSets().get(psId),
         "missing current patch set %s", psId.get());
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
index 198eb2f..73b7aec 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesCache.java
@@ -101,7 +101,8 @@
           + P // status
           + P + set(state.pastAssignees(), K)
           + P + set(state.hashtags(), str(10))
-          + P + map(state.patchSets(), patchSet())
+          + P + list(state.patchSets(), patchSet())
+          + P + list(state.allPastReviewers(), approval())
           + P + list(state.reviewerUpdates(), 4 * O + K + K + P)
           + P + list(state.submitRecords(), P + list(2, str(4) + P + K) + P)
           + P + list(state.allChangeMessages(), changeMessage())
@@ -172,6 +173,15 @@
           + P; // pushCertificate
     }
 
+    private static int approval() {
+      return O
+          + P + patchSetId() + P + K + P + O + str(10)
+          + 2 // value
+          + P + T // granted
+          + P // tag
+          + P; // realAccountId
+    }
+
     private static int changeMessage() {
       int key = K + str(20);
       return O
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
index 67eb328..e0f7920 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNotesState.java
@@ -15,14 +15,12 @@
 package com.google.gerrit.server.notedb;
 
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.Comparator.comparing;
 
 import com.google.auto.value.AutoValue;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.ImmutableSortedMap;
 import com.google.common.collect.Multimap;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubmitRecord;
@@ -59,17 +57,17 @@
     return new AutoValue_ChangeNotesState(
         change.getId(),
         null,
-        ImmutableSet.<Account.Id>of(),
-        ImmutableSet.<String>of(),
-        ImmutableSortedMap.<PatchSet.Id, PatchSet>of(),
-        ImmutableListMultimap.<PatchSet.Id, PatchSetApproval>of(),
+        ImmutableSet.of(),
+        ImmutableSet.of(),
+        ImmutableList.of(),
+        ImmutableList.of(),
         ReviewerSet.empty(),
-        ImmutableList.<Account.Id>of(),
-        ImmutableList.<ReviewerStatusUpdate>of(),
-        ImmutableList.<SubmitRecord>of(),
-        ImmutableList.<ChangeMessage>of(),
-        ImmutableListMultimap.<PatchSet.Id, ChangeMessage>of(),
-        ImmutableListMultimap.<RevId, Comment>of());
+        ImmutableList.of(),
+        ImmutableList.of(),
+        ImmutableList.of(),
+        ImmutableList.of(),
+        ImmutableListMultimap.of(),
+        ImmutableListMultimap.of());
   }
 
   static ChangeNotesState create(
@@ -117,8 +115,8 @@
             status),
         ImmutableSet.copyOf(pastAssignees),
         ImmutableSet.copyOf(hashtags),
-        ImmutableSortedMap.copyOf(patchSets, comparing(PatchSet.Id::get)),
-        ImmutableListMultimap.copyOf(approvals),
+        ImmutableList.copyOf(patchSets.entrySet()),
+        ImmutableList.copyOf(approvals.entries()),
         reviewers,
         ImmutableList.copyOf(allPastReviewers),
         ImmutableList.copyOf(reviewerUpdates),
@@ -161,8 +159,8 @@
   // Other related to this Change.
   abstract ImmutableSet<Account.Id> pastAssignees();
   abstract ImmutableSet<String> hashtags();
-  abstract ImmutableSortedMap<PatchSet.Id, PatchSet> patchSets();
-  abstract ImmutableListMultimap<PatchSet.Id, PatchSetApproval> approvals();
+  abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSet>> patchSets();
+  abstract ImmutableList<Map.Entry<PatchSet.Id, PatchSetApproval>> approvals();
 
   abstract ReviewerSet reviewers();
   abstract ImmutableList<Account.Id> allPastReviewers();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
index 4f6e2e8..363cfd7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java
@@ -23,21 +23,27 @@
 
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Iterator;
 import java.util.Objects;
 
-class EventList<E extends Event> extends ArrayList<E> {
-  private static final long serialVersionUID = 1L;
+class EventList<E extends Event> implements Iterable<E> {
+  private final ArrayList<E> list = new ArrayList<>();
 
-  private E getLast() {
-    return get(size() - 1);
+  @Override
+  public Iterator<E> iterator() {
+    return list.iterator();
   }
 
-  private long getLastTime() {
-    return getLast().when.getTime();
+  void add(E e) {
+    list.add(e);
   }
 
-  private long getFirstTime() {
-    return get(0).when.getTime();
+  void clear() {
+    list.clear();
+  }
+
+  boolean isEmpty() {
+    return list.isEmpty();
   }
 
   boolean canAdd(E e) {
@@ -114,4 +120,24 @@
   String getTag() {
     return getLast().tag;
   }
+
+  private E get(int i) {
+    return list.get(i);
+  }
+
+  private int size() {
+    return list.size();
+  }
+
+  private E getLast() {
+    return list.get(list.size() - 1);
+  }
+
+  private long getLastTime() {
+    return getLast().when.getTime();
+  }
+
+  private long getFirstTime() {
+    return list.get(0).when.getTime();
+  }
 }
diff --git a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
index 530b7be..adf0bf1 100644
--- a/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
+++ b/polygerrit-ui/app/behaviors/gr-path-list-behavior/gr-path-list-behavior_test.html
@@ -13,6 +13,8 @@
 See the License for the specific language governing permissions and
 limitations under the License.
 -->
+<!-- Polymer included for the html import polyfill. -->
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
 <script src="../../bower_components/web-component-tester/browser.js"></script>
 <title>gr-path-list-behavior</title>
 
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 1a6759b..96c6f93 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -375,6 +375,12 @@
     },
 
     _handleGetDiffError: function(response) {
+      // Loading the diff may respond with 409 if the file is too large. In this
+      // case, use a toast error..
+      if (response.status === 409) {
+        this.fire('server-error', {response: response});
+        return;
+      }
       this.fire('page-error', {response: response});
     },
 
@@ -386,8 +392,8 @@
           this.path,
           this._handleGetDiffError.bind(this)).then(function(diff) {
                this.filesWeblinks = {
-                 meta_a: diff.meta_a && diff.meta_a.web_links,
-                 meta_b: diff.meta_b && diff.meta_b.web_links,
+                 meta_a: diff && diff.meta_a && diff.meta_a.web_links,
+                 meta_b: diff && diff.meta_b && diff.meta_b.web_links,
                };
                return diff;
              }.bind(this));
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
index b9b49c0..20ed61f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff_test.html
@@ -327,6 +327,16 @@
         });
         content.click();
       });
+
+      test('_getDiff handles null diff responses', function(done) {
+        stub('gr-rest-api-interface', {
+          getDiff: function() { return Promise.resolve(null); },
+        });
+        element.changeNum = 123;
+        element.patchRange = {basePatchNum: 1, patchNum: 2};
+        element.path = 'file.txt';
+        element._getDiff().then(done);
+      });
     });
 
     suite('logged in', function() {
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
index 31918dd..601a056 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete.js
@@ -118,11 +118,11 @@
     },
 
     attached: function() {
-      this.listen(document.body, 'click', '_handleBodyClick');
+      this.listen(document.body, 'tap', '_handleBodyTap');
     },
 
     detached: function() {
-      this.unlisten(document.body, 'click', '_handleBodyClick');
+      this.unlisten(document.body, 'tap', '_handleBodyTap');
     },
 
     get focusStart() {
@@ -233,7 +233,7 @@
       }
     },
 
-    _handleBodyClick: function(e) {
+    _handleBodyTap: function(e) {
       var eventPath = Polymer.dom(e).path;
       for (var i = 0; i < eventPath.length; i++) {
         if (eventPath[i] === this) {
@@ -244,6 +244,7 @@
     },
 
     _handleSuggestionTap: function(e) {
+      e.stopPropagation();
       this.$.cursor.setCursor(e.target);
       this._commit();
       this.focus();
diff --git a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
index 7a26e72..685f568 100644
--- a/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-autocomplete/gr-autocomplete_test.html
@@ -290,6 +290,7 @@
       assert.isTrue(focusSpy.called);
       assert.isTrue(commitSpy.called);
       assert.isTrue(element.$.suggestions.hasAttribute('hidden'));
+      assert.isTrue(element._focused);
       focusSpy.restore();
       commitSpy.restore();
     });