ExportReviewNotes: Load changes from notedb if enabled

Make sure that change notes are created only once.

Change-Id: Ie9b936cbedc333c0d7697af706900187a34e7060
Signed-off-by: Edwin Kempin <ekempin@google.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
index 249f9b6..9fce4c8 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/CreateReviewNotes.java
@@ -156,7 +156,10 @@
       }
 
       for (RevCommit c : rw) {
-        ObjectId content = createNoteContent(loadPatchSet(c, branch));
+        PatchSet ps = loadPatchSet(c, branch);
+        ChangeNotes notes =
+            notesFactory.create(reviewDb, project, ps.getId().getParentKey());
+        ObjectId content = createNoteContent(notes, ps);
         if (content != null) {
           monitor.update(1);
           getNotes().set(c, content);
@@ -166,19 +169,20 @@
     }
   }
 
-  void createNotes(List<Change> changes, ProgressMonitor monitor)
+  void createNotes(List<ChangeNotes> notes, ProgressMonitor monitor)
       throws OrmException, IOException {
     try (RevWalk rw = new RevWalk(git)) {
       if (monitor == null) {
         monitor = NullProgressMonitor.INSTANCE;
       }
 
-      for (Change c : changes) {
+      for (ChangeNotes cn : notes) {
         monitor.update(1);
-        PatchSet ps = reviewDb.patchSets().get(c.currentPatchSetId());
+        PatchSet ps =
+            reviewDb.patchSets().get(cn.getChange().currentPatchSetId());
         ObjectId commitId = ObjectId.fromString(ps.getRevision().get());
         RevCommit commit = rw.parseCommit(commitId);
-        getNotes().set(commitId, createNoteContent(ps));
+        getNotes().set(commitId, createNoteContent(cn, ps));
         getMessage().append("* ").append(commit.getShortMessage()).append("\n");
       }
     }
@@ -225,13 +229,13 @@
     }
   }
 
