| // 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.ChangeNotes; |
| import com.google.gerrit.server.notedb.ChangeUpdate; |
| import com.google.gerrit.server.patch.PatchListCache; |
| import com.google.gerrit.server.patch.PatchListNotAvailableException; |
| 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 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; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| 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 ChangeNotes.Factory changeNotesFactory; |
| 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, |
| ChangeNotes.Factory changeNotesFactory, |
| 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.changeNotesFactory = changeNotesFactory; |
| 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, PatchListNotAvailableException { |
| ChangeNotes notes = changeNotesFactory.createChecked(db, change); |
| for (PatchSet ps : ChangeUtil.PS_ID_ORDER.sortedCopy(psUtil.byChange(db, notes))) { |
| 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, PatchListNotAvailableException { |
| ChangeNotes notes = changeNotesFactory.createChecked(db, change); |
| |
| Map<String, Comment> drafts = scanDraftComments(notes, ps, author); |
| |
| 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; |
| } else { |
| e.lineNbr = c.line == null ? 0 : c.line; |
| } |
| ups.add(e); |
| } |
| |
| Iterables.addAll(del, drafts.values()); |
| ChangeUpdate update = |
| updateFactory.create(notes, genericUserFactory.create(author), 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(ChangeNotes notes, PatchSet ps, Account.Id account) |
| throws OrmException { |
| Map<String, Comment> drafts = Maps.newHashMap(); |
| for (Comment c : commentsUtil.draftByPatchSetAuthor(db, ps.getId(), account, notes)) { |
| drafts.put(c.key.uuid, c); |
| } |
| return drafts; |
| } |
| } |