Merge changes Iea17346e,Ifba1d457,I7895e7c1
* changes: Move static types out of ChangeRebuilderImpl Move ChangeRebuilder(Impl) to a subpackage ChangeBundle: Remove infeasible TODO
This commit is contained in:
		@@ -59,10 +59,10 @@ import com.google.gerrit.server.git.RepoRefCache;
 | 
			
		||||
import com.google.gerrit.server.git.UpdateException;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeBundle;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeRebuilder.NoPatchSetsException;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbChangeState;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
 | 
			
		||||
import com.google.gerrit.server.notedb.TestChangeRebuilderWrapper;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder.NoPatchSetsException;
 | 
			
		||||
import com.google.gerrit.testutil.ConfigSuite;
 | 
			
		||||
import com.google.gerrit.testutil.NoteDbChecker;
 | 
			
		||||
import com.google.gerrit.testutil.NoteDbMode;
 | 
			
		||||
 
 | 
			
		||||
@@ -47,8 +47,8 @@ import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.git.WorkQueue;
 | 
			
		||||
import com.google.gerrit.server.index.DummyIndexModule;
 | 
			
		||||
import com.google.gerrit.server.index.change.ReindexAfterUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.notedb.NotesMigration;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 
 | 
			
		||||
@@ -20,8 +20,8 @@ import com.google.gerrit.extensions.restapi.Response;
 | 
			
		||||
import com.google.gerrit.extensions.restapi.RestModifyView;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.change.Rebuild.Input;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.notedb.NotesMigration;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,6 +27,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.config.AllUsersName;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeNotesCommit.ChangeNotesRevWalk;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
 
 | 
			
		||||
@@ -53,6 +53,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
			
		||||
import com.google.gerrit.server.ReviewerSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 | 
			
		||||
import com.google.gwtorm.client.Column;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
@@ -241,10 +242,7 @@ public class ChangeBundle {
 | 
			
		||||
    checkColumns(Change.Id.class, 1);
 | 
			
		||||
 | 
			
		||||
    checkColumns(Change.class,
 | 
			
		||||
        1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18,
 | 
			
		||||
        // TODO(dborowitz): It's potentially possible to compare noteDbState in
 | 
			
		||||
        // the Change with the state implied by a ChangeNotes.
 | 
			
		||||
        101);
 | 
			
		||||
        1, 2, 3, 4, 5, 7, 8, 10, 12, 13, 14, 17, 18, 101);
 | 
			
		||||
    checkColumns(ChangeMessage.Key.class, 1, 2);
 | 
			
		||||
    checkColumns(ChangeMessage.class, 1, 2, 3, 4, 5, 6);
 | 
			
		||||
    checkColumns(PatchSet.Id.class, 1, 2);
 | 
			
		||||
 
 | 
			
		||||
@@ -61,20 +61,21 @@ import java.util.Locale;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
public class ChangeNoteUtil {
 | 
			
		||||
  static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
 | 
			
		||||
  static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id");
 | 
			
		||||
  static final FooterKey FOOTER_COMMIT = new FooterKey("Commit");
 | 
			
		||||
  static final FooterKey FOOTER_GROUPS = new FooterKey("Groups");
 | 
			
		||||
  static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
 | 
			
		||||
  static final FooterKey FOOTER_LABEL = new FooterKey("Label");
 | 
			
		||||
  static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
 | 
			
		||||
  static final FooterKey FOOTER_STATUS = new FooterKey("Status");
 | 
			
		||||
  static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject");
 | 
			
		||||
  static final FooterKey FOOTER_SUBMISSION_ID = new FooterKey("Submission-id");
 | 
			
		||||
  static final FooterKey FOOTER_SUBMITTED_WITH =
 | 
			
		||||
  public static final FooterKey FOOTER_BRANCH = new FooterKey("Branch");
 | 
			
		||||
  public static final FooterKey FOOTER_CHANGE_ID = new FooterKey("Change-id");
 | 
			
		||||
  public static final FooterKey FOOTER_COMMIT = new FooterKey("Commit");
 | 
			
		||||
  public static final FooterKey FOOTER_GROUPS = new FooterKey("Groups");
 | 
			
		||||
  public static final FooterKey FOOTER_HASHTAGS = new FooterKey("Hashtags");
 | 
			
		||||
  public static final FooterKey FOOTER_LABEL = new FooterKey("Label");
 | 
			
		||||
  public static final FooterKey FOOTER_PATCH_SET = new FooterKey("Patch-set");
 | 
			
		||||
  public static final FooterKey FOOTER_STATUS = new FooterKey("Status");
 | 
			
		||||
  public static final FooterKey FOOTER_SUBJECT = new FooterKey("Subject");
 | 
			
		||||
  public static final FooterKey FOOTER_SUBMISSION_ID =
 | 
			
		||||
      new FooterKey("Submission-id");
 | 
			
		||||
  public static final FooterKey FOOTER_SUBMITTED_WITH =
 | 
			
		||||
      new FooterKey("Submitted-with");
 | 
			
		||||
  static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
 | 
			
		||||
  static final FooterKey FOOTER_TAG = new FooterKey("Tag");
 | 
			
		||||
  public static final FooterKey FOOTER_TOPIC = new FooterKey("Topic");
 | 
			
		||||
  public static final FooterKey FOOTER_TAG = new FooterKey("Tag");
 | 
			
		||||
 | 
			
		||||
  private static final String AUTHOR = "Author";
 | 
			
		||||
  private static final String BASE_PATCH_SET = "Base-for-patch-set";
 | 
			
		||||
 
 | 
			
		||||
@@ -52,6 +52,7 @@ import com.google.gerrit.server.ReviewerSet;
 | 
			
		||||
import com.google.gerrit.server.ReviewerStatusUpdate;
 | 
			
		||||
import com.google.gerrit.server.git.RefCache;
 | 
			
		||||
import com.google.gerrit.server.git.RepoRefCache;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectCache;
 | 
			
		||||
import com.google.gerrit.server.query.change.ChangeData;
 | 
			
		||||
 
 | 
			
