diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java index b443e66dee..f61402bf5e 100644 --- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java +++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java @@ -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; diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java index 0adb1af5f6..0656c3b7ba 100644 --- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java +++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/RebuildNoteDb.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java index 5fe0e0b13a..c27fa53d8c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebuild.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java index 679a9de1ea..3c669f09de 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java index e15af9d92e..ee9745c9d1 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java @@ -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); diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java index 4c1a734646..f157b42a86 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeNoteUtil.java @@ -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"; 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 63276827f7..9ffc70afdc 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 @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java index 77b8dc097f..0a9bc6e2c2 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeUpdate.java @@ -284,7 +284,7 @@ public class ChangeUpdate extends AbstractChangeUpdate { this.commitSubject = commitSubject; } - void setSubject(String subject) { + public void setSubject(String subject) { this.subject = subject; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java index 08195e41d6..eb3b753fb7 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/DraftCommentNotes.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java index 4a7a781087..a56d02aa5e 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbChangeState.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java index ff3b4b865d..876f47f0be 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbModule.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java index cad531ffe7..7f6eb5c72c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/NoteDbUpdateManager.java @@ -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 getObjectId(String refName) throws IOException { + public Optional 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; } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java index c0bb8ab854..7bfb20cdea 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/TestChangeRebuilderWrapper.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java new file mode 100644 index 0000000000..0e6d3e98a7 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/AbortUpdateException.java @@ -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"); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java new file mode 100644 index 0000000000..a00334d280 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ApprovalEvent.java @@ -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()); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java new file mode 100644 index 0000000000..a990e19a7c --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeMessageEvent.java @@ -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); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java similarity index 94% rename from gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java rename to gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java index 679b5e2dc0..ea96012c9c 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilder.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilder.java @@ -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; diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java similarity index 59% rename from gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java rename to gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java index 08acbaddaf..281452ed2a 100644 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java @@ -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 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 extends ArrayList { - 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 reviewer; - - ReviewerEvent( - Table.Cell 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 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 hashtags; - - HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when, - Set 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"); - } - } - } } diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java new file mode 100644 index 0000000000..2098727730 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ConflictingUpdateException.java @@ -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())); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java new file mode 100644 index 0000000000..886f6c4a67 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/CreateChangeEvent.java @@ -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); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java new file mode 100644 index 0000000000..5c34f0c030 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/Event.java @@ -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(); + } +} 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 new file mode 100644 index 0000000000..398657b0c4 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/EventList.java @@ -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 extends ArrayList { + 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; + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java new file mode 100644 index 0000000000..3080be7e69 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/FinalUpdatesEvent.java @@ -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"); + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java new file mode 100644 index 0000000000..21b3b6ed23 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/HashtagsEvent.java @@ -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 hashtags; + + HashtagsEvent(PatchSet.Id psId, Account.Id who, Timestamp when, + Set 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); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java new file mode 100644 index 0000000000..8d962bea8b --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchLineCommentEvent.java @@ -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); + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java new file mode 100644 index 0000000000..c3fb267310 --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/PatchSetEvent.java @@ -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 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; + } + } +} diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java new file mode 100644 index 0000000000..ef9c5c68ec --- /dev/null +++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ReviewerEvent.java @@ -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 reviewer; + + ReviewerEvent( + Table.Cell 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()); + } +} diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java index c093b759b8..054a82bc10 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java +++ b/gerrit-server/src/test/java/com/google/gerrit/server/notedb/ChangeBundleTest.java @@ -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; diff --git a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java index 61bfe78f4a..9cc7acab4a 100644 --- a/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java +++ b/gerrit-server/src/test/java/com/google/gerrit/testutil/NoteDbChecker.java @@ -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;