blob: 3a77620594dd0f18aa35690fedb8441c28856d0b [file] [log] [blame]
package com.googlesource.gerrit.plugins.chatgpt;
import com.google.inject.Inject;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import com.googlesource.gerrit.plugins.chatgpt.data.ChangeSetDataHandler;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClient;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClientReview;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.messages.DebugMessages;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.patch.comment.GerritCommentRange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptReplyItem;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.chatgpt.ChatGptResponseContent;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritCodeRange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritComment;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.review.ReviewBatch;
import com.googlesource.gerrit.plugins.chatgpt.mode.interfaces.client.api.chatgpt.IChatGptClient;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.util.*;
import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getGson;
@Slf4j
public class PatchSetReviewer {
private static final String SPLIT_REVIEW_MSG = "Too many changes. Please consider splitting into patches smaller " +
"than %s lines for review.";
private final Configuration config;
private final GerritClient gerritClient;
private final ChangeSetData changeSetData;
@Getter
private final IChatGptClient chatGptClient;
private GerritCommentRange gerritCommentRange;
private List<ReviewBatch> reviewBatches;
private List<GerritComment> commentProperties;
private List<Integer> reviewScores;
@Inject
PatchSetReviewer(GerritClient gerritClient, Configuration config, ChangeSetData changeSetData, IChatGptClient chatGptClient) {
this.config = config;
this.gerritClient = gerritClient;
this.changeSetData = changeSetData;
this.chatGptClient = chatGptClient;
}
public void review(GerritChange change) throws Exception {
reviewBatches = new ArrayList<>();
reviewScores = new ArrayList<>();
commentProperties = gerritClient.getClientData(change).getCommentProperties();
gerritCommentRange = new GerritCommentRange(gerritClient, change);
GerritClientReview gerritClientReview = new GerritClientReview(config);
String patchSet = gerritClient.getPatchSet(change);
if (patchSet.isEmpty()) {
log.info("No file to review has been found in the PatchSet");
return;
}
ChangeSetDataHandler.update(config, change, gerritClient, changeSetData);
String reviewReply = getReviewReply(change, patchSet);
log.debug("ChatGPT response: {}", reviewReply);
retrieveReviewBatches(reviewReply, change);
gerritClientReview.setReview(change, reviewBatches, getReviewScore());
}
private void setCommentBatchMap(ReviewBatch batchMap, Integer batchID) {
if (commentProperties != null && batchID < commentProperties.size()) {
GerritComment commentProperty = commentProperties.get(batchID);
if (commentProperty != null && (commentProperty.getLine() != null || commentProperty.getRange() != null)) {
String id = commentProperty.getId();
String filename = commentProperty.getFilename();
Integer line = commentProperty.getLine();
GerritCodeRange range = commentProperty.getRange();
if (range != null) {
batchMap.setId(id);
batchMap.setFilename(filename);
batchMap.setLine(line);
batchMap.setRange(range);
}
}
}
}
private void setPatchSetReviewBatchMap(ReviewBatch batchMap, ChatGptReplyItem replyItem) {
Optional<GerritCodeRange> optGerritCommentRange = gerritCommentRange.getGerritCommentRange(replyItem);
if (optGerritCommentRange.isPresent()) {
GerritCodeRange gerritCodeRange = optGerritCommentRange.get();
batchMap.setFilename(replyItem.getFilename());
batchMap.setLine(gerritCodeRange.getStartLine());
batchMap.setRange(gerritCodeRange);
}
}
private void retrieveReviewBatches(String reviewReply, GerritChange change) {
ChatGptResponseContent reviewJson = getGson().fromJson(reviewReply, ChatGptResponseContent.class);
for (ChatGptReplyItem replyItem : reviewJson.getReplies()) {
String reply = replyItem.getReply();
Integer score = replyItem.getScore();
boolean isNotNegative = isNotNegativeReply(score);
boolean isIrrelevant = isIrrelevantReply(replyItem);
boolean isHidden = replyItem.isRepeated() || replyItem.isConflicting() || isIrrelevant || isNotNegative;
if (!replyItem.isConflicting() && !isIrrelevant && score != null) {
reviewScores.add(score);
}
if (changeSetData.getReplyFilterEnabled() && isHidden) {
continue;
}
if (changeSetData.getDebugMode()) {
reply += DebugMessages.getDebugMessage(replyItem, isHidden);
}
ReviewBatch batchMap = new ReviewBatch();
batchMap.setContent(reply);
if (change.getIsCommentEvent() && replyItem.getId() != null) {
setCommentBatchMap(batchMap, replyItem.getId());
}
else {
setPatchSetReviewBatchMap(batchMap, replyItem);
}
reviewBatches.add(batchMap);
}
}
private String getReviewReply(GerritChange change, String patchSet) throws Exception {
List<String> patchLines = Arrays.asList(patchSet.split("\n"));
if (patchLines.size() > config.getMaxReviewLines()) {
log.warn("Patch set too large. Skipping review. changeId: {}", change.getFullChangeId());
return String.format(SPLIT_REVIEW_MSG, config.getMaxReviewLines());
}
return chatGptClient.ask(config, changeSetData, change, patchSet);
}
private Integer getReviewScore() {
if (config.isVotingEnabled()) {
return reviewScores.isEmpty() ? 0 : Collections.min(reviewScores);
}
else {
return null;
}
}
private boolean isNotNegativeReply(Integer score) {
return score != null &&
config.getFilterNegativeComments() &&
score >= config.getFilterCommentsBelowScore();
}
private boolean isIrrelevantReply(ChatGptReplyItem replyItem) {
return config.getFilterRelevantComments() &&
replyItem.getRelevance() != null &&
replyItem.getRelevance() < config.getFilterCommentsRelevanceThreshold();
}
}