		||||
@@ -284,7 +284,7 @@ public class ChangeUpdate extends AbstractChangeUpdate {
 | 
			
		||||
    this.commitSubject = commitSubject;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void setSubject(String subject) {
 | 
			
		||||
  public void setSubject(String subject) {
 | 
			
		||||
    this.subject = subject;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -31,6 +31,7 @@ import com.google.gerrit.reviewdb.client.RevId;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.git.RepoRefCache;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager.StagedResult;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.inject.assistedinject.Assisted;
 | 
			
		||||
 
 | 
			
		||||
@@ -78,7 +78,7 @@ public class NoteDbChangeState {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @VisibleForTesting
 | 
			
		||||
  static NoteDbChangeState parse(Change.Id id, String str) {
 | 
			
		||||
  public static NoteDbChangeState parse(Change.Id id, String str) {
 | 
			
		||||
    if (str == null) {
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
@@ -204,7 +204,7 @@ public class NoteDbChangeState {
 | 
			
		||||
    return id.get().equals(draftIds.get(accountId));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs)
 | 
			
		||||
  public boolean isUpToDate(RefCache changeRepoRefs, RefCache draftsRepoRefs)
 | 
			
		||||
      throws IOException {
 | 
			
		||||
    if (!isChangeUpToDate(changeRepoRefs)) {
 | 
			
		||||
      return false;
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ import com.google.gerrit.reviewdb.client.Change.Id;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project.NameKey;
 | 
			
		||||
import com.google.gerrit.reviewdb.server.ReviewDb;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 | 
			
		||||
import com.google.inject.TypeLiteral;
 | 
			
		||||
import com.google.inject.name.Names;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -119,10 +119,10 @@ public class NoteDbUpdateManager implements AutoCloseable {
 | 
			
		||||
    @Nullable abstract NoteDbUpdateManager.StagedResult staged();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static class OpenRepo implements AutoCloseable {
 | 
			
		||||
    final Repository repo;
 | 
			
		||||
    final RevWalk rw;
 | 
			
		||||
    final ChainedReceiveCommands cmds;
 | 
			
		||||
  public static class OpenRepo implements AutoCloseable {
 | 
			
		||||
    public final Repository repo;
 | 
			
		||||
    public final RevWalk rw;
 | 
			
		||||
    public final ChainedReceiveCommands cmds;
 | 
			
		||||
 | 
			
		||||
    private final InMemoryInserter tempIns;
 | 
			
		||||
    @Nullable private final ObjectInserter finalIns;
 | 
			
		||||
@@ -143,7 +143,7 @@ public class NoteDbUpdateManager implements AutoCloseable {
 | 
			
		||||
      this.close = close;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Optional<ObjectId> getObjectId(String refName) throws IOException {
 | 
			
		||||
    public Optional<ObjectId> getObjectId(String refName) throws IOException {
 | 
			
		||||
      return cmds.get(refName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -233,17 +233,17 @@ public class NoteDbUpdateManager implements AutoCloseable {
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  NoteDbUpdateManager setCheckExpectedState(boolean checkExpectedState) {
 | 
			
		||||
  public NoteDbUpdateManager setCheckExpectedState(boolean checkExpectedState) {
 | 
			
		||||
    this.checkExpectedState = checkExpectedState;
 | 
			
		||||
    return this;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  OpenRepo getChangeRepo() throws IOException {
 | 
			
		||||
  public OpenRepo getChangeRepo() throws IOException {
 | 
			
		||||
    initChangeRepo();
 | 
			
		||||
    return changeRepo;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  OpenRepo getAllUsersRepo() throws IOException {
 | 
			
		||||
  public OpenRepo getAllUsersRepo() throws IOException {
 | 
			
		||||
    initAllUsersRepo();
 | 
			
		||||
    return allUsersRepo;
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -20,6 +20,8 @@ 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.notedb.NoteDbUpdateManager.Result;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,25 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gwtorm.server.OrmRuntimeException;
 | 
			
		||||
 | 
			
		||||
class AbortUpdateException extends OrmRuntimeException {
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
  AbortUpdateException() {
 | 
			
		||||
    super("aborted");
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,41 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSetApproval;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
 | 
			
		||||
class ApprovalEvent extends Event {
 | 
			
		||||
  private PatchSetApproval psa;
 | 
			
		||||
 | 
			
		||||
  ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) {
 | 
			
		||||
    super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted(),
 | 
			
		||||
        changeCreatedOn, psa.getTag());
 | 
			
		||||
    this.psa = psa;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    update.putApproval(psa.getLabel(), psa.getValue());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,106 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.ChangeMessage;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
class ChangeMessageEvent extends Event {
 | 
			
		||||
  private static final Pattern TOPIC_SET_REGEXP =
 | 
			
		||||
      Pattern.compile("^Topic set to (.+)$");
 | 
			
		||||
  private static final Pattern TOPIC_CHANGED_REGEXP =
 | 
			
		||||
      Pattern.compile("^Topic changed from (.+) to (.+)$");
 | 
			
		||||
  private static final Pattern TOPIC_REMOVED_REGEXP =
 | 
			
		||||
      Pattern.compile("^Topic (.+) removed$");
 | 
			
		||||
 | 
			
		||||
  private static final Pattern STATUS_ABANDONED_REGEXP =
 | 
			
		||||
      Pattern.compile("^Abandoned(\n.*)*$");
 | 
			
		||||
  private static final Pattern STATUS_RESTORED_REGEXP =
 | 
			
		||||
      Pattern.compile("^Restored(\n.*)*$");
 | 
			
		||||
 | 
			
		||||
  private final ChangeMessage message;
 | 
			
		||||
  private final Change noteDbChange;
 | 
			
		||||
 | 
			
		||||
  ChangeMessageEvent(ChangeMessage message, Change noteDbChange,
 | 
			
		||||
      Timestamp changeCreatedOn) {
 | 
			
		||||
    super(message.getPatchSetId(), message.getAuthor(),
 | 
			
		||||
        message.getWrittenOn(), changeCreatedOn, message.getTag());
 | 
			
		||||
    this.message = message;
 | 
			
		||||
    this.noteDbChange = noteDbChange;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    update.setChangeMessage(message.getMessage());
 | 
			
		||||
    setTopic(update);
 | 
			
		||||
    setStatus(update);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void setTopic(ChangeUpdate update) {
 | 
			
		||||
    String msg = message.getMessage();
 | 
			
		||||
    if (msg == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    Matcher m = TOPIC_SET_REGEXP.matcher(msg);
 | 
			
		||||
    if (m.matches()) {
 | 
			
		||||
      String topic = m.group(1);
 | 
			
		||||
      update.setTopic(topic);
 | 
			
		||||
      noteDbChange.setTopic(topic);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    m = TOPIC_CHANGED_REGEXP.matcher(msg);
 | 
			
		||||
    if (m.matches()) {
 | 
			
		||||
      String topic = m.group(2);
 | 
			
		||||
      update.setTopic(topic);
 | 
			
		||||
      noteDbChange.setTopic(topic);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (TOPIC_REMOVED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
      update.setTopic(null);
 | 
			
		||||
      noteDbChange.setTopic(null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void setStatus(ChangeUpdate update) {
 | 
			
		||||
    String msg = message.getMessage();
 | 
			
		||||
    if (msg == null) {
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    if (STATUS_ABANDONED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
      update.setStatus(Change.Status.ABANDONED);
 | 
			
		||||
      noteDbChange.setStatus(Change.Status.ABANDONED);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (STATUS_RESTORED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
      update.setStatus(Change.Status.NEW);
 | 
			
		||||
      noteDbChange.setStatus(Change.Status.NEW);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -12,7 +12,7 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.notedb;
 | 
			
		||||
package com.google.gerrit.server.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.ImmutableMultimap;
 | 
			
		||||
import com.google.common.util.concurrent.ListenableFuture;
 | 
			
		||||
@@ -20,6 +20,8 @@ import com.google.common.util.concurrent.ListeningExecutorService;
 | 
			
		||||
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.notedb.ChangeBundle;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
@@ -12,19 +12,16 @@
 | 
			
		||||
// See the License for the specific language governing permissions and
 | 
			
		||||
// limitations under the License.
 | 
			
		||||
 | 
			
		||||
package com.google.gerrit.server.notedb;
 | 
			
		||||
package com.google.gerrit.server.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.MoreObjects.firstNonNull;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkArgument;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkNotNull;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
 | 
			
		||||
import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
 | 
			
		||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_HASHTAGS;
 | 
			
		||||
import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET;
 | 
			
		||||
import static java.util.concurrent.TimeUnit.SECONDS;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.MoreObjects;
 | 
			
		||||
import com.google.common.base.Optional;
 | 
			
		||||
import com.google.common.base.Predicate;
 | 
			
		||||
import com.google.common.base.Splitter;
 | 
			
		||||
@@ -56,20 +53,25 @@ import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
			
		||||
import com.google.gerrit.server.account.AccountCache;
 | 
			
		||||
import com.google.gerrit.server.config.AnonymousCowardName;
 | 
			
		||||
import com.google.gerrit.server.git.ChainedReceiveCommands;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeBundle;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeNoteUtil;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbChangeState;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo;
 | 
			
		||||
import com.google.gerrit.server.notedb.NoteDbUpdateManager.Result;
 | 
			
		||||
import com.google.gerrit.server.notedb.NotesMigration;
 | 
			
		||||
import com.google.gerrit.server.notedb.ReviewerStateInternal;
 | 
			
		||||
import com.google.gerrit.server.patch.PatchListCache;
 | 
			
		||||
import com.google.gerrit.server.project.NoSuchChangeException;
 | 
			
		||||
import com.google.gerrit.server.project.ProjectCache;
 | 
			
		||||
import com.google.gwtorm.server.AtomicUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
import com.google.gwtorm.server.OrmRuntimeException;
 | 
			
		||||
import com.google.gwtorm.server.SchemaFactory;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.errors.ConfigInvalidException;
 | 
			
		||||
import org.eclipse.jgit.errors.InvalidObjectIdException;
 | 
			
		||||
import org.eclipse.jgit.errors.MissingObjectException;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectInserter;
 | 
			
		||||
import org.eclipse.jgit.lib.PersonIdent;
 | 
			
		||||
@@ -92,8 +94,6 @@ import java.util.Comparator;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
import java.util.regex.Matcher;
 | 
			
		||||
import java.util.regex.Pattern;
 | 
			
		||||
 | 
			
		||||
public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
  private static final Logger log =
 | 
			
		||||
@@ -108,13 +108,13 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
   * historically didn't necessarily use the same timestamp, and tended to call
 | 
			
		||||
   * {@code System.currentTimeMillis()} independently.
 | 
			
		||||
   */
 | 
			
		||||
  static final long MAX_WINDOW_MS = SECONDS.toMillis(3);
 | 
			
		||||
  public static final long MAX_WINDOW_MS = SECONDS.toMillis(3);
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * The maximum amount of time between two consecutive events to consider them
 | 
			
		||||
   * to be in the same batch.
 | 
			
		||||
   */
 | 
			
		||||
  private static final long MAX_DELTA_MS = SECONDS.toMillis(1);
 | 
			
		||||
  static final long MAX_DELTA_MS = SECONDS.toMillis(1);
 | 
			
		||||
 | 
			
		||||
  private final AccountCache accountCache;
 | 
			
		||||
  private final ChangeDraftUpdate.Factory draftUpdateFactory;
 | 
			
		||||
@@ -168,24 +168,6 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class AbortUpdateException extends OrmRuntimeException {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    AbortUpdateException() {
 | 
			
		||||
      super("aborted");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ConflictingUpdateException extends OrmRuntimeException {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    ConflictingUpdateException(Change change, String expectedNoteDbState) {
 | 
			
		||||
      super(String.format(
 | 
			
		||||
          "Expected change %s to have noteDbState %s but was %s",
 | 
			
		||||
          change.getId(), expectedNoteDbState, change.getNoteDbState()));
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public Result rebuild(NoteDbUpdateManager manager,
 | 
			
		||||
      ChangeBundle bundle) throws NoSuchChangeException, IOException,
 | 
			
		||||
@@ -466,7 +448,7 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
    ChangeUpdate update = updateFactory.create(
 | 
			
		||||
        change,
 | 
			
		||||
        events.getAccountId(),
 | 
			
		||||
        events.newAuthorIdent(),
 | 
			
		||||
        newAuthorIdent(events),
 | 
			
		||||
        events.getWhen(),
 | 
			
		||||
        labelNameComparator);
 | 
			
		||||
    update.setAllowWriteToNewRef(true);
 | 
			
		||||
@@ -488,7 +470,7 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
    ChangeDraftUpdate update = draftUpdateFactory.create(
 | 
			
		||||
        change,
 | 
			
		||||
        events.getAccountId(),
 | 
			
		||||
        events.newAuthorIdent(),
 | 
			
		||||
        newAuthorIdent(events),
 | 
			
		||||
        events.getWhen());
 | 
			
		||||
    update.setPatchSetId(events.getPatchSetId());
 | 
			
		||||
    for (PatchLineCommentEvent e : events) {
 | 
			
		||||
@@ -498,6 +480,16 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
    events.clear();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private PersonIdent newAuthorIdent(EventList<?> events) {
 | 
			
		||||
    Account.Id id = events.getAccountId();
 | 
			
		||||
    if (id == null) {
 | 
			
		||||
      return new PersonIdent(serverIdent, events.getWhen());
 | 
			
		||||
    }
 | 
			
		||||
    return changeNoteUtil.newIdent(
 | 
			
		||||
        accountCache.get(id).getAccount(), events.getWhen(), serverIdent,
 | 
			
		||||
        anonymousCowardName);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private List<HashtagsEvent> getHashtagsEvents(Change change,
 | 
			
		||||
      NoteDbUpdateManager manager) throws IOException {
 | 
			
		||||
    String refName = changeMetaRef(change.getId());
 | 
			
		||||
@@ -591,470 +583,10 @@ public class ChangeRebuilderImpl extends ChangeRebuilder {
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  private abstract static class Event {
 | 
			
		||||
    // NOTE: EventList only supports direct subclasses, not an arbitrary
 | 
			
		||||
    // hierarchy.
 | 
			
		||||
 | 
			
		||||
    final Account.Id who;
 | 
			
		||||
    final Timestamp when;
 | 
			
		||||
    final String tag;
 | 
			
		||||
    final boolean predatesChange;
 | 
			
		||||
    PatchSet.Id psId;
 | 
			
		||||
 | 
			
		||||
    protected Event(PatchSet.Id psId, Account.Id who, Timestamp when,
 | 
			
		||||
        Timestamp changeCreatedOn, String tag) {
 | 
			
		||||
      this.psId = psId;
 | 
			
		||||
      this.who = who;
 | 
			
		||||
      this.tag = tag;
 | 
			
		||||
      // Truncate timestamps at the change's createdOn timestamp.
 | 
			
		||||
      predatesChange = when.before(changeCreatedOn);
 | 
			
		||||
      this.when = predatesChange ? changeCreatedOn : when;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    protected void checkUpdate(AbstractChangeUpdate update) {
 | 
			
		||||
      checkState(Objects.equals(update.getPatchSetId(), psId),
 | 
			
		||||
          "cannot apply event for %s to update for %s",
 | 
			
		||||
          update.getPatchSetId(), psId);
 | 
			
		||||
      checkState(when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS,
 | 
			
		||||
          "event at %s outside update window starting at %s",
 | 
			
		||||
          when, update.getWhen());
 | 
			
		||||
      checkState(Objects.equals(update.getNullableAccountId(), who),
 | 
			
		||||
          "cannot apply event by %s to update by %s",
 | 
			
		||||
          who, update.getNullableAccountId());
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @return whether this event type must be unique per {@link ChangeUpdate},
 | 
			
		||||
     *     i.e. there may be at most one of this type.
 | 
			
		||||
     */
 | 
			
		||||
    abstract boolean uniquePerUpdate();
 | 
			
		||||
 | 
			
		||||
    abstract void apply(ChangeUpdate update) throws OrmException, IOException;
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    public String toString() {
 | 
			
		||||
      return MoreObjects.toStringHelper(this)
 | 
			
		||||
          .add("psId", psId)
 | 
			
		||||
          .add("who", who)
 | 
			
		||||
          .add("when", when)
 | 
			
		||||
          .toString();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private class EventList<E extends Event> extends ArrayList<E> {
 | 
			
		||||
    private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
    private E getLast() {
 | 
			
		||||
      return get(size() - 1);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getLastTime() {
 | 
			
		||||
      return getLast().when.getTime();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private long getFirstTime() {
 | 
			
		||||
      return get(0).when.getTime();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    boolean canAdd(E e) {
 | 
			
		||||
      if (isEmpty()) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      if (e instanceof FinalUpdatesEvent) {
 | 
			
		||||
        return false; // FinalUpdatesEvent always gets its own update.
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      Event last = getLast();
 | 
			
		||||
      if (!Objects.equals(e.who, last.who)
 | 
			
		||||
          || !e.psId.equals(last.psId)
 | 
			
		||||
          || !Objects.equals(e.tag, last.tag)) {
 | 
			
		||||
        return false; // Different patch set, author, or tag.
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      long t = e.when.getTime();
 | 
			
		||||
      long tFirst = getFirstTime();
 | 
			
		||||
      long tLast = getLastTime();
 | 
			
		||||
      checkArgument(t >= tLast,
 | 
			
		||||
          "event %s is before previous event in list %s", e, last);
 | 
			
		||||
      if (t - tLast > MAX_DELTA_MS || t - tFirst > MAX_WINDOW_MS) {
 | 
			
		||||
        return false; // Too much time elapsed.
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (!e.uniquePerUpdate()) {
 | 
			
		||||
        return true;
 | 
			
		||||
      }
 | 
			
		||||
      for (Event o : this) {
 | 
			
		||||
        if (e.getClass() == o.getClass()) {
 | 
			
		||||
          return false; // Only one event of this type allowed per update.
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      // TODO(dborowitz): Additional heuristics, like keeping events separate if
 | 
			
		||||
      // they affect overlapping fields within a single entity.
 | 
			
		||||
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Timestamp getWhen() {
 | 
			
		||||
      return get(0).when;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PatchSet.Id getPatchSetId() {
 | 
			
		||||
      PatchSet.Id id = checkNotNull(get(0).psId);
 | 
			
		||||
      for (int i = 1; i < size(); i++) {
 | 
			
		||||
        checkState(get(i).psId.equals(id),
 | 
			
		||||
            "mismatched patch sets in EventList: %s != %s", id, get(i).psId);
 | 
			
		||||
      }
 | 
			
		||||
      return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Account.Id getAccountId() {
 | 
			
		||||
      Account.Id id = get(0).who;
 | 
			
		||||
      for (int i = 1; i < size(); i++) {
 | 
			
		||||
        checkState(Objects.equals(id, get(i).who),
 | 
			
		||||
            "mismatched users in EventList: %s != %s", id, get(i).who);
 | 
			
		||||
      }
 | 
			
		||||
      return id;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    PersonIdent newAuthorIdent() {
 | 
			
		||||
      Account.Id id = getAccountId();
 | 
			
		||||
      if (id == null) {
 | 
			
		||||
        return new PersonIdent(serverIdent, getWhen());
 | 
			
		||||
      }
 | 
			
		||||
      return changeNoteUtil.newIdent(
 | 
			
		||||
          accountCache.get(id).getAccount(), getWhen(), serverIdent,
 | 
			
		||||
          anonymousCowardName);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    String getTag() {
 | 
			
		||||
      return getLast().tag;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static void createChange(ChangeUpdate update, Change change) {
 | 
			
		||||
  static void createChange(ChangeUpdate update, Change change) {
 | 
			
		||||
    update.setSubjectForCommit("Create change");
 | 
			
		||||
    update.setChangeId(change.getKey().get());
 | 
			
		||||
    update.setBranch(change.getDest().get());
 | 
			
		||||
    update.setSubject(change.getOriginalSubject());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class CreateChangeEvent extends Event {
 | 
			
		||||
    private final Change change;
 | 
			
		||||
 | 
			
		||||
    private static PatchSet.Id psId(Change change, Integer minPsNum) {
 | 
			
		||||
      int n;
 | 
			
		||||
      if (minPsNum == null) {
 | 
			
		||||
        // There were no patch sets for the change at all, so something is very
 | 
			
		||||
        // wrong. Bail and use 1 as the patch set.
 | 
			
		||||
        n = 1;
 | 
			
		||||
      } else {
 | 
			
		||||
        n = minPsNum;
 | 
			
		||||
      }
 | 
			
		||||
      return new PatchSet.Id(change.getId(), n);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    CreateChangeEvent(Change change, Integer minPsNum) {
 | 
			
		||||
      super(psId(change, minPsNum), change.getOwner(), change.getCreatedOn(),
 | 
			
		||||
          change.getCreatedOn(), null);
 | 
			
		||||
      this.change = change;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      createChange(update, change);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ApprovalEvent extends Event {
 | 
			
		||||
    private PatchSetApproval psa;
 | 
			
		||||
 | 
			
		||||
    ApprovalEvent(PatchSetApproval psa, Timestamp changeCreatedOn) {
 | 
			
		||||
      super(psa.getPatchSetId(), psa.getAccountId(), psa.getGranted(),
 | 
			
		||||
          changeCreatedOn, psa.getTag());
 | 
			
		||||
      this.psa = psa;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      update.putApproval(psa.getLabel(), psa.getValue());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ReviewerEvent extends Event {
 | 
			
		||||
    private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer;
 | 
			
		||||
 | 
			
		||||
    ReviewerEvent(
 | 
			
		||||
        Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer,
 | 
			
		||||
        Timestamp changeCreatedOn) {
 | 
			
		||||
      super(
 | 
			
		||||
          // Reviewers aren't generally associated with a particular patch set
 | 
			
		||||
          // (although as an implementation detail they were in ReviewDb). Just
 | 
			
		||||
          // use the latest patch set at the time of the event.
 | 
			
		||||
          null,
 | 
			
		||||
          reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null);
 | 
			
		||||
      this.reviewer = reviewer;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey());
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class PatchSetEvent extends Event {
 | 
			
		||||
    private final Change change;
 | 
			
		||||
    private final PatchSet ps;
 | 
			
		||||
    private final RevWalk rw;
 | 
			
		||||
    private boolean createChange;
 | 
			
		||||
 | 
			
		||||
    PatchSetEvent(Change change, PatchSet ps, RevWalk rw) {
 | 
			
		||||
      super(ps.getId(), ps.getUploader(), ps.getCreatedOn(),
 | 
			
		||||
          change.getCreatedOn(), null);
 | 
			
		||||
      this.change = change;
 | 
			
		||||
      this.ps = ps;
 | 
			
		||||
      this.rw = rw;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      if (createChange) {
 | 
			
		||||
        createChange(update, change);
 | 
			
		||||
      } else {
 | 
			
		||||
        update.setSubject(change.getSubject());
 | 
			
		||||
        update.setSubjectForCommit("Create patch set " + ps.getPatchSetId());
 | 
			
		||||
      }
 | 
			
		||||
      setRevision(update, ps);
 | 
			
		||||
      List<String> groups = ps.getGroups();
 | 
			
		||||
      if (!groups.isEmpty()) {
 | 
			
		||||
        update.setGroups(ps.getGroups());
 | 
			
		||||
      }
 | 
			
		||||
      if (ps.isDraft()) {
 | 
			
		||||
        update.setPatchSetState(PatchSetState.DRAFT);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setRevision(ChangeUpdate update, PatchSet ps)
 | 
			
		||||
        throws IOException {
 | 
			
		||||
      String rev = ps.getRevision().get();
 | 
			
		||||
      String cert = ps.getPushCertificate();
 | 
			
		||||
      ObjectId id;
 | 
			
		||||
      try {
 | 
			
		||||
        id = ObjectId.fromString(rev);
 | 
			
		||||
      } catch (InvalidObjectIdException e) {
 | 
			
		||||
        update.setRevisionForMissingCommit(rev, cert);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      try {
 | 
			
		||||
        update.setCommit(rw, id, cert);
 | 
			
		||||
      } catch (MissingObjectException e) {
 | 
			
		||||
        update.setRevisionForMissingCommit(rev, cert);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class PatchLineCommentEvent extends Event {
 | 
			
		||||
    public final PatchLineComment c;
 | 
			
		||||
    private final Change change;
 | 
			
		||||
    private final PatchSet ps;
 | 
			
		||||
    private final PatchListCache cache;
 | 
			
		||||
 | 
			
		||||
    PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps,
 | 
			
		||||
        PatchListCache cache) {
 | 
			
		||||
      super(PatchLineCommentsUtil.getCommentPsId(c), c.getAuthor(),
 | 
			
		||||
          c.getWrittenOn(), change.getCreatedOn(), c.getTag());
 | 
			
		||||
      this.c = c;
 | 
			
		||||
      this.change = change;
 | 
			
		||||
      this.ps = ps;
 | 
			
		||||
      this.cache = cache;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return false;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      if (c.getRevId() == null) {
 | 
			
		||||
        setCommentRevId(c, cache, change, ps);
 | 
			
		||||
      }
 | 
			
		||||
      update.putComment(c);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
 | 
			
		||||
      if (c.getRevId() == null) {
 | 
			
		||||
        setCommentRevId(c, cache, change, ps);
 | 
			
		||||
      }
 | 
			
		||||
      draftUpdate.putComment(c);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class HashtagsEvent extends Event {
 | 
			
		||||
    private final Set<String> hashtags;
 | 
			
		||||
 | 
			
		||||
    HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when,
 | 
			
		||||
        Set<String> hashtags, Timestamp changeCreatdOn) {
 | 
			
		||||
      super(psId, who, when, changeCreatdOn,
 | 
			
		||||
          // Somewhat confusingly, hashtags do not use the setTag method on
 | 
			
		||||
          // AbstractChangeUpdate, so pass null as the tag.
 | 
			
		||||
          null);
 | 
			
		||||
      this.hashtags = hashtags;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      // Since these are produced from existing commits in the old NoteDb graph,
 | 
			
		||||
      // we know that there must be one per commit in the rebuilt graph.
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
      update.setHashtags(hashtags);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class ChangeMessageEvent extends Event {
 | 
			
		||||
    private static final Pattern TOPIC_SET_REGEXP =
 | 
			
		||||
        Pattern.compile("^Topic set to (.+)$");
 | 
			
		||||
    private static final Pattern TOPIC_CHANGED_REGEXP =
 | 
			
		||||
        Pattern.compile("^Topic changed from (.+) to (.+)$");
 | 
			
		||||
    private static final Pattern TOPIC_REMOVED_REGEXP =
 | 
			
		||||
        Pattern.compile("^Topic (.+) removed$");
 | 
			
		||||
 | 
			
		||||
    private static final Pattern STATUS_ABANDONED_REGEXP =
 | 
			
		||||
        Pattern.compile("^Abandoned(\n.*)*$");
 | 
			
		||||
    private static final Pattern STATUS_RESTORED_REGEXP =
 | 
			
		||||
        Pattern.compile("^Restored(\n.*)*$");
 | 
			
		||||
 | 
			
		||||
    private final ChangeMessage message;
 | 
			
		||||
    private final Change noteDbChange;
 | 
			
		||||
 | 
			
		||||
    ChangeMessageEvent(ChangeMessage message, Change noteDbChange,
 | 
			
		||||
        Timestamp changeCreatedOn) {
 | 
			
		||||
      super(message.getPatchSetId(), message.getAuthor(),
 | 
			
		||||
          message.getWrittenOn(), changeCreatedOn, message.getTag());
 | 
			
		||||
      this.message = message;
 | 
			
		||||
      this.noteDbChange = noteDbChange;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
      checkUpdate(update);
 | 
			
		||||
      update.setChangeMessage(message.getMessage());
 | 
			
		||||
      setTopic(update);
 | 
			
		||||
      setStatus(update);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setTopic(ChangeUpdate update) {
 | 
			
		||||
      String msg = message.getMessage();
 | 
			
		||||
      if (msg == null) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      Matcher m = TOPIC_SET_REGEXP.matcher(msg);
 | 
			
		||||
      if (m.matches()) {
 | 
			
		||||
        String topic = m.group(1);
 | 
			
		||||
        update.setTopic(topic);
 | 
			
		||||
        noteDbChange.setTopic(topic);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      m = TOPIC_CHANGED_REGEXP.matcher(msg);
 | 
			
		||||
      if (m.matches()) {
 | 
			
		||||
        String topic = m.group(2);
 | 
			
		||||
        update.setTopic(topic);
 | 
			
		||||
        noteDbChange.setTopic(topic);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (TOPIC_REMOVED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
        update.setTopic(null);
 | 
			
		||||
        noteDbChange.setTopic(null);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private void setStatus(ChangeUpdate update) {
 | 
			
		||||
      String msg = message.getMessage();
 | 
			
		||||
      if (msg == null) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      if (STATUS_ABANDONED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
        update.setStatus(Change.Status.ABANDONED);
 | 
			
		||||
        noteDbChange.setStatus(Change.Status.ABANDONED);
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (STATUS_RESTORED_REGEXP.matcher(msg).matches()) {
 | 
			
		||||
        update.setStatus(Change.Status.NEW);
 | 
			
		||||
        noteDbChange.setStatus(Change.Status.NEW);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private static class FinalUpdatesEvent extends Event {
 | 
			
		||||
    private final Change change;
 | 
			
		||||
    private final Change noteDbChange;
 | 
			
		||||
 | 
			
		||||
    FinalUpdatesEvent(Change change, Change noteDbChange) {
 | 
			
		||||
      super(change.currentPatchSetId(), change.getOwner(),
 | 
			
		||||
          change.getLastUpdatedOn(), change.getCreatedOn(), null);
 | 
			
		||||
      this.change = change;
 | 
			
		||||
      this.noteDbChange = noteDbChange;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @Override
 | 
			
		||||
    boolean uniquePerUpdate() {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    @SuppressWarnings("deprecation")
 | 
			
		||||
    @Override
 | 
			
		||||
    void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
      if (!Objects.equals(change.getTopic(), noteDbChange.getTopic())) {
 | 
			
		||||
        update.setTopic(change.getTopic());
 | 
			
		||||
      }
 | 
			
		||||
      if (!Objects.equals(change.getStatus(), noteDbChange.getStatus())) {
 | 
			
		||||
        // TODO(dborowitz): Stamp approximate approvals at this time.
 | 
			
		||||
        update.fixStatus(change.getStatus());
 | 
			
		||||
      }
 | 
			
		||||
      if (change.getSubmissionId() != null) {
 | 
			
		||||
        update.setSubmissionId(change.getSubmissionId());
 | 
			
		||||
      }
 | 
			
		||||
      if (!update.isEmpty()) {
 | 
			
		||||
        update.setSubjectForCommit("Final NoteDb migration updates");
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,28 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gwtorm.server.OrmRuntimeException;
 | 
			
		||||
 | 
			
		||||
class ConflictingUpdateException extends OrmRuntimeException {
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
  ConflictingUpdateException(Change change, String expectedNoteDbState) {
 | 
			
		||||
    super(String.format(
 | 
			
		||||
        "Expected change %s to have noteDbState %s but was %s",
 | 
			
		||||
        change.getId(), expectedNoteDbState, change.getNoteDbState()));
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,55 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
 | 
			
		||||
class CreateChangeEvent extends Event {
 | 
			
		||||
  private final Change change;
 | 
			
		||||
 | 
			
		||||
  private static PatchSet.Id psId(Change change, Integer minPsNum) {
 | 
			
		||||
    int n;
 | 
			
		||||
    if (minPsNum == null) {
 | 
			
		||||
      // There were no patch sets for the change at all, so something is very
 | 
			
		||||
      // wrong. Bail and use 1 as the patch set.
 | 
			
		||||
      n = 1;
 | 
			
		||||
    } else {
 | 
			
		||||
      n = minPsNum;
 | 
			
		||||
    }
 | 
			
		||||
    return new PatchSet.Id(change.getId(), n);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  CreateChangeEvent(Change change, Integer minPsNum) {
 | 
			
		||||
    super(psId(change, minPsNum), change.getOwner(), change.getCreatedOn(),
 | 
			
		||||
        change.getCreatedOn(), null);
 | 
			
		||||
    this.change = change;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    ChangeRebuilderImpl.createChange(update, change);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,79 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
import static com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl.MAX_WINDOW_MS;
 | 
			
		||||
 | 
			
		||||
import com.google.common.base.MoreObjects;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.AbstractChangeUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
abstract class Event {
 | 
			
		||||
  // NOTE: EventList only supports direct subclasses, not an arbitrary
 | 
			
		||||
  // hierarchy.
 | 
			
		||||
 | 
			
		||||
  final Account.Id who;
 | 
			
		||||
  final Timestamp when;
 | 
			
		||||
  final String tag;
 | 
			
		||||
  final boolean predatesChange;
 | 
			
		||||
  PatchSet.Id psId;
 | 
			
		||||
 | 
			
		||||
  protected Event(PatchSet.Id psId, Account.Id who, Timestamp when,
 | 
			
		||||
      Timestamp changeCreatedOn, String tag) {
 | 
			
		||||
    this.psId = psId;
 | 
			
		||||
    this.who = who;
 | 
			
		||||
    this.tag = tag;
 | 
			
		||||
    // Truncate timestamps at the change's createdOn timestamp.
 | 
			
		||||
    predatesChange = when.before(changeCreatedOn);
 | 
			
		||||
    this.when = predatesChange ? changeCreatedOn : when;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  protected void checkUpdate(AbstractChangeUpdate update) {
 | 
			
		||||
    checkState(Objects.equals(update.getPatchSetId(), psId),
 | 
			
		||||
        "cannot apply event for %s to update for %s",
 | 
			
		||||
        update.getPatchSetId(), psId);
 | 
			
		||||
    checkState(when.getTime() - update.getWhen().getTime() <= MAX_WINDOW_MS,
 | 
			
		||||
        "event at %s outside update window starting at %s",
 | 
			
		||||
        when, update.getWhen());
 | 
			
		||||
    checkState(Objects.equals(update.getNullableAccountId(), who),
 | 
			
		||||
        "cannot apply event by %s to update by %s",
 | 
			
		||||
        who, update.getNullableAccountId());
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * @return whether this event type must be unique per {@link ChangeUpdate},
 | 
			
		||||
   *     i.e. there may be at most one of this type.
 | 
			
		||||
   */
 | 
			
		||||
  abstract boolean uniquePerUpdate();
 | 
			
		||||
 | 
			
		||||
  abstract void apply(ChangeUpdate update) throws OrmException, IOException;
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  public String toString() {
 | 
			
		||||
    return MoreObjects.toStringHelper(this)
 | 
			
		||||
        .add("psId", psId)
 | 
			
		||||
        .add("who", who)
 | 
			
		||||
        .add("when", when)
 | 
			
		||||
        .toString();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,107 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import static com.google.common.base.Preconditions.checkArgument;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkNotNull;
 | 
			
		||||
import static com.google.common.base.Preconditions.checkState;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.ArrayList;
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
class EventList<E extends Event> extends ArrayList<E> {
 | 
			
		||||
  private static final long serialVersionUID = 1L;
 | 
			
		||||
 | 
			
		||||
  private E getLast() {
 | 
			
		||||
    return get(size() - 1);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private long getLastTime() {
 | 
			
		||||
    return getLast().when.getTime();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private long getFirstTime() {
 | 
			
		||||
    return get(0).when.getTime();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  boolean canAdd(E e) {
 | 
			
		||||
    if (isEmpty()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    if (e instanceof FinalUpdatesEvent) {
 | 
			
		||||
      return false; // FinalUpdatesEvent always gets its own update.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Event last = getLast();
 | 
			
		||||
    if (!Objects.equals(e.who, last.who)
 | 
			
		||||
        || !e.psId.equals(last.psId)
 | 
			
		||||
        || !Objects.equals(e.tag, last.tag)) {
 | 
			
		||||
      return false; // Different patch set, author, or tag.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    long t = e.when.getTime();
 | 
			
		||||
    long tFirst = getFirstTime();
 | 
			
		||||
    long tLast = getLastTime();
 | 
			
		||||
    checkArgument(t >= tLast,
 | 
			
		||||
        "event %s is before previous event in list %s", e, last);
 | 
			
		||||
    if (t - tLast > ChangeRebuilderImpl.MAX_DELTA_MS || t - tFirst > ChangeRebuilderImpl.MAX_WINDOW_MS) {
 | 
			
		||||
      return false; // Too much time elapsed.
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!e.uniquePerUpdate()) {
 | 
			
		||||
      return true;
 | 
			
		||||
    }
 | 
			
		||||
    for (Event o : this) {
 | 
			
		||||
      if (e.getClass() == o.getClass()) {
 | 
			
		||||
        return false; // Only one event of this type allowed per update.
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO(dborowitz): Additional heuristics, like keeping events separate if
 | 
			
		||||
    // they affect overlapping fields within a single entity.
 | 
			
		||||
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Timestamp getWhen() {
 | 
			
		||||
    return get(0).when;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  PatchSet.Id getPatchSetId() {
 | 
			
		||||
    PatchSet.Id id = checkNotNull(get(0).psId);
 | 
			
		||||
    for (int i = 1; i < size(); i++) {
 | 
			
		||||
      checkState(get(i).psId.equals(id),
 | 
			
		||||
          "mismatched patch sets in EventList: %s != %s", id, get(i).psId);
 | 
			
		||||
    }
 | 
			
		||||
    return id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Account.Id getAccountId() {
 | 
			
		||||
    Account.Id id = get(0).who;
 | 
			
		||||
    for (int i = 1; i < size(); i++) {
 | 
			
		||||
      checkState(Objects.equals(id, get(i).who),
 | 
			
		||||
          "mismatched users in EventList: %s != %s", id, get(i).who);
 | 
			
		||||
    }
 | 
			
		||||
    return id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String getTag() {
 | 
			
		||||
    return getLast().tag;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,56 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.util.Objects;
 | 
			
		||||
 | 
			
		||||
class FinalUpdatesEvent extends Event {
 | 
			
		||||
  private final Change change;
 | 
			
		||||
  private final Change noteDbChange;
 | 
			
		||||
 | 
			
		||||
  FinalUpdatesEvent(Change change, Change noteDbChange) {
 | 
			
		||||
    super(change.currentPatchSetId(), change.getOwner(),
 | 
			
		||||
        change.getLastUpdatedOn(), change.getCreatedOn(), null);
 | 
			
		||||
    this.change = change;
 | 
			
		||||
    this.noteDbChange = noteDbChange;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @SuppressWarnings("deprecation")
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
    if (!Objects.equals(change.getTopic(), noteDbChange.getTopic())) {
 | 
			
		||||
      update.setTopic(change.getTopic());
 | 
			
		||||
    }
 | 
			
		||||
    if (!Objects.equals(change.getStatus(), noteDbChange.getStatus())) {
 | 
			
		||||
      // TODO(dborowitz): Stamp approximate approvals at this time.
 | 
			
		||||
      update.fixStatus(change.getStatus());
 | 
			
		||||
    }
 | 
			
		||||
    if (change.getSubmissionId() != null) {
 | 
			
		||||
      update.setSubmissionId(change.getSubmissionId());
 | 
			
		||||
    }
 | 
			
		||||
    if (!update.isEmpty()) {
 | 
			
		||||
      update.setSubjectForCommit("Final NoteDb migration updates");
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,48 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
import java.util.Set;
 | 
			
		||||
 | 
			
		||||
class HashtagsEvent extends Event {
 | 
			
		||||
  private final Set<String> hashtags;
 | 
			
		||||
 | 
			
		||||
  HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when,
 | 
			
		||||
      Set<String> hashtags, Timestamp changeCreatdOn) {
 | 
			
		||||
    super(psId, who, when, changeCreatdOn,
 | 
			
		||||
        // Somewhat confusingly, hashtags do not use the setTag method on
 | 
			
		||||
        // AbstractChangeUpdate, so pass null as the tag.
 | 
			
		||||
        null);
 | 
			
		||||
    this.hashtags = hashtags;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    // Since these are produced from existing commits in the old NoteDb graph,
 | 
			
		||||
    // we know that there must be one per commit in the rebuilt graph.
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
    update.setHashtags(hashtags);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,64 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import static com.google.gerrit.server.PatchLineCommentsUtil.setCommentRevId;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeDraftUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gerrit.server.patch.PatchListCache;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
class PatchLineCommentEvent extends Event {
 | 
			
		||||
  public final PatchLineComment c;
 | 
			
		||||
  private final Change change;
 | 
			
		||||
  private final PatchSet ps;
 | 
			
		||||
  private final PatchListCache cache;
 | 
			
		||||
 | 
			
		||||
  PatchLineCommentEvent(PatchLineComment c, Change change, PatchSet ps,
 | 
			
		||||
      PatchListCache cache) {
 | 
			
		||||
    super(PatchLineCommentsUtil.getCommentPsId(c), c.getAuthor(),
 | 
			
		||||
        c.getWrittenOn(), change.getCreatedOn(), c.getTag());
 | 
			
		||||
    this.c = c;
 | 
			
		||||
    this.change = change;
 | 
			
		||||
    this.ps = ps;
 | 
			
		||||
    this.cache = cache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws OrmException {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    if (c.getRevId() == null) {
 | 
			
		||||
      setCommentRevId(c, cache, change, ps);
 | 
			
		||||
    }
 | 
			
		||||
    update.putComment(c);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void applyDraft(ChangeDraftUpdate draftUpdate) throws OrmException {
 | 
			
		||||
    if (c.getRevId() == null) {
 | 
			
		||||
      setCommentRevId(c, cache, change, ps);
 | 
			
		||||
    }
 | 
			
		||||
    draftUpdate.putComment(c);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,87 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Change;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.PatchSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.PatchSetState;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import org.eclipse.jgit.errors.InvalidObjectIdException;
 | 
			
		||||
import org.eclipse.jgit.errors.MissingObjectException;
 | 
			
		||||
import org.eclipse.jgit.lib.ObjectId;
 | 
			
		||||
import org.eclipse.jgit.revwalk.RevWalk;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.util.List;
 | 
			
		||||
 | 
			
		||||
class PatchSetEvent extends Event {
 | 
			
		||||
  private final Change change;
 | 
			
		||||
  private final PatchSet ps;
 | 
			
		||||
  private final RevWalk rw;
 | 
			
		||||
  boolean createChange;
 | 
			
		||||
 | 
			
		||||
  PatchSetEvent(Change change, PatchSet ps, RevWalk rw) {
 | 
			
		||||
    super(ps.getId(), ps.getUploader(), ps.getCreatedOn(),
 | 
			
		||||
        change.getCreatedOn(), null);
 | 
			
		||||
    this.change = change;
 | 
			
		||||
    this.ps = ps;
 | 
			
		||||
    this.rw = rw;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    if (createChange) {
 | 
			
		||||
      ChangeRebuilderImpl.createChange(update, change);
 | 
			
		||||
    } else {
 | 
			
		||||
      update.setSubject(change.getSubject());
 | 
			
		||||
      update.setSubjectForCommit("Create patch set " + ps.getPatchSetId());
 | 
			
		||||
    }
 | 
			
		||||
    setRevision(update, ps);
 | 
			
		||||
    List<String> groups = ps.getGroups();
 | 
			
		||||
    if (!groups.isEmpty()) {
 | 
			
		||||
      update.setGroups(ps.getGroups());
 | 
			
		||||
    }
 | 
			
		||||
    if (ps.isDraft()) {
 | 
			
		||||
      update.setPatchSetState(PatchSetState.DRAFT);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private void setRevision(ChangeUpdate update, PatchSet ps)
 | 
			
		||||
      throws IOException {
 | 
			
		||||
    String rev = ps.getRevision().get();
 | 
			
		||||
    String cert = ps.getPushCertificate();
 | 
			
		||||
    ObjectId id;
 | 
			
		||||
    try {
 | 
			
		||||
      id = ObjectId.fromString(rev);
 | 
			
		||||
    } catch (InvalidObjectIdException e) {
 | 
			
		||||
      update.setRevisionForMissingCommit(rev, cert);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
    try {
 | 
			
		||||
      update.setCommit(rw, id, cert);
 | 
			
		||||
    } catch (MissingObjectException e) {
 | 
			
		||||
      update.setRevisionForMissingCommit(rev, cert);
 | 
			
		||||
      return;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,51 @@
 | 
			
		||||
// Copyright (C) 2016 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.notedb.rebuild;
 | 
			
		||||
 | 
			
		||||
import com.google.common.collect.Table;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Account;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeUpdate;
 | 
			
		||||
import com.google.gerrit.server.notedb.ReviewerStateInternal;
 | 
			
		||||
import com.google.gwtorm.server.OrmException;
 | 
			
		||||
 | 
			
		||||
import java.io.IOException;
 | 
			
		||||
import java.sql.Timestamp;
 | 
			
		||||
 | 
			
		||||
class ReviewerEvent extends Event {
 | 
			
		||||
  private Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer;
 | 
			
		||||
 | 
			
		||||
  ReviewerEvent(
 | 
			
		||||
      Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> reviewer,
 | 
			
		||||
      Timestamp changeCreatedOn) {
 | 
			
		||||
    super(
 | 
			
		||||
        // Reviewers aren't generally associated with a particular patch set
 | 
			
		||||
        // (although as an implementation detail they were in ReviewDb). Just
 | 
			
		||||
        // use the latest patch set at the time of the event.
 | 
			
		||||
        null,
 | 
			
		||||
        reviewer.getColumnKey(), reviewer.getValue(), changeCreatedOn, null);
 | 
			
		||||
    this.reviewer = reviewer;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  boolean uniquePerUpdate() {
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @Override
 | 
			
		||||
  void apply(ChangeUpdate update) throws IOException, OrmException {
 | 
			
		||||
    checkUpdate(update);
 | 
			
		||||
    update.putReviewer(reviewer.getColumnKey(), reviewer.getRowKey());
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -39,6 +39,7 @@ import com.google.gerrit.reviewdb.client.PatchSetApproval;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.Project;
 | 
			
		||||
import com.google.gerrit.reviewdb.client.RevId;
 | 
			
		||||
import com.google.gerrit.server.ReviewerSet;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
 | 
			
		||||
import com.google.gerrit.testutil.TestChanges;
 | 
			
		||||
import com.google.gerrit.testutil.TestTimeUtil;
 | 
			
		||||
import com.google.gwtorm.client.KeyUtil;
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ import com.google.gerrit.server.PatchLineCommentsUtil;
 | 
			
		||||
import com.google.gerrit.server.git.GitRepositoryManager;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeBundle;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeNotes;
 | 
			
		||||
import com.google.gerrit.server.notedb.ChangeRebuilder;
 | 
			
		||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilder;
 | 
			
		||||
import com.google.inject.Inject;
 | 
			
		||||
import com.google.inject.Provider;
 | 
			
		||||
import com.google.inject.Singleton;
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user