Skip to content

Commit

Permalink
fix: commit message problem (#49)
Browse files Browse the repository at this point in the history
Co-authored-by: 刘海鹏 <[email protected]>
  • Loading branch information
liuhaipenggg and 刘海鹏 authored Jul 16, 2024
1 parent 7244db7 commit 08fa9a5
Show file tree
Hide file tree
Showing 4 changed files with 147 additions and 63 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,26 @@

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.diff.impl.patch.FilePatch;
import com.intellij.openapi.diff.impl.patch.IdeaTextPatchBuilder;
import com.intellij.openapi.diff.impl.patch.UnifiedDiffWriter;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.EditorEx;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vcs.FilePath;
import com.intellij.openapi.vcs.VcsDataKeys;
import com.intellij.openapi.vcs.changes.ui.ChangesBrowserBase;
import com.intellij.openapi.vcs.changes.ui.CommitDialogChangesBrowser;
import com.intellij.openapi.vcs.changes.Change;
import com.intellij.openapi.vcs.changes.CurrentContentRevision;
import com.intellij.openapi.vcs.ui.CommitMessage;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.util.ObjectUtils;
import com.intellij.vcs.commit.AbstractCommitWorkflowHandler;
import com.zhongan.devpilot.DevPilotIcons;
import com.zhongan.devpilot.actions.notifications.DevPilotNotification;
import com.zhongan.devpilot.constant.DefaultConst;
Expand All @@ -18,27 +30,39 @@
import com.zhongan.devpilot.integrations.llms.entity.DevPilotChatCompletionRequest;
import com.zhongan.devpilot.integrations.llms.entity.DevPilotChatCompletionResponse;
import com.zhongan.devpilot.integrations.llms.entity.DevPilotMessage;
import com.zhongan.devpilot.settings.state.LanguageSettingsState;
import com.zhongan.devpilot.util.DevPilotMessageBundle;
import com.zhongan.devpilot.util.DocumentUtil;
import com.zhongan.devpilot.util.MessageUtil;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.jetbrains.annotations.NotNull;

import static com.intellij.util.ObjectUtils.tryCast;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;
import git4idea.repo.GitRepository;
import git4idea.repo.GitRepositoryManager;

public class GenerateGitCommitMessageAction extends AnAction {

private static final Logger log = Logger.getInstance(GenerateGitCommitMessageAction.class);

public GenerateGitCommitMessageAction() {
super(DevPilotMessageBundle.get("devpilot.action.changesview.generateCommit"), DevPilotMessageBundle.get("devpilot.action.changesview.generateCommit"), DevPilotIcons.SYSTEM_ICON);
}

@Override
public void update(@NotNull AnActionEvent e) {
super.update(e);
Presentation presentation = e.getPresentation();
presentation.setText(DevPilotMessageBundle.get("devpilot.action.changesview.generateCommit"));
presentation.setDescription(DevPilotMessageBundle.get("devpilot.action.changesview.generateCommit"));
presentation.setIcon(DevPilotIcons.SYSTEM_ICON);
}

@Override
Expand All @@ -49,70 +73,124 @@ public void actionPerformed(@NotNull AnActionEvent e) {
}

try {
String gitDiff = getGitDiff(project, getReferencedFilePaths(e));

if (DocumentUtil.experienceEstimatedTokens(gitDiff) + DocumentUtil.experienceEstimatedTokens(PromptConst.GENERATE_COMMIT) > DefaultConst.GPT_35_TOKEN_MAX_LENGTH) {
DevPilotNotification.warn(DevPilotMessageBundle.get("devpilot.changesview.tokens.estimation.overflow"));
List<Change> changeList = getReferencedFilePaths(e);
String diff = getGitDiff(e, changeList);
if (StringUtils.isEmpty(diff)) {
DevPilotNotification.info("no changes selected");
return;
}

var commitMessage = tryCast(e.getData(VcsDataKeys.COMMIT_MESSAGE_CONTROL), CommitMessage.class);
var commitMessage = ObjectUtils.tryCast(e.getData(VcsDataKeys.COMMIT_MESSAGE_CONTROL), CommitMessage.class);
var editor = commitMessage != null ? commitMessage.getEditorField().getEditor() : null;
if (editor != null) {
((EditorEx) editor).setCaretVisible(false);

DevPilotMessage userMessage = MessageUtil.createUserMessage(gitDiff, "-1");
DevPilotChatCompletionRequest devPilotChatCompletionRequest = new DevPilotChatCompletionRequest();
devPilotChatCompletionRequest.getMessages().add(MessageUtil.createSystemMessage(PromptConst.GENERATE_COMMIT));
devPilotChatCompletionRequest.getMessages().add(userMessage);
devPilotChatCompletionRequest.setStream(Boolean.FALSE);

var llmProvider = new LlmProviderFactory().getLlmProvider(project);
DevPilotChatCompletionResponse result = llmProvider.chatCompletionSync(devPilotChatCompletionRequest);

if (result.isSuccessful()) {
var application = ApplicationManager.getApplication();
application.invokeLater(() ->
application.runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () ->
editor.getDocument().setText(result.getContent()))));
} else {
DevPilotNotification.warn(result.getContent());
}

}
ApplicationManager.getApplication().invokeLater(() ->
ApplicationManager.getApplication().runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () -> {
if (editor != null) {
editor.getDocument().setText(" ");
}
})));
generateCommitMessage(project, diff, editor);
} catch (Exception ex) {
DevPilotNotification.warn("Exception occurred while generating commit message");
}
}