-  private ObjectId createNoteContent(PatchSet ps)
+  private ObjectId createNoteContent(ChangeNotes notes, PatchSet ps)
       throws OrmException, IOException {
     HeaderFormatter fmt =
         new HeaderFormatter(gerritServerIdent.getTimeZone(), anonymousCowardName);
     if (ps != null) {
       try {
-        createCodeReviewNote(ps, fmt);
+        createCodeReviewNote(notes, ps, fmt);
         return getInserter().insert(Constants.OBJ_BLOB,
             fmt.toString().getBytes("UTF-8"));
       } catch (NoSuchChangeException e) {
@@ -255,15 +259,13 @@
     return null; // TODO: createNoCodeReviewNote(branch, c, fmt);
   }
 
-  private void createCodeReviewNote(PatchSet ps, HeaderFormatter fmt)
-      throws OrmException, NoSuchChangeException {
+  private void createCodeReviewNote(ChangeNotes notes, PatchSet ps,
+      HeaderFormatter fmt) throws OrmException, NoSuchChangeException {
     // This races with the label normalization/writeback done by MergeOp. It may
     // repeat some work, but results should be identical except in the case of
     // an additional race with a permissions change.
     // TODO(dborowitz): These will eventually be stamped in the ChangeNotes at
     // commit time so we will be able to skip this normalization step.
-    ChangeNotes notes =
-        notesFactory.create(reviewDb, project, ps.getId().getParentKey());
     Change change = notes.getChange();
     ChangeControl ctl = changeControlFactory.controlFor(
         notes, userFactory.create(change.getOwner()));
diff --git a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/ExportReviewNotes.java b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/ExportReviewNotes.java
index 80c5fa5..d1cbb9a 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/ExportReviewNotes.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/reviewnotes/ExportReviewNotes.java
@@ -14,12 +14,14 @@
 
 package com.googlesource.gerrit.plugins.reviewnotes;
 
-import com.google.common.collect.ArrayListMultimap;
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableListMultimap;
 import com.google.common.collect.ListMultimap;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.sshd.SshCommand;
 import com.google.gwtorm.server.OrmException;
 import com.google.gwtorm.server.SchemaFactory;
@@ -33,10 +35,8 @@
 import org.kohsuke.args4j.Option;
 
 import java.io.IOException;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 
 /** Export review notes for all submitted changes in all projects. */
 public class ExportReviewNotes extends SshCommand {
@@ -52,7 +52,10 @@
   @Inject
   private CreateReviewNotes.Factory reviewNotesFactory;
 
-  private ListMultimap<Project.NameKey, Change> changes;
+  @Inject
+  private ChangeNotes.Factory notesFactory;
+
+  private ListMultimap<Project.NameKey, ChangeNotes> changes;
   private ThreadSafeProgressMonitor monitor;
 
   @Override
@@ -61,12 +64,10 @@
       threads = 1;
     }
 
-    List<Change> allChangeList = allChanges();
-    monitor = new ThreadSafeProgressMonitor(new TextProgressMonitor(stdout));
-    monitor.beginTask("Scanning changes", allChangeList.size());
-    changes = cluster(allChangeList);
-    allChangeList = null;
+    changes = mergedChanges();
 
+    monitor = new ThreadSafeProgressMonitor(new TextProgressMonitor(stdout));
+    monitor.beginTask("Scanning merged changes", changes.size());
     monitor.startWorkers(threads);
     for (int tid = 0; tid < threads; tid++) {
       new Worker().start();
@@ -75,32 +76,25 @@
     monitor.endTask();
   }
 
-  private List<Change> allChanges() {
-    try (ReviewDb db = database.open()){
-      return db.changes().all().toList();
-    } catch (OrmException e) {
+  private ListMultimap<Project.NameKey, ChangeNotes> mergedChanges() {
+    try (ReviewDb db = database.open()) {
+      return notesFactory.create(db, new Predicate<ChangeNotes>() {
+        @Override
+        public boolean apply(ChangeNotes notes) {
+          return notes.getChange().getStatus() == Change.Status.MERGED;
+        }
+      });
+    } catch (OrmException | IOException e) {
       stderr.println("Cannot read changes from database " + e.getMessage());
-      return Collections.emptyList();
+      return ImmutableListMultimap.of();
     }
   }
 
-  private ListMultimap<Project.NameKey, Change> cluster(List<Change> changes) {
-    ListMultimap<Project.NameKey, Change> m = ArrayListMultimap.create();
-    for (Change change : changes) {
-      if (change.getStatus() == Change.Status.MERGED) {
-        m.put(change.getProject(), change);
-      } else {
-        monitor.update(1);
-      }
-    }
-    return m;
-  }
-
-  private void export(ReviewDb db, Project.NameKey project, List<Change> changes)
-      throws IOException, OrmException {
+  private void export(ReviewDb db, Project.NameKey project,
+      List<ChangeNotes> notes) throws IOException, OrmException {
     try (Repository git = gitManager.openRepository(project)) {
       CreateReviewNotes crn = reviewNotesFactory.create(db, project, git);
-      crn.createNotes(changes, monitor);
+      crn.createNotes(notes, monitor);
       crn.commitNotes();
     } catch (RepositoryNotFoundException e) {
       stderr.println("Unable to open project: " + project.get());
@@ -109,27 +103,27 @@
     }
   }
 
-  private Map.Entry<Project.NameKey, List<Change>> next() {
+  private Map.Entry<Project.NameKey, List<ChangeNotes>> next() {
     synchronized (changes) {
       if (changes.isEmpty()) {
         return null;
       }
 
       final Project.NameKey name = changes.keySet().iterator().next();
-      final List<Change> list = changes.removeAll(name);
-      return new Map.Entry<Project.NameKey, List<Change>>() {
+      final List<ChangeNotes> list = changes.removeAll(name);
+      return new Map.Entry<Project.NameKey, List<ChangeNotes>>() {
         @Override
         public Project.NameKey getKey() {
           return name;
         }
 
         @Override
-        public List<Change> getValue() {
+        public List<ChangeNotes> getValue() {
           return list;
         }
 
         @Override
-        public List<Change> setValue(List<Change> value) {
+        public List<ChangeNotes> setValue(List<ChangeNotes> value) {
           throw new UnsupportedOperationException();
         }
       };
@@ -141,7 +135,7 @@
     public void run() {
       try (ReviewDb db = database.open()){
         for (;;) {
-          Entry<Project.NameKey, List<Change>> next = next();
+          Map.Entry<Project.NameKey, List<ChangeNotes>> next = next();
           if (next != null) {
             try {
               export(db, next.getKey(), next.getValue());