blob: 2ada0e58692297db17b374f1001302de048370d7 [file] [log] [blame]
// 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.client.ChangeStatus;
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.server.ReviewDb;
import com.google.gerrit.server.Sequences;
import com.google.gerrit.server.git.UpdateException;
import com.google.gerrit.server.index.change.ChangeIndexer;
import com.google.gerrit.server.notedb.NotesMigration;
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.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.Constants;
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;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
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.enabled();
}
void replay() throws IOException, OrmException,
NoSuchAccountException, NoSuchChangeException, RestApiException,
UpdateException, ConfigInvalidException {
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 {
if (c.status == ChangeStatus.DRAFT) {
// no import of draft changes
return;
}
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, 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, 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));
}
private static String fullName(String branch) {
if (branch.startsWith(Constants.R_HEADS)) {
return branch;
}
return Constants.R_HEADS + branch;
}
}