diff --git a/src/tools/repositories.ts b/src/tools/repositories.ts index 9f10e235..0acdd962 100644 --- a/src/tools/repositories.ts +++ b/src/tools/repositories.ts @@ -930,10 +930,11 @@ function configureRepoTools(server: McpServer, tokenProvider: () => Promise Promise { + async ({ + project, + repository, + fromCommit, + toCommit, + version, + versionType, + skip, + top, + includeLinks, + includeWorkItems, + searchText, + author, + authorEmail, + committer, + committerEmail, + fromDate, + toDate, + commitIds, + historySimplificationMode, + }) => { try { const connection = await connectionProvider(); const gitApi = await connection.getGitApi(); + // If specific commit IDs are provided, use getCommits with commit ID filtering + if (commitIds && commitIds.length > 0) { + const commits = []; + const batchSize = Math.min(top || 10, commitIds.length); + const startIndex = skip || 0; + const endIndex = Math.min(startIndex + batchSize, commitIds.length); + + // Process commits in the requested range + const requestedCommitIds = commitIds.slice(startIndex, endIndex); + + // Use getCommits for each commit ID to maintain consistency + for (const commitId of requestedCommitIds) { + try { + const searchCriteria: GitQueryCommitsCriteria = { + includeLinks: includeLinks, + includeWorkItems: includeWorkItems, + fromCommitId: commitId, + toCommitId: commitId, + }; + + const commitResults = await gitApi.getCommits(repository, searchCriteria, project, 0, 1); + + if (commitResults && commitResults.length > 0) { + commits.push(commitResults[0]); + } + } catch (error) { + // Log error but continue with other commits + console.warn(`Failed to retrieve commit ${commitId}: ${error instanceof Error ? error.message : String(error)}`); + // Add error information to result instead of failing completely + commits.push({ + commitId: commitId, + error: `Failed to retrieve: ${error instanceof Error ? error.message : String(error)}`, + }); + } + } + + return { + content: [{ type: "text", text: JSON.stringify(commits, null, 2) }], + }; + } + const searchCriteria: GitQueryCommitsCriteria = { fromCommitId: fromCommit, toCommitId: toCommit, @@ -959,6 +1031,26 @@ function configureRepoTools(server: McpServer, tokenProvider: () => Promise Promise commit.comment?.toLowerCase().includes(searchText.toLowerCase())); + } + + // Filter by author email if specified + if (authorEmail && filteredCommits) { + filteredCommits = filteredCommits.filter((commit) => commit.author?.email?.toLowerCase() === authorEmail.toLowerCase()); + } + + // Filter by committer if specified + if (committer && filteredCommits) { + filteredCommits = filteredCommits.filter( + (commit) => commit.committer?.name?.toLowerCase().includes(committer.toLowerCase()) || commit.committer?.email?.toLowerCase().includes(committer.toLowerCase()) + ); + } + + // Filter by committer email if specified + if (committerEmail && filteredCommits) { + filteredCommits = filteredCommits.filter((commit) => commit.committer?.email?.toLowerCase() === committerEmail.toLowerCase()); + } return { - content: [{ type: "text", text: JSON.stringify(commits, null, 2) }], + content: [{ type: "text", text: JSON.stringify(filteredCommits, null, 2) }], }; } catch (error) { return { diff --git a/test/src/tools/repositories.test.ts b/test/src/tools/repositories.test.ts index e1989fd5..3f8c8e4c 100644 --- a/test/src/tools/repositories.test.ts +++ b/test/src/tools/repositories.test.ts @@ -4244,4 +4244,100 @@ describe("repos tools", () => { expect(result.content[0].text).toBe(JSON.stringify(mockThread, null, 2)); }); }); + + describe("enhanced commit search functions", () => { + describe("repo_search_commits enhanced functionality", () => { + it("should search commits with enhanced filters", async () => { + configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); + + const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === REPO_TOOLS.search_commits); + if (!call) throw new Error("repo_search_commits tool not registered"); + const [, , , handler] = call; + + const mockCommits = [ + { + commitId: "abc123", + comment: "Fix bug in authentication", + author: { name: "John Doe", email: "john@example.com" }, + committer: { name: "John Doe", email: "john@example.com" }, + push: { date: "2023-01-01T00:00:00Z" }, + }, + ]; + mockGitApi.getCommits.mockResolvedValue(mockCommits); + + const params = { + project: "test-project", + repository: "test-repo", + searchText: "authentication", + author: "John Doe", + fromDate: "2023-01-01T00:00:00Z", + toDate: "2023-12-31T23:59:59Z", + top: 10, + }; + + const result = await handler(params); + + expect(mockGitApi.getCommits).toHaveBeenCalledWith( + "test-repo", + expect.objectContaining({ + author: "John Doe", + fromDate: "2023-01-01T00:00:00Z", + toDate: "2023-12-31T23:59:59Z", + }), + "test-project", + undefined, + 10 + ); + + expect(result.content[0].text).toBe(JSON.stringify(mockCommits, null, 2)); + }); + + it("should retrieve specific commits by IDs", async () => { + configureRepoTools(server, tokenProvider, connectionProvider, userAgentProvider); + + const call = (server.tool as jest.Mock).mock.calls.find(([toolName]) => toolName === REPO_TOOLS.search_commits); + if (!call) throw new Error("repo_search_commits tool not registered"); + const [, , , handler] = call; + + const mockCommit1 = { commitId: "abc123", comment: "First commit" }; + const mockCommit2 = { commitId: "def456", comment: "Second commit" }; + + mockGitApi.getCommits.mockResolvedValueOnce([mockCommit1]).mockResolvedValueOnce([mockCommit2]); + + const params = { + project: "test-project", + repository: "test-repo", + commitIds: ["abc123", "def456"], + top: 10, + }; + + const result = await handler(params); + + expect(mockGitApi.getCommits).toHaveBeenCalledTimes(2); + expect(mockGitApi.getCommits).toHaveBeenCalledWith( + "test-repo", + expect.objectContaining({ + fromCommitId: "abc123", + toCommitId: "abc123", + }), + "test-project", + 0, + 1 + ); + expect(mockGitApi.getCommits).toHaveBeenCalledWith( + "test-repo", + expect.objectContaining({ + fromCommitId: "def456", + toCommitId: "def456", + }), + "test-project", + 0, + 1 + ); + + const expectedCommits = [mockCommit1, mockCommit2]; + expect(result.content[0].text).toBe(JSON.stringify(expectedCommits, null, 2)); + }); + }); + }); });