Line data Source code
1 : // Copyright (C) 2020 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.acceptance.testsuite.change; 16 : 17 : import com.google.gerrit.entities.Account; 18 : import com.google.gerrit.entities.Comment.Status; 19 : import com.google.gerrit.entities.HumanComment; 20 : import com.google.gerrit.entities.Patch; 21 : import com.google.gerrit.entities.PatchSet; 22 : import com.google.gerrit.entities.Project; 23 : import com.google.gerrit.entities.RobotComment; 24 : import com.google.gerrit.extensions.client.Comment; 25 : import com.google.gerrit.extensions.client.Comment.Range; 26 : import com.google.gerrit.extensions.restapi.RestApiException; 27 : import com.google.gerrit.server.CommentsUtil; 28 : import com.google.gerrit.server.IdentifiedUser; 29 : import com.google.gerrit.server.IdentifiedUser.GenericFactory; 30 : import com.google.gerrit.server.git.GitRepositoryManager; 31 : import com.google.gerrit.server.notedb.ChangeNotes; 32 : import com.google.gerrit.server.notedb.ChangeUpdate; 33 : import com.google.gerrit.server.update.BatchUpdate; 34 : import com.google.gerrit.server.update.BatchUpdateOp; 35 : import com.google.gerrit.server.update.ChangeContext; 36 : import com.google.gerrit.server.update.UpdateException; 37 : import com.google.gerrit.server.util.time.TimeUtil; 38 : import com.google.inject.Inject; 39 : import com.google.inject.assistedinject.Assisted; 40 : import java.io.IOException; 41 : import java.time.Instant; 42 : import org.eclipse.jgit.lib.ObjectInserter; 43 : import org.eclipse.jgit.lib.Repository; 44 : import org.eclipse.jgit.revwalk.RevWalk; 45 : 46 : /** 47 : * The implementation of {@link PerPatchsetOperations}. 48 : * 49 : * <p>There is only one implementation of {@link PerPatchsetOperations}. Nevertheless, we keep the 50 : * separation between interface and implementation to enhance clarity. 51 : */ 52 : public class PerPatchsetOperationsImpl implements PerPatchsetOperations { 53 : private final GitRepositoryManager repositoryManager; 54 : private final IdentifiedUser.GenericFactory userFactory; 55 : private final BatchUpdate.Factory batchUpdateFactory; 56 : private final CommentsUtil commentsUtil; 57 : 58 : private final ChangeNotes changeNotes; 59 : private final PatchSet.Id patchsetId; 60 : 61 : public interface Factory { 62 : PerPatchsetOperationsImpl create(ChangeNotes changeNotes, PatchSet.Id patchsetId); 63 : } 64 : 65 : @Inject 66 : private PerPatchsetOperationsImpl( 67 : GitRepositoryManager repositoryManager, 68 : GenericFactory userFactory, 69 : BatchUpdate.Factory batchUpdateFactory, 70 : CommentsUtil commentsUtil, 71 : @Assisted ChangeNotes changeNotes, 72 6 : @Assisted PatchSet.Id patchsetId) { 73 6 : this.repositoryManager = repositoryManager; 74 6 : this.userFactory = userFactory; 75 6 : this.batchUpdateFactory = batchUpdateFactory; 76 6 : this.commentsUtil = commentsUtil; 77 6 : this.changeNotes = changeNotes; 78 6 : this.patchsetId = patchsetId; 79 6 : } 80 : 81 : @Override 82 : public TestPatchset get() { 83 4 : PatchSet patchset = changeNotes.getPatchSets().get(patchsetId); 84 4 : return TestPatchset.builder().patchsetId(patchsetId).commitId(patchset.commitId()).build(); 85 : } 86 : 87 : @Override 88 : public TestCommentCreation.Builder newComment() { 89 5 : return TestCommentCreation.builder(this::createComment, Status.PUBLISHED); 90 : } 91 : 92 : @Override 93 : public TestCommentCreation.Builder newDraftComment() { 94 3 : return TestCommentCreation.builder(this::createComment, Status.DRAFT); 95 : } 96 : 97 : @Override 98 : public TestRobotCommentCreation.Builder newRobotComment() { 99 3 : return TestRobotCommentCreation.builder(this::createRobotComment); 100 : } 101 : 102 : private String createComment(TestCommentCreation commentCreation) 103 : throws IOException, RestApiException, UpdateException { 104 5 : Project.NameKey project = changeNotes.getProjectName(); 105 : 106 5 : try (Repository repository = repositoryManager.openRepository(project); 107 5 : ObjectInserter objectInserter = repository.newObjectInserter(); 108 5 : RevWalk revWalk = new RevWalk(objectInserter.newReader())) { 109 5 : Instant now = TimeUtil.now(); 110 : 111 5 : IdentifiedUser author = getAuthor(commentCreation); 112 5 : CommentAdditionOp commentAdditionOp = new CommentAdditionOp(commentCreation); 113 5 : try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, author, now)) { 114 5 : batchUpdate.setRepository(repository, revWalk, objectInserter); 115 5 : batchUpdate.addOp(changeNotes.getChangeId(), commentAdditionOp); 116 5 : batchUpdate.execute(); 117 : } 118 5 : return commentAdditionOp.createdCommentUuid; 119 : } 120 : } 121 : 122 : private IdentifiedUser getAuthor(TestCommentCreation commentCreation) { 123 5 : Account.Id authorId = commentCreation.author().orElse(changeNotes.getChange().getOwner()); 124 5 : return userFactory.create(authorId); 125 : } 126 : 127 : private IdentifiedUser getAuthor(TestRobotCommentCreation robotCommentCreation) { 128 3 : Account.Id authorId = robotCommentCreation.author().orElse(changeNotes.getChange().getOwner()); 129 3 : return userFactory.create(authorId); 130 : } 131 : 132 : private static Comment.Range toCommentRange(TestRange range) { 133 2 : Comment.Range commentRange = new Range(); 134 2 : commentRange.startLine = range.start().line(); 135 2 : commentRange.startCharacter = range.start().charOffset(); 136 2 : commentRange.endLine = range.end().line(); 137 2 : commentRange.endCharacter = range.end().charOffset(); 138 2 : return commentRange; 139 : } 140 : 141 : private class CommentAdditionOp implements BatchUpdateOp { 142 : private String createdCommentUuid; 143 : private final TestCommentCreation commentCreation; 144 : 145 5 : public CommentAdditionOp(TestCommentCreation commentCreation) { 146 5 : this.commentCreation = commentCreation; 147 5 : } 148 : 149 : @Override 150 : public boolean updateChange(ChangeContext context) { 151 5 : HumanComment comment = toNewComment(context, commentCreation); 152 5 : ChangeUpdate changeUpdate = context.getUpdate(patchsetId); 153 5 : changeUpdate.putComment(commentCreation.status(), comment); 154 : // For published comments, only the tag set on the ChangeUpdate (and not on the HumanComment) 155 : // matters. 156 5 : commentCreation.tag().ifPresent(changeUpdate::setTag); 157 5 : createdCommentUuid = comment.key.uuid; 158 5 : return true; 159 : } 160 : 161 : private HumanComment toNewComment(ChangeContext context, TestCommentCreation commentCreation) { 162 5 : String message = commentCreation.message().orElse("The text of a test comment."); 163 : 164 5 : String filePath = commentCreation.file().orElse(Patch.PATCHSET_LEVEL); 165 5 : short side = commentCreation.side().orElse(CommentSide.PATCHSET_COMMIT).getNumericSide(); 166 5 : Boolean unresolved = commentCreation.unresolved().orElse(null); 167 5 : String parentUuid = commentCreation.parentUuid().orElse(null); 168 5 : Instant createdOn = commentCreation.createdOn().orElse(context.getWhen()); 169 5 : HumanComment newComment = 170 5 : commentsUtil.newHumanComment( 171 5 : context.getNotes(), 172 5 : context.getUser(), 173 : createdOn, 174 : filePath, 175 : patchsetId, 176 : side, 177 : message, 178 : unresolved, 179 : parentUuid); 180 : // For draft comments, only the tag set on the HumanComment (and not on the ChangeUpdate) 181 : // matters. 182 5 : commentCreation.tag().ifPresent(tag -> newComment.tag = tag); 183 : 184 5 : commentCreation.line().ifPresent(line -> newComment.setLineNbrAndRange(line, null)); 185 : // Specification of range trumps explicit line specification. 186 5 : commentCreation 187 5 : .range() 188 5 : .map(PerPatchsetOperationsImpl::toCommentRange) 189 5 : .ifPresent(range -> newComment.setLineNbrAndRange(null, range)); 190 : 191 5 : commentsUtil.setCommentCommitId( 192 5 : newComment, context.getChange(), changeNotes.getPatchSets().get(patchsetId)); 193 5 : return newComment; 194 : } 195 : } 196 : 197 : private String createRobotComment(TestRobotCommentCreation robotCommentCreation) 198 : throws IOException, RestApiException, UpdateException { 199 3 : Project.NameKey project = changeNotes.getProjectName(); 200 : 201 3 : try (Repository repository = repositoryManager.openRepository(project); 202 3 : ObjectInserter objectInserter = repository.newObjectInserter(); 203 3 : RevWalk revWalk = new RevWalk(objectInserter.newReader())) { 204 3 : Instant now = TimeUtil.now(); 205 : 206 3 : IdentifiedUser author = getAuthor(robotCommentCreation); 207 3 : RobotCommentAdditionOp robotCommentAdditionOp = 208 : new RobotCommentAdditionOp(robotCommentCreation); 209 3 : try (BatchUpdate batchUpdate = batchUpdateFactory.create(project, author, now)) { 210 3 : batchUpdate.setRepository(repository, revWalk, objectInserter); 211 3 : batchUpdate.addOp(changeNotes.getChangeId(), robotCommentAdditionOp); 212 3 : batchUpdate.execute(); 213 : } 214 3 : return robotCommentAdditionOp.createdRobotCommentUuid; 215 : } 216 : } 217 : 218 : private class RobotCommentAdditionOp implements BatchUpdateOp { 219 : private String createdRobotCommentUuid; 220 : private final TestRobotCommentCreation robotCommentCreation; 221 : 222 3 : public RobotCommentAdditionOp(TestRobotCommentCreation robotCommentCreation) { 223 3 : this.robotCommentCreation = robotCommentCreation; 224 3 : } 225 : 226 : @Override 227 : public boolean updateChange(ChangeContext context) { 228 3 : RobotComment robotComment = toNewRobotComment(context, robotCommentCreation); 229 3 : ChangeUpdate changeUpdate = context.getUpdate(patchsetId); 230 3 : changeUpdate.putRobotComment(robotComment); 231 : // For robot comments, only the tag set on the ChangeUpdate (and not on the RobotComment) 232 : // matters. 233 3 : robotCommentCreation.tag().ifPresent(changeUpdate::setTag); 234 3 : createdRobotCommentUuid = robotComment.key.uuid; 235 3 : return true; 236 : } 237 : 238 : private RobotComment toNewRobotComment( 239 : ChangeContext context, TestRobotCommentCreation robotCommentCreation) { 240 3 : String message = robotCommentCreation.message().orElse("The text of a test robot comment."); 241 : 242 3 : String filePath = robotCommentCreation.file().orElse(Patch.PATCHSET_LEVEL); 243 3 : short side = robotCommentCreation.side().orElse(CommentSide.PATCHSET_COMMIT).getNumericSide(); 244 3 : String robotId = robotCommentCreation.robotId().orElse("robot"); 245 3 : String robotRunId = robotCommentCreation.robotId().orElse("1"); 246 3 : RobotComment newRobotComment = 247 3 : commentsUtil.newRobotComment( 248 : context, filePath, patchsetId, side, message, robotId, robotRunId); 249 : 250 : // TODO(paiking): This should not be needed, as the tag only matters in ChangeUpdate. 251 3 : robotCommentCreation.tag().ifPresent(tag -> newRobotComment.tag = tag); 252 : 253 3 : robotCommentCreation.line().ifPresent(line -> newRobotComment.setLineNbrAndRange(line, null)); 254 : // Specification of range trumps explicit line specification. 255 3 : robotCommentCreation 256 3 : .range() 257 3 : .map(PerPatchsetOperationsImpl::toCommentRange) 258 3 : .ifPresent(range -> newRobotComment.setLineNbrAndRange(null, range)); 259 : 260 3 : robotCommentCreation 261 3 : .parentUuid() 262 3 : .ifPresent(parentUuid -> newRobotComment.parentUuid = parentUuid); 263 3 : robotCommentCreation.url().ifPresent(url -> newRobotComment.url = url); 264 3 : if (!robotCommentCreation.properties().isEmpty()) { 265 1 : newRobotComment.properties = robotCommentCreation.properties(); 266 : } 267 : 268 3 : commentsUtil.setCommentCommitId( 269 3 : newRobotComment, context.getChange(), changeNotes.getPatchSets().get(patchsetId)); 270 3 : return newRobotComment; 271 : } 272 : } 273 : }