| // Copyright (C) 2015 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.googlesource.gerrit.plugins.importer; |
| |
| import com.google.common.collect.Iterators; |
| import com.google.gerrit.common.Nullable; |
| import com.google.gerrit.common.data.GlobalCapability; |
| import com.google.gerrit.common.errors.NoSuchAccountException; |
| import com.google.gerrit.extensions.common.ChangeInfo; |
| import com.google.gerrit.extensions.restapi.RestApiException; |
| import com.google.gerrit.extensions.restapi.Url; |
| import com.google.gerrit.reviewdb.client.Branch; |
| import com.google.gerrit.reviewdb.client.Change; |
| 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.server.Sequences; |
| import com.google.gerrit.server.index.change.ChangeIndexer; |
| import com.google.gerrit.server.notedb.NotesMigration; |
| import com.google.gerrit.server.patch.PatchListNotAvailableException; |
| import com.google.gerrit.server.project.NoSuchChangeException; |
| import com.google.gerrit.server.query.change.ChangeData; |
| import com.google.gerrit.server.query.change.InternalChangeQuery; |
| import com.google.gerrit.server.update.UpdateException; |
| import com.google.gwtorm.server.OrmException; |
| import com.google.inject.Inject; |
| import com.google.inject.Provider; |
| import com.google.inject.assistedinject.Assisted; |
| import java.io.IOException; |
| import java.util.Collections; |
| import java.util.List; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.ProgressMonitor; |
| import org.eclipse.jgit.lib.Repository; |
| import org.eclipse.jgit.revwalk.RevWalk; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| class ReplayChangesStep { |
| |
| interface Factory { |
| ReplayChangesStep create( |
| @Nullable String fromGerrit, |
| GerritApi api, |
| Repository repo, |
| @Assisted("srcProject") Project.NameKey srcProject, |
| @Assisted("targetProject") Project.NameKey targetProject, |
| @Assisted("force") boolean force, |
| @Assisted("resume") boolean resume, |
| ResumeImportStatistic importStatistic, |
| ProgressMonitor pm); |
| } |
| |
| private static Logger log = LoggerFactory.getLogger(ReplayChangesStep.class); |
| |
| private final ReplayRevisionsStep.Factory replayRevisionsFactory; |
| private final ReplayInlineCommentsStep.Factory replayInlineCommentsFactory; |
| private final ReplayMessagesStep.Factory replayMessagesFactory; |
| private final AddApprovalsStep.Factory addApprovalsFactory; |
| private final AddHashtagsStep.Factory addHashtagsFactory; |
| private final InsertLinkToOriginalChangeStep.Factory insertLinkToOriginalFactory; |
| private final AccountUtil accountUtil; |
| private final ReviewDb db; |
| private final ChangeIndexer indexer; |
| private final Provider<InternalChangeQuery> queryProvider; |
| private final Sequences sequences; |
| private final String fromGerrit; |
| private final GerritApi api; |
| private final Repository repo; |
| private final Project.NameKey srcProject; |
| private final Project.NameKey targetProject; |
| private final boolean force; |
| private final boolean resume; |
| private final ResumeImportStatistic importStatistic; |
| private final ProgressMonitor pm; |
| private final boolean isNoteDbEnabled; |
| |
| @Inject |
| ReplayChangesStep( |
| ReplayRevisionsStep.Factory replayRevisionsFactory, |
| ReplayInlineCommentsStep.Factory replayInlineCommentsFactory, |
| ReplayMessagesStep.Factory replayMessagesFactory, |
| AddApprovalsStep.Factory addApprovalsFactory, |
| AddHashtagsStep.Factory addHashtagsFactory, |
| InsertLinkToOriginalChangeStep.Factory insertLinkToOriginalFactory, |
| AccountUtil accountUtil, |
| ReviewDb db, |
| ChangeIndexer indexer, |
| Provider<InternalChangeQuery> queryProvider, |
| Sequences sequences, |
| NotesMigration migration, |
| @Assisted @Nullable String fromGerrit, |
| @Assisted GerritApi api, |
| @Assisted Repository repo, |
| @Assisted("srcProject") Project.NameKey srcProject, |
| @Assisted("targetProject") Project.NameKey targetProject, |
| @Assisted("force") boolean force, |
| @Assisted("resume") boolean resume, |
| @Assisted ResumeImportStatistic importStatistic, |
| @Assisted ProgressMonitor pm) { |
| this.replayRevisionsFactory = replayRevisionsFactory; |
| this.replayInlineCommentsFactory = replayInlineCommentsFactory; |
| this.replayMessagesFactory = replayMessagesFactory; |
| this.addApprovalsFactory = addApprovalsFactory; |
| this.addHashtagsFactory = addHashtagsFactory; |
| this.insertLinkToOriginalFactory = insertLinkToOriginalFactory; |
| this.accountUtil = accountUtil; |
| this.db = db; |
| this.indexer = indexer; |
| this.queryProvider = queryProvider; |
| this.sequences = sequences; |
| this.fromGerrit = fromGerrit; |
| this.api = api; |
| this.repo = repo; |
| this.srcProject = srcProject; |
| this.targetProject = targetProject; |
| this.force = force; |
| this.resume = resume; |
| this.importStatistic = importStatistic; |
| this.pm = pm; |
| this.isNoteDbEnabled = migration.readChanges(); |
| } |
| |
| void replay() |
| throws IOException, OrmException, NoSuchAccountException, NoSuchChangeException, |
| RestApiException, UpdateException, ConfigInvalidException, |
| PatchListNotAvailableException { |
| int start = 0; |
| int limit = GlobalCapability.DEFAULT_MAX_QUERY_LIMIT; |
| pm.beginTask("Replay Changes", ProgressMonitor.UNKNOWN); |
| for (; ; ) { |
| List<ChangeInfo> changes = api.queryChanges(srcProject.get(), start, limit); |
| if (changes.isEmpty()) { |
| break; |
| } |
| start += changes.size(); |
| try (RevWalk rw = new RevWalk(repo)) { |
| ChangeInfo last = null; |
| for (ChangeInfo c : changes) { |
| try { |
| replayChange(rw, c); |
| } catch (Exception e) { |
| log.error(String.format("Failed to replay change %s.", Url.decode(c.id)), e); |
| throw e; |
| } |
| last = c; |
| pm.update(1); |
| } |
| if (!Boolean.TRUE.equals(last._moreChanges)) { |
| break; |
| } |
| } |
| } |
| pm.endTask(); |
| } |
| |
| private void replayChange(RevWalk rw, ChangeInfo c) |
| throws IOException, OrmException, NoSuchAccountException, NoSuchChangeException, |
| RestApiException, IllegalArgumentException, UpdateException, ConfigInvalidException, |
| PatchListNotAvailableException { |
| Change change = resume ? findChange(c) : null; |
| boolean resumeChange; |
| if (change == null) { |
| resumeChange = false; |
| change = createChange(c); |
| } else { |
| resumeChange = true; |
| if (!force && change.getLastUpdatedOn().equals(c.updated)) { |
| // change was not modified since last import |
| return; |
| } |
| } |
| |
| if (c.revisions.isEmpty()) { |
| log.warn(String.format("Change %s has no revisions.", c.id)); |
| return; |
| } |
| |
| replayRevisionsFactory.create(repo, rw, change, c).replay(api); |
| upsertChange(resumeChange, change, c); |
| |
| replayInlineCommentsFactory.create(change, c, api, resumeChange).replay(); |
| replayMessagesFactory.create(change, c, resumeChange).replay(api); |
| addApprovalsFactory.create(change, c, resume).add(api); |
| if (isNoteDbEnabled) { |
| addHashtagsFactory.create(change, c, resumeChange).add(); |
| } |
| |
| insertLinkToOriginalFactory.create(fromGerrit, change, c, resumeChange).insert(); |
| |
| indexer.index(db, change); |
| |
| if (resumeChange) { |
| importStatistic.numChangesUpdated++; |
| } else { |
| importStatistic.numChangesCreated++; |
| } |
| } |
| |
| private Change findChange(ChangeInfo c) throws OrmException { |
| List<Change> changes = |
| ChangeData.asChanges( |
| queryProvider |
| .get() |
| .byBranchKey( |
| new Branch.NameKey(targetProject, RefNames.fullName(c.branch)), |
| new Change.Key(c.changeId))); |
| if (changes.isEmpty()) { |
| return null; |
| } |
| return db.changes().get(Iterators.getOnlyElement(changes.iterator()).getId()); |
| } |
| |
| private Change createChange(ChangeInfo c) |
| throws OrmException, NoSuchAccountException, IOException, RestApiException, |
| ConfigInvalidException { |
| Change.Id changeId = new Change.Id(sequences.nextChangeId()); |
| |
| Change change = |
| new Change( |
| new Change.Key(c.changeId), |
| changeId, |
| accountUtil.resolveUser(api, c.owner), |
| new Branch.NameKey(targetProject, RefNames.fullName(c.branch)), |
| c.created); |
| change.setStatus(Change.Status.forChangeStatus(c.status)); |
| change.setTopic(c.topic); |
| change.setLastUpdatedOn(c.updated); |
| return change; |
| } |
| |
| private void upsertChange(boolean resumeChange, Change change, ChangeInfo c) throws OrmException { |
| if (resumeChange) { |
| change.setStatus(Change.Status.forChangeStatus(c.status)); |
| change.setTopic(c.topic); |
| change.setLastUpdatedOn(c.updated); |
| } |
| db.changes().upsert(Collections.singleton(change)); |
| } |
| } |