Enable inline code lookup for stateful requests
Inline code lookup for ChatGPT code snippets is now supported in
stateful review requests, aligning with the existing functionality for
stateless requests.
Change-Id: I816b37d698950869137df16c8f8da4ed2175b910
Signed-off-by: Patrizio <patrizio.gelosi@amarulasolutions.com>
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientPatchSet.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientPatchSet.java
index 4e17e7e..ff54e0e 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientPatchSet.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/api/gerrit/GerritClientPatchSet.java
@@ -1,24 +1,38 @@
package com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.util.ManualRequestContext;
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.patch.diff.FileDiffProcessed;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritFileDiff;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritPatchSetFileDiff;
+import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritReviewFileDiff;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
+import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getNoEscapedGson;
+import static java.util.stream.Collectors.toList;
+
@Slf4j
public class GerritClientPatchSet extends GerritClientAccount {
+ protected final List<String> diffs;
+
@Getter
protected Integer revisionBase = 0;
+ private boolean isCommitMessage;
+
public GerritClientPatchSet(Configuration config, AccountCache accountCache) {
super(config, accountCache);
+ diffs = new ArrayList<>();
}
public void retrieveRevisionBase(GerritChange change) {
@@ -48,8 +62,74 @@
return isChangeSetBased(changeSetData) ? 0 : revisionBase;
}
+ protected void retrieveFileDiff(GerritChange change, List<String> files, int revisionBase) throws Exception {
+ List<String> enabledFileExtensions = config.getEnabledFileExtensions();
+ try (ManualRequestContext requestContext = config.openRequestContext()) {
+ for (String filename : files) {
+ isCommitMessage = filename.equals("/COMMIT_MSG");
+ if (!isCommitMessage && (filename.lastIndexOf(".") < 1 ||
+ !enabledFileExtensions.contains(filename.substring(filename.lastIndexOf("."))))) {
+ continue;
+ }
+ DiffInfo diff =
+ config
+ .getGerritApi()
+ .changes()
+ .id(
+ change.getProjectName(),
+ change.getBranchNameKey().shortName(),
+ change.getChangeKey().get())
+ .current()
+ .file(filename)
+ .diff(revisionBase);
+ processFileDiff(filename, diff);
+ }
+ }
+ }
+
private boolean isChangeSetBased(ChangeSetData changeSetData) {
return !changeSetData.getForcedReviewLastPatchSet();
}
+ private void processFileDiff(String filename, DiffInfo diff) {
+ log.debug("FileDiff content processed: {}", filename);
+
+ GerritPatchSetFileDiff gerritPatchSetFileDiff = new GerritPatchSetFileDiff();
+ Optional.ofNullable(diff.metaA)
+ .ifPresent(
+ meta -> gerritPatchSetFileDiff.setMetaA(GerritClientPatchSet.toMeta(meta)));
+ Optional.ofNullable(diff.metaB)
+ .ifPresent(
+ meta -> gerritPatchSetFileDiff.setMetaB(GerritClientPatchSet.toMeta(meta)));
+ Optional.ofNullable(diff.content)
+ .ifPresent(
+ content ->
+ gerritPatchSetFileDiff.setContent(
+ content.stream()
+ .map(GerritClientPatchSet::toContent)
+ .collect(toList())));
+
+ // Initialize the reduced file diff for the Gerrit review with fields `meta_a` and `meta_b`
+ GerritReviewFileDiff gerritReviewFileDiff = new GerritReviewFileDiff(gerritPatchSetFileDiff.getMetaA(),
+ gerritPatchSetFileDiff.getMetaB());
+ FileDiffProcessed fileDiffProcessed = new FileDiffProcessed(config, isCommitMessage, gerritPatchSetFileDiff);
+ fileDiffsProcessed.put(filename, fileDiffProcessed);
+ gerritReviewFileDiff.setContent(fileDiffProcessed.getReviewDiffContent());
+ diffs.add(getNoEscapedGson().toJson(gerritReviewFileDiff));
+ }
+
+ protected static GerritFileDiff.Meta toMeta(DiffInfo.FileMeta input) {
+ GerritFileDiff.Meta meta = new GerritFileDiff.Meta();
+ meta.setContentType(input.contentType);
+ meta.setName(input.name);
+ return meta;
+ }
+
+ protected static GerritPatchSetFileDiff.Content toContent(DiffInfo.ContentEntry input) {
+ GerritPatchSetFileDiff.Content content = new GerritPatchSetFileDiff.Content();
+ content.a = input.a;
+ content.b = input.b;
+ content.ab = input.ab;
+ return content;
+ }
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/patch/code/CodeFinder.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/patch/code/CodeFinder.java
index 2267261..0f736cf 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/patch/code/CodeFinder.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/common/client/patch/code/CodeFinder.java
@@ -15,6 +15,7 @@
@Slf4j
public class CodeFinder {
private static final String PUNCTUATION_REGEX = "([()\\[\\]{}<>:;,?&+\\-*/%|=])";
+ private static final String BEGINNING_DIFF_REGEX = "(?:^|\n)[+\\-]";
private static final String ENDING_ELLIPSIS_REGEX = "\\.\\.\\.\\W*$";
private final String NON_PRINTING_REPLACEMENT;
@@ -58,7 +59,10 @@
}
private void updateCodePattern(ChatGptReplyItem replyItem) {
- String commentedCode = replyItem.getCodeSnippet().replaceAll(ENDING_ELLIPSIS_REGEX, "").trim();
+ String commentedCode = replyItem.getCodeSnippet()
+ .replaceAll(BEGINNING_DIFF_REGEX, "")
+ .replaceAll(ENDING_ELLIPSIS_REGEX, "")
+ .trim();
String commentedCodeRegex = Pattern.quote(commentedCode);
// Generalize the regex to capture snippets where existing sequences of non-printing chars have been modified
// from the original code
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
index 74f0cd3..e7c92c9 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateful/client/api/gerrit/GerritClientPatchSetStateful.java
@@ -14,12 +14,18 @@
import com.googlesource.gerrit.plugins.chatgpt.mode.stateful.client.api.git.GitRepoFiles;
import lombok.extern.slf4j.Slf4j;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static com.googlesource.gerrit.plugins.chatgpt.settings.Settings.COMMIT_MESSAGE_FILTER_OUT_PREFIXES;
@Slf4j
public class GerritClientPatchSetStateful extends GerritClientPatchSet implements IGerritClientPatchSet {
+ private static final Pattern EXTRACT_B_FILENAMES_FROM_PATCH_SET = Pattern.compile("^diff --git .*? b/(.*)$",
+ Pattern.MULTILINE);
+
private final GitRepoFiles gitRepoFiles;
private final PluginDataHandler pluginDataHandler;
@@ -42,12 +48,16 @@
ChatGptAssistant chatGptAssistant = new ChatGptAssistant(config, change, gitRepoFiles, pluginDataHandler);
chatGptAssistant.setupAssistant();
- return getPatchFromGerrit();
+ String formattedPatch = getPatchFromGerrit();
+ List<String> files = extractFilesFromPatch(formattedPatch);
+ retrieveFileDiff(change, files, revisionBase);
+
+ return formattedPatch;
}
private String getPatchFromGerrit() throws Exception {
try (ManualRequestContext requestContext = config.openRequestContext()) {
- String gerritPatch = config
+ String formattedPatch = config
.getGerritApi()
.changes()
.id(
@@ -57,19 +67,28 @@
.current()
.patch()
.asString();
- log.debug("Gerrit Patch retrieved: {}", gerritPatch);
+ log.debug("Formatted Patch retrieved: {}", formattedPatch);
- return filterPatch(gerritPatch);
+ return filterPatch(formattedPatch);
}
}
- private String filterPatch(String gerritPatch) {
+ private String filterPatch(String formattedPatch) {
// Remove Patch heading up to the Change-Id annotation
Pattern CONFIG_ID_HEADING_PATTERN = Pattern.compile(
"^.*?" + COMMIT_MESSAGE_FILTER_OUT_PREFIXES.get("CHANGE_ID") + " " + change.getChangeKey().get(),
Pattern.DOTALL
);
- return CONFIG_ID_HEADING_PATTERN.matcher(gerritPatch).replaceAll("");
+ return CONFIG_ID_HEADING_PATTERN.matcher(formattedPatch).replaceAll("");
+ }
+
+ private List<String> extractFilesFromPatch(String formattedPatch) {
+ Matcher extractFilenameMatcher = EXTRACT_B_FILENAMES_FROM_PATCH_SET.matcher(formattedPatch);
+ List<String> files = new ArrayList<>();
+ while (extractFilenameMatcher.find()) {
+ files.add(extractFilenameMatcher.group(1));
+ }
+ return files;
}
}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
index bbba27f..a757858 100644
--- a/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
+++ b/src/main/java/com/googlesource/gerrit/plugins/chatgpt/mode/stateless/client/api/gerrit/GerritClientPatchSetStateless.java
@@ -2,40 +2,27 @@
import com.google.inject.Inject;
import com.google.common.annotations.VisibleForTesting;
-import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.common.FileInfo;
import com.google.gerrit.server.account.AccountCache;
import com.google.gerrit.server.util.ManualRequestContext;
import com.googlesource.gerrit.plugins.chatgpt.config.Configuration;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritChange;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.api.gerrit.GerritClientPatchSet;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.client.patch.diff.FileDiffProcessed;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritFileDiff;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritPatchSetFileDiff;
-import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.api.gerrit.GerritReviewFileDiff;
import com.googlesource.gerrit.plugins.chatgpt.mode.common.model.data.ChangeSetData;
import com.googlesource.gerrit.plugins.chatgpt.mode.interfaces.client.api.gerrit.IGerritClientPatchSet;
import lombok.extern.slf4j.Slf4j;
import static java.util.stream.Collectors.toList;
-import java.util.ArrayList;
import java.util.List;
import java.util.Map;
-import java.util.Optional;
-
-import static com.googlesource.gerrit.plugins.chatgpt.utils.GsonUtils.getNoEscapedGson;
@Slf4j
public class GerritClientPatchSetStateless extends GerritClientPatchSet implements IGerritClientPatchSet {
- private final List<String> diffs;
- private boolean isCommitMessage;
-
@VisibleForTesting
@Inject
public GerritClientPatchSetStateless(Configuration config, AccountCache accountCache) {
super(config, accountCache);
- diffs = new ArrayList<>();
}
public String getPatchSet(ChangeSetData changeSetData, GerritChange change) throws Exception {
@@ -83,72 +70,9 @@
}
}
- private void processFileDiff(String filename, DiffInfo diff) {
- log.debug("FileDiff content processed: {}", filename);
-
- GerritPatchSetFileDiff gerritPatchSetFileDiff = new GerritPatchSetFileDiff();
- Optional.ofNullable(diff.metaA)
- .ifPresent(
- meta -> gerritPatchSetFileDiff.setMetaA(GerritClientPatchSetStateless.toMeta(meta)));
- Optional.ofNullable(diff.metaB)
- .ifPresent(
- meta -> gerritPatchSetFileDiff.setMetaB(GerritClientPatchSetStateless.toMeta(meta)));
- Optional.ofNullable(diff.content)
- .ifPresent(
- content ->
- gerritPatchSetFileDiff.setContent(
- content.stream()
- .map(GerritClientPatchSetStateless::toContent)
- .collect(toList())));
-
- // Initialize the reduced file diff for the Gerrit review with fields `meta_a` and `meta_b`
- GerritReviewFileDiff gerritReviewFileDiff = new GerritReviewFileDiff(gerritPatchSetFileDiff.getMetaA(),
- gerritPatchSetFileDiff.getMetaB());
- FileDiffProcessed fileDiffProcessed = new FileDiffProcessed(config, isCommitMessage, gerritPatchSetFileDiff);
- fileDiffsProcessed.put(filename, fileDiffProcessed);
- gerritReviewFileDiff.setContent(fileDiffProcessed.getReviewDiffContent());
- diffs.add(getNoEscapedGson().toJson(gerritReviewFileDiff));
- }
-
private String getFileDiffsJson(GerritChange change, List<String> files, int revisionBase) throws Exception {
- List<String> enabledFileExtensions = config.getEnabledFileExtensions();
- try (ManualRequestContext requestContext = config.openRequestContext()) {
- for (String filename : files) {
- isCommitMessage = filename.equals("/COMMIT_MSG");
- if (!isCommitMessage && (filename.lastIndexOf(".") < 1 ||
- !enabledFileExtensions.contains(filename.substring(filename.lastIndexOf("."))))) {
- continue;
- }
- DiffInfo diff =
- config
- .getGerritApi()
- .changes()
- .id(
- change.getProjectName(),
- change.getBranchNameKey().shortName(),
- change.getChangeKey().get())
- .current()
- .file(filename)
- .diff(revisionBase);
- processFileDiff(filename, diff);
- }
- }
+ retrieveFileDiff(change, files, revisionBase);
diffs.add(String.format("{\"changeId\": \"%s\"}", change.getFullChangeId()));
return "[" + String.join(",", diffs) + "]\n";
}
-
- private static GerritFileDiff.Meta toMeta(DiffInfo.FileMeta input) {
- GerritFileDiff.Meta meta = new GerritFileDiff.Meta();
- meta.setContentType(input.contentType);
- meta.setName(input.name);
- return meta;
- }
-
- private static GerritPatchSetFileDiff.Content toContent(DiffInfo.ContentEntry input) {
- GerritPatchSetFileDiff.Content content = new GerritPatchSetFileDiff.Content();
- content.a = input.a;
- content.b = input.b;
- content.ab = input.ab;
- return content;
- }
}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
index 55f0ac8..79e15ea 100644
--- a/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
+++ b/src/test/java/com/googlesource/gerrit/plugins/chatgpt/ChatGptReviewStatefulTest.java
@@ -2,7 +2,9 @@
import com.github.tomakehurst.wiremock.client.WireMock;
import com.google.common.net.HttpHeaders;
+import com.google.gerrit.extensions.api.changes.FileApi;
import com.google.gerrit.extensions.api.changes.ReviewInput;
+import com.google.gerrit.extensions.common.DiffInfo;
import com.google.gerrit.extensions.restapi.BinaryResult;
import com.google.gerrit.extensions.restapi.RestApiException;
import com.google.gson.JsonObject;
@@ -135,6 +137,11 @@
.setContentType("text/plain")
.setContentLength(formattedPatchContent.length());
when(revisionApiMock.patch()).thenReturn(binaryResult);
+
+ FileApi testFileMock = mock(FileApi.class);
+ when(revisionApiMock.file("test_file_1.py")).thenReturn(testFileMock);
+ DiffInfo testFileDiff = readTestFileToClass("__files/stateful/gerritPatchSetDiffTestFile.json", DiffInfo.class);
+ when(testFileMock.diff(0)).thenReturn(testFileDiff);
}
protected void initComparisonContent() {
diff --git a/src/test/resources/__files/stateful/gerritPatchSetDiffTestFile.json b/src/test/resources/__files/stateful/gerritPatchSetDiffTestFile.json
new file mode 100644
index 0000000..151f0a9
--- /dev/null
+++ b/src/test/resources/__files/stateful/gerritPatchSetDiffTestFile.json
@@ -0,0 +1,78 @@
+{
+ "meta_a": {
+ "name": "test_file_1.py",
+ "content_type": "text/x-python",
+ "lines": 43
+ },
+ "meta_b": {
+ "name": "test_file_1.py",
+ "content_type": "text/x-python",
+ "lines": 43
+ },
+ "change_type": "MODIFIED",
+ "diff_header": [
+ "diff --git a/test_file_1.py b/test_file_1.py",
+ "index 95ab5e7..e368bb0 100644",
+ "--- a/test_file_1.py",
+ "+++ b/test_file_1.py"
+ ],
+ "content": [
+ {
+ "ab": [
+ "from types import Any, Callable, Type, Union",
+ "",
+ "__all__ = [\"importclass\", \"preprocess_classes\", \"TypeClassOrPath\"]",
+ "",
+ "TypeClassOrPath = Union[Type, str]",
+ "",
+ "",
+ "def importclass(",
+ " module_name: str,",
+ " class_name: Union[str, None] = None",
+ ") -> Type:",
+ " \"\"\"",
+ " Dynamically import a class from a specified module.",
+ "",
+ " :param module_name: The name of the module to import.",
+ " :param class_name: The name of the class in the module to import. Defaults to None.",
+ " :return: The dynamically imported class.",
+ " \"\"\"",
+ " if not class_name:"
+ ]
+ },
+ {
+ "a": [
+ " module_name, class_name = module_name.rsplit('.', 1)"
+ ],
+ "b": [
+ " module_name, class_name = module_name.rsplit('.', 2)"
+ ],
+ "common": true
+ },
+ {
+ "ab": [
+ " loaded_module = importclass(module_name, fromlist=[class_name])",
+ " return getattr(loaded_module, class_name)",
+ "",
+ "",
+ "def preprocess_classes(func: Callable) -> Callable:",
+ " \"\"\"Decorator to convert dot-notated class paths into strings from positional arguments.\"\"\"",
+ " def __preprocess_classes_wrapper(*all_classes: TypeClassOrPath, **kwargs: Any) -> Any:",
+ " \"\"\"",
+ " Dynamically import classes if they are passed as strings.",
+ "",
+ " :param all_classes: A variable number of class paths (strings or actual types).",
+ " :param kwargs: Any keyword arguments to pass to the decorated function.",
+ " :return: The result of the decorated function.",
+ " \"\"\"",
+ " classes_processed = (",
+ " class_id if isinstance(class_id, type)",
+ " else importclass(class_id)",
+ " for class_id in all_classes",
+ " )",
+ " return func(*classes_processed, *kwargs),",
+ " return __preprocess_classes_wrapper"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/test/resources/__files/stateful/gerritPatchSetFiles.json b/src/test/resources/__files/stateful/gerritPatchSetFiles.json
new file mode 100644
index 0000000..75320cb
--- /dev/null
+++ b/src/test/resources/__files/stateful/gerritPatchSetFiles.json
@@ -0,0 +1,8 @@
+{
+ "test_file_1.py":{
+ "old_mode":33188,
+ "new_mode":33188,
+ "lines_inserted":1,
+ "lines_deleted":1
+ }
+}
\ No newline at end of file