1+ package cc.unitmesh.git.mcp
2+
3+ import cc.unitmesh.devti.mcp.host.AbstractMcpTool
4+ import cc.unitmesh.devti.mcp.host.NoArgs
5+ import cc.unitmesh.devti.mcp.host.Response
6+ import com.intellij.compiler.cache.git.GitCommitsIterator
7+ import com.intellij.openapi.project.Project
8+ import com.intellij.openapi.project.guessProjectDir
9+ import com.intellij.openapi.vcs.ProjectLevelVcsManager
10+ import com.intellij.openapi.vcs.changes.ChangeListManager
11+ import com.intellij.openapi.vfs.toNioPathOrNull
12+ import git4idea.history.GitHistoryUtils
13+ import git4idea.repo.GitRepositoryManager
14+ import kotlinx.serialization.Serializable
15+ import kotlin.io.path.Path
16+
17+ @Serializable
18+ data class CommitQuery (val text : String )
19+
20+ class FindCommitByTextTool : AbstractMcpTool <CommitQuery >() {
21+ override val name: String = " find_commit_by_message"
22+ override val description: String = """
23+ Searches for a commit based on the provided text or keywords in the project history.
24+ Useful for finding specific change sets or code modifications by commit messages or diff content.
25+ Takes a query parameter and returns the matching commit information.
26+ Returns matched commit hashes as a JSON array.
27+ """
28+
29+ override fun handle (project : Project , args : CommitQuery ): Response {
30+ val queryText = args.text
31+ val matchingCommits = mutableListOf<String >()
32+
33+ try {
34+ val vcs = ProjectLevelVcsManager .getInstance(project).allVcsRoots
35+ .mapNotNull { it.path }
36+
37+ if (vcs.isEmpty()) {
38+ return Response (" Error: No VCS configured for this project" )
39+ }
40+
41+ // Iterate over each VCS root to search for commits
42+ vcs.forEach { vcsRoot ->
43+ val repository = GitRepositoryManager .getInstance(project).getRepositoryForRoot(vcsRoot)
44+ ? : return @forEach
45+
46+ val gitLog = GitHistoryUtils .history(project, repository.root)
47+
48+ gitLog.forEach { commit ->
49+ if (commit.fullMessage.contains(queryText, ignoreCase = true )) {
50+ matchingCommits.add(commit.id.toString())
51+ }
52+ }
53+ }
54+
55+ // Check if any matches were found
56+ return if (matchingCommits.isNotEmpty()) {
57+ Response (matchingCommits.joinToString(prefix = " [" , postfix = " ]" , separator = " ," ) { " \" $it \" " })
58+ } else {
59+ Response (" No commits found matching the query: $queryText " )
60+ }
61+
62+ } catch (e: Exception ) {
63+ // Handle any errors that occur during the search
64+ return Response (" Error while searching commits: ${e.message} " )
65+ }
66+ return Response (" Feature not yet implemented" )
67+ }
68+ }
69+
70+ class GetVcsStatusTool : AbstractMcpTool <NoArgs >() {
71+ override val name: String = " get_project_vcs_status"
72+ override val description: String = """
73+ Retrieves the current version control status of files in the project.
74+ Use this tool to get information about modified, added, deleted, and moved files in your VCS (e.g., Git).
75+ Returns a JSON-formatted list of changed files, where each entry contains:
76+ - path: The file path relative to project root
77+ - type: The type of change (e.g., MODIFICATION, ADDITION, DELETION, MOVED)
78+ Returns an empty list ([]) if no changes are detected or VCS is not configured.
79+ Returns error "project dir not found" if project directory cannot be determined.
80+ Note: Works with any VCS supported by the IDE, but is most commonly used with Git
81+ """
82+
83+ override fun handle (project : Project , args : NoArgs ): Response {
84+ val projectDir = project.guessProjectDir()?.toNioPathOrNull()
85+ ? : return Response (error = " project dir not found" )
86+
87+ val changeListManager = ChangeListManager .getInstance(project)
88+ val changes = changeListManager.allChanges
89+
90+ return Response (changes.mapNotNull { change ->
91+ val absolutePath = change.virtualFile?.path ? : change.afterRevision?.file?.path
92+ val changeType = change.type
93+
94+ if (absolutePath != null ) {
95+ try {
96+ val relativePath = projectDir.relativize(Path (absolutePath)).toString()
97+ relativePath to changeType
98+ } catch (e: IllegalArgumentException ) {
99+ null // Skip files outside project directory
100+ }
101+ } else {
102+ null
103+ }
104+ }.joinToString(" ,\n " , prefix = " [" , postfix = " ]" ) {
105+ """ {"path": "${it.first} ", "type": "${it.second} "}"""
106+ })
107+ }
108+ }
0 commit comments