diff options
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java')
-rw-r--r-- | gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java | 692 |
1 files changed, 0 insertions, 692 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java deleted file mode 100644 index cabe18f998..0000000000 --- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/rebuild/ChangeRebuilderImpl.java +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright (C) 2014 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.MoreObjects.firstNonNull; -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.notedb.ChangeNoteUtil.FOOTER_HASHTAGS; -import static com.google.gerrit.server.notedb.ChangeNoteUtil.FOOTER_PATCH_SET; -import static java.util.concurrent.TimeUnit.SECONDS; -import static java.util.stream.Collectors.toList; - -import com.google.common.base.Splitter; -import com.google.common.collect.FluentIterable; -import com.google.common.collect.ImmutableCollection; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.ListMultimap; -import com.google.common.collect.Lists; -import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.Ordering; -import com.google.common.collect.Sets; -import com.google.common.collect.Table; -import com.google.common.primitives.Ints; -import com.google.gerrit.common.Nullable; -import com.google.gerrit.reviewdb.client.Account; -import com.google.gerrit.reviewdb.client.Change; -import com.google.gerrit.reviewdb.client.ChangeMessage; -import com.google.gerrit.reviewdb.client.Comment; -import com.google.gerrit.reviewdb.client.PatchLineComment; -import com.google.gerrit.reviewdb.client.PatchLineComment.Status; -import com.google.gerrit.reviewdb.client.PatchSet; -import com.google.gerrit.reviewdb.client.PatchSetApproval; -import com.google.gerrit.reviewdb.client.Project; -import com.google.gerrit.reviewdb.client.RefNames; -import com.google.gerrit.reviewdb.server.ReviewDb; -import com.google.gerrit.reviewdb.server.ReviewDbUtil; -import com.google.gerrit.server.CommentsUtil; -import com.google.gerrit.server.GerritPersonIdent; -import com.google.gerrit.server.account.AccountCache; -import com.google.gerrit.server.config.AnonymousCowardName; -import com.google.gerrit.server.config.GerritServerConfig; -import com.google.gerrit.server.config.GerritServerId; -import com.google.gerrit.server.notedb.ChangeBundle; -import com.google.gerrit.server.notedb.ChangeBundleReader; -import com.google.gerrit.server.notedb.ChangeDraftUpdate; -import com.google.gerrit.server.notedb.ChangeNoteUtil; -import com.google.gerrit.server.notedb.ChangeNotes; -import com.google.gerrit.server.notedb.ChangeUpdate; -import com.google.gerrit.server.notedb.NoteDbChangeState; -import com.google.gerrit.server.notedb.NoteDbChangeState.PrimaryStorage; -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.gerrit.server.update.ChainedReceiveCommands; -import com.google.gwtorm.client.Key; -import com.google.gwtorm.server.Access; -import com.google.gwtorm.server.AtomicUpdate; -import com.google.gwtorm.server.OrmException; -import com.google.gwtorm.server.SchemaFactory; -import com.google.inject.Inject; -import java.io.IOException; -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.Iterator; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.TreeMap; -import org.eclipse.jgit.errors.ConfigInvalidException; -import org.eclipse.jgit.lib.Config; -import org.eclipse.jgit.lib.ObjectId; -import org.eclipse.jgit.lib.PersonIdent; -import org.eclipse.jgit.lib.Ref; -import org.eclipse.jgit.revwalk.RevCommit; -import org.eclipse.jgit.revwalk.RevWalk; -import org.eclipse.jgit.transport.ReceiveCommand; - -public class ChangeRebuilderImpl extends ChangeRebuilder { - /** - * The maximum amount of time between the ReviewDb timestamp of the first and last events batched - * together into a single NoteDb update. - * - * <p>Used to account for the fact that different records with their own timestamps (e.g. {@link - * PatchSetApproval} and {@link ChangeMessage}) historically didn't necessarily use the same - * timestamp, and tended to call {@code System.currentTimeMillis()} independently. - */ - 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. - */ - static final long MAX_DELTA_MS = SECONDS.toMillis(1); - - private final AccountCache accountCache; - private final ChangeBundleReader bundleReader; - private final ChangeDraftUpdate.Factory draftUpdateFactory; - private final ChangeNoteUtil changeNoteUtil; - private final ChangeNotes.Factory notesFactory; - private final ChangeUpdate.Factory updateFactory; - private final CommentsUtil commentsUtil; - private final NoteDbUpdateManager.Factory updateManagerFactory; - private final NotesMigration migration; - private final PatchListCache patchListCache; - private final PersonIdent serverIdent; - private final ProjectCache projectCache; - private final String anonymousCowardName; - private final String serverId; - private final long skewMs; - - @Inject - ChangeRebuilderImpl( - @GerritServerConfig Config cfg, - SchemaFactory<ReviewDb> schemaFactory, - AccountCache accountCache, - ChangeBundleReader bundleReader, - ChangeDraftUpdate.Factory draftUpdateFactory, - ChangeNoteUtil changeNoteUtil, - ChangeNotes.Factory notesFactory, - ChangeUpdate.Factory updateFactory, - CommentsUtil commentsUtil, - NoteDbUpdateManager.Factory updateManagerFactory, - NotesMigration migration, - PatchListCache patchListCache, - @GerritPersonIdent PersonIdent serverIdent, - @Nullable ProjectCache projectCache, - @AnonymousCowardName String anonymousCowardName, - @GerritServerId String serverId) { - super(schemaFactory); - this.accountCache = accountCache; - this.bundleReader = bundleReader; - this.draftUpdateFactory = draftUpdateFactory; - this.changeNoteUtil = changeNoteUtil; - this.notesFactory = notesFactory; - this.updateFactory = updateFactory; - this.commentsUtil = commentsUtil; - this.updateManagerFactory = updateManagerFactory; - this.migration = migration; - this.patchListCache = patchListCache; - this.serverIdent = serverIdent; - this.projectCache = projectCache; - this.anonymousCowardName = anonymousCowardName; - this.serverId = serverId; - this.skewMs = NoteDbChangeState.getReadOnlySkew(cfg); - } - - @Override - public Result rebuild(ReviewDb db, Change.Id changeId) throws IOException, OrmException { - return rebuild(db, changeId, true); - } - - @Override - public Result rebuildEvenIfReadOnly(ReviewDb db, Change.Id changeId) - throws IOException, OrmException { - return rebuild(db, changeId, false); - } - - private Result rebuild(ReviewDb db, Change.Id changeId, boolean checkReadOnly) - throws IOException, OrmException { - db = ReviewDbUtil.unwrapDb(db); - // Read change just to get project; this instance is then discarded so we can read a consistent - // ChangeBundle inside a transaction. - Change change = db.changes().get(changeId); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - try (NoteDbUpdateManager manager = updateManagerFactory.create(change.getProject())) { - buildUpdates(manager, bundleReader.fromReviewDb(db, changeId)); - return execute(db, changeId, manager, checkReadOnly, true); - } - } - - @Override - public Result rebuild(NoteDbUpdateManager manager, ChangeBundle bundle) - throws NoSuchChangeException, IOException, OrmException { - Change change = new Change(bundle.getChange()); - buildUpdates(manager, bundle); - return manager.stageAndApplyDelta(change); - } - - @Override - public NoteDbUpdateManager stage(ReviewDb db, Change.Id changeId) - throws IOException, OrmException { - db = ReviewDbUtil.unwrapDb(db); - Change change = checkNoteDbState(ChangeNotes.readOneReviewDbChange(db, changeId)); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - NoteDbUpdateManager manager = updateManagerFactory.create(change.getProject()); - buildUpdates(manager, bundleReader.fromReviewDb(db, changeId)); - manager.stage(); - return manager; - } - - @Override - public Result execute(ReviewDb db, Change.Id changeId, NoteDbUpdateManager manager) - throws OrmException, IOException { - return execute(db, changeId, manager, true, true); - } - - public Result execute( - ReviewDb db, - Change.Id changeId, - NoteDbUpdateManager manager, - boolean checkReadOnly, - boolean executeManager) - throws OrmException, IOException { - db = ReviewDbUtil.unwrapDb(db); - Change change = checkNoteDbState(ChangeNotes.readOneReviewDbChange(db, changeId)); - if (change == null) { - throw new NoSuchChangeException(changeId); - } - - String oldNoteDbStateStr = change.getNoteDbState(); - Result r = manager.stageAndApplyDelta(change); - String newNoteDbStateStr = change.getNoteDbState(); - if (newNoteDbStateStr == null) { - throw new OrmException( - String.format( - "Rebuilding change %s produced no writes to NoteDb: %s", - changeId, bundleReader.fromReviewDb(db, changeId))); - } - NoteDbChangeState newNoteDbState = - checkNotNull(NoteDbChangeState.parse(changeId, newNoteDbStateStr)); - try { - db.changes() - .atomicUpdate( - changeId, - new AtomicUpdate<Change>() { - @Override - public Change update(Change change) { - if (checkReadOnly) { - NoteDbChangeState.checkNotReadOnly(change, skewMs); - } - String currNoteDbStateStr = change.getNoteDbState(); - if (Objects.equals(currNoteDbStateStr, newNoteDbStateStr)) { - // Another thread completed the same rebuild we were about to. - throw new AbortUpdateException(); - } else if (!Objects.equals(oldNoteDbStateStr, currNoteDbStateStr)) { - // Another thread updated the state to something else. - throw new ConflictingUpdateRuntimeException(change, oldNoteDbStateStr); - } - change.setNoteDbState(newNoteDbStateStr); - return change; - } - }); - } catch (ConflictingUpdateRuntimeException e) { - // Rethrow as an OrmException so the caller knows to use staged results. Strictly speaking - // they are not completely up to date, but result we send to the caller is the same as if this - // rebuild had executed before the other thread. - throw new ConflictingUpdateException(e); - } catch (AbortUpdateException e) { - if (newNoteDbState.isUpToDate( - manager.getChangeRepo().cmds.getRepoRefCache(), - manager.getAllUsersRepo().cmds.getRepoRefCache())) { - // If the state in ReviewDb matches NoteDb at this point, it means another thread - // successfully completed this rebuild. It's ok to not execute the update in this case, - // since the object referenced in the Result was flushed to the repo by whatever thread won - // the race. - return r; - } - // If the state doesn't match, that means another thread attempted this rebuild, but - // failed. Fall through and try to update the ref again. - } - if (migration.failChangeWrites()) { - // Don't even attempt to execute if read-only, it would fail anyway. But do throw an exception - // to the caller so they know to use the staged results instead of reading from the repo. - throw new OrmException(NoteDbUpdateManager.CHANGES_READ_ONLY); - } - if (executeManager) { - manager.execute(); - } - return r; - } - - static Change checkNoteDbState(Change c) throws OrmException { - // Can only rebuild a change if its primary storage is ReviewDb. - NoteDbChangeState s = NoteDbChangeState.parse(c); - if (s != null && s.getPrimaryStorage() != PrimaryStorage.REVIEW_DB) { - throw new OrmException(String.format("cannot rebuild change %s with state %s", c.getId(), s)); - } - return c; - } - - @Override - public void buildUpdates(NoteDbUpdateManager manager, ChangeBundle bundle) - throws IOException, OrmException { - manager.setCheckExpectedState(false).setRefLogMessage("Rebuilding change"); - Change change = new Change(bundle.getChange()); - if (bundle.getPatchSets().isEmpty()) { - throw new NoPatchSetsException(change.getId()); - } - if (change.getLastUpdatedOn().compareTo(change.getCreatedOn()) < 0) { - // A bug in data migration might set created_on to the time of the migration. The - // correct timestamps were lost, but we can at least set it so created_on is not after - // last_updated_on. - // See https://bugs.chromium.org/p/gerrit/issues/detail?id=7397 - change.setCreatedOn(change.getLastUpdatedOn()); - } - - // We will rebuild all events, except for draft comments, in buckets based on author and - // timestamp. - List<Event> events = new ArrayList<>(); - ListMultimap<Account.Id, DraftCommentEvent> draftCommentEvents = - MultimapBuilder.hashKeys().arrayListValues().build(); - - events.addAll(getHashtagsEvents(change, manager)); - - // Delete ref only after hashtags have been read. - deleteChangeMetaRef(change, manager.getChangeRepo().cmds); - deleteDraftRefs(change, manager.getAllUsersRepo()); - - Integer minPsNum = getMinPatchSetNum(bundle); - TreeMap<PatchSet.Id, PatchSetEvent> patchSetEvents = - new TreeMap<>(ReviewDbUtil.intKeyOrdering()); - - for (PatchSet ps : bundle.getPatchSets()) { - PatchSetEvent pse = new PatchSetEvent(change, ps, manager.getChangeRepo().rw); - patchSetEvents.put(ps.getId(), pse); - events.add(pse); - for (Comment c : getComments(bundle, serverId, Status.PUBLISHED, ps)) { - CommentEvent e = new CommentEvent(c, change, ps, patchListCache); - events.add(e.addDep(pse)); - } - for (Comment c : getComments(bundle, serverId, Status.DRAFT, ps)) { - DraftCommentEvent e = new DraftCommentEvent(c, change, ps, patchListCache); - draftCommentEvents.put(c.author.getId(), e); - } - } - ensurePatchSetOrder(patchSetEvents); - - for (PatchSetApproval psa : bundle.getPatchSetApprovals()) { - PatchSetEvent pse = patchSetEvents.get(psa.getPatchSetId()); - if (pse != null) { - events.add(new ApprovalEvent(psa, change.getCreatedOn()).addDep(pse)); - } - } - - for (Table.Cell<ReviewerStateInternal, Account.Id, Timestamp> r : - bundle.getReviewers().asTable().cellSet()) { - events.add(new ReviewerEvent(r, change.getCreatedOn())); - } - - Change noteDbChange = new Change(null, null, null, null, null); - for (ChangeMessage msg : bundle.getChangeMessages()) { - Event msgEvent = new ChangeMessageEvent(change, noteDbChange, msg, change.getCreatedOn()); - if (msg.getPatchSetId() != null) { - PatchSetEvent pse = patchSetEvents.get(msg.getPatchSetId()); - if (pse == null) { - continue; // Ignore events for missing patch sets. - } - msgEvent.addDep(pse); - } - events.add(msgEvent); - } - - sortAndFillEvents(change, noteDbChange, bundle.getPatchSets(), events, minPsNum); - - EventList<Event> el = new EventList<>(); - for (Event e : events) { - if (!el.canAdd(e)) { - flushEventsToUpdate(manager, el, change); - checkState(el.canAdd(e)); - } - el.add(e); - } - flushEventsToUpdate(manager, el, change); - - EventList<DraftCommentEvent> plcel = new EventList<>(); - for (Account.Id author : draftCommentEvents.keys()) { - for (DraftCommentEvent e : Ordering.natural().sortedCopy(draftCommentEvents.get(author))) { - if (!plcel.canAdd(e)) { - flushEventsToDraftUpdate(manager, plcel, change); - checkState(plcel.canAdd(e)); - } - plcel.add(e); - } - flushEventsToDraftUpdate(manager, plcel, change); - } - } - - private static Integer getMinPatchSetNum(ChangeBundle bundle) { - Integer minPsNum = null; - for (PatchSet ps : bundle.getPatchSets()) { - int n = ps.getId().get(); - if (minPsNum == null || n < minPsNum) { - minPsNum = n; - } - } - return minPsNum; - } - - private static void ensurePatchSetOrder(TreeMap<PatchSet.Id, PatchSetEvent> events) { - if (events.isEmpty()) { - return; - } - Iterator<PatchSetEvent> it = events.values().iterator(); - PatchSetEvent curr = it.next(); - while (it.hasNext()) { - PatchSetEvent next = it.next(); - next.addDep(curr); - curr = next; - } - } - - private static List<Comment> getComments( - ChangeBundle bundle, String serverId, PatchLineComment.Status status, PatchSet ps) { - return bundle.getPatchLineComments().stream() - .filter(c -> c.getPatchSetId().equals(ps.getId()) && c.getStatus() == status) - .map(plc -> plc.asComment(serverId)) - .sorted(CommentsUtil.COMMENT_ORDER) - .collect(toList()); - } - - private void sortAndFillEvents( - Change change, - Change noteDbChange, - ImmutableCollection<PatchSet> patchSets, - List<Event> events, - Integer minPsNum) { - Event finalUpdates = new FinalUpdatesEvent(change, noteDbChange, patchSets); - events.add(finalUpdates); - setPostSubmitDeps(events); - new EventSorter(events).sort(); - - // Ensure the first event in the list creates the change, setting the author and any required - // footers. Also force the creation time of the first patch set to match the creation time of - // the change. - Event first = events.get(0); - if (first instanceof PatchSetEvent && change.getOwner().equals(first.user)) { - first.when = change.getCreatedOn(); - ((PatchSetEvent) first).createChange = true; - } else { - events.add(0, new CreateChangeEvent(change, minPsNum)); - } - - // Final pass to correct some inconsistencies. - // - // First, fill in any missing patch set IDs using the latest patch set of the change at the time - // of the event, because NoteDb can't represent actions with no associated patch set ID. This - // workaround is as if a user added a ChangeMessage on the change by replying from the latest - // patch set. - // - // Start with the first patch set that actually exists. If there are no patch sets at all, - // minPsNum will be null, so just bail and use 1 as the patch set ID. - // - // Second, ensure timestamps are nondecreasing, by copying the previous timestamp if this - // happens. This assumes that the only way this can happen is due to dependency constraints, and - // it is ok to give an event the same timestamp as one of its dependencies. - int ps = firstNonNull(minPsNum, 1); - for (int i = 0; i < events.size(); i++) { - Event e = events.get(i); - if (e.psId == null) { - e.psId = new PatchSet.Id(change.getId(), ps); - } else { - ps = Math.max(ps, e.psId.get()); - } - - if (i > 0) { - Event p = events.get(i - 1); - if (e.when.before(p.when)) { - e.when = p.when; - } - } - } - } - - private void setPostSubmitDeps(List<Event> events) { - Optional<Event> submitEvent = - Lists.reverse(events).stream().filter(Event::isSubmit).findFirst(); - if (submitEvent.isPresent()) { - events.stream().filter(Event::isPostSubmitApproval).forEach(e -> e.addDep(submitEvent.get())); - } - } - - private void flushEventsToUpdate( - NoteDbUpdateManager manager, EventList<Event> events, Change change) - throws OrmException, IOException { - if (events.isEmpty()) { - return; - } - Comparator<String> labelNameComparator; - if (projectCache != null) { - labelNameComparator = projectCache.get(change.getProject()).getLabelTypes().nameComparator(); - } else { - // No project cache available, bail and use natural ordering; there's no semantic difference - // anyway difference. - labelNameComparator = Ordering.natural(); - } - ChangeUpdate update = - updateFactory.create( - change, - events.getAccountId(), - events.getRealAccountId(), - newAuthorIdent(events), - events.getWhen(), - labelNameComparator); - update.setAllowWriteToNewRef(true); - update.setPatchSetId(events.getPatchSetId()); - update.setTag(events.getTag()); - for (Event e : events) { - e.apply(update); - } - manager.add(update); - events.clear(); - } - - private void flushEventsToDraftUpdate( - NoteDbUpdateManager manager, EventList<DraftCommentEvent> events, Change change) { - if (events.isEmpty()) { - return; - } - ChangeDraftUpdate update = - draftUpdateFactory.create( - change, - events.getAccountId(), - events.getRealAccountId(), - newAuthorIdent(events), - events.getWhen()); - update.setPatchSetId(events.getPatchSetId()); - for (DraftCommentEvent e : events) { - e.applyDraft(update); - } - manager.add(update); - 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()); - Optional<ObjectId> old = manager.getChangeRepo().getObjectId(refName); - if (!old.isPresent()) { - return Collections.emptyList(); - } - - RevWalk rw = manager.getChangeRepo().rw; - List<HashtagsEvent> events = new ArrayList<>(); - rw.reset(); - rw.markStart(rw.parseCommit(old.get())); - for (RevCommit commit : rw) { - Account.Id authorId; - try { - authorId = changeNoteUtil.parseIdent(commit.getAuthorIdent(), change.getId()); - } catch (ConfigInvalidException e) { - continue; // Corrupt data, no valid hashtags in this commit. - } - PatchSet.Id psId = parsePatchSetId(change, commit); - Set<String> hashtags = parseHashtags(commit); - if (authorId == null || psId == null || hashtags == null) { - continue; - } - - Timestamp commitTime = new Timestamp(commit.getCommitterIdent().getWhen().getTime()); - events.add(new HashtagsEvent(psId, authorId, commitTime, hashtags, change.getCreatedOn())); - } - return events; - } - - private Set<String> parseHashtags(RevCommit commit) { - List<String> hashtagsLines = commit.getFooterLines(FOOTER_HASHTAGS); - if (hashtagsLines.isEmpty() || hashtagsLines.size() > 1) { - return null; - } - - if (hashtagsLines.get(0).isEmpty()) { - return ImmutableSet.of(); - } - return Sets.newHashSet(Splitter.on(',').split(hashtagsLines.get(0))); - } - - private PatchSet.Id parsePatchSetId(Change change, RevCommit commit) { - List<String> psIdLines = commit.getFooterLines(FOOTER_PATCH_SET); - if (psIdLines.size() != 1) { - return null; - } - Integer psId = Ints.tryParse(psIdLines.get(0)); - if (psId == null) { - return null; - } - return new PatchSet.Id(change.getId(), psId); - } - - private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds) throws IOException { - String refName = changeMetaRef(change.getId()); - Optional<ObjectId> old = cmds.get(refName); - if (old.isPresent()) { - cmds.add(new ReceiveCommand(old.get(), ObjectId.zeroId(), refName)); - } - } - - private void deleteDraftRefs(Change change, OpenRepo allUsersRepo) throws IOException { - for (Ref r : - allUsersRepo - .repo - .getRefDatabase() - .getRefs(RefNames.refsDraftCommentsPrefix(change.getId())) - .values()) { - allUsersRepo.cmds.add(new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName())); - } - } - - 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()); - if (change.getRevertOf() != null) { - update.setRevertOf(change.getRevertOf().get()); - } - } - - @Override - public void rebuildReviewDb(ReviewDb db, Project.NameKey project, Change.Id changeId) - throws OrmException { - // TODO(dborowitz): Fail fast if changes tables are disabled in ReviewDb. - ChangeNotes notes = notesFactory.create(db, project, changeId); - ChangeBundle bundle = ChangeBundle.fromNotes(commentsUtil, notes); - - db = ReviewDbUtil.unwrapDb(db); - db.changes().beginTransaction(changeId); - try { - Change c = db.changes().get(changeId); - if (c != null) { - PrimaryStorage ps = PrimaryStorage.of(c); - switch (ps) { - case REVIEW_DB: - return; // Nothing to do. - case NOTE_DB: - break; // Continue and rebuild. - default: - throw new OrmException("primary storage of " + changeId + " is " + ps); - } - } else { - c = notes.getChange(); - } - db.changes().upsert(Collections.singleton(c)); - putExactlyEntities( - db.changeMessages(), db.changeMessages().byChange(c.getId()), bundle.getChangeMessages()); - putExactlyEntities(db.patchSets(), db.patchSets().byChange(c.getId()), bundle.getPatchSets()); - putExactlyEntities( - db.patchSetApprovals(), - db.patchSetApprovals().byChange(c.getId()), - bundle.getPatchSetApprovals()); - putExactlyEntities( - db.patchComments(), - db.patchComments().byChange(c.getId()), - bundle.getPatchLineComments()); - db.commit(); - } finally { - db.rollback(); - } - } - - private static <T, K extends Key<?>> void putExactlyEntities( - Access<T, K> access, Iterable<T> existing, Collection<T> ents) throws OrmException { - Set<K> toKeep = access.toMap(ents).keySet(); - access.delete( - FluentIterable.from(existing).filter(e -> !toKeep.contains(access.primaryKey(e)))); - access.upsert(ents); - } -} |