private @NotNull List<String> getReferencedFilePaths(AnActionEvent event) {
var changesBrowserBase = event.getData(ChangesBrowserBase.DATA_KEY);
if (changesBrowserBase == null) {
return List.of();
private void generateCommitMessage(Project project, String diff, Editor editor) {
if (DocumentUtil.experienceEstimatedTokens(diff) + DocumentUtil.experienceEstimatedTokens(PromptConst.GENERATE_COMMIT) > DefaultConst.GPT_35_TOKEN_MAX_LENGTH) {
DevPilotNotification.warn(DevPilotMessageBundle.get("devpilot.changesview.tokens.estimation.overflow"));
}

var includedChanges = ((CommitDialogChangesBrowser) changesBrowserBase).getIncludedChanges();
return includedChanges.stream()
.filter(item -> item.getVirtualFile() != null)
.map(item -> item.getVirtualFile().getPath())
.collect(toList());
new Task.Backgroundable(project, DevPilotMessageBundle.get("devpilot.commit.tip"), true) {
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
if (editor != null) {
((EditorEx) editor).setCaretVisible(false);

String prompt = constructPrompt(PromptConst.GENERATE_COMMIT);
String diffPrompt = PromptConst.DIFF_PREVIEW.replace("{diff}", diff);
DevPilotMessage userMessage = MessageUtil.createUserMessage(diffPrompt, "-1");
DevPilotChatCompletionRequest devPilotChatCompletionRequest = new DevPilotChatCompletionRequest();
devPilotChatCompletionRequest.getMessages().add(MessageUtil.createSystemMessage(prompt));
devPilotChatCompletionRequest.getMessages().add(userMessage);
devPilotChatCompletionRequest.setStream(Boolean.FALSE);
var llmProvider = new LlmProviderFactory().getLlmProvider(project);
DevPilotChatCompletionResponse result = llmProvider.chatCompletionSync(devPilotChatCompletionRequest);

if (result.isSuccessful()) {
var application = ApplicationManager.getApplication();
application.invokeLater(() ->
application.runWriteAction(() ->
WriteCommandAction.runWriteCommandAction(project, () ->
editor.getDocument().setText(result.getContent()))));
} else {
DevPilotNotification.warn(result.getContent());
}
}
}
}.queue();
}

private Process createGitDiffProcess(String projectPath, List<String> filePaths) throws IOException {
var command = new ArrayList<String>();
command.add("git");
command.add("diff");
command.addAll(filePaths);
private @NotNull List<Change> getReferencedFilePaths(AnActionEvent event) {

var processBuilder = new ProcessBuilder(command);
processBuilder.directory(new File(projectPath));
return processBuilder.start();
var workflowHandler = event.getDataContext().getData(VcsDataKeys.COMMIT_WORKFLOW_HANDLER);
List<Change> changeList = new ArrayList<>();
if (workflowHandler instanceof AbstractCommitWorkflowHandler) {
List<Change> includedChanges = ((AbstractCommitWorkflowHandler<?, ?>) workflowHandler).getUi().getIncludedChanges();
if (!includedChanges.isEmpty()) {
changeList.addAll(includedChanges);
}
List<FilePath> filePaths = ((AbstractCommitWorkflowHandler<?, ?>) workflowHandler).getUi().getIncludedUnversionedFiles();
if (!filePaths.isEmpty()) {
for (FilePath filePath : filePaths) {
Change change = new Change(null, new CurrentContentRevision(filePath));
changeList.add(change);
}
}
}
return changeList;
}

private String getGitDiff(Project project, List<String> filePaths) throws IOException {
var process = createGitDiffProcess(project.getBasePath(), filePaths);
var reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
return reader.lines().collect(joining("\n"));
private String getGitDiff(AnActionEvent event, List<Change> includedChanges) {
if (includedChanges.isEmpty()) {
return null;
}
StringBuilder result = new StringBuilder();
Project project = event.getProject();
if (project == null) {
return null;
}
GitRepositoryManager gitRepositoryManager = GitRepositoryManager.getInstance(project);
Map<GitRepository, List<Change>> changesByRepository = new HashMap<>();
for (Change change : includedChanges) {
VirtualFile file = change.getVirtualFile();
if (file != null) {
GitRepository repository = gitRepositoryManager.getRepositoryForFileQuick(file);
changesByRepository.computeIfAbsent(repository, k -> new ArrayList<>()).add(change);
}
}

changesByRepository.forEach((gitRepository, changes) -> {
if (gitRepository != null) {
try {
if (project.getBasePath() == null) {
return;
}
List<FilePatch> filePatches = IdeaTextPatchBuilder.buildPatch(project, changes, Path.of(project.getBasePath()), false, true);
StringWriter stringWriter = new StringWriter();
stringWriter.write("Repository: " + gitRepository.getRoot().getPath() + "\n");

UnifiedDiffWriter.write(project, filePatches, stringWriter, "\n", null);

result.append(stringWriter);
} catch (Exception e) {
log.info(e.getMessage());
}
}
});
return result.toString();
}

public String constructPrompt(String promptContent) {
Integer languageIndex = LanguageSettingsState.getInstance().getLanguageIndex();
Locale locale = languageIndex == 0 ? Locale.ENGLISH : Locale.SIMPLIFIED_CHINESE;
return promptContent.replace("{locale}", locale.getDisplayLanguage());
}
}
6 changes: 4 additions & 2 deletions src/main/java/com/zhongan/devpilot/constant/PromptConst.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ private PromptConst() {

public static final String ANSWER_IN_CHINESE = "\n\n请用中文回答";

public static final String GENERATE_COMMIT = "Summarize the git diff with a concise and descriptive commit message. Adopt the imperative mood, present tense, active voice, and include relevant verbs. Remember that your entire response will be directly used as the git commit message.";

public static final String GENERATE_COMMIT = "Write a clean and comprehensive commit message that accurately summarizes the changes made in the given `git diff` output, following the best practices and conventional commit convention. Remember that your entire response will be directly used as the git commit message. The response should be in the language {locale}.";

public static final String DIFF_PREVIEW = "This is the `git diff`:\n" + "{diff}";

public final static String MOCK_WEB_MVC = "please use MockMvc to mock web requests, ";

}
2 changes: 2 additions & 0 deletions src/main/resources/messages/devpilot_en.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ devpilot.action.edit.settings=Edit Settings...
devpilot.chatWindow.context.overflow=This model's maximum context length is 16K tokens.
devpilot.chatWindow.response.null=Nothing to see here.
devpilot.commit.tip=Generating commit message
devpilot.alter.file.exist=File already exists.
devpilot.alter.file.not.exist=File does not exist.
devpilot.alter.code.not.selected=Please select the code block first.
Expand Down
2 changes: 2 additions & 0 deletions src/main/resources/messages/devpilot_zh.properties
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ devpilot.action.edit.settings=\u7F16\u8F91\u8BBE\u7F6E
devpilot.chatWindow.context.overflow=\u6A21\u578B\u4E0A\u4E0B\u6587\u6700\u5927\u4E3A 16k tokens.
devpilot.chatWindow.response.null=\u65E0\u54CD\u5E94\uFF0C\u8BF7\u91CD\u8BD5

devpilot.commit.tip=\u751f\u6210\u63d0\u4ea4\u4fe1\u606f\u4e2d

devpilot.alter.file.exist=\u6587\u4EF6\u5DF2\u5B58\u5728
devpilot.alter.file.not.exist=\u6587\u4EF6\u4E0D\u5B58\u5728
devpilot.alter.code.not.selected=\u8BF7\u5148\u9009\u62E9\u4EE3\u7801\u5757
Expand Down

0 comments on commit 08fa9a5

Please sign in to comment.