blob: 07293a6302df6c565a41fa3bc8c076c15ba8c37b [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 static com.google.gerrit.server.CommentsUtil.setCommentRevId;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.common.errors.NoSuchAccountException;
import com.google.gerrit.extensions.client.Side;
import com.google.gerrit.extensions.common.ChangeInfo;
import com.google.gerrit.extensions.common.CommentInfo;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gerrit.extensions.restapi.Url;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.Comment;
import com.google.gerrit.reviewdb.client.CommentRange;
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.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.CommentsUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.PatchSetUtil;
import com.google.gerrit.server.config.GerritServerId;
import com.google.gerrit.server.notedb.ChangeUpdate;
import com.google.gerrit.server.patch.PatchListCache;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.assistedinject.Assisted;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
class ReplayInlineCommentsStep {
interface Factory {
ReplayInlineCommentsStep create(Change change, ChangeInfo changeInfo,
GerritApi api, boolean resume);
}
private static final Logger log = LoggerFactory
.getLogger(ReplayInlineCommentsStep.class);
private final AccountUtil accountUtil;
private final ReviewDb db;
private final IdentifiedUser.GenericFactory genericUserFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final ChangeUpdate.Factory updateFactory;
private final CommentsUtil commentsUtil;
private final PatchListCache patchListCache;
private final PatchSetUtil psUtil;
private final String serverId;
private final Change change;
private final ChangeInfo changeInfo;
private final GerritApi api;
private final boolean resume;
@Inject
public ReplayInlineCommentsStep(AccountUtil accountUtil,
ReviewDb db,
IdentifiedUser.GenericFactory genericUserFactory,
ChangeControl.GenericFactory changeControlFactory,
ChangeUpdate.Factory updateFactory,
CommentsUtil commentsUtil,
PatchListCache patchListCache,
PatchSetUtil psUtil,
@GerritServerId String serverId,
@Assisted Change change,
@Assisted ChangeInfo changeInfo,
@Assisted GerritApi api,
@Assisted boolean resume) {
this.accountUtil = accountUtil;
this.db = db;
this.genericUserFactory = genericUserFactory;
this.changeControlFactory = changeControlFactory;
this.updateFactory = updateFactory;
this.commentsUtil = commentsUtil;
this.patchListCache = patchListCache;
this.psUtil = psUtil;
this.serverId = serverId;
this.change = change;
this.changeInfo = changeInfo;
this.api = api;
this.resume = resume;
}
void replay()
throws RestApiException, OrmException, IOException, NoSuchChangeException,
NoSuchAccountException, ConfigInvalidException {
ChangeControl ctrl = control(change, change.getOwner());
for (PatchSet ps : ChangeUtil.PS_ID_ORDER
.sortedCopy(psUtil.byChange(db, ctrl.getNotes()))) {
Iterable<CommentInfo> comments = api.getComments(
changeInfo._number, ps.getRevision().get());
if (resume) {
if (comments == null) {
// the revision does not exist in the source system,
// it must be a revision that was created in the target system after
// the initial import
log.warn(String.format(
"Project %s was modified in target system: "
+ "Skip replay inline comments for patch set %s"
+ " which doesn't exist in the source system.",
change.getProject().get(), ps.getId().toString()));
continue;
}
comments = filterComments(ps, comments);
} else if (comments == null) {
log.warn(String.format(
"Cannot retrieve comments for revision %s, "
+ "revision not found in source system: "
+ "Skip replay inline comments for patch set %s of project %s.",
ps.getRevision().get(), ps.getId().toString(),
change.getProject().get()));
continue;
}
Multimap<Account.Id, CommentInfo> commentsByAuthor = ArrayListMultimap.create();
for (CommentInfo comment : comments) {
Account.Id id = accountUtil.resolveUser(api, comment.author);
commentsByAuthor.put(id, comment);
}
for (Account.Id id : commentsByAuthor.keySet()) {
insertComments(ps, id, commentsByAuthor.get(id));
}
}
}
private Iterable<CommentInfo> filterComments(PatchSet ps,
Iterable<CommentInfo> comments) throws OrmException {
Set<String> existingUuids = new HashSet<>();
for (PatchLineComment c : db.patchComments().byPatchSet(ps.getId())) {
existingUuids.add(c.getKey().get());
}
Iterator<CommentInfo> it = comments.iterator();
while (it.hasNext()) {
CommentInfo c = it.next();
if (existingUuids.contains(Url.decode(c.id))) {
it.remove();
}
}
return comments;
}
private void insertComments(PatchSet ps, Account.Id author,
Collection<CommentInfo> comments) throws OrmException, IOException,
NoSuchChangeException {
ChangeControl ctrl = control(change, author);
Map<String, Comment> drafts = scanDraftComments(ctrl, ps);
List<Comment> del = Lists.newArrayList();
List<Comment> ups = Lists.newArrayList();
for (CommentInfo c : comments) {
String parent = Url.decode(c.inReplyTo);
Comment e = drafts.remove(Url.decode(c.id));
if (e == null) {
e = new Comment(
new Comment.Key(Url.decode(c.id), c.path, ps.getId().get()),
author,
c.updated,
c.side == Side.PARENT ? (short) 0 : (short) 1,
c.message,
serverId,
c.unresolved == null ? false : c.unresolved);
} else if (parent != null) {
e.parentUuid = parent;
}
setCommentRevId(e, patchListCache, change, ps);
if (c.range != null) {
e.setRange(new CommentRange(
c.range.startLine,
c.range.startCharacter,
c.range.endLine,
c.range.endCharacter));
e.lineNbr = c.range.endLine;
}
ups.add(e);
}
Iterables.addAll(del, drafts.values());
ChangeUpdate update = updateFactory.create(ctrl, TimeUtil.nowTs());
update.setPatchSetId(ps.getId());
commentsUtil.deleteComments(db, update, del);
commentsUtil.putComments(db, update, Status.PUBLISHED, ups);
update.commit();
}
private Map<String, Comment> scanDraftComments(ChangeControl ctrl,
PatchSet ps) throws OrmException {
Map<String, Comment> drafts = Maps.newHashMap();
for (Comment c : commentsUtil.draftByPatchSetAuthor(db, ps.getId(),
((IdentifiedUser) ctrl.getUser()).getAccountId(),
ctrl.getNotes())) {
drafts.put(c.key.uuid, c);
}
return drafts;
}
private ChangeControl control(Change change, Account.Id id)
throws NoSuchChangeException {
try {
return changeControlFactory.controlFor(db, change,
genericUserFactory.create(id));
} catch (OrmException e) {
throw new NoSuchChangeException(change.getId());
}
}
}