Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions gradle/wrapper/gradle-wrapper.jar
Git LFS file not shown
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,7 @@ data class AiTestSettingsModel(
val sonarQubePasswordEnv: String = "",
val sonarQubeTargetCoverage: String = "80",
val sonarQubeMaxFiles: String = "5",
val suppressedGlobalPrompts: List<String> = emptyList()
val suppressedGlobalPrompts: List<String> = emptyList(),
val skillProfilesYaml: String = "",
val suppressedGlobalSkills: List<String> = emptyList()
)

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsDataKeys
import com.intellij.openapi.vcs.changes.Change

class GenerateCommitMessageAction : AnAction("Generate Commit Message") {

Expand All @@ -18,17 +20,22 @@ class GenerateCommitMessageAction : AnAction("Generate Commit Message") {
Notifications.error(project, "Generate Commit Message", "Commit message box is not available.")
return
}

val workflowUi = e.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
val includedChanges = getIncludedChanges(workflowUi)
val includedUnversioned = getIncludedUnversionedFiles(workflowUi)

val selectedPrompt = LlmSettingsLoader.loadConfig(project).prompts.profiles.commitMessage.selected
ButtonUsageReportService.getInstance(project).recordPromptUsage("commit.message", selectedPrompt)

ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Generating Commit Message", false) {
override fun run(indicator: ProgressIndicator) {
try {
indicator.text = "Collecting git diff..."
val diff = GitDiffProvider.getDiff(project)
val diff = GitDiffProvider.getDiffForSelectedChanges(project, includedChanges, includedUnversioned)

if (diff.isBlank()) {
Notifications.error(project, "Generate Commit Message", "No local git changes found.")
Notifications.error(project, "Generate Commit Message", "No selected changes to commit.")
return
}

Expand All @@ -37,7 +44,6 @@ class GenerateCommitMessageAction : AnAction("Generate Commit Message") {

ApplicationManager.getApplication().invokeLater {
commitMessageUi.setCommitMessage(message.trim())
// commitMessageUi.focus()
}
} catch (ex: Exception) {
Notifications.error(
Expand All @@ -54,4 +60,26 @@ class GenerateCommitMessageAction : AnAction("Generate Commit Message") {
e.presentation.isEnabledAndVisible =
e.project != null && e.getData(VcsDataKeys.COMMIT_MESSAGE_CONTROL) != null
}

@Suppress("UNCHECKED_CAST")
private fun getIncludedChanges(workflowUi: Any?): List<Change> {
if (workflowUi == null) return emptyList()
return try {
val method = workflowUi.javaClass.getMethod("getIncludedChanges")
(method.invoke(workflowUi) as? List<*>)?.filterIsInstance<Change>().orEmpty()
} catch (_: Exception) {
emptyList()
}
}

@Suppress("UNCHECKED_CAST")
private fun getIncludedUnversionedFiles(workflowUi: Any?): List<FilePath> {
if (workflowUi == null) return emptyList()
return try {
val method = workflowUi.javaClass.getMethod("getIncludedUnversionedFiles")
(method.invoke(workflowUi) as? List<*>)?.filterIsInstance<FilePath>().orEmpty()
} catch (_: Exception) {
emptyList()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.Task
import com.intellij.openapi.project.DumbAware
import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.VcsDataKeys
import com.intellij.openapi.vcs.changes.Change

class GenerateCommitMessagePromptMenuAction : ActionGroup("Generate Commit Message (Choose Prompt)", true), DumbAware {
init {
Expand Down Expand Up @@ -44,17 +46,21 @@ private class GenerateCommitMessageByPromptAction(
val project = e.project ?: return
val commitMessageUi = e.getData(VcsDataKeys.COMMIT_MESSAGE_CONTROL) ?: return

val workflowUi = e.getData(VcsDataKeys.COMMIT_WORKFLOW_UI)
val includedChanges = getIncludedChanges(workflowUi)
val includedUnversioned = getIncludedUnversionedFiles(workflowUi)

saveDefaultCommitPromptProfile(project, promptName)
ButtonUsageReportService.getInstance(project).recordPromptUsage("commit.message", promptName)

ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Generating Commit Message", false) {
override fun run(indicator: ProgressIndicator) {
try {
indicator.text = "Collecting git diff..."
val diff = GitDiffProvider.getDiff(project)
val diff = GitDiffProvider.getDiffForSelectedChanges(project, includedChanges, includedUnversioned)

if (diff.isBlank()) {
Notifications.error(project, "Generate Commit Message", "No local git changes found.")
Notifications.error(project, "Generate Commit Message", "No selected changes to commit.")
return
}

Expand All @@ -78,6 +84,28 @@ private class GenerateCommitMessageByPromptAction(
})
}

@Suppress("UNCHECKED_CAST")
private fun getIncludedChanges(workflowUi: Any?): List<Change> {
if (workflowUi == null) return emptyList()
return try {
val method = workflowUi.javaClass.getMethod("getIncludedChanges")
(method.invoke(workflowUi) as? List<*>)?.filterIsInstance<Change>().orEmpty()
} catch (_: Exception) {
emptyList()
}
}

@Suppress("UNCHECKED_CAST")
private fun getIncludedUnversionedFiles(workflowUi: Any?): List<FilePath> {
if (workflowUi == null) return emptyList()
return try {
val method = workflowUi.javaClass.getMethod("getIncludedUnversionedFiles")
(method.invoke(workflowUi) as? List<*>)?.filterIsInstance<FilePath>().orEmpty()
} catch (_: Exception) {
emptyList()
}
}

private fun saveDefaultCommitPromptProfile(project: Project, selectedName: String) {
val current = LlmSettingsLoader.loadSettingsModel(project)
if (current.commitPromptProfileDefault == selectedName) return
Expand Down
Original file line number Diff line number Diff line change
@@ -1,46 +1,63 @@
package org.openprojectx.ai.plugin

import com.intellij.openapi.project.Project
import com.intellij.openapi.vcs.FilePath
import com.intellij.openapi.vcs.changes.Change
import git4idea.repo.GitRepositoryManager
import java.io.File

object GitDiffProvider {

fun getDiff(project: Project): String {
val basePath = project.basePath ?: error("Project base path is unavailable")
fun getDiffForSelectedChanges(project: Project, changes: List<Change>, unversionedFiles: List<FilePath>): String {
val repo = GitRepositoryManager.getInstance(project).repositories.firstOrNull()
?: error("No Git repository found for project")
val repoRoot = repo.root.path

fun toRelativePath(absolutePath: String): String? {
val f = File(absolutePath)
val canonical = if (f.isAbsolute) f.canonicalPath else File(repoRoot, absolutePath).canonicalPath
return if (canonical != null && canonical.startsWith(repoRoot)) {
canonical.removePrefix(repoRoot).removePrefix("/").removePrefix("\\")
} else null
}

val process = ProcessBuilder(
"git",
"diff",
"--cached",
"--",
"."
val filePaths = changes.mapNotNull { change ->
val revision = change.afterRevision ?: change.beforeRevision
revision?.file?.path?.let { toRelativePath(it) }
} + unversionedFiles.mapNotNull { it.path?.let { p -> toRelativePath(p) } }
val uniquePaths = filePaths.distinct()

if (uniquePaths.isEmpty()) return ""

val stagedProcess = ProcessBuilder(
mutableListOf("git", "diff", "--cached", "--") + uniquePaths
)
.directory(File(repo.root.path))
.redirectErrorStream(true)
.start()

val staged = process.inputStream.bufferedReader().use { it.readText() }
process.waitFor()
val staged = stagedProcess.inputStream.bufferedReader().use { it.readText() }
stagedProcess.waitFor()

if (staged.isNotBlank()) return staged

val workingTreeProcess = ProcessBuilder(
"git",
"diff",
"--",
"."
val unstagedProcess = ProcessBuilder(
mutableListOf("git", "diff", "--") + uniquePaths
)
.directory(File(repo.root.path))
.redirectErrorStream(true)
.start()

val unstaged = workingTreeProcess.inputStream.bufferedReader().use { it.readText() }
workingTreeProcess.waitFor()

return unstaged
val unstaged = unstagedProcess.inputStream.bufferedReader().use { it.readText() }
unstagedProcess.waitFor()

return buildString {
if (staged.isNotBlank()) {
appendLine(staged.trimEnd())
}
if (unstaged.isNotBlank()) {
if (isNotEmpty()) appendLine()
append(unstaged.trimEnd())
}
}
}


Expand Down
Loading
Loading